First Commit

This commit is contained in:
Lonny Lowe 2026-09-24 11:29:54 -05:00
commit 2e5a4751a7
30 changed files with 2517 additions and 0 deletions

130
scripts/tls.sh Executable file
View file

@ -0,0 +1,130 @@
#!/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=<dc fqdn> 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 <<EOF
Next steps
1) .env:
AD_TLS_CERT=/run/ad-secrets/tls/dc.crt
AD_TLS_KEY=/run/ad-secrets/tls/dc.key
AD_TLS_CA=/run/ad-secrets/tls/ca.crt
2) docker compose up -d --force-recreate ad-dc
3) verify : openssl s_client -connect ${FQDN}:636 -CAfile $D/ca.crt </dev/null
4) trust it on Windows 11 (admin PowerShell). This is the step that removes the popups -
a joined machine that trusts the issuing CA sees a perfectly normal certificate:
certutil -addstore -f ROOT "C:\\path\\to\\ca.crt"
or straight from the DC (needs any domain credential):
certutil -addstore -f ROOT "\\\\${ZONE}\\NETLOGON\\ad-root-ca.crt"
IMPORTANT: install into the LOCAL COMPUTER store (certutil -addstore ROOT, elevated),
not the current-user store. LDAP/Schannel binds run as SYSTEM/service accounts and will
not see a per-user trust.
For a fleet, publish ca.crt through GPO: Computer Configuration > 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