From c737bd3192d4ad6353aaf248265521a2555b95a9 Mon Sep 17 00:00:00 2001 From: Alejandro Rosales Date: Mon, 7 Sep 2026 09:42:24 -0600 Subject: [PATCH] Add Linux Active Directory enrollment bootstrap --- README.md | 8 + docs/bootstrap-recovery.md | 28 ++- docs/linux-client-enrollment.md | 78 +++++++++ scripts/Enroll-SguLinuxDomainClient.sh | 233 +++++++++++++++++++++++++ scripts/New-SguBootstrapPackages.ps1 | 20 ++- scripts/Publish-GiteaRelease.ps1 | 2 + 6 files changed, 365 insertions(+), 4 deletions(-) create mode 100644 docs/linux-client-enrollment.md create mode 100755 scripts/Enroll-SguLinuxDomainClient.sh diff --git a/README.md b/README.md index 22d1720..39b3a95 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Operational documentation: - [Broker location, health, timeout, and recovery](docs/broker-operations.md) - [Windows domain join and remote-access onboarding](docs/windows-client-onboarding.md) - [Required Credential Provider client enrollment](docs/client-enrollment.md) +- [Linux client enrollment with realmd and SSSD](docs/linux-client-enrollment.md) - [Domain monitoring, usage reports, and six-month retention](docs/monitoring.md) - [Decision: do not persist password verifiers in Redis](docs/decisions/0001-no-password-cache.md) @@ -106,6 +107,13 @@ Start-SguServerBootstrap.cmd 192.168.50.10 Start-SguClientEnrollment.cmd 192.168.50.10 ``` +Linux clients are enrolled through their native PAM/SSSD stack instead of the +Windows Credential Provider: + +```bash +sudo bash ./Enroll-SguLinuxDomainClient.sh --domain-controller 192.168.50.10 +``` + The server command creates a new forest and resumes by itself after its required restart. The client command registers a unique non-exportable mTLS certificate, installs and validates SGU before domain join, then enables the managed remote diff --git a/docs/bootstrap-recovery.md b/docs/bootstrap-recovery.md index bf828ae..145f0aa 100644 --- a/docs/bootstrap-recovery.md +++ b/docs/bootstrap-recovery.md @@ -1,6 +1,6 @@ # Recuperación desde cero y alta en una sola ejecución -Los releases entregan dos ZIP independientes. Cada uno contiene sus binarios, +Los releases entregan tres ZIP independientes. Cada uno contiene sus binarios, scripts, instalador offline requerido y un manifiesto SHA-256 interno. No contienen contraseñas, claves privadas ni certificados reutilizables. @@ -131,6 +131,30 @@ bosque modificar el dominio. Para la operación soportada, el script aplica una transacción proveedor-primero y se niega a unir un equipo que no haya pasado las validaciones. +## Cliente Linux nuevo + +El ZIP `sgu-linux-client-bootstrap-VERSION.zip` usa el mecanismo nativo de +Linux: `realmd`, Kerberos, SSSD, NSS y PAM. No distribuye ni instala el +Credential Provider de Windows. + +En una VM con Internet por `Default Switch` y una NIC privada conectada a +`Laboratorio AD`, ejecutar como administrador: + +```bash +sudo bash ./Enroll-SguLinuxDomainClient.sh \ + --domain-controller 192.168.50.10 \ + --domain-interface eth0 \ + --domain-address 192.168.50.12/24 \ + --enable-ssh +``` + +La contraseña de la cuenta autorizada para la unión se solicita de forma +interactiva por `realmd`; no se incluye en la línea de comandos. El script +configura DNS de AD en la NIC privada, preserva la ruta de Internet, crea la +cuenta de equipo en `OU=Laboratorio`, habilita creación de directorio personal +mediante PAM y valida la cuenta de equipo con `adcli testjoin`. La documentación +de operación completa está en [linux-client-enrollment.md](linux-client-enrollment.md). + ## Crear y publicar un release Desde el repositorio y con el SDK fijado en `global.json`: @@ -150,4 +174,4 @@ la línea de comandos. Para empaquetar recursos institucionales adicionales: -ServerContentPath C:\Preparacion\Packages ``` -`SHA256SUMS-VERSION.txt` permite comprobar ambos ZIP antes de usarlos. +`SHA256SUMS-VERSION.txt` permite comprobar los tres ZIP antes de usarlos. diff --git a/docs/linux-client-enrollment.md b/docs/linux-client-enrollment.md new file mode 100644 index 0000000..b9f6ac7 --- /dev/null +++ b/docs/linux-client-enrollment.md @@ -0,0 +1,78 @@ +# Enrolamiento de clientes Linux + +El enrolador Linux incorpora una estación Debian/Ubuntu o RHEL/Fedora/Rocky/AlmaLinux a `lci.lasalle.mx` mediante `realmd`, `adcli`, Kerberos y SSSD. No instala el Credential Provider de Windows: Linux conserva su propio inicio de sesión PAM/SSSD. + +La contraseña de la cuenta autorizada para unir equipos se solicita directamente por `realmd`. Nunca se acepta como argumento, ni se escribe en archivos, registros o la línea de comandos. + +## Requisitos de red + +El equipo debe alcanzar por una interfaz privada al controlador de dominio, DNS y Kerberos. En el laboratorio: + +| Servicio | Destino | +| --- | --- | +| Controlador y DNS | `192.168.50.10` | +| Dominio | `lci.lasalle.mx` | +| OU de equipos | `OU=Laboratorio,DC=lci,DC=lasalle,DC=mx` | + +Para una VM Hyper-V con dos NIC, mantén el Internet en `Default Switch` y conecta una segunda NIC a `Laboratorio AD`. El enrolador puede configurar la segunda NIC sin modificar la ruta predeterminada: + +```bash +sudo bash ./Enroll-SguLinuxDomainClient.sh \ + --domain-controller 192.168.50.10 \ + --domain-interface eth0 \ + --domain-address 192.168.50.12/24 \ + --enable-ssh +``` + +`--domain-interface` y `--domain-address` son opcionales como pareja. Si ya se aprovisionó la red privada mediante cloud-init, DHCP o gestión de configuración, omítelos y conserva únicamente `--domain-controller`. + +El script se niega a reconfigurar una interfaz que posea la ruta predeterminada; así no deja a la máquina sin salida a Internet al agregar AD. + +## Qué instala y configura + +1. Instala `realmd`, `adcli`, SSSD, Kerberos y los módulos NSS/PAM adecuados para la familia de distribución. +2. Comprueba el registro DNS SRV de Active Directory y sincronización de hora ya existente. +3. Establece el nombre de host `NOMBRE.lci.lasalle.mx` antes de crear la cuenta de equipo. +4. Une el equipo con `adcli` en `OU=Laboratorio`. +5. Activa SSSD, creación de directorio personal mediante PAM y valida la contraseña de la cuenta de equipo con `adcli testjoin`. +6. Cuando se proporcionó la NIC privada, activa actualizaciones DNS dinámicas de SSSD en esa interfaz. +7. Con `--enable-ssh`, instala y habilita OpenSSH y abre únicamente el servicio SSH cuando el firewall local ya está activo. + +El objeto de equipo aparece como `NOMBRE` en `OU=Laboratorio`. SSSD registra su registro A cuando la actualización DNS dinámica está activada. + +## Inicio de sesión de dominio + +Después de la unión, el formato explícito es: + +```text +usuario@lci.lasalle.mx +``` + +La primera sesión crea `/home/usuario@lci.lasalle.mx`. El valor predeterminado de SSSD conserva credenciales para desconexiones breves de la red; las contraseñas no son administradas ni almacenadas por el Auth Broker. + +Para limitar quién puede iniciar sesión, incluye un grupo de AD: + +```bash +sudo bash ./Enroll-SguLinuxDomainClient.sh \ + --domain-controller 192.168.50.10 \ + --allow-group 'SG-Laboratorio-Linux-Users' +``` + +Ese modo ejecuta `realm deny --all` seguido de `realm permit --groups`; crea y administra el grupo antes de usarlo. + +## Verificación y salida controlada + +```bash +realm list +sudo adcli testjoin --domain=lci.lasalle.mx +getent passwd 'usuario@lci.lasalle.mx' +sudo sssctl domain-status lci.lasalle.mx +``` + +Para sacar un equipo del dominio de forma explícita: + +```bash +sudo realm leave lci.lasalle.mx +``` + +Esta última acción elimina la relación de confianza local; debe ejecutarse sólo durante baja o reconstrucción del equipo. diff --git a/scripts/Enroll-SguLinuxDomainClient.sh b/scripts/Enroll-SguLinuxDomainClient.sh new file mode 100755 index 0000000..b8a169d --- /dev/null +++ b/scripts/Enroll-SguLinuxDomainClient.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash +# Enroll-SguLinuxDomainClient.sh +# +# Idempotently joins a Debian/Ubuntu or RHEL-family Linux workstation to the +# SGU Active Directory laboratory. The join password is always requested by +# realmd; this script never accepts, logs, or stores it. + +set -Eeuo pipefail +IFS=$'\n\t' + +DOMAIN_NAME='lci.lasalle.mx' +DOMAIN_CONTROLLER='' +DOMAIN_DNS='' +COMPUTER_OU='OU=Laboratorio,DC=lci,DC=lasalle,DC=mx' +JOIN_USER='Administrator' +DOMAIN_INTERFACE='' +DOMAIN_ADDRESS='' +COMPUTER_NAME='' +ALLOW_GROUP='' +ENABLE_SSH=false + +usage() { + cat <<'EOF' +Usage: + sudo ./Enroll-SguLinuxDomainClient.sh --domain-controller [options] + +Required: + --domain-controller VALUE Fixed IPv4 address or DNS name of the AD controller. + +Options: + --domain-name VALUE AD DNS domain (default: lci.lasalle.mx). + --domain-dns VALUE DNS server for the AD network (default: domain controller). + --computer-ou DN Destination computer OU. + --join-user USER AD account permitted to join computers (default: Administrator). + --computer-name NAME NetBIOS host name; its FQDN becomes NAME.DOMAIN. + --domain-interface IFACE Private NIC connected to the AD network. + --domain-address CIDR Static IPv4 address for --domain-interface, e.g. 192.168.50.12/24. + --allow-group GROUP Restrict Linux sign-in to this AD group after joining. + --enable-ssh Install, enable, and (when active) permit OpenSSH in the local firewall. + --help Show this help. + +Network safety: + --domain-interface and --domain-address must be supplied together. The selected + interface must not own the default route, so the command cannot replace the + Internet route while attaching a private AD NIC. + +The AD password is requested interactively by realmd. It is never accepted as an +argument or written to a file, log, or command line. +EOF +} + +fail() { + printf 'ERROR: %s\n' "$*" >&2 + exit 1 +} + +need_command() { + command -v "$1" >/dev/null 2>&1 || fail "Required command is unavailable: $1" +} + +while (($#)); do + case "$1" in + --domain-controller) DOMAIN_CONTROLLER=${2:?Missing value for --domain-controller}; shift 2 ;; + --domain-name) DOMAIN_NAME=${2:?Missing value for --domain-name}; shift 2 ;; + --domain-dns) DOMAIN_DNS=${2:?Missing value for --domain-dns}; shift 2 ;; + --computer-ou) COMPUTER_OU=${2:?Missing value for --computer-ou}; shift 2 ;; + --join-user) JOIN_USER=${2:?Missing value for --join-user}; shift 2 ;; + --computer-name) COMPUTER_NAME=${2:?Missing value for --computer-name}; shift 2 ;; + --domain-interface) DOMAIN_INTERFACE=${2:?Missing value for --domain-interface}; shift 2 ;; + --domain-address) DOMAIN_ADDRESS=${2:?Missing value for --domain-address}; shift 2 ;; + --allow-group) ALLOW_GROUP=${2:?Missing value for --allow-group}; shift 2 ;; + --enable-ssh) ENABLE_SSH=true; shift ;; + --help|-h) usage; exit 0 ;; + *) fail "Unknown argument: $1. Use --help for usage." ;; + esac +done + +[[ ${EUID} -eq 0 ]] || fail 'Run this command with sudo or as root.' +[[ -n $DOMAIN_CONTROLLER ]] || fail '--domain-controller is required.' + +if [[ -z $DOMAIN_DNS ]]; then + DOMAIN_DNS=$DOMAIN_CONTROLLER +fi + +if [[ -n $DOMAIN_INTERFACE || -n $DOMAIN_ADDRESS ]]; then + [[ -n $DOMAIN_INTERFACE && -n $DOMAIN_ADDRESS ]] || \ + fail '--domain-interface and --domain-address must be supplied together.' +fi + +if [[ -z $COMPUTER_NAME ]]; then + COMPUTER_NAME=$(hostname -s) +fi +COMPUTER_NAME=${COMPUTER_NAME^^} +HOST_FQDN="${COMPUTER_NAME,,}.${DOMAIN_NAME,,}" + +install_prerequisites() { + local -a packages=() + if command -v apt-get >/dev/null 2>&1; then + packages=(realmd sssd sssd-tools adcli libnss-sss libpam-sss krb5-user packagekit samba-common-bin) + if [[ $ENABLE_SSH == true ]]; then + packages+=(openssh-server) + fi + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y "${packages[@]}" + pam-auth-update --enable mkhomedir --force + return + fi + + if command -v dnf >/dev/null 2>&1; then + packages=(realmd sssd sssd-tools adcli oddjob oddjob-mkhomedir samba-common-tools krb5-workstation) + if [[ $ENABLE_SSH == true ]]; then + packages+=(openssh-server) + fi + dnf install -y "${packages[@]}" + authselect select sssd with-mkhomedir --force + return + fi + + fail 'Supported package managers are apt-get (Debian/Ubuntu) and dnf (RHEL/Fedora/Rocky/AlmaLinux).' +} + +configure_private_ad_interface() { + [[ -n $DOMAIN_INTERFACE ]] || return + need_command nmcli + ip link show "$DOMAIN_INTERFACE" >/dev/null 2>&1 || \ + fail "Network interface does not exist: $DOMAIN_INTERFACE" + + local default_interface + default_interface=$(ip route show default | awk 'NR == 1 { print $5 }') + if [[ $default_interface == "$DOMAIN_INTERFACE" ]]; then + fail "Refusing to reconfigure $DOMAIN_INTERFACE because it owns the default route. Use the private AD NIC." + fi + + local connection_name="SGU-Lab-AD-${DOMAIN_INTERFACE}" + if ! nmcli -t -f NAME connection show | grep -Fxq "$connection_name"; then + nmcli connection add type ethernet ifname "$DOMAIN_INTERFACE" con-name "$connection_name" + fi + nmcli connection modify "$connection_name" \ + connection.autoconnect yes \ + ipv4.method manual \ + ipv4.addresses "$DOMAIN_ADDRESS" \ + ipv4.dns "$DOMAIN_DNS" \ + ipv4.dns-search "$DOMAIN_NAME" \ + ipv4.never-default yes \ + ipv6.method ignore + nmcli connection up "$connection_name" +} + +enable_sssd_dyndns() { + [[ -n $DOMAIN_INTERFACE ]] || return + local configuration_directory='/etc/sssd/conf.d' + local configuration_path="${configuration_directory}/90-sgu-dyndns.conf" + local temporary_path + temporary_path=$(mktemp) + printf '%s\n' \ + "[domain/${DOMAIN_NAME,,}]" \ + "ad_hostname = ${HOST_FQDN}" \ + 'dyndns_update = True' \ + 'dyndns_update_ptr = True' \ + "dyndns_iface = ${DOMAIN_INTERFACE}" \ + 'dyndns_refresh_interval = 43200' >"$temporary_path" + install -d -o root -g root -m 700 "$configuration_directory" + install -o root -g root -m 600 "$temporary_path" "$configuration_path" + rm -f "$temporary_path" +} + +enable_ssh() { + [[ $ENABLE_SSH == true ]] || return + local service_name='sshd' + if systemctl list-unit-files ssh.service >/dev/null 2>&1; then + service_name='ssh' + fi + systemctl enable --now "$service_name" + if command -v ufw >/dev/null 2>&1 && ufw status | grep -q '^Status: active'; then + ufw allow OpenSSH + elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then + firewall-cmd --permanent --add-service=ssh + firewall-cmd --reload + fi +} + +verify_domain_connectivity() { + need_command getent + getent ahostsv4 "$DOMAIN_CONTROLLER" >/dev/null || \ + fail "Could not resolve the domain controller: $DOMAIN_CONTROLLER" + if command -v resolvectl >/dev/null 2>&1; then + resolvectl query --type=SRV "_ldap._tcp.dc._msdcs.${DOMAIN_NAME}" >/dev/null || \ + fail "AD DNS does not provide _ldap._tcp.dc._msdcs.${DOMAIN_NAME}." + fi +} + +configure_private_ad_interface +install_prerequisites +verify_domain_connectivity + +# Establish a canonical host name before adcli creates or refreshes the +# computer object, SPNs, and keytab entries. +hostnamectl set-hostname "$HOST_FQDN" + +if realm list --name-only 2>/dev/null | grep -Fxqi "$DOMAIN_NAME"; then + printf 'Computer is already joined to %s; validating and refreshing configuration.\n' "$DOMAIN_NAME" +else + realm discover "$DOMAIN_NAME" >/dev/null + printf 'Joining %s. realmd will request the password for %s interactively.\n' "$DOMAIN_NAME" "$JOIN_USER" + realm join \ + --membership-software=adcli \ + --client-software=sssd \ + --computer-ou="$COMPUTER_OU" \ + --user="$JOIN_USER" \ + "$DOMAIN_NAME" +fi + +enable_sssd_dyndns +systemctl enable --now sssd +sssctl config-check +systemctl restart sssd +adcli update --domain="$DOMAIN_NAME" --host-fqdn="$HOST_FQDN" --computer-name="$COMPUTER_NAME" +adcli testjoin --domain="$DOMAIN_NAME" + +if [[ -n $ALLOW_GROUP ]]; then + realm deny --all + realm permit --groups "$ALLOW_GROUP" +fi + +enable_ssh + +printf '\nLinux enrollment completed.\n' +printf ' Host: %s\n' "$HOST_FQDN" +printf ' Domain: %s\n' "$DOMAIN_NAME" +printf ' OU: %s\n' "$COMPUTER_OU" +printf ' Login format: %%U@%s\n' "$DOMAIN_NAME" +realm list diff --git a/scripts/New-SguBootstrapPackages.ps1 b/scripts/New-SguBootstrapPackages.ps1 index 901c5e3..b27b994 100644 --- a/scripts/New-SguBootstrapPackages.ps1 +++ b/scripts/New-SguBootstrapPackages.ps1 @@ -80,15 +80,17 @@ if (-not $runtimeInstaller) { New-Item -ItemType Directory -Path $resolvedOutputRoot -Force | Out-Null $clientRoot = Join-Path $resolvedOutputRoot "sgu-client-bootstrap-$Version" $serverRoot = Join-Path $resolvedOutputRoot "sgu-server-bootstrap-$Version" +$linuxClientRoot = Join-Path $resolvedOutputRoot "sgu-linux-client-bootstrap-$Version" $clientZip = "$clientRoot.zip" $serverZip = "$serverRoot.zip" -foreach ($target in @($clientRoot,$serverRoot,$clientZip,$serverZip)) { +$linuxClientZip = "$linuxClientRoot.zip" +foreach ($target in @($clientRoot,$serverRoot,$linuxClientRoot,$clientZip,$serverZip,$linuxClientZip)) { if (Test-Path -LiteralPath $target) { throw "Release target already exists: $target" } } -New-Item -ItemType Directory -Path $clientRoot,$serverRoot -Force | Out-Null +New-Item -ItemType Directory -Path $clientRoot,$serverRoot,$linuxClientRoot -Force | Out-Null Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Invoke-SguClientBootstrap.ps1') ` -Destination (Join-Path $clientRoot 'Invoke-SguClientBootstrap.ps1') @@ -120,6 +122,17 @@ Write-PackageManifest -PackageRoot $clientRoot -PackageVersion $Version -Package Compress-Archive -Path (Join-Path $clientRoot '*') -DestinationPath $clientZip ` -CompressionLevel Optimal +# Linux clients use their native PAM/SSSD sign-in stack rather than the Windows +# Credential Provider. Keep their self-contained bootstrap independent so a +# Linux administrator never receives Windows binaries or certificate material. +Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Enroll-SguLinuxDomainClient.sh') ` + -Destination (Join-Path $linuxClientRoot 'Enroll-SguLinuxDomainClient.sh') +Copy-RequiredFile -Source (Join-Path $repositoryRoot 'docs\linux-client-enrollment.md') ` + -Destination (Join-Path $linuxClientRoot 'README.md') +Write-PackageManifest -PackageRoot $linuxClientRoot -PackageVersion $Version -PackageKind LinuxClient +Compress-Archive -Path (Join-Path $linuxClientRoot '*') -DestinationPath $linuxClientZip ` + -CompressionLevel Optimal + Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Initialize-SguDomainController.ps1') ` -Destination (Join-Path $serverRoot 'Initialize-SguDomainController.ps1') Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Start-SguServerBootstrap.cmd') ` @@ -161,6 +174,7 @@ Compress-Archive -Path (Join-Path $serverRoot '*') -DestinationPath $serverZip ` $checksums = @( ("{0} {1}" -f (Get-FileHash -LiteralPath $clientZip -Algorithm SHA256).Hash, (Split-Path $clientZip -Leaf)) ("{0} {1}" -f (Get-FileHash -LiteralPath $serverZip -Algorithm SHA256).Hash, (Split-Path $serverZip -Leaf)) + ("{0} {1}" -f (Get-FileHash -LiteralPath $linuxClientZip -Algorithm SHA256).Hash, (Split-Path $linuxClientZip -Leaf)) ) $checksumsPath = Join-Path $resolvedOutputRoot "SHA256SUMS-$Version.txt" [IO.File]::WriteAllLines($checksumsPath, $checksums, [Text.UTF8Encoding]::new($false)) @@ -169,6 +183,8 @@ $checksumsPath = Join-Path $resolvedOutputRoot "SHA256SUMS-$Version.txt" Version = $Version ClientPackage = $clientZip ClientSha256 = (Get-FileHash -LiteralPath $clientZip -Algorithm SHA256).Hash + LinuxClientPackage = $linuxClientZip + LinuxClientSha256 = (Get-FileHash -LiteralPath $linuxClientZip -Algorithm SHA256).Hash ServerPackage = $serverZip ServerSha256 = (Get-FileHash -LiteralPath $serverZip -Algorithm SHA256).Hash Checksums = $checksumsPath diff --git a/scripts/Publish-GiteaRelease.ps1 b/scripts/Publish-GiteaRelease.ps1 index 70b415a..ea00652 100644 --- a/scripts/Publish-GiteaRelease.ps1 +++ b/scripts/Publish-GiteaRelease.ps1 @@ -17,6 +17,7 @@ $tagName = "v$Version" $assetPaths = @( (Join-Path $ReleaseDirectory "sgu-client-bootstrap-$Version.zip"), (Join-Path $ReleaseDirectory "sgu-server-bootstrap-$Version.zip"), + (Join-Path $ReleaseDirectory "sgu-linux-client-bootstrap-$Version.zip"), (Join-Path $ReleaseDirectory "SHA256SUMS-$Version.txt") ) foreach ($assetPath in $assetPaths) { @@ -100,6 +101,7 @@ Bootstrap reproducible para el laboratorio SGU. - **Advertencia:** el bootstrap de servidor crea un bosque nuevo. No restaura los SID, contraseñas ni relaciones de confianza del bosque anterior; para conservarlos se requiere una recuperación de bosque desde una copia de estado del sistema. - `sgu-server-bootstrap-$Version.zip`: crea el bosque AD/DNS, OUs, grupo RDP, GPO, recurso `Packages`, broker mTLS y administración remota; se reanuda solo después del reinicio. - `sgu-client-bootstrap-$Version.zip`: registra un certificado mTLS único, instala y valida el Credential Provider antes de unir el equipo al dominio, habilita RDP/WinRM y se repara al arranque. +- `sgu-linux-client-bootstrap-$Version.zip`: une clientes Debian/Ubuntu o RHEL/Fedora/Rocky/AlmaLinux con realmd, Kerberos y SSSD. Solicita interactivamente la contraseña de unión y no instala el Credential Provider de Windows. - El servidor configura WEF/WEC para registrar sesiones y fallos, inventariar el estado alcanzable de las máquinas cada cinco minutos y conservar durante 183 días tanto esos eventos como el diagnóstico estructurado del Auth Broker. - Windows Home se detecta y se rechaza con una explicación, ya que no admite unión a Active Directory ni RDP host.