docs: fix iac-pipeline.md drift (provisioning/compliance role counts, templates)
Partial fix - diagram update still pending (Nextcloud unavailable). Live-verified against roles_metadata.yml: provisioning stage now has 2 roles (clamav, netbird_agent - added for NetBird mesh enrollment), compliance stage now has 16 (added ssh_ca_client). Technitium's shown tag-file example was stale (21->22 tags, missing netbird_agent). containers.tf gained an 18-line header comment that pushed its actual resource block past line 20, so the doc's 'head -20' example no longer reproduced the shown output - fixed to a targeted sed range. Also found and documented a genuine live inconsistency: host_tags.yml.tpl gained an inventory_group field with the 2026-08 LXC rollout, but no existing VM host has been re-applied since, so none of their rendered files have it yet (only new LXCs do) - called out explicitly rather than papered over.
This commit is contained in:
1 file changed
+17
-7
+17
-7
@@ -53,7 +53,7 @@ Terraform manages both VMs and LXCs now — two separate resource types, one per
|
||||
The LXC path is genuinely newer and mirrors the VM flow step-for-step, but has to close a gap the VM path never has: the stock container template has no `sshd` and no fleet users at all, since it never went through Packer. A `pct exec` bootstrap step runs first — installing `sshd` and creating both fleet identities with their keys, the same baseline Packer bakes into the VM image at build time instead of at provision time — before the same wait-for-SSH and tag-file-render steps every VM already gets. This is the real resource definition, not a paraphrase:
|
||||
|
||||
```
|
||||
$ head -20 containers.tf
|
||||
$ sed -n '20,39p' containers.tf
|
||||
resource "proxmox_virtual_environment_container" "ct" {
|
||||
provider = proxmox-bpg
|
||||
for_each = var.containers
|
||||
@@ -64,6 +64,8 @@ resource "proxmox_virtual_environment_container" "ct" {
|
||||
start_on_boot = each.value.onboot
|
||||
started = true
|
||||
|
||||
# PVE-native tags — cosmetic/searchable in the PVE UI, mirrors main.tf's
|
||||
# `tags = join(",", each.value.tags)` on the VM resource.
|
||||
tags = each.value.tags
|
||||
|
||||
cpu {
|
||||
@@ -82,10 +84,12 @@ This is the actual template that renders into every host's tag file — the lite
|
||||
|
||||
```
|
||||
$ cat host_tags.yml.tpl
|
||||
# Host metadata from Terraform provisioning
|
||||
vmid: ${vmid}
|
||||
vm_name: ${name}
|
||||
target_node: ${target_node}
|
||||
template: ${template}
|
||||
inventory_group: ${inventory_group}
|
||||
cores: ${cores}
|
||||
sockets: ${sockets}
|
||||
memory: ${memory}
|
||||
@@ -102,7 +106,12 @@ vm_tags:
|
||||
%{ endfor ~}
|
||||
```
|
||||
|
||||
Every field in this file traces back to one map entry in Terraform's own VM definitions — nothing here is hand-maintained separately from the VM it describes. And this is a real rendered instance of it, not a hypothetical fill-in — one fleet host's actual generated tag file, `ip`/`macaddr` redacted:
|
||||
Every field in this file traces back to one map entry in Terraform's own VM definitions — nothing here is hand-maintained separately from the VM it describes.
|
||||
|
||||
> [!NOTE]
|
||||
> **A field that exists in the template but not yet in any VM's real file.** `inventory_group` was added to this template alongside the 2026-08 LXC rollout — new LXCs render it correctly (`rocky_lxc`, confirmed live on netbird), but no existing VM has been re-applied since, so `inventory_group: rocky_vm` isn't actually in any of their rendered files yet, technitium's included below. A real, current gap, not a hypothetical one — worth naming rather than quietly patching the example to look more finished than the fleet actually is.
|
||||
|
||||
And this is a real rendered instance of it, not a hypothetical fill-in — one fleet host's actual generated tag file, `ip`/`macaddr` redacted:
|
||||
|
||||
```
|
||||
$ cat host_vars/technitium/host_tags.yml
|
||||
@@ -142,9 +151,10 @@ vm_tags:
|
||||
- observability_agent
|
||||
- mqtt_client
|
||||
- wazuh_agent_tuning
|
||||
- netbird_agent
|
||||
```
|
||||
|
||||
21 tags on this one host alone, spanning both the initialization roles (`packages`, `firewall_ssh`, …) and every one of the 9 compliance sub-roles — the same file Terraform generated is the exact input every Ansible stage after it reads.
|
||||
22 tags on this one host alone, spanning the initialization roles (`packages`, `firewall_ssh`, …), the provisioning-stage `netbird_agent` role, and every one of the 9 compliance sub-roles — the same file Terraform generated is the exact input every Ansible stage after it reads.
|
||||
|
||||
## Ansible: tag-driven, re-applied continuously
|
||||
|
||||
@@ -159,13 +169,13 @@ d = yaml.safe_load(open('roles_metadata.yml'))['roles_metadata']
|
||||
print('provisioning:', [k for k,v in d.items() if v.get('provisioning') is True])
|
||||
print('compliance:', sorted(k for k,v in d.items() if v.get('compliance') is True))
|
||||
"
|
||||
provisioning: ['clamav']
|
||||
compliance: ['auditd_hardening', 'banners', 'chrony_hardening', 'disk_resize', 'docker_bench', 'file_permissions', 'filesystem_hardening', 'kernel_hardening', 'mqtt_client', 'observability_agent', 'password_policy', 'service_disable', 'ssh_hardening', 'wazuh_agent_tuning', 'zsh_custom']
|
||||
provisioning: ['clamav', 'netbird_agent']
|
||||
compliance: ['auditd_hardening', 'banners', 'chrony_hardening', 'disk_resize', 'docker_bench', 'file_permissions', 'filesystem_hardening', 'kernel_hardening', 'mqtt_client', 'observability_agent', 'password_policy', 'service_disable', 'ssh_ca_client', 'ssh_hardening', 'wazuh_agent_tuning', 'zsh_custom']
|
||||
```
|
||||
|
||||
`clamav` is the only provisioning-stage role, and only one host carries it in its `vm_tags`. An under-used pipeline stage isn't the same as an unused one — it exists on purpose for exactly this shape of role, not as leftover scaffolding.
|
||||
Two roles are tagged for the provisioning stage: `clamav` (rocky-docker only) and `netbird_agent` (enrols a host into the NetBird mesh overlay covered in [`docs/netbird-zero-trust.md`](netbird-zero-trust.md) — monitoring was the first host onboarded through it). Neither fits "once at init" or "every compliance run" cleanly, which is exactly the shape this stage exists for — an under-used pipeline stage isn't the same as an unused one.
|
||||
|
||||
Compliance hardening runs as a set of independently-tagged sub-roles, selected per host via that same tag list — which controls apply to a given host is explicit and auditable in that host's own config, not an implicit "all or nothing." All 9 current sub-roles (of the 15 total compliance-stage roles above — the other 6 handle observability, notifications, and disk/shell setup, not CIS hardening specifically) are active fleet-wide; none are disabled or pending. This same tag-driven selection mechanism is what makes the deliberate CIS deviations in [`docs/security-monitoring.md`](security-monitoring.md) auditable rather than hidden — a reader can see exactly which controls a given host runs by reading its tags, not by reverse-engineering behavior.
|
||||
Compliance hardening runs as a set of independently-tagged sub-roles, selected per host via that same tag list — which controls apply to a given host is explicit and auditable in that host's own config, not an implicit "all or nothing." All 9 current sub-roles (of the 16 total compliance-stage roles above — the other 7 handle observability, notifications, SSH certificate refresh, and disk/shell setup, not CIS hardening specifically) are active fleet-wide; none are disabled or pending. This same tag-driven selection mechanism is what makes the deliberate CIS deviations in [`docs/security-monitoring.md`](security-monitoring.md) auditable rather than hidden — a reader can see exactly which controls a given host runs by reading its tags, not by reverse-engineering behavior.
|
||||
|
||||
## The Honest Exception: not everything fits the clean model
|
||||
|
||||
|
||||
Reference in new issue
Block a user