Filter concepts by levelShowing all levels.

AWS · Section 33

Secrets Management

Level
intermediate
Read
10 min
Concepts
1

Secrets Manager and Parameter Store both replace a hard-coded secret with a runtime call, but they solve different problems: Secrets Manager is purpose-built for credentials and can rotate them automatically, while Parameter Store is general configuration storage — a plain String/StringList or a KMS-encrypted SecureString — with no rotation built in. IAM scopes exactly which principal can read which secret or parameter, and the same discipline that keeps a secret out of Secrets Manager's own access logs applies to every other place a secret could leak from: an AMI, source code, a Docker image layer, or a plain environment file all ship the secret with the artifact instead of fetching it at runtime.

What is true here

  1. Secrets Manager: purpose-built for credentials, supports automatic rotation, versions each secret via staging labels during rotation.
  2. Parameter Store: general configuration data as String/StringList or KMS-encrypted SecureString, keeps 100 versions automatically, no automatic rotation.
  3. AWS's own guidance: once a value is a credential (DB password, API key, OAuth token), use Secrets Manager, not a SecureString parameter.
  4. A secret baked into an AMI, source code, a Docker image layer, or a plain .env file ships with that artifact permanently — rotating the real credential does not touch existing copies.
  5. Scope IAM access to specific secret/parameter ARNs — Resource: "*" turns one compromised role into account-wide secret exposure.

What you will be able to do

  • Choose Secrets Manager over Parameter Store for anything that is a credential, and vice versa for general configuration
  • Explain why automatic rotation only exists on the Secrets Manager side of that choice
  • Replace a hard-coded secret (AMI, source code, Docker image, .env file) with a runtime retrieval call
  • Scope an IAM policy to the specific secret ARNs a workload actually needs, not account-wide access

Secrets Management

Secrets Manager vs Parameter Store, versioning, rotation, secure retrieval, IAM permissions, and where secrets must never live.

Secrets Manager and Parameter Store

coreintermediate

Secrets Manager stores credentials — database passwords, API keys, OAuth tokens — and can rotate them on a schedule automatically. Parameter Store (part of Systems Manager) stores configuration data — AMI IDs, endpoint URLs, feature flags — as plain `String`/`StringList` values or KMS-encrypted `SecureString` values, but has no automatic rotation. A secret hard-coded in source code, baked into a Docker image, baked into an AMI, or dropped in a plain `.env` file sits wherever that artifact goes — copied, logged, or leaked with it. Both services replace that hard-coded value with a runtime call: the application asks for the secret when it starts, instead of shipping the secret inside it.

Think of it as

A hard-coded secret travels with the artifact that contains it — the Git history, the Docker image layer, the AMI snapshot — forever, even after you rotate the real credential, because nothing forces old copies to update. Secrets Manager and Parameter Store both break that link: the artifact ships a reference (an ARN or a parameter name, itself not secret), and the actual value is fetched at runtime from a service that can change the value later without touching the artifact at all. Secrets Manager adds one more piece on top: an automatic rotation schedule that changes the credential AND its stored value together, on its own, so a leaked credential has a shorter useful life even if nobody notices the leak.

What we're doing: Retrieve a database password from Secrets Manager at application startup, instead of reading it from an environment variable set by a plain .env file.

app/config.pypython
import boto3
import json

client = boto3.client("secretsmanager", region_name="us-east-1")

def get_db_password():
    response = client.get_secret_value(SecretId="prod/orders-db/password")
    return json.loads(response["SecretString"])["password"]

DB_PASSWORD = get_db_password()  # fetched at startup, never written to disk or image
1
boto3 is the AWS SDK — the same call works from a Lambda function, an ECS task, or an EC2 instance, as long as its IAM role/profile is allowed to call GetSecretValue on this secret's ARN.
2
json is needed because Secrets Manager stores the whole secret as one string — a JSON blob here holding {"username": ..., "password": ...} together, a common pattern for DB credentials.
6
get_secret_value() is the runtime call that replaces a hard-coded value — nothing about the secret's actual value exists in this file, the Docker image built from it, or Git history.
7
SecretString holds the JSON payload; a binary secret would come back in SecretBinary instead.

Why this works: The application source, the Docker image built from it, and the Git history behind it all contain zero copies of the actual password — only a resource identifier (the SecretId) and a runtime call. Rotating the real credential later means updating it once, in Secrets Manager, with no code change and no redeploy.

Baking a database password into a Docker image via an ENV instruction or a checked-in .env file

Wrong

dockerfile
FROM python:3.13-slim
ENV DB_PASSWORD=Sup3rSecret!
COPY . /app

Better

dockerfile
FROM python:3.13-slim
# No secret baked in — the running container calls Secrets Manager
# at startup, using the task/instance role's IAM permissions.
COPY . /app

What you see: Anyone who can pull or inspect the image (docker history, docker inspect, or a registry with weak access controls) reads the password in plain text — and rotating the real credential does nothing to the copy already baked into every image already built and pushed.

Why: An image layer is immutable and gets distributed independently of the running container's lifecycle — a secret written into a layer is now part of that layer's content forever, extractable from any copy of the image, regardless of what the actual credential is changed to afterward.

Secrets Manager vs Parameter Store

Secrets Manager

  • +Purpose-built for credentials — DB passwords, API keys, OAuth tokens
  • +Automatic rotation (Lambda function or native DB integration)
  • +Versioned with staging labels as part of rotation
  • +Billed per secret per month, plus per API call

Parameter Store

  • General configuration data — AMI IDs, endpoints, feature flags
  • No automatic rotation — build your own if a SecureString needs one
  • Plain String/StringList, or KMS-encrypted SecureString
  • Standard tier free; advanced tier and high throughput billed
  • Secrets Manager
    • Purpose-built for credentials — DB passwords, API keys, OAuth tokens
    • Automatic rotation (Lambda function or native DB integration)
    • Versioned with staging labels as part of rotation
    • Billed per secret per month, plus per API call
  • Parameter Store
    • General configuration data — AMI IDs, endpoints, feature flags
    • No automatic rotation — build your own if a SecureString needs one
    • Plain String/StringList, or KMS-encrypted SecureString
    • Standard tier free; advanced tier and high throughput billed

Remember: Secrets Manager: credentials, automatic rotation, versioned via staging labels. Parameter Store: general config, String/StringList/SecureString, no automatic rotation, keeps 100 versions free. Neither belongs in an AMI, source code, a Docker image layer, or a plain .env file — all four ship the secret with the artifact instead of fetching it at runtime. Scope IAM access to specific secret ARNs, never Resource: "*".

Advertisement