Database Connection String Security: Encryption, IAM Auth & Secrets Management
Database Connection String Security#
Your database connection string is the keys to your kingdom. A leaked connection string means full access to your data — reads, writes, deletes, everything.
Yet developers still hardcode credentials in config files, commit them to Git, and share them over Slack. Let's fix that.
The Problem with Traditional Connection Strings#
A typical connection string looks like this:
postgresql://admin:SuperSecret123@db.example.com:5432/production
Everything an attacker needs is right there: username, password, host, port, and database name. If this string leaks through logs, error messages, or source control, your database is compromised.
Common leak vectors:
- Hardcoded in application config committed to Git
- Logged in plaintext by ORMs or connection pools
- Shared in Slack/email between team members
- Visible in CI/CD pipeline logs
- Stored in
.envfiles without proper.gitignore
Connection Encryption with SSL/TLS#
Why Encryption Matters#
Without SSL/TLS, database traffic travels in plaintext. Anyone on the network path (cloud VPC notwithstanding) can sniff queries and results.
# Unencrypted — vulnerable to sniffing
Client → [SELECT * FROM users WHERE email='...'] → Database
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Anyone on the network can read this
# Encrypted with TLS
Client → [encrypted bytes] → Database
Only client and server can decrypt
Configuring SSL/TLS for PostgreSQL#
# Connection with SSL required
connection:
host: db.example.com
port: 5432
sslmode: verify-full
sslrootcert: /certs/ca.pem
sslcert: /certs/client-cert.pem
sslkey: /certs/client-key.pem
SSL modes ranked by security:
| Mode | Encryption | Verification | Use Case |
|---|---|---|---|
disable | No | No | Never in production |
allow | Maybe | No | Not recommended |
prefer | Yes if available | No | Default, but weak |
require | Yes | No | Minimum for production |
verify-ca | Yes | CA only | Better — verifies certificate authority |
verify-full | Yes | CA + hostname | Best — prevents MITM attacks |
Always use verify-full in production. Anything less is vulnerable to man-in-the-middle attacks.
Configuring SSL/TLS for MySQL#
[client]
ssl-mode = VERIFY_IDENTITY
ssl-ca = /certs/ca.pem
ssl-cert = /certs/client-cert.pem
ssl-key = /certs/client-key.pem
MongoDB TLS Configuration#
const client = new MongoClient(uri, {
tls: true,
tlsCAFile: '/certs/ca.pem',
tlsCertificateKeyFile: '/certs/client.pem',
tlsAllowInvalidCertificates: false, // never set true in production
tlsAllowInvalidHostnames: false // never set true in production
});
IAM Authentication: Eliminate Passwords Entirely#
IAM-based authentication replaces static passwords with cloud identity tokens. No passwords to rotate, no credentials to leak.
AWS RDS IAM Authentication#
import boto3
def get_rds_auth_token(host, port, user, region):
client = boto3.client('rds', region_name=region)
token = client.generate_db_auth_token(
DBHostname=host,
Port=port,
DBUsername=user,
Region=region
)
return token
# Token is valid for 15 minutes — auto-rotated
token = get_rds_auth_token('mydb.abc123.us-east-1.rds.amazonaws.com', 5432, 'app_user', 'us-east-1')
GCP Cloud SQL IAM Authentication#
from google.cloud.sql.connector import Connector
connector = Connector()
def get_connection():
return connector.connect(
"project:region:instance",
"pg8000",
user="service-account@project.iam.gserviceaccount.com",
db="production",
enable_iam_auth=True
)
Azure AD Authentication for Azure SQL#
# Connection string using Azure AD
Server=myserver.database.windows.net;
Database=mydb;
Authentication=Active Directory Default;
Encrypt=True;
IAM auth benefits:
- No static passwords to manage or rotate
- Credentials tied to cloud identity (service account, role)
- Automatic expiration (15-minute tokens for AWS)
- Centralized access control through IAM policies
- Full audit trail in cloud provider logs
Short-Lived Credentials with HashiCorp Vault#
Static credentials are a liability. Vault generates temporary database credentials on demand.
# How Vault dynamic secrets work
App → Vault: "I need database credentials"
Vault → Database: CREATE ROLE temp_user WITH PASSWORD 'random' VALID UNTIL '15min'
Vault → App: {username: "temp_user", password: "random", ttl: "15m"}
# After TTL expires
Vault → Database: DROP ROLE temp_user
Vault Database Secrets Engine Configuration#
# Enable the database secrets engine
vault secrets enable database
# Configure PostgreSQL connection
vault write database/config/production \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432/production" \
allowed_roles="app-role" \
username="vault_admin" \
password="admin_password"
# Create a role with a creation statement
vault write database/roles/app-role \
db_name=production \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
Application Usage#
import hvac
client = hvac.Client(url='https://vault.example.com:8200')
creds = client.secrets.database.generate_credentials('app-role')
db_config = {
'host': 'db.example.com',
'user': creds['data']['username'], # v-app-role-abc123
'password': creds['data']['password'], # randomly generated
'database': 'production'
}
# Credentials auto-expire after TTL
Secrets Management for Connection Strings#
Never Hardcode — Use a Secrets Manager#
# BAD — hardcoded in environment
DATABASE_URL=postgresql://admin:secret@db.example.com:5432/prod
# GOOD — reference to secret
DATABASE_URL=vault:database/creds/app-role
DATABASE_URL=aws-sm:prod/database/connection
DATABASE_URL=gcp-sm:projects/123/secrets/db-connection/versions/latest
Kubernetes Secrets with External Secrets Operator#
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: username
remoteRef:
key: database/creds/app-role
property: username
- secretKey: password
remoteRef:
key: database/creds/app-role
property: password
Secret Rotation Strategy#
# Rotation timeline
Day 0: Generate new credentials (Credential B)
Day 0: Update application to accept both A and B
Day 1: Switch primary to Credential B
Day 3: Verify no traffic using Credential A
Day 5: Revoke Credential A
# Automated rotation with AWS Secrets Manager
aws secretsmanager rotate-secret \
--secret-id prod/database/credentials \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123:function:rotate-db-creds \
--rotation-rules AutomaticallyAfterDays=30
Audit Logging Database Connections#
Every connection to your database should be logged and monitored.
PostgreSQL Connection Audit Logging#
-- Enable connection logging
ALTER SYSTEM SET log_connections = on;
ALTER SYSTEM SET log_disconnections = on;
ALTER SYSTEM SET log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h ';
-- Log all DDL statements
ALTER SYSTEM SET log_statement = 'ddl';
What to Monitor#
# Alert on these patterns
- Connection from unknown IP addresses
- Connections using the root/admin account
- Unusual connection volume spikes
- Failed authentication attempts (brute force)
- Connections outside business hours (if applicable)
- Connections from deprecated credential sets
Centralized Audit with pgAudit#
-- Install pgAudit extension
CREATE EXTENSION pgaudit;
-- Log all reads and writes
ALTER SYSTEM SET pgaudit.log = 'read, write, ddl';
-- Log role-specific activity
ALTER ROLE app_user SET pgaudit.log = 'write, ddl';
Security Checklist#
| Practice | Priority | Effort |
|---|---|---|
Enable SSL/TLS (verify-full) | Critical | Low |
| Move secrets out of code/config | Critical | Medium |
| Use IAM auth where available | High | Medium |
| Implement short-lived credentials | High | High |
| Enable connection audit logging | High | Low |
| Rotate credentials automatically | Medium | Medium |
| Restrict network access (VPC, firewall) | Critical | Low |
| Use separate credentials per service | High | Low |
| Monitor for credential leaks (GitGuardian) | Medium | Low |
Common Mistakes#
- Using
sslmode=requireinstead ofverify-full—requireencrypts but does not verify the server identity - Sharing one credential across all services — if one service is compromised, all services are compromised
- Long-lived credentials without rotation — credentials should expire, not live forever
- Logging connection strings in error messages — sanitize all logs to strip credentials
- Storing secrets in environment variables without encryption — env vars are visible in process listings and crash dumps
Summary#
| Layer | What to Do |
|---|---|
| Transport | SSL/TLS with verify-full, mTLS for internal |
| Authentication | IAM auth, short-lived tokens, no static passwords |
| Secrets | Vault/Secrets Manager, auto-rotation, never hardcode |
| Access control | Per-service credentials, least privilege grants |
| Monitoring | Connection logging, pgAudit, anomaly alerting |
Audit your database security at codelit.io — 413 architecture articles and growing. Generate architecture diagrams, run security checks, and visualize your infrastructure.
Try it on Codelit
AI Architecture Review
Get an AI audit covering security gaps, bottlenecks, and scaling risks
Related articles
Try these templates
Authentication & Authorization System
OAuth2/OIDC-based auth with SSO, MFA, session management, and role-based access control.
8 componentsHeadless CMS Platform
Headless content management with structured content, media pipeline, API-first delivery, and editorial workflows.
8 componentsProject Management Platform
Jira/Linear-like tool with issues, sprints, boards, workflows, and real-time collaboration.
8 componentsBuild this architecture
Generate an interactive architecture for Database Connection String Security in seconds.
Try it in Codelit →
Comments