Initial commit: MAAS-deployable Samba Active Directory domain controller

Builds a Debian image that MAAS deploys to bare metal as an Active Directory
domain controller, with first-boot automation that either creates a domain or
joins an existing one without anyone logging in. To a Windows client the result
is an AD domain: same Kerberos, same LDAP, same Group Policy, same domain join.

The request was for "primary and backup domain controllers", which is NT 4
terminology. Active Directory has no such split — every DC holds a writable copy
and any of them can service a logon. What lives on one DC at a time are the five
FSMO roles, one confusingly named "PDC Emulator". The image therefore offers
provision (create the domain) and join (add another equal DC), which is the
distinction that actually exists.

Contents:

  * customize-samba-ad.sh, run inside the packer-maas build VM: installs samba,
    winbind, Kerberos, chrony and rsync, swaps the cloud kernel for the generic
    one, and deletes every trace of a domain so one image can produce many DCs
  * adc-maas-init, a first-boot state machine covering /etc/hosts, node-unique
    identifiers, time, the resolver, provisioning or joining, the service
    switchover and a self-test that proves Kerberos issues a ticket
  * adc-sysvol-sync, a systemd timer implementing the SYSVOL workaround
  * a MAAS curtin preseed, cloud-init examples, and the release pipeline

Two findings shaped the design.

Debian builds Samba against its bundled Heimdal rather than system MIT Kerberos
— samba-ad-dc does not depend on krb5-kdc — so the AD DC role is in Samba's
supported configuration, not the experimental MIT one. The build asserts this
and fails if it ever changes.

Samba implements neither DFS-R nor FRS, so SYSVOL — where Group Policy lives —
does not replicate between DCs. Left alone, a policy created on one DC never
reaches the others and clients behave differently depending on which DC answered
them, with nothing reporting an error. adc-sysvol-sync applies the Samba wiki's
rsync workaround: sync idmap.ldb once so SID-to-uid mappings agree, rsync the
tree, then samba-tool ntacl sysvolreset because rsync carries POSIX bits while
the Windows ACLs live in AD. Without an SSH key to the source DC it exits with
an explanation rather than letting Group Policy diverge quietly.

Also carried over from maas-proxmox, where they were verified on real hardware:
the curtin-hooks that skip the kernel install and pin interface names by MAC,
Type=simple on the first-boot unit to avoid the systemd ordering cycle, and
shipping networking.service disabled so MAAS owns the network on first boot.
Specific to this image, the Debian cloud kernel is replaced with the generic one
— a bare-metal node booted with the cloud kernel can come up with no disk.

Nothing here has been built or deployed. The README says so at the top and in a
Verified status section that separates what was checked from what was not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-05 14:44:37 +02:00
commit cf07702535
25 changed files with 3158 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
#!/bin/bash
# Copyright (C) 2026 Ilker Manap
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# adc-sysvol-sync - pulls SYSVOL from another domain controller.
#
# Samba implements neither DFS-R nor FRS, so the SYSVOL share — which holds
# Group Policy objects and logon scripts — does not replicate between domain
# controllers by itself. Without something like this, a policy created on one DC
# is invisible to clients that authenticate against another, and the domain
# silently behaves differently depending on which DC a client happened to reach.
#
# This is the rsync approach from the Samba wiki: pull from the DC holding the
# PDC Emulator FSMO role, then reset the POSIX ACLs from what is stored in AD.
#
# Configuration: /etc/adc-maas/sysvol-sync.conf (SYSVOL_SOURCE=<dc fqdn or ip>)
# Authentication: root SSH key to the source DC. Provide one at deploy time;
# without it this exits with a clear message rather than failing silently.
set -uo pipefail
CONF=/etc/adc-maas/sysvol-sync.conf
SYSVOL=/var/lib/samba/sysvol
LOG_TAG=adc-sysvol-sync
log() { echo "[$LOG_TAG] $*"; logger -t "$LOG_TAG" -- "$*" 2>/dev/null || true; }
warn() { echo "[$LOG_TAG] WARN: $*" >&2; logger -t "$LOG_TAG" -p user.warning -- "$*" 2>/dev/null || true; }
[ -r "$CONF" ] || { log "no $CONF, nothing to do"; exit 0; }
# shellcheck disable=SC1090
. "$CONF"
: "${SYSVOL_SOURCE:?SYSVOL_SOURCE not set in $CONF}"
[ -d "$SYSVOL" ] || { warn "$SYSVOL missing — is this host a DC?"; exit 1; }
SSH_OPTS="-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10"
if ! ssh $SSH_OPTS "root@${SYSVOL_SOURCE}" true 2>/dev/null; then
warn "cannot reach root@${SYSVOL_SOURCE} over SSH — SYSVOL is NOT replicating"
warn "install a root SSH key on this host that is authorised on ${SYSVOL_SOURCE},"
warn "or replicate SYSVOL by some other means. Group Policy will diverge until then."
exit 1
fi
# idmap.ldb decides which POSIX uid/gid each SID maps to. If it differs between
# DCs, the same file shows different ownership depending on which DC serves it.
# The Samba wiki calls syncing it a prerequisite, not an optimisation.
if [ ! -e /var/lib/adc-maas/idmap-synced ]; then
log "syncing idmap.ldb from ${SYSVOL_SOURCE} (one time)"
if rsync -a -e "ssh $SSH_OPTS" \
"root@${SYSVOL_SOURCE}:/var/lib/samba/private/idmap.ldb" \
/var/lib/samba/private/idmap.ldb; then
mkdir -p /var/lib/adc-maas && date -Is > /var/lib/adc-maas/idmap-synced
systemctl restart samba-ad-dc >/dev/null 2>&1 || true
else
warn "idmap.ldb sync failed; uid/gid mappings may differ between DCs"
fi
fi
log "pulling SYSVOL from ${SYSVOL_SOURCE}"
if rsync -aAX --delete -e "ssh $SSH_OPTS" \
"root@${SYSVOL_SOURCE}:${SYSVOL}/" "${SYSVOL}/"; then
# rsync carries POSIX bits; the Windows ACLs come from AD and have to be
# reapplied afterwards or clients get access-denied on Group Policy.
if samba-tool ntacl sysvolreset >/dev/null 2>&1; then
log "SYSVOL synced and ACLs reset"
else
warn "sysvolreset failed — clients may be denied access to Group Policy"
exit 1
fi
else
warn "rsync failed"
exit 1
fi