feat(uptime-monitor): sync the UptimeRobot API key from OpenBao via ESO
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful

Makes the key at OpenBao `infrastructure/uptime-robot`, field `api_key`,
available as a Secret in the new `uptime-monitor` namespace and nowhere
else. No new operator: this uses the External Secrets Operator and the
`openbao` ClusterSecretStore the cluster already runs.

Nothing is monitored by this commit. The namespace holds only the key;
the IngressMonitorController, its ServiceAccount and its RBAC arrive
separately (REN-6).

* `data` + `property` instead of `dataFrom: extract`, so only the one
  field lands in the cluster rather than every field at the KV path.
* Adds `uptime-monitor` to the ClusterSecretStore `conditions.namespaces`
  allowlist, sorted. Without this ESO reports "not allowed to reference
  ClusterSecretStore" and the Secret silently never syncs.
* A DEDICATED Flux Kustomization rather than adding the ExternalSecret to
  infrastructure/post-crds. That path runs `wait: true` with no explicit
  healthChecks and 19 other Kustomizations dependOn it, so an
  ExternalSecret whose OpenBao path is not yet populated would never turn
  Ready and would stall most of the cluster. Here the blast radius of a
  missing key is this Kustomization alone, which nothing depends on.
* No OpenBao policy change: eso-read already grants `homelab/data/*`.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
svc-atlas
2026-09-25 23:40:24 +00:00
committed by atlas-bot
co-authored by Paperclip Claude Opus 5
parent a833a9083a
commit 25cabaed76
5 changed files with 102 additions and 0 deletions
@@ -0,0 +1,37 @@
---
# UptimeRobot API key for the IngressMonitorController (REN-2).
#
# OpenBao key infrastructure/uptime-robot, field `api_key`. `data` + `property`
# rather than `dataFrom: extract` (the shape most ExternalSecrets in this repo
# use) on purpose: extract copies EVERY field at the KV path into the cluster
# Secret, so any field added there later — an account id, a second token —
# would land in this namespace without anyone editing this file. Naming the one
# field keeps the Secret to exactly what the controller needs. Same shape as
# kubernetes/apps/wiki/externalsecrets.yaml.
#
# refreshInterval 1h matches the un-rotated ExternalSecrets here (velero,
# wiki-*). The rotated ones use 10m so an automated roll propagates fast — if
# kubernetes/infrastructure/openbao/rotation/ ever gains an uptime-robot
# rotator, drop this to 10m and annotate the controller Deployment for
# stakater/reloader, which the cluster already runs.
#
# The `eso` role already reads this path: eso-read grants `homelab/data/*`
# (kubernetes/infrastructure/openbao/policies/eso-read.hcl:31). No OpenBao
# policy change is needed for this key.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: uptime-robot-api-key
namespace: uptime-monitor
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: uptime-robot-api-key
data:
- secretKey: api-key
remoteRef:
key: infrastructure/uptime-robot
property: api_key
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- externalsecret.yaml
@@ -0,0 +1,44 @@
# Automated uptime monitoring (REN-2): Stakater IngressMonitorController keeps
# UptimeRobot monitors in sync with `monitor.stakater.com/enabled` annotations on
# Ingress objects. This Kustomization starts with the API key only; the
# controller, its ServiceAccount and its RBAC land here in REN-6.
#
# A DEDICATED Kustomization rather than adding the ExternalSecret to
# ./kubernetes/infrastructure/post-crds, which is where the other
# infrastructure-layer ExternalSecrets live. That directory is applied by the
# `infrastructure-crds` Kustomization, which runs `wait: true` with no explicit
# healthChecks, so EVERY object under it must go healthy or the whole
# Kustomization reports NotReady — and 19 other Kustomizations in this directory
# dependsOn it. An ExternalSecret whose OpenBao path is not yet populated never
# turns Ready, so putting this key there would put `homelab/infrastructure/uptime-robot`
# in the critical path of most of the cluster. Here, the only thing a missing or
# misnamed key can stall is this Kustomization, which nothing depends on.
#
# Same reason `wait: true` is kept below rather than dropped: the wait is how a
# key that failed to sync becomes visible, and it is safe precisely because the
# blast radius is this Kustomization alone.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: uptime-monitor
namespace: flux-system
spec:
interval: 10m
# 2m, not the 1m most app Kustomizations use: the one object here is an
# ExternalSecret against a key a human may still be placing in OpenBao, and a
# retry every minute against a 404 is just noise in the ESO and Flux logs.
retryInterval: 2m
timeout: 5m
sourceRef:
kind: GitRepository
name: flux-system
path: ./kubernetes/apps/uptime-monitor
prune: true
wait: true
dependsOn:
# ESO itself, and the `uptime-monitor` namespace (declared centrally in
# kubernetes/infrastructure/namespaces/namespaces.yaml).
- name: infrastructure
# The `openbao` ClusterSecretStore, including the conditions.namespaces
# allowlist entry this namespace needs.
- name: infrastructure-crds
@@ -244,3 +244,19 @@ apiVersion: v1
kind: Namespace
metadata:
name: paperclip
---
# IngressMonitorController, which syncs UptimeRobot monitors from Ingress
# annotations (REN-2). Declared here rather than in the workload Kustomization
# for the reason spelled out above `woodpecker`: the `infrastructure` KS creates
# namespaces, and `infrastructure-crds` (post-crds, which includes
# ../network-policies/) runs after it — so declaring it in the workload KS would
# invert that order the moment this namespace gains a CiliumNetworkPolicy, which
# it will when the controller arrives and needs scoped egress to the UptimeRobot
# API. It holds nothing but the API key Secret until then.
#
# No pod-security labels: the controller needs no hostPath, hostNetwork or
# privilege, so the cluster's default `baseline` enforcement is enough.
apiVersion: v1
kind: Namespace
metadata:
name: uptime-monitor
@@ -30,6 +30,7 @@ spec:
- paperclip
- paperless
- trmnl
- uptime-monitor # uptime-robot-api-key (apps/uptime-monitor/externalsecret.yaml)
- velero
- woodpecker # wp-pg barman creds; woodpecker moving out of `apps` (#433)
- wordpress