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:
59
maas/curtin_userdata_custom.in
Normal file
59
maas/curtin_userdata_custom.in
Normal file
@@ -0,0 +1,59 @@
|
||||
#cloud-config
|
||||
# Copyright (C) 2026 Ilker Manap
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# MAAS curtin preseed - Samba AD domain controller image
|
||||
#
|
||||
# Dosya adi: curtin_userdata_custom_@@ARCH@@_generic_@@IMAGE_NAME@@
|
||||
# Kopyalanacak yer (MAAS snap): /var/snap/maas/current/preseeds/
|
||||
# (MAAS deb paketi): /etc/maas/preseeds/
|
||||
#
|
||||
# Bu ad yalnizca 'custom/@@IMAGE_NAME@@' adiyla, '@@ARCH@@/generic' mimarisiyle
|
||||
# yuklenen imaja uygulanir; diger ozel imajlariniz etkilenmez.
|
||||
|
||||
# Cekirdek kurulumu imajdaki /curtin/curtin-hooks tarafindan devre disi
|
||||
# birakilir (Proxmox cekirdegi imajda hazir gelir). Asagidaki blok yalnizca
|
||||
# kanca herhangi bir nedenle calismazsa devreye giren yedek yoldur.
|
||||
#
|
||||
# DIKKAT: burada 'kernel: null' KULLANMAYIN. MAAS ile gelen curtin
|
||||
# surumlerinin bir kismi bunu desteklemiyor ve kurulum su hatayla basarisiz
|
||||
# oluyor: install_kernel -> AttributeError: 'NoneType' object has no
|
||||
# attribute 'get'
|
||||
kernel:
|
||||
package: linux-image-@@ARCH@@
|
||||
fallback-package: linux-image-@@ARCH@@
|
||||
mapping: {}
|
||||
|
||||
apt:
|
||||
preserve_sources_list: true
|
||||
|
||||
debconf_selections:
|
||||
maas: |
|
||||
{{for line in str(curtin_preseed).splitlines()}}
|
||||
{{line}}
|
||||
{{endfor}}
|
||||
|
||||
late_commands:
|
||||
maas: [wget, '--no-proxy', '{{node_disable_pxe_url}}', '--post-data', '{{node_disable_pxe_data}}', '-O', '/dev/null']
|
||||
|
||||
# Hedef sistemi /mnt altina bagla
|
||||
late_01: mount --bind $TARGET_MOUNT_POINT /mnt
|
||||
|
||||
# MAAS datasource tanimini hedef sisteme tasi
|
||||
late_02: grep -A2 datasource /etc/cloud/cloud.cfg.d/91_kernel_cmdline_url.cfg | sed 's/curtin//' | tee /mnt/etc/cloud/cloud.cfg.d/91_maas_datasource.cfg
|
||||
|
||||
# Debian cloud-init sablonundaki Ubuntu referanslarini duzelt
|
||||
late_03: sed -i 's@ubuntu.com/ubuntu@debian.org/debian@g;s@archive@deb@g;s@ubuntu@debian@g;s@Ubuntu@Debian@g;s@security.debian.org/debian@security.debian.org@g' /mnt/etc/cloud/cloud.cfg
|
||||
|
||||
# zz-update-grub kancasindaki 'set -e' tuzagini kaldir
|
||||
late_04: sed -i '/^set -e/{n;N;d}' /mnt/etc/kernel/postinst.d/zz-update-grub
|
||||
|
||||
# packer-maas'in netplan.io kontrolunu atlatmak icin koydugu sarmalayicilari sil
|
||||
late_05: rm -f /usr/local/bin/dpkg-query /usr/local/bin/netplan
|
||||
late_06: rm -f /mnt/usr/local/bin/dpkg-query /mnt/usr/local/bin/netplan
|
||||
|
||||
# Safety net: make sure the first-boot service really is enabled
|
||||
late_07: chroot /mnt systemctl enable adc-maas-init.service || true
|
||||
|
||||
# Clear any stage markers left over from a previous run
|
||||
late_08: rm -f /mnt/var/lib/adc-maas/*.done /mnt/var/lib/adc-maas/complete
|
||||
40
maas/examples/01-first-dc.yaml
Normal file
40
maas/examples/01-first-dc.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
#cloud-config
|
||||
# Copyright (C) 2026 Ilker Manap
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# The FIRST domain controller: creates the domain.
|
||||
#
|
||||
# In Active Directory this is not a "primary" DC in the NT 4 sense — every DC is
|
||||
# an equal, writable peer. This one simply happens to run the provisioning step,
|
||||
# and it starts out holding the five FSMO roles.
|
||||
#
|
||||
# maas $PROFILE machine deploy $SYSTEM_ID \
|
||||
# osystem=custom distro_series=samba-ad-dc \
|
||||
# user_data="$(base64 -w0 01-first-dc.yaml)"
|
||||
|
||||
write_files:
|
||||
- path: /etc/adc-maas/conf.d/50-ad.conf
|
||||
permissions: "0600"
|
||||
owner: root:root
|
||||
content: |
|
||||
AD_MODE=provision
|
||||
|
||||
# Kerberos realm: your DNS domain in UPPERCASE. Use something you control
|
||||
# and that is not your public web domain. Never a bare ".local".
|
||||
AD_REALM=AD.EXAMPLE.COM
|
||||
|
||||
# NetBIOS name: uppercase, at most 15 characters, no dots.
|
||||
AD_DOMAIN=EXAMPLE
|
||||
|
||||
# Becomes the domain Administrator password. Active Directory enforces
|
||||
# complexity: 7+ characters, three of upper/lower/digit/symbol. A weak one
|
||||
# makes provisioning fail with an unhelpful error.
|
||||
AD_ADMIN_PASSWORD='CHANGE-ME-Str0ng!'
|
||||
|
||||
# Where the DC forwards queries it is not authoritative for. Without this
|
||||
# the DC resolves your domain and nothing else.
|
||||
AD_DNS_FORWARDER=192.0.2.1
|
||||
|
||||
# Subnet allowed to use this DC as a time source. Domain members need
|
||||
# working time — Kerberos rejects a skew over five minutes.
|
||||
AD_NTP_ALLOW=192.0.2.0/24
|
||||
55
maas/examples/02-additional-dc.yaml
Normal file
55
maas/examples/02-additional-dc.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
#cloud-config
|
||||
# Copyright (C) 2026 Ilker Manap
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# An ADDITIONAL domain controller: joins the existing domain.
|
||||
#
|
||||
# This is what gives the domain redundancy. Two DCs mean logons survive losing
|
||||
# one of them; point clients at both as DNS servers.
|
||||
|
||||
write_files:
|
||||
- path: /etc/adc-maas/conf.d/50-ad.conf
|
||||
permissions: "0600"
|
||||
owner: root:root
|
||||
content: |
|
||||
AD_MODE=join
|
||||
AD_REALM=AD.EXAMPLE.COM
|
||||
AD_DOMAIN=EXAMPLE
|
||||
|
||||
# An existing DC, by IP. Needed because this host cannot resolve the
|
||||
# domain until it points its resolver at a DC that already serves it.
|
||||
AD_JOIN_PEER=192.0.2.10
|
||||
|
||||
# Credentials of a Domain Admin on that DC.
|
||||
AD_JOIN_USER=Administrator
|
||||
AD_ADMIN_PASSWORD='CHANGE-ME-Str0ng!'
|
||||
|
||||
AD_DNS_FORWARDER=192.0.2.1
|
||||
AD_NTP_ALLOW=192.0.2.0/24
|
||||
|
||||
# SYSVOL — where Group Policy lives — does not replicate by itself:
|
||||
# Samba implements neither DFS-R nor FRS. This pulls it from the first DC
|
||||
# over rsync and reapplies the ACLs afterwards.
|
||||
#
|
||||
# It needs a root SSH key here that is authorised on AD_SYSVOL_SOURCE.
|
||||
# Without one the sync exits with an explanation instead of silently
|
||||
# letting Group Policy diverge between DCs.
|
||||
AD_SYSVOL_SYNC=on
|
||||
AD_SYSVOL_SOURCE=192.0.2.10
|
||||
AD_SYSVOL_INTERVAL=5min
|
||||
|
||||
# The private key the SYSVOL sync uses to reach the source DC. Its public
|
||||
# half must be in /root/.ssh/authorized_keys on that DC.
|
||||
#
|
||||
# This puts a private key into MAAS user-data, where anyone with MAAS access
|
||||
# can read it. Use a key generated for this purpose only, authorised for
|
||||
# nothing else. If that is not acceptable, leave this out and set up SYSVOL
|
||||
# replication by hand after deployment - the sync script says exactly what it
|
||||
# needs and refuses to pretend it is working.
|
||||
- path: /root/.ssh/id_ed25519
|
||||
permissions: "0600"
|
||||
owner: root:root
|
||||
content: |
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
REPLACE-WITH-A-KEY-DEDICATED-TO-SYSVOL-REPLICATION
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
13
maas/examples/03-no-domain.yaml
Normal file
13
maas/examples/03-no-domain.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
#cloud-config
|
||||
# Copyright (C) 2026 Ilker Manap
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# Deploy the image without touching any domain — useful for staging a machine
|
||||
# now and provisioning or joining later by hand.
|
||||
|
||||
write_files:
|
||||
- path: /etc/adc-maas/conf.d/50-ad.conf
|
||||
permissions: "0600"
|
||||
owner: root:root
|
||||
content: |
|
||||
AD_MODE=none
|
||||
Reference in New Issue
Block a user