Skip to content

Add database password support via config fields and env vars#716

Open
rdimitrov wants to merge 3 commits intomainfrom
rdimitrov/add-db-password-env-var-support
Open

Add database password support via config fields and env vars#716
rdimitrov wants to merge 3 commits intomainfrom
rdimitrov/add-db-password-env-var-support

Conversation

@rdimitrov
Copy link
Copy Markdown
Member

@rdimitrov rdimitrov commented Apr 14, 2026

Summary

  • Add Password and MigrationPassword fields to DatabaseConfig, enabling database credentials via YAML config or THV_REGISTRY_DATABASE_PASSWORD / THV_REGISTRY_DATABASE_MIGRATIONPASSWORD environment variables as an alternative to the PGPASSFILE mechanism
  • Fully backwards compatible: empty fields preserve existing PGPASSFILE / ~/.pgpass behavior
  • Mutual-exclusion validation rejects password fields when dynamicAuth is configured
  • Security hardening: json:"-" tags, slog.LogValuer redaction, Helm chart ConfigMap warning
  • Extract buildMigrationConnString() helper shared by all 4 migration callers (serve, migrate-up, migrate-down, prime-db)
  • Viper SetDefault ensures env var overrides work even when password keys are absent from YAML
  • Fix smoke test compose healthcheck port (8080 → 8081) after internal server split

Fixes: #683

Precedence

Dynamic auth token (AWS RDS IAM) > config password field / env var > PGPASSFILE / ~/.pgpass

Test plan

  • task lint-fix passes (0 issues)
  • task test passes for all changed packages (internal/config, cmd/thv-registry-api/app)
  • Smoke test: 31/31 pass with existing PGPASSFILE setup (backwards compat)
  • Smoke test: 10/10 pass with THV_REGISTRY_DATABASE_PASSWORD + THV_REGISTRY_DATABASE_MIGRATIONPASSWORD env vars (no PGPASSFILE)
  • Config validation rejects password + dynamicAuth together (unit test)

🤖 Generated with Claude Code

Add `Password` and `MigrationPassword` fields to `DatabaseConfig`,
allowing database credentials to be provided via YAML config or
`THV_REGISTRY_DATABASE_PASSWORD` / `THV_REGISTRY_DATABASE_MIGRATIONPASSWORD`
environment variables as an alternative to the PGPASSFILE mechanism.

The change is fully backwards compatible: when no password fields are
set, behavior is identical to today (pgx falls back to PGPASSFILE /
~/.pgpass). Password fields and `dynamicAuth` are mutually exclusive,
enforced by config validation.

Security hardening includes `json:"-"` tags to prevent accidental JSON
serialization, an `slog.LogValuer` implementation to redact passwords
from structured logs, and a Helm chart warning against placing passwords
in the config block (which renders into a Kubernetes ConfigMap).

Viper `SetDefault` calls ensure the env var overrides work even when the
password keys are absent from the YAML config file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 14, 2026

Codecov Report

❌ Patch coverage is 61.53846% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.74%. Comparing base (7106e46) to head (9accc17).

Files with missing lines Patch % Lines
internal/config/config.go 61.53% 10 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #716      +/-   ##
==========================================
- Coverage   60.76%   60.74%   -0.02%     
==========================================
  Files         107      107              
  Lines       10394    10415      +21     
==========================================
+ Hits         6316     6327      +11     
- Misses       3534     3544      +10     
  Partials      544      544              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The health and readiness endpoints moved to the internal server on port
8081. Update the docker-compose.smoke-test.yaml healthcheck to use the
correct port and expose 8081 to the host for external probes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rdimitrov rdimitrov marked this pull request as ready for review April 14, 2026 09:37
@rdimitrov
Copy link
Copy Markdown
Member Author

Implementation Plan

Context

Currently the only way to pass static DB passwords to the registry server is via PGPASSFILE (~/.pgpass). This requires an initContainer to copy a Secret-mounted file to an emptyDir and chmod 600 it — operationally complex for what should be a simple credential. The team agreed to decouple the operator from config.yaml internals, and adding direct password fields is the simplest path to let users pass credentials without the pgpass ceremony.

This change is fully backwards compatible: when no password fields are set, behavior is identical to today (pgx falls back to PGPASSFILE).

Changes

1. Add password fields to DatabaseConfig

File: internal/config/config.go

Two new fields with json:"-" tags to prevent accidental JSON serialization:

Password          string `yaml:"password,omitempty" json:"-"`
MigrationPassword string `yaml:"migrationPassword,omitempty" json:"-"`

Viper SetDefault calls in LoadConfig() ensure THV_REGISTRY_DATABASE_PASSWORD / THV_REGISTRY_DATABASE_MIGRATIONPASSWORD env vars work even when the password keys are absent from the YAML config file.

2. Update GetPassword() and GetMigrationPassword()

  • GetPassword() → returns d.Password (empty string = pgpassfile fallback)
  • GetMigrationPassword() → returns d.MigrationPassword if set; falls back to d.Password when migrationUser == user; otherwise returns "" for pgpassfile

3. Update connection string builders

  • GetConnectionString()BuildConnectionStringWithAuth(d.User, d.GetPassword())
  • GetMigrationConnectionString()BuildConnectionStringWithAuth(d.GetMigrationUser(), d.GetMigrationPassword())

4. Extract buildMigrationConnString helper

File: cmd/thv-registry-api/app/serve.go

Extracted shared helper used by all 4 migration callers (serve.go, migrate_up.go, migrate_down.go, prime_db.go). Implements precedence: DynamicAuth token > config password field > PGPASSFILE. This also resolved a cyclomatic complexity lint issue in migrate_up.go.

5. Add mutual-exclusion validation

File: internal/config/config.go (validateStorageConfig)

Rejects configurations where password/migrationPassword and dynamicAuth are both set.

6. Security hardening

  • json:"-" tags on password fields prevent accidental JSON serialization
  • slog.LogValuer implementation on *DatabaseConfig redacts passwords from structured logs (shows has_password/has_migration_password booleans instead)
  • Helm chart values.yaml includes explicit WARNING against placing passwords in the config block (rendered into a Kubernetes ConfigMap)

7. Update tests

internal/config/config_test.go:

  • TestDatabaseConfigGetPassword — table-driven with empty/set cases
  • TestDatabaseConfigGetMigrationPassword — 5 cases covering all fallback paths
  • TestDatabaseConfigGetConnectionString — added password-in-URL case
  • TestDatabaseConfigGetMigrationConnectionString — added migration password and fallback cases
  • TestViperEnvOverrideDatabasePassword — verifies env var works when key is absent from YAML
  • TestValidateStorageConfigRejectsPasswordWithDynamicAuth — mutual-exclusion validation

internal/authz/authz_helpers_test.go:

  • Replaced os.Setenv("PGPASSWORD") workaround with Password field on config struct

8. Update Helm chart and docs

  • values.yaml — updated comments, added secretKeyRef examples for password env vars
  • docs/environment-variables.md — added password env vars, updated security note
  • docs/database.md — added fields to table, updated password security section with precedence
  • docs/configuration.md — updated password references

9. Fix smoke test compose healthcheck

File: docker-compose.smoke-test.yaml

Health/readiness endpoints moved to the internal server on port 8081. Fixed healthcheck URL and exposed port 8081.

Verification

  1. task lint-fix — 0 issues
  2. task test — all changed packages pass
  3. ✅ Smoke test with PGPASSFILE — 31/31 passed (backwards compat)
  4. ✅ Smoke test with THV_REGISTRY_DATABASE_PASSWORD + THV_REGISTRY_DATABASE_MIGRATIONPASSWORD — 10/10 passed (no PGPASSFILE)
  5. ✅ Config validation rejects password + dynamicAuth (unit test)

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.

Migration from PGPASSFILE to PGPASSWORD env var

2 participants