Secrets Manager, Parameter Store and KMS
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Commands and behaviour on this page are current for AWS CLI v2 (2.37 line), the AWS Workload Credentials Provider
3.1.x, the AWS Secrets and Configuration Provider (ASCP) for the Kubernetes Secrets Store CSI Driver and Spring Cloud
AWS 4.x (Spring Boot 4) or 3.4.x (Spring Boot 3.5), all checked on 2026-10-01; re-verify against aws --version
and the linked reference pages before relying on them in a pipeline. Examples use us-east-1 and the placeholder
account ID 111122223333; both are stand-ins for your own values, and every secret value below is an obvious
placeholder such as <your-password>.
Every Bookshelf service that touches a database password, an API key or a signing key reads it from AWS instead of
from an environment variable baked into an image or a file in Git. Three services share the work, and picking the
right one is most of the design: AWS Secrets Manager stores and rotates secrets (the Aurora credentials in
bookshelf/catalog/db), Systems Manager Parameter Store stores configuration, optionally encrypted
(/bookshelf/), and *AWS Key Management Service (KMS) holds the encryption keys that protect both, as well as S3
buckets, EBS volumes, RDS storage and almost everything else on AWS. This page covers each service, how they fit
together, and every consumption path Bookshelf uses: the Java SDK, Spring Cloud AWS, ECS task definitions, Lambda
and EKS. Plain, non-secret configuration and feature flags are covered on
Runtime Configuration; the IAM concepts used throughout (roles, trust and permission policies)
on Identity and Access. For the Azure counterpart, see
Key Vault and Secrets, where one service (Key Vault) covers what AWS splits across three.
Which service for what
| Aspect | Secrets Manager | Parameter Store (SecureString) |
KMS |
|---|---|---|---|
Purpose |
Store, version and rotate secrets: database credentials, API keys, OAuth client secrets. |
Store configuration hierarchies; |
Create and use cryptographic keys; the key material never leaves KMS in plaintext. |
Rotation |
Built in: managed rotation for RDS/Aurora master passwords and managed external secrets, or your own rotation Lambda function, on a schedule. |
None. You change the value yourself (or with your own automation). |
Automatic (default 365 days, 90 to 2,560 configurable) and on-demand rotation of symmetric keys. |
Value size |
Up to 65,536 bytes. |
4 KB (standard) or 8 KB (advanced). |
|
Versioning |
Versions with staging labels |
Numeric versions plus labels; the history is kept. |
Key material versions are kept for decryption; the key ID never changes. |
Cross-Region |
Replicate a secret to other Regions; replicas stay in sync. |
No native replication; copy with automation. |
Multi-Region keys with the same key material in several Regions. |
Cross-account |
Resource-based policy on the secret (plus a customer managed key for the encryption). |
Advanced parameters can be shared through AWS Resource Access Manager. |
Key policy (and grants) allow principals from other accounts. |
Pricing model |
Per secret per month plus per API calls. |
Standard parameters have no charge; advanced parameters are charged per parameter per month plus API calls. |
Per customer managed key per month plus per request; AWS managed keys have no monthly fee. |
Choose it when |
The value is a credential that should rotate, be audited per read, or be replicated. |
The value is configuration, or a low-risk secret, and you want a free or cheap, hierarchical store. |
You need to control who can decrypt data, audit key use, or encrypt your own data directly. |
A rule of thumb that fits Bookshelf: database credentials and third-party API keys go to Secrets Manager, feature toggles and endpoints go to Parameter Store (standard tier), and the keys behind both are customer managed KMS keys when you need cross-account access, a custom key policy or per-use audit, and the AWS managed keys otherwise. Newer guides often state that "Parameter Store is free, Secrets Manager is not" as the whole decision; the difference that matters is rotation, versioning with labels, cross-Region replication and per-secret resource policies, which Parameter Store does not provide.
AWS KMS
AWS KMS creates and controls cryptographic keys and performs encryption, decryption, signing and key generation inside FIPS 140-validated hardware security modules (HSMs). Applications call the KMS API; they do not hold the key. Almost every AWS service that encrypts data integrates with KMS (S3 SSE-KMS, EBS, RDS, Secrets Manager, Lambda environment variables, EKS secrets), so understanding KMS is the foundation for everything else on this page.
Key types
| Category | What it is |
|---|---|
AWS owned keys |
Keys that AWS creates and manages in its own accounts to protect resources by default (for example DynamoDB tables with default encryption). They are invisible to you: no key policy, no CloudTrail entries, no charge. |
AWS managed keys |
Keys that AWS creates in your account on behalf of a service, with aliases like |
Customer managed keys (CMKs) |
Keys you create, name with an alias, control with a key policy, rotate, disable and schedule for deletion. This is the category the rest of this section covers. Each costs a monthly fee while it exists. |
Within the customer managed category, the key spec and key usage decide what the key can do:
| Key type | Typical key usage | Use it for |
|---|---|---|
Symmetric ( |
|
The default and by far the most common: envelope encryption, service integrations, Secrets Manager and |
Asymmetric (RSA, ECC, SM2 in China) |
|
Signing JWTs or documents, or encrypting data for parties outside AWS that hold only the public key. No automatic rotation. |
HMAC |
|
Message authentication codes where the secret must stay in KMS. No automatic rotation. |
Post-quantum signing (ML-DSA) |
|
Long-lived signatures that must resist quantum attacks; verify the current list of key specs in the KMS developer guide. |
The origin of the key material can be KMS itself (the default), imported material you supply, a CloudHSM key store (a single-tenant HSM cluster you own, for compliance that requires dedicated hardware) or an external key store (the key stays in an HSM outside AWS). CloudHSM and external key stores add cost and operational burden and are only worth it when a regulation names them; Bookshelf uses the default origin.
Key policies, IAM policies and grants
Three mechanisms authorise key use, and they combine differently from most AWS services:
-
The key policy is the primary access control and the only one that is mandatory. Unlike an S3 bucket policy, a KMS key with no statement allowing the account root principal cannot be managed by IAM at all. The default key policy created by the console contains a statement that gives the account root
kms:*, which is what delegates authorisation to IAM policies in the same account. -
IAM policies grant KMS permissions to roles and users, but only if the key policy permits IAM to do so (the root statement above) or names the principal directly.
-
Grants are programmatic, temporary, narrowly scoped permissions added to a key without editing its key policy. AWS services use them when you create an encrypted resource (an EBS volume attached to an instance, an RDS instance); applications use them for delegation, passing a specific operation set to a specific principal, with optional constraints on the encryption context. Grants are eventually consistent (use the grant token to use one immediately) and are removed by retiring or revoking them.
A key policy for the Bookshelf data key alias/bookshelf-data that separates administrators (whose statement grants no kms:Decrypt) from users (the catalog-api task role and the Secrets Manager service acting for it):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EnableIamPoliciesInThisAccount",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "KeyAdministrators",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/bookshelf-key-admin" },
"Action": [
"kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*",
"kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:TagResource",
"kms:UntagResource", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion",
"kms:RotateKeyOnDemand"
],
"Resource": "*"
},
{
"Sid": "UseTheKeyOnlyThroughSecretsManagerAndSsm",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/catalog-api-task-role" },
"Action": [ "kms:Decrypt", "kms:DescribeKey" ],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": [
"secretsmanager.us-east-1.amazonaws.com",
"ssm.us-east-1.amazonaws.com"
]
}
}
}
]
}
The kms:ViaService condition means the role can use the key only when the request comes from Secrets Manager or
Systems Manager on its behalf, never by calling kms:Decrypt directly. The root statement is what keeps the key
manageable if the administrator role is deleted; removing it is how people lock themselves out of a key, and then
need AWS Support to recover it. Do not put user-supplied data in the key policy; see
Key policies in AWS KMS.
Envelope encryption
Encrypt accepts at most 4,096 bytes, and sending large payloads to a remote service is slow. KMS and the AWS
Encryption SDK therefore use envelope encryption: ask KMS for a unique data key, encrypt the data locally with
the plaintext copy, discard it, and store the encrypted copy of the data key next to the ciphertext. To decrypt,
send the stored encrypted data key to KMS (Decrypt), receive the plaintext data key and decrypt locally. Only the
small data key ever travels to KMS, and each object gets its own key.
The encryption context is a set of non-secret key/value pairs (for example app=bookshelf and tenant=<tenant-id>)
that is cryptographically bound to the ciphertext, logged in CloudTrail in plaintext and required again on
decryption. Use it to tie a ciphertext to its purpose and to write policies such as "decrypt only when
kms:EncryptionContext:app equals `bookshelf`". AWS services do this for you: S3, EBS, RDS and Secrets Manager all
use envelope encryption internally with their own data keys under your KMS key.
Create a key
In the AWS Management Console
-
Open Key Management Service (KMS) and choose the correct Region in the selector; keys are Regional.
-
Choose Customer managed keys > Create key.
-
Key type Symmetric, Key usage Encrypt and decrypt. Under Advanced options, leave Key material origin as KMS and choose Single-Region key (or Multi-Region key, see Multi-Region keys).
-
Set the alias
bookshelf-data(aliases are stored asalias/bookshelf-data), a description and tags. -
Define key administrative permissions (choose
bookshelf-key-admin) and Define key usage permissions (choosecatalog-api-task-role). The console writes a key policy from these choices that you can review on the last step and edit. -
Finish. Open the key and enable Key rotation on its Key rotation tab.
With the AWS CLI
export AWS_REGION=us-east-1
KEY_ID=$(aws kms create-key \
--description "Bookshelf data key" \
--key-spec SYMMETRIC_DEFAULT --key-usage ENCRYPT_DECRYPT \
--policy file://key-policy.json \
--tags TagKey=app,TagValue=bookshelf \
--query KeyMetadata.KeyId --output text)
aws kms create-alias --alias-name alias/bookshelf-data --target-key-id "$KEY_ID"
# Small payload: direct encrypt / decrypt with an encryption context
aws kms encrypt --key-id alias/bookshelf-data \
--plaintext fileb://plain.txt \
--encryption-context app=bookshelf \
--query CiphertextBlob --output text | base64 --decode > cipher.bin
aws kms decrypt --ciphertext-blob fileb://cipher.bin \
--encryption-context app=bookshelf \
--query Plaintext --output text | base64 --decode
# Envelope encryption: get a data key (Plaintext for local use, CiphertextBlob to store)
aws kms generate-data-key --key-id alias/bookshelf-data --key-spec AES_256 \
--encryption-context app=bookshelf
aws kms decrypt needs no --key-id for symmetric keys because the key identity is embedded in the ciphertext
(pass it anyway to make the policy intent explicit). Reference: the
AWS CLI Command Reference — kms and
Create a KMS key.
To delegate a narrow permission without touching the key policy, add and later retire a grant:
aws kms create-grant --key-id alias/bookshelf-data \
--grantee-principal arn:aws:iam::111122223333:role/orders-api-task-role \
--operations Decrypt GenerateDataKey \
--constraints EncryptionContextSubset={app=bookshelf}
aws kms list-grants --key-id alias/bookshelf-data
aws kms revoke-grant --key-id alias/bookshelf-data --grant-id <grant-id>
See Grants in AWS KMS.
Key rotation
Rotation replaces the key material while the key ID, alias and policy stay the same; ciphertext made under older material still decrypts, because KMS keeps every version. Only symmetric keys with KMS-generated material support it; asymmetric, HMAC and imported-material keys do not, and you rotate those by creating a new key and updating the alias.
# Automatic rotation, every 365 days (the default; allowed range is 90 to 2,560 days)
aws kms enable-key-rotation --key-id alias/bookshelf-data --rotation-period-in-days 365
aws kms get-key-rotation-status --key-id alias/bookshelf-data
# Rotate right now (for example after a suspected exposure)
aws kms rotate-key-on-demand --key-id alias/bookshelf-data
Rotation does not re-encrypt existing data: old ciphertexts keep working and new encryptions use the new material. Automatic rotations are included in the key price; on-demand rotations are charged like API requests. See Rotate AWS KMS keys.
Multi-Region keys
By default a KMS key exists in one Region, and ciphertext from us-east-1 cannot be decrypted by a different key
in eu-west-1. A multi-Region key is a set of interoperable keys with the same key ID (prefixed mrk-) and the
same key material in different Regions, so data encrypted in one Region can be decrypted in another without a
cross-Region call. Typical uses are global applications, disaster recovery, DynamoDB global tables, Aurora global
databases, and Secrets Manager replicas. Each replica has its own key policy, alias and grants, and a single
primary key controls shared properties such as rotation.
KEY_ID=$(aws kms create-key --multi-region --description "Bookshelf data key (MRK)" \
--query KeyMetadata.KeyId --output text)
aws kms replicate-key --key-id "$KEY_ID" --replica-region us-west-2 \
--region us-east-1 --policy file://key-policy.json
Multi-Region keys are not a way to share one key across Regions' IAM boundaries: you still manage each replica’s policy. See Multi-Region keys in AWS KMS.
Disable and delete a key
Deleting a KMS key destroys its key material and makes everything encrypted under it unrecoverable (a deleted
symmetric key cannot be recreated, even from the same material). Because of that, AWS never deletes a key
immediately: you schedule deletion with a mandatory waiting period of 7 to 30 days (default 30; the real
deletion can take up to 24 hours longer). While the key is PendingDeletion it cannot be used for any
cryptographic operation, and you can cancel the deletion at any time before the period ends; afterwards the key,
its aliases and metadata are gone. If you are unsure, disable the key instead (reversible).
aws kms disable-key --key-id alias/bookshelf-data # reversible
aws kms schedule-key-deletion --key-id "$KEY_ID" --pending-window-in-days 7
aws kms describe-key --key-id "$KEY_ID" \
--query "KeyMetadata.[KeyState,DeletionDate]" --output text
aws kms cancel-key-deletion --key-id "$KEY_ID" # key stays Disabled
aws kms enable-key --key-id "$KEY_ID" # then re-enable it
Create a CloudWatch alarm on use attempts during the waiting period (any Decrypt of a pending key is evidence that
something still needs it). A multi-Region primary key cannot be deleted until all its replicas are; a replica can
be deleted at any time. See Delete an AWS KMS key.
AWS Secrets Manager
A secret is a named container for a value (a string up to 64 KB, usually JSON such as
{"username":"catalog","password":"<your-password>"}) plus metadata, versions and the KMS key that encrypts it.
Each change creates a new version; versions are tagged with staging labels: AWSCURRENT is what readers get by
default, AWSPENDING marks a version being prepared during rotation, and AWSPREVIOUS is the one before
AWSCURRENT. A secret holds up to 100 versions, and unlabeled versions older than 24 hours are removed when the
count exceeds that. Secrets are Regional resources whose ARN ends with a six-character random suffix, for example
arn:aws:secretsmanager:us-east-1:111122223333:secret:bookshelf/catalog/db-AbCdEf.
Create and read a secret
In the AWS Management Console
-
Open AWS Secrets Manager (check the Region) and choose Store a new secret.
-
Choose the Secret type: Credentials for Amazon RDS database, Credentials for other database, Other type of secret (key/value or plaintext) or Managed external secret (see Managed external secrets). For
bookshelf/catalog/dbchoose the RDS type, enter the username and password and select thebookshelf-catalogAurora cluster. -
Encryption key: keep
aws/secretsmanager(the AWS managed key) or choosealias/bookshelf-data(required for cross-account sharing). -
Secret name:
bookshelf/catalog/db, description, tags; optionally add a Resource permissions policy and Replicate secret to another Region. -
Configure rotation (optional now), review, Store.
-
To read it back: open the secret > Retrieve secret value. Every read is a
GetSecretValuecall recorded in CloudTrail under your identity.
With the AWS CLI
# Generate a strong password locally on AWS's side; never put it in shell history
DB_PASSWORD=$(aws secretsmanager get-random-password --password-length 32 \
--exclude-characters '"@/\' --require-each-included-type \
--query RandomPassword --output text)
aws secretsmanager create-secret \
--name bookshelf/catalog/db \
--description "Bookshelf catalog database credentials" \
--kms-key-id alias/bookshelf-data \
--secret-string "{\"username\":\"catalog\",\"password\":\"$DB_PASSWORD\",\"host\":\"<cluster-endpoint>\",\"port\":5432,\"dbname\":\"catalog\"}" \
--tags Key=app,Value=bookshelf
aws secretsmanager get-secret-value --secret-id bookshelf/catalog/db \
--query SecretString --output text
aws secretsmanager describe-secret --secret-id bookshelf/catalog/db
aws secretsmanager list-secret-version-ids --secret-id bookshelf/catalog/db
To change the value write a new version (the new version becomes AWSCURRENT and the old one AWSPREVIOUS), and
read a specific version by label or ID:
aws secretsmanager put-secret-value --secret-id bookshelf/catalog/db \
--secret-string file://new-secret.json
aws secretsmanager get-secret-value --secret-id bookshelf/catalog/db \
--version-stage AWSPREVIOUS
Keep sustained writes below about one PutSecretValue per 10 minutes per secret; faster updates create versions
faster than Secrets Manager prunes them. Reference: the
AWS CLI Command Reference — secretsmanager
and Create an AWS Secrets Manager secret.
Rotation
Rotation changes the credential in both Secrets Manager and the service that checks it, using a four-step state machine run by a Lambda rotation function that Secrets Manager invokes:
Three rotation strategies exist, in order of preference:
-
Managed rotation (RDS, Aurora, Redshift, DocumentDB master user): let the database service own the secret. Create the instance or cluster with
--manage-master-user-passwordand the secret is created, encrypted and rotated by the service (every 7 days by default) with no Lambda function in your account. Older guides still hand-build a rotation function for the master user; for new clusters this is no longer needed. -
Rotation function for application users or other stores: Secrets Manager provides templates for the supported databases, in single-user and alternating-users strategies. The alternating-users strategy switches between two database users so there is no moment when the credential in use is invalid, which suits applications that hold long-lived connection pools.
-
Managed external secrets (Managed external secrets) for supported SaaS partners.
# Master password managed by the service (RDS / Aurora)
aws rds create-db-cluster --db-cluster-identifier bookshelf-catalog \
--engine aurora-postgresql --master-username catalog_admin \
--manage-master-user-password --master-user-secret-kms-key-id alias/bookshelf-data
# Application user: attach your rotation Lambda and a schedule
aws secretsmanager rotate-secret --secret-id bookshelf/catalog/db \
--rotation-lambda-arn arn:aws:lambda:us-east-1:111122223333:function:bookshelf-rotate-db \
--rotation-rules AutomaticallyAfterDays=30
# Rotate immediately, and inspect the result
aws secretsmanager rotate-secret --secret-id bookshelf/catalog/db
aws secretsmanager describe-secret --secret-id bookshelf/catalog/db \
--query "[RotationEnabled,LastRotatedDate,NextRotationDate]"
The rotation function runs in your VPC when the database is private, so it needs a route to the Secrets Manager endpoint (a VPC interface endpoint or a NAT gateway, see VPC Networking) and permission to call Secrets Manager and decrypt the key. Applications must re-read the secret after a rotation (see the consumption patterns below); a connection pool that caches the old password for its lifetime fails at the next reconnect. Details are in Rotate AWS Secrets Manager secrets and the databases' own pages: Amazon RDS and Amazon Aurora.
Managed external secrets
Managed external secrets are a secret type, introduced in 2025, that stores and automatically rotates credentials for supported integration partners (SaaS vendors that publish a predefined secret format). Rotation runs without a Lambda function in your account, the partner defines the secret’s required JSON shape, and the full Secrets Manager feature set (IAM and resource policies, replication, CloudTrail, EventBridge) applies. In the console it is the Managed external secret option in Store a new secret; the partner list and programmatic creation steps are in Using Secrets Manager managed external secrets. Use it for partner API keys instead of hand-writing one more rotation function.
Resource policies
Like an S3 bucket policy, a resource-based policy attached to a secret says who may use it, which is how you share a secret across accounts, or add a guard rail on top of IAM. It is evaluated together with the caller’s identity policies; for a caller in another account both must allow the request, and the secret must use a customer managed key whose key policy also allows that account.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPartnerAccountRead",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::<reporting-account-id>:role/reporting-reader" },
"Action": [ "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret" ],
"Resource": "*"
},
{
"Sid": "DenyEveryoneElseOutsideTheOrganization",
"Effect": "Deny",
"Principal": "*",
"Action": "secretsmanager:*",
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:PrincipalOrgID": "o-exampleorgid" },
"ArnNotEquals": {
"aws:PrincipalArn": [
"arn:aws:iam::<reporting-account-id>:role/reporting-reader",
"arn:aws:iam::111122223333:role/bookshelf-key-admin"
]
}
}
}
]
}
aws secretsmanager put-resource-policy --secret-id bookshelf/catalog/db \
--resource-policy file://secret-policy.json --block-public-policy
aws secretsmanager validate-resource-policy --resource-policy file://secret-policy.json
aws secretsmanager get-resource-policy --secret-id bookshelf/catalog/db
Resource in a secret’s resource policy is always "*", meaning "this secret". Test a draft with
validate-resource-policy (it flags public access and syntax problems) before attaching it; a wrong deny can lock
out even your administrators. See
Attach a permissions policy to a secret.
Replication
A secret can be replicated to other Regions for disaster recovery or to serve workloads close to users. Each replica is a read-only copy, encrypted with a KMS key you choose in the destination Region, kept in sync with the primary (including rotation results). A replica is billed as a secret. To make a replica an independent writable secret (failover), promote it.
aws secretsmanager replicate-secret-to-regions --secret-id bookshelf/catalog/db \
--add-replica-regions Region=us-west-2,KmsKeyId=alias/bookshelf-data-west
aws secretsmanager describe-secret --secret-id bookshelf/catalog/db \
--query ReplicationStatus
# In a disaster: make the replica a standalone secret
aws secretsmanager stop-replication-to-replica --secret-id bookshelf/catalog/db \
--region us-west-2
Use a multi-Region KMS key (Multi-Region keys) to keep the encryption consistent, or a per-Region key if the Regions' compliance rules differ. Reference: Replicate AWS Secrets Manager secrets to other Regions.
Events and audit
Every Secrets Manager and KMS call is recorded by CloudTrail, including who read GetSecretValue and the KMS
Decrypt it triggered, which is the main reason to prefer Secrets Manager over a plain parameter for credentials.
Secrets Manager also publishes events to EventBridge (rotation start/success/failure, value changes, deletion
scheduled) that you can route to SNS or a Lambda function:
aws events put-rule --name bookshelf-secret-changes --event-pattern '{
"source": ["aws.secretsmanager"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": { "eventName": ["PutSecretValue", "UpdateSecret", "DeleteSecret", "RotationFailed"] }
}'
Monitoring and alerting patterns are on Monitoring and Observability. See Monitor AWS Secrets Manager secrets.
Deleting a secret: the recovery window
delete-secret does not remove a secret immediately. It marks the secret for deletion with a recovery window of 7
to 30 days (default 30) during which it cannot be read, its name cannot be reused, and restore-secret brings it
back; afterwards it is deleted permanently. --force-delete-without-recovery skips the window and is immediate and
irreversible, which is appropriate only for throwaway secrets (a test environment’s, or one created by mistake).
aws secretsmanager delete-secret --secret-id bookshelf/catalog/db --recovery-window-in-days 7
aws secretsmanager restore-secret --secret-id bookshelf/catalog/db
aws secretsmanager delete-secret --secret-id bookshelf/tmp --force-delete-without-recovery
Replicas must be removed (remove-regions-from-replication) before the primary can be deleted. A common outage is
deleting a secret that a running service still reads: the task or pod keeps working until its next restart or
cache refresh, then fails. Check describe-secret and CloudTrail GetSecretValue activity first.
Parameter Store SecureString
Systems Manager Parameter Store is a hierarchical key/value store (/bookshelf/prod/catalog/page-size) of three
types: String, StringList and SecureString. A SecureString value is encrypted with a KMS key (the AWS managed
alias/aws/ssm unless you pass --key-id) and decrypted when read with --with-decryption. Parameters are
versioned, can carry labels and tags, and can be read by path to load a whole application’s configuration with one
call.
| Tier | Limits | Notes |
|---|---|---|
Standard (default) |
Up to 10,000 parameters per account and Region; value up to 4 KB. |
No additional charge. No parameter policies. |
Advanced |
Up to 100,000 parameters; value up to 8 KB. |
Charged per advanced parameter per month. Adds parameter policies (expiration, no-change notification) and sharing across accounts. A standard parameter can be promoted to advanced, but an advanced parameter cannot be changed back to standard; delete and recreate it. |
Separately, a throughput setting controls the request rate (default throughput versus higher throughput, which is charged per interaction); tiers control storage attributes, throughput controls rate.
In the AWS Management Console
-
Open AWS Systems Manager > Application Management > Parameter Store (check the Region) and choose Create parameter.
-
Name:
/bookshelf/prod/catalog/internal-api-key; Tier: Standard (or Advanced). -
Type: SecureString; KMS key source: My current account, then the AWS managed key or a customer managed key.
-
Value: the value (for a real secret, paste it from your secret store; never type secrets into screenshots or tickets). Create parameter.
-
Browse the hierarchy under My parameters; use History to see versions.
With the AWS CLI
aws ssm put-parameter --name /bookshelf/prod/catalog/page-size \
--type String --value 25 --tier Standard
aws ssm put-parameter --name /bookshelf/prod/catalog/internal-api-key \
--type SecureString --key-id alias/bookshelf-data \
--value '<your-api-key>' --tier Standard
# One parameter, decrypted
aws ssm get-parameter --name /bookshelf/prod/catalog/internal-api-key \
--with-decryption --query Parameter.Value --output text
# Everything for the catalog service, recursively and decrypted
aws ssm get-parameters-by-path --path /bookshelf/prod/catalog \
--recursive --with-decryption \
--query "Parameters[].[Name,Type]" --output table
# Update (a new version is created) and label
aws ssm put-parameter --name /bookshelf/prod/catalog/page-size --type String \
--value 50 --overwrite
Reference: AWS Systems Manager Parameter Store and the AWS CLI Command Reference — ssm.
Parameter Store can also read a Secrets Manager secret through the reserved path
/aws/reference/secretsmanager/<secret-name>, so one ssm:GetParameter call (with --with-decryption) works for both
stores; a service that has only Parameter Store integration, such as an older CloudFormation or Lambda feature, can
still get a rotated secret that way. Use IAM permissions on ssm:GetParameter and
secretsmanager:GetSecretValue for that path.
Consuming secrets from applications
bookshelf/catalog/db"] PS["Parameter Store
/bookshelf/*"] KMS["KMS key
alias/bookshelf-data"] SM --- KMS PS --- KMS end SDK["Java SDK or
Spring Cloud AWS
(task role, Pod Identity)"] -->|GetSecretValue / GetParameter| SM SDK --> PS LAM["Lambda function
+ Parameters and Secrets extension
localhost:2773"] -->|cached reads| SM ECS["ECS task
secrets in task definition
(execution role)"] -->|resolved at task start| SM EKS["EKS pod
Secrets Store CSI Driver + ASCP
or External Secrets Operator"] -->|Pod Identity role| SM
Whatever the path, the identity is the one that already runs the code (ECS task or execution role, Lambda
execution role, EKS Pod Identity role) and the IAM policy lists exactly the secrets and the key that identity needs;
long-lived access keys never appear in the container. A least-privilege identity policy for the catalog-api task
role:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadCatalogSecret",
"Effect": "Allow",
"Action": [ "secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret" ],
"Resource": "arn:aws:secretsmanager:us-east-1:111122223333:secret:bookshelf/catalog/*"
},
{
"Sid": "ReadCatalogParameters",
"Effect": "Allow",
"Action": [ "ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath" ],
"Resource": "arn:aws:ssm:us-east-1:111122223333:parameter/bookshelf/prod/catalog*"
},
{
"Sid": "DecryptWithTheBookshelfKey",
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:us-east-1:111122223333:key/<key-id>",
"Condition": {
"StringEquals": {
"kms:ViaService": [
"secretsmanager.us-east-1.amazonaws.com",
"ssm.us-east-1.amazonaws.com"
]
}
}
}
]
}
Two details that cause real incidents: the secret ARN in a policy ends with a random suffix, so use a trailing
or a name prefix (bookshelf/catalog/) rather than the exact ARN, which would not match after the secret is
recreated; and the kms:Decrypt permission is required in addition to the Secrets Manager permission whenever the
secret uses a customer managed key (it is not needed for the AWS managed aws/secretsmanager key within the same
account).
Java SDK, with caching
AWS SDK for Java 2.x with the default credential chain; SecretsManagerClient calls cost money and add latency,
so cache the value and refresh it:
package com.example.bookshelf.config;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import java.time.Duration;
import java.time.Instant;
public class DbSecretProvider {
private final SecretsManagerClient client = SecretsManagerClient.create();
private final Duration ttl = Duration.ofMinutes(5);
private volatile String cached;
private volatile Instant loadedAt = Instant.MIN;
public String dbSecretJson() {
if (cached == null || Instant.now().isAfter(loadedAt.plus(ttl))) {
cached = client.getSecretValue(GetSecretValueRequest.builder()
.secretId("bookshelf/catalog/db").build()).secretString();
loadedAt = Instant.now();
}
return cached;
}
}
For production, prefer the maintained caching library (com.amazonaws.secretsmanager:aws-secretsmanager-caching-java,
whose 2.x line uses the SDK v2) with its SecretCache and SecretCacheConfiguration, which also handles version
stages and refresh jitter, and read the AWS SDK for Java 2.x
Get a Secrets Manager secret value using Java.
Never log the secret JSON.
Spring Cloud AWS
Bookshelf’s catalog-api is a Spring Boot service, so it imports secrets and parameters straight into the
Environment with Spring Cloud AWS (4.x for Spring Boot 4, 3.4.x for Spring Boot 3.5), using the credentials of the
task role. The mechanics of spring.config.import, profiles and precedence are on
Configuration and Profiles; the Bookshelf-specific lines are:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>${spring.cloud.aws.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-secrets-manager</artifactId>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-parameter-store</artifactId>
</dependency>
</dependencies>
spring:
config:
import:
- "aws-secretsmanager:bookshelf/catalog/db"
- "optional:aws-parameterstore:/bookshelf/prod/catalog/"
datasource:
url: jdbc:postgresql://${host}:${port}/${dbname}
username: ${username}
password: ${password}
The keys of the secret’s JSON (username, password, host, port, dbname) become properties, so the datasource
above resolves from the secret; Parameter Store entries under the prefix become properties named by their last
path segments. Spring Cloud AWS reads values at start-up; to pick up a rotated password at run time enable its
reload support (spring.cloud.aws.secretsmanager.reload.strategy, see the Spring Cloud AWS reference) or restart
the service after rotation. The optional: prefix keeps local development working without AWS credentials.
ECS and Fargate
In a task definition, secrets entries (not environment) tell the ECS agent to fetch the value when the task
starts and inject it as an environment variable. The task execution role (not the task role) needs the read
permissions, because ECS resolves them before your code runs. See
ECS and Fargate for the roles themselves.
{
"family": "catalog-api",
"executionRoleArn": "arn:aws:iam::111122223333:role/catalog-api-execution-role",
"taskRoleArn": "arn:aws:iam::111122223333:role/catalog-api-task-role",
"containerDefinitions": [{
"name": "catalog-api",
"image": "111122223333.dkr.ecr.us-east-1.amazonaws.com/bookshelf/catalog-api:1.0.0",
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:111122223333:secret:bookshelf/catalog/db-AbCdEf:password::"
},
{
"name": "DB_USERNAME",
"valueFrom": "arn:aws:secretsmanager:us-east-1:111122223333:secret:bookshelf/catalog/db-AbCdEf:username::"
},
{
"name": "PAGE_SIZE",
"valueFrom": "arn:aws:ssm:us-east-1:111122223333:parameter/bookshelf/prod/catalog/page-size"
}
]
}]
}
The format for Secrets Manager is secret-arn:json-key:version-stage:version-id: the trailing :: means
"`AWSCURRENT`, latest version", and omitting the :password:: suffix injects the whole JSON string. Parameter Store
entries use the parameter ARN, or just the name when the parameter is in the same Region. The execution role needs
secretsmanager:GetSecretValue, ssm:GetParameters and kms:Decrypt on the keys involved (the policy above,
attached to the execution role). The value is resolved once, at task start: after a rotation (or a new
put-secret-value) start a new deployment with
aws ecs update-service --cluster bookshelf --service catalog-api --force-new-deployment so the tasks restart and
re-read, or let the application read the secret itself through the SDK. Reference:
Pass Secrets Manager secrets through Amazon ECS environment variables
and Pass Systems Manager parameters.
Lambda
Lambda’s own environment variables are encrypted but not rotated, so credentials usually come from a secret at run
time. Add the AWS Parameters and Secrets Lambda Extension as a layer: it runs a local HTTP cache on port 2773
that calls Secrets Manager or Parameter Store with the function’s execution role, caches results (TTL up to 300
seconds, default 300) and spares your function an SDK dependency. The same local-endpoint design is also available
as the AWS Workload Credentials Provider (formerly named the Secrets Manager Agent), a standalone binary for
containers, EC2 and Lambda, which uses the same port and the X-Aws-Parameters-Secrets-Token header.
# Resolve the current extension layer ARN for the Region (x86_64; use .../arm64/latest for Graviton)
LAYER_ARN=$(aws ssm get-parameter \
--name /aws/service/aws-parameters-and-secrets-lambda-extension/x86/latest \
--query Parameter.Value --output text)
aws lambda update-function-configuration --function-name notifier \
--layers "$LAYER_ARN" \
--environment "Variables={SECRETS_MANAGER_TTL=300,PARAMETERS_SECRETS_EXTENSION_LOG_LEVEL=INFO}"
The function then calls the local endpoint with the session token as the header (the extension rejects requests without it; this is its SSRF protection):
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:2773/secretsmanager/get?secretId=bookshelf%2Fcatalog%2Fdb"))
.header("X-Aws-Parameters-Secrets-Token", System.getenv("AWS_SESSION_TOKEN"))
.build();
String json = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString()).body();
Parameter Store values use /systemsmanager/parameters/get?name=<url-encoded-name>&withDecryption=true. The
execution role needs the same secretsmanager:GetSecretValue / ssm:GetParameter and kms:Decrypt statements as
above. With SnapStart the AWS_SESSION_TOKEN variable is not always set; read the token from the SDK credential
provider chain instead. Because the extension has no cache invalidation, a rotated secret can be stale for up to
the TTL; lower SECRETS_MANAGER_TTL or retry once after an authentication failure. See
AWS Lambda, and the
Use Secrets Manager secrets in Lambda functions
page.
EKS
Kubernetes Secret objects are only base64-encoded; on EKS, the recommended pattern keeps the source of truth in
Secrets Manager and brings values into pods in one of two ways. Both authenticate with EKS Pod Identity (see
EKS Clusters and Deploying Microservices on EKS) so that only the service account
that needs the secret can read it.
Secrets Store CSI Driver with the AWS Secrets and Configuration Provider (ASCP) mounts secrets as files in a
volume and can sync them to a Kubernetes Secret:
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver \
--namespace kube-system --set syncSecret.enabled=true --set enableSecretRotation=true
helm repo add aws-secrets-manager https://aws.github.io/secrets-store-csi-driver-provider-aws
helm install secrets-provider-aws aws-secrets-manager/secrets-store-csi-driver-provider-aws \
--namespace kube-system
aws eks create-pod-identity-association --cluster-name bookshelf-eks \
--namespace bookshelf --service-account catalog-api \
--role-arn arn:aws:iam::111122223333:role/catalog-api-pod-role
The role’s trust policy uses the Pod Identity principal and the same permission policy as above:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "pods.eks.amazonaws.com" },
"Action": [ "sts:AssumeRole", "sts:TagSession" ]
}]
}
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: bookshelf-catalog-db
namespace: bookshelf
spec:
provider: aws
parameters:
usePodIdentity: "true"
objects: |
- objectName: "bookshelf/catalog/db"
objectType: "secretsmanager"
jmesPath:
- path: username
objectAlias: db-username
- path: password
objectAlias: db-password
secretObjects:
- secretName: catalog-db
type: Opaque
data:
- objectName: db-username
key: username
- objectName: db-password
key: password
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: catalog-api
namespace: bookshelf
spec:
replicas: 2
selector:
matchLabels: { app: catalog-api }
template:
metadata:
labels: { app: catalog-api }
spec:
serviceAccountName: catalog-api
containers:
- name: catalog-api
image: 111122223333.dkr.ecr.us-east-1.amazonaws.com/bookshelf/catalog-api:1.0.0
volumeMounts:
- name: secrets
mountPath: /mnt/secrets-store
readOnly: true
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef: { name: catalog-db, key: password }
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: bookshelf-catalog-db
The mount (and the synced Secret) exist only while a pod that mounts the volume runs. With
enableSecretRotation the driver re-reads the secret periodically and updates the files, but environment
variables from a synced Secret are fixed at pod start, so mount the file and re-read it, or restart the
Deployment. Private clusters need a VPC endpoint for AWS STS and for Secrets Manager. See
Use ASCP with Pod Identity for Amazon EKS
and the Kubernetes primer in Orchestration: Swarm Mode and Kubernetes.
External Secrets Operator (ESO) is the alternative: a CNCF-hosted controller that polls Secrets Manager or
Parameter Store and writes ordinary Kubernetes Secret objects, so pods consume them with the usual secretKeyRef
or envFrom and no volume. It uses the default credential chain, so it works with the Pod Identity or IRSA role of
the operator itself:
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: catalog-db
namespace: bookshelf
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: catalog-db
data:
- secretKey: password
remoteRef:
key: bookshelf/catalog/db
property: password
Choose ASCP when you want values to exist only as files in running pods and to keep Kubernetes Secret objects out
of etcd; choose ESO when you want GitOps-friendly declarative ExternalSecret objects, support for several
backends (Vault, Azure Key Vault, Google Secret Manager) or a cluster-wide store. Both bill you for Secrets Manager
API calls, so set a sensible refresh interval. Runtime configuration (ConfigMaps) is on
Runtime Configuration; the Spring-side view of Kubernetes Secrets is on
Configuration and Profiles.
GitHub Actions secrets or Secrets Manager
CI pipelines authenticate to AWS with OIDC federation and a short-lived role (see
Identity and Access and Docker CI/CD with GitHub Actions), so no AWS key
needs to be stored in GitHub at all. Keep secrets that only the pipeline needs (a registry token, a signing
passphrase) in GitHub Actions secrets, and secrets that the running application needs in Secrets Manager;
a workflow that must read one can do so after assuming its role, for example with the
aws-actions/aws-secretsmanager-get-secrets action or aws secretsmanager get-secret-value (mask the value with
::add-mask:: before it can reach a log). The CI/CD page is Developer Tools and DevOps.
Certificates and CloudHSM, in brief
AWS Certificate Manager (ACM) issues and renews public TLS certificates for free for use with integrated services
(CloudFront, ALB, API Gateway, and others) and issues private certificates from AWS Private CA for internal
mTLS, with a separate monthly CA charge. Public certificates validate through DNS (a CNAME in the hosted zone) and
renew automatically; certificates for CloudFront must be requested in us-east-1. See
CloudFront and CDN Delivery, Load Balancers and Gateways and
Route 53, DNS and ACM Certificates. AWS CloudHSM provides single-tenant HSM clusters you control
(FIPS 140-3 Level 3) when a standard requires dedicated hardware or you need to run your own PKCS#11/JCE software;
most workloads are well served by KMS, which is multi-tenant but built on validated HSMs.
Pricing, limits and cost drivers
Prices change and vary by Region; they are deliberately not repeated here. Check the Secrets Manager pricing, KMS pricing and Systems Manager pricing pages (checked 2026-10-01) before sizing. The cost drivers are:
-
Secrets Manager: a monthly fee for every secret (including each replica and each secret for a service that creates one, such as a managed master password), plus a charge per 10,000 API calls. A recently deleted secret is billed until the recovery window ends. Cache reads (SDK cache, Lambda extension, CSI driver with a sensible rotation interval) to cut calls. A new account usually has a free trial for each new secret; confirm on the pricing page.
-
Parameter Store: standard parameters carry no storage charge and the default throughput is free; advanced parameters are charged per parameter per month, and higher throughput per interaction.
-
KMS: a monthly fee for each customer managed key (each multi-Region replica counts as a key, and each key version is not charged), plus a charge per 10,000 requests; AWS managed keys have no monthly fee. A busy S3 bucket with SSE-KMS can generate millions of requests; S3 Bucket Keys (see S3 Security and Static Hosting) reduce the KMS call volume dramatically.
Key quotas to design around (all Regional; verify in Service Quotas):
| Item | Limit |
|---|---|
Secrets Manager secrets per Region |
500,000 (not adjustable) |
Secret value size |
65,536 bytes |
Versions per secret |
100 |
|
10,000 per second per Region |
Control-plane writes (create, put, update) |
50 per second per Region combined |
Resource policy size |
20,480 characters |
Parameter Store parameters |
10,000 (standard) / 100,000 (advanced) per account and Region |
|
4 KB (standard) / 8 KB (advanced) |
KMS |
4,096 bytes |
KMS deletion waiting period |
7 to 30 days |
Secrets Manager recovery window |
7 to 30 days |
KMS request quotas are shared across all uses of a key in the account and Region; very high-volume encryption should use envelope encryption with cached data keys rather than a KMS call per record.
Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
|
The identity lacks |
Policy simulator, the exact ARN from |
|
The secret or parameter uses a customer managed key and the identity cannot decrypt it, or the key is disabled or pending deletion. |
|
ECS task stops with |
The execution role cannot read the secret/parameter or key, or the task has no network path to Secrets Manager, Systems Manager or KMS. |
The execution role policy, a NAT gateway or VPC endpoints for |
Application still uses the old password after rotation |
The value is cached or injected once (ECS, env vars, a connection pool, Lambda extension TTL). |
Force a new deployment, shorten the cache TTL, use the alternating-users rotation strategy, and re-read on authentication failure. |
Rotation fails with |
The rotation function cannot reach the database or Secrets Manager endpoint, lacks permissions, or the database user cannot change its own password. |
The function’s CloudWatch Logs, the security groups, a Secrets Manager VPC endpoint, and the |
|
Names cannot be reused during the recovery window. |
|
Pod stays in |
The ASCP or driver is not installed, the Pod Identity association is missing, |
|
|
Too many |
Cache, stagger start-up, add retry with exponential backoff and jitter, and request a quota increase only after caching. |
|
Wrong name (leading |
|
Locked out of a key |
The key policy has no root statement and the only named principal was deleted. |
Contact AWS Support; keep the root statement and a break-glass role in every key policy. |
Clean up
Secrets and keys carry monthly fees while they exist, but both have deliberate safety nets that make "delete" a two-step operation. Remove the resources in dependency order:
# 1. Stop consumers first (ECS service, Lambda function, EKS pod associations)
aws eks delete-pod-identity-association --cluster-name bookshelf-eks \
--association-id <association-id>
# 2. Remove replicas, then schedule the secret for deletion (recovery window 7 to 30 days)
aws secretsmanager remove-regions-from-replication --secret-id bookshelf/catalog/db \
--remove-replica-regions us-west-2
aws secretsmanager delete-secret --secret-id bookshelf/catalog/db --recovery-window-in-days 7
# Throwaway secrets only: immediate and irreversible
aws secretsmanager delete-secret --secret-id bookshelf/tmp --force-delete-without-recovery
# 3. Delete parameters
aws ssm delete-parameters --names /bookshelf/prod/catalog/page-size \
/bookshelf/prod/catalog/internal-api-key
# 4. Delete the key last: remove its alias, then schedule deletion (7 to 30 days)
aws kms delete-alias --alias-name alias/bookshelf-data
aws kms schedule-key-deletion --key-id "$KEY_ID" --pending-window-in-days 7
Order matters: do not schedule a key’s deletion while any secret, parameter, bucket, volume or database still
uses it; those resources become unreadable when the waiting period ends. Until the recovery window ends a secret, and until the waiting period ends a key, can still be restored (restore-secret, cancel-key-deletion), and the monthly
fee for a key is charged while it exists, so confirm on the KMS pricing page whether a PendingDeletion key is
billed. If you used CloudFormation, aws cloudformation delete-stack follows the resource’s DeletionPolicy, and
for secrets and keys the template’s retention settings control this behaviour. Remove any SecretProviderClass,
ExternalSecret and CSI/ESO Helm releases with helm uninstall, and delete the CloudWatch alarms and EventBridge
rules created above (aws events delete-rule --name bookshelf-secret-changes, after removing its targets).
Related pages
-
Runtime Configuration covers AppConfig, Parameter Store hierarchies for non-secret configuration and ConfigMaps.
-
Identity and Access explains the roles, trust policies and Pod Identity used above.
-
ECS and Fargate, AWS Lambda and Deploying Microservices on EKS show where these secrets plug in.
-
Amazon RDS and Amazon Aurora cover managed master passwords and database credentials.
-
S3 Security and Static Hosting and Block and File Storage on AWS show KMS encryption of S3 and EBS.
-
Configuration and Profiles is the Spring configuration reference.
-
Key Vault and Secrets is the Azure counterpart.