From 6a1aca24a9f852b0ed07666143182d267383816e Mon Sep 17 00:00:00 2001 From: Kaare Hoff Skovgaard Date: Tue, 29 Jul 2025 00:27:07 +0200 Subject: [PATCH] Getting close to working ldap setup with postfix and dovecot LDAP login works for IMAP, but postfix doesn't recognise the mail addresses for the users. --- .../infrastructure/mailserver/default.nix | 28 +-- .../nixos/infrastructure/mailserver/ldap.nix | 76 +++++++ .../infrastructure/mailserver/mta-sts.nix | 15 +- .../mailserver/package/nixos-module.nix | 23 --- .../mailserver/package/package.nix | 194 ------------------ .../mailserver/package/spam-filter.nix | 43 ---- .../mailserver/package/webadmin.nix | 77 ------- .../default.nix | 21 ++ .../mx.kaareskovgaard.net/default.nix | 24 +-- .../mx.kaareskovgaard.net/disko.nix | 2 +- .../security.kaareskovgaard.net/README.md | 8 + .../security.kaareskovgaard.net/kanidm.nix | 10 +- .../kanidm_dovecot.nix | 92 +++++++++ .../kanidm_ldap.nix | 8 + 14 files changed, 245 insertions(+), 376 deletions(-) create mode 100644 nix/modules/nixos/infrastructure/mailserver/ldap.nix delete mode 100644 nix/modules/nixos/infrastructure/mailserver/package/nixos-module.nix delete mode 100644 nix/modules/nixos/infrastructure/mailserver/package/package.nix delete mode 100644 nix/modules/nixos/infrastructure/mailserver/package/spam-filter.nix delete mode 100644 nix/modules/nixos/infrastructure/mailserver/package/webadmin.nix create mode 100644 nix/packages/kanidm-dovecot-service-account-provision/default.nix create mode 100644 nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_dovecot.nix create mode 100644 nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_ldap.nix diff --git a/nix/modules/nixos/infrastructure/mailserver/default.nix b/nix/modules/nixos/infrastructure/mailserver/default.nix index bc257a4..6f124b7 100644 --- a/nix/modules/nixos/infrastructure/mailserver/default.nix +++ b/nix/modules/nixos/infrastructure/mailserver/default.nix @@ -26,7 +26,7 @@ in ./tls-rpt.nix ./prometheus.nix ./openid-connect.nix - ./package/nixos-module.nix + ./ldap.nix ]; config = lib.mkIf cfg.enable { # TODO: Include a similiar rule for openstack @@ -83,25 +83,29 @@ in enableImapSsl = true; enableSubmission = false; enableSubmissionSsl = true; - stateVersion = 3; fqdn = config.khscodes.networking.fqdn; - systemDomain = config.khscodes.networking.fqdn; useUTF8FolderNames = true; domains = cfg.domains; certificateScheme = "acme"; }; services.dovecot2.extraConfig = '' - oauth2 { - openid_configuration_url = https://login.kaareskovgaard.net/oauth2/openid/stalwart/.well-known/openid-configuration - scope = email openid profile - username_attribute = preferred_username - client_id = stalwart - client_secret = <${config.khscodes.infrastructure.kanidm-client-application.secretFile} - tokeninfo_url = https://login.kaareskovgaard.net/oauth2/token - introspection_url = https://login.kaareskovgaard.net/oauth2/token/introspect - introspection_mode = post + auth_mechanisms = $auth_mechanisms oauthbearer xoauth2 + + passdb { + driver = oauth2 + mechanisms = xoauth2 oauthbearer + args = /etc/dovecot/dovecot-oauth2.conf.ext } ''; + environment.etc."dovecot/dovecot-oauth2.conf.ext".text = '' + scope = email openid profile + username_attribute = preferred_username + client_id = dovecot + client_secret = <${config.khscodes.infrastructure.kanidm-client-application.secretFile} + tokeninfo_url = https://login.kaareskovgaard.net/oauth2/token + introspection_url = https://login.kaareskovgaard.net/oauth2/token/introspect + introspection_mode = post + ''; services.prometheus.exporters.postfix = { enable = true; }; diff --git a/nix/modules/nixos/infrastructure/mailserver/ldap.nix b/nix/modules/nixos/infrastructure/mailserver/ldap.nix new file mode 100644 index 0000000..622efcd --- /dev/null +++ b/nix/modules/nixos/infrastructure/mailserver/ldap.nix @@ -0,0 +1,76 @@ +{ lib, config, ... }: +let + cfg = config.khscodes.infrastructure.mailserver; + secretFile = "/run/secret/dovecot/ldap"; +in +{ + options.khscodes.infrastructure.mailserver = { + ldap = { + mount = lib.mkOption { + type = lib.types.str; + }; + path = lib.mkOption { + type = lib.types.str; + }; + }; + }; + + config = lib.mkIf cfg.enable { + mailserver.debug = true; + mailserver.ldap = { + enable = true; + uris = [ "ldaps://login.kaareskovgaard.net" ]; + searchBase = "dc=login,dc=kaareskovgaard,dc=net"; + searchScope = "sub"; + bind = { + dn = "dn=token"; + passwordFile = secretFile; + }; + dovecot = { + # Map LDAP uid to dovecot user, and ldap userPassword to dovecot password + passAttrs = "uid=user"; + passFilter = "(&(class=account)(memberOf=mail_user)(uid=%u))"; + userFilter = "(&(class=account)(memberOf=mail_user)(uid=%u))"; + }; + postfix = { + filter = "(&(class=account)(memberOf=mail_user)(mail=%s))"; + mailAttribute = "mail"; + uidAttribute = "uid"; + }; + }; + khscodes.services.vault-agent.templates = [ + { + contents = '' + {{- with secret "${cfg.ldap.mount}/data/${cfg.ldap.path}" -}} + {{ .Data.data.apiToken }} + {{- end -}} + ''; + destination = secretFile; + owner = "dovecot"; + group = "dovecot"; + restartUnits = [ + "dovecot2.service" + "postfix.service" + ]; + } + ]; + # TODO: Include a similiar rule for openstack + khscodes.infrastructure.hetzner-instance.extraFirewallRules = [ + { + direction = "out"; + protocol = "tcp"; + port = 636; + destination_ips = [ + "0.0.0.0/0" + "::/0" + ]; + description = "ldaps"; + } + ]; + khscodes.infrastructure.vault-server-approle.policy = { + "${cfg.ldap.mount}/data/${cfg.ldap.path}" = { + capabilities = [ "read" ]; + }; + }; + }; +} diff --git a/nix/modules/nixos/infrastructure/mailserver/mta-sts.nix b/nix/modules/nixos/infrastructure/mailserver/mta-sts.nix index ebb9536..df75f48 100644 --- a/nix/modules/nixos/infrastructure/mailserver/mta-sts.nix +++ b/nix/modules/nixos/infrastructure/mailserver/mta-sts.nix @@ -9,12 +9,15 @@ let cfg = config.khscodes.infrastructure.mailserver; # Increment this if ever changing mta-sts settings. policyVersion = 2; - mtaStsWellKnown = pkgs.writeTextFile "mta-sts.txt" '' - version: STSv1 - mode: enforce - max_age: 600 - mx: ${fqdn} - ''; + mtaStsWellKnown = pkgs.writeTextFile { + name = "mta-sts.txt"; + text = '' + version: STSv1 + mode: enforce + max_age: 600 + mx: ${fqdn} + ''; + }; in { config = lib.mkIf cfg.enable { diff --git a/nix/modules/nixos/infrastructure/mailserver/package/nixos-module.nix b/nix/modules/nixos/infrastructure/mailserver/package/nixos-module.nix deleted file mode 100644 index 5d4160c..0000000 --- a/nix/modules/nixos/infrastructure/mailserver/package/nixos-module.nix +++ /dev/null @@ -1,23 +0,0 @@ -# This file contains patches for Nixos 25.05 to be compatible with new stalwart mail -{ - lib, - config, - pkgs, - ... -}: -let - configFormat = pkgs.formats.toml { }; - configFile = configFormat.generate "stalwart-mail.toml" config.services.stalwart-mail.settings; -in -{ - systemd.services.stalwart-mail = lib.mkIf config.services.stalwart-mail.enable { - serviceConfig = { - User = "stalwart-mail"; - Group = "stalwart-mail"; - ExecStart = lib.mkForce [ - "" - "${lib.getExe config.services.stalwart-mail.package} --config=${configFile}" - ]; - }; - }; -} diff --git a/nix/modules/nixos/infrastructure/mailserver/package/package.nix b/nix/modules/nixos/infrastructure/mailserver/package/package.nix deleted file mode 100644 index b1c1256..0000000 --- a/nix/modules/nixos/infrastructure/mailserver/package/package.nix +++ /dev/null @@ -1,194 +0,0 @@ -{ - lib, - rustPlatform, - fetchFromGitHub, - pkg-config, - protobuf, - bzip2, - openssl, - sqlite, - foundationdb, - zstd, - stdenv, - nix-update-script, - nixosTests, - rocksdb, - callPackage, - withFoundationdb ? false, - stalwartEnterprise ? false, -}: - -rustPlatform.buildRustPackage (finalAttrs: { - pname = "stalwart-mail" + (lib.optionalString stalwartEnterprise "-enterprise"); - version = "0.13.2"; - - src = fetchFromGitHub { - owner = "stalwartlabs"; - repo = "stalwart"; - rev = "51a0a1445d74a8cfb880e9d88f5be390fa0e9365"; - hash = "sha256-VdeHb1HVGXA5RPenhhK4r/kkQiLG8/4qhdxoJ3xIqR4="; - }; - - cargoHash = "sha256-Wu6skjs3Stux5nCX++yoQPeA33Qln67GoKcob++Ldng="; - - nativeBuildInputs = [ - pkg-config - protobuf - rustPlatform.bindgenHook - ]; - - buildInputs = [ - bzip2 - openssl - sqlite - zstd - ] - ++ lib.optionals (stdenv.hostPlatform.isLinux && withFoundationdb) [ foundationdb ]; - - # Issue: https://github.com/stalwartlabs/stalwart/issues/1104 - buildNoDefaultFeatures = true; - buildFeatures = [ - "postgres" - "rocks" - "elastic" - "redis" - ] - ++ lib.optionals withFoundationdb [ "foundationdb" ] - ++ lib.optionals stalwartEnterprise [ "enterprise" ]; - - env = { - OPENSSL_NO_VENDOR = true; - ZSTD_SYS_USE_PKG_CONFIG = true; - ROCKSDB_INCLUDE_DIR = "${rocksdb}/include"; - ROCKSDB_LIB_DIR = "${rocksdb}/lib"; - }; - - postInstall = '' - mkdir -p $out/etc/stalwart - - mkdir -p $out/lib/systemd/system - - substitute resources/systemd/stalwart-mail.service $out/lib/systemd/system/stalwart-mail.service \ - --replace "__PATH__" "$out" - ''; - - checkFlags = lib.forEach [ - # Require running mysql, postgresql daemon - "directory::imap::imap_directory" - "directory::internal::internal_directory" - "directory::ldap::ldap_directory" - "directory::sql::sql_directory" - "directory::oidc::oidc_directory" - "store::blob::blob_tests" - "store::lookup::lookup_tests" - "smtp::lookup::sql::lookup_sql" - # thread 'directory::smtp::lmtp_directory' panicked at tests/src/store/mod.rs:122:44: - # called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" } - "directory::smtp::lmtp_directory" - # thread 'imap::imap_tests' panicked at tests/src/imap/mod.rs:436:14: - # Missing store type. Try running `STORE= cargo test`: NotPresent - "imap::imap_tests" - # thread 'jmap::jmap_tests' panicked at tests/src/jmap/mod.rs:303:14: - # Missing store type. Try running `STORE= cargo test`: NotPresent - "jmap::jmap_tests" - # Failed to read system DNS config: io error: No such file or directory (os error 2) - "smtp::inbound::data::data" - # Expected "X-My-Header: true" but got Received: from foobar.net (unknown [10.0.0.123]) - "smtp::inbound::scripts::sieve_scripts" - # thread 'smtp::outbound::lmtp::lmtp_delivery' panicked at tests/src/smtp/session.rs:313:13: - # Expected " (failed to lookup" but got From: "Mail Delivery Subsystem" - "smtp::outbound::lmtp::lmtp_delivery" - # thread 'smtp::outbound::extensions::extensions' panicked at tests/src/smtp/inbound/mod.rs:45:23: - # No queue event received. - "smtp::outbound::extensions::extensions" - # panicked at tests/src/smtp/outbound/smtp.rs:173:5: - "smtp::outbound::smtp::smtp_delivery" - # panicked at tests/src/smtp/outbound/lmtp.rs - "smtp::outbound::lmtp::lmtp_delivery" - # thread 'smtp::queue::retry::queue_retry' panicked at tests/src/smtp/queue/retry.rs:119:5: - # assertion `left == right` failed - # left: [1, 2, 2] - # right: [1, 2, 3] - "smtp::queue::retry::queue_retry" - # thread 'smtp::queue::virtualq::virtual_queue' panicked at /build/source/crates/store/src/dispatch/store.rs:548:14: - # called `Result::unwrap()` on an `Err` value: Error(Event { inner: Store(MysqlError), keys: [(Reason, String("Input/output error: Input/output error: Connection refused (os error 111)")), (CausedBy, String("crates/store/src/dispatch/store.rs:301"))] }) - "smtp::queue::virtualq::virtual_queue" - # Missing store type. Try running `STORE= cargo test`: NotPresent - "store::store_tests" - # Missing store type. Try running `STORE= cargo test`: NotPresent - "cluster::cluster_tests" - # Missing store type. Try running `STORE= cargo test`: NotPresent - "webdav::webdav_tests" - # thread 'config::parser::tests::toml_parse' panicked at crates/utils/src/config/parser.rs:463:58: - # called `Result::unwrap()` on an `Err` value: "Expected ['\\n'] but found '!' in value at line 70." - "config::parser::tests::toml_parse" - # error[E0432]: unresolved import `r2d2_sqlite` - # use of undeclared crate or module `r2d2_sqlite` - "backend::sqlite::pool::SqliteConnectionManager::with_init" - # thread 'smtp::reporting::analyze::report_analyze' panicked at tests/src/smtp/reporting/analyze.rs:88:5: - # assertion `left == right` failed - # left: 0 - # right: 12 - "smtp::reporting::analyze::report_analyze" - # thread 'smtp::inbound::dmarc::dmarc' panicked at tests/src/smtp/inbound/mod.rs:59:26: - # Expected empty queue but got Reload - "smtp::inbound::dmarc::dmarc" - # thread 'smtp::queue::concurrent::concurrent_queue' panicked at tests/src/smtp/inbound/mod.rs:65:9: - # assertion `left == right` failed - "smtp::queue::concurrent::concurrent_queue" - # Failed to read system DNS config: io error: No such file or directory (os error 2) - "smtp::inbound::auth::auth" - # Failed to read system DNS config: io error: No such file or directory (os error 2) - "smtp::inbound::antispam::antispam" - # Failed to read system DNS config: io error: No such file or directory (os error 2) - "smtp::inbound::vrfy::vrfy_expn" - # thread 'smtp::management::queue::manage_queue' panicked at tests/src/smtp/inbound/mod.rs:45:23: - # No queue event received. - # NOTE: Test unreliable on high load systems - "smtp::management::queue::manage_queue" - # thread 'responses::tests::parse_responses' panicked at crates/dav-proto/src/responses/mod.rs:671:17: - # assertion `left == right` failed: failed for 008.xml - # left: ElementEnd - # right: Bytes([...]) - "responses::tests::parse_responses" - ] (test: "--skip=${test}"); - - doCheck = !(stdenv.hostPlatform.isLinux && stdenv.hostPlatform.isAarch64); - - # Allow network access during tests on Darwin/macOS - __darwinAllowLocalNetworking = true; - - passthru = { - inherit rocksdb; # make used rocksdb version available (e.g., for backup scripts) - webadmin = callPackage ./webadmin.nix { }; - spam-filter = callPackage ./spam-filter.nix { }; - updateScript = nix-update-script { }; - tests.stalwart-mail = nixosTests.stalwart-mail; - }; - - meta = { - description = "Secure & Modern All-in-One Mail Server (IMAP, JMAP, SMTP)"; - homepage = "https://github.com/stalwartlabs/mail-server"; - changelog = "https://github.com/stalwartlabs/mail-server/blob/main/CHANGELOG.md"; - license = [ - lib.licenses.agpl3Only - ] - ++ lib.optionals stalwartEnterprise [ - { - fullName = "Stalwart Enterprise License 1.0 (SELv1) Agreement"; - url = "https://github.com/stalwartlabs/mail-server/blob/main/LICENSES/LicenseRef-SEL.txt"; - free = false; - redistributable = false; - } - ]; - - mainProgram = "stalwart"; - maintainers = with lib.maintainers; [ - happysalada - onny - oddlama - pandapip1 - norpol - ]; - }; -}) diff --git a/nix/modules/nixos/infrastructure/mailserver/package/spam-filter.nix b/nix/modules/nixos/infrastructure/mailserver/package/spam-filter.nix deleted file mode 100644 index d0ca6c6..0000000 --- a/nix/modules/nixos/infrastructure/mailserver/package/spam-filter.nix +++ /dev/null @@ -1,43 +0,0 @@ -{ - lib, - fetchFromGitHub, - stdenv, - stalwart-mail, - nix-update-script, -}: - -stdenv.mkDerivation (finalAttrs: { - pname = "spam-filter"; - version = "2.0.3"; - - src = fetchFromGitHub { - owner = "stalwartlabs"; - repo = "spam-filter"; - tag = "v${finalAttrs.version}"; - hash = "sha256-NhD/qUiGhgESwR2IOzAHfDATRlgWMcCktlktvVfDONk="; - }; - - buildPhase = '' - bash ./build.sh - ''; - - installPhase = '' - mkdir -p $out - cp spam-filter.toml $out/ - ''; - - passthru = { - updateScript = nix-update-script { }; - }; - - meta = { - description = "Secure & modern all-in-one mail server Stalwart (spam-filter module)"; - homepage = "https://github.com/stalwartlabs/spam-filter"; - changelog = "https://github.com/stalwartlabs/spam-filter/blob/${finalAttrs.src.tag}/CHANGELOG.md"; - license = with lib.licenses; [ - mit - asl20 - ]; - inherit (stalwart-mail.meta) maintainers; - }; -}) diff --git a/nix/modules/nixos/infrastructure/mailserver/package/webadmin.nix b/nix/modules/nixos/infrastructure/mailserver/package/webadmin.nix deleted file mode 100644 index 18785be..0000000 --- a/nix/modules/nixos/infrastructure/mailserver/package/webadmin.nix +++ /dev/null @@ -1,77 +0,0 @@ -{ - lib, - rustPlatform, - stalwart-mail, - fetchFromGitHub, - trunk, - tailwindcss_3, - fetchNpmDeps, - nix-update-script, - nodejs, - npmHooks, - llvmPackages, - wasm-bindgen-cli_0_2_93, - binaryen, - zip, -}: - -rustPlatform.buildRustPackage (finalAttrs: { - pname = "webadmin"; - version = "0.1.31"; - - src = fetchFromGitHub { - owner = "stalwartlabs"; - repo = "webadmin"; - rev = "6f1368b8a1160341b385980accea489ee0e45440"; - hash = "sha256-/EWn/wiY6zFNhObfo11OkoGufcUODMYs18P3vTBbB8s="; - }; - - npmDeps = fetchNpmDeps { - name = "${finalAttrs.pname}-npm-deps"; - hash = "sha256-na1HEueX8w7kuDp8LEtJ0nD1Yv39cyk6sEMpS1zix2s="; - }; - - cargoHash = "sha256-Q05+wH9+NfkfmEDJFLuWVQ7wuDeEu9h1XmOMN6SYdyU="; - - postPatch = '' - # Using local tailwindcss for compilation - substituteInPlace Trunk.toml --replace-fail "npx tailwindcss" "tailwindcss" - ''; - - nativeBuildInputs = [ - binaryen - llvmPackages.bintools-unwrapped - nodejs - npmHooks.npmConfigHook - tailwindcss_3 - trunk - # needs to match with wasm-bindgen version in upstreams Cargo.lock - wasm-bindgen-cli_0_2_93 - - zip - ]; - - NODE_PATH = "$npmDeps"; - - buildPhase = '' - trunk build --offline --frozen --release - ''; - - installPhase = '' - cd dist - mkdir -p $out - zip -r $out/webadmin.zip * - ''; - - passthru = { - updateScript = nix-update-script { }; - }; - - meta = { - description = "Secure & modern all-in-one mail server Stalwart (webadmin module)"; - homepage = "https://github.com/stalwartlabs/webadmin"; - changelog = "https://github.com/stalwartlabs/webadmin/blob/${finalAttrs.src.tag}/CHANGELOG.md"; - license = lib.licenses.agpl3Only; - inherit (stalwart-mail.meta) maintainers; - }; -}) diff --git a/nix/packages/kanidm-dovecot-service-account-provision/default.nix b/nix/packages/kanidm-dovecot-service-account-provision/default.nix new file mode 100644 index 0000000..b699a63 --- /dev/null +++ b/nix/packages/kanidm-dovecot-service-account-provision/default.nix @@ -0,0 +1,21 @@ +{ pkgs, ... }: +pkgs.writeShellApplication { + name = "kanidm-dovecot-service-account-provision"; + runtimeInputs = [ + pkgs.openssh + pkgs.openbao + pkgs.uutils-coreutils-noprefix + + ]; + text = '' + tmpfile="$(mktemp)" + exec {fd}>&1 + trap 'rm "$tmpfile"' EXIT + api_token="$(ssh -tt security.kaareskovgaard.net -- kanidm-provision-dovecot-service-account | tee >(cat - >&"$fd"))" + # Using tail to do this ends up including a \r character, so remove it. + api_token="$(echo "$api_token" | tail -n1 | head -c-2)" + exec {fd}<&- + echo "$api_token" | bao kv put -mount=kanidm "ldap/dovecot" apiToken=- + >&2 echo "API token stored in vault" + ''; +} diff --git a/nix/systems/aarch64-linux/mx.kaareskovgaard.net/default.nix b/nix/systems/aarch64-linux/mx.kaareskovgaard.net/default.nix index b5e223b..6976ed5 100644 --- a/nix/systems/aarch64-linux/mx.kaareskovgaard.net/default.nix +++ b/nix/systems/aarch64-linux/mx.kaareskovgaard.net/default.nix @@ -29,7 +29,7 @@ khscodes.infrastructure = { kanidm-client-application = { enable = true; - appName = "stalwart"; + appName = "dovecot"; secretOwner = "dovecot2"; perms = "0600"; }; @@ -50,22 +50,12 @@ prefixPath = "dkim"; }; }; + ldap = { + mount = "kanidm"; + path = "ldap/dovecot"; + }; }; }; - services.stalwart-mail.settings.directory.memory = { - type = "memory"; - principals = [ - { - class = "individual"; - name = "khs"; - fullName = "Kaare Agerlin Skovgaard"; - email = [ - "kaare@agerlinskovgaard.dk" - "kaare@agerlin-skovgaard.dk" - ]; - } - ]; - }; services.roundcube = { enable = true; hostName = "mail.kaareskovgaard.net"; @@ -77,11 +67,11 @@ $config['imap_host'] = "ssl://${config.khscodes.networking.fqdn}"; $config['oauth_provider'] = 'generic'; $config['oauth_provider_name'] = 'Kanidm'; - $config['oauth_client_id'] = 'stalwart'; + $config['oauth_client_id'] = 'dovecot'; $config['oauth_client_secret'] = file_get_contents("${config.khscodes.infrastructure.kanidm-client-application.secretFile}"); $config['oauth_auth_uri'] = 'https://login.kaareskovgaard.net/ui/oauth2'; $config['oauth_token_uri'] = 'https://login.kaareskovgaard.net/oauth2/token'; - $config['oauth_identity_uri'] = 'https://login.kaareskovgaard.net/oauth2/openid/stalwart/userinfo'; + $config['oauth_identity_uri'] = 'https://login.kaareskovgaard.net/oauth2/openid/dovecot/userinfo'; $config['oauth_identity_fields'] = ['preferred_username']; $config['oauth_scope'] = 'email openid profile'; ''; diff --git a/nix/systems/aarch64-linux/mx.kaareskovgaard.net/disko.nix b/nix/systems/aarch64-linux/mx.kaareskovgaard.net/disko.nix index 3c8f7db..f566ba2 100644 --- a/nix/systems/aarch64-linux/mx.kaareskovgaard.net/disko.nix +++ b/nix/systems/aarch64-linux/mx.kaareskovgaard.net/disko.nix @@ -34,7 +34,7 @@ in exec = lib.getExe zfsLoadKeyScript; restartUnits = [ "postfix.service" - "dovecot.service" + "dovecot2.service" "rspamd.service" ]; } diff --git a/nix/systems/aarch64-linux/security.kaareskovgaard.net/README.md b/nix/systems/aarch64-linux/security.kaareskovgaard.net/README.md index a0e7460..29e7239 100644 --- a/nix/systems/aarch64-linux/security.kaareskovgaard.net/README.md +++ b/nix/systems/aarch64-linux/security.kaareskovgaard.net/README.md @@ -41,3 +41,11 @@ nix run '.#configure-instance' -- security-kaareskovgaard.net ``` Then `nix run '.#bitwarden-to-vault` can transfer the needed Bitwarden secrets to vault, enabling other instances to not rely on Bitwarden. + +## Provision Dovecot LDAP service account + +Lastly, provision the service account for Dovecot (need to log into OpenBAO with `bao login -method=oidc` first): + +```bash +nix run '.#kanidm-dovecot-service-account-provision` +``` diff --git a/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix index 7d3c376..6c44a12 100644 --- a/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix +++ b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix @@ -33,14 +33,18 @@ let send "$password\n" expect eof EOD - kanidm person credential create-reset-token --url https://login.kaareskovgaard.net "$username" trap "kanidm logout --url https://login.kaareskovgaard.net" EXIT + kanidm person credential create-reset-token --url https://login.kaareskovgaard.net "$username" ''; }; in { - imports = [ ./kanidm_application.nix ]; + imports = [ + ./kanidm_application.nix + ./kanidm_dovecot.nix + ./kanidm_ldap.nix + ]; config = { khscodes.security.kanidm.applications = { openbao = { @@ -120,7 +124,7 @@ in }; }; }; - stalwart = { + dovecot = { allowedRedirectUris = [ "https://mail.kaareskovgaard.net/index.php/login/oauth" ]; landingUri = "https://mail.kaareskovgaard.net"; displayName = "Mail"; diff --git a/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_dovecot.nix b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_dovecot.nix new file mode 100644 index 0000000..22fbda0 --- /dev/null +++ b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_dovecot.nix @@ -0,0 +1,92 @@ +{ + pkgs, + config, + ... +}: +let + domain = "login.kaareskovgaard.net"; + provisionServiceAccount = pkgs.writeShellApplication { + runtimeInputs = [ + config.services.kanidm.package + pkgs.uutils-coreutils-noprefix + pkgs.jq + pkgs.gnugrep + pkgs.expect + ]; + name = "kanidm-provision-dovecot-service-account"; + text = '' + KANIDM_URL="https://${domain}" + export KANIDM_URL + >&2 echo "Resetting password for idm_admin, please provide sudo password" + password="$(sudo -u kanidm kanidmd recover-account -c /etc/kanidm/server.toml -o json idm_admin 2> /dev/null | grep '^{' | jq --raw-output '.password')" + expect <&2 + spawn kanidm login --name idm_admin + expect "Enter password" + send "$password\n" + expect eof + EOD + trap "kanidm logout > /dev/null" EXIT + + dovecot_svc="$(kanidm service-account get dovecot_ldap)" + if [[ "$dovecot_svc" == "No matching entries" ]]; then + kanidm service-account create dovecot_ldap "Dovecot LDAP" admin >&2 + fi + kanidm group set-members idm_mail_servers dovecot_ldap >&2 + kanidm logout + password="$(sudo -u kanidm kanidmd recover-account -c /etc/kanidm/server.toml -o json admin 2> /dev/null | grep '^{' | jq --raw-output '.password')" + expect <&2 + spawn kanidm login --name admin + expect "Enter password" + send "$password\n" + expect eof + EOD + + api_token="$(kanidm service-account api-token status dovecot_ldap)" + if [[ "$api_token" != "No api tokens exist" ]]; then + # Output is currently some lines, one of which starts with 'token_id: ' and then followed by the token id. So extract it + token_id="$(echo "$api_token" | awk '/^token_id: /{print $2}')" + kanidm service-account api-token destroy dovecot_ldap "$token_id" >&2 + fi + token="$(kanidm service-account api-token generate dovecot_ldap "LDAP")" + + echo "$token" | tail -n1 + ''; + }; + setPosixPassword = pkgs.writeShellApplication { + runtimeInputs = [ + config.services.kanidm.package + pkgs.uutils-coreutils-noprefix + pkgs.jq + pkgs.gnugrep + pkgs.expect + ]; + name = "kanidm-set-posix-password"; + text = '' + user="''${1:-}" + if [[ "$user" == "" ]]; then + >&2 echo "Usage kanidm-set-posix-password " + exit 1 + fi + KANIDM_URL="https://${domain}" + export KANIDM_URL + >&2 echo "Resetting password for idm_admin, please provide sudo password" + password="$(sudo -u kanidm kanidmd recover-account -c /etc/kanidm/server.toml -o json idm_admin 2> /dev/null | grep '^{' | jq --raw-output '.password')" + expect <&2 + spawn kanidm login --name idm_admin + expect "Enter password" + send "$password\n" + expect eof + EOD + trap "kanidm logout > /dev/null" EXIT + kanidm person posix set --name idm_admin "$user" + ''; + }; +in +{ + config = { + environment.systemPackages = [ + provisionServiceAccount + setPosixPassword + ]; + }; +} diff --git a/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_ldap.nix b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_ldap.nix new file mode 100644 index 0000000..b9a14dd --- /dev/null +++ b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_ldap.nix @@ -0,0 +1,8 @@ +{ + config = { + services.kanidm.serverSettings = { + ldapbindaddress = "[::]:636"; + }; + networking.firewall.allowedTCPPorts = [ 636 ]; + }; +}