#!/usr/bin/env bash # ===================================================================================== # End-to-end smoke test. Fully isolated: realm TESTLAB.LOCAL on an internal-only docker # network, named volumes, no host ports, throwaway credentials. # # Proves, in order: # 1. the image builds # 2. a fresh volume provisions a working AD DC (DNS/Kerberos/LDAP/GC/SYSVOL) # 3. hardening params actually land in smb.conf # 4. a restart REUSES the database instead of re-provisioning (idempotency) # 5. the full verify battery passes both before and after the restart # # ./tests/smoke.sh # build + test + destroy # KEEP=1 ./tests/smoke.sh # leave the lab container running for poking at # ===================================================================================== set -uo pipefail cd "$(dirname "$0")/.." LAB_FILE=docker-compose.lab.yml CID=ad-lab LAB_PW='Lab!Only9Tester#2026' IMAGE=local/samba-ad-dc:4.22-debian13 fail_hard() { echo echo "!! ${1}" >&2 echo "---- recent log (schema-apply noise filtered) ----" >&2 docker compose -f "$LAB_FILE" logs --tail 400 2>&1 \ | grep -vE '(Applied|Skip) (Domain|Forest) Update' | tail -40 | sed 's/^/ /' >&2 [ "${KEEP:-0}" = 1 ] || docker compose -f "$LAB_FILE" down -v >/dev/null 2>&1 || true exit 1 } echo "==> 1/6 build image" if ! docker build -t "$IMAGE" -f image/Dockerfile . ; then echo "!! image build failed" >&2 exit 1 fi echo "==> 2/6 start lab DC" docker compose -f "$LAB_FILE" down -v >/dev/null 2>&1 || true # always start from a clean slate docker compose -f "$LAB_FILE" up -d >/dev/null 2>&1 || fail_hard "compose up failed" wait_healthy() { # $1 = seconds local deadline=$(( SECONDS + ${1:-240} )) st="" while [ "$SECONDS" -lt "$deadline" ]; do st="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$CID" 2>/dev/null || echo missing)" [ "$st" = healthy ] && return 0 [ "$st" = unhealthy ] && return 2 printf '.' sleep 4 done return 1 } printf ' waiting for healthy ' wait_healthy 300; rc=$? echo " -> $rc" [ "$rc" = 0 ] || fail_hard "lab DC never became healthy (rc=$rc)" if ! docker compose -f "$LAB_FILE" logs 2>&1 | grep -q 'provisioning complete'; then fail_hard "expected the entrypoint to report a fresh provisioning run" fi echo " fresh provisioning reported" verify() { AD_SKIP_DOTENV=1 AD_CONTAINER="$CID" \ AD_REALM=TESTLAB.LOCAL AD_DNS_DOMAIN=testlab.local AD_HOSTNAME=dc1 \ AD_ADMIN_PASSWORD="$LAB_PW" ./scripts/verify.sh } echo "==> 3/6 verification battery (fresh forest)" verify || fail_hard "verification failed on a freshly provisioned forest" echo "==> 4/6 hardening landed in smb.conf" for kv in 'server signing = required' 'smb encrypt = desired' 'ntlm auth = ntlmv2-only' 'restrict anonymous = 2'; do if docker exec "$CID" grep -q "^[[:space:]]*${kv}[[:space:]]*$" /etc/samba/smb.conf; then echo " ok: $kv" else fail_hard "missing '$kv' in rendered smb.conf" fi done echo "==> 5/6 restart must NOT re-provision" # Count log lines first and diff afterwards: `docker logs --since` is interpreted in the # daemon's local timezone, which silently mis-filters when the host is not UTC. log_before="$(docker compose -f "$LAB_FILE" logs 2>/dev/null | wc -l)" docker restart "$CID" >/dev/null 2>&1 || fail_hard "restart failed" printf ' waiting for healthy after restart ' wait_healthy 180; rc=$? echo " -> $rc" [ "$rc" = 0 ] || fail_hard "not healthy after restart" log="$(docker compose -f "$LAB_FILE" logs 2>/dev/null | tail -n +$((log_before + 1)))" if printf '%s' "$log" | grep -q 'provisioning NEW forest'; then fail_hard "restart tried to re-provision the domain (idempotency broken)" fi printf '%s' "$log" | grep -q 'existing AD database found' || fail_hard "restart did not detect the existing database" echo " entrypoint reused the existing database" echo "==> 6/6 verification battery (after restart)" verify || fail_hard "verification failed after restart" echo echo -e "\033[32mSMOKE TEST PASSED\033[0m (image: $IMAGE)" if [ "${KEEP:-0}" = 1 ]; then echo "lab left running: docker compose -f $LAB_FILE logs -f | docker exec -it $CID bash" else docker compose -f "$LAB_FILE" down -v >/dev/null 2>&1 || true echo "lab destroyed" fi