Secrets Management
Secrets are sensitive pieces of information that provide access to systems, services, and data. Poor secrets management is a primary attack vector for security breaches. This guide provides comprehensive strategies for handling secrets throughout the software development lifecycle.
What Are Secrets?
Secrets are any credentials, keys, or sensitive configuration values that grant access or contain confidential information:
- API Keys and Tokens: Third-party service credentials (Stripe, SendGrid, AWS access keys)
- Database Credentials: Usernames, passwords, connection strings for databases
- Encryption Keys: Keys used to encrypt/decrypt data at rest or in transit
- Certificates and Private Keys: TLS/SSL certificates, code signing certificates, SSH keys
- OAuth Client Secrets: Application secrets for OAuth flows
- Signing Keys: JWT signing keys, HMAC secrets for webhook validation
- Service Account Credentials: Credentials for service-to-service authentication
Unlike configuration values (timeouts, feature flags, URLs), secrets must never be exposed in code, logs, or error messages. The fundamental principle is that secrets should be treated as high-value assets requiring protection at every stage of their lifecycle.
Secrets Management Principles
Never Commit Secrets to Version Control
Version control systems like Git maintain complete history. Once a secret is committed, it remains in the repository history even if removed in a later commit. This creates permanent security exposure as anyone with repository access (including historical access) can retrieve the secret.
# Bad: Secrets in code
const API_KEY = "sk_live_abc123xyz789"; // Never do this!
# Good: Secrets from environment
const API_KEY = process.env.STRIPE_API_KEY;
Even in private repositories, secrets in version control are vulnerable because:
- Former employees may retain repository copies
- Third-party tools and CI/CD systems may cache the repository
- Repository backups may be stored in less secure locations
- Accidental public exposure (making repo public, forking to public space)
Principle of Least Privilege
Grant secrets only the minimum permissions necessary for their intended use. For example, a database credential used by an application for read-only queries should only have SELECT permissions, not DELETE or DROP.
This limits the blast radius of credential compromise. If an application-level secret is exposed, the attacker's access is constrained by the secret's limited permissions.
Separation of Environments
Secrets must be completely isolated between environments. A developer working in the development environment should never have access to production secrets. This separation is enforced through:
- Different secrets management namespaces (e.g., separate Vault paths for dev/staging/prod)
- Environment-specific service accounts and credentials
- Access control policies tied to environment boundaries
The risk of mixing environments includes accidental production data exposure in development, testing with production credentials, and privilege escalation paths.
Secret Rotation
Secrets should be rotated regularly and automatically when possible. Rotation limits the window of opportunity for compromised secrets. Consider that secrets can be compromised through:
- Log files where they were accidentally printed
- Memory dumps or heap snapshots
- Network interception
- Developer workstations
- Third-party service breaches
Regular rotation (monthly, quarterly, or after personnel changes) reduces the likelihood that a historical compromise remains exploitable.
Secret Types and Handling
API Keys and Service Credentials
API keys are typically long-lived credentials that authenticate your application to third-party services. They are often passed in HTTP headers or query parameters.
Best practices:
- Store API keys in secrets management systems, not environment variables on developer machines
- Use separate API keys per environment (dev keys for development, production keys for production)
- Prefer API keys with restricted scopes when services support granular permissions
- Monitor API key usage for anomalies (unusual request patterns, geographic location changes)
- Implement key rotation without service disruption by supporting multiple concurrent keys during transition periods
Example of secure API key usage in a Spring Boot application:
@Configuration
public class ExternalServiceConfig {
// Injected from secrets management system (Vault, AWS Secrets Manager)
@Value("${external.service.api.key}")
private String apiKey;
@Bean
public ExternalServiceClient externalServiceClient() {
return ExternalServiceClient.builder()
.apiKey(apiKey)
.build();
}
}
The actual secret value is never in code. Instead, it's injected at runtime from a secrets backend that the application authenticates to using IAM roles or service accounts.
Database Credentials
Database credentials grant access to potentially sensitive data and must be carefully protected.
Connection string security:
// Bad: Hardcoded credentials
const connectionString = "postgres://admin:[email protected]:5432/mydb";
// Good: Credentials from secrets manager
const connectionString = await secretsManager.getSecret("db-connection-string");
const pool = new Pool({ connectionString });
For databases, consider these practices:
- Use IAM database authentication when available (AWS RDS, Azure SQL supports authentication via IAM roles instead of passwords)
- Create application-specific database users with minimal required privileges (SELECT, INSERT on specific tables only)
- Avoid shared credentials across applications; each service should have its own database user
- Use connection pooling to minimize credential exposure in connection strings
- Encrypt database connections with TLS to prevent credential interception
Short-lived credentials: Many secrets management systems can generate short-lived database credentials that automatically expire:
This pattern eliminates the need for manual password rotation and limits the time window for credential compromise.
Encryption Keys
Encryption keys protect data at rest and in transit. They require special handling because compromise of encryption keys exposes all data protected by those keys.
Key types:
- Data Encryption Keys (DEK): Encrypt actual data; typically symmetric keys (AES-256)
- Key Encryption Keys (KEK): Encrypt DEKs; enables key rotation without re-encrypting all data
- Master Keys: Root keys stored in Hardware Security Modules (HSMs) or managed KMS services
Envelope encryption pattern:
This hierarchy enables key rotation at the KEK level without re-encrypting all user data. Only the DEKs need to be re-encrypted with a new KEK.
Example with AWS KMS:
@Service
public class EncryptionService {
private final AWSKMS kmsClient;
@Value("${kms.key.id}")
private String kmsKeyId;
public byte[] encrypt(byte[] plaintext) {
EncryptRequest request = EncryptRequest.builder()
.keyId(kmsKeyId)
.plaintext(SdkBytes.fromByteArray(plaintext))
.build();
EncryptResponse response = kmsClient.encrypt(request);
return response.ciphertextBlob().asByteArray();
}
public byte[] decrypt(byte[] ciphertext) {
DecryptRequest request = DecryptRequest.builder()
.ciphertextBlob(SdkBytes.fromByteArray(ciphertext))
.build();
DecryptResponse response = kmsClient.decrypt(request);
return response.plaintext().asByteArray();
}
}
The KMS key itself never leaves the KMS service. The application sends data to be encrypted/decrypted, and KMS performs the operation using the key stored securely in its HSM.
Certificates and Private Keys
TLS certificates and their private keys secure communication channels. Private keys must be protected with the same rigor as passwords.
Certificate management:
- Store private keys in secrets management systems, never in application code repositories
- Use certificate automation tools (Let's Encrypt, AWS Certificate Manager) to avoid manual certificate handling
- Set up certificate expiration monitoring (alert 30 days before expiration)
- Implement zero-downtime certificate rotation by supporting multiple certificates during transition
// Loading TLS certificate from secrets manager
const tlsOptions = {
key: await secretsManager.getSecret('tls-private-key'),
cert: await secretsManager.getSecret('tls-certificate'),
ca: await secretsManager.getSecret('tls-ca-bundle'),
};
const server = https.createServer(tlsOptions, app);
For mobile applications code signing certificates, store them in CI/CD secrets management (GitLab CI/CD variables, encrypted files) and inject them only during build time, never committing them to repositories.
JWT Signing Keys
JSON Web Tokens (JWTs) use signing keys to ensure token authenticity. Compromise of signing keys allows attackers to forge tokens and impersonate any user.
@Configuration
public class JwtConfig {
@Value("${jwt.signing.key}")
private String signingKey;
@Bean
public JwtDecoder jwtDecoder() {
// Use RS256 (asymmetric) over HS256 (symmetric) when possible
// Public key can be shared; private key remains secret
return NimbusJwtDecoder.withPublicKey(loadPublicKey()).build();
}
private RSAPublicKey loadPublicKey() {
// Load from secrets manager, not filesystem
String publicKeyPem = secretsManager.getSecret("jwt-public-key");
// Parse and return public key
}
}
Key rotation for JWTs: When rotating JWT signing keys, support a grace period where both old and new keys are valid for verification. This prevents immediate invalidation of existing tokens during deployment.
Secrets Management Tools
HashiCorp Vault
Vault is a centralized secrets management platform that provides secure storage, dynamic secrets generation, and comprehensive audit logging.
Key features:
- Dynamic secrets: Generate short-lived credentials on-demand for databases, cloud providers, SSH
- Encryption as a Service: Encrypt/decrypt data without exposing keys to applications
- Leasing and renewal: All secrets have TTLs and can be renewed or revoked
- Audit logging: Complete audit trail of all secret access
Architecture:
Example: Application authentication and secret retrieval:
@Configuration
public class VaultConfig {
@Value("${vault.uri}")
private String vaultUri;
@Value("${vault.role}")
private String appRole;
@Bean
public VaultTemplate vaultTemplate() {
// Authenticate using AppRole (role ID + secret ID)
VaultEndpoint endpoint = VaultEndpoint.from(URI.create(vaultUri));
AppRoleAuthentication auth = new AppRoleAuthentication(
AppRoleAuthenticationOptions.builder()
.roleId(RoleId.provided(System.getenv("VAULT_ROLE_ID")))
.secretId(SecretId.provided(System.getenv("VAULT_SECRET_ID")))
.build()
);
return new VaultTemplate(endpoint, auth);
}
}
@Service
public class SecretService {
@Autowired
private VaultTemplate vaultTemplate;
public String getDatabasePassword() {
// Read secret from Vault
VaultResponse response = vaultTemplate.read("secret/data/myapp/database");
return response.getRequiredData().get("password").toString();
}
public DatabaseCredentials getDynamicDbCredentials() {
// Request dynamic credentials (valid for configured TTL)
VaultResponse response = vaultTemplate.read("database/creds/myapp-role");
return DatabaseCredentials.builder()
.username(response.getData().get("username").toString())
.password(response.getData().get("password").toString())
.build();
}
}
Vault's dynamic secrets are particularly powerful. Instead of sharing a long-lived database password across all application instances, each instance requests its own temporary credentials that expire after a defined period (e.g., 1 hour). If an instance is compromised, the attacker gains access for only the remaining TTL.
Cloud Provider Secrets Managers
AWS Secrets Manager
AWS Secrets Manager integrates tightly with AWS services and IAM, enabling applications running on EC2, ECS, or Lambda to retrieve secrets without embedding credentials.
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
const client = new SecretsManagerClient({ region: "us-east-1" });
async function getSecret(secretName: string): Promise<string> {
const command = new GetSecretValueCommand({ SecretId: secretName });
const response = await client.send(command);
return response.SecretString || "";
}
// Usage
const dbPassword = await getSecret("prod/database/password");
The application authenticates to AWS Secrets Manager using an IAM role assigned to the EC2 instance or ECS task. No credentials are embedded in code or environment variables.
Automatic rotation: AWS Secrets Manager supports automatic rotation of secrets using Lambda functions:
Azure Key Vault
Azure Key Vault provides secrets, keys, and certificate management integrated with Azure Active Directory for authentication.
@Configuration
public class AzureKeyVaultConfig {
@Bean
public SecretClient secretClient() {
String keyVaultUrl = "https://mykeyvault.vault.azure.net/";
// Authenticate using Managed Identity (no credentials needed)
DefaultAzureCredentialBuilder credentialBuilder = new DefaultAzureCredentialBuilder();
return new SecretClientBuilder()
.vaultUrl(keyVaultUrl)
.credential(credentialBuilder.build())
.buildClient();
}
}
@Service
public class SecretService {
@Autowired
private SecretClient secretClient;
public String getSecret(String secretName) {
KeyVaultSecret secret = secretClient.getSecret(secretName);
return secret.getValue();
}
}
Google Secret Manager
Google Secret Manager integrates with Google Cloud IAM for access control and provides versioning of secrets.
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
const client = new SecretManagerServiceClient();
async function accessSecret(secretName: string): Promise<string> {
const name = `projects/my-project/secrets/${secretName}/versions/latest`;
const [version] = await client.accessSecretVersion({ name });
return version.payload?.data?.toString() || '';
}
Choosing a Secrets Management Tool
Selection criteria:
| Factor | HashiCorp Vault | AWS Secrets Manager | Azure Key Vault | Google Secret Manager |
|---|---|---|---|---|
| Multi-cloud | ✓ Yes | AWS only | Azure only | GCP only |
| Dynamic secrets | ✓ Yes | Limited | No | No |
| Audit logging | Comprehensive | CloudTrail | Azure Monitor | Cloud Audit Logs |
| Cost | Self-hosted or managed | Per secret + API calls | Per operation | Per operation |
| Complexity | Higher learning curve | Simple | Simple | Simple |
Use Vault when you need multi-cloud support, dynamic secrets generation, or encryption-as-a-service capabilities. Use cloud provider secrets managers when you're fully committed to a single cloud platform and prefer managed services with simpler operational overhead.
Secret Rotation Strategies
Secret rotation reduces the risk of credential compromise by limiting the validity period of secrets. Rotation can be manual, scheduled, or event-driven (after a security incident).
Automated Rotation
Automated rotation eliminates human error and ensures secrets are rotated on a consistent schedule.
Database password rotation:
During rotation, maintain a grace period where both old and new credentials are valid. This prevents immediate application failures if some instances haven't yet updated to the new credentials.
Rotation implementation example:
@Service
public class DatabaseCredentialRotation {
@Autowired
private SecretsManager secretsManager;
@Autowired
private DatabaseService databaseService;
public void rotateCredentials(String secretId) {
// Step 1: Generate new password
String newPassword = generateSecurePassword();
// Step 2: Update database with new password
databaseService.updateUserPassword("app_user", newPassword);
// Step 3: Create new version in secrets manager
secretsManager.putSecretValue(PutSecretValueRequest.builder()
.secretId(secretId)
.secretString(buildSecretJson("app_user", newPassword))
.versionStages("AWSPENDING") // Not yet current
.build());
// Step 4: Test new credentials
boolean testSuccessful = testDatabaseConnection("app_user", newPassword);
if (testSuccessful) {
// Step 5: Promote new version to current
secretsManager.updateSecretVersionStage(UpdateSecretVersionStageRequest.builder()
.secretId(secretId)
.versionStage("AWSCURRENT")
.moveToVersionId(getCurrentVersionId(secretId))
.build());
// Step 6: Schedule old credential cleanup (after grace period)
scheduleOldCredentialCleanup(secretId, Duration.ofHours(24));
} else {
// Rollback: keep old credentials
logger.error("Credential rotation failed, keeping old credentials");
}
}
private String generateSecurePassword() {
// Use cryptographically secure random generator
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[32];
random.nextBytes(bytes);
return Base64.getEncoder().encodeToString(bytes);
}
}
Zero-Downtime Rotation
To achieve zero-downtime rotation, applications must support using multiple credentials simultaneously during the transition period.
Application-side support:
class DatabaseConnectionPool {
private primaryCredentials: Credentials;
private fallbackCredentials?: Credentials;
async connect(): Promise<Connection> {
try {
// Try primary credentials first
return await this.connectWithCredentials(this.primaryCredentials);
} catch (error) {
if (this.fallbackCredentials) {
// Fall back to old credentials during rotation
return await this.connectWithCredentials(this.fallbackCredentials);
}
throw error;
}
}
async refreshCredentials() {
const newCredentials = await secretsManager.getSecret('db-credentials');
// Keep old credentials as fallback during transition
this.fallbackCredentials = this.primaryCredentials;
this.primaryCredentials = newCredentials;
// Remove fallback after grace period
setTimeout(() => {
this.fallbackCredentials = undefined;
}, 60000); // 1 minute grace period
}
}
Rotation Frequency
Balance security and operational overhead when determining rotation frequency:
- Critical production secrets: Quarterly or after any security incident
- Database credentials: Monthly if manual, weekly if automated with dynamic secrets
- API keys: Rotate when service supports it without disruption (on-demand)
- Encryption keys: Annually for KEKs; DEKs can be rotated more frequently
- TLS certificates: Annually or use Let's Encrypt with automated 90-day rotation
Environment-Specific Secrets
Secrets must be completely isolated between environments to prevent accidental production access from non-production systems.
Access control:
- Developers have read access to
dev/namespace only - CI/CD pipelines for staging have access to
staging/namespace - Production deployments use service accounts with access restricted to
prod/namespace
Configuration by environment:
# application-dev.yml
vault:
uri: https://vault.example.com
namespace: dev/myapp
role: myapp-dev
# application-prod.yml
vault:
uri: https://vault.example.com
namespace: prod/myapp
role: myapp-prod
The application code remains identical across environments. Only the configuration file changes, pointing to the appropriate secrets namespace.
Local Development Secrets
Local development requires secrets (database connections, third-party API keys for testing) but must not use production secrets.
.env Files
For local development, use .env files with development-only credentials. These files must never be committed to version control.
# .env (gitignored)
DATABASE_URL=postgres://localhost:5432/myapp_dev
STRIPE_API_KEY=sk_test_abc123 # Test key, not production
JWT_SIGNING_KEY=dev-only-key-not-secure
.gitignore configuration:
.env
.env.local
*.env
Provide a template .env.example file committed to the repository showing required variables without actual values:
# .env.example (committed to repo)
DATABASE_URL=postgres://localhost:5432/myapp_dev
STRIPE_API_KEY=sk_test_your_test_key_here
JWT_SIGNING_KEY=generate-a-random-key
New developers copy .env.example to .env and fill in their own development credentials.
git-secrets and Pre-commit Hooks
Prevent accidental commits of secrets using git-secrets, a tool that scans commits for patterns matching secrets.
Installation:
# Install git-secrets
brew install git-secrets # macOS
# or
apt-get install git-secrets # Linux
# Set up in repository
git secrets --install
git secrets --register-aws # AWS patterns
git secrets --add 'sk_live_[a-zA-Z0-9]+' # Stripe live keys
git secrets --add 'password\s*=\s*.+' # Password assignments
If a commit matches a registered pattern, git-secrets blocks the commit:
$ git commit -m "Add payment processing"
[ERROR] Matched pattern: sk_live_abc123xyz
Pre-commit hooks with Husky: For JavaScript/TypeScript projects, integrate secret scanning with Husky:
{
"husky": {
"hooks": {
"pre-commit": "npm run check-secrets && lint-staged"
}
},
"scripts": {
"check-secrets": "git secrets --scan"
}
}
Developer Credentials vs. Shared Secrets
Avoid sharing development secrets across the team. Instead, provide each developer with personal credentials:
- Development databases: Each developer runs local PostgreSQL/MySQL or uses Docker containers
- Third-party services: Each developer creates personal test accounts (Stripe test keys, SendGrid sandbox)
- OAuth applications: Register separate development OAuth applications for local testing
This approach improves security (compromise of one developer's machine doesn't affect others) and debugging (each developer's logs are isolated).
CI/CD Pipeline Secrets
CI/CD pipelines require secrets to deploy applications, run tests against external services, and push artifacts to registries.
GitLab CI/CD Variables
GitLab provides secure variable storage for CI/CD pipelines with protection and masking options.
Setting CI/CD variables:
- Navigate to Settings > CI/CD > Variables
- Add variable with:
- Key: Variable name (e.g.,
DATABASE_PASSWORD) - Value: Secret value
- Type: Variable or File (use File for certificates, keys)
- Protected: Only available on protected branches (main, production)
- Masked: Hidden in job logs
- Key: Variable name (e.g.,
# .gitlab-ci.yml
deploy:
stage: deploy
script:
- echo "Deploying with database password"
- ./deploy.sh
environment:
name: production
only:
- main
In the script, access variables via environment variables: $DATABASE_PASSWORD
Masked variables are automatically hidden in logs:
$ echo $DATABASE_PASSWORD
[MASKED]
Encrypted Secrets in Git
For secrets that must be version-controlled (e.g., shared team secrets, Kubernetes secrets), encrypt them before committing.
Using git-crypt:
# Install git-crypt
brew install git-crypt
# Initialize in repository
git-crypt init
# Specify files to encrypt in .gitattributes
echo "secrets/** filter=git-crypt diff=git-crypt" >> .gitattributes
# Add collaborators (using GPG keys)
git-crypt add-gpg-user [email protected]
Files matching the pattern in .gitattributes are encrypted when committed and decrypted when checked out by authorized users.
Alternative: Mozilla SOPS
SOPS (Secrets OPerationS) encrypts files using AWS KMS, GCP KMS, Azure Key Vault, or GPG keys.
# secrets.yaml (encrypted with SOPS)
database:
host: db.example.com
username: admin
password: ENC[AES256_GCM,data:AbCdEf==,iv:123456,tag:78910,type:str]
Decrypt during CI/CD:
# .gitlab-ci.yml
deploy:
before_script:
- sops -d secrets.yaml > secrets.decrypted.yaml
script:
- ./deploy.sh --secrets secrets.decrypted.yaml
after_script:
- rm secrets.decrypted.yaml # Clean up
Limiting Secret Exposure in Logs
Even masked variables can leak through indirect logging. Follow these practices:
- Never echo or print secrets in scripts (use masked variables sparingly)
- Avoid passing secrets as command-line arguments (visible in process lists)
- Use temporary files with restricted permissions for secret data
- Configure log aggregation to filter secret patterns
# Bad: Secret visible in command line
docker login -u user -p $PASSWORD registry.example.com
# Good: Read password from stdin
echo $PASSWORD | docker login -u user --password-stdin registry.example.com
Secret Scanning in Repositories
Detect secrets accidentally committed to repositories using automated scanning tools.
Tools
GitLeaks: Scans Git repositories for secrets using regex patterns and entropy detection.
# Install gitleaks
brew install gitleaks
# Scan repository
gitleaks detect --source . --verbose
# Scan repository history
gitleaks detect --source . --log-opts="--all"
TruffleHog: Uses entropy analysis and regex patterns to find secrets in commit history.
# Install trufflehog
pip install trufflehog
# Scan repository
trufflehog git https://github.com/user/repo --only-verified
GitHub Secret Scanning: GitHub automatically scans public repositories and provides secret scanning for private repositories (requires GitHub Advanced Security).
When a secret is detected, GitHub:
- Notifies repository administrators
- Notifies the service provider (e.g., AWS, Stripe) to revoke the token
- Provides remediation guidance
Integration in CI/CD
Add secret scanning to your CI/CD pipeline to catch secrets before they reach production:
# .gitlab-ci.yml
secret-scan:
stage: test
image: zricethezav/gitleaks:latest
script:
- gitleaks detect --source . --verbose --redact
allow_failure: false # Fail build if secrets found
Responding to Secret Leaks
If a secret is discovered in version control:
- Immediately revoke the secret (delete API key, reset password, revoke certificate)
- Generate new secret and update applications
- Remove secret from Git history (using
git filter-branchor BFG Repo-Cleaner) - Audit access logs to determine if the secret was used by unauthorized parties
- Notify stakeholders if the exposure could have resulted in a breach
- Update detection rules to prevent similar secrets from being committed in the future
# Remove secret from Git history using BFG Repo-Cleaner
java -jar bfg.jar --replace-text passwords.txt my-repo.git
cd my-repo
git reflog expire --expire=now --all && git gc --prune=now --aggressive
Force push the cleaned history:
git push --force --all
Important: Even after removal, the secret is compromised. Revocation is mandatory.
Encryption at Rest and in Transit
Secrets must be encrypted both when stored (at rest) and when transmitted (in transit).
Encryption at Rest
All secrets management systems encrypt secrets at rest using strong encryption algorithms (AES-256). However, encryption is only as strong as the key management:
- Master keys should be stored in Hardware Security Modules (HSMs) or managed KMS services
- Key rotation must be performed regularly for master keys
- Access controls on master keys should be tightly restricted (minimal personnel, MFA required)
For application-level encryption:
@Service
public class LocalSecretStorage {
private final SecretKey encryptionKey;
public LocalSecretStorage() {
// Load encryption key from KMS, not local filesystem
this.encryptionKey = loadKeyFromKMS();
}
public String encryptSecret(String plaintext) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, generateIV());
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, spec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(ciphertext);
}
public String decryptSecret(String ciphertext) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Extract IV and decrypt
// ...
}
private byte[] generateIV() {
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
return iv;
}
}
Never use ECB mode for encryption (it's insecure). Use authenticated encryption modes like GCM that provide both confidentiality and integrity.
Encryption in Transit
All communication involving secrets must use TLS (HTTPS, database TLS connections, gRPC with TLS).
Database connections:
# application.yml
spring:
datasource:
url: jdbc:postgresql://db.example.com:5432/mydb?sslmode=require
# sslmode=require enforces TLS connection
API calls with secrets:
const response = await fetch('https://api.example.com/payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`, // Over HTTPS only
},
body: JSON.stringify(paymentData),
});
Verify TLS certificate validity in production environments to prevent man-in-the-middle attacks:
// Avoid disabling certificate validation (common mistake in development)
// Bad:
// SSLContext sc = SSLContext.getInstance("TLS");
// sc.init(null, trustAllCerts, new SecureRandom());
// Good: Use default trust manager that validates certificates
HttpClient client = HttpClient.newBuilder()
.sslContext(SSLContext.getDefault())
.build();
Best Practices Summary
- Never commit secrets to version control – Use environment variables, secrets management systems, or encrypted files
- Use dedicated secrets management tools – Vault, AWS Secrets Manager, Azure Key Vault for centralized secret storage
- Implement secret rotation – Automate rotation where possible; maintain grace periods during rotation
- Separate secrets by environment – Complete isolation between dev, staging, and production
- Encrypt secrets at rest and in transit – Use strong encryption (AES-256-GCM); enforce TLS for all communications
- Implement secret scanning – Use GitLeaks, TruffleHog, or provider scanning in CI/CD pipelines
- Apply principle of least privilege – Grant secrets minimum required permissions; use short-lived credentials when possible
- Monitor and audit secret access – Enable comprehensive audit logging; alert on anomalous access patterns
- Respond rapidly to leaks – Have an incident response plan for secret exposure; revoke immediately
Related Documentation
- Security Best Practices – General application security guidelines
- API Security – Securing API keys and authentication tokens
- Spring Boot Security – Spring Security configuration for secret management
- GitLab CI/CD Pipelines – CI/CD variable management and deployment secrets
- Docker Security – Managing secrets in containerized applications
- Kubernetes Best Practices – Kubernetes secrets and external secrets operators