Files
Alexey Artamonov 2b5877eff9 feat(postgres)!: generate user passwords and read them at runtime
users[].password is gone from the chart, its schema and the API type.
Every application-user password is generated into <release>-credentials
and kept across reconciles by lookup, so an upgraded release keeps the
password it already has and a password left in values is ignored. The
init-job mounts that Secret and applies each password with ALTER ROLE
and a psql variable, so no password is rendered into the init-script
Secret that Helm keeps in release history. While bootstrap is enabled
the Secret is marked credentials-pending, and NOTES say the generated
passwords apply once the init-job runs.

Names reach superuser SQL and the init-script shell, so render-time
guards reject a username, database or extension name outside
[-._a-zA-Z0-9] or past the length PostgreSQL silently truncates: 63
bytes for a role or extension, 54 for a database, whose <db>_readonly
role would otherwise truncate and fail the next run. A username over 63
bytes that the credentials Secret already carries still upgrades.
users.postgres, the CNPG superuser, is rejected in every bootstrap
state. Changing a database's key in values drops the database, so the
reject message and the README give the rename that keeps the data: a
platform administrator renames it and its roles as the CNPG superuser
before the key changes.

The init-job cannot hang its Helm hook: the readiness wait ends on
elapsed time below activeDeadlineSeconds and the Job never retries, so a
purged restore target fails with a diagnostic. The delete-users pass
reads managed users from pg_shdescription instead of parsing the
version-dependent \du+ layout, which on PostgreSQL 16 and later matched
nothing and left a removed user able to log in; it compares names as
CREATE ROLE stores them and reassigns a removed user's objects in every
database before dropping it. A database without roles renders, and
extension names are quoted for hyphenated names and folded to lowercase
so they resolve as they did unquoted. A value that carries its own
surrounding quotes, the only way a hyphenated extension rendered before,
has that pair removed and keeps its case, so such a release still
upgrades. The README reads a password with jq, since jsonpath splits a
dotted key.

Signed-off-by: Alexey Artamonov <aleksei.artamonov@aenix.io>
2026-09-24 18:10:57 +03:00

59 lines
2.0 KiB
YAML

suite: NOTES.txt restore warning is gated on both bootstrap and users
templates:
- templates/NOTES.txt
release:
name: pg-test
namespace: tenant-test
tests:
- it: warns during a restore when the release defines users
set:
bootstrap:
enabled: true
users:
testuser: {}
asserts:
- matchRegexRaw:
pattern: 'provisioned as a restore'
- matchRegexRaw:
pattern: 'pg-test-credentials Secret now holds freshly generated passwords'
- matchRegexRaw:
pattern: 'postgres\.cozystack\.io/credentials-pending'
# The flag is gated on bootstrap.enabled (init-script.yaml), which the
# restore driver clears at the START of convergence, so it is absent for
# the window before the init-job finishes. NOTES must not claim the flag
# persists "until this converges" and must point at an application login as
# the real check. Restoring that overclaim turns this red.
- notMatchRegexRaw:
pattern: 'until this converges'
- matchRegexRaw:
pattern: 'That flag tracks only'
- matchRegexRaw:
pattern: 'Do not read a missing flag as "converged"'
- matchRegexRaw:
pattern: 'Confirm convergence by logging in as an'
# The credentials Secret is gated on .Values.users in init-script.yaml, so a
# users-less restore never renders it. NOTES must not then describe that Secret
# and its pending-credentials annotation, or an operator chases a nonexistent
# object. Reverting the gate to bootstrap-only turns this red.
- it: stays quiet during a restore when there are no users
set:
bootstrap:
enabled: true
users: {}
asserts:
- notMatchRegexRaw:
pattern: 'provisioned as a restore'
- notMatchRegexRaw:
pattern: 'credentials-pending'
- it: stays quiet for a normal install
set:
bootstrap:
enabled: false
users:
testuser: {}
asserts:
- notMatchRegexRaw:
pattern: 'provisioned as a restore'