Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions k8s/bases/infrastructure/vault-config/job.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -520,15 +520,19 @@ spec:
fi

# --- 1c. Seed placeholder UniFi controller credentials (only if absent) ---
# The `unifi` tenant's Terraform CR (tofu-controller) reads the UniFi
# API credentials from secret/infrastructure/unifi/controller via the
# unifi-credentials ExternalSecret. We seed PLACEHOLDERS here
# declaratively (no SOPS) ONLY when the secret is absent, so the
# The `unifi` tenant's Crossplane provider (provider-upjet-unifi) reads
# the UniFi credentials from secret/infrastructure/unifi/controller via
# the unifi-controller-credentials ExternalSecret. We seed PLACEHOLDERS
# here declaratively (no SOPS) ONLY when the secret is absent, so the
# maintainer can overwrite them in place via the OpenBao UI/CLI and a
# later Job re-run won't clobber the real values. OpenBao is backed up
# (Raft snapshots β†’ R2 via Velero), so these manually-set values are
# durable β€” no GitOps re-seed is needed. api_url omits the /api path;
# api_key is a Limited-Admin Local-Access-Only key (UniFi OS >= 9.0.108).
# durable β€” no GitOps re-seed is needed. GATE: overwrite `api_key` with a
# UniFi Site Manager / Cloud API key that has WRITE scope (unifi.ui.com β†’
# API, Site + Application scope) β€” the provider runs in Cloud Connector
# mode (routes through https://api.ui.com), so a local Limited-Admin key
# is NOT used, and `api_url` is left as an unused placeholder for a
# possible future local/tunnel path.
if ! bao kv get -mount=secret infrastructure/unifi/controller >/dev/null 2>&1; then
echo "Seeding placeholder UniFi credentials at secret/infrastructure/unifi/controller..."
bao kv put -mount=secret infrastructure/unifi/controller \
Expand Down
43 changes: 23 additions & 20 deletions k8s/providers/hetzner/apps/unifi/external-secret.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
---
# UniFi controller API credentials from OpenBao, rendered as the single JSON
# UniFi controller credentials from OpenBao, rendered as the single JSON
# `credentials` blob the Crossplane ProviderConfig consumes (provider-config.yaml,
# secretRef β†’ key `credentials`). The provider forwards it verbatim to the
# underlying ubiquiti-community/unifi SDK config. Read via the tenant's NAMESPACED
# `openbao` SecretStore (secret-store.yaml) β€” NOT the shared cluster store β€”
# authorised by the infra-unifi-readonly OpenBao policy on the dedicated `unifi`
# role.
# SEEDING: the vault-config Job seeds PLACEHOLDERS at
# secret/infrastructure/unifi/controller on first run (only if absent). GATE:
# overwrite them in place via the OpenBao UI/CLI (api_url omits the /api path;
# Limited-Admin Local-Access-Only key, controller >= 9.0.108). OpenBao is backed
# up (Raft β†’ R2), so the value is durable.
# `openbao` SecretStore (secret-store.yaml) β€” NOT the shared cluster store.
#
# CLOUD CONNECTOR mode: `cloud_connector: "true"` makes the provider authenticate
# with `api_key` and route every request through the UniFi Cloud
# (https://api.ui.com), which PROXIES to the controller β€” so the controller does
# NOT need to be reachable from the cluster (no tunnel, no public endpoint). No
# `api_url` is needed (Cloud Connector fixes the endpoint), and `hardware_id` is
# omitted so the provider targets the first console where `owner=true` (add it
# explicitly only if you run multiple consoles).
#
# SEEDING: the vault-config Job seeds a PLACEHOLDER `api_key` at
# secret/infrastructure/unifi/controller (only if absent). GATE: overwrite it in
# place via the OpenBao UI/CLI with a UniFi **Site Manager / Cloud API key that
# has WRITE scope** (unifi.ui.com β†’ API β†’ create key with Site + Application
# scope) β€” NOT a local Limited-Admin key. OpenBao is backed up (Raft β†’ R2), so the
# value is durable.
#
# The provider unmarshals EVERY credentials value as a Go `string`, so
# `cloud_connector` MUST be the string "true" (not a JSON bool) β€” a bare bool
# fails `cannot unmarshal bool into Go value of type string`. dict|toJson
# quotes/escapes every value.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
Expand All @@ -28,18 +41,8 @@ spec:
engineVersion: v2
type: Opaque
data:
# Build the blob with dict|toJson so values are JSON-escaped. The provider
# unmarshals EVERY credentials value as a Go `string`, so allow_insecure
# MUST be the string "false" (not a JSON bool) β€” a bare bool makes the
# provider fail with `cannot unmarshal bool into Go value of type string`
# (CannotConnectToProvider on every managed resource). api_url/api_key are
# properly quoted/escaped by toJson.
credentials: '{{ dict "api_url" .api_url "api_key" .api_key "site" "default" "allow_insecure" "false" | toJson }}'
credentials: '{{ dict "api_key" .api_key "cloud_connector" "true" "site" "default" | toJson }}'
data:
- secretKey: api_url
remoteRef:
key: infrastructure/unifi/controller
property: api_url
- secretKey: api_key
remoteRef:
key: infrastructure/unifi/controller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ spec:
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
emptyDir:
sizeLimit: 256Mi