130 lines
6.3 KiB
Bash
Executable file
130 lines
6.3 KiB
Bash
Executable file
#!/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
|