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.
This commit is contained in:
parent
cd8a0db1b6
commit
6a1aca24a9
14 changed files with 245 additions and 376 deletions
|
@ -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,24 +83,28 @@ 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
|
||||
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 = stalwart
|
||||
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;
|
||||
|
|
76
nix/modules/nixos/infrastructure/mailserver/ldap.nix
Normal file
76
nix/modules/nixos/infrastructure/mailserver/ldap.nix
Normal file
|
@ -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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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" ''
|
||||
mtaStsWellKnown = pkgs.writeTextFile {
|
||||
name = "mta-sts.txt";
|
||||
text = ''
|
||||
version: STSv1
|
||||
mode: enforce
|
||||
max_age: 600
|
||||
mx: ${fqdn}
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
|
|
@ -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}"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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=<store_type> 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=<store_type> 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 "<invalid@domain.org> (failed to lookup" but got From: "Mail Delivery Subsystem" <MAILER-DAEMON@localhost>
|
||||
"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=<store_type> cargo test`: NotPresent
|
||||
"store::store_tests"
|
||||
# Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent
|
||||
"cluster::cluster_tests"
|
||||
# Missing store type. Try running `STORE=<store_type> 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
|
||||
];
|
||||
};
|
||||
})
|
|
@ -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;
|
||||
};
|
||||
})
|
|
@ -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;
|
||||
};
|
||||
})
|
|
@ -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"
|
||||
'';
|
||||
}
|
|
@ -29,7 +29,7 @@
|
|||
khscodes.infrastructure = {
|
||||
kanidm-client-application = {
|
||||
enable = true;
|
||||
appName = "stalwart";
|
||||
appName = "dovecot";
|
||||
secretOwner = "dovecot2";
|
||||
perms = "0600";
|
||||
};
|
||||
|
@ -50,21 +50,11 @@
|
|||
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;
|
||||
|
@ -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';
|
||||
'';
|
||||
|
|
|
@ -34,7 +34,7 @@ in
|
|||
exec = lib.getExe zfsLoadKeyScript;
|
||||
restartUnits = [
|
||||
"postfix.service"
|
||||
"dovecot.service"
|
||||
"dovecot2.service"
|
||||
"rspamd.service"
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
```
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 <<EOD >&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 <<EOD >&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 <user>"
|
||||
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 <<EOD >&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
|
||||
];
|
||||
};
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
config = {
|
||||
services.kanidm.serverSettings = {
|
||||
ldapbindaddress = "[::]:636";
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 636 ];
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue