Skip to content

fix(database): make root password create-only for SecretBackendConnection (fixes #38)#136

Closed
clain23 wants to merge 1 commit into
upbound:mainfrom
maestra-io:fix/issue-38-password-create-only
Closed

fix(database): make root password create-only for SecretBackendConnection (fixes #38)#136
clain23 wants to merge 1 commit into
upbound:mainfrom
maestra-io:fix/issue-38-password-create-only

Conversation

@clain23

@clain23 clain23 commented Jun 24, 2026

Copy link
Copy Markdown

Problem (fixes #38)

vault_database_secret_backend_connection re-sends the root password on every reconcile, clobbering a rotate-root'd password (28P01 / broken dynamic creds).

Root cause: Vault's database/config/<name> read API never returns the password, so the refreshed Terraform state has no password. Upjet then diffs the desired config (with the password resolved from passwordSecretRef) against state (no password) → a perpetual non-empty diff on <engine>.0.passworddatabaseSecretBackendConnectionUpdate's d.HasChange(passwordKey) is true every reconcile → the password is PUT back to database/config, overwriting whatever rotate-root last set.

This makes the resource fundamentally incompatible with rotate-root, which is the documented way to take ownership of the root credential after creation.

Fix

A TerraformCustomDiff that drops every engine <engine>.0.password attribute from the Update diff (keyed on a non-empty Terraform state ID) while leaving the Create diff intact. The password is sent once at creation and never re-sent on reconcile, so a rotated password survives.

This is the Crossplane-native equivalent of the well-known Terraform workaround lifecycle.ignore_changes = [postgresql[0].password].

  • Pure Upjet config (config/database/config.go + wiring in config/provider.go) — no schema/CRD change, no make generate diff.
  • Create path unchanged (password still required + sent for new connections).
  • Engine list sourced from GetConnectionDetailsMapping() (all 17 engines with a root password); a unit test guards against drift.

Test

config/database/config_test.go: Create keeps the password; Update drops it for all engines; nil/Destroy diffs untouched; engine-list completeness.

Validated in production (Crossplane v2) across two clusters: with this change the connections reconcile under full managementPolicies without ever re-writing database/config, and a rotate-root'd password survives provider cold-starts.

…tion

Vault's database/config/<name> read API never returns the password, so
Upjet's diff perpetually sees the passwordSecretRef-resolved password as a
change and re-applies it on every reconcile/cold-start, clobbering a
rotate-root'd root password (28P01). Add a TerraformCustomDiff that drops
<engine>.0.password from the Update diff (non-empty TF state ID) while
keeping it on Create. Crossplane-native equivalent of the TF
lifecycle.ignore_changes=[<engine>[0].password] already used in maestra-io/vault.

Lets the composition use full managementPolicies + passwordSecretRef without
the deny-update workaround, eliminating the 403 path-exists collision.

Fixes upbound#38

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Upbound-CLA

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@clain23 clain23 closed this Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vault-provider does not allow root user rotation

2 participants