Appearance
Learn how to securely store and manage sensitive credentials, API keys, and configuration using AWS Secrets Manager and other secret management solutions.
Overview
Proper secrets management is critical for security. Never store sensitive data in:
- Source code
- Configuration files committed to git
- Environment variables in plain text
- Container images
Instead, use dedicated secrets management services like:
- AWS Secrets Manager
- HashiCorp Vault
- Azure Key Vault
- Google Secret Manager
AWS Secrets Manager
Prerequisites
- AWS Account
- IAM user with Secrets Manager permissions
- AWS CLI configured
Installation
bash
# Install AWS SDK
npm install @aws-sdk/client-secrets-managerBasic Setup
typescript
// src/config/secrets.ts
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({
region: process.env.AWS_REGION || 'us-east-1',
});
export async function getSecret(secretName: string): Promise<any> {
try {
const command = new GetSecretValueCommand({
SecretId: secretName,
});
const response = await client.send(command);
if (response.SecretString) {
return JSON.parse(response.SecretString);
}
// Binary secret
if (response.SecretBinary) {
return Buffer.from(response.SecretBinary).toString('base64');
}
throw new Error('Secret not found');
} catch (error) {
console.error('Error retrieving secret:', error);
throw error;
}
}Creating Secrets
Via AWS Console
- Navigate to AWS Secrets Manager
- Click Store a new secret
- Select secret type (e.g., Other type of secret)
- Enter key-value pairs:json
{ "DATABASE_URL": "postgresql://user:pass@host:5432/db", "JWT_SECRET": "your-jwt-secret", "SMTP_PASSWORD": "your-smtp-password" } - Name the secret:
pocketbook/production/config - Configure rotation (optional)
- Review and store
Via AWS CLI
bash
# Create secret
aws secretsmanager create-secret \
--name pocketbook/production/config \
--secret-string '{
"DATABASE_URL": "postgresql://user:pass@host:5432/db",
"JWT_SECRET": "your-jwt-secret",
"SMTP_PASSWORD": "your-smtp-password"
}' \
--region us-east-1
# Update secret
aws secretsmanager update-secret \
--secret-id pocketbook/production/config \
--secret-string '{
"DATABASE_URL": "postgresql://user:newpass@host:5432/db",
"JWT_SECRET": "new-jwt-secret",
"SMTP_PASSWORD": "new-smtp-password"
}'
# Retrieve secret
aws secretsmanager get-secret-value \
--secret-id pocketbook/production/config \
--query SecretString \
--output textApplication Integration
typescript
// src/config/index.ts
import { getSecret } from './secrets';
interface AppConfig {
database: {
url: string;
};
jwt: {
secret: string;
};
smtp: {
password: string;
};
}
let config: AppConfig | null = null;
export async function loadConfig(): Promise<AppConfig> {
if (config) return config;
if (process.env.NODE_ENV === 'production') {
// Load from Secrets Manager
const secrets = await getSecret('pocketbook/production/config');
config = {
database: {
url: secrets.DATABASE_URL,
},
jwt: {
secret: secrets.JWT_SECRET,
},
smtp: {
password: secrets.SMTP_PASSWORD,
},
};
} else {
// Load from environment variables for development
config = {
database: {
url: process.env.DATABASE_URL!,
},
jwt: {
secret: process.env.JWT_SECRET!,
},
smtp: {
password: process.env.SMTP_PASSWORD!,
},
};
}
return config;
}
// Usage in application
import express from 'express';
import { loadConfig } from './config';
const app = express();
async function startServer() {
const config = await loadConfig();
// Use configuration
console.log('Starting server with secure configuration');
app.listen(3000);
}
startServer();Caching Secrets
Reduce API calls by caching secrets:
typescript
class SecretsCache {
private cache: Map<string, { value: any; expiry: number }> = new Map();
private ttl: number = 5 * 60 * 1000; // 5 minutes
async get(secretName: string): Promise<any> {
const cached = this.cache.get(secretName);
if (cached && cached.expiry > Date.now()) {
return cached.value;
}
const value = await getSecret(secretName);
this.cache.set(secretName, {
value,
expiry: Date.now() + this.ttl,
});
return value;
}
invalidate(secretName?: string): void {
if (secretName) {
this.cache.delete(secretName);
} else {
this.cache.clear();
}
}
}
export const secretsCache = new SecretsCache();Automatic Secret Rotation
Enable Rotation
bash
aws secretsmanager rotate-secret \
--secret-id pocketbook/production/database \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRotation \
--rotation-rules AutomaticallyAfterDays=30Rotation Lambda Function
typescript
// lambda/rotate-secret.ts
import {
SecretsManagerClient,
GetSecretValueCommand,
UpdateSecretCommand,
} from '@aws-sdk/client-secrets-manager';
export async function handler(event: any) {
const secretId = event.SecretId;
const token = event.ClientRequestToken;
const step = event.Step;
const client = new SecretsManagerClient({ region: 'us-east-1' });
switch (step) {
case 'createSecret':
// Generate new secret value
const newSecret = generateNewSecret();
await createNewSecretVersion(client, secretId, token, newSecret);
break;
case 'setSecret':
// Update the service with new secret
await updateServiceWithNewSecret(secretId, token);
break;
case 'testSecret':
// Test new secret
await testNewSecret(secretId, token);
break;
case 'finishSecret':
// Mark rotation as complete
await finishSecretRotation(client, secretId, token);
break;
}
}
function generateNewSecret(): string {
return crypto.randomBytes(32).toString('hex');
}IAM Permissions
Application IAM Policy
json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:123456789012:secret:pocketbook/*"
]
}
]
}Rotation Lambda Policy
json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:DescribeSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:UpdateSecretVersionStage"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:pocketbook/*"
}
]
}HashiCorp Vault
Installation
bash
# Using Docker
docker run -d \
--name vault \
-p 8200:8200 \
-e VAULT_DEV_ROOT_TOKEN_ID=myroot \
vault
# Using binary
wget https://releases.hashicorp.com/vault/1.15.0/vault_1.15.0_linux_amd64.zip
unzip vault_1.15.0_linux_amd64.zip
sudo mv vault /usr/local/bin/Basic Configuration
bash
# Initialize Vault
export VAULT_ADDR='http://127.0.0.1:8200'
vault operator init
# Unseal Vault (use keys from init)
vault operator unseal <unseal-key-1>
vault operator unseal <unseal-key-2>
vault operator unseal <unseal-key-3>
# Login
vault login <root-token>
# Enable KV secrets engine
vault secrets enable -path=pocketbook kv-v2
# Write secret
vault kv put pocketbook/production/config \
database_url="postgresql://..." \
jwt_secret="..." \
smtp_password="..."
# Read secret
vault kv get pocketbook/production/configNode.js Integration
bash
npm install node-vaulttypescript
import vault from 'node-vault';
const vaultClient = vault({
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_TOKEN,
});
async function getVaultSecret(path: string): Promise<any> {
try {
const response = await vaultClient.read(path);
return response.data.data;
} catch (error) {
console.error('Error reading from Vault:', error);
throw error;
}
}
// Usage
const secrets = await getVaultSecret('pocketbook/production/config');
console.log(secrets.database_url);Environment-Specific Secrets
Organize by Environment
pocketbook/
├── development/
│ ├── database
│ ├── api-keys
│ └── oauth
├── staging/
│ ├── database
│ ├── api-keys
│ └── oauth
└── production/
├── database
├── api-keys
└── oauthLoad Based on Environment
typescript
function getSecretPath(secretName: string): string {
const env = process.env.NODE_ENV || 'development';
return `pocketbook/${env}/${secretName}`;
}
const dbSecrets = await getSecret(getSecretPath('database'));Docker Integration
Docker Secrets (Swarm Mode)
bash
# Create secret
echo "my-db-password" | docker secret create db_password -
# Use in service
docker service create \
--name pocketbook \
--secret db_password \
pocketbook:latestAccess in application:
typescript
import { readFileSync } from 'fs';
const dbPassword = readFileSync('/run/secrets/db_password', 'utf8').trim();Docker Compose with Secrets
yaml
version: '3.8'
services:
app:
image: pocketbook:latest
environment:
- AWS_REGION=us-east-1
- SECRET_NAME=pocketbook/production/config
secrets:
- aws_credentials
secrets:
aws_credentials:
file: ./secrets/aws_credentialsKubernetes Secrets
Create Secret
bash
# From literal values
kubectl create secret generic pocketbook-secrets \
--from-literal=database-url='postgresql://...' \
--from-literal=jwt-secret='...'
# From file
kubectl create secret generic pocketbook-secrets \
--from-file=config.jsonUse in Deployment
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pocketbook
spec:
template:
spec:
containers:
- name: app
image: pocketbook:latest
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: pocketbook-secrets
key: database-url
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: pocketbook-secrets
key: jwt-secretExternal Secrets Operator
Sync secrets from AWS Secrets Manager to Kubernetes:
yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: pocketbook
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: pocketbook-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: pocketbook-secrets
data:
- secretKey: database-url
remoteRef:
key: pocketbook/production/config
property: DATABASE_URL
- secretKey: jwt-secret
remoteRef:
key: pocketbook/production/config
property: JWT_SECRETBest Practices
- Never commit secrets to git - Use
.gitignorefor secret files - Use separate secrets per environment - Development, staging, production
- Enable audit logging - Track secret access and modifications
- Implement rotation - Regularly rotate sensitive credentials
- Principle of least privilege - Grant minimal required permissions
- Encrypt secrets at rest and in transit - Use TLS/SSL
- Cache secrets appropriately - Balance between security and performance
- Monitor secret access - Alert on unusual access patterns
- Use versioning - Keep history of secret changes
- Test secret rotation - Ensure rotation doesn't break services
Troubleshooting
Secret Not Found
typescript
try {
const secret = await getSecret('pocketbook/config');
} catch (error) {
if (error.name === 'ResourceNotFoundException') {
console.error('Secret does not exist');
}
throw error;
}Permission Denied
Check IAM permissions and ensure the role/user has secretsmanager:GetSecretValue permission.
Caching Issues
Invalidate cache after secret rotation:
typescript
secretsCache.invalidate('pocketbook/production/config');Next Steps
- Key Rotation - Implement key rotation strategies
- Security Guide - Additional security best practices
- Monitoring - Monitor secret access
- Deployment Overview (internal documentation) - Secrets in deployment
