mirror of
https://github.com/bashclub/zamba-lxc-toolbox
synced 2026-08-03 23:16:11 +00:00
Create zmb-ad_tls_manager
This commit is contained in:
@@ -0,0 +1,709 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# samba-dc-tls.sh - CA und TLS-Zertifikate fuer Samba AD DCs generisch neu bauen
|
||||
#
|
||||
# Auslegung: mehrere DCs teilen sich EINE CA. Die CA liegt auf DC1, die
|
||||
# weiteren DCs bekommen ein Bundle (cert/key/ca) oder lassen einen CSR
|
||||
# signieren. Alle Aenderungen werden gesichert; schlaegt der Health-Check
|
||||
# nach dem Restart fehl, wird automatisch zurueckgerollt.
|
||||
#
|
||||
# Getestet mit Samba 4.17+ / OpenSSL 1.1.1+ / 3.x auf Debian und RHEL.
|
||||
#
|
||||
# Lizenz: MIT
|
||||
#
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
|
||||
VERSION="1.1"
|
||||
PROG="$(basename "$0")"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Defaults - per Umgebungsvariable oder Option ueberschreibbar
|
||||
# ---------------------------------------------------------------------------
|
||||
CA_DIR="${CA_DIR:-/var/lib/samba/private/tls-ca}"
|
||||
CA_DAYS="${CA_DAYS:-3650}" # CA-Laufzeit (10 Jahre)
|
||||
CERT_DAYS="${CERT_DAYS:-825}" # DC-Zertifikat; 825 = Apple/Chrome-Limit
|
||||
KEY_BITS="${KEY_BITS:-4096}" # RSA; breiteste Client-Kompatibilitaet
|
||||
CA_CN="${CA_CN:-}" # Default: "<REALM> Samba AD CA"
|
||||
ORG="${ORG:-}" # Default: Realm
|
||||
EXTRA_DNS="${EXTRA_DNS:-}" # zusaetzliche DNS-SANs, komma-/leerzeichensepariert
|
||||
EXTRA_IP="${EXTRA_IP:-}" # zusaetzliche IP-SANs
|
||||
BACKUP_KEEP="${BACKUP_KEEP:-10}" # wie viele Backup-Generationen behalten
|
||||
|
||||
DO_RESTART=1
|
||||
DO_ROLLBACK=1
|
||||
DRY_RUN=0
|
||||
FORCE=0
|
||||
ASSUME_YES=0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ausgabe-Helfer
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ -t 1 ]]; then
|
||||
C_R=$'\e[31m'; C_G=$'\e[32m'; C_Y=$'\e[33m'; C_B=$'\e[34m'; C_0=$'\e[0m'
|
||||
else
|
||||
C_R=""; C_G=""; C_Y=""; C_B=""; C_0=""
|
||||
fi
|
||||
log() { printf '%s[*]%s %s\n' "$C_B" "$C_0" "$*"; }
|
||||
ok() { printf '%s[+]%s %s\n' "$C_G" "$C_0" "$*"; }
|
||||
warn() { printf '%s[!]%s %s\n' "$C_Y" "$C_0" "$*" >&2; }
|
||||
die() { printf '%s[x]%s %s\n' "$C_R" "$C_0" "$*" >&2; exit 1; }
|
||||
run() { if (( DRY_RUN )); then printf ' would run: %s\n' "$*"; else "$@"; fi; }
|
||||
|
||||
confirm() {
|
||||
(( ASSUME_YES )) && return 0
|
||||
local a; read -r -p " $1 [j/N] " a || true
|
||||
[[ "$a" =~ ^([jJ]|[yY])$ ]]
|
||||
}
|
||||
|
||||
need_root() { [[ "$(id -u)" -eq 0 ]] || die "Bitte als root ausfuehren."; }
|
||||
|
||||
need_bin() {
|
||||
local m=()
|
||||
for b in "$@"; do command -v "$b" >/dev/null 2>&1 || m+=("$b"); done
|
||||
((${#m[@]})) && die "Fehlende Programme: ${m[*]}"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Samba-Umgebung einlesen
|
||||
# ---------------------------------------------------------------------------
|
||||
tp() { testparm -s --parameter-name="$1" 2>/dev/null | tail -n1 | tr -d '\r'; }
|
||||
|
||||
abspath_in_private() {
|
||||
local p="$1"
|
||||
[[ "$p" == /* ]] && { printf '%s' "$p"; return; }
|
||||
printf '%s/%s' "$PRIVATE_DIR" "$p"
|
||||
}
|
||||
|
||||
detect_env() {
|
||||
need_bin testparm openssl
|
||||
|
||||
SERVER_ROLE="$(tp 'server role')"
|
||||
[[ "$SERVER_ROLE" == *"domain controller"* ]] || \
|
||||
warn "server role = '${SERVER_ROLE:-?}' - das sieht nicht nach einem AD DC aus."
|
||||
|
||||
REALM="$(tp realm)"
|
||||
[[ -n "$REALM" ]] || die "Kein 'realm' in der smb.conf gefunden."
|
||||
REALM_LC="${REALM,,}"
|
||||
NETBIOS="$(tp 'netbios name')"
|
||||
PRIVATE_DIR="$(tp 'private dir')"
|
||||
PRIVATE_DIR="${PRIVATE_DIR:-/var/lib/samba/private}"
|
||||
|
||||
TLS_ENABLED="$(tp 'tls enabled')"
|
||||
TLS_CERT="$(abspath_in_private "$(tp 'tls certfile')")"
|
||||
TLS_KEY="$(abspath_in_private "$(tp 'tls keyfile')")"
|
||||
TLS_CA="$(abspath_in_private "$(tp 'tls cafile')")"
|
||||
TLS_DIR="$(dirname "$TLS_CERT")"
|
||||
|
||||
# FQDN ermitteln - hostname -f kann bei kaputtem DNS luegen
|
||||
local short; short="$(hostname -s 2>/dev/null || true)"
|
||||
FQDN="$(hostname -f 2>/dev/null || true)"
|
||||
if [[ -z "$FQDN" || "$FQDN" != *.* || "${FQDN,,}" != *"$REALM_LC" ]]; then
|
||||
FQDN="${short,,}.${REALM_LC}"
|
||||
fi
|
||||
FQDN="${FQDN,,}"
|
||||
SHORTNAME="${short,,}"
|
||||
|
||||
ORG="${ORG:-$REALM}"
|
||||
CA_CN="${CA_CN:-$REALM Samba AD CA}"
|
||||
|
||||
CA_KEY="$CA_DIR/ca-key.pem"
|
||||
CA_CERT="$CA_DIR/ca-cert.pem"
|
||||
CA_SRL="$CA_DIR/ca.srl"
|
||||
STATE_FILE="$TLS_DIR/.samba-dc-tls.state"
|
||||
}
|
||||
|
||||
# NTDS-DSA-GUID fuer den <guid>._msdcs.<realm> SAN
|
||||
detect_dsa_guid() {
|
||||
local samdb="$PRIVATE_DIR/sam.ldb" dsdn guid
|
||||
DSA_GUID=""
|
||||
command -v ldbsearch >/dev/null 2>&1 || return 0
|
||||
[[ -r "$samdb" ]] || return 0
|
||||
dsdn="$(ldbsearch -H "$samdb" -s base -b '' dsServiceName 2>/dev/null \
|
||||
| sed -n 's/^dsServiceName: //p' | head -n1)" || true
|
||||
[[ -n "$dsdn" ]] || return 0
|
||||
guid="$(ldbsearch -H "$samdb" -s base -b "$dsdn" objectGUID 2>/dev/null \
|
||||
| sed -n 's/^objectGUID: //p' | head -n1)" || true
|
||||
[[ "$guid" =~ ^[0-9a-fA-F-]{36}$ ]] && DSA_GUID="${guid,,}"
|
||||
return 0
|
||||
}
|
||||
|
||||
print_env() {
|
||||
cat <<EOF
|
||||
Realm .......... $REALM
|
||||
NetBIOS ........ ${NETBIOS:-?}
|
||||
FQDN ........... $FQDN
|
||||
private dir .... $PRIVATE_DIR
|
||||
tls enabled .... ${TLS_ENABLED:-?}
|
||||
tls certfile ... $TLS_CERT
|
||||
tls keyfile .... $TLS_KEY
|
||||
tls cafile ..... $TLS_CA
|
||||
CA-Verzeichnis . $CA_DIR
|
||||
EOF
|
||||
[[ -n "${DSA_GUID:-}" ]] && printf ' DSA-GUID ....... %s\n' "$DSA_GUID"
|
||||
if [[ "${TLS_ENABLED,,}" == "no" ]]; then
|
||||
warn "'tls enabled = no' - Samba nutzt die Zertifikate dann nicht."
|
||||
warn "In der [global]-Sektion setzen: tls enabled = yes"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OpenSSL-Konfiguration erzeugen
|
||||
# ---------------------------------------------------------------------------
|
||||
# $1 = CN/FQDN, $2 = Zielpfad der Config, $3 = optionale DSA-GUID
|
||||
write_cert_conf() {
|
||||
local cn="$1" out="$2" guid="${3:-}"
|
||||
local -a dns=() ips=()
|
||||
local n
|
||||
|
||||
dns+=("$cn")
|
||||
[[ "$cn" == *.* ]] && dns+=("${cn%%.*}")
|
||||
dns+=("$REALM_LC")
|
||||
[[ -n "$guid" ]] && dns+=("${guid}._msdcs.${REALM_LC}")
|
||||
for n in ${EXTRA_DNS//,/ }; do [[ -n "$n" ]] && dns+=("${n,,}"); done
|
||||
for n in ${EXTRA_IP//,/ }; do [[ -n "$n" ]] && ips+=("$n"); done
|
||||
|
||||
# Duplikate entfernen, Reihenfolge erhalten
|
||||
local -a udns=(); local -A seen=()
|
||||
for n in "${dns[@]}"; do [[ -n "${seen[$n]:-}" ]] || { udns+=("$n"); seen[$n]=1; }; done
|
||||
|
||||
{
|
||||
cat <<EOF
|
||||
[req]
|
||||
default_md = sha256
|
||||
prompt = no
|
||||
distinguished_name = dn
|
||||
req_extensions = v3_req
|
||||
|
||||
[dn]
|
||||
O = $ORG
|
||||
CN = $cn
|
||||
|
||||
[v3_req]
|
||||
basicConstraints = critical,CA:FALSE
|
||||
keyUsage = critical,digitalSignature,keyEncipherment
|
||||
extendedKeyUsage = serverAuth,clientAuth
|
||||
subjectAltName = @alt
|
||||
|
||||
[v3_sign]
|
||||
basicConstraints = critical,CA:FALSE
|
||||
keyUsage = critical,digitalSignature,keyEncipherment
|
||||
extendedKeyUsage = serverAuth,clientAuth
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer
|
||||
subjectAltName = @alt
|
||||
|
||||
[alt]
|
||||
EOF
|
||||
local i=1
|
||||
for n in "${udns[@]}"; do printf 'DNS.%d = %s\n' "$i" "$n"; ((i++)); done
|
||||
i=1
|
||||
for n in "${ips[@]}"; do printf 'IP.%d = %s\n' "$i" "$n"; ((i++)); done
|
||||
} >"$out"
|
||||
|
||||
log "SANs: ${udns[*]} ${ips[*]:-}"
|
||||
}
|
||||
|
||||
write_ca_conf() {
|
||||
cat >"$1" <<EOF
|
||||
[req]
|
||||
default_md = sha256
|
||||
prompt = no
|
||||
distinguished_name = dn
|
||||
x509_extensions = v3_ca
|
||||
|
||||
[dn]
|
||||
O = $ORG
|
||||
CN = $CA_CN
|
||||
|
||||
[v3_ca]
|
||||
basicConstraints = critical,CA:TRUE,pathlen:0
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
EOF
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CA
|
||||
# ---------------------------------------------------------------------------
|
||||
cmd_init_ca() {
|
||||
need_root
|
||||
if [[ -f "$CA_KEY" && -f "$CA_CERT" ]] && (( ! FORCE )); then
|
||||
log "CA existiert bereits: $CA_CERT"
|
||||
openssl x509 -in "$CA_CERT" -noout -subject -enddate | sed 's/^/ /'
|
||||
return 0
|
||||
fi
|
||||
(( FORCE )) && [[ -f "$CA_CERT" ]] && warn "--force: bestehende CA wird ersetzt!"
|
||||
|
||||
log "Erzeuge CA in $CA_DIR"
|
||||
run install -d -m 0700 -o root -g root "$CA_DIR"
|
||||
(( DRY_RUN )) && return 0
|
||||
|
||||
local conf; conf="$(mktemp)"; write_ca_conf "$conf"
|
||||
openssl req -x509 -new -nodes -newkey "rsa:$KEY_BITS" -sha256 \
|
||||
-days "$CA_DAYS" -keyout "$CA_KEY.new" -out "$CA_CERT.new" -config "$conf"
|
||||
rm -f "$conf"
|
||||
mv "$CA_KEY.new" "$CA_KEY"; chmod 0600 "$CA_KEY"
|
||||
mv "$CA_CERT.new" "$CA_CERT"; chmod 0644 "$CA_CERT"
|
||||
openssl rand -hex 16 >"$CA_SRL"
|
||||
ok "CA erstellt (gueltig $CA_DAYS Tage)."
|
||||
openssl x509 -in "$CA_CERT" -noout -subject -enddate | sed 's/^/ /'
|
||||
}
|
||||
|
||||
require_ca() {
|
||||
[[ -f "$CA_KEY" && -f "$CA_CERT" ]] || \
|
||||
die "Keine CA unter $CA_DIR. Erst '$PROG init-ca' ausfuehren (auf dem CA-DC)."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Zertifikat erzeugen / signieren
|
||||
# ---------------------------------------------------------------------------
|
||||
# $1 = FQDN, $2 = Ausgabeverzeichnis, $3 = optionale DSA-GUID
|
||||
make_cert() {
|
||||
local cn="$1" dir="$2" guid="${3:-}"
|
||||
local conf="$dir/openssl.cnf"
|
||||
install -d -m 0700 "$dir"
|
||||
write_cert_conf "$cn" "$conf" "$guid"
|
||||
|
||||
openssl req -new -nodes -newkey "rsa:$KEY_BITS" -sha256 \
|
||||
-keyout "$dir/key.pem" -out "$dir/csr.pem" -config "$conf"
|
||||
chmod 0600 "$dir/key.pem"
|
||||
|
||||
[[ -s "$CA_SRL" ]] || openssl rand -hex 16 >"$CA_SRL"
|
||||
openssl x509 -req -in "$dir/csr.pem" -sha256 -days "$CERT_DAYS" \
|
||||
-CA "$CA_CERT" -CAkey "$CA_KEY" -CAserial "$CA_SRL" \
|
||||
-extfile "$conf" -extensions v3_sign -out "$dir/cert.pem" 2>/dev/null
|
||||
chmod 0644 "$dir/cert.pem"
|
||||
cp -f "$CA_CERT" "$dir/ca.pem"; chmod 0644 "$dir/ca.pem"
|
||||
rm -f "$dir/csr.pem"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pruefungen
|
||||
# ---------------------------------------------------------------------------
|
||||
check_pair() {
|
||||
local cert="$1" key="$2" ca="$3" fqdn="${4:-}"
|
||||
local a b
|
||||
|
||||
a="$(openssl x509 -in "$cert" -noout -pubkey | openssl pkey -pubin -outform DER 2>/dev/null | openssl dgst -sha256 -r | cut -d' ' -f1)"
|
||||
b="$(openssl pkey -in "$key" -pubout -outform DER 2>/dev/null | openssl dgst -sha256 -r | cut -d' ' -f1)"
|
||||
[[ -n "$a" && "$a" == "$b" ]] || die "Key und Zertifikat passen nicht zusammen: $cert / $key"
|
||||
|
||||
openssl verify -CAfile "$ca" "$cert" >/dev/null 2>&1 \
|
||||
|| die "Zertifikat wird von der CA nicht verifiziert: $cert"
|
||||
|
||||
openssl x509 -in "$cert" -noout -ext extendedKeyUsage 2>/dev/null | grep -q 'TLS Web Server Authentication' \
|
||||
|| die "Zertifikat hat kein serverAuth-EKU."
|
||||
|
||||
if [[ -n "$fqdn" ]]; then
|
||||
openssl x509 -in "$cert" -noout -ext subjectAltName 2>/dev/null | grep -qi "DNS:$fqdn" \
|
||||
|| die "SAN enthaelt den FQDN '$fqdn' nicht."
|
||||
fi
|
||||
ok "Zertifikat, Key und CA sind konsistent."
|
||||
}
|
||||
|
||||
health_check() {
|
||||
local host="${1:-$FQDN}" rc=0
|
||||
log "Health-Check gegen ldaps://$host:636"
|
||||
if ! timeout 15 openssl s_client -connect "127.0.0.1:636" -servername "$host" \
|
||||
-CAfile "$TLS_CA" -verify_return_error </dev/null >/dev/null 2>&1; then
|
||||
warn "LDAPS-Handshake auf 127.0.0.1:636 fehlgeschlagen."
|
||||
rc=1
|
||||
else
|
||||
ok "LDAPS-Handshake ok (Kette gegen eigene CA verifiziert)."
|
||||
fi
|
||||
|
||||
if command -v ldbsearch >/dev/null 2>&1; then
|
||||
if ldbsearch -H "ldaps://$host" -k yes -s base -b '' defaultNamingContext >/dev/null 2>&1 \
|
||||
|| ldbsearch -H "ldap://$host" -k yes -s base -b '' defaultNamingContext >/dev/null 2>&1; then
|
||||
ok "LDAP-Abfrage gegen $host beantwortet."
|
||||
else
|
||||
warn "LDAP-Abfrage gegen $host fehlgeschlagen (evtl. kein Kerberos-Ticket - nicht zwingend ein Fehler)."
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v samba-tool >/dev/null 2>&1; then
|
||||
if samba-tool drs showrepl >/tmp/.drs.$$ 2>&1; then
|
||||
if grep -qiE 'consecutive failure|WERR_|failed' /tmp/.drs.$$; then
|
||||
warn "drs showrepl meldet Fehler:"; sed 's/^/ /' /tmp/.drs.$$ | head -n 30
|
||||
rc=1
|
||||
else
|
||||
ok "Replikation (drs showrepl) ohne Fehler."
|
||||
fi
|
||||
else
|
||||
warn "samba-tool drs showrepl fehlgeschlagen."; rc=1
|
||||
fi
|
||||
rm -f /tmp/.drs.$$
|
||||
fi
|
||||
return $rc
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dienststeuerung
|
||||
# ---------------------------------------------------------------------------
|
||||
detect_unit() {
|
||||
local u
|
||||
for u in samba-ad-dc samba sernet-samba-ad; do
|
||||
if systemctl cat "$u.service" >/dev/null 2>&1; then
|
||||
if systemctl is-enabled "$u.service" >/dev/null 2>&1 || systemctl is-active "$u.service" >/dev/null 2>&1; then
|
||||
printf '%s' "$u.service"; return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
restart_samba() {
|
||||
local unit
|
||||
if ! unit="$(detect_unit)"; then
|
||||
warn "Keine aktive Samba-AD-Unit gefunden - bitte manuell neu starten."
|
||||
return 0
|
||||
fi
|
||||
log "Starte $unit neu ..."
|
||||
run systemctl restart "$unit"
|
||||
(( DRY_RUN )) && return 0
|
||||
sleep 4
|
||||
systemctl is-active --quiet "$unit" || die "$unit ist nach dem Restart nicht aktiv!"
|
||||
ok "$unit laeuft."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Installation mit Backup / Rollback
|
||||
# ---------------------------------------------------------------------------
|
||||
backup_current() {
|
||||
local ts bdir
|
||||
ts="$(date +%Y%m%d-%H%M%S)"
|
||||
bdir="$TLS_DIR/backup-$ts"
|
||||
install -d -m 0700 "$bdir"
|
||||
local f
|
||||
for f in "$TLS_CERT" "$TLS_KEY" "$TLS_CA"; do
|
||||
[[ -f "$f" ]] && cp -a "$f" "$bdir/"
|
||||
done
|
||||
printf '%s\n' "$bdir" >"$STATE_FILE"; chmod 0600 "$STATE_FILE"
|
||||
ok "Backup: $bdir"
|
||||
|
||||
# alte Generationen aufraeumen
|
||||
local -a old
|
||||
mapfile -t old < <(ls -1d "$TLS_DIR"/backup-* 2>/dev/null | sort)
|
||||
if (( ${#old[@]} > BACKUP_KEEP )); then
|
||||
local del=$(( ${#old[@]} - BACKUP_KEEP )) i
|
||||
for ((i=0; i<del; i++)); do rm -rf "${old[$i]}"; done
|
||||
fi
|
||||
printf '%s' "$bdir"
|
||||
}
|
||||
|
||||
install_bundle() {
|
||||
local src="$1" # Verzeichnis mit cert.pem, key.pem, ca.pem
|
||||
check_pair "$src/cert.pem" "$src/key.pem" "$src/ca.pem" "$FQDN"
|
||||
|
||||
log "Aktuelle Zertifikate:"
|
||||
if [[ -f "$TLS_CERT" ]]; then
|
||||
openssl x509 -in "$TLS_CERT" -noout -subject -enddate 2>/dev/null | sed 's/^/ alt: /' || true
|
||||
else
|
||||
printf ' alt: (keine vorhanden)\n'
|
||||
fi
|
||||
openssl x509 -in "$src/cert.pem" -noout -subject -enddate | sed 's/^/ neu: /'
|
||||
|
||||
if ! confirm "Zertifikate installieren und Samba neu starten?"; then
|
||||
die "Abgebrochen."
|
||||
fi
|
||||
|
||||
local bdir; bdir="$(backup_current)"
|
||||
|
||||
install -d -m 0700 -o root -g root "$TLS_DIR"
|
||||
install -m 0600 -o root -g root "$src/key.pem" "$TLS_KEY.new"
|
||||
install -m 0644 -o root -g root "$src/cert.pem" "$TLS_CERT.new"
|
||||
install -m 0644 -o root -g root "$src/ca.pem" "$TLS_CA.new"
|
||||
mv -f "$TLS_KEY.new" "$TLS_KEY"
|
||||
mv -f "$TLS_CERT.new" "$TLS_CERT"
|
||||
mv -f "$TLS_CA.new" "$TLS_CA"
|
||||
command -v restorecon >/dev/null 2>&1 && restorecon -R "$TLS_DIR" >/dev/null 2>&1 || true
|
||||
ok "Dateien installiert."
|
||||
|
||||
restart_samba
|
||||
|
||||
if ! health_check "$FQDN"; then
|
||||
if (( DO_ROLLBACK )); then
|
||||
warn "Health-Check fehlgeschlagen - rolle zurueck auf $bdir"
|
||||
restore_backup "$bdir"
|
||||
restart_samba
|
||||
health_check "$FQDN" || warn "Auch nach Rollback nicht sauber - bitte manuell pruefen!"
|
||||
die "Rollback durchgefuehrt. Zertifikate wurden NICHT geaendert."
|
||||
fi
|
||||
warn "Health-Check fehlgeschlagen (Rollback deaktiviert)."
|
||||
return 1
|
||||
fi
|
||||
ok "Fertig. Neues Zertifikat ist aktiv."
|
||||
}
|
||||
|
||||
restore_backup() {
|
||||
local bdir="$1"
|
||||
[[ -d "$bdir" ]] || die "Backup-Verzeichnis nicht gefunden: $bdir"
|
||||
[[ -f "$bdir/$(basename "$TLS_KEY")" ]] && install -m 0600 "$bdir/$(basename "$TLS_KEY")" "$TLS_KEY"
|
||||
[[ -f "$bdir/$(basename "$TLS_CERT")" ]] && install -m 0644 "$bdir/$(basename "$TLS_CERT")" "$TLS_CERT"
|
||||
[[ -f "$bdir/$(basename "$TLS_CA")" ]] && install -m 0644 "$bdir/$(basename "$TLS_CA")" "$TLS_CA"
|
||||
ok "Backup zurueckgespielt: $bdir"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kommandos
|
||||
# ---------------------------------------------------------------------------
|
||||
cmd_renew() {
|
||||
need_root; require_ca; detect_dsa_guid
|
||||
local tmp; tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
|
||||
log "Erzeuge Zertifikat fuer $FQDN"
|
||||
(( DRY_RUN )) && { log "(dry-run) kein Schluesselmaterial erzeugt"; return 0; }
|
||||
make_cert "$FQDN" "$tmp" "${DSA_GUID:-}"
|
||||
install_bundle "$tmp"
|
||||
}
|
||||
|
||||
cmd_issue_for() {
|
||||
need_root; require_ca
|
||||
local host="${1,,}"; shift || true
|
||||
local guid="${1:-}"
|
||||
[[ -n "$host" ]] || die "Verwendung: $PROG issue-for <fqdn.des.dc> [dsa-guid]"
|
||||
[[ "$host" == *.* ]] || die "Bitte den vollstaendigen FQDN angeben."
|
||||
|
||||
local out="${OUT_DIR:-/root/samba-tls-$host}"
|
||||
log "Erzeuge Bundle fuer $host in $out"
|
||||
(( DRY_RUN )) && return 0
|
||||
rm -rf "$out"
|
||||
make_cert "$host" "$out" "$guid"
|
||||
check_pair "$out/cert.pem" "$out/key.pem" "$out/ca.pem" "$host"
|
||||
|
||||
local tgz="/root/samba-tls-$host.tar.gz"
|
||||
tar -czf "$tgz" -C "$out" cert.pem key.pem ca.pem
|
||||
chmod 0600 "$tgz"
|
||||
ok "Bundle: $tgz"
|
||||
cat <<EOF
|
||||
|
||||
Uebertragen und einspielen:
|
||||
scp $tgz root@$host:/root/
|
||||
ssh root@$host '$PROG install /root/$(basename "$tgz")'
|
||||
EOF
|
||||
}
|
||||
|
||||
cmd_request() {
|
||||
need_root; detect_dsa_guid
|
||||
local out="${OUT_DIR:-/root/samba-tls-request}"
|
||||
install -d -m 0700 "$out"
|
||||
local conf="$out/openssl.cnf"
|
||||
write_cert_conf "$FQDN" "$conf" "${DSA_GUID:-}"
|
||||
(( DRY_RUN )) && return 0
|
||||
openssl req -new -nodes -newkey "rsa:$KEY_BITS" -sha256 \
|
||||
-keyout "$out/key.pem" -out "$out/csr.pem" -config "$conf"
|
||||
chmod 0600 "$out/key.pem"
|
||||
ok "CSR: $out/csr.pem (Key bleibt hier: $out/key.pem)"
|
||||
cat <<EOF
|
||||
|
||||
Auf dem CA-DC signieren lassen:
|
||||
scp $out/csr.pem root@<ca-dc>:/tmp/
|
||||
ssh root@<ca-dc> '$PROG sign /tmp/csr.pem $FQDN'
|
||||
Danach cert.pem + ca.pem zurueckkopieren und:
|
||||
$PROG install-files $out/cert.pem $out/key.pem $out/ca.pem
|
||||
EOF
|
||||
}
|
||||
|
||||
cmd_sign() {
|
||||
need_root; require_ca
|
||||
local csr="$1" cn="${2:-}"
|
||||
[[ -f "$csr" ]] || die "CSR nicht gefunden: $csr"
|
||||
[[ -n "$cn" ]] || cn="$(openssl req -in "$csr" -noout -subject | sed -n 's/.*CN *= *\([^,/]*\).*/\1/p')"
|
||||
[[ -n "$cn" ]] || die "CN konnte nicht ermittelt werden - bitte als 2. Argument angeben."
|
||||
local out="${OUT_DIR:-/root/samba-tls-signed-$cn}"
|
||||
install -d -m 0700 "$out"
|
||||
local conf="$out/openssl.cnf"
|
||||
write_cert_conf "${cn,,}" "$conf" "${DSA_GUID:-}"
|
||||
(( DRY_RUN )) && return 0
|
||||
[[ -s "$CA_SRL" ]] || openssl rand -hex 16 >"$CA_SRL"
|
||||
openssl x509 -req -in "$csr" -sha256 -days "$CERT_DAYS" \
|
||||
-CA "$CA_CERT" -CAkey "$CA_KEY" -CAserial "$CA_SRL" \
|
||||
-extfile "$conf" -extensions v3_sign -out "$out/cert.pem" 2>/dev/null
|
||||
cp -f "$CA_CERT" "$out/ca.pem"
|
||||
chmod 0644 "$out/cert.pem" "$out/ca.pem"
|
||||
ok "Signiert: $out/cert.pem (+ ca.pem)"
|
||||
}
|
||||
|
||||
cmd_install() {
|
||||
need_root
|
||||
local arg="$1"
|
||||
local tmp; tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
|
||||
if [[ -d "$arg" ]]; then
|
||||
cp -f "$arg/cert.pem" "$arg/key.pem" "$arg/ca.pem" "$tmp/"
|
||||
elif [[ -f "$arg" ]]; then
|
||||
tar -xzf "$arg" -C "$tmp"
|
||||
else
|
||||
die "Bundle nicht gefunden: $arg"
|
||||
fi
|
||||
chmod 0600 "$tmp/key.pem"
|
||||
install_bundle "$tmp"
|
||||
}
|
||||
|
||||
cmd_install_files() {
|
||||
need_root
|
||||
local c="$1" k="$2" a="$3"
|
||||
local tmp; tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
|
||||
install -m 0644 "$c" "$tmp/cert.pem"
|
||||
install -m 0600 "$k" "$tmp/key.pem"
|
||||
install -m 0644 "$a" "$tmp/ca.pem"
|
||||
install_bundle "$tmp"
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
print_env
|
||||
echo
|
||||
local f
|
||||
for f in "$CA_CERT:CA" "$TLS_CERT:DC-Zertifikat"; do
|
||||
local p="${f%%:*}" n="${f##*:}"
|
||||
if [[ -f "$p" ]]; then
|
||||
printf ' %s (%s)\n' "$n" "$p"
|
||||
openssl x509 -in "$p" -noout -subject -issuer -startdate -enddate 2>/dev/null | sed 's/^/ /'
|
||||
openssl x509 -in "$p" -noout -ext subjectAltName 2>/dev/null | sed -n '2p' | sed 's/^ */ SAN: /'
|
||||
if ! openssl x509 -in "$p" -noout -checkend $((30*86400)) >/dev/null 2>&1; then
|
||||
warn " laeuft in weniger als 30 Tagen ab!"
|
||||
fi
|
||||
echo
|
||||
else
|
||||
warn "$n fehlt: $p"
|
||||
fi
|
||||
done
|
||||
[[ -f "$STATE_FILE" ]] && printf ' letztes Backup: %s\n' "$(cat "$STATE_FILE")"
|
||||
return 0
|
||||
}
|
||||
|
||||
cmd_verify() {
|
||||
need_root
|
||||
[[ -f "$TLS_CERT" && -f "$TLS_KEY" && -f "$TLS_CA" ]] || die "Zertifikatsdateien unvollstaendig."
|
||||
check_pair "$TLS_CERT" "$TLS_KEY" "$TLS_CA" "$FQDN"
|
||||
health_check "$FQDN"
|
||||
}
|
||||
|
||||
cmd_rollback() {
|
||||
need_root
|
||||
local bdir="${1:-}"
|
||||
if [[ -z "$bdir" ]]; then
|
||||
[[ -f "$STATE_FILE" ]] || die "Kein Backup-Zeiger gefunden. Pfad bitte angeben."
|
||||
bdir="$(cat "$STATE_FILE")"
|
||||
fi
|
||||
confirm "Auf $bdir zurueckrollen?" || die "Abgebrochen."
|
||||
restore_backup "$bdir"
|
||||
restart_samba
|
||||
health_check "$FQDN" || warn "Health-Check nach Rollback nicht sauber."
|
||||
}
|
||||
|
||||
cmd_export_ca() {
|
||||
require_ca
|
||||
local out="${1:-/root/$(printf '%s' "$REALM_LC" | tr '.' '-')-ca.crt}"
|
||||
cp -f "$CA_CERT" "$out"; chmod 0644 "$out"
|
||||
ok "CA-Zertifikat exportiert: $out"
|
||||
cat <<EOF
|
||||
|
||||
Verteilung an Clients:
|
||||
Windows : per GPO nach 'Vertrauenswuerdige Stammzertifizierungsstellen'
|
||||
Debian : cp $out /usr/local/share/ca-certificates/ && update-ca-certificates
|
||||
RHEL : cp $out /etc/pki/ca-trust/source/anchors/ && update-ca-trust
|
||||
Nicht vergessen: sssd/realmd, Keycloak, Nextcloud, Zammad, Backup-Tools,
|
||||
Drucker, NAS - alles was per LDAPS/StartTLS an die DCs geht.
|
||||
EOF
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
$PROG $VERSION - CA und TLS-Zertifikate fuer Samba AD DCs neu bauen
|
||||
|
||||
Verwendung: $PROG [Optionen] <Kommando> [Argumente]
|
||||
|
||||
Kommandos:
|
||||
status Umgebung und aktuelle Zertifikate anzeigen
|
||||
init-ca Neue CA anlegen (einmalig, auf DC1)
|
||||
renew Zertifikat fuer DIESEN DC neu bauen + installieren
|
||||
issue-for <fqdn> [dsa-guid] Bundle fuer einen anderen DC erzeugen (auf dem CA-DC)
|
||||
install <bundle.tar.gz|dir> Bundle auf diesem DC installieren
|
||||
request Key + CSR lokal erzeugen (CA-Key bleibt auf DC1)
|
||||
sign <csr> [cn] CSR auf dem CA-DC signieren
|
||||
install-files <cert> <key> <ca>
|
||||
verify Konsistenz- und Health-Check
|
||||
rollback [backup-dir] Letzten Stand zurueckspielen
|
||||
export-ca [datei] CA-Zertifikat zur Client-Verteilung exportieren
|
||||
|
||||
Optionen:
|
||||
--ca-dir DIR CA-Verzeichnis (Default: $CA_DIR)
|
||||
--days N Laufzeit DC-Zertifikat (Default: $CERT_DAYS)
|
||||
--ca-days N Laufzeit CA (Default: $CA_DAYS)
|
||||
--bits N RSA-Schluessellaenge (Default: $KEY_BITS)
|
||||
--dns a,b,c zusaetzliche DNS-SANs (z.B. Alias-Namen, ldap.firma.de)
|
||||
--ip 10.0.0.1,... zusaetzliche IP-SANs
|
||||
--out DIR Ausgabeverzeichnis fuer issue-for/request/sign
|
||||
--no-restart Samba nicht neu starten
|
||||
--no-rollback kein automatischer Rollback bei fehlgeschlagenem Check
|
||||
--yes keine Rueckfragen
|
||||
--force bestehende CA ueberschreiben
|
||||
--dry-run nur anzeigen, nichts aendern
|
||||
-h, --help diese Hilfe
|
||||
|
||||
Ablauf bei zwei DCs (empfohlen):
|
||||
DC1: $PROG init-ca
|
||||
$PROG renew
|
||||
$PROG issue-for dc2.${REALM_LC:-example.local}
|
||||
scp /root/samba-tls-dc2.${REALM_LC:-example.local}.tar.gz root@dc2:/root/
|
||||
DC2: $PROG install /root/samba-tls-dc2.${REALM_LC:-example.local}.tar.gz
|
||||
DC1: $PROG export-ca # CA an alle Clients verteilen
|
||||
|
||||
Immer nur EINEN DC nach dem anderen anfassen und dazwischen
|
||||
'samba-tool drs showrepl' auf beiden Seiten pruefen.
|
||||
EOF
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Argumente
|
||||
# ---------------------------------------------------------------------------
|
||||
OUT_DIR="${OUT_DIR:-}"
|
||||
ARGS=()
|
||||
while (( $# )); do
|
||||
case "$1" in
|
||||
--ca-dir) CA_DIR="$2"; shift 2 ;;
|
||||
--days) CERT_DAYS="$2"; shift 2 ;;
|
||||
--ca-days) CA_DAYS="$2"; shift 2 ;;
|
||||
--bits) KEY_BITS="$2"; shift 2 ;;
|
||||
--dns) EXTRA_DNS="$2"; shift 2 ;;
|
||||
--ip) EXTRA_IP="$2"; shift 2 ;;
|
||||
--out) OUT_DIR="$2"; shift 2 ;;
|
||||
--ca-cn) CA_CN="$2"; shift 2 ;;
|
||||
--org) ORG="$2"; shift 2 ;;
|
||||
--no-restart) DO_RESTART=0; shift ;;
|
||||
--no-rollback) DO_ROLLBACK=0; shift ;;
|
||||
--yes|-y) ASSUME_YES=1; shift ;;
|
||||
--force) FORCE=1; shift ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--) shift; ARGS+=("$@"); break ;;
|
||||
-*) die "Unbekannte Option: $1" ;;
|
||||
*) ARGS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if (( ! DO_RESTART )); then
|
||||
restart_samba() {
|
||||
warn "Restart uebersprungen (--no-restart) - das Zertifikat wird erst nach einem Neustart aktiv."
|
||||
}
|
||||
fi
|
||||
|
||||
CMD="${ARGS[0]:-status}"
|
||||
detect_env
|
||||
|
||||
case "$CMD" in
|
||||
status) cmd_status ;;
|
||||
init-ca) cmd_init_ca ;;
|
||||
renew) cmd_init_ca; cmd_renew ;;
|
||||
issue-for) cmd_issue_for "${ARGS[1]:-}" "${ARGS[2]:-}" ;;
|
||||
install) cmd_install "${ARGS[1]:?Bundle angeben}" ;;
|
||||
install-files) cmd_install_files "${ARGS[1]:?cert}" "${ARGS[2]:?key}" "${ARGS[3]:?ca}" ;;
|
||||
request) cmd_request ;;
|
||||
sign) cmd_sign "${ARGS[1]:?CSR angeben}" "${ARGS[2]:-}" ;;
|
||||
verify) cmd_verify ;;
|
||||
rollback) cmd_rollback "${ARGS[1]:-}" ;;
|
||||
export-ca) cmd_export_ca "${ARGS[1]:-}" ;;
|
||||
help) usage ;;
|
||||
*) usage; die "Unbekanntes Kommando: $CMD" ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user