fix(security): split DATABASE_URL into individual DB secrets, redact password with secrecy (#894) - #2
Open
Vincent6581 wants to merge 1 commit into
Open
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves solutions-plug#894.
DATABASE_URLwas 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.tomlsecrecy = "0.8"withserdefeatureservices/api/src/config.rsDbCredentialsstruct:host: String,port: u16,name: String,user: String,password: SecretStringDebugimpl prints password as[redacted]— it can never leak via{:?}ortracingspansto_connection_string() -> SecretStringassemblespostgres://user:pass@host:port/name— callers must call.expose_secret()explicitlyfrom_env()readsDB_HOST,DB_PORT,DB_NAME,DB_USER,DB_PASSWORDConfig.database_url: StringwithConfig.db_credentials: DbCredentialsdatabase_urlfield (assembled once infrom_env()) for backward-compat with the sqlx pool initialiserConfig::validate()now checks individual fields (host/name/user/password non-empty); password is checked viaexpose_secret()— never printed in error outputConfigliterals updated to useDbCredentialsstructstest_config_validate_malformed_database_url(checks URL prefix — no longer meaningful) withtest_config_validate_missing_db_passwordinfrastructure/terraform/modules/ecs/main.tfvariable "database_url"andaws_secretsmanager_secret.database_urldb_host,db_port(default 5432),db_name,db_user,db_password(sensitive)aws_secretsmanager_secret+aws_secretsmanager_secret_versionresources:db_host,db_port,db_name,db_user,db_passwordsecretsblock now injectsDB_HOST,DB_PORT,DB_NAME,DB_USER,DB_PASSWORDindividuallyGetSecretValuepolicy updated to reference the five new secret ARNsAcceptance criteria
src/config.rsassembles the connection string at runtime from individual componentsSecretStringand never appears in log outputMigration note
Operators must set the five new Terraform variables (
db_host,db_port,db_name,db_user,db_password) before applying. The olddatabase_urlvariable is removed — aterraform planwill show thedatabase_urlsecret being destroyed and five new secrets being created.