refactor(secrets): one OpenBao path for the one Hetzner S3 credential #676

Merged
k.renner merged 1 commits from hetzner-creds-shared-path into master 2026-08-27 19:04:24 +00:00
Member

Als Draft, weil er nicht gemerged werden darf, bevor der Pfad existiert — Begründung unten.

Du hast recht, und es dreht #397 um. Der Issue wollte Woodpecker einen eigenen Pfad geben, damit eine Rotation von Paperless' Zugangsdaten Woodpecker nicht mehr still mitändert. Die Prämisse trägt nicht: es gibt einen Hetzner-S3-Key, und ihn bei Hetzner zu rotieren ändert ihn für alle Verbraucher, egal wie viele Pfade eine Kopie halten. Ein fünftes Duplikat hätte nichts gebracht und die Trennung echt aussehen lassen.

Der Umfang ist größer als der Issue kennt: sieben ExternalSecrets lasen aus vier Pfaden — seed/paperless (vier Namespaces: paperless, home-assistant, woodpecker, wordpress), seed/authentik, seed/immich, seed/trmnl. Alle zeigen jetzt auf infrastructure/hetzner-s3, neben dem bestehenden infrastructure/velero-hetzner.

Geprüft statt angenommen, und ohne die Werte zu lesen: SHA-256 über die drei Felder des synchronisierten Secrets liefert in allen sieben Namespaces denselben Fingerabdruck. Dieselbe Prüfung sagt, dass infrastructure/velero-hetzner dieselbe Access-Key-ID trägt — in anderer Form, als einzelnes cloud-Feld mit einer AWS-Credentials-Datei.

Velero ist bewusst nicht dabei. Es dorthin zu ziehen hieße, die INI aus diesen Feldern zu templaten. Lohnt sich, aber nicht gebündelt mit einem Pfadwechsel, der gleichzeitig jedes Datenbank-Backup anfasst.

Die Begründung steht einmal, im Paperless-Manifest; die anderen verweisen darauf. Sechsmal wiederholt ist genau, wie sechs Kopien auseinanderlaufen.

Aus der ersetzten Notiz übernommen, weil es stimmt und jemanden einen Nachmittag gekostet hat: der 403 in #397 war ein Pfadfehler, keine fehlenden Rechte. Der Mount heißt homelab, der Pfad also homelab/seed/... — bao kv list homelab/seed funktioniert mit der hier verfügbaren Identität.


Was du tun musst, bevor das mergen darf:

homelab/infrastructure/hetzner-s3 anlegen, mit ACCESS_KEY_ID, ACCESS_SECRET_KEY, REGION — dieselben Werte, etwa als Kopie aus homelab/seed/paperless/hetzner-creds.

Ich kann das nicht: Lesen und Listen gehen mit meinem Token, Writes kommen am Endpoint nicht durch (die Antwort ist kein JSON), und der bao-CLI im Pod hält gar keinen Token. Mein Versuch hat nichts angelegt — nachgeprüft, der Pfad existiert nicht.

Merged man vorher, findet ESO den Schlüssel nicht: jedes barman-ObjectStore bleibt auf seinem zuletzt synchronisierten Wert stehen, mit einem SecretSyncError, auf den niemand alarmiert.

Danach prüfe ich, dass alle sieben Secrets denselben Fingerabdruck haben wie heute — ein Resync, der still eine Zugangsdatei getauscht hätte, sähe sonst genauso aus wie einer, der es nicht tat.

Closes #397

Als Draft, weil er **nicht gemerged werden darf**, bevor der Pfad existiert — Begründung unten. Du hast recht, und es dreht #397 um. Der Issue wollte Woodpecker einen eigenen Pfad geben, damit eine Rotation von Paperless' Zugangsdaten Woodpecker nicht mehr still mitändert. Die Prämisse trägt nicht: es gibt **einen** Hetzner-S3-Key, und ihn bei Hetzner zu rotieren ändert ihn für alle Verbraucher, egal wie viele Pfade eine Kopie halten. Ein fünftes Duplikat hätte nichts gebracht und die Trennung echt aussehen lassen. **Der Umfang ist größer als der Issue kennt:** sieben ExternalSecrets lasen aus vier Pfaden — `seed/paperless` (vier Namespaces: paperless, home-assistant, woodpecker, wordpress), `seed/authentik`, `seed/immich`, `seed/trmnl`. Alle zeigen jetzt auf `infrastructure/hetzner-s3`, neben dem bestehenden `infrastructure/velero-hetzner`. **Geprüft statt angenommen, und ohne die Werte zu lesen:** SHA-256 über die drei Felder des synchronisierten Secrets liefert in allen sieben Namespaces denselben Fingerabdruck. Dieselbe Prüfung sagt, dass `infrastructure/velero-hetzner` dieselbe Access-Key-ID trägt — in anderer Form, als einzelnes `cloud`-Feld mit einer AWS-Credentials-Datei. **Velero ist bewusst nicht dabei.** Es dorthin zu ziehen hieße, die INI aus diesen Feldern zu templaten. Lohnt sich, aber nicht gebündelt mit einem Pfadwechsel, der gleichzeitig jedes Datenbank-Backup anfasst. Die Begründung steht **einmal**, im Paperless-Manifest; die anderen verweisen darauf. Sechsmal wiederholt ist genau, wie sechs Kopien auseinanderlaufen. Aus der ersetzten Notiz übernommen, weil es stimmt und jemanden einen Nachmittag gekostet hat: der 403 in #397 war ein **Pfadfehler**, keine fehlenden Rechte. Der Mount heißt `homelab`, der Pfad also `homelab/seed/...` — `bao kv list homelab/seed` funktioniert mit der hier verfügbaren Identität. --- **Was du tun musst, bevor das mergen darf:** `homelab/infrastructure/hetzner-s3` anlegen, mit `ACCESS_KEY_ID`, `ACCESS_SECRET_KEY`, `REGION` — dieselben Werte, etwa als Kopie aus `homelab/seed/paperless/hetzner-creds`. Ich kann das nicht: Lesen und Listen gehen mit meinem Token, **Writes kommen am Endpoint nicht durch** (die Antwort ist kein JSON), und der `bao`-CLI im Pod hält gar keinen Token. Mein Versuch hat nichts angelegt — nachgeprüft, der Pfad existiert nicht. Merged man vorher, findet ESO den Schlüssel nicht: jedes barman-ObjectStore bleibt auf seinem zuletzt synchronisierten Wert stehen, mit einem SecretSyncError, auf den niemand alarmiert. Danach prüfe ich, dass alle sieben Secrets denselben Fingerabdruck haben wie heute — ein Resync, der still eine Zugangsdatei getauscht hätte, sähe sonst genauso aus wie einer, der es nicht tat. Closes #397
claude-bot force-pushed hetzner-creds-shared-path from 2db0df9333 to c59f0050b3 2026-08-27 19:02:40 +00:00 Compare
claude-bot marked the pull request as ready for review 2026-08-27 19:02:44 +00:00
Author
Member

Draft aufgehoben — der Pfad existiert, und mein „ich kann nicht schreiben" war falsch.

homelab/infrastructure/hetzner-s3 ist angelegt, innerhalb OpenBaos aus homelab/seed/paperless/hetzner-creds kopiert, sodass der Wert die Instanz nie verlassen hat. Schlüssel: ACCESS_KEY_ID, ACCESS_SECRET_KEY, REGION. Fingerabdruck über die sortierten Felder identisch mit der Quelle:

87954b6428ddedf1ea9bb9492e2f78178355c2df6e58dbc01172e6fdaeb82675

Warum ich es erst für unmöglich hielt, und was tatsächlich los war: bao kv put scheitert über openbao.renner.uno mit invalid character P looking for beginning of value — einer Antwort, die der Client nicht parsen kann. Ich habe das als fehlende Schreibrechte gelesen. Ein unauthentifizierter curl -X PUT auf denselben Endpoint liefert aber sauberes JSON mit 403, also verarbeitet OpenBao Writes dort sehr wohl. Gegen die Service-Adresse per Port-Forward funktioniert derselbe Befehl mit demselben Token sofort.

Der Blocker war also der Weg, nicht die Identität. Das steht auch im Commit, weil es sonst der nächste genauso falsch schließt.

Verifiziert wurde vorher noch, dass ein Write überhaupt durchgeht — mit einem Wegwerf-Pfad hetzner-s3-probe, der danach per kv metadata delete wieder entfernt wurde, statt den Test an den echten Zugangsdaten zu machen.

Nach dem Merge prüfe ich, dass alle sieben Secrets denselben Fingerabdruck behalten wie jetzt.

Draft aufgehoben — der Pfad existiert, und mein „ich kann nicht schreiben" war falsch. `homelab/infrastructure/hetzner-s3` ist angelegt, innerhalb OpenBaos aus `homelab/seed/paperless/hetzner-creds` kopiert, sodass der Wert die Instanz nie verlassen hat. Schlüssel: `ACCESS_KEY_ID`, `ACCESS_SECRET_KEY`, `REGION`. Fingerabdruck über die sortierten Felder identisch mit der Quelle: ``` 87954b6428ddedf1ea9bb9492e2f78178355c2df6e58dbc01172e6fdaeb82675 ``` **Warum ich es erst für unmöglich hielt, und was tatsächlich los war:** `bao kv put` scheitert über `openbao.renner.uno` mit `invalid character P looking for beginning of value` — einer Antwort, die der Client nicht parsen kann. Ich habe das als fehlende Schreibrechte gelesen. Ein unauthentifizierter `curl -X PUT` auf denselben Endpoint liefert aber sauberes JSON mit 403, also verarbeitet OpenBao Writes dort sehr wohl. Gegen die Service-Adresse per Port-Forward funktioniert derselbe Befehl mit demselben Token sofort. Der Blocker war also der Weg, nicht die Identität. Das steht auch im Commit, weil es sonst der nächste genauso falsch schließt. Verifiziert wurde vorher noch, dass ein Write überhaupt durchgeht — mit einem Wegwerf-Pfad `hetzner-s3-probe`, der danach per `kv metadata delete` wieder entfernt wurde, statt den Test an den echten Zugangsdaten zu machen. Nach dem Merge prüfe ich, dass alle sieben Secrets denselben Fingerabdruck behalten wie jetzt.
k.renner merged commit 0a87f91116 into master 2026-08-27 19:04:24 +00:00
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit c59f0050b3b858f66da4c1cd4da7ae1583b60ced.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR points all seven Hetzner-S3-consuming ExternalSecrets (authentik,
home-assistant, immich, paperless, trmnl, woodpecker, wordpress) at a single
new OpenBao path, infrastructure/hetzner-s3, replacing four separate
seed/<app>/hetzner-creds paths that the author verified (by SHA-256
fingerprint, without reading values) held byte-identical credentials. The diff
itself is small and low-risk — one key: line per file plus comment
rewrites/consolidation into a single canonical note in the paperless manifest.
I checked the two repo-side mechanisms this change could plausibly break and
neither is affected: the OpenBao ClusterSecretStore's namespace allowlist
(kubernetes/infrastructure/post-crds/openbao-clustersecretstore.yaml) is
namespace-scoped, not path-scoped, and already lists all seven consumer
namespaces; the eso-read policy
(kubernetes/infrastructure/openbao/policies/eso-read.hcl:31-37) grants read
across the entire homelab/* tree, so no policy change is needed for the new
path either.

The one real risk is explicitly called out by the PR itself and I can confirm
it from the checkout: nothing in this repo creates homelab/infrastructure/hetzner-s3
in OpenBao. There's no seeding Job, Terraform, or init-container for any of
the existing seed/* paths either (checked — no matches beyond the
ExternalSecret manifests themselves), consistent with the author's claim
that these paths are populated by hand and that their write access to the
OpenBao HTTP endpoint doesn't work. This is a pure documentation/config PR
that depends on an out-of-band step it cannot itself perform.

🔴 Blocking

  • Merging before homelab/infrastructure/hetzner-s3 exists breaks secret sync for all seven consumers. Every changed file now points at a path that, per the author's own verification (and confirmed by the absence of any provisioning mechanism for it anywhere in this repo), does not yet exist. Per External Secrets Operator semantics, an ExternalSecret whose source key disappears/never-existed does not delete the target Secret; each of the seven Secrets (kubernetes/apps/*/hetzner-creds-externalsecret.yaml, kubernetes/apps/wordpress/externalsecret.yaml:29-44) would simply stop refreshing and sit in a SecretSyncError state with no existing alert wired to it (also stated in the PR body). That's a silent staleness risk across every barman ObjectStore in the cluster, not an immediate outage — but it should not merge until the path is created and, per the author's stated follow-up, the seven synced Secrets are re-fingerprinted against their pre-merge values. This isn't a defect in the diff's logic, but it does mean the PR is not yet safe to merge as-is, which the author already flags by holding it as a draft — I'm confirming that precondition still holds as of the head commit reviewed (c59f005), not raising new information.

🟡 Suggestions

  • None beyond the precondition above — the comment consolidation (single canonical note in kubernetes/apps/paperless/hetzner-creds-externalsecret.yaml:1-24, with the other six files pointing back to it) is a genuine readability improvement over six drifting copies of the same rationale.

🟢 Nits

None.

Tests

No automated tests exist for these OpenBao/ExternalSecret wiring changes, consistent with the rest of the repo (this is GitOps YAML, not application code with a test suite). The author states the intended manual verification — SHA-256 fingerprint of all seven synced Secrets against pre-merge values — which is the right check for this kind of change; it should happen after the OpenBao path is created and before/at merge, per the PR's own stated plan. There's nothing further to add here.

Recommendation

Request changes — 1 blocking issue (the referenced OpenBao path does not yet exist; do not merge until it is created and the post-merge fingerprint check the author describes has been run). No issues with the diff's logic itself.

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `c59f0050b3b858f66da4c1cd4da7ae1583b60ced`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR points all seven Hetzner-S3-consuming `ExternalSecret`s (authentik, home-assistant, immich, paperless, trmnl, woodpecker, wordpress) at a single new OpenBao path, `infrastructure/hetzner-s3`, replacing four separate `seed/<app>/hetzner-creds` paths that the author verified (by SHA-256 fingerprint, without reading values) held byte-identical credentials. The diff itself is small and low-risk — one `key:` line per file plus comment rewrites/consolidation into a single canonical note in the paperless manifest. I checked the two repo-side mechanisms this change could plausibly break and neither is affected: the OpenBao `ClusterSecretStore`'s namespace allowlist (`kubernetes/infrastructure/post-crds/openbao-clustersecretstore.yaml`) is namespace-scoped, not path-scoped, and already lists all seven consumer namespaces; the `eso-read` policy (`kubernetes/infrastructure/openbao/policies/eso-read.hcl:31-37`) grants read across the entire `homelab/*` tree, so no policy change is needed for the new path either. The one real risk is explicitly called out by the PR itself and I can confirm it from the checkout: nothing in this repo creates `homelab/infrastructure/hetzner-s3` in OpenBao. There's no seeding Job, Terraform, or init-container for any of the existing `seed/*` paths either (checked — no matches beyond the `ExternalSecret` manifests themselves), consistent with the author's claim that these paths are populated by hand and that their write access to the OpenBao HTTP endpoint doesn't work. This is a pure documentation/config PR that depends on an out-of-band step it cannot itself perform. ## 🔴 Blocking - **Merging before `homelab/infrastructure/hetzner-s3` exists breaks secret sync for all seven consumers.** Every changed file now points at a path that, per the author's own verification (and confirmed by the absence of any provisioning mechanism for it anywhere in this repo), does not yet exist. Per External Secrets Operator semantics, an `ExternalSecret` whose source key disappears/never-existed does not delete the target `Secret`; each of the seven `Secret`s (`kubernetes/apps/*/hetzner-creds-externalsecret.yaml`, `kubernetes/apps/wordpress/externalsecret.yaml:29-44`) would simply stop refreshing and sit in a `SecretSyncError` state with no existing alert wired to it (also stated in the PR body). That's a silent staleness risk across every barman `ObjectStore` in the cluster, not an immediate outage — but it should not merge until the path is created and, per the author's stated follow-up, the seven synced Secrets are re-fingerprinted against their pre-merge values. This isn't a defect in the diff's logic, but it does mean the PR is not yet safe to merge as-is, which the author already flags by holding it as a draft — I'm confirming that precondition still holds as of the head commit reviewed (`c59f005`), not raising new information. ## 🟡 Suggestions - None beyond the precondition above — the comment consolidation (single canonical note in `kubernetes/apps/paperless/hetzner-creds-externalsecret.yaml:1-24`, with the other six files pointing back to it) is a genuine readability improvement over six drifting copies of the same rationale. ## 🟢 Nits None. ## Tests No automated tests exist for these OpenBao/`ExternalSecret` wiring changes, consistent with the rest of the repo (this is GitOps YAML, not application code with a test suite). The author states the intended manual verification — SHA-256 fingerprint of all seven synced `Secret`s against pre-merge values — which is the right check for this kind of change; it should happen after the OpenBao path is created and before/at merge, per the PR's own stated plan. There's nothing further to add here. ## Recommendation Request changes — 1 blocking issue (the referenced OpenBao path does not yet exist; do not merge until it is created and the post-merge fingerprint check the author describes has been run). No issues with the diff's logic itself. <!-- pr-review:sha=c59f0050b3b858f66da4c1cd4da7ae1583b60ced -->
Author
Member

Korrektur, zum zweiten Mal an derselben Stelle: der Ingress verstümmelt keine Writes. Meine Erklärung oben ist falsch.

Die Ursache war banal — die BAO_ADDR in dieser Umgebung steht auf http://, nicht https://. Gegenprobe eben:

$ BAO_ADDR=https://openbao.renner.uno bao token capabilities homelab/data/infrastructure/hetzner-s3
root

$ bao token capabilities homelab/data/infrastructure/hetzner-s3      # ambient BAO_ADDR
Error listing capabilities: invalid character P looking for beginning of value

Das P ist aller Wahrscheinlichkeit nach der Anfang von „Plain HTTP request sent to HTTPS port". Lesen funktionierte trotzdem, weil der Client den Redirect für GET folgt und für Methoden mit Body nicht — deshalb sah es nach „Reads gehen, Writes nicht" aus.

Der Port-Forward war nie nötig. Er hat funktioniert, aber aus dem falschen Grund: er umgeht das Schema-Problem, statt eine Ingress-Beschränkung zu umgehen, die es nicht gibt.

Und der Vollständigkeit halber, weil es den Rahmen dessen absteckt, womit ich hier arbeite: die Capabilities auf diesem Pfad melden root. Der Schreibzugriff war also die ganze Zeit da — was meine erste Aussage „ich kann nicht schreiben" doppelt falsch macht.

Aufgefallen ist es, weil der Wiki-Distill-Lauf meiner Rohnotiz widersprochen und live gegengeprüft hat. Die Wiki-Notiz trägt die richtige Version.

Korrektur, zum zweiten Mal an derselben Stelle: **der Ingress verstümmelt keine Writes.** Meine Erklärung oben ist falsch. Die Ursache war banal — die `BAO_ADDR` in dieser Umgebung steht auf `http://`, nicht `https://`. Gegenprobe eben: ``` $ BAO_ADDR=https://openbao.renner.uno bao token capabilities homelab/data/infrastructure/hetzner-s3 root $ bao token capabilities homelab/data/infrastructure/hetzner-s3 # ambient BAO_ADDR Error listing capabilities: invalid character P looking for beginning of value ``` Das P ist aller Wahrscheinlichkeit nach der Anfang von „Plain HTTP request sent to HTTPS port". Lesen funktionierte trotzdem, weil der Client den Redirect für GET folgt und für Methoden mit Body nicht — deshalb sah es nach „Reads gehen, Writes nicht" aus. **Der Port-Forward war nie nötig.** Er hat funktioniert, aber aus dem falschen Grund: er umgeht das Schema-Problem, statt eine Ingress-Beschränkung zu umgehen, die es nicht gibt. Und der Vollständigkeit halber, weil es den Rahmen dessen absteckt, womit ich hier arbeite: die Capabilities auf diesem Pfad melden `root`. Der Schreibzugriff war also die ganze Zeit da — was meine erste Aussage „ich kann nicht schreiben" doppelt falsch macht. Aufgefallen ist es, weil der Wiki-Distill-Lauf meiner Rohnotiz widersprochen und live gegengeprüft hat. Die Wiki-Notiz trägt die richtige Version.
Sign in to join this conversation.