#!/usr/bin/env bash # ===================================================================================== # LDAPS certificates for the domain controller. # # Windows Schannel requires a SubjectAltName (CN-only certs are rejected), and it must be # DNS= matching what the client dials. Samba's built-in auto-generated cert is # self-signed and untrusted -> Windows shows trust errors and refusing it breaks LDAPS. # # This builds a throwaway-but-proper internal CA + a DC leaf cert: # secrets/tls/ca.crt import into Windows "Trusted Root Certification Authorities" # (best shipped via GPO so every joined host trusts it) # secrets/tls/dc.crt served by Samba on 636 # secrets/tls/dc.key 0600, never leaves the host # # For anything long-lived/external prefer ACME DNS-01 against the DC's own DNS # (nsupdate -g) or your corporate PKI instead of this CA. # ===================================================================================== set -euo pipefail cd "$(dirname "$0")/.." . "$(dirname "$0")/envfile.sh"; ad_env_load .env # parsed, never executed (see envfile.sh) HOST="${AD_HOSTNAME:-dc1}" ZONE="${AD_DNS_DOMAIN:-ad.example.com}" FQDN="${HOST}.${ZONE}" D="${AD_TLS_DIR:-secrets/tls}" DAYS_CA="${AD_TLS_CA_DAYS:-3650}" DAYS_LEAF="${AD_TLS_LEAF_DAYS:-825}" umask 077 mkdir -p "$D" if [ -f "$D/ca.key" ] && [ "${1:-}" != "--rotate-leaf-only" ]; then echo "CA already exists at $D/ca.key (leaf will be re-issued against it)." fi # ---- root CA (only if missing) ------------------------------------------------------ if [ ! -f "$D/ca.key" ]; then echo ">> generating internal root CA for ${FQDN}" openssl req -x509 -newkey rsa:4096 -sha256 -days "$DAYS_CA" -nodes \ -keyout "$D/ca.key" -out "$D/ca.crt" \ -subj "/C=US/O=${ZONE} /CN=${ZONE} Root CA" \ -addext "basicConstraints=critical,CA:TRUE,pathlen:1" \ -addext "keyUsage=critical,keyCertSign,cRLSign" chmod 600 "$D/ca.key" fi # ---- leaf ------------------------------------------------------------------------ echo ">> issuing DC leaf: ${FQDN} (+ ${HOST})" # SAN must cover every name/address a client may dial. Windows (Schannel) requires a SAN - a # CN-only cert is rejected outright - and tools that dial the DC by address need an IP SAN or # they fail hostname verification even though the chain is fine. SAN="DNS:${FQDN},DNS:${HOST}" [ -n "${AD_IP:-}" ] && SAN="${SAN},IP:${AD_IP}" [ -n "${AD_IP6:-}" ] && SAN="${SAN},IP:${AD_IP6}" [ -n "${AD_TLS_SAN_EXTRA:-}" ] && SAN="${SAN},${AD_TLS_SAN_EXTRA}" SAN="$(printf '%s' "$SAN" | tr ',' '\n' | awk '!seen[$0]++' | paste -sd, -)" # dedupe openssl req -newkey rsa:2048 -sha256 -nodes \ -keyout "$D/dc.key" -out "$D/dc.csr" \ -subj "/C=US/O=${ZONE}/CN=${FQDN}" openssl x509 -req -in "$D/dc.csr" -CA "$D/ca.crt" -CAkey "$D/ca.key" \ -CAcreateserial -days "$DAYS_LEAF" -sha256 \ -out "$D/dc.crt" \ -extfile <(printf '%s\n' \ "subjectAltName=${SAN}" \ "extendedKeyUsage=serverAuth" \ "keyUsage=critical,digitalSignature,keyEncipherment" \ "basicConstraints=critical,CA:FALSE") chmod 600 "$D/dc.key" rm -f "$D/dc.csr" openssl verify -CAfile "$D/ca.crt" "$D/dc.crt" echo ">> SAN of issued cert:" openssl x509 -in "$D/dc.crt" -noout -ext subjectAltName | sed 's/^/ /' # ---- publish the CA where joined machines can fetch it. NETLOGON is read-only and # world-readable by design, which is exactly why Windows ships its own CA there too. # Re-checked with sysvolcheck afterwards: if the extra file upsets the NT ACLs we undo it # rather than leave SYSVOL inconsistent. # AD_TLS_PUBLISH=no skips this entirely - MANDATORY for lab/test certs, otherwise a test run # would push a throwaway CA into the production DC's SYSVOL. CID="${AD_CONTAINER:-ad-dc}" if [ "${AD_TLS_PUBLISH:-yes}" != "yes" ]; then echo ">> CA publication skipped (AD_TLS_PUBLISH=no)" elif [ "$(docker inspect -f '{{.State.Running}}' "$CID" 2>/dev/null || echo false)" = "true" ]; then NETLOGON_DIR="/var/lib/samba/sysvol/${ZONE}/scripts" if docker exec "$CID" test -d "$NETLOGON_DIR" 2>/dev/null; then if docker exec "$CID" cp /run/ad-secrets/tls/ca.crt "$NETLOGON_DIR/ad-root-ca.crt" 2>/dev/null \ || docker cp "$D/ca.crt" "${CID}:${NETLOGON_DIR}/ad-root-ca.crt" 2>/dev/null; then docker exec "$CID" chmod 644 "$NETLOGON_DIR/ad-root-ca.crt" 2>/dev/null || true if docker exec "$CID" samba-tool ntacl sysvolcheck >/dev/null 2>&1; then echo ">> CA published for clients: \\\\${ZONE}\\NETLOGON\\ad-root-ca.crt" else docker exec "$CID" rm -f "$NETLOGON_DIR/ad-root-ca.crt" 2>/dev/null || true echo ">> CA publication rolled back (it broke the SYSVOL ACL check) - distribute ca.crt manually" fi fi else echo ">> no NETLOGON scripts dir in ${CID}; distribute ca.crt manually" fi else echo ">> ${CID} is not running; skipping CA publication (run this again after 'make up')" fi echo ">> CA SHA-256 fingerprint (verify out-of-band before trusting):" openssl x509 -in "$D/ca.crt" -noout -fingerprint -sha256 | sed 's/^/ /' cat < Policies > Windows Settings > Security Settings > Public Key Policies > Trusted Root CA. 5) BACK UP $D/ca.key OFFLINE. It is the trust anchor: lose it and every cert signed by it must be reissued and re-trusted; leak it and someone can mint certs your clients trust. EOF