Skip to content

Secrets Management


Secrets Management Overview

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

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 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

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

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

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

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

Secrets Management 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

  1. Why not use environment variables for secrets?
  2. Visible in process listings
  3. Inherited by child processes
  4. Difficult to rotate
  5. May be logged accidentally

  6. How to handle secret rotation with zero downtime?

  7. Dual/versioned secrets approach
  8. Both old and new valid during transition
  9. Consumers refresh, then old key revoked

  10. Vault vs AWS Secrets Manager?

  11. Vault: Multi-cloud, dynamic secrets, more features
  12. Secrets Manager: AWS-native, simpler, RDS integration

  13. How to prevent secrets in git commits?

  14. Pre-commit hooks
  15. Git-secrets tool
  16. GitHub secret scanning
  17. .gitignore patterns

  18. What are dynamic secrets?

  19. Generated on-demand with short TTL
  20. Unique per consumer/request
  21. Auto-expire, no rotation needed
  22. Example: Vault database credentials

  • *