1 Commits

Author SHA1 Message Date
1d7eec7ad3 system: add kanidm PAM/NSS client module
Some checks failed
Run nix flake check / flake-check (push) Has been cancelled
Add homelab.kanidm.enable option for central authentication via Kanidm.
The module configures:
- PAM/NSS integration with kanidm-unixd
- Client connection to auth.home.2rjus.net
- Login authorization for ssh-users group

Enable on testvm01-03 for testing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 13:43:41 +01:00
7 changed files with 20 additions and 457 deletions

View File

@@ -66,9 +66,9 @@ This future migration path is a strong argument for Kanidm over LDAP-only soluti
- Vault integration for idm_admin password
- LDAPS on port 636
2. **Configure provisioning**
- Groups provisioned declaratively: `admins`, `users`, `ssh-users`
- Users managed imperatively via CLI (allows setting POSIX passwords in one step)
2. **Configure declarative provisioning**
- Groups: `admins`, `users`, `ssh-users`
- User: `torjus` (member of all groups)
- POSIX attributes enabled (UID/GID range 65,536-69,999)
3. **Test NAS integration** (in progress)
@@ -80,16 +80,14 @@ This future migration path is a strong argument for Kanidm over LDAP-only soluti
- Grafana
- Other services as needed
5. **Create client module** in `system/` for PAM/NSS
- Module: `system/kanidm-client.nix`
- `homelab.kanidm.enable = true` enables PAM/NSS
- Short usernames (not SPN format)
- Home directory symlinks via `home_alias`
- Enabled on test tier: testvm01, testvm02, testvm03
5. **Create client module** in `system/` for PAM/NSS
- Enable on all hosts that need central auth
- Configure trusted CA
6. **Documentation**
- `docs/user-management.md` - CLI workflows, troubleshooting
- User/group creation procedures verified working
6. **Documentation**
- User management procedures
- Adding new OAuth2 clients
- Troubleshooting PAM/NSS issues
## Progress
@@ -108,37 +106,14 @@ This future migration path is a strong argument for Kanidm over LDAP-only soluti
- Prometheus monitoring scrape target configured
**Provisioned entities:**
- Groups: `admins`, `users`, `ssh-users` (declarative)
- Users managed via CLI (imperative)
- Groups: `admins`, `users`, `ssh-users`
- User: `torjus` (member of all groups, POSIX enabled with GID 65536)
**Verified working:**
- WebUI login with idm_admin
- LDAP bind and search with POSIX-enabled user
- LDAPS with valid internal CA certificate
### Completed (2026-02-08) - PAM/NSS Client
**Client module deployed (`system/kanidm-client.nix`):**
- `homelab.kanidm.enable = true` enables PAM/NSS integration
- Connects to auth.home.2rjus.net
- Short usernames (`torjus` instead of `torjus@home.2rjus.net`)
- Home directory symlinks (`/home/torjus` → UUID-based dir)
- Login restricted to `ssh-users` group
**Enabled on test tier:**
- testvm01, testvm02, testvm03
**Verified working:**
- User/group resolution via `getent`
- SSH login with Kanidm unix passwords
- Home directory creation with symlinks
- Imperative user/group creation via CLI
**Documentation:**
- `docs/user-management.md` with full CLI workflows
- Password requirements (min 10 chars)
- Troubleshooting guide (nscd, cache invalidation)
### UID/GID Range (Resolved)
**Range: 65,536 - 69,999** (manually allocated)
@@ -153,9 +128,10 @@ Rationale:
### Next Steps
1. Enable PAM/NSS on production hosts (after test tier validation)
1. Deploy to monitoring01 to enable Prometheus scraping
2. Configure TrueNAS LDAP client for NAS integration testing
3. Add OAuth2 clients (Grafana first)
4. Create PAM/NSS client module for other hosts
## References

View File

@@ -1,267 +0,0 @@
# User Management with Kanidm
Central authentication for the homelab using Kanidm.
## Overview
- **Server**: kanidm01.home.2rjus.net (auth.home.2rjus.net)
- **WebUI**: https://auth.home.2rjus.net
- **LDAPS**: port 636
## CLI Setup
The `kanidm` CLI is available in the devshell:
```bash
nix develop
# Login as idm_admin
kanidm login --name idm_admin --url https://auth.home.2rjus.net
```
## User Management
POSIX users are managed imperatively via the `kanidm` CLI. This allows setting
all attributes (including UNIX password) in one workflow.
### Creating a POSIX User
```bash
# Create the person
kanidm person create <username> "<Display Name>"
# Add to groups
kanidm group add-members ssh-users <username>
# Enable POSIX (UID is auto-assigned)
kanidm person posix set <username>
# Set UNIX password (required for SSH login, min 10 characters)
kanidm person posix set-password <username>
# Optionally set login shell
kanidm person posix set <username> --shell /bin/zsh
```
### Example: Full User Creation
```bash
kanidm person create testuser "Test User"
kanidm group add-members ssh-users testuser
kanidm person posix set testuser
kanidm person posix set-password testuser
kanidm person get testuser
```
After creation, verify on a client host:
```bash
getent passwd testuser
ssh testuser@testvm01.home.2rjus.net
```
### Viewing User Details
```bash
kanidm person get <username>
```
### Removing a User
```bash
kanidm person delete <username>
```
## Group Management
Groups for POSIX access are also managed via CLI.
### Creating a POSIX Group
```bash
# Create the group
kanidm group create <group-name>
# Enable POSIX with a specific GID
kanidm group posix set <group-name> --gidnumber <gid>
```
### Adding Members
```bash
kanidm group add-members <group-name> <username>
```
### Viewing Group Details
```bash
kanidm group get <group-name>
kanidm group list-members <group-name>
```
### Example: Full Group Creation
```bash
kanidm group create testgroup
kanidm group posix set testgroup --gidnumber 68010
kanidm group add-members testgroup testuser
kanidm group get testgroup
```
After creation, verify on a client host:
```bash
getent group testgroup
```
### Current Groups
| Group | GID | Purpose |
|-------|-----|---------|
| ssh-users | 68000 | SSH login access |
| admins | 68001 | Administrative access |
| users | 68002 | General users |
### UID/GID Allocation
Kanidm auto-assigns UIDs/GIDs from its configured range. For manually assigned GIDs:
| Range | Purpose |
|-------|---------|
| 65,536+ | Users (auto-assigned) |
| 68,000 - 68,999 | Groups (manually assigned) |
## PAM/NSS Client Configuration
Enable central authentication on a host:
```nix
homelab.kanidm.enable = true;
```
This configures:
- `services.kanidm.enablePam = true`
- Client connection to auth.home.2rjus.net
- Login authorization for `ssh-users` group
- Short usernames (`torjus` instead of `torjus@home.2rjus.net`)
- Home directory symlinks (`/home/torjus` → UUID-based directory)
### Enabled Hosts
- testvm01, testvm02, testvm03 (test tier)
### Options
```nix
homelab.kanidm = {
enable = true;
server = "https://auth.home.2rjus.net"; # default
allowedLoginGroups = [ "ssh-users" ]; # default
};
```
### Home Directories
Home directories use UUID-based paths for stability (so renaming a user doesn't
require moving their home directory). Symlinks provide convenient access:
```
/home/torjus -> /home/e4f4c56c-4aee-4c20-846f-90cb69807733
```
The symlinks are created by `kanidm-unixd-tasks` on first login.
## Testing
### Verify NSS Resolution
```bash
# Check user resolution
getent passwd <username>
# Check group resolution
getent group <group-name>
```
### Test SSH Login
```bash
ssh <username>@<hostname>.home.2rjus.net
```
## Troubleshooting
### "PAM user mismatch" error
SSH fails with "fatal: PAM user mismatch" in logs. This happens when Kanidm returns
usernames in SPN format (`torjus@home.2rjus.net`) but SSH expects short names (`torjus`).
**Solution**: Configure `uid_attr_map = "name"` in unixSettings (already set in our module).
Check current format:
```bash
getent passwd torjus
# Should show: torjus:x:65536:...
# NOT: torjus@home.2rjus.net:x:65536:...
```
### User resolves but SSH fails immediately
The user's login group (e.g., `ssh-users`) likely doesn't have POSIX enabled:
```bash
# Check if group has POSIX
getent group ssh-users
# If empty, enable POSIX on the server
kanidm group posix set ssh-users --gidnumber 68000
```
### User doesn't resolve via getent
1. Check kanidm-unixd service is running:
```bash
systemctl status kanidm-unixd
```
2. Check unixd can reach server:
```bash
kanidm-unix status
# Should show: system: online, Kanidm: online
```
3. Check client can reach server:
```bash
curl -s https://auth.home.2rjus.net/status
```
4. Check user has POSIX enabled on server:
```bash
kanidm person get <username>
```
5. Restart nscd to clear stale cache:
```bash
systemctl restart nscd
```
6. Invalidate kanidm cache:
```bash
kanidm-unix cache-invalidate
```
### Changes not taking effect after deployment
NixOS uses nsncd (a Rust reimplementation of nscd) for NSS caching. After deploying
kanidm-unixd config changes, you may need to restart both services:
```bash
systemctl restart kanidm-unixd
systemctl restart nscd
```
### Test PAM authentication directly
Use the kanidm-unix CLI to test PAM auth without SSH:
```bash
kanidm-unix auth-test --name <username>
```

View File

@@ -207,7 +207,6 @@
pkgs.ansible
pkgs.opentofu
pkgs.openbao
pkgs.kanidm_1_8
(pkgs.callPackage ./scripts/create-host { })
homelab-deploy.packages.${pkgs.system}.default
];

View File

@@ -17,8 +17,7 @@
};
};
# Provision base groups only - users are managed via CLI
# See docs/user-management.md for details
# Provisioning - initial users/groups
provision = {
enable = true;
idmAdminPasswordFile = config.vault.secrets.kanidm-idm-admin.outputDir;
@@ -29,7 +28,10 @@
ssh-users = { };
};
# Regular users (persons) are managed imperatively via kanidm CLI
persons.torjus = {
displayName = "Torjus";
groups = [ "admins" "users" "ssh-users" ];
};
};
};
@@ -44,7 +46,7 @@
extraDomainNames = [ "${config.networking.hostName}.home.2rjus.net" ];
};
# Vault secret for idm_admin password (used for provisioning)
# Vault secret for idm_admin password
vault.secrets.kanidm-idm-admin = {
secretPath = "kanidm/idm-admin-password";
extractKey = "password";

View File

@@ -9,7 +9,6 @@
./motd.nix
./packages.nix
./nix.nix
./pipe-to-loki.nix
./root-user.nix
./pki/root-ca.nix
./sshd.nix

View File

@@ -30,12 +30,6 @@ in
unixSettings = {
pam_allowed_login_groups = cfg.allowedLoginGroups;
# Use short names (torjus) instead of SPN format (torjus@home.2rjus.net)
# This prevents "PAM user mismatch" errors with SSH
uid_attr_map = "name";
gid_attr_map = "name";
# Create symlink /home/torjus -> /home/torjus@home.2rjus.net
home_alias = "name";
};
};
};

View File

@@ -1,140 +0,0 @@
{
config,
pkgs,
lib,
...
}:
let
pipe-to-loki = pkgs.writeShellApplication {
name = "pipe-to-loki";
runtimeInputs = with pkgs; [
curl
jq
util-linux
coreutils
];
text = ''
set -euo pipefail
LOKI_URL="http://monitoring01.home.2rjus.net:3100/loki/api/v1/push"
HOSTNAME=$(hostname)
SESSION_ID=""
RECORD_MODE=false
usage() {
echo "Usage: pipe-to-loki [--id ID] [--record]"
echo ""
echo "Send command output or interactive sessions to Loki."
echo ""
echo "Options:"
echo " --id ID Set custom session ID (default: auto-generated)"
echo " --record Start interactive recording session"
echo ""
echo "Examples:"
echo " command | pipe-to-loki # Pipe command output"
echo " command | pipe-to-loki --id foo # Pipe with custom ID"
echo " pipe-to-loki --record # Start recording session"
exit 1
}
generate_id() {
local random_chars
random_chars=$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' \n')
echo "''${HOSTNAME}-$(date +%s)-''${random_chars}"
}
send_to_loki() {
local content="$1"
local type="$2"
local timestamp_ns
timestamp_ns=$(date +%s%N)
local payload
payload=$(jq -n \
--arg job "pipe-to-loki" \
--arg host "$HOSTNAME" \
--arg type "$type" \
--arg id "$SESSION_ID" \
--arg ts "$timestamp_ns" \
--arg content "$content" \
'{
streams: [{
stream: {
job: $job,
host: $host,
type: $type,
id: $id
},
values: [[$ts, $content]]
}]
}')
if curl -s -X POST "$LOKI_URL" \
-H "Content-Type: application/json" \
-d "$payload" > /dev/null; then
return 0
else
echo "Error: Failed to send to Loki" >&2
return 1
fi
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--id)
SESSION_ID="$2"
shift 2
;;
--record)
RECORD_MODE=true
shift
;;
--help|-h)
usage
;;
*)
echo "Unknown option: $1" >&2
usage
;;
esac
done
# Generate ID if not provided
if [[ -z "$SESSION_ID" ]]; then
SESSION_ID=$(generate_id)
fi
if $RECORD_MODE; then
# Session recording mode
SCRIPT_FILE=$(mktemp)
trap 'rm -f "$SCRIPT_FILE"' EXIT
echo "Recording session $SESSION_ID... (exit to send)"
# Use script to record the session
script -q "$SCRIPT_FILE"
# Read the transcript and send to Loki
content=$(cat "$SCRIPT_FILE")
if send_to_loki "$content" "session"; then
echo "Session $SESSION_ID sent to Loki"
fi
else
# Pipe mode - read from stdin
if [[ -t 0 ]]; then
echo "Error: No input provided. Pipe a command or use --record for interactive mode." >&2
exit 1
fi
content=$(cat)
if send_to_loki "$content" "command"; then
echo "Sent to Loki with id: $SESSION_ID"
fi
fi
'';
};
in
{
environment.systemPackages = [ pipe-to-loki ];
}