feat: manage the UniFi network declaratively via tofu-controller#2042
Merged
Conversation
Install tofu-controller (flux-iac, chart 0.16.4) as a prod-only controller and wire a `unifi` tenant that reconciles the UniFi network from the new devantler-tech/unifi repo (plain OpenTofu, filipowm/unifi provider). - providers/hetzner/infrastructure/controllers/tofu-controller/: HelmRepository + HelmRelease (installs the Terraform infra.contrib.fluxcd.io CRD), registered in the controllers kustomization. - providers/hetzner/unifi/: namespace, tf-runner SA + RBAC, CiliumNetworkPolicy, GitRepository (private repo, token auth), Terraform CR (v1alpha2), and the credentials/git-auth Secrets (values substituted from variables-cluster). - clusters/prod/unifi-flux-kustomization.yaml: dedicated, isolated, prod-only Flux Kustomization (wait:false, dependsOn infrastructure-controllers) so a UniFi stall never blocks app deploys — same pattern as overprovisioning. - docs/unifi-management.md. Observe-first by design: Terraform CR ships approvePlan:"" (plan only, never applies) and destroyResourcesOnDeletion:false. Validated with `ksail workload validate --config ksail.prod.yaml` (325 files, pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The devantler-tech/unifi repo is now public and holds no secrets, so: - GitRepository drops its secretRef (no clone auth needed); remove the unifi-git-auth Secret and the unifi_git_token gate entirely. - Replace the SOPS/variables-cluster-substituted unifi-credentials Secret with an ExternalSecret that reads infrastructure/unifi/controller from OpenBao via the shared `openbao` ClusterSecretStore — mirroring external-dns's Cloudflare token (non-bootstrap-critical infra credential). - Add the infra-unifi-readonly OpenBao policy and attach it to the external-secrets role (vault-config job). - unifi Flux Kustomization now dependsOn `infrastructure` (the ClusterSecretStore is an infrastructure-layer resource) and drops the now-unneeded SOPS decryption + postBuild substitution. Single remaining gate: `bao kv put secret/infrastructure/unifi/controller api_url=... api_key=...`. Validated with `ksail workload validate` (324 files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8 tasks
# Conflicts: # k8s/bases/infrastructure/vault-config/job.yaml
Resolve the conflict by taking main's clusters/prod/kustomization.yaml (which dropped overprovisioning), and per maintainer direction relocate the UniFi tenant to follow the proven tenant pattern: - Move k8s/providers/hetzner/unifi/ -> k8s/providers/hetzner/apps/unifi/ and register it in providers/hetzner/apps/kustomization.yaml, so the shared `apps` Flux Kustomization reconciles it like wedding-app/ascoachingogvaner. - Delete the dedicated clusters/prod/unifi-flux-kustomization.yaml. - Prod-only via the hetzner apps overlay (tofu-controller + the controller API are Hetzner-only; the Terraform CRD is absent on local/CI). - Docs updated: apps-layer placement + seed the OpenBao secret before merge (the apps layer is wait:true). Validated: `ksail workload validate --config ksail.prod.yaml` (327 files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the external-dns-style `bao kv put` seeding with the platform's GitOps seeding pattern so OpenBao is reproducible from Git (and self-heals after a vault re-init): - seed-unifi PushSecret (providers/hetzner/infrastructure/vault-seed/) pushes unifi_api_url + unifi_api_key from variables-cluster to secret/infrastructure/unifi/controller — mirrors seed-hcloud, prod-only. - variables-cluster-secret.enc.yaml ships SOPS-encrypted PLACEHOLDERS (unifi_api_url=https://unifi.example.invalid, unifi_api_key=PLACEHOLDER…) so the chain is healthy on merge; the maintainer sets the real key by editing that one SOPS file. - vault-seed-write policy gains secret/data/infrastructure/unifi/* write. - Docs + ExternalSecret comment updated to the declarative flow. Validated: `ksail workload validate --config ksail.prod.yaml` (328 files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop SOPS-based seeding in favour of the same pattern provider-upjet-github uses: the vault-config Job seeds a placeholder at secret/infrastructure/unifi/controller on first run (only if absent, so a re-run never clobbers the real value), and the maintainer overwrites it in place via the OpenBao UI. OpenBao is backed up (Raft → R2), so the manually-set value is durable. - vault-config job.yaml: add the "1c" placeholder-seed block (mirrors the GitHub App "1b" block); drop the now-unneeded vault-seed-write infrastructure/unifi/* grant (the Job seeds with its own admin token). - Revert SOPS seeding: remove unifi_api_url/unifi_api_key from variables-cluster-secret.enc.yaml; delete the seed-unifi PushSecret + its kustomization entry. - Keep infra-unifi-readonly + the ExternalSecret reading via the openbao ClusterSecretStore (unifi is a plain infra ns, like external-dns). - Docs + ExternalSecret comment updated to the Job-placeholder + UI-overwrite flow. Also merges origin/main (brings in the merged provider-upjet-github work). Validated: `ksail workload validate --config ksail.prod.yaml` (356 files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mit) Completes the previous commit (which only captured the seed-unifi.yaml deletion): - vault-config job.yaml: add the "1c" UniFi placeholder-seed block (mirrors the GitHub App "1b" block, idempotent — only if absent); drop the now-unneeded vault-seed-write infrastructure/unifi/* grant. - variables-cluster-secret.enc.yaml: remove the SOPS unifi_api_url/unifi_api_key. - vault-seed kustomization: de-register seed-unifi. - ExternalSecret comment + docs: Job-placeholder + OpenBao-UI overwrite flow. Validated: `ksail workload validate --config ksail.prod.yaml` (356 files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Nikolai Emil Damm <nikolaiemildamm@icloud.com>
Contributor
|
🎉 This PR is included in version 1.69.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Manage the UniFi network declaratively as a platform tenant. The desired
state lives in the public repo
devantler-tech/unifi(plain OpenTofuusing
filipowm/unifi);this PR installs tofu-controller and wires a
unifitenant so the clusterreconciles that repo against the controller API.
The repo is public and holds no secrets; the only sensitive value (the UniFi
API key) lives in OpenBao and is pulled in via an
ExternalSecret— exactlylike external-dns's Cloudflare token. A real-CRD
provider-upjet-unifi(Crossplane) is the tracked steady-state follow-up. See
docs/unifi-management.md.Contents
infrastructure/controllers/tofu-controller/— HelmRepository + HelmRelease(flux-iac chart
0.16.4, installs theTerraformCRD), registered in thecontrollers kustomization. Prod-only.
providers/hetzner/unifi/— namespace (restricted PSS),tf-runnerSA +namespaced RBAC, CiliumNetworkPolicy,
GitRepository(public, no auth),TerraformCR, and anExternalSecretpulling the API creds from OpenBao.bases/infrastructure/vault-config/job.yaml— adds theinfra-unifi-readonlyOpenBao policy and attaches it to the
external-secretsrole.clusters/prod/unifi-flux-kustomization.yaml— dedicated, isolated,prod-only Flux Kustomization (
wait: false,dependsOn: infrastructure) so aUniFi stall never blocks app deploys (same rationale as overprovisioning).
docs/unifi-management.md.Safety (observe-first)
TerraformCR shipsapprovePlan: ""→ plan only, never applies. Reviewthe plan,
importexisting objects until it's a no-op, then approve a specificplan id (or flip to
auto) once trusted.destroyResourcesOnDeletion: false— deleting the CR never tears down the network.Gate (maintainer) — one secret; Kustomization is red by design until done
Generate a Limited Admin, Local Access Only API key (UniFi OS ≥ 9.0.108) and seed
OpenBao:
(
api_urlomits the/apipath.) GitOps alternative: SOPS-encrypt invariables-cluster+ a PushSecret underproviders/hetzner/infrastructure/vault-seed/.Validate on the live cluster before promoting
require-mutual-authapplies to thetofu-controller ↔ tf-runner gRPC (:30000). Cilium auto-issues SPIFFE identities,
so the L3/L4 allow should suffice — confirm the runner connects.
restricted; if the tf-runner image can'trun non-root, rely on Kyverno securityContext mutation or relax to
baseline.0.16.4value paths and Kyverno fit.networkpolicy.yamlallowsworld:443,8443; tighten to thecontroller address + OpenTofu registry FQDNs once known.
Validation done
ksail workload validate --config ksail.prod.yaml→ 324 files validated, pass(incl. the
TerraformCR andExternalSecret).kubectl kustomizebuilds clean.🤖 Generated with Claude Code