0cc0b5064d
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)
172 lines
5.6 KiB
Nix
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}"];
|
|
};
|
|
};
|
|
}
|