Files
m3ta-chiron 0cc0b5064d fix(thin-client): deep-build fixes + Snipe-IT/Alloy real impl
Tiefen-Validierung via 'nix build .#nixosConfigurations.AZ-TC-NN.config.
system.build.toplevel' hat mehrere reale Bugs gefunden, die 'nix flake
check' nicht sah. Alle drei Pilot-Hosts bauen jetzt sauber durch.

Gefixst:
- freerdp3 → freerdp (umbenannt in nixpkgs-unstable)
- SDDM Theme.Logo will INI-Atom (string), nicht Nix-path → '${path}'
- security.pam.mount.extraVolumes will list-of-string, nicht ein String
- sudoers: 'domain admins' muss als 'domain\ admins' escaped werden
- agenix file-Pfade: ../../secrets/ → ../../../secrets/ (Tiefe korrigiert)
- snapper: config.services.snapper.package gibt es nicht → pkgs.snapper
- samba4Full entfernt (blockiert durch ceph-common python metadata issue
  in nixpkgs-unstable; Thin Clients brauchen es nicht — cifs-utils reicht)
- corp-wifi-ca.pem durch gültiges Dummy-PEM ersetzt (openssl-generiert,
  mit明显 REPLACE-MARKER; build kann PEM parsen)

Functional gemacht:
- Alloy: echtes River-Config mit loki.source.journal + loki.write statt
  barem logging-stub. Journal-Logs mit host/unit/severity-Labels nach
  Loki.
- Snipe-IT: echte Check-in-Logik via curl + jq. Lookup by asset_tag,
  PATCH falls exists, POST falls neu. startAt täglich 03:30. API-Token
  via agenix (snipeit-api-token.age).
- node_exporter push: realer curl-Push alle 60s mit retry on failure.

Safety:
- Placeholder-Assertions in roles/thin-client/default.nix: build schlägt
  fehl, wenn wifi.ssid/hotline/company noch Placeholder sind (außer
  site='staging'). Pilot-Hosts haben site='staging' bis echte Werte da.
- assets/corp-wifi-ca.pem hat deutlich sichtbaren REPLACE-Hinweis.

Neue Options:
- az.tc.monitoring.snipeItUrl (default: snipeit.az-group.local)

Neue Secrets (Placeholder .age-Files zum Ausfüllen):
- snipeit-api-token.age (fleet-wide shared)
2026-07-29 09:06:12 +02:00

172 lines
5.6 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 # AD computer-account precreate + join helper
sssd # AD client daemon
krb5 # MIT Kerberos client
realmd # DBus service for realm discovery (used by adcli wrapper)
# NOTE: samba4Full intentionally NOT included — it would pull in
# ceph-common which is currently broken in nixpkgs-unstable
# (python metadata issue). Thin Clients don't need Samba server
# or smbclient — share mounting uses cifs-utils (in smb-mounts.nix).
# If `net` or `smbclient` are ever needed, install on the admin
# workstation (AZ-LT-NIX), not on the Thin Client.
];
# 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}"];
};
};
}