Skip to content

fix(security): split DATABASE_URL into individual DB secrets, redact password with secrecy (#894) - #2

Open
Vincent6581 wants to merge 1 commit into
mainfrom
fix/894-db-credentials-secrets-manager
Open

fix(security): split DATABASE_URL into individual DB secrets, redact password with secrecy (#894)#2
Vincent6581 wants to merge 1 commit into
mainfrom
fix/894-db-credentials-secrets-manager

Conversation

@Vincent6581

Copy link
Copy Markdown
Owner

Summary

Resolves solutions-plug#894.

DATABASE_URL was a single connection string containing the password in plaintext — visible to any process reading environment variables, logged by ECS task definition events, and visible in CloudWatch Logs.

This PR replaces it with five individual Secrets Manager secrets, assembles the connection string at the last possible moment inside a SecretString, and ensures the password is never written to logs.


Changes

services/api/Cargo.toml

  • Add secrecy = "0.8" with serde feature

services/api/src/config.rs

  • New DbCredentials struct:
    • Fields: host: String, port: u16, name: String, user: String, password: SecretString
    • Debug impl prints password as [redacted] — it can never leak via {:?} or tracing spans
    • to_connection_string() -> SecretString assembles postgres://user:pass@host:port/name — callers must call .expose_secret() explicitly
    • from_env() reads DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD
  • Replace Config.database_url: String with Config.db_credentials: DbCredentials
  • Keep a deprecated database_url field (assembled once in from_env()) for backward-compat with the sqlx pool initialiser
  • Config::validate() now checks individual fields (host/name/user/password non-empty); password is checked via expose_secret() — never printed in error output
  • All four test Config literals updated to use DbCredentials structs
  • Replaced test_config_validate_malformed_database_url (checks URL prefix — no longer meaningful) with test_config_validate_missing_db_password

infrastructure/terraform/modules/ecs/main.tf

  • Remove variable "database_url" and aws_secretsmanager_secret.database_url
  • Add variables: db_host, db_port (default 5432), db_name, db_user, db_password (sensitive)
  • Add five aws_secretsmanager_secret + aws_secretsmanager_secret_version resources: db_host, db_port, db_name, db_user, db_password
  • ECS task definition secrets block now injects DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD individually
  • IAM GetSecretValue policy updated to reference the five new secret ARNs

Acceptance criteria

  • Database credentials moved to AWS Secrets Manager
  • Terraform ECS module injects individual DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD secrets
  • src/config.rs assembles the connection string at runtime from individual components
  • Connection string password is wrapped in SecretString and never appears in log output

Migration note

Operators must set the five new Terraform variables (db_host, db_port, db_name, db_user, db_password) before applying. The old database_url variable is removed — a terraform plan will show the database_url secret being destroyed and five new secrets being created.

…h secrecy (solutions-plug#894)

The full DATABASE_URL (containing username + password) was stored as a single
environment variable, visible to any process that could read env vars, and
logged by ECS task definition events and CloudWatch Logs.

Changes:
- Add secrecy = "0.8" dependency to Cargo.toml (features = ["serde"])
- Add DbCredentials struct in config.rs:
  - Individual fields: host, port, name, user, password: SecretString
  - Custom Debug impl that prints password as [redacted]
  - DbCredentials::to_connection_string() -> SecretString assembles the URL
    at the last moment; result is never stored as a plain String
  - DbCredentials::from_env() reads DB_HOST, DB_PORT, DB_NAME, DB_USER,
    DB_PASSWORD from environment variables
- Replace Config.database_url: String with Config.db_credentials: DbCredentials;
  keep a deprecated database_url field assembled via to_connection_string() for
  backward-compat with the sqlx pool initialiser (marked #[deprecated])
- Update Config::validate() to check individual DB fields (host/name/user/password
  non-empty) rather than validating a URL string — the password is checked via
  expose_secret() and never printed in error messages
- Update all test Config literals to use DbCredentials structs; replace the
  now-obsolete test_config_validate_malformed_database_url test with
  test_config_validate_missing_db_password
- Terraform (infrastructure/terraform/modules/ecs/main.tf):
  - Remove database_url variable and aws_secretsmanager_secret.database_url
  - Add db_host, db_port, db_name, db_user, db_password variables
  - Add five individual aws_secretsmanager_secret resources
  - Update ECS task definition secrets block to inject DB_HOST, DB_PORT,
    DB_NAME, DB_USER, DB_PASSWORD individually
  - Update IAM GetSecretValue policy to reference the five new secret ARNs

Closes solutions-plug#894
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.

Database password visible in DATABASE_URL environment variable

1 participant