Files
AZ-NIX-CLIENTS/roles/thin-client/identity/ad.nix
T
m3ta-chiron 98dc2917e8 feat: +thin-client role + AZ-TC-01..03 pilot
Auf Basis des Grilling-Sessions mit 20 design decisions umgesetztes
v1-Skeleton der Thin-Client-Rolle plus 3 Pilot-Hosts.

Architektur:
- roles/thin-client/ als geschlossene Rolle (default.nix compose + 7
  Subdirectories: hardware, session, identity, network, peripherals,
  apps, monitoring, deployment)
- hosts/AZ-TC-NN/default.nix als ~20-Zeilen-Wrapper pro Fleet-Host
- flake.nix instanziiert AZ-TC-01..03 via Fleet-Helper
- secrets.nix mit per-host agenix-Secret-Stubs

Submodule:
- hardware: dell-optiplex-micro + generic-x86_64-uefi Fallback
- identity: AD (sssd/krb5/keytab via agenix), lokale Notfalluser
  (sascha.koenig + jannik.mueller ohne m3ta-home), sudo-Policy
- session: KDE Plasma 6 + Wayland + SDDM, Branding (Wallpaper + Footer),
  PipeWire Audio
- network: NetworkManager + wpa_supplicant + 802.1X EAP-TLS, NetBird
  + SSH via NetBird, systemd-resolved (Corp + NetBird DNS), hardened
  firewall, OpenSSH
- peripherals: CUPS mit Pull-Print-Queue, pam_mount für DFS-Shares
- apps: Chromium (ManagedBookmarks, Bitwarden force-install, no local
  passwords), Office-Web .desktop-Shortcuts, Remmina (mehrere TS,
  Kerberos SSO), RustDesk Client + Daemon, OBS Studio, Autostart
- monitoring: node_exporter → Pushgateway, Alloy (stub für Loki),
  Snipe-IT Asset-Checkin (stub)
- deployment: Disko BTRFS-Layout, auto-upgrade daily + reboot window,
  snapper snapshots

Build-Validierung: 'nix flake check' bestanden für AZ-TC-01/02/03.

Siehe roles/thin-client/README.md für den Provisionierungs-Workflow
und die Liste der noch auszufüllenden Platzhalter (TODO-Kommentare
in den jeweiligen Modulen).
2026-07-29 08:34:01 +02:00

168 lines
5.1 KiB
Nix

# roles/thin-client/identity/ad.nix
#
# Active Directory integration for Thin Clients.
#
# Real realm: AZ-GROUP
# DNS domain: az-group.local
# Join mechanism: Pre-created computer accounts + keytab via agenix
# (no interactive realm join, no service account with join privileges).
#
# Provisioning per host (admin-side, one-shot):
# 1. adcli precreate --computer-name=AZ-TC-01$ \
# --domain=az-group.local \
# --host-fqdn=AZ-TC-01.az-group.local \
# --login-type=computer \
# --os-name="NixOS" --os-version="25.11" \
# --domain-ou="OU=ThinClients,DC=az-group,DC=local" \
# <admin>@AZ-GROUP
# 2. adcli join --computer-name=AZ-TC-01$ \
# --domain=az-group.local \
# --host-fqdn=AZ-TC-01.az-group.local \
# --login-type=computer \
# --user=<admin> --verbose \
# -K /tmp/AZ-TC-01.keytab
# 3. agenix -e secrets/AZ-TC-01-krb5-keytab.age (paste /tmp/AZ-TC-01.keytab)
# 4. rm /tmp/AZ-TC-01.keytab
# 5. Deploy; the keytab decrypts to /etc/krb5.keytab on the host.
#
# SSSD resolves users/groups via AD; kerberos auth via keytab for the
# machine account. User login uses their AD password (offline-capable via
# cache_credentials=true).
{
config,
lib,
pkgs,
...
}: let
cfg = config.az.tc;
hostname = config.networking.hostName;
domain = "az-group.local";
realm = "AZ-GROUP";
in {
options.az.tc.ad = {
ou = lib.mkOption {
type = lib.types.str;
default = "OU=ThinClients,DC=az-group,DC=local";
description = ''
AD Organizational Unit where Thin Client computer objects live.
Override if your AD layout differs. Used for documentation and
the pre-create workflow; not consumed at runtime.
'';
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
adcli
sssd
samba4Full
krb5
realmd
oddjob
];
# Kerberos client
security.krb5 = {
enable = true;
settings = {
libdefaults = {
default_realm = realm;
udp_preference_limit = 0;
forwardable = true;
proxiable = true;
rdns = false;
};
domain_realm = {
".az-group.local" = realm;
"az-group.local" = realm;
};
realms = {
"${realm}" = {
kdc = ["az-dc01.az-group.local" "az-dc02.az-group.local"];
admin_server = "az-dc01.az-group.local";
};
};
};
};
# PAM: create per-user /home on first login (used by domain users)
security.pam = {
services.login.makeHomeDir = true;
services.sddm.makeHomeDir = true;
services.sshd.makeHomeDir = true;
makeHomeDir.umask = "077";
};
services.nscd.enable = true;
services.sssd = {
enable = true;
config = ''
[sssd]
domains = az-group.local
config_file_version = 2
services = nss, pam
[domain/az-group.local]
id_provider = ad
auth_provider = ad
access_provider = ad
chpass_provider = ad
ad_domain = ${domain}
ad_server = az-dc01.az-group.local, az-dc02.az-group.local
krb5_realm = ${realm}
krb5_server = az-dc01.az-group.local, az-dc02.az-group.local
krb5_keytab = /etc/krb5.keytab
krb5_store_password_if_offline = True
cache_credentials = True
use_fully_qualified_names = false
fallback_homedir = /home/%u
override_shell = /run/current-system/sw/bin/bash
default_shell = /run/current-system/sw/bin/bash
ad_gpo_access_control = permissive
enumerate = true
ldap_id_mapping = false
'';
};
# DNS — Thin Clients use AD DCs as resolver (NetBird DNS overlays
# for corp-VPN-only domains via systemd-resolved; see network/dns.nix)
networking.nameservers = lib.mkDefault ["az-dc01.az-group.local" "az-dc02.az-group.local"];
networking.domain = lib.mkDefault domain;
networking.search = lib.mkDefault [domain];
# Host FQDN — important for Kerberos SPN matching.
# `networking.hostName` is set by the host's default.nix; here we
# only add the hosts file fallback so the FQDN resolves locally even
# before DNS is reachable.
networking.extraHosts = ''
127.0.0.1 ${hostname}.${domain} ${hostname}
'';
# agenix-deployed machine keytab
age.secrets."${hostname}-krb5-keytab" = {
file = ../../secrets/${hostname}-krb5-keytab.age;
path = "/etc/krb5.keytab";
mode = "0600";
owner = "root";
group = "root";
};
# Ensure keytab path is readable by sssd (runs as root) but not by
# anyone else.
systemd.services.sssd = {
after = ["age-identity.service"];
wants = ["age-identity.service"];
serviceConfig.ExecStartPre = let
check = pkgs.writeShellScript "check-keytab" ''
if [ ! -s /etc/krb5.keytab ]; then
echo "ERROR: /etc/krb5.keytab is empty or missing." >&2
echo "Provision via adcli join + agenix, see roles/thin-client/README.md." >&2
exit 1
fi
'';
in ["+${check}"];
};
};
}