/Secrets Manager & Parameter Store
Concept
Medium

Secrets Manager & Parameter Store

11 min read·Secrets ManagerParameter StoreSSMSecurityDVA-C02

A comprehensive deep dive into AWS Secrets Manager and SSM Parameter Store — secret types, automatic rotation, versioning, hierarchies, SDK patterns, Lambda integration, cross-account access, and DVA-C02 exam essentials.


Secrets Manager vs Parameter Store — Mental Model

Both services store sensitive configuration, but they solve different problems.

Core mental model: Secrets Manager is a secrets lifecycle manager — it stores, versions, rotates, and audits credentials automatically. Parameter Store is a configuration store that also handles secrets as a side-effect — free, hierarchical, and great for non-rotating config values.

Rendering diagram…

Part 1 — AWS Secrets Manager

Core Concepts

FeatureDetail
StorageEncrypted JSON blob, up to 64 KB
EncryptionKMS (default: aws/secretsmanager, or CMK)
VersioningAWSCURRENT, AWSPENDING, AWSPREVIOUS labels
Auto-rotationBuilt-in for RDS, Redshift, DocumentDB, Aurora
Custom rotationLambda function for any other secret type
Cost$0.40/secret/month + $0.05 per 10,000 API calls
Cross-accountResource-based policy on the secret
ReplicationReplicate secrets to multiple regions

Creating and Retrieving Secrets (SDK)

javascript
1import {
2  SecretsManagerClient,
3  CreateSecretCommand,
4  GetSecretValueCommand,
5  PutSecretValueCommand,
6  RotateSecretCommand,
7  DeleteSecretCommand,
8} from '@aws-sdk/client-secrets-manager';
9
10const sm = new SecretsManagerClient({ region: 'us-east-1' });
11
12// ── CREATE ────────────────────────────────────────────────────────────────────
13await sm.send(new CreateSecretCommand({
14  Name: '/myapp/prod/db-credentials',
15  Description: 'Production RDS credentials',
16  SecretString: JSON.stringify({
17    username: 'admin',
18    password: 'S3cur3P@ss!',
19    host: 'mydb.cluster.us-east-1.rds.amazonaws.com',
20    port: 5432,
21    dbname: 'appdb',
22  }),
23  KmsKeyId: 'alias/my-secrets-key',   // omit to use aws/secretsmanager
24  Tags: [{ Key: 'Environment', Value: 'prod' }],
25}));
26
27// ── GET CURRENT VALUE ─────────────────────────────────────────────────────────
28const { SecretString } = await sm.send(new GetSecretValueCommand({
29  SecretId: '/myapp/prod/db-credentials',
30  // VersionStage defaults to AWSCURRENT
31}));
32const creds = JSON.parse(SecretString);
33console.log(creds.password);  // S3cur3P@ss!
34
35// ── GET A SPECIFIC VERSION ────────────────────────────────────────────────────
36const { SecretString: prev } = await sm.send(new GetSecretValueCommand({
37  SecretId: '/myapp/prod/db-credentials',
38  VersionStage: 'AWSPREVIOUS',
39}));

Secret Versioning & Rotation Labels

Rendering diagram…
Version StageMeaning
AWSCURRENTThe active, in-use secret value
AWSPENDINGNew value being rotated in (during rotation window)
AWSPREVIOUSPrevious value (kept temporarily for in-flight connections)
Custom stageYour own labels for blue/green or versioned deploys

Automatic Rotation Setup

bash
1# Enable automatic rotation — every 30 days for an RDS secret
2aws secretsmanager rotate-secret   --secret-id /myapp/prod/db-credentials   --rotation-rules AutomaticallyAfterDays=30   --rotate-immediately   # rotate right now as well
3
4# Enable for RDS using the managed rotation function (no Lambda needed)
5aws secretsmanager rotate-secret   --secret-id /myapp/prod/db-credentials   --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRDSMySQLRotationSingleUser   --rotation-rules AutomaticallyAfterDays=30

AWS provides managed rotation Lambda functions for:

  • RDS MySQL / PostgreSQL / Oracle / SQL Server (single-user and multi-user)
  • Amazon Redshift
  • Amazon DocumentDB
  • Amazon ElastiCache (Redis)

For anything else (third-party APIs, custom databases), you write a Lambda with four lifecycle hooks:

Lambda HookWhat it does
createSecretGenerate new credential value, store as AWSPENDING
setSecretSet the new credential in the target service (DB, API)
testSecretVerify the AWSPENDING secret actually works
finishSecretMove AWSPENDING → AWSCURRENT, old → AWSPREVIOUS
javascript
1// Custom rotation Lambda skeleton
2export async function handler(event) {
3  const { Step, SecretId, ClientRequestToken } = event;
4
5  switch (Step) {
6    case 'createSecret':
7      // Generate new password and call sm.putSecretValue with VersionStages: ['AWSPENDING']
8      break;
9    case 'setSecret':
10      // Retrieve AWSPENDING secret, update the target resource
11      break;
12    case 'testSecret':
13      // Retrieve AWSPENDING secret, attempt to authenticate with target resource
14      // Throw on failure — Secrets Manager will halt rotation
15      break;
16    case 'finishSecret':
17      // Call sm.updateSecretVersionStage to move AWSPENDING → AWSCURRENT
18      break;
19  }
20}

Caching Secrets in Lambda (Best Practice)

Calling GetSecretValue on every Lambda invocation adds latency and cost. Use the Secrets Manager caching client:

javascript
1import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
2
3const sm = new SecretsManagerClient({});
4
5// Module-level cache — survives warm invocations
6let cachedSecret = null;
7let cacheExpiry = 0;
8const CACHE_TTL_MS = 5 * 60 * 1000;  // 5 minutes
9
10async function getSecret(secretId) {
11  const now = Date.now();
12  if (cachedSecret && now < cacheExpiry) return cachedSecret;
13
14  const { SecretString } = await sm.send(
15    new GetSecretValueCommand({ SecretId: secretId })
16  );
17  cachedSecret = JSON.parse(SecretString);
18  cacheExpiry = now + CACHE_TTL_MS;
19  return cachedSecret;
20}
21
22export async function handler() {
23  const creds = await getSecret('/myapp/prod/db-credentials');
24  // use creds.password
25}

Cross-Account Secret Access

json
1{
2  "Version": "2012-10-17",
3  "Statement": [
4    {
5      "Sid": "AllowCrossAccountRead",
6      "Effect": "Allow",
7      "Principal": {
8        "AWS": "arn:aws:iam::987654321098:role/AppRole"
9      },
10      "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
11      "Resource": "*"
12    }
13  ]
14}

The KMS key used to encrypt the secret must also grant the cross-account principal kms:Decrypt — otherwise the decrypted value cannot be returned even if the resource policy allows it.

Secret Replication (Multi-Region)

bash
1# Replicate a secret to another region (read replica — auto-synced)
2aws secretsmanager replicate-secret-to-regions   --secret-id /myapp/prod/db-credentials   --add-replica-regions '[{"Region":"eu-west-1"},{"Region":"ap-southeast-1"}]'

Each replica is independently encrypted with a KMS key in that region. Use replicas for low-latency reads and DR scenarios.


Part 2 — SSM Parameter Store

Parameter Types

TypeEncryptedMax SizeCost
StringNo4 KB (std) / 8 KB (adv)Free (std)
StringListNo4 KB / 8 KBFree (std)
SecureStringYes (KMS)4 KB / 8 KBFree (std), $0.05/param (adv)

Standard vs Advanced

StandardAdvanced
Max parameter size4 KB8 KB
Parameter policies (TTL, expiry)NoYes
Max parameters per account10,000Unlimited
Throughput40 TPS (default)3,000 TPS
CostFree$0.05/param/month
Cost to retrieveFree$0.05 per 10,000 API calls

Hierarchy & Naming Convention

text
1/                          ← root
2├── myapp/
3│   ├── dev/
4│   │   ├── db-url         String
5│   │   ├── db-password    SecureString
6│   │   └── feature-flags  StringList
7│   └── prod/
8│       ├── db-url
9│       ├── db-password
10│       └── api-key
11└── shared/
12    └── slack-webhook
bash
1# Put a standard string
2aws ssm put-parameter   --name "/myapp/prod/db-url"   --value "postgres://mydb.us-east-1.rds.amazonaws.com:5432/appdb"   --type String   --overwrite
3
4# Put a SecureString encrypted with a CMK
5aws ssm put-parameter   --name "/myapp/prod/db-password"   --value "S3cur3P@ss!"   --type SecureString   --key-id alias/my-param-key   --overwrite
6
7# Get a single parameter (auto-decrypt SecureString)
8aws ssm get-parameter   --name "/myapp/prod/db-password"   --with-decryption
9
10# Get ALL parameters under a path (recursive)
11aws ssm get-parameters-by-path   --path "/myapp/prod"   --recursive   --with-decryption

SDK Usage in Node.js

javascript
1import { SSMClient, GetParameterCommand, GetParametersByPathCommand, PutParameterCommand } from '@aws-sdk/client-ssm';
2
3const ssm = new SSMClient({ region: 'us-east-1' });
4
5// ── READ SINGLE ───────────────────────────────────────────────────────────────
6async function getParam(name) {
7  const { Parameter } = await ssm.send(new GetParameterCommand({
8    Name: name,
9    WithDecryption: true,   // required for SecureString
10  }));
11  return Parameter.Value;
12}
13
14// ── READ ALL UNDER A PATH ─────────────────────────────────────────────────────
15async function getParamsByPath(path) {
16  const params = {};
17  let nextToken;
18
19  do {
20    const { Parameters, NextToken } = await ssm.send(
21      new GetParametersByPathCommand({
22        Path: path,
23        Recursive: true,
24        WithDecryption: true,
25        NextToken: nextToken,
26      })
27    );
28    for (const p of Parameters) {
29      // /myapp/prod/db-url  →  'db-url'
30      const key = p.Name.split('/').pop();
31      params[key] = p.Value;
32    }
33    nextToken = NextToken;
34  } while (nextToken);
35
36  return params;  // { 'db-url': '...', 'db-password': '...', 'api-key': '...' }
37}
38
39// ── WRITE ─────────────────────────────────────────────────────────────────────
40await ssm.send(new PutParameterCommand({
41  Name: '/myapp/prod/feature-dark-mode',
42  Value: 'true',
43  Type: 'String',
44  Overwrite: true,
45}));

Parameter Policies (Advanced Only)

Parameter policies allow automatic expiry and notifications — no code needed.

bash
1# Advanced parameter with an expiry policy (auto-deletes after date)
2aws ssm put-parameter   --name "/myapp/prod/temp-api-key"   --value "sk-temp-abc123"   --type SecureString   --tier Advanced   --policies '[
3    {
4      "Type": "Expiration",
5      "Version": "1.0",
6      "Attributes": {
7        "Timestamp": "2025-12-31T00:00:00.000Z"
8      }
9    },
10    {
11      "Type": "ExpirationNotification",
12      "Version": "1.0",
13      "Attributes": {
14        "Before": "10",
15        "Unit": "Days"
16      }
17    },
18    {
19      "Type": "NoChangeNotification",
20      "Version": "1.0",
21      "Attributes": {
22        "After": "30",
23        "Unit": "Days"
24      }
25    }
26  ]'
Policy TypeWhat it does
ExpirationDeletes the parameter at a specified timestamp
ExpirationNotificationSends EventBridge event N days before expiry
NoChangeNotificationSends EventBridge event if parameter not updated in N days

Loading Config at Lambda Cold Start

javascript
1// Load all config once at module init (cold start only)
2const ssm = new SSMClient({});
3
4let config;
5
6async function loadConfig() {
7  if (config) return config;
8  const params = await getParamsByPath('/myapp/prod');
9  config = params;
10  return config;
11}
12
13export async function handler(event) {
14  const { 'db-url': dbUrl, 'db-password': dbPass } = await loadConfig();
15  // connect to DB using dbUrl and dbPass
16}

This runs GetParametersByPath once per cold start, then reuses the in-memory config for all warm invocations.


Integration with CloudFormation & CDK

yaml
1# CloudFormation — reference a Parameter Store value
2Parameters:
3  DbPassword:
4    Type: 'AWS::SSM::Parameter::Value<String>'
5    Default: /myapp/prod/db-password
6
7Resources:
8  RDSInstance:
9    Type: AWS::RDS::DBInstance
10    Properties:
11      MasterUserPassword: !Ref DbPassword
typescript
1// CDK — read Parameter Store value at synth time
2import * as ssm from 'aws-cdk-lib/aws-ssm';
3
4const dbUrl = ssm.StringParameter.valueForStringParameter(
5  this, '/myapp/prod/db-url'
6);
7
8// CDK — read Secrets Manager secret at deploy time
9import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
10
11const secret = secretsmanager.Secret.fromSecretNameV2(
12  this, 'DbSecret', '/myapp/prod/db-credentials'
13);
14const dbPassword = secret.secretValueFromJson('password').unsafeUnwrap();

ECS & Kubernetes Integration

json
1// ECS Task Definition — inject secrets as environment variables
2{
3  "containerDefinitions": [{
4    "name": "app",
5    "image": "myapp:latest",
6    "secrets": [
7      {
8        "name": "DB_PASSWORD",
9        "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/prod/db-credentials:password::"
10      },
11      {
12        "name": "API_KEY",
13        "valueFrom": "/myapp/prod/api-key"
14      }
15    ],
16    "environment": [
17      { "name": "NODE_ENV", "value": "production" }
18    ]
19  }]
20}

ECS fetches the secrets at task launch and injects them as environment variables. The task execution role must have secretsmanager:GetSecretValue or ssm:GetParameters plus the KMS decrypt permission.


Rotation Architecture (Full Flow)

Rendering diagram…

Comparison: Secrets Manager vs Parameter Store

Secrets ManagerParameter Store
Cost$0.40/secret/monthFree (standard), $0.05/param (advanced)
Max size64 KB4 KB (std) / 8 KB (adv)
Auto rotationBuilt-in Lambda rotation❌ Manual (you write Lambda + EventBridge)
Versioning labelsAWSCURRENT / AWSPENDING / AWSPREVIOUSVersion numbers (integer)
Expiry / TTL❌ (delete manually)✅ Advanced — Expiration policy
Cross-accountResource policy❌ IAM cross-account only via roles
Multi-region replication✅ Built-in
CloudFormation dynamic ref{{resolve:secretsmanager:name:SecretString:key}}{{resolve:ssm-secure:/path/name}}
CloudTrail auditEvery GetSecretValue loggedEvery GetParameter logged
Hierarchy / path namingFlat (use / as convention only)True hierarchical path with GetParametersByPath
Best forDB credentials, API keys, rotating secretsApp config, feature flags, any non-rotating config

CloudFormation Dynamic References

yaml
1# Secrets Manager dynamic reference
2MyFunction:
3  Type: AWS::Lambda::Function
4  Properties:
5    Environment:
6      Variables:
7        DB_PASSWORD: '{{resolve:secretsmanager:/myapp/prod/db-credentials:SecretString:password}}'
8
9# Parameter Store dynamic reference (SecureString)
10        API_KEY: '{{resolve:ssm-secure:/myapp/prod/api-key}}'
11
12# Parameter Store dynamic reference (plain String)
13        DB_HOST: '{{resolve:ssm:/myapp/prod/db-url}}'

Security Best Practices

PracticeWhy
Never hardcode secrets in code or env filesLeaked via git history, process listings
Use Parameter Store for non-rotating configCheaper than Secrets Manager for static values
Enable rotation immediately for DB credentialsLimits blast radius if credentials are compromised
Use dedicated KMS CMK per environmentSeparate dev/staging/prod key access
Scope IAM to specific secret ARNsLeast privilege — avoid secretsmanager:* on *
Enable CloudTrailAudit every secret access attempt
Use VPC endpoint for Secrets ManagerKeeps traffic off the public internet
Cache secrets in LambdaReduces cost and latency without sacrificing freshness

DVA-C02 Quick Reference

TopicKey Fact
Secrets Manager cost$0.40/secret/month
Parameter Store standard costFree
Parameter Store advanced cost$0.05/parameter/month
Max secret size64 KB
Max standard parameter size4 KB
Max advanced parameter size8 KB
Rotation stagesAWSCURRENT → AWSPENDING → AWSCURRENT (old becomes AWSPREVIOUS)
Rotation Lambda hooks (order)createSecret → setSecret → testSecret → finishSecret
Built-in rotation supportRDS, Redshift, DocumentDB, Aurora, ElastiCache
Custom rotationLambda with all 4 lifecycle hooks
SecureString encryptionKMS (CMK or aws/ssm)
GetParametersByPathRetrieve all params under a path prefix recursively
Parameter policies requireAdvanced tier
Cross-account Secrets ManagerResource-based policy + KMS key policy grants
Replicate secret across regionsreplicate-secret-to-regions — each region gets own KMS key
CloudFormation secret ref syntax{{resolve:secretsmanager:name:SecretString:jsonKey}}
CloudFormation SecureString ref{{resolve:ssm-secure:/path/param}}
ECS secrets injectionsecrets in task definition — fetched at task launch
Secrets Manager vs Parameter StoreSecrets Manager: rotation + lifecycle; Parameter Store: config + free tier

Practice Questions2

medium

Q1. A developer stores an RDS database password in AWS Secrets Manager. The application retrieves it at startup. Six months later, the application breaks because the password changed. What Secrets Manager feature caused this?


Select one answer before revealing.

medium

Q2. A developer needs to store API keys, database passwords, and TLS certificates as configuration values accessible to Lambda functions. Some values are sensitive (must be encrypted); others are non-sensitive. Which service combination is recommended?


Select one answer before revealing.