Skip to content

Secrets Manager Integration

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

Basic 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

  1. Navigate to AWS Secrets Manager
  2. Click Store a new secret
  3. Select secret type (e.g., Other type of secret)
  4. Enter key-value pairs:
    json
    {
      "DATABASE_URL": "postgresql://user:pass@host:5432/db",
      "JWT_SECRET": "your-jwt-secret",
      "SMTP_PASSWORD": "your-smtp-password"
    }
  5. Name the secret: pocketbook/production/config
  6. Configure rotation (optional)
  7. 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 text

Application 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=30

Rotation 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/config

Node.js Integration

bash
npm install node-vault
typescript
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
    └── oauth

Load 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:latest

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

Kubernetes 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.json

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

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

Best Practices

  1. Never commit secrets to git - Use .gitignore for secret files
  2. Use separate secrets per environment - Development, staging, production
  3. Enable audit logging - Track secret access and modifications
  4. Implement rotation - Regularly rotate sensitive credentials
  5. Principle of least privilege - Grant minimal required permissions
  6. Encrypt secrets at rest and in transit - Use TLS/SSL
  7. Cache secrets appropriately - Balance between security and performance
  8. Monitor secret access - Alert on unusual access patterns
  9. Use versioning - Keep history of secret changes
  10. 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

Resources

Released under the MIT License.