Secrets Management¶
Secrets Management Overview¶
Secrets Management Solutions¶
| Solution | Best For |
|---|---|
| HashiCorp Vault | Multi-cloud, dynamic secrets, encryption as a service |
| AWS Secrets Manager | AWS-native apps, RDS integration |
| AWS Parameter Store | Configuration + simple secrets (cheaper) |
| Azure Key Vault | Azure-native apps |
| GCP Secret Manager | GCP-native apps |
| Kubernetes Secrets | K8s-native, limited features |
| CyberArk | Enterprise, compliance-heavy |
| 1Password/Bitwarden | Team secrets, CLI tools |
HashiCorp Vault¶
Vault CLI Examples¶
# Login
vault login -method=token token=s.xxxxxxx
# KV Secrets Engine (v2)
# Write secret
vault kv put secret/myapp/config \
db_user="admin" \
db_pass="secret123"
# Read secret
vault kv get secret/myapp/config
vault kv get -field=db_pass secret/myapp/config
# List secrets
vault kv list secret/myapp/
# Delete secret
vault kv delete secret/myapp/config
# Dynamic Secrets (Database)
# Configure database connection
vault write database/config/my-postgres \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/mydb" \
allowed_roles="my-role" \
username="vault_admin" \
password="admin_password"
# Create role
vault write database/roles/my-role \
db_name=my-postgres \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';" \
default_ttl="1h" \
max_ttl="24h"
# Get dynamic credentials
vault read database/creds/my-role
# Returns: username=v-token-my-role-xxx, password=yyy (auto-generated)
Vault with Spring Boot¶
// application.yml
spring:
cloud:
vault:
uri: https://vault.example.com:8200
authentication: APPROLE
app-role:
role-id: ${VAULT_ROLE_ID}
secret-id: ${VAULT_SECRET_ID}
kv:
enabled: true
backend: secret
default-context: myapp
// bootstrap.yml
spring:
application:
name: myapp
profiles:
active: production
cloud:
vault:
kv:
application-name: myapp
// Usage - secrets automatically injected
@Value("${db.username}")
private String dbUsername;
@Value("${db.password}")
private String dbPassword;
// Programmatic access
@Service
public class VaultService {
private final VaultTemplate vaultTemplate;
public VaultService(VaultTemplate vaultTemplate) {
this.vaultTemplate = vaultTemplate;
}
public String getSecret(String path, String key) {
VaultResponse response = vaultTemplate.read("secret/data/" + path);
if (response != null && response.getData() != null) {
Map<String, Object> data = (Map<String, Object>) response.getData().get("data");
return (String) data.get(key);
}
return null;
}
public void writeSecret(String path, Map<String, String> secrets) {
vaultTemplate.write("secret/data/" + path, Map.of("data", secrets));
}
}
AWS Secrets Manager¶
AWS CLI Examples¶
# Create secret
aws secretsmanager create-secret \
--name myapp/production/db \
--secret-string '{"username":"admin","password":"secret123"}'
# Get secret
aws secretsmanager get-secret-value \
--secret-id myapp/production/db \
--query SecretString \
--output text
# Update secret
aws secretsmanager update-secret \
--secret-id myapp/production/db \
--secret-string '{"username":"admin","password":"new_password"}'
# Rotate secret
aws secretsmanager rotate-secret \
--secret-id myapp/production/db \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789:function:rotator
# Delete secret
aws secretsmanager delete-secret \
--secret-id myapp/production/db \
--recovery-window-in-days 7
Java SDK¶
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.*;
@Service
public class AwsSecretsService {
private final SecretsManagerClient client;
private final ObjectMapper objectMapper;
public AwsSecretsService() {
this.client = SecretsManagerClient.builder()
.region(Region.US_EAST_1)
.build();
this.objectMapper = new ObjectMapper();
}
public DatabaseCredentials getDatabaseCredentials(String secretId) {
GetSecretValueRequest request = GetSecretValueRequest.builder()
.secretId(secretId)
.build();
GetSecretValueResponse response = client.getSecretValue(request);
String secretString = response.secretString();
return objectMapper.readValue(secretString, DatabaseCredentials.class);
}
// With caching to reduce API calls
private final LoadingCache<String, String> secretCache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(new CacheLoader<String, String>() {
@Override
public String load(String secretId) {
return fetchSecret(secretId);
}
});
public String getSecret(String secretId) {
return secretCache.getUnchecked(secretId);
}
}
@Data
public class DatabaseCredentials {
private String username;
private String password;
private String host;
private int port;
private String dbname;
}
Secret Rotation Lambda¶
# Lambda function for automatic rotation
import boto3
import json
import string
import secrets
def lambda_handler(event, context):
secret_id = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
client = boto3.client('secretsmanager')
if step == "createSecret":
create_secret(client, secret_id, token)
elif step == "setSecret":
set_secret(client, secret_id, token)
elif step == "testSecret":
test_secret(client, secret_id, token)
elif step == "finishSecret":
finish_secret(client, secret_id, token)
def create_secret(client, secret_id, token):
# Generate new password
alphabet = string.ascii_letters + string.digits + "!@#$%^&*()"
new_password = ''.join(secrets.choice(alphabet) for _ in range(32))
# Get current secret
current = client.get_secret_value(SecretId=secret_id)
secret_dict = json.loads(current['SecretString'])
# Update password
secret_dict['password'] = new_password
# Store as pending
client.put_secret_value(
SecretId=secret_id,
ClientRequestToken=token,
SecretString=json.dumps(secret_dict),
VersionStages=['AWSPENDING']
)
def set_secret(client, secret_id, token):
# Update the actual database password
pending = client.get_secret_value(
SecretId=secret_id,
VersionStage='AWSPENDING'
)
secret_dict = json.loads(pending['SecretString'])
# Connect to database and change password
# ... database-specific code ...
def test_secret(client, secret_id, token):
# Verify new credentials work
pending = client.get_secret_value(
SecretId=secret_id,
VersionStage='AWSPENDING'
)
# Test connection with new credentials
def finish_secret(client, secret_id, token):
# Mark new version as current
client.update_secret_version_stage(
SecretId=secret_id,
VersionStage='AWSCURRENT',
MoveToVersionId=token,
RemoveFromVersionId=get_current_version(client, secret_id)
)
AWS Parameter Store¶
# Create parameter
aws ssm put-parameter \
--name "/production/myapp/db/password" \
--value "secret123" \
--type "SecureString" \
--key-id "alias/myapp-key"
# Get parameter
aws ssm get-parameter \
--name "/production/myapp/db/password" \
--with-decryption
# Get multiple parameters by path
aws ssm get-parameters-by-path \
--path "/production/myapp" \
--recursive \
--with-decryption
# Delete parameter
aws ssm delete-parameter \
--name "/production/myapp/db/password"
Kubernetes Secrets¶
Kubernetes Secrets YAML¶
# Create secret
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
username: YWRtaW4= # base64 encoded
password: c2VjcmV0MTIz # base64 encoded
---
# Use in Pod
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: app
image: myapp:latest
# As environment variables
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
# Or as volume mount
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: db-credentials
External Secrets Operator¶
# ExternalSecret - syncs from AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: production/myapp/db
property: username
- secretKey: password
remoteRef:
key: production/myapp/db
property: password
---
# ClusterSecretStore
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: external-secrets
Environment Variables¶
// Fetching secrets at startup (better pattern)
@Configuration
public class SecretsConfig {
@Bean
public DataSource dataSource(SecretsService secretsService) {
DatabaseCredentials creds = secretsService.getDatabaseCredentials("myapp/db");
HikariConfig config = new HikariConfig();
config.setJdbcUrl(creds.getUrl());
config.setUsername(creds.getUsername());
config.setPassword(creds.getPassword());
return new HikariDataSource(config);
}
}
Secret Rotation¶
Rotation Implementation¶
@Service
public class SecretRotationService {
private final VaultTemplate vault;
private final ApplicationEventPublisher eventPublisher;
// Dual-secret rotation
public void rotateApiKey(String serviceName) {
// 1. Generate new key
String newKey = generateSecureKey();
// 2. Get current key
String currentKey = vault.read("secret/data/" + serviceName + "/api-key")
.getData().get("key").toString();
// 3. Store both keys
Map<String, Object> secrets = new HashMap<>();
secrets.put("current_key", newKey);
secrets.put("previous_key", currentKey);
vault.write("secret/data/" + serviceName + "/api-key",
Map.of("data", secrets));
// 4. Notify consumers to refresh
eventPublisher.publishEvent(new SecretRotatedEvent(serviceName));
// 5. Schedule removal of old key
scheduler.schedule(() -> {
Map<String, Object> finalSecrets = Map.of("current_key", newKey);
vault.write("secret/data/" + serviceName + "/api-key",
Map.of("data", finalSecrets));
}, Duration.ofHours(24));
}
}
// Consumer that handles rotation
@Component
public class ApiKeyHolder {
private volatile String apiKey;
private final SecretsService secretsService;
@PostConstruct
public void init() {
refreshKey();
}
@EventListener(SecretRotatedEvent.class)
public void onSecretRotated(SecretRotatedEvent event) {
if ("my-service".equals(event.getServiceName())) {
refreshKey();
}
}
@Scheduled(fixedRate = 300000) // Every 5 minutes
public void refreshKey() {
this.apiKey = secretsService.getSecret("my-service/api-key", "current_key");
}
public String getApiKey() {
return apiKey;
}
}
Best Practices¶
Pre-commit Hook for Secrets¶
#!/bin/bash
# .git/hooks/pre-commit
# Patterns that might indicate secrets
PATTERNS=(
'password\s*=\s*["\x27][^"\x27]+'
'api_key\s*=\s*["\x27][^"\x27]+'
'secret\s*=\s*["\x27][^"\x27]+'
'AKIA[0-9A-Z]{16}' # AWS Access Key
'-----BEGIN RSA PRIVATE KEY-----'
'-----BEGIN PRIVATE KEY-----'
)
for pattern in "${PATTERNS[@]}"; do
if git diff --cached --name-only -z | xargs -0 grep -l -E "$pattern" 2>/dev/null; then
echo "ERROR: Potential secret detected in commit!"
echo "Pattern: $pattern"
exit 1
fi
done
exit 0
Common Interview Questions¶
- Why not use environment variables for secrets?
- Visible in process listings
- Inherited by child processes
- Difficult to rotate
-
May be logged accidentally
-
How to handle secret rotation with zero downtime?
- Dual/versioned secrets approach
- Both old and new valid during transition
-
Consumers refresh, then old key revoked
-
Vault vs AWS Secrets Manager?
- Vault: Multi-cloud, dynamic secrets, more features
-
Secrets Manager: AWS-native, simpler, RDS integration
-
How to prevent secrets in git commits?
- Pre-commit hooks
- Git-secrets tool
- GitHub secret scanning
-
.gitignore patterns
-
What are dynamic secrets?
- Generated on-demand with short TTL
- Unique per consumer/request
- Auto-expire, no rotation needed
- Example: Vault database credentials
- *