Merge pull request 'Take the database password from PGPASSWORD, not the DSN' (#9) from postgres-dsn-password into main
CI / test (push) Successful in 4s
CI / chart (push) Successful in 1s
CI / security (push) Successful in 10s

Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
2026-09-20 09:07:32 +00:00
4 changed files with 47 additions and 23 deletions
+6 -5
View File
@@ -87,11 +87,12 @@ fmt: ## Report unformatted files
echo "gofmt needed:"; echo "$$unformatted"; gofmt -d .; exit 1; \
fi
# database.dsnSecret.name has no default and the deployment `required`s it: the
# chart provisions no database and cannot guess where the credentials live, so a
# render without it is meant to fail. Setting it here keeps the lint honest about
# what a working install needs.
HELM_LINT_SET = --set image.tag=v0.0.0 --set database.dsnSecret.name=terdut-db
# database.dsn has no default and the deployment `required`s it: the chart
# provisions no database and cannot guess where it is, so a render without it is
# meant to fail. Setting it here keeps the lint honest about what a working
# install needs.
HELM_LINT_SET = --set image.tag=v0.0.0 \
--set 'database.dsn=postgres://terdut@terdut-postgres:5432/terdut?sslmode=require'
.PHONY: helm-lint
helm-lint: ## Lint and render the chart
+12 -5
View File
@@ -119,17 +119,24 @@ than an `Ingress`. TLS is terminated at the gateway, so the server itself never
| `networking.listener` | `""` | Gateway listener (`sectionName`) to bind to. Empty attaches to every matching listener, **including plaintext HTTP** — set it to the HTTPS listener's name to serve TLS only |
| `networking.servicePort` | `8080` | Port the route forwards to; keep in sync with `service.port` |
| `bootstrap.enabled` | `true` | Runs a post-install hook that creates the first user and stores its API key in the `<release>-admin-key` Secret. Already-bootstrapped servers are left alone |
| `database.dsnSecret.name` | `""` | **Required.** Existing Secret holding the Postgres DSN. The chart provisions no database |
| `database.dsnSecret.key` | `dsn` | Key within that Secret |
| `database.dsn` | `""` | **Required.** Postgres DSN, with no password in it. The chart provisions no database |
| `database.passwordSecret.name` | `""` | Secret supplying `PGPASSWORD`. With the Zalando postgres operator, the Secret it generates for the role |
| `database.passwordSecret.key` | `password` | Key within that Secret |
The API key travels in an `Authorization: Bearer` header, so set `networking.listener` whenever the
hostname is reachable outside a trusted network.
#### The database
The chart provisions no database: it takes a DSN from a Secret and expects a Postgres that already
exists. In this cluster the wrapper chart declares an `acid.zalan.do/v1 postgresql` CR and passes
the Secret the operator writes; anywhere else, any reachable Postgres 14+ will do.
The chart provisions no database: it takes a DSN and expects a Postgres that already exists. In this
cluster the wrapper chart declares an `acid.zalan.do/v1 postgresql` CR; anywhere else, any reachable
Postgres 14+ will do.
The DSN carries no password. pgx falls back to libpq's environment variables for whatever the DSN
leaves out, so the password arrives as `PGPASSWORD` from a Secret and never appears in values, in
the rendered manifest or in `kubectl describe pod`. With the postgres operator that Secret is the
one it generates for the role, so a rebuild mints a new password with nothing to keep in sync —
the same wiring miniflux uses.
The server migrates its own schema on startup, so a new database only has to exist and be writable.
@@ -32,14 +32,18 @@ spec:
env:
- name: TERDUT_ADDR
value: ":{{ .Values.service.port }}"
# The connection string, from a Secret: it carries the password.
# The wrapper chart points this at the Secret the Postgres operator
# writes for this database's role.
- name: TERDUT_DB_DSN
value: {{ required "database.dsn is required" .Values.database.dsn | quote }}
{{- if .Values.database.passwordSecret.name }}
# The password reaches pgx through libpq's environment variable
# rather than through the DSN, so it stays out of the rendered
# manifest. pgx fills in from PG* whatever the DSN leaves out.
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ required "database.dsnSecret.name is required" .Values.database.dsnSecret.name }}
key: {{ .Values.database.dsnSecret.key }}
name: {{ .Values.database.passwordSecret.name }}
key: {{ .Values.database.passwordSecret.key }}
{{- end }}
- name: TERDUT_STALE_AFTER
value: "{{ .Values.sweeper.staleAfter }}"
- name: TERDUT_ARCHIVE_AFTER
+20 -8
View File
@@ -11,16 +11,28 @@ image:
tag: "latest"
pullPolicy: IfNotPresent
# Postgres connection, as a DSN in an existing Secret:
# postgres://user:password@host:5432/terdut?sslmode=require
#
# The chart provisions no database. In this cluster the wrapper chart declares an
# acid.zalan.do postgresql CR and points this at the Secret the operator writes;
# anywhere else, any reachable Postgres will do.
# Postgres connection. The chart provisions no database; it expects one to exist.
database:
dsnSecret:
# Required. A DSN with no password in it:
# postgres://terdut@terdut-postgres:5432/terdut?sslmode=require
#
# The password is deliberately a separate setting. pgx falls back to libpq's
# environment variables for anything the DSN omits, so PGPASSWORD supplies it
# without the credential appearing in values, in the rendered manifest, or in
# `kubectl describe pod`.
dsn: ""
# Where PGPASSWORD comes from. With the Zalando postgres operator this is the
# Secret it generates for the role — `<user>.<cluster>.credentials.postgresql.acid.zalan.do`,
# whose keys are `username` and `password` — so a from-scratch rebuild mints a
# new password and the server picks it up with nothing to keep in sync.
#
# Read at process start only: rotating the password needs a pod restart.
#
# Leave name empty only if the DSN carries its own password, which puts it in
# the manifest.
passwordSecret:
name: ""
key: dsn
key: password
service:
type: ClusterIP