mTLS Authentication
Configure mutual TLS (client certificate) authentication for database connections.
Overview
mTLS provides:
- Strong authentication via X.509 certificates
- Encrypted connections (TLS)
- No password management required
- Certificate-based identity
Prerequisites
- TLS-enabled database server
- Client certificate and key
- CA certificate (for verification)
Basic Configuration
from horizon_epoch import Config
config = Config(
metadata_url="postgresql://localhost/horizon_epoch"
).add_postgres(
name="production",
host="db.example.com",
database="production",
username="epoch_user",
sslmode="verify-full",
ssl_cert="/path/to/client.crt",
ssl_key="/path/to/client.key",
ssl_rootcert="/path/to/ca.crt"
)
TLS Modes
| Mode | Server Cert | Client Cert | Description |
|---|---|---|---|
disable | No | No | Unencrypted connection |
require | No | No | Encrypted, no verification |
verify-ca | Yes | Optional | Verify server cert against CA |
verify-full | Yes | Optional | Verify cert + hostname |
For mTLS, use verify-full with client certificates.
Certificate Files
Client Certificate
PEM-encoded X.509 certificate:
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAJC1HiIAZAiU...
-----END CERTIFICATE-----
Client Key
PEM-encoded private key:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA7Zq7k...
-----END RSA PRIVATE KEY-----
Or encrypted key:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHzBJBgkqhkiG9w0BBQ0wPDA...
-----END ENCRYPTED PRIVATE KEY-----
CA Certificate
For verifying server certificate:
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvg...
-----END CERTIFICATE-----
Configuration File
# epoch.toml
[storage.postgres.production]
host = "db.example.com"
database = "production"
username = "epoch_user"
sslmode = "verify-full"
ssl_cert = "/etc/epoch/certs/client.crt"
ssl_key = "/etc/epoch/certs/client.key"
ssl_rootcert = "/etc/epoch/certs/ca.crt"
Using Vault PKI
Dynamically issue certificates from Vault:
config = Config(
vault_addr="https://vault.example.com:8200",
vault_token="${VAULT_TOKEN}"
).add_postgres(
name="production",
host="db.example.com",
database="production",
username="epoch_user",
sslmode="verify-full",
vault_pki_role="pki/issue/epoch-client",
ssl_rootcert="/etc/epoch/certs/ca.crt"
)
Certificates are automatically renewed before expiry.
PostgreSQL Server Setup
Enable SSL on PostgreSQL server:
# postgresql.conf
ssl = on
ssl_cert_file = '/var/lib/postgresql/server.crt'
ssl_key_file = '/var/lib/postgresql/server.key'
ssl_ca_file = '/var/lib/postgresql/ca.crt'
Configure client certificate authentication:
# pg_hba.conf
# TYPE DATABASE USER ADDRESS METHOD
hostssl all epoch_user 0.0.0.0/0 cert clientcert=verify-full
Create user with certificate CN:
CREATE USER epoch_user;
-- The CN in the client cert must match the username
Encrypted Private Keys
If your key is password-protected:
config.add_postgres(
name="production",
host="db.example.com",
ssl_key="/path/to/encrypted.key",
ssl_key_password="${SSL_KEY_PASSWORD}"
)
Certificate Rotation
Manual Rotation
- Generate new certificate
- Update configuration
- Restart/reload application
Automatic with Vault
Certificates are automatically rotated:
config = Config(
vault_pki_role="pki/issue/epoch-client",
cert_renewal_threshold=0.7 # Renew when 70% of TTL elapsed
)
Troubleshooting
Certificate Expired
Error: SSL error: certificate has expired
- Check certificate expiry:
openssl x509 -enddate -noout -in client.crt - Renew certificate
- If using Vault, check renewal is working
Certificate Verification Failed
Error: SSL error: certificate verify failed
- Verify CA certificate is correct
- Check certificate chain is complete
- Verify server hostname matches certificate
Permission Denied on Key File
Error: could not load private key file
- Check file permissions:
chmod 600 client.key - Verify file is readable by process user
Key Doesn’t Match Certificate
Error: key values mismatch
- Verify key matches certificate:
openssl x509 -noout -modulus -in client.crt | md5sum openssl rsa -noout -modulus -in client.key | md5sum
Wrong Password for Encrypted Key
Error: bad decrypt
- Verify password is correct
- Check key file format (PKCS#8 vs traditional)
Security Best Practices
- Protect private keys - Use encrypted keys, restrict file permissions
- Use short-lived certificates - Rotate frequently
- Verify server certificates - Always use
verify-full - Audit certificate usage - Log certificate fingerprints
- Use separate certificates - Don’t share between environments
CLI Usage
# Using environment variables
export EPOCH_SSL_CERT="/path/to/client.crt"
export EPOCH_SSL_KEY="/path/to/client.key"
export EPOCH_SSL_ROOTCERT="/path/to/ca.crt"
epoch init my-repo \
--metadata-url "postgresql://user@db.example.com/horizon_epoch?sslmode=verify-full"
Next Steps
- Vault Integration - Dynamic certificate issuance
- SSH Tunnels - Alternative secure connection
- Credential Providers - Overview of auth methods