AWS Lesson 65 of 123

AWS Secrets Manager vs SSM Parameter Store, In Depth: Secrets, Rotation & Config

In a nutshell

Think of your app’s sensitive values like the keys and the settings for a building. Secrets are the keys — the database password, the API key, the TLS private key — anything that lets someone in and causes real harm if it leaks. Configuration is the settings — which floor you’re on, the opening hours, the thermostat target — values that shape behaviour but aren’t dangerous on their own. You never tape a key to the front door (never hard-code a secret in source), and you never rebuild the whole building to change the thermostat (never bake config into an image). You keep both in a locked, audited cabinet and fetch them when needed.

AWS gives you two such cabinets. Secrets Manager is the high-security safe: it encrypts everything, and — its headline trick — it can rotate a secret automatically (swap the password for a fresh one on a schedule, with no downtime), share a secret across accounts, and copy it across Regions. It charges rent: about $0.40 per secret per month. SSM Parameter Store is the big filing cabinet: it holds plain settings for free and also holds encrypted secrets (a SecureString), organised in neat folders like /myapp/prod/db/host — but it does not rotate anything for you.

The whole lesson comes down to one decision you’ll make over and over: does this value need automatic rotation, or cross-account / cross-Region sharing? If yes, it goes in Secrets Manager. If it’s ordinary config, or a secret you’ll rotate yourself and you’d rather not pay per secret, it goes in Parameter Store. Most real apps use both — the config tree and static keys in Parameter Store, the auto-rotating database password in Secrets Manager. Remember only that sentence and you already have the answer to the most common question on this topic.

Level: Intermediate · Time: ~50 min · You’ll need: an AWS account, basic AWS CLI comfort, and a working mental model of IAM and KMS envelope encryption — both stores encrypt with KMS and gate access with IAM.

Every application needs two things it must not hard-code: secrets (database passwords, API keys, TLS private keys, OAuth client secrets) and configuration (feature flags, endpoint URLs, log levels, queue names). Put either in source code and you have a problem — a credential in a git history is a credential in every fork, every laptop, and every build log forever; a config value baked into an image means a rebuild for every environment. AWS gives you two managed services for this, and the single most common interview question on the topic is which one do I use? — because they overlap, they are priced very differently, and choosing wrong costs either money or capability. AWS Secrets Manager is a purpose-built secrets store with automatic rotation, versioning, and cross-account sharing — it costs per secret per month. AWS Systems Manager Parameter Store is a general-purpose configuration store that also stores encrypted secrets (SecureString), and its standard tier is free.

This lesson is the exhaustive tour of both. We will go through every Secrets Manager feature — the secret types, the four-step automatic rotation model with Lambda, staging labels (AWSCURRENT, AWSPENDING, AWSPREVIOUS), resource policies and cross-account access, multi-Region replication, and the real cost — and every Parameter Store feature — String, StringList, and KMS-backed SecureString; standard vs advanced tiers; hierarchies and recursive lookups; versions; and parameter policies (expiration and change notification). Then comes the part that matters most in practice: a clear when-to-use-which comparison table, every retrieval pattern (SDK, CLI, the Lambda extension and caching, the ECS/EKS integrations), how to lock both down with IAM, and how to audit every read and write with CloudTrail. By the end you will never again hard-code a secret, and you will pick the right store every time.

Learning objectives

By the end of this lesson you will be able to:

Prerequisites & where this fits

You need an AWS account, basic comfort with the AWS CLI (aws configure), and a working mental model of IAM (identities, policies, the difference between an identity policy and a resource policy) and AWS KMS (keys and envelope encryption) — both services encrypt with KMS, and access to both is governed by IAM, so the earlier IAM and KMS lessons are the foundation this one builds on. No prior secrets-management background is assumed; every term is defined as it appears. This is a lesson in the Security module of the AWS Zero-to-Hero course, following AWS KMS & Encryption, In Depth (which explains the envelope encryption both services rely on). After this, the course moves on to messaging with AWS Messaging Fundamentals: SQS, SNS & EventBridge. Once you have the fundamentals here, the advanced companion Secrets Manager Rotation at Scale: Custom Rotation Lambdas, RDS Credentials, and Cross-Account Sharing takes rotation and cross-account sharing to production depth.

Core concepts: secrets vs configuration, and the two stores

Before the options, fix two distinctions that the whole lesson hangs on.

Secret vs configuration. A secret is a value whose disclosure causes harm: a password, an API key, a private key, a connection string with a password in it. Configuration is a value that shapes behaviour but is not itself sensitive: a feature flag, a region name, a bucket name, a timeout. The line blurs (a database hostname is config; the password is a secret), and the right answer is often to store the password as a secret and the rest as config. The reason the distinction matters: secrets need rotation, tight auditing, and short blast radius; configuration mostly needs versioning and easy retrieval.

The two stores. AWS gives you two managed key-value stores for these values, both encrypted, both reached by IAM-controlled API calls — never a file on disk, never an environment variable baked into an image.

AWS Secrets Manager SSM Parameter Store
Built for Secrets Configuration (and secrets, via SecureString)
Encryption Always KMS-encrypted Plaintext (String/StringList) or KMS-encrypted (SecureString)
Automatic rotation Yes (native, with Lambda) No (you build it yourself)
Versioning Yes, with staging labels Yes, numeric versions (+ labels on advanced)
Cross-account sharing Yes (resource policy) No native resource policy (share via other means)
Multi-Region Replica secrets Per-Region (no built-in replication)
Cost ~$0.40 per secret/month + $0.05 per 10k API calls Standard tier: free; advanced ~$0.05/param/month
Size limit 64 KB per secret 4 KB (standard) / 8 KB (advanced)
API secretsmanager:* ssm:*Parameter*

The decisive question, in one line: does the value need automatic rotation or cross-account sharing? If yes → Secrets Manager. If it is plain configuration, or a secret you will rotate yourself and you want it free → Parameter Store (SecureString for secrets). We will sharpen this into a full decision table later; keep this mental model as you read the details.

One crucial bridge between them: Parameter Store can reference a Secrets Manager secret. If you read the parameter name /aws/reference/secretsmanager/<secret-name> through the SSM API, Parameter Store fetches the value from Secrets Manager for you. This lets tools that only speak “Parameter Store” (some older integrations, certain IaC patterns) consume Secrets Manager secrets without code changes.

AWS Secrets Manager, in depth

AWS Secrets Manager stores, encrypts, rotates, and serves secrets through an API. Every secret is always encrypted with a KMS key — either the AWS-managed key aws/secretsmanager (free, zero setup) or a customer managed key (CMK) you control (required for cross-account sharing and key separation). You never store a secret in plaintext here; that is the whole point.

What a secret looks like

A secret has a name (often a path-like string such as prod/payments/db), an optional description, a KMS key, optional tags, an optional resource policy, an optional rotation configuration, and one or more versions, each holding a secret value. The secret value is up to 64 KB and is stored as either:

Secret types (what the console offers)

The API stores any string or blob, but the console offers typed wizards that pre-fill the JSON shape and wire up the right rotation Lambda:

Secret type What it stores Rotation support
Amazon RDS credentials username/password (+ host, port, engine, dbname) for an RDS DB Native — AWS provides the rotation Lambda
Amazon Redshift credentials Same shape for a Redshift cluster Native rotation Lambda
Amazon DocumentDB credentials Same shape for DocumentDB Native rotation Lambda
Other database Generic DB credentials (MySQL/PostgreSQL/etc. not on RDS) Native templates for some engines
Credentials for other service Any username/password pair Custom rotation Lambda (you write it)
Other type of secret Free-form key/value or plaintext (API keys, tokens) Custom rotation Lambda (you write it)

The type is essentially a convenience: it determines the JSON template and which rotation Lambda template AWS offers. Under the hood every secret is just a name plus versions of SecretString/SecretBinary.

Versioning and staging labels — the heart of Secrets Manager

This is the concept interviewers probe and the one beginners get wrong, so go slowly. Secrets Manager never overwrites a secret value in place. Every change creates a new version, identified by a VersionId (a UUID). To track which version is the live one, Secrets Manager attaches staging labels — named pointers that move between versions:

Staging label Meaning
AWSCURRENT The current, live version. This is what GetSecretValue returns when you don’t specify a version. Your applications use this.
AWSPENDING A new version being created/tested during rotation, not yet promoted. Only present mid-rotation.
AWSPREVIOUS The immediately prior version (the one AWSCURRENT used to point at). Your rollback safety net.

A label points to exactly one version, and a version can carry multiple labels. Rotation is, mechanically, the act of creating a new version labelled AWSPENDING and then moving AWSCURRENT to it (which automatically demotes the old current to AWSPREVIOUS). Understanding that “rotation = moving the AWSCURRENT label” demystifies the whole rotation flow below.

You can also create your own custom staging labels (e.g. to pin a version), but AWSCURRENT/AWSPENDING/AWSPREVIOUS are reserved and managed by rotation.

Key version facts:

Automatic rotation — the four-step model

Rotation means periodically replacing a secret’s value with a new one and updating the system that uses it, without downtime. Secrets Manager orchestrates this by invoking a rotation Lambda that implements a four-step contract. The Lambda is called once per step, with an event telling it the SecretId, the Token (the VersionId of the AWSPENDING version), and the Step:

Step Name What the Lambda must do
1 createSecret Generate a new secret value (e.g. GetRandomPassword) and store it as a new version labelled AWSPENDING. Idempotent — if AWSPENDING already exists, do nothing.
2 setSecret Apply the pending value to the target service (e.g. ALTER USER … PASSWORD on the database) so the new credential actually works.
3 testSecret Verify the pending value works — connect to the DB with it and run a trivial query. If this fails, rotation aborts and AWSCURRENT is untouched.
4 finishSecret Move the AWSCURRENT label onto the AWSPENDING version (demoting the old one to AWSPREVIOUS). Rotation complete.

The brilliance of this design: until step 4, AWSCURRENT still points at the old, working credential, so applications keep running. Only when the new credential has been proven (step 3) does it become current. If any step throws, the secret stays on the old value and CloudWatch logs the failure.

Single-user vs alternating-users rotation (the classic RDS choice):

Rotation schedule — you set it as a rotation interval in days (e.g. every 30 days) or a cron/rate schedule (e.g. cron(0 8 1 * ? *) for the 1st of each month at 08:00 UTC). You can also choose to rotate immediately on save (runs once now to validate the Lambda) or defer the first rotation to the next window. A rotation window duration bounds how long a rotation may take.

The deep mechanics of writing custom rotators, the master-secret pattern, and alternating-users in full are covered in the advanced companion lesson linked at the end — here you need to know the four-step contract and the two strategies cold, because that is what gets asked.

Managed rotation vs Lambda-based rotation

The four-step model above always runs — but who owns the rotation function has changed, and this trips up people who learned Secrets Manager a few years ago. There are now two ways rotation happens:

Closely related: RDS and Aurora can manage the master user password directly in Secrets Manager for you (the “Manage master credentials in AWS Secrets Manager” option). RDS creates the secret, owns it, and rotates it on a schedule — you never see or set the master password, you just read the secret. That is the lowest-effort way to get a rotating database credential, and it’s the pattern to reach for on greenfield RDS.

Rule of thumb: managed target (RDS / Aurora / Redshift / DocumentDB) → prefer managed rotation (or RDS-managed master credentials); anything else → a rotation Lambda. Either way the AWSCURRENT / AWSPENDING / AWSPREVIOUS dance and the four steps are identical underneath — only the ownership of the rotator differs.

Resource policies and cross-account access

A resource policy (a JSON policy attached to the secret itself) controls who can access it, complementing the IAM identity policies attached to principals. The headline use is cross-account sharing: to let account B read a secret owned by account A, you need both:

  1. A resource policy on the secret (account A) that Allows the account-B principal secretsmanager:GetSecretValue.
  2. A key policy / grant on the KMS key (account A) that lets the account-B principal kms:Decrypt — and the secret must use a CMK, not the AWS-managed aws/secretsmanager key, because the managed key cannot be shared cross-account. This is the single most common cross-account “why is it AccessDenied?” cause.

You can also use a resource policy within one account to deny everything except a specific role (a belt-and-braces guardrail), or to block access (an explicit Deny). Use aws secretsmanager validate-resource-policy to catch broad/public policies before applying them.

Replication (multi-Region secrets)

Secrets Manager can replicate a secret to other Regions. You designate a primary secret and add replica Regions; Secrets Manager keeps the replicas in sync (value and metadata) and re-encrypts each replica with a KMS key in that Region (you choose which). Why it matters:

Replicas are billed as separate secrets. You cannot replicate into a Region that already has a secret of the same name (resolve the conflict first).

Other Secrets Manager features

Secrets Manager cost

The pricing model is the reason people reach for Parameter Store instead: ~$0.40 per secret per month (prorated) plus $0.05 per 10,000 API calls. A replica counts as another secret. Ten thousand secrets is ~$4,000/month before API calls — so don’t store configuration here, and cache reads (the Lambda extension does this for you) so you aren’t billed per request in a hot loop.

AWS Systems Manager Parameter Store, in depth

Parameter Store (part of AWS Systems Manager) is a hierarchical key-value store for configuration data and secrets. Its standard tier is free, which makes it the default home for the dozens of non-rotating config values and many secrets a typical application needs. A parameter has a name (a path like /myapp/prod/db/host), a type, a value, a tier, an optional description, a KMS key (for SecureString), optional policies, and numeric versions.

Parameter types

Type What it stores Encryption Notes
String A single plaintext string None For non-sensitive config: hostnames, flags, URLs, ARNs.
StringList A comma-separated list None Returned as a comma-separated string; split client-side. For lists of subnets, AZs, etc.
SecureString A single string encrypted with KMS KMS For secrets: passwords, API keys. Choose the AWS-managed aws/ssm key or a CMK.

A few important subtleties:

Standard vs advanced tier

Each parameter is standard or advanced, and the tier changes limits, cost, and capabilities:

Standard Advanced
Value size up to 4 KB up to 8 KB
Parameters per account/Region 10,000 100,000
Parameter policies (expire, notify) No Yes
Higher-throughput limit option No Yes (with throughput setting)
Cost Free ~$0.05 per parameter/month + per-API charges for higher throughput

You set the tier explicitly, or use --tier Intelligent-Tiering, which automatically promotes a parameter to advanced only if it needs to (value > 4 KB or a policy is attached) — so you pay for advanced only when required. Default to standard; let intelligent tiering handle the exceptions.

Hierarchies and paths

Parameter names are paths separated by /, which lets you organise configuration as a tree and fetch whole subtrees in one call:

/myapp/prod/db/host
/myapp/prod/db/port
/myapp/prod/db/password      (SecureString)
/myapp/staging/db/host

The power move is get-parameters-by-path with --recursive, which returns every parameter under a prefix — so one call fetches all of /myapp/prod/ for an environment. This is the idiomatic way to load configuration: name parameters by /<app>/<env>/<group>/<key> and pull the env subtree at startup. Paths also make IAM scoping clean — grant ssm:GetParametersByPath on arn:aws:ssm:…:parameter/myapp/prod/* and a role can read exactly its environment and nothing else.

Versions, labels, and history

Parameter policies (advanced tier only)

Advanced parameters support policies — JSON rules that automate lifecycle, attached at creation or update:

These policies are the closest Parameter Store gets to Secrets Manager’s lifecycle automation — they notify, but they do not rotate; you still build the rotation yourself (e.g. an EventBridge rule triggering a Lambda).

Parameter Store cost

Standard tier is free for storage and standard-throughput API calls — which is the whole appeal. Advanced parameters are ~$0.05 per parameter per month, and high-throughput API interactions are billed per call when you opt into the higher-throughput setting. For the vast majority of configuration, Parameter Store costs nothing.

When to use which: the decision table

This is the question, so here is the clear answer. Both can store a secret; the differences decide it.

Need Use Secrets Manager Use Parameter Store
Automatic rotation (RDS, Redshift, custom) ✅ Native, built-in ❌ Build it yourself (EventBridge + Lambda)
Cross-account sharing of the value ✅ Resource policy + CMK ❌ No native resource policy
Multi-Region replication of the value ✅ Replica secrets ❌ Per-Region only
Plain configuration (flags, URLs, ARNs) ⚠️ Works but wasteful ($/secret) ✅ Free String/StringList
Secrets, but free and you’ll rotate yourself ⚠️ Costs $0.40/secret/mo ✅ SecureString (free, standard tier)
Large value (up to 64 KB) ✅ 64 KB ❌ 4 KB / 8 KB only
Built-in random-password generation ✅ GetRandomPassword + rotation ❌ Generate it yourself
Hierarchical config, fetch a subtree at once ⚠️ No path-tree fetch ✅ get-parameters-by-path --recursive
Tightest cost at thousands of values ❌ $0.40 each adds up fast ✅ Standard tier free

The rules I actually apply:

Retrieval patterns: getting secrets into your app safely

Storing a secret is half the job; reading it without leaking it (into logs, env vars, or images) is the other half. Here is every common pattern.

AWS CLI

# Secrets Manager — returns the JSON value of AWSCURRENT
aws secretsmanager get-secret-value --secret-id prod/payments/db \
  --query SecretString --output text

# A specific stage (e.g. roll back to previous)
aws secretsmanager get-secret-value --secret-id prod/payments/db \
  --version-stage AWSPREVIOUS --query SecretString --output text

# Parameter Store — a SecureString MUST pass --with-decryption
aws ssm get-parameter --name /myapp/prod/db/password \
  --with-decryption --query Parameter.Value --output text

# Fetch a whole environment subtree in one call
aws ssm get-parameters-by-path --path /myapp/prod/ --recursive \
  --with-decryption --query 'Parameters[].[Name,Value]' --output text

SDK (application code)

In application code you call the same APIs through the SDK and hold the value in memory only — never write it to disk or an env var. Pseudocode (any language SDK):

client = SecretsManager()
resp   = client.get_secret_value(SecretId="prod/payments/db")
creds  = json.parse(resp.SecretString)   # {username, password, host, port}
db.connect(creds.host, creds.username, creds.password)
# cache `creds` in memory; refresh on a timer or on auth failure

The single most important rule: cache the value in memory and reuse it, refreshing periodically (or when a DB connection fails auth, which signals a rotation). Calling GetSecretValue on every request is both slow and, on Secrets Manager, billed per call.

The Lambda extension (caching) — the recommended Lambda pattern

For AWS Lambda, AWS publishes the AWS Parameters and Secrets Lambda Extension (a layer). It runs a tiny local HTTP cache alongside your function; your code fetches from http://localhost:2773/… instead of calling the AWS API directly. Benefits:

# Inside a Lambda with the extension layer attached:
# Secrets Manager
curl "http://localhost:2773/secretsmanager/get?secretId=prod/payments/db" \
  -H "X-Aws-Parameters-Secrets-Token: $AWS_SESSION_TOKEN"
# Parameter Store (SecureString decrypted)
curl "http://localhost:2773/systemsmanager/parameters/get?name=/myapp/prod/db/password&withDecryption=true" \
  -H "X-Aws-Parameters-Secrets-Token: $AWS_SESSION_TOKEN"

This is the default I recommend for Lambda — it removes both the cost and the cold-start latency of naïve per-invocation fetches. (For non-Lambda apps, AWS also ships language-specific caching libraries, e.g. the AWS Secrets Manager caching client.)

ECS and EKS integration (inject at startup, no code change)

A note on the env-var pattern: injecting a secret as an environment variable is convenient but the value is then visible to anything that can read the process environment (and may surface in crash dumps). Mounting as a file (EKS CSI) or fetching via the extension is tighter; choose based on your threat model.

CloudFormation / IaC dynamic references

Infrastructure-as-code can pull a secret at deploy time without putting it in the template, using dynamic references:

'{{resolve:secretsmanager:prod/payments/db:SecretString:password}}'
'{{resolve:ssm-secure:/myapp/prod/db/password}}'
'{{resolve:ssm:/myapp/prod/db/host}}'

Use these so the value never lands in the template, parameters file, or state — only the reference does.

IAM access control

Access to both services is IAM-governed — by default no principal can read a secret or parameter; you grant least privilege explicitly.

Secrets Manager — scope to specific secret ARNs (or by tag), and remember the KMS half:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadPaymentsDbSecret",
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
      "Resource": "arn:aws:secretsmanager:eu-west-2:111122223333:secret:prod/payments/db-*"
    },
    {
      "Sid": "DecryptWithCmk",
      "Effect": "Allow",
      "Action": "kms:Decrypt",
      "Resource": "arn:aws:kms:eu-west-2:111122223333:key/<cmk-id>"
    }
  ]
}

Two gotchas: secret ARNs end in a random 6-character suffix, so use a trailing -* (or -??????) when you write the ARN by name; and if the secret uses a CMK, the principal needs kms:Decrypt on that key (the AWS-managed key grants this implicitly within the account).

Parameter Store — scope by path prefix, and include KMS for SecureString:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
      "Resource": "arn:aws:ssm:eu-west-2:111122223333:parameter/myapp/prod/*"
    },
    {
      "Effect": "Allow",
      "Action": "kms:Decrypt",
      "Resource": "arn:aws:kms:eu-west-2:111122223333:key/<cmk-id>"
    }
  ]
}

Path-based scoping is the big win for Parameter Store: one statement on /myapp/prod/* grants exactly the prod environment. For both services, prefer tag-based (ABAC) policies at scale so you don’t maintain ARN lists, and grant write actions (PutSecretValue, PutParameter) only to deployment roles, never to running apps.

ABAC: granting by tag instead of by ARN

At scale, listing every secret ARN in every policy becomes unmanageable. The fix is attribute-based access control (ABAC) — grant access based on a tag on the secret rather than its name. Secrets Manager exposes the condition key secretsmanager:ResourceTag/<tag-key> for exactly this; Parameter Store has the equivalent ssm:resourceTag/<tag-key>. A single policy then covers every present and future secret that carries the right tag:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "ReadProdSecretsByTag",
    "Effect": "Allow",
    "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
    "Resource": "*",
    "Condition": {
      "StringEquals": {
        "secretsmanager:ResourceTag/Environment": "prod",
        "aws:PrincipalTag/Environment": "prod"
      }
    }
  }]
}

This says “a principal tagged Environment=prod may read any secret tagged Environment=prod” — no ARNs to maintain, and it self-extends as teams add secrets. The trade-off is that your tagging discipline becomes a security control: a mistagged secret is a hole, so restrict who can call TagResource / UntagResource (a secretsmanager:TagResource grant on an untrusted role is effectively a path to any secret). Pair ABAC with a guardrail that denies changing the Environment tag except from your deployment role. The IAM least-privilege lesson covers the tag-governance side in depth.

Auditing with CloudTrail

Every API call to both services is recorded by AWS CloudTrail — this is how you answer “who read this secret, and when?” in an audit or incident.

AWS Secrets Manager vs Parameter Store

The diagram contrasts the two stores side by side — Secrets Manager with its staging-label versioning and four-step rotation Lambda on the left, SSM Parameter Store with its String/SecureString hierarchy and tiers on the right — and shows the shared retrieval paths (SDK, CLI, the Lambda extension cache, ECS/EKS injection) and the KMS-plus-CloudTrail layer that both rely on for encryption and auditing.

Hands-on lab: store, retrieve, and rotate a secret

This lab uses the AWS CLI and is effectively free: Parameter Store standard tier is free, and you will create a single Secrets Manager secret and delete it immediately (cost is prorated to fractions of a cent). You will store config in Parameter Store, store a secret in Secrets Manager, retrieve both correctly, enable rotation, and clean up. Use a non-production account/Region (examples use eu-west-2).

Prerequisites: the CLI configured (aws configure) with permissions for ssm:*Parameter*, secretsmanager:*, and kms:* on the AWS-managed keys.

Part A — configuration in Parameter Store

# Plain String config (free, not encrypted)
aws ssm put-parameter --name /lab/app/db/host \
  --type String --value "db.internal.example.com"
aws ssm put-parameter --name /lab/app/db/port \
  --type String --value "5432"

# A SecureString secret (KMS-encrypted with the aws/ssm managed key)
aws ssm put-parameter --name /lab/app/db/password \
  --type SecureString --value "S3cr3t-do-not-commit"

# Read the whole environment subtree in ONE call, decrypting secrets
aws ssm get-parameters-by-path --path /lab/app/ --recursive \
  --with-decryption \
  --query 'Parameters[].[Name,Value]' --output table
# validation: shows host, port, and the DECRYPTED password

# Prove --with-decryption is required: omit it and the password is ciphertext
aws ssm get-parameter --name /lab/app/db/password \
  --query Parameter.Value --output text
# -> AQICAHh... (ciphertext, because we did NOT pass --with-decryption)

Part B — a secret in Secrets Manager

# Create a JSON secret (the shape rotation Lambdas expect)
aws secretsmanager create-secret --name lab/app/api \
  --description "Lab third-party API credentials" \
  --secret-string '{"api_key":"AKIA-EXAMPLE","api_secret":"shhh"}'
# note the returned ARN (ends in a random 6-char suffix)

# Retrieve the current value (AWSCURRENT)
aws secretsmanager get-secret-value --secret-id lab/app/api \
  --query SecretString --output text
# -> {"api_key":"AKIA-EXAMPLE","api_secret":"shhh"}

# Update it -> creates a NEW version, moves AWSCURRENT, old -> AWSPREVIOUS
aws secretsmanager put-secret-value --secret-id lab/app/api \
  --secret-string '{"api_key":"AKIA-EXAMPLE-2","api_secret":"shhh2"}'

# Roll back by reading the PREVIOUS stage
aws secretsmanager get-secret-value --secret-id lab/app/api \
  --version-stage AWSPREVIOUS --query SecretString --output text
# -> the original value (validation that staging labels moved)

# Inspect the version-to-label mapping
aws secretsmanager describe-secret --secret-id lab/app/api \
  --query VersionIdsToStages
# -> shows which VersionId carries AWSCURRENT vs AWSPREVIOUS

Part C — generate a strong password (the rotation helper)

aws secretsmanager get-random-password \
  --password-length 24 --exclude-punctuation \
  --query RandomPassword --output text
# validation: a 24-char random password (what rotation step 1 uses)

Part D — the cross-service bridge

# Read the Secrets Manager secret THROUGH the Parameter Store API
aws ssm get-parameter \
  --name /aws/reference/secretsmanager/lab/app/api \
  --with-decryption --query Parameter.Value --output text
# -> the same JSON value, fetched via the SSM API (no Secrets Manager call in your code)

Cleanup (avoid charges)

# Parameter Store (free, but tidy up)
aws ssm delete-parameters --names \
  /lab/app/db/host /lab/app/db/port /lab/app/db/password

# Secrets Manager — force-delete now (skip the recovery window) since this is a lab
aws secretsmanager delete-secret --secret-id lab/app/api \
  --force-delete-without-recovery

Cost note: Parameter Store standard tier is free. The one Secrets Manager secret is billed at ~$0.40/month prorated — held for a few minutes it is a fraction of a cent, and --force-delete-without-recovery stops billing immediately. In production you would omit --force-delete-without-recovery and let the 7–30 day recovery window protect you. The real-world cost levers are: number of secrets in Secrets Manager (store config in Parameter Store instead), API call volume (cache aggressively — use the Lambda extension), replica secrets (each replica is billed), and advanced-tier parameters (use Intelligent-Tiering so you only pay when a parameter actually needs advanced).

Going deeper

The core above is enough to pass the exam and ship safely. This section is for the production nuances and failure modes that separate “it worked in the demo” from “it survived the incident.”

Rotation failure modes and the stuck AWSPENDING

Rotation is a small distributed state machine, and its failure modes all trace back to the staging labels. The one you will eventually hit: a rotation that fails after createSecret but before finishSecret leaves an AWSPENDING version attached with a value that was never promoted. Secrets Manager will not start a second rotation while one is “in progress,” and because createSecret is required to be idempotent (“if AWSPENDING already exists, do nothing”), a buggy Lambda that isn’t idempotent can wedge itself — it keeps finding the old pending version and never converges. The fixes: make createSecret genuinely idempotent (check for an existing AWSPENDING first), and if a rotation is truly stuck, remove the AWSPENDING staging label from the orphaned version with update-secret-version-stage so the next run starts clean. Crucially, AWSCURRENT is never touched until finishSecret, so a failed rotation never breaks running apps — it just needs a human to unstick the pending version.

Two more real-world snags: the rotation Lambda must be able to reach the target (in-VPC databases need the Lambda in the same VPC/subnets with a security-group path, or rotation fails at setSecret / testSecret), and alternating-users rotation needs a master/superuser secret with privileges to manage the two users — omit it and rotation can create a user it cannot later alter. Alarm on the rotation-failure event via an EventBridge rule; a silent rotation failure is a credential quietly ageing past your compliance window.

Throughput, quotas, and why caching is non-negotiable

Both services are API-metered and both throttle. Parameter Store standard throughput defaults to roughly 40 transactions per second (shared across the Get* operations in a Region); you can raise it to thousands with the higher-throughput setting, but that is billed per call and is a deliberate opt-in. Secrets Manager GetSecretValue has its own high-but-finite request quota. The practical consequence is identical for both: do not fetch on every request. A hot Lambda that calls GetSecretValue per invocation will, at scale, both blow your Secrets Manager bill ($0.05 per 10k calls adds up in a busy service) and eventually throttle. The Parameters and Secrets Lambda Extension (or a caching client outside Lambda) is not a nicety — it is how you stay under quota and control cost, with a TTL short enough that a rotated value is picked up within minutes, not hours. batch-get-secret-value further collapses “read ten secrets at boot” into a single call.

Detecting and containing leaked secrets

The store keeps secrets safe; the leak is almost always your own code, logs, or git history. Defence in depth here:

And the rule that underpins all of it: rotate on the assumption you will leak. Rotation shrinks the window in which a leaked credential is useful; if a key was exposed, rotation (or deletion) is the remediation, and testSecret guarantees you never promote a broken replacement.

AppConfig: when Parameter Store isn’t enough for config

Parameter Store is a store — it hands you a value. When you need to change configuration safely in a running fleet — feature flags, kill switches, tunables — reach for AWS AppConfig (also part of Systems Manager). AppConfig adds what a plain parameter lacks: validators (JSON Schema, or a Lambda that vets the new config before it ships), deployment strategies (roll the change out gradually — canary, then linear, then everyone), a bake time with CloudWatch-alarm rollback (if an alarm fires mid-deploy, AppConfig automatically reverts), and first-class feature flags. It can source config from its own hosted store, S3, or Parameter Store. The mental split: Parameter Store / Secrets Manager = where a value lives; AppConfig = how a value change is safely delivered. Feature flags in a raw SecureString with no rollout control is the anti-pattern AppConfig exists to fix.

Injection gotchas: ECS, EKS, and dynamic references

The zero-code injection paths each hide an edge you must know:

Cross-Region replication and the DR runbook

Replication is easy to enable and easy to misunderstand operationally. Three facts change how you design with it. First, rotation happens only on the primary — replicas receive the rotated value automatically, so you never configure rotation per Region; a replica with its own rotation is a mistake. Second, to actually fail over you promote a replica to a standalone secret (stop-replicating-to-replica-regions / promote), at which point it detaches from the primary and becomes independently writable and rotatable — that is the deliberate, one-way DR action, not something that happens on its own. Third, each replica is re-encrypted with a KMS key in its own Region (you pick it), so the reader in us-east-1 decrypts with a us-east-1 key and never makes a cross-Region KMS call. The gotcha that bites teams: you cannot create a replica in a Region that already holds a secret of the same name — resolve the name collision first. Monitor replica status (a replica can show Failed if its Region’s KMS key is missing or its permissions are wrong), and remember every replica is billed as a full secret — so replicate the handful of secrets your DR plan actually needs, not the entire vault.

AWS-managed key vs CMK: when the extra key is worth it

Both stores default to a free AWS-managed key (aws/secretsmanager, aws/ssm), and for single-account, low-ceremony workloads that is the right choice — zero setup, no monthly key charge, and kms:Decrypt is granted implicitly to principals in the account. You must step up to a customer managed key (CMK) when you need any of: cross-account sharing (the managed key cannot be shared, so a CMK is mandatory), an independent, auditable key policy (a second gate you can tighten or Deny separately from the secret’s own policy), separation of duties (a security team owns the key while app teams own the secret), or control over key rotation and provenance. The costs of a CMK are real but small: ~$1/month per key plus per-request KMS charges, and — the operational catch — a CMK does not grant decrypt implicitly, so every reader now needs an explicit kms:Decrypt statement (the single most common “why is this AccessDenied?” after adding a CMK). Rule of thumb: default to the managed key; reach for a CMK the moment you need cross-account, an independent audit boundary, or separated ownership.

Cost math at scale

The pricing asymmetry drives real architecture. Ten thousand secrets in Secrets Manager is ~$4,000/month before a single API call; the same ten thousand values as standard Parameter Store parameters is $0. So the discipline is: secrets that must rotate or be shared → Secrets Manager; everything else → Parameter Store, and within Parameter Store default to standard, letting Intelligent-Tiering promote only the handful of parameters that genuinely need advanced (a policy, or a value >4 KB). Every replica secret is billed as a full secret, so multi-Region DR doubles (or triples) per-secret cost — replicate the secrets that need it, not the whole vault. And cache reads: the API-call line item is invisible until a hot path makes it enormous.

Common mistakes & troubleshooting

Symptom Likely cause Fix
get-parameter returns ciphertext (AQICAH…) not the value Forgot --with-decryption on a SecureString Pass --with-decryption (and ensure the role has kms:Decrypt)
AccessDeniedException reading a secret despite a Secrets Manager Allow The secret uses a CMK and the principal lacks kms:Decrypt on it Add kms:Decrypt for the key (or use the AWS-managed key, which grants it in-account)
Cross-account read is AccessDenied Only the IAM policy was set, not the resource policy, or the secret uses the managed KMS key Add a resource policy on the secret and use a CMK with cross-account kms:Decrypt
ARN-scoped IAM policy never matches the secret Secret ARNs end in a random 6-char suffix; the policy used the bare name End the resource with -* (e.g. …:secret:prod/db-*)
App breaks right after a rotation App cached the old password forever / no retry on auth failure Cache with a TTL and refresh on auth failure; prefer the Lambda extension’s caching
Rotation Lambda fails at setSecret/testSecret Lambda can’t reach the DB (no VPC config, SG, or it lacks the master/superuser secret) Put the Lambda in the DB’s VPC/subnets, open the SG, grant it the master secret
put-parameter fails with “already exists” Overwrite is not implicit Add --overwrite (only deployment roles should have PutParameter)
Secrets Manager bill is unexpectedly high Per-call billing from fetching on every request, or many config values stored as secrets Cache reads (Lambda extension), move plain config to Parameter Store
Parameter rejected: value too large String/standard exceeds 4 KB Use the advanced tier (8 KB) via --tier Advanced/Intelligent-Tiering, or split the value

Common beginner mistakes

These are misconceptions rather than error messages — the wrong mental model that leads you to build the wrong thing. (For symptom → cause → fix of concrete errors, see the troubleshooting table above.)

Best practices

Security notes

Interview & exam questions

  1. When do you use Secrets Manager vs SSM Parameter Store? Secrets Manager when you need automatic rotation, cross-account sharing, multi-Region replication, large (≤64 KB) values, or built-in password generation. Parameter Store for plain configuration and for secrets you’ll rotate yourself or that don’t rotate — because its standard tier is free and it has hierarchical paths. Both can store secrets; rotation and sharing are what tip the choice to Secrets Manager.

  2. Explain Secrets Manager staging labels. AWSCURRENT points to the live version (what GetSecretValue returns by default); AWSPENDING is a new version created during rotation but not yet promoted; AWSPREVIOUS is the prior version (rollback). Rotation is literally moving AWSCURRENT onto the tested AWSPENDING version.

  3. Walk through the four-step rotation model. createSecret (generate a new value, store as AWSPENDING), setSecret (apply it to the target service), testSecret (verify it works), finishSecret (move AWSCURRENT to the pending version). Until step 4, AWSCURRENT still points to the working credential, so there’s no downtime.

  4. Single-user vs alternating-users rotation — when each? Single-user changes the one user’s password (simplest, brief reconnection window). Alternating-users keeps two users and flips between them so the old credential stays valid until clients move (zero-downtime, needs a master secret). Use alternating-users for databases that can’t tolerate any auth blip.

  5. How do you share a secret with another AWS account? Attach a resource policy on the secret allowing the other account’s principal GetSecretValue, and use a customer managed KMS key with a key policy/grant allowing that principal kms:Decrypt (the AWS-managed aws/secretsmanager key can’t be shared cross-account).

  6. What’s the difference between String, StringList, and SecureString? String is one plaintext value; StringList is a comma-separated list (plaintext); SecureString is a single value encrypted with KMS. Reading a SecureString requires --with-decryption and kms:Decrypt.

  7. Standard vs advanced Parameter Store tier? Standard: up to 4 KB, 10,000 params/Region, free, no policies. Advanced: up to 8 KB, 100,000 params, ~$0.05/param/month, supports parameter policies and a higher-throughput option. Intelligent-Tiering auto-promotes only when needed.

  8. How do you fetch all of an environment’s configuration in one call? Name parameters under a path (/app/prod/...) and call get-parameters-by-path --recursive --with-decryption on the prefix — one call returns the whole subtree.

  9. What’s the recommended way to read secrets in a Lambda? The AWS Parameters and Secrets Lambda Extension — a layer running a local HTTP cache that serves both Secrets Manager and Parameter Store, caching across warm invocations to cut API cost and latency, with a configurable TTL so values refresh after rotation.

  10. How do ECS and EKS get secrets without code changes? ECS injects them as environment variables via the task definition secrets/valueFrom (resolved by the task execution role at start). EKS uses the Secrets Store CSI Driver with the AWS provider (ASCP) to mount secrets as files, governed by IRSA/Pod Identity.

  11. You’re getting AccessDenied reading a secret even though the IAM policy allows GetSecretValue. Why? The secret is encrypted with a CMK and the principal lacks kms:Decrypt on that key — both permissions are required. (Also check the ARN’s random suffix and, for cross-account, the resource policy.)

  12. How do you audit who read a secret? CloudTrail records GetSecretValue/GetParameter as management events (principal, time, IP, ARN), and the KMS Decrypt call is logged separately — two independent trails. Send CloudTrail to CloudWatch and alarm on unexpected reads or deletes.

  13. What happens when you delete a secret? By default deletion is scheduled after a 7–30 day recovery window (default 30), during which restore-secret undoes it; --force-delete-without-recovery deletes immediately (and stops billing). Parameter Store deletes are immediate (no recovery window).

  14. How can Parameter Store consume a Secrets Manager secret? Read the parameter name /aws/reference/secretsmanager/<secret-name> through the SSM API — Parameter Store fetches the value from Secrets Manager — so tools that only speak Parameter Store can use Secrets Manager secrets unchanged.

Quick check

  1. Which staging label does GetSecretValue return when you don’t specify a version?
  2. Name the four rotation steps in order.
  3. What must you pass to get-parameter to read a SecureString value (not ciphertext)?
  4. Which Parameter Store tier is free, and what is its per-parameter size limit?
  5. What two permissions does a principal need to read a CMK-encrypted secret?

Answers

  1. AWSCURRENT — the current, live version.
  2. createSecret → setSecret → testSecret → finishSecret.
  3. --with-decryption (and the role needs kms:Decrypt on the key).
  4. The standard tier is free, with a 4 KB per-parameter value limit (advanced is 8 KB and billed per parameter).
  5. secretsmanager:GetSecretValue on the secret and kms:Decrypt on the customer managed key.

Practice challenges

Six graded exercises, easy to hard. Each is runnable in a non-prod account/Region (Parameter Store standard is free; the one secret costs a fraction of a cent). Try each before opening the solution.

1 (Beginner) — Store and read a config value and a secret. Put /train/app/log_level as a plain String with value info, and /train/app/db_password as a SecureString. Read both back so the password comes out as plaintext, not ciphertext.

<details> <summary>Solution</summary>

aws ssm put-parameter --name /train/app/log_level --type String --value info
aws ssm put-parameter --name /train/app/db_password --type SecureString --value "S3cr3t-demo"
aws ssm get-parameter --name /train/app/log_level --query Parameter.Value --output text
aws ssm get-parameter --name /train/app/db_password --with-decryption --query Parameter.Value --output text

Why: a SecureString returns ciphertext unless you pass --with-decryption (and hold kms:Decrypt) — proving the “encrypted means a separate decrypt step” model. </details>

2 (Beginner) — Fetch a whole environment in one call. Add /train/app/region = eu-west-2, then retrieve every parameter under /train/app/ with a single command, decrypting the secure one.

<details> <summary>Solution</summary>

aws ssm put-parameter --name /train/app/region --type String --value eu-west-2
aws ssm get-parameters-by-path --path /train/app/ --recursive \
  --with-decryption --query 'Parameters[].[Name,Value]' --output table

Why: get-parameters-by-path --recursive is the idiomatic “load my whole config at boot in one call” pattern, and it keeps IAM scoping to a single path prefix. </details>

3 (Intermediate) — Version and roll back a secret. Create a Secrets Manager secret train/api with {"key":"v1"}, update it to {"key":"v2"}, then read the value that AWSCURRENT used to point at — without knowing its VersionId.

<details> <summary>Solution</summary>

aws secretsmanager create-secret --name train/api --secret-string '{"key":"v1"}'
aws secretsmanager put-secret-value --secret-id train/api --secret-string '{"key":"v2"}'
aws secretsmanager get-secret-value --secret-id train/api \
  --version-stage AWSPREVIOUS --query SecretString --output text

Why: updating moves AWSCURRENT and demotes the old version to AWSPREVIOUS, so --version-stage AWSPREVIOUS is a label-based rollback with no VersionId bookkeeping. </details>

4 (Intermediate) — Read a Secrets Manager secret through the Parameter Store API. Using only an aws ssm command, fetch the value of the train/api secret you created above.

<details> <summary>Solution</summary>

aws ssm get-parameter \
  --name /aws/reference/secretsmanager/train/api \
  --with-decryption --query Parameter.Value --output text

Why: the /aws/reference/secretsmanager/<name> bridge lets tools and code that only speak Parameter Store consume Secrets Manager secrets with no code change — one retrieval path for both stores. </details>

5 (Advanced) — Write a least-privilege, tag-based read policy. Draft an IAM policy that lets a principal read any secret tagged Environment=train (and decrypt it with CMK arn:aws:kms:eu-west-2:123456789012:key/EXAMPLE), without naming a single secret ARN.

<details> <summary>Solution</summary>

{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
      "Resource": "*",
      "Condition": { "StringEquals": { "secretsmanager:ResourceTag/Environment": "train" } } },
    { "Effect": "Allow", "Action": "kms:Decrypt",
      "Resource": "arn:aws:kms:eu-west-2:123456789012:key/EXAMPLE" }
  ]
}

Why: the secretsmanager:ResourceTag/Environment condition is ABAC — it self-extends to future secrets carrying the tag, and it still needs the second kms:Decrypt statement because a CMK read is always two permissions. </details>

6 (Advanced) — Design a zero-downtime rotating credential and clean up. For a high-throughput RDS database that cannot tolerate an auth blip, state which rotation strategy you’d choose and why, whether you’d use a rotation Lambda or managed rotation, and the one command that deletes the train/api secret immediately while leaving no lingering bill.

<details> <summary>Solution</summary>

aws secretsmanager delete-secret --secret-id train/api --force-delete-without-recovery
aws ssm delete-parameters --names /train/app/log_level /train/app/db_password /train/app/region

Why: alternating-users is the standard answer for “no downtime,” managed rotation removes the operational burden for supported engines, and --force-delete-without-recovery stops billing at once — acceptable only in throwaway environments (production keeps the 7–30 day window). </details>

Exercise

Take an application you know that currently keeps a database password and a few config values in environment variables or a checked-in .env file. Produce a one-page secrets-and-config design that: (a) puts the non-sensitive config (host, port, flags) in Parameter Store under a /<app>/<env>/... hierarchy and lists the single get-parameters-by-path call the app makes at startup; (b) decides where the database password lives — Secrets Manager with automatic rotation (state single- or alternating-users and the schedule) or Parameter Store SecureString if it won’t auto-rotate — with a one-line justification; © writes the least-privilege IAM policy the running app needs (path/ARN scope plus the KMS Decrypt); (d) chooses the retrieval mechanism (Lambda extension, ECS secrets injection, or EKS CSI) for the app’s runtime; and (e) names the CloudTrail alarm you’d set to detect an unexpected read. Keep it to choices and one-line justifications — the goal is to practise the decision framework end to end.

Certification mapping

This lesson maps to the security and developer domains of the AWS certification path. For AWS Certified Security – Specialty (SCS-C02), expect questions on choosing Secrets Manager vs Parameter Store, the rotation model and staging labels, cross-account secret sharing (resource policy plus CMK kms:Decrypt), and auditing reads via CloudTrail and KMS — the “both permissions are required” and “managed key can’t be shared cross-account” points are near-guaranteed. For AWS Certified Developer – Associate (DVA-C02), the emphasis is on retrieving secrets/parameters in code, the Lambda extension caching, ECS secrets/valueFrom injection, dynamic references in CloudFormation, and SecureString with --with-decryption. For Solutions Architect – Associate (SAA-C03), expect the high-level “which service” decision and the multi-Region replica concept. Across all of them, the cost contrast (Parameter Store standard free vs Secrets Manager per-secret) is a recurring distractor-buster.

Glossary

Next steps

You now know both stores end to end — what each is for, every option they expose, how to retrieve values safely, and how to lock them down and audit them. The natural next move is to take rotation and cross-account sharing to production depth: read Secrets Manager Rotation at Scale: Custom Rotation Lambdas, RDS Credentials, and Cross-Account Sharing, which implements the four-step rotation Lambda in full, contrasts single- and alternating-user RDS strategies, shows how to write a custom rotator for third-party credentials, and covers cross-account sharing via resource policies and KMS grants. After that, the course continues with decoupling and messaging in AWS Messaging Fundamentals: SQS, SNS & EventBridge — When to Use Which, where the IAM and resource-policy patterns you learned here reappear on queues and topics.

AWSSecrets ManagerParameter StoreSSMSecurityRotation
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments