Files
homelab-infra/infrastructure/samba-ad/README.md
T
panxiao81 97021109ac
yaml / yaml (pull_request) Successful in 39s
ansible / lint (pull_request) Failing after 3m5s
feat(dns): 建立统一清单并接管 Samba 静态记录
2026-09-10 17:24:49 +00:00

425 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Samba Active Directory Domain Controller
Samba AD DC is the **identity source of truth** for the whole environment: Kerberos,
LDAP, DNS, SMB and Group Policy — a real Active Directory domain that Windows VMs can
domain-join and be GPO-managed, **without a licensed Windows Server as the DC**.
Consumers layered on top (later docs):
- **Samba file server** — domain member serving SMB shares.
- **Linux clients** — `realmd`/`sssd` domain join for central login.
- **Authelia** — LDAP source against this DC → OIDC / forward-auth web SSO.
This document covers **Phase 1 (the DC)** and **Phase 2 (the Windows admin box)** only.
**Everything here is managed as code with Ansible** — see `ansible/`. The shell
commands in §1–§2 are documentation of *what the roles automate*, not steps to run by
hand. Run the playbooks.
---
## Ansible quickstart
```bash
cd ansible
# 1. one-time: ansible + collections + WinRM lib
# (uv-installed ansible: uv tool install ansible-core --with ansible --with pywinrm --with requests-ntlm)
ansible-galaxy collection install -r requirements.yml
# 2. set your values (already filled in for ad.ddupan.top / DDUPAN / 192.168.10.5)
$EDITOR group_vars/all/vars.yml # realm, netbios, DC IP, forwarder, KMS host
$EDITOR inventory/hosts.yml # DC + Windows box addresses
cp group_vars/all/vault.example.yml group_vars/all/vault.yml
$EDITOR group_vars/all/vault.yml # admin passwords
ansible-vault encrypt group_vars/all/vault.yml # optional; plaintext also works (gitignored)
# 3. create the VM locally (libvirt + Ubuntu cloud image + cloud-init)
ssh-keygen -t ed25519 # if you don't have ~/.ssh/id_ed25519.pub yet
ansible-playbook create-dc-vm.yml # boots dc1 on br0 at 192.168.10.5
# 4. provision the domain, then verify
ansible-playbook provision-dc.yml --ask-vault-pass
ansible-playbook provision-dc.yml --ask-vault-pass --tags verify
# 5. join + configure the Windows admin box (needs WinRM reachable)
ansible-playbook join-windows.yml --ask-vault-pass
```
Re-running is safe: domain provisioning is guarded by the `sam.ldb` database, so a
second run is a no-op there and only reconciles config drift.
**Layout**
| Path | Purpose |
|---|---|
| `roles/dc_vm/` | creates the DC VM locally via libvirt + cloud-init |
| `roles/samba_ad_dc/` | provisions the DC (§1) — idempotent |
| `ansible/collections/ansible_collections/ddupan/homelab/` | repository-local collection containing the Samba DNS module and its `ansible-test` tests |
| `roles/samba_ad_dc/tasks/verify.yml` | smoke tests (`--tags verify`) |
| `roles/samba_ad_dc/tasks/legacy.yml` | opt-in retro-client protocols (§ Retro clients) |
| `roles/windows_vm/` | unattended-installs the Windows Server 2025 admin box (§2.0) |
| `roles/win_domain_join/` | joins Windows box, installs RSAT, KMS activation (§2) |
| `group_vars/all/vars.yml` | all non-secret settings — **edit this** |
| `group_vars/all/vault.yml` | admin passwords (gitignored; encrypt with ansible-vault) |
| `inventory/hosts.yml` | DC + Windows hosts |
Static DNS records are declared once in `../../dns/records.yml`. The role manages
only those RRsets and deliberately leaves Samba-generated AD locator records alone.
---
## 0. Decisions — fill these in before touching anything
These map 1:1 onto `ansible/group_vars/all.yml`.
| Item | Value | Notes |
|---|---|---|
| DNS realm | `AD.DDUPAN.TOP` | Delegated subdomain of `ddupan.top`. Never `.local`. |
| NetBIOS / short name | `DDUPAN` | ≤15 chars, uppercase, no dots. Windows-visible short domain. |
| DC hostname (FQDN) | `dc1.ad.ddupan.top` | |
| DC static IP | `192.168.10.5/24` | On `br0`; free in the `.2–.9` range. **Must stay static.** |
| Gateway | `192.168.10.1` | |
| Domain admin | `Administrator` | password from the vault |
| Forwarder DNS | `192.168.10.1` | where the DC forwards non-AD lookups |
| Windows admin box | `192.168.10.6` | RSAT/GPMC station |
> The example shell in §1–§2 still shows `example.com`-style placeholders for
> readability; the **live values above** are what `group_vars/all.yml` actually sets.
---
## 1. Provision the DC VM
> Automated by `roles/samba_ad_dc`. The commands below are what each task does.
**Base:** Debian 12 (or Ubuntu 24.04 LTS) VM — a **full VM, not LXC**. The DC is a
stateful pet; a VM avoids LXC keyring/namespace quirks with Kerberos. (The VM must
already exist and be reachable over SSH; this role configures the OS, it does not
create the VM — see "VM lifecycle" below.)
### 1.1 Host prerequisites
```bash
# Static IP, correct hostname, and time sync are non-negotiable.
hostnamectl set-hostname dc1.ad.example.com
timedatectl set-ntp true # or install chrony; Kerberos dies on >5min skew
apt update && apt install -y chrony
```
`/etc/hosts` — the DC must resolve its own FQDN to its **real** IP (not 127.0.1.1):
```
10.10.10.10 dc1.ad.example.com dc1
```
### 1.2 Free up port 53
Samba's internal DNS must bind `:53`. Disable the `systemd-resolved` stub listener:
```bash
mkdir -p /etc/systemd/resolved.conf.d
printf '[Resolve]\nDNSStubListener=no\n' > /etc/systemd/resolved.conf.d/no-stub.conf
systemctl restart systemd-resolved
# Point the DC at itself for DNS:
ln -sf /run/systemd/resolve/resolv.conf /dev/null 2>/dev/null || true
printf 'nameserver 10.10.10.10\nsearch ad.example.com\n' > /etc/resolv.conf
chattr +i /etc/resolv.conf # stop NM/cloud-init from clobbering it
```
### 1.3 Install and provision
```bash
apt install -y samba krb5-config winbind smbclient ldb-tools
# Stop distro auto-started daemons; the AD DC runs the unified `samba` service only.
systemctl disable --now smbd nmbd winbind 2>/dev/null || true
systemctl unmask samba-ad-dc
# Clean any stock config so provision writes fresh
mv /etc/samba/smb.conf /etc/samba/smb.conf.orig 2>/dev/null || true
samba-tool domain provision \
--use-rfc2307 \
--realm=AD.EXAMPLE.COM \
--domain=EXAMPLE \
--server-role=dc \
--dns-backend=SAMBA_INTERNAL \
--adminpass='CHANGE-ME-Strong.Passw0rd' \
--option="dns forwarder = 10.10.10.1"
systemctl enable --now samba-ad-dc
```
> **`--use-rfc2307` is mandatory and cannot be cleanly added later.** It stores POSIX
> uid/gid in the directory so Linux clients get stable IDs.
### 1.4 Wire Kerberos
```bash
cp /var/lib/samba/private/krb5.conf /etc/krb5.conf
```
### 1.5 Smoke tests
```bash
# LDAP / domain sanity
samba-tool domain level show
# Kerberos ticket for the admin
kinit [email protected] && klist
# DNS: the DC must resolve its own SRV records
host -t SRV _ldap._tcp.ad.example.com.
host -t SRV _kerberos._udp.ad.example.com.
host -t A dc1.ad.example.com.
# SMB: default shares present
smbclient -L localhost -U administrator
```
All four must succeed before moving on. **If anything AD-related "doesn't work"
later, it is almost always DNS** — every member must use the DC as its resolver, and
the DC must resolve itself.
---
## 2. Windows admin box (your one long-lived Windows VM)
You need exactly **one** Windows VM as the GPO authoring / AD management station.
Samba stores GPOs but you edit them with the Windows GPMC; `samba-tool gpo` only
covers basic operations.
### 2.0 Build it as code — `create-windows-vm.yml` (role `windows_vm`)
Fully unattended install of **Windows Server 2025 Standard (Desktop Experience)** from
your ISO, the Windows analogue of the DC's cloud-init:
```bash
ansible-playbook create-windows-vm.yml # ~30-45 min: install + reboots + WinRM setup
```
What it does:
- Creates zvol `data/vm/winadmin`, **UEFI/GPT**, **virtio disk + virtio NIC**.
- Renders `autounattend.xml` (edition index 2, locale zh-CN, GVLK, admin pw from vault),
packs it on a seed ISO. **Injects virtio drivers** (`viostor`/`NetKVM`, 2k25) during
Setup via `DriverPaths` off the `virtio-win` ISO so Setup sees the virtio disk.
- `FirstLogonCommands`: static IP `192.168.10.6` → DC DNS, **WinRM-over-HTTPS** listener
(self-signed), open 5986, **enable RDP**, and run **virtio-win guest tools** (QEMU
guest agent + balloon).
- Gets past "Press any key to boot from CD" with `virsh send-key`, waits for WinRM,
then ejects install media and deletes the password-bearing seed ISO.
Then hand off to §2.1–2.4 below, automated by `join-windows.yml` (role `win_domain_join`)
— domain join → RSAT → KMS activate. Needs WinRM reachable; the commands below are what
those tasks do.
> The seed ISO holds the local admin password in cleartext while Setup runs; the play
> removes it once WinRM is up. Edition index / GVLK / locale are role vars — verify the
> index against your ISO with `wiminfo …\sources\install.wim`.
### 2.1 Point it at the DC for DNS
On the Windows VM's NIC, set **DNS server = `10.10.10.10`** (the DC). This is the
single most common failure point — a Windows box using any other resolver cannot
find the domain.
### 2.2 Join the domain
`System → Rename this PC (advanced) → Domain: ad.example.com` →
authenticate as `EXAMPLE\Administrator` → reboot.
### 2.3 Install RSAT (management tools)
On Windows 10/11:
```powershell
Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0
Add-WindowsCapability -Online -Name Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0
Add-WindowsCapability -Online -Name Rsat.Dns.Tools~~~~0.0.1.0
```
This gives you **AD Users & Computers**, **Group Policy Management (GPMC)**, and the
DNS console — the graphical admin surface for the domain.
### 2.4 Activate against existing KMS
This repo already runs `vlmcsd` (KMS emulator). Activate the Windows VM against it so
the admin box stays licensed:
```powershell
slmgr /skms kms.example.com:1688 # your vlmcsd host
slmgr /ipk <GVLK-for-this-edition> # public KMS client setup key for the edition
slmgr /ato
```
---
## VM lifecycle (creating the box as code)
The VM itself is created by `roles/dc_vm` (`create-dc-vm.yml`), which runs against
the **local libvirt host**:
1. Downloads the **latest Ubuntu 24.04 LTS cloud image** (`.../noble/current/…`) once
into `/var/lib/libvirt/images/base/`.
2. Creates a **ZFS zvol** `data/vm/dc1` (`dc_vm_disk_gb`, default 25 GB, 16K
volblocksize — matching `data/vm/win2k25`) and writes the image into it raw with
`qemu-img convert`. cloud-init `growpart` expands the rootfs to fill it on first boot.
3. Renders `user-data` / `meta-data` / `network-config` from templates and packs a
**NoCloud seed ISO** (`genisoimage`, volid `cidata`). The seed sets a static IP
(`192.168.10.5/24`, gw `.1`), the hostname, and injects your SSH public key.
4. `virt-install --import` attaches the zvol (`/dev/zvol/data/vm/dc1`, virtio) and the
seed ISO, boots the domain on bridge **`br0`**, then waits for SSH.
It's idempotent — if the `dc1` domain exists the block is skipped; the zvol write is
separately guarded so a re-run never clobbers an existing disk.
**Before running:** you need an SSH keypair; the play injects
`dc_vm_ssh_pubkey_file` (default `~/.ssh/id_ed25519.pub`). Generate one with
`ssh-keygen -t ed25519` if absent — the play fails fast otherwise.
Tunables live in `roles/dc_vm/defaults/main.yml` (vCPU, RAM, disk, image URL,
bridge, gateway, `dc_vm_zvol_parent`). Alternatives (PXE via your existing `netboot/`, or a Debian base)
are still viable, but the contract is the same: hand `samba_ad_dc` a booted VM with a
static IP matching `samba_ad_dc_ip`, SSH reachable as the `ansible` user.
---
## 3. Operations
### Add users / groups (either UI or CLI)
```bash
samba-tool user create alice
samba-tool group add engineering
samba-tool group addmembers engineering alice
```
…or do it graphically from the Windows admin box via **AD Users & Computers**.
### Backups — do this before the domain becomes load-bearing
```bash
samba-tool domain backup offline --targetdir=/var/backups/samba
```
Schedule it (cron/systemd timer) and ship the tarball off-box.
### Second DC (resilience) — easy now, painful to retrofit after an outage
On a second freshly-provisioned member (DNS pointed at `dc1`):
```bash
samba-tool domain join ad.example.com DC \
-U"EXAMPLE\administrator" --dns-backend=SAMBA_INTERNAL
```
---
## 4. Gotchas cheat-sheet
- **DNS is the whole ballgame.** ~90% of "AD is broken" is a member not using the DC
as its resolver, or the DC not resolving itself. Domain members point at
`192.168.10.5` directly; non-domain LAN devices reach AD names via the router's
conditional forward — see `router-dns-nec-ix.md`.
- **Time skew > 5 min → Kerberos silently fails.** Keep `chrony` healthy on the DC and
all members.
- **Don't containerize the DC in K8s** — it fights K8s's DNS/networking model. VM only.
- **`--use-rfc2307` at provision time** or you lose stable Linux uid/gid mapping.
- **One share off the DC is fine for testing, but real shares belong on a member
file server** (next doc), not the DC.
- **GPO editing needs the Windows RSAT box**; keep that one VM around.
---
## Retro clients (Win9x / NT4 / Windows 2000 / XP)
Modern Samba disables the old protocols these machines need (SMB1, NTLMv1, LANMAN,
DES Kerberos). Support is **opt-in** via `samba_ad_legacy_clients: true` in
`group_vars/all.yml`, which runs `roles/samba_ad_dc/tasks/legacy.yml` to inject the
insecure knobs and restart the DC.
> ⚠️ This materially weakens the **entire domain** — LM/NTLMv1 hashes are trivially
> crackable and SMB1 is wormable. Only enable it on an **isolated VLAN** for the retro
> machines. Ideally, don't weaken the primary DC at all: stand up a **dedicated legacy
> Samba member server** for file access and keep the DC strict. For domain *logon* of
> 9x/NT4 (below) the DC itself must speak old crypto, so segment the network instead.
What each era can actually do:
| Client | Domain **join** | Domain **logon** | SMB **share access** | What it needs |
|---|---|---|---|---|
| **Windows XP / 2003** (NT 5.1/5.2) | ✅ real AD join | ✅ Kerberos/NTLMv2 | ✅ | SMB1 (`server min protocol = NT1`). Mostly works with the toggle. |
| **Windows 2000** (NT 5.0) | ✅ real AD join | ✅ | ✅ | SMB1 **+** weak Kerberos crypto (DES) — `allow_weak_crypto`. Fiddly. |
| **Windows NT4** | ❌ (no Kerberos) | ✅ NT4-style (NTLM) | ✅ | NTLM + `allow nt4 crypto`. Joins as an NT4 domain, not AD. |
| **Windows 9x / ME** | ❌ (not a domain member) | ⚠️ NT-domain network logon only | ✅ | LANMAN auth + WINS. No Kerberos, ever. LM hashes only. |
The toggle turns on (see `legacy.yml`):
```
server min protocol = NT1 # SMB1 for XP/2000/NT4/9x
ntlm auth = ntlmv1-permitted # NTLMv1
lanman auth = yes # LANMAN — required by 9x, insecure
client lanman auth = yes
allow nt4 crypto = yes # NT4 member logon
wins support = yes # NetBIOS name resolution for 9x/NT4
```
plus `allow_weak_crypto = true` in `/etc/krb5.conf` for Windows 2000's DES Kerberos.
Practical notes for the oldest clients:
- **Win9x needs WINS**, not DNS — point the clients' WINS server at the DC's IP (or a
legacy member running `wins support = yes`). Browsing relies on NetBIOS.
- **9x can't join** — it does an NT-domain *network logon* to a logon server and then
accesses `\\server\share` with domain credentials. Set the client's "Logon to
Windows NT domain" to `DDUPAN`.
- **Passwords:** after enabling weak crypto, accounts used by Win2000 may need a
password reset so the DES/RC4 keys get regenerated with the new enctypes allowed.
- Keep these clients on `192.168.10.x` with the DC as WINS + gateway, firewalled off
from anything you care about.
---
## 5. Linux Samba member fileserver (`join-member.yml`)
Joins a Linux host to the domain as a **Samba member fileserver** (`security = ADS`
+ winbind), so AD users/groups can authenticate to its SMB shares. Codified as the
`samba_member` role; runs against the `samba_members` inventory group.
```bash
# Inventory already has `laptop` (the KVM host, 192.168.10.127) under samba_members.
ansible-playbook join-member.yml --check --diff # preview (safe; no real join)
ansible-playbook join-member.yml # join for real → creates LAPTOP$ in AD
ansible-playbook join-member.yml --tags verify # re-run smoke tests only
```
What the role does, in order: install `winbind` + `libnss-winbind` + `krb5-user`
(**no `libpam-winbind`** — this is a fileserver join, not OS login) → write
`/etc/krb5.conf` for the realm → add a split-DNS drop-in → rewrite `smb.conf`
(`[global]` becomes an ADS member; existing shares preserved, original saved to
`smb.conf.pre-ads`) → add `winbind` to NSS `passwd`/`group` → `net ads join`
(guarded by `net ads testjoin`, so re-runs are no-ops) → start winbind → smoke tests.
Design choices:
- **RID idmap** (`idmap config DDUPAN : backend = rid`, range `10000-999999`) —
algorithmic, deterministic, needs no RFC2307 attributes. The DC *is* `--use-rfc2307`,
so switching to `backend = ad` for centrally-managed `uidNumber`/`gidNumber` is a
later option (must be identical on every member). Administrator → uid `10500`.
- **Split-DNS is mandatory here.** The LAN router (`192.168.10.1`) does **not** answer
realm SRV lookups, so the role drops `/etc/systemd/resolved.conf.d/ad-realm.conf`
routing `ad.ddupan.top` → the DC (`192.168.10.5`), which is authoritative and fast.
- **`apt update` is OFF by default** (`samba_member_apt_update_cache: false`). That
host's upstream DNS forwarder is flaky and a full refresh touches every repo in
`sources.list.d`; the member packages are already in the local cache. Pass
`-e samba_member_apt_update_cache=true` to force a refresh when DNS is healthy.
- Shares are data (`samba_member_shares` in the role defaults) — the guest `[win]`
share and the Cockpit ZFS `include` are carried over verbatim.
Verify by hand: `sudo net ads testjoin` → *Join is OK*; `wbinfo --online-status`;
`getent group 'domain admins'`; `smbclient -L localhost -N`.
## Next docs
- ~~`samba-fileserver` — domain-member SMB shares.~~ **Done** — see §5 above.
- `linux-domain-join` — `realmd` + `sssd` (OS login, distinct from this fileserver join).
- `../../apps/authelia/` — LDAP source → OIDC / forward-auth web SSO.