[recoverability][high] Hetzner buckets have no versioning or object-lock (backups + tfstate) #191

Closed
opened 2026-07-14 09:47:00 +00:00 by claude-bot · 2 comments
Member

Finding

opentofu/object_storage.tf sets object_locking = false and no versioning/lifecycle on all four Hetzner buckets: renner-uno-k8s-cnpg, renner-uno-k8s-velero, renner-uno-k8s-secrets, renner-uno-tfstate.

Verified

object_storage.tf:53 object_locking = false (applied via for_each to all buckets); no versioning block present.

Impact

One leaked/misused S3 credential can irrecoverably delete Velero backups, CNPG WAL archives, and the Terraform state — no rollback at the storage layer.

Fix

Enable versioning + object-lock (retention) on at least the backup and tfstate buckets.


Automated security review 2026-07-14. Verified.

## Finding opentofu/object_storage.tf sets object_locking = false and no versioning/lifecycle on all four Hetzner buckets: renner-uno-k8s-cnpg, renner-uno-k8s-velero, renner-uno-k8s-secrets, renner-uno-tfstate. ## Verified object_storage.tf:53 object_locking = false (applied via for_each to all buckets); no versioning block present. ## Impact One leaked/misused S3 credential can irrecoverably delete Velero backups, CNPG WAL archives, and the Terraform state — no rollback at the storage layer. ## Fix Enable versioning + object-lock (retention) on at least the backup and tfstate buckets. --- _Automated security review 2026-07-14. Verified._
Author
Member

Test connectivity check - verifying tea comment command works

Test connectivity check - verifying tea comment command works
Author
Member

Verified against origin/master (07c93e4)

Confirmed: opentofu/object_storage.tf:53 object_locking = false, no versioning, on all four buckets (renner-uno-k8s-cnpg, renner-uno-k8s-velero, renner-uno-k8s-secrets, renner-uno-tfstate). enable_hetzner_buckets defaults to true (variables_hetzner.tf:27) — these buckets are already live with real data, not pending activation.

Fix opened: PR #227

Adds minio_s3_bucket_versioning (Enabled) on all four buckets, plus minio_s3_bucket_lifecycle noncurrent-version expiration (30d) on the two high-churn backup buckets (cnpg, velero) to match existing CNPG Barman / Velero retention and avoid unbounded storage growth. tofu fmt/tofu validate pass locally.

object_locking NOT included — needs an operator decision

Checked the aminueza/minio provider source (resource_minio_s3_bucket.go, provider 3.38.1, pinned ~> 3.33): object_locking is consumed only in Create, no Update handling, no ForceNew. Flipping it to true in the .tf for these already-existing buckets would be silently accepted by tofu apply and change nothing on the actual bucket — a fix that reads as done but isn't.

Real object-lock requires destroying and recreating each bucket (S3/MinIO semantics: object lock is create-time-only), i.e. a full data migration for buckets holding live CNPG WAL archives, Velero backups, and the Terraform state backend itself (renner-uno-tfstate — the very backend this plan/apply runs against).

Question for the operator: how do you want to handle object-lock?

  • (a) Accept a migration: create new object-lock-enabled buckets, cut over CNPG/Velero/tfstate backend config, backfill or let old backups age out, decommission the old buckets — real work, real downtime window, and needs confirming Hetzner's S3-compatible impl actually honors Object Lock (untested).
  • (b) Treat versioning (this PR) as sufficient mitigation for now and defer object-lock indefinitely — versioning already stops a bare DeleteObject from being unrecoverable; an attacker with the credential could still purge specific versions or suspend versioning, but that's a materially higher bar than the current "one API call wipes everything."
  • (c) Something else (e.g. Hetzner Console-side manual enable, if their backend supports it outside Terraform).

Not applying (a) blind since it touches the live tfstate backend and real backup data. Leaving this open pending a decision; PR #227 covers the safe/non-destructive half of the fix.

## Verified against origin/master (07c93e4) Confirmed: opentofu/object_storage.tf:53 `object_locking = false`, no versioning, on all four buckets (`renner-uno-k8s-cnpg`, `renner-uno-k8s-velero`, `renner-uno-k8s-secrets`, `renner-uno-tfstate`). `enable_hetzner_buckets` defaults to `true` (variables_hetzner.tf:27) — these buckets are already live with real data, not pending activation. ## Fix opened: PR #227 Adds `minio_s3_bucket_versioning` (Enabled) on all four buckets, plus `minio_s3_bucket_lifecycle` noncurrent-version expiration (30d) on the two high-churn backup buckets (cnpg, velero) to match existing CNPG Barman / Velero retention and avoid unbounded storage growth. `tofu fmt`/`tofu validate` pass locally. ## object_locking NOT included — needs an operator decision Checked the `aminueza/minio` provider source (`resource_minio_s3_bucket.go`, provider 3.38.1, pinned `~> 3.33`): `object_locking` is consumed **only in Create**, no `Update` handling, no `ForceNew`. Flipping it to `true` in the .tf for these already-existing buckets would be silently accepted by `tofu apply` and change **nothing** on the actual bucket — a fix that reads as done but isn't. Real object-lock requires **destroying and recreating** each bucket (S3/MinIO semantics: object lock is create-time-only), i.e. a full data migration for buckets holding live CNPG WAL archives, Velero backups, and the Terraform state backend itself (`renner-uno-tfstate` — the very backend this plan/apply runs against). **Question for the operator:** how do you want to handle object-lock? - (a) Accept a migration: create new object-lock-enabled buckets, cut over CNPG/Velero/tfstate backend config, backfill or let old backups age out, decommission the old buckets — real work, real downtime window, and needs confirming Hetzner's S3-compatible impl actually honors Object Lock (untested). - (b) Treat versioning (this PR) as sufficient mitigation for now and defer object-lock indefinitely — versioning already stops a bare `DeleteObject` from being unrecoverable; an attacker with the credential could still purge specific versions or suspend versioning, but that's a materially higher bar than the current "one API call wipes everything." - (c) Something else (e.g. Hetzner Console-side manual enable, if their backend supports it outside Terraform). Not applying (a) blind since it touches the live tfstate backend and real backup data. Leaving this open pending a decision; PR #227 covers the safe/non-destructive half of the fix.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: konairius/homelab#191