Compare commits

..
Author SHA1 Message Date
Richie d08915b541 feat(kanidm): add Kanidm service configuration and documentation 2026-10-16 21:16:29 -04:00
4 changed files with 211 additions and 1 deletions

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
# Kanidm on Jeeves
Kanidm is served at **https://auth.tmmworkshop.com**. Portal terminates public
HTTPS and forwards requests over Tailscale to Jeeves on port 8443, verifying
Jeeves's TLS certificate. Only the Tailscale interface exposes that backend port.
The service module is automatically imported by `services/default.nix`.
`services/kanidm.nix` pins Kanidm to the 1.11 release series; review the
[upgrade guide](https://kanidm.github.io/kanidm/stable/server_updates.html)
before changing that series. The flake lock determines the patch release.
## Before deployment
1. Point `auth.tmmworkshop.com` at Portal's public address, using the same DNS
arrangement as `gitea.tmmworkshop.com`. Port 80 must reach Portal for ACME.
2. Enable [HTTPS certificates in Tailscale](https://tailscale.com/kb/1153/enabling-https).
Jeeves must be able to obtain a certificate for `jeeves.taile39c31.ts.net`.
3. Allow Portal to reach Jeeves on TCP 8443 in your Tailscale policy.
The trusted proxy address in `services/kanidm.nix` is Portal's current Tailscale
IPv4 address, `100.80.102.33`. Update it if Portal is re-enrolled with a new address.
HAProxy connects over IPv4 and replaces incoming `X-Forwarded-For` headers.
## Deploy
Ensure the new Nix file is tracked by Git before using this Git-backed flake.
From this repository, using the existing SSH aliases:
```sh
nixos-rebuild switch --flake .#jeeves --target-host jeeves --sudo
nixos-rebuild switch --flake .#portal-1 --target-host portal-1 --sudo
```
Deploy Jeeves first, then Portal. Portal manages the public Let's Encrypt
certificate. `kanidm-tls.service` on Jeeves obtains the backend certificate and
checks for renewal daily, restarting Kanidm only when the certificate changes.
Private keys are generated at runtime and never stored in this repository or
the Nix store.
On Jeeves, check startup:
```sh
sudo systemctl status kanidm-tls.service kanidm.service
sudo journalctl -u kanidm-tls.service -u kanidm.service -n 100
```
If the initial certificate request fails, fix the Tailscale HTTPS configuration
and run `sudo systemctl restart kanidm.service`; its dependency retries the
certificate request. A successful oneshot certificate service normally appears
as inactive after finishing.
After both hosts are deployed:
```sh
curl --fail https://auth.tmmworkshop.com/status
```
## First login
On Jeeves, recover the initial administrator passwords interactively:
```sh
sudo kanidmd recover-account admin
sudo kanidmd recover-account idm_admin
kanidm login --name idm_admin
kanidm person create richie "Richie"
kanidm person credential create-reset-token richie
```
Store the recovered passwords securely. Follow the reset URL to enroll your
personal account's passkey. `admin` manages the Kanidm server; `idm_admin`
manages people, groups, and application integrations. Creating `richie` does
not automatically grant administrator rights. The client is installed and
configured to use `https://auth.tmmworkshop.com`.
Register each website as its own OAuth2/OIDC client and each backend identity
as a service account, then grant application scopes through groups. This
deployment does not create application clients or change existing sites' login
settings. See the [OAuth2 guide](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html)
and [service-account guide](https://kanidm.github.io/kanidm/stable/accounts/service_accounts.html).
## Storage and recovery
- Database: `/var/lib/kanidm/kanidm.db` on Jeeves's existing ZFS `/var` filesystem.
- Online backups: `/var/lib/kanidm/backups`, daily at 02:30, retaining 14.
- Backend certificate and key: `/var/lib/kanidm-tls`, readable by Kanidm.
- Server configuration: `/etc/kanidm/server.toml`.
The existing `root_pool/var` snapshot policy covers the database and backups.
Copy backups off Jeeves as part of disaster recovery; local snapshots do not
protect against loss of the host. Follow Kanidm's
[backup and restore instructions](https://kanidm.github.io/kanidm/stable/backup_and_restore.html).
The configured identity domain and origin are both `auth.tmmworkshop.com`.
Changing them after enrollment can affect passkeys and application integrations;
follow the [domain rename guide](https://kanidm.github.io/kanidm/stable/domain_rename.html).
+2
View File
@@ -8,6 +8,7 @@ let
grafana = 3000;
jellyfin_http = 8096;
jellyswarrm = 3100;
kanidm = 8443;
nix_binary_cache = 5000;
ollama = 11434;
open_webui = 8080;
@@ -28,6 +29,7 @@ in
ports.gems
ports.jellyfin_http
ports.jellyswarrm
ports.kanidm
ports.nix_binary_cache
ports.share
];
+101
View File
@@ -0,0 +1,101 @@
{ config, pkgs, ... }:
let
certificateDir = "/var/lib/kanidm-tls";
in
{
services.kanidm = {
# Pin the minor release: Kanidm upgrades require following its upgrade guide.
package = pkgs.kanidm_1_11;
server = {
enable = true;
settings = {
domain = "auth.tmmworkshop.com";
origin = "https://auth.tmmworkshop.com";
bindaddress = "0.0.0.0:8443";
db_fs_type = "zfs";
tls_chain = "${certificateDir}/certificate.pem";
tls_key = "${certificateDir}/private-key.pem";
# Only Portal may supply the original client address. HAProxy replaces
# X-Forwarded-For, so clients cannot supply a forged address themselves.
http_client_address_info.x-forward-for = [ "100.80.102.33" ];
# /var/lib/kanidm is on root_pool/var, which is already snapshotted.
online_backup = {
schedule = "30 2 * * *";
versions = 14;
};
};
};
client = {
enable = true;
settings.uri = "https://auth.tmmworkshop.com";
};
};
systemd = {
services = {
kanidm = {
requires = [ "kanidm-tls.service" ];
after = [ "kanidm-tls.service" ];
};
# Kanidm requires TLS even behind HAProxy. Use Jeeves's Tailscale
# certificate so Portal can verify both the CA and the backend hostname.
# HTTPS certificates must be enabled in the Tailscale admin console.
kanidm-tls = {
description = "Obtain and renew Kanidm's backend TLS certificate";
requires = [ "tailscaled.service" ];
wants = [ "tailscaled-autoconnect.service" ];
after = [
"tailscaled.service"
"tailscaled-autoconnect.service"
];
path = [
config.services.tailscale.package
pkgs.coreutils
pkgs.systemd
];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "kanidm";
StateDirectory = "kanidm-tls";
StateDirectoryMode = "0750";
RuntimeDirectory = "kanidm-tls";
RuntimeDirectoryMode = "0700";
UMask = "0077";
TimeoutStartSec = "2min";
};
script = ''
set -euo pipefail
tailscale cert --min-validity 720h \
--cert-file /run/kanidm-tls/certificate.pem \
--key-file /run/kanidm-tls/private-key.pem \
jeeves.taile39c31.ts.net
if ! cmp -s /run/kanidm-tls/certificate.pem ${certificateDir}/certificate.pem \
|| ! cmp -s /run/kanidm-tls/private-key.pem ${certificateDir}/private-key.pem; then
install -m 0640 -o root -g kanidm /run/kanidm-tls/certificate.pem ${certificateDir}/certificate.pem
install -m 0640 -o root -g kanidm /run/kanidm-tls/private-key.pem ${certificateDir}/private-key.pem
# The NixOS service bind-mounts the certificate files, so restart to
# pick up renewals. Do not wait on a service that depends on this one.
if systemctl is-active --quiet kanidm.service; then
systemctl --no-block try-restart kanidm.service
fi
fi
'';
};
};
timers.kanidm-tls = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
};
}
+12 -1
View File
@@ -32,7 +32,7 @@ defaults
#Application Setup
frontend ContentSwitching
bind [::]:80 v4v6
bind [::]:443 v4v6 ssl crt /var/lib/acme/audiobookshelf.tmmworkshop.com/full.pem crt /var/lib/acme/cache.tmmworkshop.com/full.pem crt /var/lib/acme/gems.tmmworkshop.com/full.pem crt /var/lib/acme/jellyfin.tmmworkshop.com/full.pem crt /var/lib/acme/share.tmmworkshop.com/full.pem crt /var/lib/acme/gitea.tmmworkshop.com/full.pem crt
bind [::]:443 v4v6 ssl crt /var/lib/acme/audiobookshelf.tmmworkshop.com/full.pem crt /var/lib/acme/auth.tmmworkshop.com/full.pem crt /var/lib/acme/cache.tmmworkshop.com/full.pem crt /var/lib/acme/gems.tmmworkshop.com/full.pem crt /var/lib/acme/jellyfin.tmmworkshop.com/full.pem crt /var/lib/acme/share.tmmworkshop.com/full.pem crt /var/lib/acme/gitea.tmmworkshop.com/full.pem
mode http
# ACME challenge routing (must be first)
@@ -40,6 +40,7 @@ frontend ContentSwitching
# Host ACLs (defined early so rate-limiting can scope to a single vhost)
acl host_audiobookshelf hdr(host) -i audiobookshelf.tmmworkshop.com
acl host_auth hdr(host) -i auth.tmmworkshop.com
acl host_cache hdr(host) -i cache.tmmworkshop.com
acl host_jellyfin hdr(host) -i jellyfin.tmmworkshop.com
acl host_share hdr(host) -i share.tmmworkshop.com
@@ -111,6 +112,7 @@ frontend ContentSwitching
use_backend acme_challenge if is_acme
use_backend audiobookshelf_nodes if host_audiobookshelf
use_backend kanidm if host_auth
use_backend cache_nodes if host_cache
use_backend jellyfin if host_jellyfin
use_backend share_nodes if host_share
@@ -141,6 +143,15 @@ backend audiobookshelf_nodes
mode http
server server jeeves:8000
backend kanidm
mode http
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Proto https
option httpchk
http-check send meth GET uri /status ver HTTP/1.1 hdr Host auth.tmmworkshop.com
http-check expect status 200
server kanidm jeeves.taile39c31.ts.net:8443 ssl verify required ca-file /etc/ssl/certs/ca-certificates.crt verifyhost jeeves.taile39c31.ts.net sni str(jeeves.taile39c31.ts.net) check check-sni jeeves.taile39c31.ts.net resolve-prefer ipv4
backend cache_nodes
mode http
server server jeeves:5000