lldap: automatic provision + system users + stalwart + whatever
This commit is contained in:
parent
4a0129585a
commit
82caf96d36
19 changed files with 405 additions and 285 deletions
|
@ -81,6 +81,11 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# setup lldap user for authelia that can send emails
|
||||||
|
services.lldap.provision.users = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||||
|
authelia = llib.mkProvisionUserSystem "authelia" config.age.secrets.authelia-smtp-password.path;
|
||||||
|
});
|
||||||
|
|
||||||
services.nginx.virtualHosts."${svc_domain}" = {
|
services.nginx.virtualHosts."${svc_domain}" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (lib) types;
|
|
||||||
|
|
||||||
cfg = config.mine.lldap_provision;
|
|
||||||
|
|
||||||
# helpers
|
|
||||||
_configFile = {
|
|
||||||
user_attributes = lib.mapAttrsToList (n: v: v) cfg.user_attributes;
|
|
||||||
group_attributes = lib.mapAttrsToList (n: v: v) cfg.group_attributes;
|
|
||||||
users = lib.mapAttrsToList (n: v: v // {
|
|
||||||
user_id = if v ? user_id then v.user_id else n;
|
|
||||||
}) cfg.users;
|
|
||||||
groups = lib.mapAttrsToList (n: v: v // {
|
|
||||||
display_name = if v ? display_name then v.display_name else n;
|
|
||||||
}) cfg.groups;
|
|
||||||
};
|
|
||||||
configFile = (pkgs.formats.json {}).generate "lldap-declarative.json" _configFile;
|
|
||||||
|
|
||||||
# opts
|
|
||||||
optsAttributes = { name, config, ... }: {
|
|
||||||
options = {
|
|
||||||
name = lib.mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = name;
|
|
||||||
description = "The name of the attribute";
|
|
||||||
};
|
|
||||||
|
|
||||||
attributeType = lib.mkOption {
|
|
||||||
type = types.enum [ "STRING" "INTEGER" "JPEG_PHOTO" "DATE_TIME" ];
|
|
||||||
description = "Type of the attribute";
|
|
||||||
};
|
|
||||||
|
|
||||||
isList = lib.mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "Is this attribute a list (multiple values for this attribute)";
|
|
||||||
};
|
|
||||||
|
|
||||||
isEditable = lib.mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "Should the user be able to edit this value?";
|
|
||||||
};
|
|
||||||
|
|
||||||
isVisible = lib.mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "Should the user be able to see this value?";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
in {
|
|
||||||
options = {
|
|
||||||
mine.lldap_provision = {
|
|
||||||
enable = lib.mkEnableOption "LLDAP declarative setup";
|
|
||||||
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = config.services.lldap.settings.http_url;
|
|
||||||
description = "URL for the LLDAP instance";
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
username = lib.mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = "Username to use when signing into lldap";
|
|
||||||
};
|
|
||||||
|
|
||||||
passwordFile = lib.mkOption {
|
|
||||||
type = types.path;
|
|
||||||
description = "Path for the password file to authenticate the user";
|
|
||||||
};
|
|
||||||
|
|
||||||
group_attributes = lib.mkOption {
|
|
||||||
type = types.attrsOf (types.submodule optsAttributes);
|
|
||||||
default = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
user_attributes = lib.mkOption {
|
|
||||||
type = types.attrsOf (types.submodule optsAttributes);
|
|
||||||
default = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
users = lib.mkOption {
|
|
||||||
type = types.attrsOf types.anything;
|
|
||||||
default = {};
|
|
||||||
example = {
|
|
||||||
user1 = {
|
|
||||||
password = "env:LLDAP_USER1_PASSWORD";
|
|
||||||
mail = "something@something.dk";
|
|
||||||
|
|
||||||
foo = "value for user attribute foo";
|
|
||||||
bar = "value for user attribute bar";
|
|
||||||
groups = [ "group1" "group2" ];
|
|
||||||
};
|
|
||||||
user2 = { user_id = "superuserawesome"; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
groups = lib.mkOption {
|
|
||||||
type = types.attrsOf types.anything;
|
|
||||||
default = {};
|
|
||||||
example = {
|
|
||||||
base_member = {
|
|
||||||
foo = "value for group attribute foo";
|
|
||||||
bar = "value for group attribute bar";
|
|
||||||
};
|
|
||||||
system = {
|
|
||||||
display_name = "system_service - override display_name";
|
|
||||||
};
|
|
||||||
testgroup = {};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
systemd.services.lldapsetup = {
|
|
||||||
description = "setup lldap declaratively";
|
|
||||||
wantedBy = [ config.systemd.services.lldap.name "multi-user.target" ];
|
|
||||||
after = [ config.systemd.services.lldap.name ];
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
LLDAP_URL = cfg.url;
|
|
||||||
LLDAP_USERNAME = cfg.username;
|
|
||||||
LLDAP_PASSWORD = "file:${cfg.passwordFile}";
|
|
||||||
};
|
|
||||||
|
|
||||||
path = with pkgs; [
|
|
||||||
lldap
|
|
||||||
];
|
|
||||||
|
|
||||||
script = let
|
|
||||||
pythonEnv = pkgs.python3.withPackages(ps: with ps; [ gql aiohttp requests ]);
|
|
||||||
pythonDir = pkgs.runCommand "lldap-bootstrap" {} ''
|
|
||||||
mkdir -p $out/bootstrap
|
|
||||||
cp -a ${./.}/. $out/bootstrap
|
|
||||||
'';
|
|
||||||
in ''
|
|
||||||
cd ${pythonDir}
|
|
||||||
${pythonEnv}/bin/python -m bootstrap.main ${configFile}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -19,10 +19,88 @@ index 6f42473..b3746a1 100644
|
||||||
&config,
|
&config,
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
whoamiPatch = pkgs.writeText "lldap-whoami.patch" ''
|
||||||
|
diff --git a/server/src/infra/ldap_handler.rs b/server/src/infra/ldap_handler.rs
|
||||||
|
index 7257c31..feda03c 100644
|
||||||
|
--- a/server/src/infra/ldap_handler.rs
|
||||||
|
+++ b/server/src/infra/ldap_handler.rs
|
||||||
|
@@ -26,7 +26,7 @@ use ldap3_proto::proto::{
|
||||||
|
LdapDerefAliases, LdapExtendedRequest, LdapExtendedResponse, LdapFilter, LdapModify,
|
||||||
|
LdapModifyRequest, LdapModifyType, LdapOp, LdapPartialAttribute, LdapPasswordModifyRequest,
|
||||||
|
LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest, LdapSearchResultEntry,
|
||||||
|
- LdapSearchScope,
|
||||||
|
+ LdapSearchScope, OID_PASSWORD_MODIFY, OID_WHOAMI,
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tracing::{debug, instrument, warn};
|
||||||
|
@@ -181,7 +181,7 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
|
||||||
|
LdapPartialAttribute {
|
||||||
|
atype: "supportedExtension".to_string(),
|
||||||
|
// Password modification extension.
|
||||||
|
- vals: vec![b"1.3.6.1.4.1.4203.1.11.1".to_vec()],
|
||||||
|
+ vals: vec![OID_PASSWORD_MODIFY.as_bytes().to_vec()],
|
||||||
|
},
|
||||||
|
LdapPartialAttribute {
|
||||||
|
atype: "supportedControl".to_string(),
|
||||||
|
@@ -204,6 +204,11 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
|
||||||
|
atype: "isGlobalCatalogReady".to_string(),
|
||||||
|
vals: vec![b"false".to_vec()],
|
||||||
|
},
|
||||||
|
+ LdapPartialAttribute {
|
||||||
|
+ atype: "supportedExtension".to_string(),
|
||||||
|
+ // whoami extension.
|
||||||
|
+ vals: vec![OID_WHOAMI.as_bytes().to_vec()],
|
||||||
|
+ },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -413,16 +418,33 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||||
|
|
||||||
|
#[instrument(skip_all, level = "debug")]
|
||||||
|
async fn do_extended_request(&mut self, request: &LdapExtendedRequest) -> Vec<LdapOp> {
|
||||||
|
- match LdapPasswordModifyRequest::try_from(request) {
|
||||||
|
- Ok(password_request) => self
|
||||||
|
+ if let Ok(password_request) = LdapPasswordModifyRequest::try_from(request) {
|
||||||
|
+ return self
|
||||||
|
.do_password_modification(&password_request)
|
||||||
|
.await
|
||||||
|
- .unwrap_or_else(|e: LdapError| vec![make_extended_response(e.code, e.message)]),
|
||||||
|
- Err(_) => vec![make_extended_response(
|
||||||
|
- LdapResultCode::UnwillingToPerform,
|
||||||
|
- format!("Unsupported extended operation: {}", &request.name),
|
||||||
|
- )],
|
||||||
|
+ .unwrap_or_else(|e: LdapError| vec![make_extended_response(e.code, e.message)]);
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ if request.name == OID_WHOAMI {
|
||||||
|
+ let dn = self
|
||||||
|
+ .user_info
|
||||||
|
+ .as_ref()
|
||||||
|
+ .map(|user_info| {
|
||||||
|
+ format!(
|
||||||
|
+ "dn:uid={},ou=people,{}",
|
||||||
|
+ user_info.user.clone().into_string(),
|
||||||
|
+ self.ldap_info.base_dn_str
|
||||||
|
+ )
|
||||||
|
+ })
|
||||||
|
+ .unwrap_or_default();
|
||||||
|
+
|
||||||
|
+ return vec![make_extended_response(LdapResultCode::Success, dn)];
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return vec![make_extended_response(
|
||||||
|
+ LdapResultCode::UnwillingToPerform,
|
||||||
|
+ format!("Unsupported extended operation: {}", &request.name),
|
||||||
|
+ )];
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_modify_change(
|
||||||
|
'';
|
||||||
|
|
||||||
pkgLLDAPCli = pkgs.callPackage ./../../../../shared/pkgs/lldap-cli.nix {};
|
pkgLLDAPCli = pkgs.callPackage ./../../../../shared/pkgs/lldap-cli.nix {};
|
||||||
in {
|
in {
|
||||||
imports = [
|
imports = [
|
||||||
# ./test.nix
|
./provision.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
|
@ -33,7 +111,7 @@ in {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
package = pkgs.lldap.overrideAttrs (old: {
|
package = pkgs.lldap.overrideAttrs (old: {
|
||||||
patches = old.patches ++ [ resetPasswordStartPatch ];
|
patches = old.patches ++ [ resetPasswordStartPatch whoamiPatch ];
|
||||||
});
|
});
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
|
@ -100,22 +178,29 @@ in {
|
||||||
|
|
||||||
users = {
|
users = {
|
||||||
admin = "admin";
|
admin = "admin";
|
||||||
bind = "bind_user";
|
# bind = "bind_user";
|
||||||
};
|
} // (lib.mapAttrs (n: v: v.user_id) config.services.lldap.provision.users);
|
||||||
|
|
||||||
groups = {
|
groups = {
|
||||||
admin = "lldap_admin";
|
admin = "lldap_admin";
|
||||||
member = "base_member";
|
member = "base_member";
|
||||||
system = "system_service";
|
password_manager = "lldap_password_manager";
|
||||||
system_email = "system_email";
|
strict_readonly = "lldap_strict_readonly";
|
||||||
};
|
# system = "system_service";
|
||||||
|
# system_email = "system_email";
|
||||||
|
} // (lib.mapAttrs (n: v: v.display_name) config.services.lldap.provision.groups);
|
||||||
|
|
||||||
ou = {
|
ou = {
|
||||||
groups = "groups";
|
groups = "groups";
|
||||||
users = "people";
|
users = "people";
|
||||||
};
|
};
|
||||||
|
|
||||||
attr = {
|
attr = let
|
||||||
|
toCamelCase = w: let
|
||||||
|
parts = lib.splitString "_" w;
|
||||||
|
cap = lib.imap0 (i: v: if i == 0 then v else (lib.toUpper (lib.substring 0 1 v)) + (lib.substring 1 (lib.stringLength v) v));
|
||||||
|
in lib.concatStrings (cap parts);
|
||||||
|
in {
|
||||||
uid = "uid";
|
uid = "uid";
|
||||||
firstname = "givenName";
|
firstname = "givenName";
|
||||||
lastname = "sn";
|
lastname = "sn";
|
||||||
|
@ -124,9 +209,9 @@ in {
|
||||||
groupname = "cn";
|
groupname = "cn";
|
||||||
|
|
||||||
# custom
|
# custom
|
||||||
member_email = "member_email";
|
# member_email = "member_email";
|
||||||
mail_disk_quota = "mail_disk_quota";
|
# mail_disk_quota = "mail_disk_quota";
|
||||||
};
|
} // (lib.mapAttrs (n: v: toCamelCase v.name) config.services.lldap.provision.user_attributes);
|
||||||
|
|
||||||
age_secret = config.age.secrets.lldap-bind-user-pass.path;
|
age_secret = config.age.secrets.lldap-bind-user-pass.path;
|
||||||
};
|
};
|
||||||
|
@ -156,6 +241,31 @@ in {
|
||||||
|
|
||||||
mkAnd = v: { type = "and"; values = v; };
|
mkAnd = v: { type = "and"; values = v; };
|
||||||
mkOr = v: { type = "or"; values = v; };
|
mkOr = v: { type = "or"; values = v; };
|
||||||
|
|
||||||
|
# mkProvision helpers for creating users
|
||||||
|
mkProvisionEmail = name: "${name}@${config.mine.shared.settings.domain}";
|
||||||
|
mkProvisionUserNormal = name: config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||||
|
user_id = name;
|
||||||
|
membermail = mkProvisionEmail name;
|
||||||
|
mail = "env:EMAIL_${lib.toUpper name}";
|
||||||
|
groups = [ lconfig.groups.member ];
|
||||||
|
membermaildiskquota = 100*1024*1024; # mb
|
||||||
|
});
|
||||||
|
|
||||||
|
mkProvisionUserSystem = name: password_file: config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||||
|
user_id = name;
|
||||||
|
membermail = mkProvisionEmail name;
|
||||||
|
password = "file:${password_file}";
|
||||||
|
groups = [ lconfig.groups.system_mail lconfig.groups.system_service ];
|
||||||
|
membermaildiskquota = 10*1024*1024; # mb
|
||||||
|
});
|
||||||
|
|
||||||
|
mkProvisionUserAdmin = name: config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||||
|
user_id = name;
|
||||||
|
membermail = mkProvisionEmail name;
|
||||||
|
groups = [ lconfig.groups.admin lconfig.groups.member ];
|
||||||
|
membermaildiskquota = 100*1024*1024; # mb
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
mine.shared.meta.lldap = {
|
mine.shared.meta.lldap = {
|
||||||
|
|
|
@ -104,7 +104,7 @@ class LLDAPGroups:
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = [v]
|
v = [v]
|
||||||
|
|
||||||
insertAttributes.append({"name": to_snakecase(k), "value": v})
|
insertAttributes.append({"name": k, "value": v})
|
||||||
|
|
||||||
ds = DSLSchema(self._client.schema)
|
ds = DSLSchema(self._client.schema)
|
||||||
query = dsl_gql(
|
query = dsl_gql(
|
|
@ -22,7 +22,7 @@ from .attributes import LLDAPAttributes
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
logging.getLogger("lldapbootstrap").setLevel(logging.DEBUG)
|
logging.getLogger("bootstrap").setLevel(logging.DEBUG)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
|
@ -95,7 +95,7 @@ class LLDAPUsers:
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = [v]
|
v = [v]
|
||||||
|
|
||||||
insertAttributes.append({"name": to_snakecase(k), "value": v})
|
insertAttributes.append({"name": k, "value": v})
|
||||||
|
|
||||||
ds = DSLSchema(self._client.schema)
|
ds = DSLSchema(self._client.schema)
|
||||||
query = dsl_gql(
|
query = dsl_gql(
|
166
machines/gerd/services/lldap/module/default.nix
Normal file
166
machines/gerd/services/lldap/module/default.nix
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) types;
|
||||||
|
|
||||||
|
cfg = config.services.lldap;
|
||||||
|
format = pkgs.formats.json { };
|
||||||
|
|
||||||
|
# helpers
|
||||||
|
_configFile = {
|
||||||
|
user_attributes = lib.mapAttrsToList (n: v: v) cfg.provision.user_attributes;
|
||||||
|
group_attributes = lib.mapAttrsToList (n: v: v) cfg.provision.group_attributes;
|
||||||
|
users = lib.mapAttrsToList (n: v: v) cfg.provision.users;
|
||||||
|
groups = lib.mapAttrsToList (n: v: v) cfg.provision.groups;
|
||||||
|
# users = lib.mapAttrsToList (n: v: v // {
|
||||||
|
# user_id = if v ? user_id then v.user_id else n;
|
||||||
|
# }) cfg.users;
|
||||||
|
# groups = lib.mapAttrsToList (n: v: v // {
|
||||||
|
# display_name = if v ? display_name then v.display_name else n;
|
||||||
|
# }) cfg.groups;
|
||||||
|
};
|
||||||
|
configFile = format.generate "lldap-declarative.json" _configFile;
|
||||||
|
|
||||||
|
# opts
|
||||||
|
optsAttributes = { name, config, ... }: {
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = name;
|
||||||
|
description = "The name of the attribute";
|
||||||
|
};
|
||||||
|
|
||||||
|
attributeType = lib.mkOption {
|
||||||
|
type = types.enum [ "STRING" "INTEGER" "JPEG_PHOTO" "DATE_TIME" ];
|
||||||
|
description = "Type of the attribute";
|
||||||
|
};
|
||||||
|
|
||||||
|
isList = lib.mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Is this attribute a list (multiple values for this attribute)";
|
||||||
|
};
|
||||||
|
|
||||||
|
isEditable = lib.mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Should the user be able to edit this value?";
|
||||||
|
};
|
||||||
|
|
||||||
|
isVisible = lib.mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Should the user be able to see this value?";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
optsUser = { name, config, ... }: {
|
||||||
|
freeformType = format.type;
|
||||||
|
options = {
|
||||||
|
user_id = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = name;
|
||||||
|
description = "The name of the user";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
optsGroup = { name, config, ... }: {
|
||||||
|
freeformType = format.type;
|
||||||
|
options = {
|
||||||
|
display_name = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = name;
|
||||||
|
description = "The display name of the group";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
services.lldap = {
|
||||||
|
provisionUsername = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Username to use when signing into lldap";
|
||||||
|
};
|
||||||
|
|
||||||
|
provisionPasswordFile = lib.mkOption {
|
||||||
|
type = types.path;
|
||||||
|
description = "Path for the password file to authenticate the user";
|
||||||
|
};
|
||||||
|
|
||||||
|
provision = {
|
||||||
|
group_attributes = lib.mkOption {
|
||||||
|
type = types.attrsOf (types.submodule optsAttributes);
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
user_attributes = lib.mkOption {
|
||||||
|
type = types.attrsOf (types.submodule optsAttributes);
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
users = lib.mkOption {
|
||||||
|
type = types.attrsOf (types.submodule optsUser);
|
||||||
|
default = {};
|
||||||
|
example = {
|
||||||
|
user1 = {
|
||||||
|
password = "env:LLDAP_USER1_PASSWORD";
|
||||||
|
mail = "something@something.dk";
|
||||||
|
|
||||||
|
foo = "value for user attribute foo";
|
||||||
|
bar = "value for user attribute bar";
|
||||||
|
groups = [ "group1" "group2" ];
|
||||||
|
};
|
||||||
|
user2 = { user_id = "superuserawesome"; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
groups = lib.mkOption {
|
||||||
|
type = types.attrsOf (types.submodule optsGroup);
|
||||||
|
default = {};
|
||||||
|
example = {
|
||||||
|
base_member = {
|
||||||
|
foo = "value for group attribute foo";
|
||||||
|
bar = "value for group attribute bar";
|
||||||
|
};
|
||||||
|
system = {
|
||||||
|
display_name = "system_service - override display_name";
|
||||||
|
};
|
||||||
|
testgroup = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf (cfg.enable && cfg.provision != {}) {
|
||||||
|
systemd.services.lldapsetup = {
|
||||||
|
description = "setup lldap declaratively";
|
||||||
|
wantedBy = [ config.systemd.services.lldap.name "multi-user.target" ];
|
||||||
|
after = [ config.systemd.services.lldap.name ];
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
LLDAP_URL = "${cfg.settings.http_url}:${builtins.toString cfg.settings.http_port}";
|
||||||
|
LLDAP_USERNAME = cfg.provisionUsername;
|
||||||
|
LLDAP_PASSWORD = "file:${cfg.provisionPasswordFile}";
|
||||||
|
};
|
||||||
|
|
||||||
|
path = with pkgs; [
|
||||||
|
lldap
|
||||||
|
];
|
||||||
|
|
||||||
|
script = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages(ps: with ps; [ gql aiohttp requests ]);
|
||||||
|
pythonDir = pkgs.runCommand "lldap-bootstrap" {} ''
|
||||||
|
mkdir -p $out/bootstrap
|
||||||
|
cp -a ${./bootstrap}/. $out/bootstrap
|
||||||
|
'';
|
||||||
|
in ''
|
||||||
|
cd ${pythonDir}
|
||||||
|
${pythonEnv}/bin/python -m bootstrap.main ${configFile}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
68
machines/gerd/services/lldap/provision.nix
Normal file
68
machines/gerd/services/lldap/provision.nix
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./module
|
||||||
|
];
|
||||||
|
|
||||||
|
services.lldap = {
|
||||||
|
provisionUsername = "admin";
|
||||||
|
provisionPasswordFile = config.age.secrets.lldap-admin-user-pass.path;
|
||||||
|
|
||||||
|
provision = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||||
|
# users
|
||||||
|
users = {
|
||||||
|
# normal users
|
||||||
|
testusername = {
|
||||||
|
membermail = "env:EMAIL_EMAIL0";
|
||||||
|
groups = [ config.services.lldap.provision.groups.system_mail.display_name ];
|
||||||
|
};
|
||||||
|
|
||||||
|
user1 = llib.mkProvisionUserNormal "thief420";
|
||||||
|
|
||||||
|
# admin users
|
||||||
|
admin = llib.mkProvisionUserAdmin "admin";
|
||||||
|
eyjhb = llib.mkProvisionUserAdmin "eyjhb";
|
||||||
|
rasmus = llib.mkProvisionUserAdmin "rasmus";
|
||||||
|
|
||||||
|
# system users - defined in each service
|
||||||
|
# should not be done here
|
||||||
|
|
||||||
|
# bind user
|
||||||
|
bind = {
|
||||||
|
user_id = "bind_user";
|
||||||
|
groups = [ lconfig.groups.password_manager lconfig.groups.strict_readonly ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# groups
|
||||||
|
groups = {
|
||||||
|
"base_member" = {};
|
||||||
|
"system_service" = {};
|
||||||
|
"system_mail" = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
# attributes
|
||||||
|
group_attributes = {
|
||||||
|
group_foo = {
|
||||||
|
attributeType = "STRING";
|
||||||
|
isEditable = true;
|
||||||
|
isVisible = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
user_attributes = {
|
||||||
|
membermail = {
|
||||||
|
attributeType = "STRING";
|
||||||
|
isEditable = false;
|
||||||
|
isVisible = true;
|
||||||
|
};
|
||||||
|
membermaildiskquota = {
|
||||||
|
attributeType = "INTEGER";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.lldapsetup.serviceConfig.EnvironmentFile = config.age.secrets.lldap-user-emails-env.path;
|
||||||
|
}
|
|
@ -1,98 +0,0 @@
|
||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
mkEmail = name: "${name}@${config.mine.shared.settings.domain}";
|
|
||||||
|
|
||||||
mkUserNormal = name: {
|
|
||||||
user_id = name;
|
|
||||||
member_email = mkEmail name;
|
|
||||||
mail = "env:EMAIL_${lib.toUpper name}";
|
|
||||||
groups = [ "base_member" ];
|
|
||||||
mail_disk_quota = 100*1024*1024; # mb
|
|
||||||
};
|
|
||||||
|
|
||||||
mkUserSystem = name: password_file: {
|
|
||||||
user_id = name;
|
|
||||||
member_email = mkEmail name;
|
|
||||||
password = "file:${password_file}";
|
|
||||||
# TODO: remove base_member in the future, or have
|
|
||||||
# more granular controls for emails and shit
|
|
||||||
groups = [ "base_member" "system_service" ];
|
|
||||||
mail_disk_quota = 10*1024*1024; # mb
|
|
||||||
};
|
|
||||||
|
|
||||||
mkUserAdmin = name: {
|
|
||||||
user_id = name;
|
|
||||||
member_email = mkEmail name;
|
|
||||||
groups = [ "base_member" "lldap_admin" ];
|
|
||||||
mail_disk_quota = 100*1024*1024; # mb
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
imports = [
|
|
||||||
./bootstrap/lldap-state-module.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
mine.lldap_provision = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
url = config.mine.shared.meta.lldap.url;
|
|
||||||
username = "admin";
|
|
||||||
passwordFile = config.age.secrets.lldap-admin-user-pass.path;
|
|
||||||
# username = "testusername";
|
|
||||||
# passwordFile = ./test.txt;
|
|
||||||
|
|
||||||
group_attributes = {
|
|
||||||
group_foo = {
|
|
||||||
attributeType = "STRING";
|
|
||||||
isEditable = true;
|
|
||||||
isVisible = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
user_attributes = {
|
|
||||||
member_email = {
|
|
||||||
attributeType = "STRING";
|
|
||||||
isEditable = false;
|
|
||||||
isVisible = true;
|
|
||||||
};
|
|
||||||
mail_disk_quota = {
|
|
||||||
attributeType = "INTEGER";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
groups = let
|
|
||||||
gs = [
|
|
||||||
"base_member"
|
|
||||||
"system_service"
|
|
||||||
"system_email"
|
|
||||||
];
|
|
||||||
in lib.listToAttrs (lib.forEach gs (v: lib.nameValuePair v { display_name = v; }));
|
|
||||||
|
|
||||||
users = {
|
|
||||||
# normal users
|
|
||||||
testusername = {
|
|
||||||
member_email = "env:USER1_EMAIL";
|
|
||||||
};
|
|
||||||
|
|
||||||
user1 = mkUserNormal "thief420";
|
|
||||||
|
|
||||||
# admin users
|
|
||||||
admin = mkUserAdmin "admin";
|
|
||||||
eyjhb = mkUserAdmin "eyjhb";
|
|
||||||
rasmus = mkUserAdmin "rasmus";
|
|
||||||
|
|
||||||
# system users
|
|
||||||
authelia = mkUserSystem "authelia" config.age.secrets.authelia-smtp-password.path;
|
|
||||||
wger = mkUserSystem "wger" config.age.secrets.wger-ldap-pass.path;
|
|
||||||
|
|
||||||
# bind user
|
|
||||||
bind_user = {
|
|
||||||
groups = [ "lldap_password_manager" "lldap_strict_readonly" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.lldapsetup.environment = {
|
|
||||||
USER1_EMAIL = "eyjhbbbbbbb@fricloud.dk";
|
|
||||||
EMAIL_THIEF420 = "someemail@gmail.com";
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -50,7 +50,10 @@ in {
|
||||||
filter = let
|
filter = let
|
||||||
_mkFilter = attrs: ph: config.mine.shared.lib.ldap.mkFilter (lconfig: llib:
|
_mkFilter = attrs: ph: config.mine.shared.lib.ldap.mkFilter (lconfig: llib:
|
||||||
llib.mkAnd [
|
llib.mkAnd [
|
||||||
(llib.mkGroup lconfig.groups.member)
|
(llib.mkOr [
|
||||||
|
(llib.mkGroup lconfig.groups.member)
|
||||||
|
(llib.mkGroup lconfig.groups.system_mail)
|
||||||
|
])
|
||||||
(llib.mkOr (lib.forEach attrs (v: llib.mkSearch v ph)))
|
(llib.mkOr (lib.forEach attrs (v: llib.mkSearch v ph)))
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -58,22 +61,21 @@ in {
|
||||||
attrs = config.mine.shared.settings.ldap.attr // { emailAlias = "mailAlias"; emailList = "mailList"; };
|
attrs = config.mine.shared.settings.ldap.attr // { emailAlias = "mailAlias"; emailList = "mailList"; };
|
||||||
in {
|
in {
|
||||||
name = _mkFilter [ attrs.uid ] "?";
|
name = _mkFilter [ attrs.uid ] "?";
|
||||||
email = _mkFilter [ attrs.email attrs.emailAlias attrs.emailList ] "?";
|
email = _mkFilter [ attrs.membermail ] "?";
|
||||||
verify = _mkFilter [ attrs.email attrs.emailAlias ] "*?*";
|
|
||||||
expand = _mkFilter [ attrs.emailList ] "?";
|
|
||||||
domains = _mkFilter [ attrs.email attrs.emailAlias ] "*@?";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
attributes = {
|
attributes = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||||
name = "uid";
|
name = lconfig.attr.uid;
|
||||||
|
# name = lconfig.attr.member_mail;
|
||||||
|
description = lconfig.attr.firstname;
|
||||||
|
email = lconfig.attr.membermail;
|
||||||
|
quota = lconfig.attr.membermaildiskquota;
|
||||||
class = "objectClass";
|
class = "objectClass";
|
||||||
description = "givenName";
|
|
||||||
secret = "uid";
|
|
||||||
groups = "memberOf";
|
groups = "memberOf";
|
||||||
email = "mail";
|
# we dont have access to this in lldap
|
||||||
# email-alias = "mailAlias";
|
# secret = lconfig.attr.stalwart_secret;
|
||||||
# quota = "diskQuota";
|
});
|
||||||
};
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ in {
|
||||||
|
|
||||||
# wger specific settings
|
# wger specific settings
|
||||||
wgerSettings = {
|
wgerSettings = {
|
||||||
EMAIL_FROM = "wger Workout Manager <wger@${svc_domain}>";
|
EMAIL_FROM = "wger Workout Manager <wger@${config.mine.shared.settings.domain}>";
|
||||||
|
|
||||||
# use authelia for authentication (disable guest users + regisration)
|
# use authelia for authentication (disable guest users + regisration)
|
||||||
AUTH_PROXY_HEADER = config.mine.shared.lib.authelia.protectedHeaders.username;
|
AUTH_PROXY_HEADER = config.mine.shared.lib.authelia.protectedHeaders.username;
|
||||||
|
@ -39,7 +39,7 @@ in {
|
||||||
EMAIL_PORT = config.mine.shared.settings.mail.ports.submissions;
|
EMAIL_PORT = config.mine.shared.settings.mail.ports.submissions;
|
||||||
EMAIL_USE_SSL = true;
|
EMAIL_USE_SSL = true;
|
||||||
EMAIL_HOST_USER = "wger";
|
EMAIL_HOST_USER = "wger";
|
||||||
EMAIL_HOST_PASSWORD = "$EMAIL_HOST_PASSWORD";
|
EMAIL_HOST_PASSWORD = "file:${config.age.secrets.wger-ldap-pass.path}";
|
||||||
EMAIL_FROM_ADDRESS = config.services.wger.wgerSettings.EMAIL_FROM;
|
EMAIL_FROM_ADDRESS = config.services.wger.wgerSettings.EMAIL_FROM;
|
||||||
EMAIL_PAGE_DOMAIN = SITE_URL;
|
EMAIL_PAGE_DOMAIN = SITE_URL;
|
||||||
};
|
};
|
||||||
|
@ -62,6 +62,14 @@ in {
|
||||||
locations."/api".proxyPass = "http://localhost:${builtins.toString port}";
|
locations."/api".proxyPass = "http://localhost:${builtins.toString port}";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# setup lldap user for authelia that can send emails
|
||||||
|
services.lldap.provision.users = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||||
|
wger = llib.mkProvisionUserSystem "wger" config.age.secrets.wger-ldap-pass.path;
|
||||||
|
});
|
||||||
|
|
||||||
|
# setup permissions
|
||||||
|
age.secrets.wger-ldap-pass.owner = config.services.wger.user;
|
||||||
|
|
||||||
# metadata
|
# metadata
|
||||||
mine.shared.meta.wger = {
|
mine.shared.meta.wger = {
|
||||||
name = "Wger";
|
name = "Wger";
|
||||||
|
|
|
@ -23,6 +23,8 @@ let
|
||||||
for k, v in json.load(f).items():
|
for k, v in json.load(f).items():
|
||||||
if isinstance(v, str) and v.startswith("$"):
|
if isinstance(v, str) and v.startswith("$"):
|
||||||
v = os.environ[v[1:]]
|
v = os.environ[v[1:]]
|
||||||
|
if isinstance(v, str) and v.startswith("file:"):
|
||||||
|
v = open(v[5:], "r").read().strip()
|
||||||
|
|
||||||
globals()[k] = v
|
globals()[k] = v
|
||||||
|
|
||||||
|
@ -30,6 +32,8 @@ let
|
||||||
for k, v in json.load(f).items():
|
for k, v in json.load(f).items():
|
||||||
if isinstance(v, str) and v.startswith("$"):
|
if isinstance(v, str) and v.startswith("$"):
|
||||||
v = os.environ[v[1:]]
|
v = os.environ[v[1:]]
|
||||||
|
if isinstance(v, str) and v.startswith("file:"):
|
||||||
|
v = open(v[5:], "r").read().strip()
|
||||||
|
|
||||||
WGER_SETTINGS[k] = v
|
WGER_SETTINGS[k] = v
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
group = "secrets-lldap-bind-user-pass";
|
group = "secrets-lldap-bind-user-pass";
|
||||||
mode = "0440";
|
mode = "0440";
|
||||||
};
|
};
|
||||||
|
lldap-user-emails-env.file = ./lldap/user-emails-env.age;
|
||||||
lldap-bind-user-pass-hedgedoc-env.file = ./lldap/bind-user-pass-hedgedoc-env.age;
|
lldap-bind-user-pass-hedgedoc-env.file = ./lldap/bind-user-pass-hedgedoc-env.age;
|
||||||
|
|
||||||
# mumble
|
# mumble
|
||||||
|
|
BIN
secrets/lldap/user-emails-env.age
Normal file
BIN
secrets/lldap/user-emails-env.age
Normal file
Binary file not shown.
|
@ -28,6 +28,7 @@ in
|
||||||
"lldap/admin-user-pass.age".publicKeys = defaultAccess;
|
"lldap/admin-user-pass.age".publicKeys = defaultAccess;
|
||||||
"lldap/bind-user-pass.age".publicKeys = defaultAccess;
|
"lldap/bind-user-pass.age".publicKeys = defaultAccess;
|
||||||
"lldap/bind-user-pass-hedgedoc-env.age".publicKeys = defaultAccess;
|
"lldap/bind-user-pass-hedgedoc-env.age".publicKeys = defaultAccess;
|
||||||
|
"lldap/user-emails-env.age".publicKeys = defaultAccess;
|
||||||
|
|
||||||
# mumble
|
# mumble
|
||||||
"murmur/env.age".publicKeys = defaultAccess;
|
"murmur/env.age".publicKeys = defaultAccess;
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg KGoB/V0cCAZsfVmoLDmA5Xs2HOHqjg54TYqixYQduEw
|
-> ssh-ed25519 QSDXqg 1g79p7fDXhx/jHR4Z6PY4MsJyITD84/bimvr0jRcgCQ
|
||||||
sqDb6QnEbwEncAbxKLRLkjCQIwMLBTNMVcejFOwhZWM
|
41z4XNjkwik7rdj9UdJs/ZR+gUGa+rTOXFEQz50UWlo
|
||||||
-> X25519 o64XZRaiK7ZEquTMmXTyhpdArawiuXC+5W5seFrJclY
|
-> X25519 XzyBVMh7elt7LdkzGAG1qz5kiKAZIeFHJeYVhYCh+gY
|
||||||
qTLXrNGMTPAXs5EzMuCiQ07Ho2LT1KTku2f1AlCHPlk
|
cbEWc7hdQ7ddoBnFRUFYzvunIGn/tNMckaEao7Lcxw4
|
||||||
-> ssh-ed25519 n8n9DQ a8ESfbksuY++k52UJwTKJtb4/aiYzQqUgyYqfug5oyA
|
-> ssh-ed25519 n8n9DQ bl0lknR3pVULG/2mRe7rtb6oFjBgr5zVayHM8Oc0dCM
|
||||||
bZygFOW6YSg83CmZRpsNDux+UgOxCfja1eQ/R4NyLXM
|
gYkyO5PNrzwDMqcS5s5RnXH7kLIw5IdYB9qLzXTraX0
|
||||||
-> ssh-ed25519 BTp6UA yFBZAlGtHV98t6UA8QbELjOW/Pu6KYVPjbXFvijl9m0
|
-> ssh-ed25519 BTp6UA D36+NXsMYDzeqgFbMTsdTuKWgHbQUcTw0jheGX3ndmw
|
||||||
+eobFp5YNBsr2+10Huimwypn3S4/lc7zoX5Ldko9mhA
|
pTtYYl2jLimVJdGAKhRqgcBg6tpg0LOvNnY+QoRgomk
|
||||||
--- g7w825LgydJlmyZiqnIL0ofUsTn+e47rFmSG8ft6Qqg
|
--- lsPTx+ZG4T1MFVjue9ceTeieyEI99LF0D9RNBTnZG08
|
||||||
!lï•:^çÄÙƒ}R&X‚º^_ã213·-éŒË£0ÅnBþ–<C3BE>DK€æ&Ù©Dþ:¾^½ÒUwÃÌóŸ
8(£‡ä X‡¾QZsÖªŒ<C2AA>â^(CÂ!ÍìÊ$ ™Üöý×(‹wÎ8t“ô¾<C3B4>Ñ!Úç²±Ð̈ït;¥ÃNgÚÛ§ˆ<C2A7>Ž[²f+Ù‚Q°
|
ÿƒ¨
|
||||||
|
-¾w¶ðf²Brî6Îö´ßH^»»ÇõÄéPÉEÌ󨞜¬ßà3×B-áû-‚ót?³nd^ë7¬có/[z‡f˜xŠëû÷{ ji1A°BŸÛ¢7Âø*ú
|
Loading…
Add table
Add a link
Reference in a new issue