Security and encryption (KMS, Secrets Manager, Parameter Store) — DVA-C02 Mastery Guide
- Sources: course chapter 23 (Security and Encryption: KMS, Encryption SDK, SSM Parameter Store, Secrets Manager, CloudHSM, CloudFormation dynamic references, CloudWatch Logs and CodeBuild secrets, Nitro Enclaves). The course serves as the syllabus only; all wording here is original.
- Written 2026-10-12 · values checked against AWS docs on 2026-10-12. The marker
⚠️ Changed: <current>. Older material may say <old>.flags a value or behaviour that differs from the course or from older practice tests: learn the current value.⚠️ unverifiedmarks a detail that could not be confirmed in AWS documentation: don't build an exam answer on it. Every change is listed in Changed vs older material; once you confirm them they go tonotes/current-facts.md.- Tier 1 (highest exam weight), bank target 40 questions. Domain: Security (26 % of the exam), task statements "encryption with AWS services" and "sensitive data in application code". KMS throttling, caching and rotation failures also appear as troubleshooting scenarios.
- How to use it:
/explain <topic>teaches from these sections (concept → exam facts → developer angle → traps → 3 check questions);/quiz security-encryptionasks bank questions whosesource_notelinks point at the## N.headings below;/drill security-encryptionfires short recall prompts fromnotes/security-encryption-cheat-sheet.md. Suggested loop: read a section and answer its practice questions before opening the answers →/explainwhat is still unclear →/quiz→/drill.
How Security and encryption (KMS, Secrets Manager, Parameter Store) appears on DVA-C02
The exam treats encryption as something your code and configuration must get right, not as cryptography theory:
- Choosing the mechanism: KMS
Encryptfor small values, envelope encryption (GenerateDataKeyor the Encryption SDK) for anything bigger, a service's built-in encryption when the data lives in S3, EBS, DynamoDB or CloudWatch Logs, CloudHSM or a custom key store only when a regulation demands single-tenant HSMs. - Authorizing key use: key policy vs IAM policy vs grant, cross-account access,
kms:ViaServiceand encryption-context conditions, and theAccessDeniedExceptiona missing statement produces. - Keeping secrets out of code: Parameter Store
SecureStringvs Secrets Manager, rotation, caching, the Lambda extension, CloudFormation dynamic references, CodeBuild environment variables. - Troubleshooting:
ThrottlingExceptionfrom KMS (shared quotas, S3 traffic, data key caching, S3 Bucket Keys),InvalidCiphertextException(encryption context mismatch),DisabledException(disabled key),KMSInvalidStateException(key pending deletion, pending import or unavailable), rotation functions that time out.
Typical question shapes: a scenario with one deciding constraint (LEAST operational overhead, MOST secure, most cost-effective, no application code changes); an error code to explain; "Which TWO actions…" with five options; the exact API action, CLI flag, condition key or template syntax that does the job.
Domain map: Security → every section · Troubleshooting and Optimization → §3 (errors), §5 (quotas, Bucket Keys), §9–§10 (caching, rotation failures) · Development with AWS Services → §4 (Encryption SDK), §8–§9 (reading configuration and secrets) · Deployment → §11 (templates), §12 (CodeBuild).
Out of scope here: S3 encryption types and headers beyond Bucket Keys (see notes/s3-guide.md), EBS volume, snapshot and AMI encryption workflows (see notes/ec2-mastery-guide.md §6), IAM policy grammar and policy evaluation (see notes/iam-guide.md and notes/advanced-identity-guide.md), TLS certificates with ACM (see notes/other-services-guide.md), RDS encryption at rest and IAM database authentication (see notes/rds-guide.md), Lambda environment-variable encryption (see notes/lambda-guide.md), ECS secret injection (see notes/containers-guide.md), buildspec phases and artifacts (see notes/cicd-guide.md), CloudFormation template mechanics (see notes/cloudformation-guide.md). No HSM administration or network design: VPC endpoints and NAT gateways appear only as the reason a call times out.
| # | Topic | Why it is on the exam |
|---|---|---|
| 1 | Encryption models and KMS key types | Who controls the key decides auditing, sharing and cost; asymmetric vs symmetric vs HMAC |
| 2 | Key policies grants and cross account access | Most "access denied" questions are key policy questions |
| 3 | KMS API operations and the 4 KB limit | The right API for the job and the error codes that come back |
| 4 | Envelope encryption and the Encryption SDK | Anything bigger than 4 KB; data key caching under load |
| 5 | Request quotas throttling and S3 Bucket Keys | ThrottlingException fixes, SSE-KMS cost |
| 6 | Key rotation deletion and multi Region keys | Rotation types and periods, safe deletion, cross-Region decryption |
| 7 | CloudHSM versus KMS and custom key stores | When a single-tenant HSM is required |
| 8 | SSM Parameter Store | Tiers, hierarchies, SecureString, policies, throughput |
| 9 | Secrets Manager basics and Parameter Store comparison | Choosing the store; retrieving and caching secrets; cross-account |
| 10 | Secrets Manager rotation and replication | Rotation steps, schedules, strategies, failures; multi-Region secrets |
| 11 | CloudFormation dynamic references and RDS managed secrets | Secrets in templates without exposing them |
| 12 | CloudWatch Logs encryption and CodeBuild secrets | KMS keys on log groups; secrets in builds |
| 13 | Nitro Enclaves and attested KMS requests | Processing data that even the host must not see |
1. Encryption models and KMS key types
Concept
- Three places to encrypt. In transit: TLS between client and endpoint stops eavesdropping and tampering on the wire. Server-side at rest: the service receives the data over TLS, encrypts it before writing it to storage and decrypts it when you read it, so the service must be allowed to use the key. Client-side: your code encrypts before upload, so the storage service only ever holds ciphertext it can't read.
- AWS KMS is the managed key service that integrated AWS services and your own code call. Key material is generated and used inside HSMs and never leaves them in plaintext; every use is an API call that is authenticated (SigV4), authorized (key policy, IAM, grants), counted against a quota and recorded in CloudTrail.
- A KMS key is a Regional logical resource (key ID, ARN, key policy, state, metadata) that wraps one or more versions of key material. Rotation adds material; the identity of the key never changes.
- Ownership decides control: customer managed keys (you create and govern them), AWS managed keys (alias
aws/<service>, created in your account by a service), AWS owned keys (kept in an AWS service account, invisible to you).
Exam facts
| Customer managed key | AWS managed key | AWS owned key | |
|---|---|---|---|
| Created by | You (CreateKey) |
An AWS service, in your account (aws/s3, aws/ebs, aws/ssm, aws/secretsmanager, …) |
An AWS service, in its own account |
| Key policy | Written by you | Set by the service; viewable, not editable | Not visible |
| Use from your own code | Yes | No, only through the owning service | No |
| Share with another account | Yes (key policy + IAM) | No | Decided by the service |
| CloudTrail | Yes | Yes | No |
| Rotation | Optional: automatic (period 90–2,560 days, default 365) and on-demand (§6) | Every year, can't be changed | Decided by the service |
| Monthly fee | $1 per key (prorated hourly) | None | None |
| Request charges | Yes, beyond the free tier | Yes (some services pay them for you) | None |
- ⚠️ Changed: AWS managed keys have no monthly fee but each use is billed as a KMS request (some services absorb it), and they are a legacy key type: since 2021 new services encrypt by default with AWS owned keys. Older material may say AWS managed keys are free.
- Key specs (chosen at creation, never changed):
SYMMETRIC_DEFAULT: AES-256-GCM, the default. The type integrated services use for encryption at rest, and the only type that supports automatic rotation and custom key stores.- RSA (
RSA_2048,RSA_3072,RSA_4096): key usageENCRYPT_DECRYPTorSIGN_VERIFY, picked once. - ECC: NIST P-256/P-384/P-521 for sign/verify or key agreement (
DeriveSharedSecret);ECC_SECG_P256K1(cryptocurrency) andECC_NIST_EDWARDS25519(Ed25519) for sign/verify. - HMAC (
HMAC_224toHMAC_512):GenerateMac/VerifyMacfor tokens and message authentication. - ML-DSA (
ML_DSA_44,ML_DSA_65,ML_DSA_87): post-quantum signatures.
- ⚠️ Changed: KMS offers symmetric, RSA, ECC (Ed25519 added November 2025), HMAC (April 2022) and ML-DSA post-quantum signing keys (June 2025), plus a key-agreement usage for NIST ECC keys (June 2024). Older material may say KMS keys are only symmetric AES-256 or asymmetric RSA/ECC.
- Asymmetric keys: the public key can be downloaded (
GetPublicKey) and used outside AWS: partners who can't call KMS encrypt with it and only you decrypt (in KMS), or anyone verifies your signatures offline. The private key never leaves KMS. - Identifiers: key ID (
1234abcd-…; multi-Region keys start withmrk-), key ARN, alias name (alias/orders), alias ARN. A key in another account must be named by key ARN or alias ARN. Thealias/aws/prefix is reserved for AWS managed keys; up to 50 aliases per key. - "Customer master key (CMK)" was renamed "KMS key" in 2021; the
CustomerMasterKeySpecfield is deprecated in favour ofKeySpec. - Pricing: $1 per key-month (imported key material included); $0.03 per 10,000 symmetric requests; a free tier of 20,000 requests per month across all Regions (asymmetric and
GenerateDataKeyPairrequests excluded); no monthly charge while a key is pending deletion. - Defaults you will meet: DynamoDB encrypts every table with an AWS owned key by default and can switch to
aws/dynamodbor a customer managed key at any time; S3 applies SSE-S3 to all new objects (details:notes/s3-guide.md); CloudWatch Logs always encrypts with a service key unless you associate a KMS key (§12).
Developer angle
# Symmetric key for application data, plus an alias the code refers to
aws kms create-key --description "orders service data" --key-spec SYMMETRIC_DEFAULT --key-usage ENCRYPT_DECRYPT
aws kms create-alias --alias-name alias/orders --target-key-id 1234abcd-12ab-34cd-56ef-1234567890ab
# Who manages this key, what is it, can it be used right now?
aws kms describe-key --key-id alias/orders \
--query 'KeyMetadata.[KeyManager,KeySpec,KeyUsage,KeyState,Origin,MultiRegion]'
# Asymmetric signing key; export the public key for offline verification
aws kms create-key --key-spec ECC_NIST_P256 --key-usage SIGN_VERIFY --description "release signing"
aws kms get-public-key --key-id alias/release-signing --query PublicKey --output text | base64 --decode > release-pub.der
DescribeKeyshowsKeyManager(CUSTOMERorAWS),KeyState(Enabled,Disabled,PendingDeletion,PendingImport,Unavailable…) andOrigin(AWS_KMS,EXTERNALfor imported material,AWS_CLOUDHSM,EXTERNAL_KEY_STORE).- IAM:
kms:CreateKey,kms:ListKeysandkms:ListAliasesbelong in IAM policies only (there is no key policy to put them in yet); thekms:KeySpeccondition limits which key specs principals may create or use.
Traps
- Trap: "Encrypt objects with
aws/s3and share them with a partner account." → Reality: AWS managed keys can't be used across accounts; use a customer managed key whose policy trusts the partner. - Trap: "Call
Encryptfrom the application with theaws/ebskey." → Reality: an AWS managed key is usable only by its own service on your behalf. - Trap: "Use an RSA key for SSE-KMS on S3." → Reality: integrated services accept only symmetric encryption keys.
- Trap: "Switch a key from encrypt/decrypt to sign/verify later." → Reality: key spec and key usage are permanent; create another key.
- Trap: "AWS owned keys show up in my CloudTrail and count against my KMS quotas." → Reality: they are invisible to you and don't count against your quotas.
- Trap: "Export the key material from KMS to encrypt locally." → Reality: KMS never returns key material; you get data keys (§4) or the public half of an asymmetric key.
Practice questions
-
A developer builds a reporting service that stores sensitive data in a DynamoDB table. The security team requires that every use of the encryption key is visible in CloudTrail and that the team can restrict who may use the key through its key policy. Which encryption option meets these requirements?
- A. Keep the default encryption with the AWS owned key.
- B. Switch the table to the AWS managed key
aws/dynamodb. - C. Switch the table to a customer managed KMS key.
- D. Encrypt each item client-side with a key stored in an environment variable.
-
A company must let external partners encrypt files that only the company can decrypt. The partners cannot call AWS APIs. Which TWO actions meet the requirement? (Select TWO.)
- A. Create an asymmetric RSA KMS key with key usage
ENCRYPT_DECRYPT. - B. Create a symmetric KMS key and give the partners its key material.
- C. Download the public key with
GetPublicKeyand distribute it to the partners. - D. Create an HMAC KMS key and send the partners its MAC tags.
- E. Create an ECC KMS key with key usage
SIGN_VERIFYand share its public key.
- A. Create an asymmetric RSA KMS key with key usage
Answers
- C. Only a customer managed key gives the team its own key policy while every use is still logged in CloudTrail. A is invisible in CloudTrail; B is logged, but its key policy is set by DynamoDB and can't be edited; D leaves a long-term key in configuration and loses KMS auditing.
- A, C. With an RSA encrypt/decrypt key, anyone holding the public key can encrypt offline while decryption stays inside KMS. B is impossible because KMS never exports symmetric key material; D produces authentication tags, not ciphertext; E creates a signing key, which can't encrypt.
2. Key policies grants and cross account access
Concept
- Every KMS key has exactly one key policy, a Regional resource policy. Unlike most AWS resources, a KMS key grants nothing to anyone (not even its own account) unless a key policy statement allows it.
- Three sources of permission combine: the key policy, IAM policies (effective only when the key policy delegates to the account), and grants (narrow, often temporary permissions that services such as EBS create on your behalf). An explicit deny anywhere wins.
- Cross-account use needs a "yes" on both sides: the owner's key policy names the other account (or its roles), and an IAM policy in the other account passes that permission to a principal.
Exam facts
- Default key policy for keys created through the API or CLI (
CreateKeywithoutPolicy): one statement,"Principal": {"AWS": "arn:aws:iam::<account>:root"},"Action": "kms:*". It gives the account control and lets IAM policies grant access; on its own it lets no user use the key. The account principal stands for the account and its administrators, not only the root user. - Default key policy for keys created in the console: that statement plus key administrators (manage, not use:
kms:Create*,Describe*,Enable*,List*,Put*,Update*,Revoke*,Disable*,Get*,Delete*, tagging,ScheduleKeyDeletion,CancelKeyDeletion,RotateKeyOnDemand) and key users (kms:Encrypt,Decrypt,ReEncrypt*,GenerateDataKey*,DescribeKey, plusCreateGrant/ListGrants/RevokeGrantlimited bykms:GrantIsForAWSResource, so services such as EBS can create grants for them). - Without the account-principal statement, IAM allows for the key have no effect (IAM denies still do). A policy that leaves nobody in control makes the key unmanageable until AWS Support helps;
PutKeyPolicyrejects a policy that would stop the caller from callingPutKeyPolicyagain unlessBypassPolicyLockoutSafetyCheckistrue(defaultfalse). - Format: up to 32 KB;
"Resource": "*"means "this key";PolicyNameis alwaysdefault; aSidmay contain spaces. Valid principals: AWS accounts, IAM roles and users, assumed-role sessions, federated sessions and some AWS service principals;"*"(every identity in every account) is accepted, but AWS warns never to allow anything with it unless conditions narrow it. IAM groups are never valid principals. - Condition keys to recognise:
kms:ViaService(use allowed only when the named service calls on the principal's behalf, value<service>.<region>.amazonaws.com; every AWS managed key's policy uses it);kms:CallerAccount;kms:EncryptionContext:<key>andkms:EncryptionContextKeys;kms:GrantIsForAWSResource;kms:KeySpec;kms:RotationPeriodInDays;kms:ScheduleKeyDeletionPendingWindowInDays;kms:RecipientAttestation:*(§13). - Grants: each grant covers one key and allows (never denies) a list of operations to one grantee principal (since March 2026 it can also be a grantee service principal, which needs a
SourceArnconstraint), optionally limited by encryption context; grants are eventually consistent (usually seconds, sometimes several minutes), soCreateGrantreturns a grant token the grantee passes to use the grant at once; up to 50,000 grants per key (EBS creates one per attached encrypted volume); a grant ends withRetireGrant(retiring principal or grantee) orRevokeGrant(key administrator). - Cross-account rules: only cryptographic operations,
DescribeKey,GetPublicKey,GetKeyRotationStatusand grant operations work across accounts, never administration such asScheduleKeyDeletion; the caller names the key by key ARN or alias ARN (key ARN only for the grant operations andGetKeyRotationStatus; symmetricDecryptcan omit the key), because a bare key ID or alias name is looked up in the caller's own account; requests are throttled against the caller's account; CloudTrail records them in both accounts. Sharing encrypted snapshots and AMIs follows the same rules (workflow:notes/ec2-mastery-guide.md§6). - Prefer granting
kms:Decryptin the key policy or in IAM with specific key ARNs: an IAMkms:Decrypton"Resource": "*"lets the principal decrypt with any key, including other accounts' keys, whose policy trusts your account.
Developer angle
Key policy statement that lets an application role in account 444455556666 use the key only through S3 in us-east-1 and only for one bucket (with S3 Bucket Keys the context value becomes the bucket ARN, §5):
{
"Sid": "OrdersAppViaS3Only",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::444455556666:role/OrdersAppRole" },
"Action": ["kms:Decrypt", "kms:GenerateDataKey"],
"Resource": "*",
"Condition": {
"StringEquals": { "kms:ViaService": "s3.us-east-1.amazonaws.com" },
"StringLike": { "kms:EncryptionContext:aws:s3:arn": "arn:aws:s3:::orders-bucket/*" }
}
}
The role's IAM policy in account 444455556666 must allow the same actions on the full key ARN. Day-to-day commands:
aws kms get-key-policy --key-id 1234abcd-12ab-34cd-56ef-1234567890ab --policy-name default --output text > policy.json
aws kms put-key-policy --key-id 1234abcd-12ab-34cd-56ef-1234567890ab --policy-name default --policy file://policy.json
aws kms create-grant --key-id arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab \
--grantee-principal arn:aws:iam::111122223333:role/BatchWorker \
--operations Decrypt GenerateDataKey \
--constraints EncryptionContextSubset={tenant=acme}
aws kms list-grants --key-id 1234abcd-12ab-34cd-56ef-1234567890ab
aws kms retire-grant --grant-token "$GRANT_TOKEN"
AccessDeniedExceptionchecklist: does the key policy delegate to the account or name the principal? Does the IAM policy allow the action on this key ARN? Is akms:ViaServiceor encryption-context condition unmet (for example, the app calls KMS directly but the key only allows S3)? Is there an explicit deny in an SCP, RCP or permissions boundary? (Resource control policies don't apply to AWS managed keys.) A cross-account call that names the key by bare key ID or alias name fails differently, withNotFoundException, because KMS searches the caller's account.
Traps
- Trap: "An IAM policy with
kms:Decryptis enough." → Reality: only if the key policy delegates to the account (the default statement) or names the principal. - Trap: "
arn:aws:iam::111122223333:rootin a key policy means only the root user." → Reality: it is the account principal, which lets the account's IAM policies grant access. - Trap: "Use a grant to block a role from a key." → Reality: grants only allow; use a deny statement.
- Trap: "Cross-account use only needs the owner's key policy." → Reality: the caller's account also needs an IAM allow.
- Trap: "Put an IAM group in the key policy
Principal." → Reality: groups are never principals; use roles or the account. - Trap: "
kms:ViaServicelimits which application calls KMS directly." → Reality: it matches only requests an AWS service makes on the principal's behalf; a direct SDK call doesn't satisfy it.
Practice questions
-
A Lambda function in account B reads objects from an S3 bucket in account A. The objects are encrypted with SSE-KMS using a customer managed key in account A. The bucket policy and the function's role already allow
s3:GetObject, but the call fails with anAccessDeniederror that comes from KMS. What should the developer do?- A. Re-encrypt the objects with the AWS managed key
aws/s3. - B. Allow
kms:Decryptfor the role in the key policy and in the role's IAM policy. - C. Create a new KMS key in account B and give the function's role a grant on it.
- D. Add a
kms:ViaServicecondition forlambda.us-east-1.amazonaws.comto the role's IAM policy.
- A. Re-encrypt the objects with the AWS managed key
-
A security engineer must make sure that a customer managed key is used by the
OrdersAppRolerole only when Amazon S3 in us-east-1 calls KMS on the role's behalf, and only for objects in theorders-bucketbucket. Which TWO condition keys should the key policy statement use? (Select TWO.)- A.
kms:GrantIsForAWSResourceset totrue - B.
kms:ViaServicewith the values3.us-east-1.amazonaws.com - C.
kms:CallerAccountset to the account ID - D.
kms:EncryptionContext:aws:s3:arnmatchingarn:aws:s3:::orders-bucket/* - E.
aws:SourceIpset to the application's address range
- A.
Answers
- B. Cross-account KMS use needs the key policy in the owning account (account A) and an IAM allow in the caller's account (account B). A fails because AWS managed keys can't be used from another account; C creates a key that never encrypted these objects; D narrows access instead of granting the missing permission (and here S3, not Lambda, calls KMS).
- B, D.
kms:ViaServicelimits use to S3 requests in us-east-1, and S3's encryption context pins the bucket. A applies only to grant operations; C limits the account but neither the service nor the bucket; E doesn't describe requests that S3 makes for the role.
3. KMS API operations and the 4 KB limit
Concept
- KMS cryptographic calls take and return small blobs:
Encryptwith a symmetric key accepts at most 4,096 bytes of plaintext. Larger data is never sent to KMS; KMS gives you data keys instead (§4). - Blobs are base64 in the CLI and raw bytes in the SDKs. Symmetric ciphertext carries metadata (which key, which key material), which is why
Decryptcan find the key on its own. - An encryption context (non-secret key-value pairs) is bound to the ciphertext as additional authenticated data: decryption succeeds only with exactly the same pairs (case-sensitive), and the pairs appear in CloudTrail, so they double as audit information and as a policy condition.
Exam facts
| Operation | What it does | Limits and notes | Permission |
|---|---|---|---|
Encrypt |
Encrypts up to 4,096 bytes with a symmetric key | RSA keys take less: RSA_2048 190 bytes (OAEP-SHA-256) or 214 (SHA-1), RSA_3072 318/342, RSA_4096 446/470; ciphertext up to 6,144 bytes | kms:Encrypt |
Decrypt |
Returns plaintext up to 4,096 bytes | KeyId optional for symmetric ciphertext (recommended); required, with EncryptionAlgorithm, for asymmetric |
kms:Decrypt |
ReEncrypt |
Decrypts and re-encrypts inside KMS; plaintext never reaches the caller | Changes the key or the encryption context; source and destination keys may be in different accounts | kms:ReEncryptFrom (source) + kms:ReEncryptTo (destination) |
GenerateDataKey |
Returns a data key in plaintext and encrypted under the KMS key | KeySpec AES_256/AES_128 or NumberOfBytes 1–1,024 (exactly one of them); symmetric KMS keys only |
kms:GenerateDataKey |
GenerateDataKeyWithoutPlaintext |
Returns only the encrypted data key | For a component that stores keys now and decrypts one (Decrypt) when it needs it |
kms:GenerateDataKeyWithoutPlaintext |
GenerateDataKeyPair (and …WithoutPlaintext) |
Asymmetric data key pair | Own small quotas (RSA_2048: 20 per second) | kms:GenerateDataKeyPair |
GenerateRandom |
1–1,024 random bytes | Uses no KMS key; optionally generated in a CloudHSM key store | kms:GenerateRandom (IAM policy) |
Sign/Verify, GenerateMac/VerifyMac, DeriveSharedSecret |
Signatures, HMAC tags, ECDH | Key usage must match the operation | matching kms: action |
- The encryption context works only with symmetric encryption keys (asymmetric and HMAC operations don't support it); if you omitted it when encrypting, omit it when decrypting, and vice versa.
- Error codes:
InvalidCiphertextException: corrupt ciphertext or an encryption context that differs from the one used to encrypt;IncorrectKeyException: theKeyIdyou passed isn't the key that encrypted the data;DisabledException: the key is disabled;KMSInvalidStateException: the key's state doesn't allow the call (pending deletion, pending import, unavailable custom key store key);NotFoundException: wrong identifier, or the key lives in another Region or account;InvalidKeyUsageException: for exampleEncryptwith aSIGN_VERIFYkey;AccessDeniedException: a policy problem (§2);ThrottlingException: quota (§5);KeyUnavailableException,KMSInternalException,DependencyTimeoutException: HTTP 500, safe to retry. DryRun(since July 2023) checks authorization without doing the work: an allowed request fails withDryRunOperationException;DecryptandReEncryptalso acceptDryRunModifiers=IGNORE_CIPHERTEXTto test permissions without a real ciphertext (the key must then be named explicitly).- KMS keys are Regional: call the endpoint of the key's Region (multi-Region keys, §6, give each Region its own copy).
- KMS can't decrypt ciphertext produced by the Encryption SDK, S3 client-side encryption or the DynamoDB Encryption Client, and those libraries can't decrypt raw
Encryptoutput.
Developer angle
# Encrypt a small secret (fileb:// passes raw bytes; the output is base64, so decode it before storing)
aws kms encrypt --key-id alias/orders --plaintext fileb://db-password.txt \
--encryption-context app=billing,env=prod \
--query CiphertextBlob --output text | base64 --decode > db-password.enc
# Decrypt with the same context; KeyId is optional for symmetric keys but explicit is safer
aws kms decrypt --ciphertext-blob fileb://db-password.enc --key-id alias/orders \
--encryption-context app=billing,env=prod \
--query Plaintext --output text | base64 --decode
# Move the ciphertext to another key without exposing the plaintext
aws kms re-encrypt --ciphertext-blob fileb://db-password.enc \
--source-encryption-context app=billing,env=prod \
--destination-key-id alias/orders-v2 --destination-encryption-context app=billing,env=prod \
--query CiphertextBlob --output text | base64 --decode > db-password-v2.enc
aws kms generate-random --number-of-bytes 32 --query Plaintext --output text
# Permission check only: an allowed call ends in DryRunOperationException
aws kms decrypt --ciphertext-blob fileb://db-password.enc --encryption-context app=billing,env=prod --dry-run
import boto3
kms = boto3.client("kms", region_name="us-east-1")
blob = kms.encrypt(KeyId="alias/orders", Plaintext=b"s3cr3t",
EncryptionContext={"app": "billing"})["CiphertextBlob"]
try:
kms.decrypt(CiphertextBlob=blob, KeyId="alias/orders",
EncryptionContext={"app": "Billing"}) # different case: decryption fails
except kms.exceptions.InvalidCiphertextException:
print("the encryption context must match exactly")
Traps
- Trap: "Encrypt a 2 MB file by calling
Encrypt." → Reality: the limit is 4,096 bytes; use a data key (§4). - Trap: "
Decryptalways needs the key ID." → Reality: it is optional for symmetric ciphertext (required for asymmetric); passing a different key raisesIncorrectKeyException. - Trap: "
InvalidCiphertextExceptionmeans the stored data is corrupt." → Reality: most often the encryption context doesn't match exactly. - Trap: "Put the customer's e-mail address in the encryption context." → Reality: the context is logged in plaintext in CloudTrail; use non-secret identifiers.
- Trap: "Decrypting and re-encrypting in the app is the same as
ReEncrypt." → Reality: it exposes plaintext and needskms:Decryptpluskms:Encrypt;ReEncryptkeeps the plaintext inside KMS. - Trap: "
GenerateRandomneeds a statement in a key policy." → Reality: it uses no key; allowkms:GenerateRandomin IAM.
Practice questions
-
After a refactor, an application that stores tokens encrypted with KMS gets
InvalidCiphertextExceptionon everyDecryptcall for existing tokens. The refactor renamed the encryption context key fromtenantIdtotenant; new tokens encrypt and decrypt correctly. What is the MOST likely fix for the existing tokens?- A. Add the
KeyIdparameter to everyDecryptcall. - B. Grant the application role
kms:ReEncrypt*on the key. - C. Enable automatic key rotation on the key and retry the failed
Decryptcalls. - D. Pass the original context key
tenantIdwith the same values toDecrypt.
- A. Add the
-
A data team must move archived ciphertext from KMS key A to KMS key B. Security requires that the plaintext never reaches the migration script. Which TWO actions meet the requirement? (Select TWO.)
- A. Call
Decryptwith key A and thenEncryptwith key B in the script. - B. Allow
kms:Decrypton key B for the script's role. - C. Call
ReEncryptwithDestinationKeyIdset to key B. - D. Enable automatic key rotation on key A.
- E. Allow
kms:ReEncryptFromon key A andkms:ReEncryptToon key B for the script's role.
- A. Call
Answers
- D. Decryption must present exactly the context bound at encryption time. A is optional for symmetric ciphertext and doesn't bypass the context check; B doesn't affect
Decrypt; C never fixes a context mismatch and doesn't change existing ciphertext. - C, E.
ReEncryptdecrypts and re-encrypts inside KMS and needsReEncryptFromon the source key plusReEncryptToon the destination key. A exposes plaintext to the script; B is the wrong permission on the wrong key; D keeps the data under key A.
4. Envelope encryption and the Encryption SDK
Concept
- Envelope encryption: encrypt the data locally with a data key, protect that data key by encrypting it under a KMS key (the wrapping key), store the encrypted data key next to the ciphertext and wipe the plaintext data key from memory. Decrypting means one
Decryptcall on the small encrypted data key, then local decryption. - Benefits: no size limit, one KMS call per object or per data key, the payload never leaves your process, and many data keys stay under one centrally controlled KMS key. Integrated services (S3 SSE-KMS, EBS, Secrets Manager, advanced
SecureStringparameters) do exactly this internally. - The AWS Encryption SDK is a free, open-source client library that implements envelope encryption with safe defaults: a unique data key per message, AES-GCM with key derivation (HKDF), key commitment and an ECDSA signature, all packed into a portable encrypted message whose header carries the encrypted data keys and the encryption context.
Exam facts
- Rule: client-side encryption of anything above 4 KB →
GenerateDataKey(or the Encryption SDK, which calls it for you); never chunk the data into 4 KBEncryptcalls. GenerateDataKey→ encrypt now (plaintext key + encrypted copy);GenerateDataKeyWithoutPlaintext→ stock encrypted keys for later (whoever encrypts with one callsDecryptfirst).- Encryption SDK implementations: C, C#/.NET, Java, JavaScript, Python, Rust and Go, plus the AWS Encryption CLI; all of them interoperate. ⚠️ Changed: the SDK also ships for .NET, Rust and Go. Older material may say the SDK exists only for Java, Python, C and JavaScript (plus the CLI).
- One message can be wrapped under several keys at once (for example KMS keys in two Regions, or a KMS key plus an on-premises HSM key); any one of them can decrypt it.
- Wrapping keys are configured with keyrings (C, JavaScript, .NET, Rust, Go, Java 3.x, Python 4.x) or master key providers (the older interface of Java and Python, also used by the CLI); the AWS KMS keyring wraps with symmetric KMS keys (multi-Region keys included), and the newer versions also accept asymmetric RSA KMS keys.
- Commitment policy (since version 2.0): default
RequireEncryptRequireDecrypt, meaning encrypt and decrypt only with key commitment, which guarantees that a ciphertext decrypts to exactly one plaintext. - The encryption context is stored in plaintext in the message header and returned on decrypt; your code should check it contains what it expects.
- Formats don't mix: the SDK can't decrypt KMS
Encryptoutput, and KMSDecryptcan't open an SDK message (nor S3 client-side or DynamoDB Encryption Client output). - Data key caching (optional): a caching cryptographic materials manager plus a local cache (
LocalCryptoMaterialsCachein Java and Python) reuses data keys under security thresholds: maximum age (required; seconds, milliseconds in JavaScript), maximum messages encrypted (1 to 2^32, default 2^32) and maximum bytes encrypted (0 to 2^63 − 1, default 2^63 − 1). Use it whenGenerateDataKeycalls are slow, expensive or throttled; the price is key reuse, so keep the thresholds tight. The .NET implementation has no data key caching, and Java 3.x deprecates it in favour of the Hierarchical keyring. - Newer SDK versions (Java 3.x, Python 4.x, .NET 4.x, Rust, Go, JavaScript for Node.js 4.1.x) also offer the AWS KMS Hierarchical keyring: branch keys protected by KMS and stored in a DynamoDB table (the key store) are cached locally, so most operations make no KMS call; decrypting needs a Hierarchical keyring that can read the same key store.
- The SDK needs AWS only if you choose KMS keys as wrapping keys.
Developer angle
Manual envelope encryption with boto3 and the cryptography package:
import os
import boto3
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
kms = boto3.client("kms")
context = {"app": "orders", "doc": "invoice-42"}
data = b"...several megabytes of invoice data..."
# Encrypt: one KMS call; the payload never leaves the process
dk = kms.generate_data_key(KeyId="alias/orders", KeySpec="AES_256", EncryptionContext=context)
nonce = os.urandom(12)
envelope = {"key": dk["CiphertextBlob"], "nonce": nonce,
"body": AESGCM(dk["Plaintext"]).encrypt(nonce, data, None)}
del dk # drop the plaintext data key as soon as possible
# Decrypt: KMS only ever sees the small encrypted data key
plain_key = kms.decrypt(CiphertextBlob=envelope["key"], EncryptionContext=context)["Plaintext"]
assert AESGCM(plain_key).decrypt(envelope["nonce"], envelope["body"], None) == data
The Encryption SDK for Python does the same in two calls, here with data key caching:
import aws_encryption_sdk
from aws_encryption_sdk import CommitmentPolicy
client = aws_encryption_sdk.EncryptionSDKClient(
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT)
provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(
key_ids=["arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"])
caching_cmm = aws_encryption_sdk.CachingCryptoMaterialsManager(
master_key_provider=provider,
cache=aws_encryption_sdk.LocalCryptoMaterialsCache(capacity=100),
max_age=300.0,
max_messages_encrypted=1000)
ciphertext, _ = client.encrypt(source=b"telemetry record", materials_manager=caching_cmm,
encryption_context={"stream": "telemetry"})
plaintext, header = client.decrypt(source=ciphertext, materials_manager=caching_cmm)
assert header.encryption_context["stream"] == "telemetry"
- Permissions: the encrypting role needs
kms:GenerateDataKey, the decrypting rolekms:Decrypt, both on the wrapping key's ARN.
Traps
- Trap: "Store the plaintext data key with the file so decryption is fast." → Reality: store only the encrypted data key; the plaintext key lives briefly in memory.
- Trap: "Decrypt an Encryption SDK message with KMS
Decrypt." → Reality: use the SDK's decrypt call; the formats are incompatible. - Trap: "Use
GenerateDataKeyWithoutPlaintextto encrypt a file right now." → Reality: it returns no plaintext key; useGenerateDataKey. - Trap: "Rotating the KMS key re-encrypts every data key." → Reality: rotation affects only new wrapping; existing encrypted data keys still decrypt as they are.
- Trap: "Data key caching has no downside." → Reality: it trades key reuse for fewer calls; set a maximum age and usage limits.
Practice questions
-
A media service must encrypt 200 MB video files client-side before uploading them to Amazon S3, using a KMS key that the security team controls, with the fewest KMS API calls per file. Which approach should the developer use?
- A. Split each file into 4 KB chunks and call
Encrypton every chunk before uploading it. - B. Call
GenerateDataKeyper file, encrypt locally, and store the encrypted data key with the object. - C. Call
GenerateRandomto create a 32-byte key, encrypt locally, and store the key in the object metadata. - D. Upload the file unencrypted and call
ReEncrypton the stored object.
- A. Split each file into 4 KB chunks and call
-
A telemetry pipeline that uses the AWS Encryption SDK receives
ThrottlingExceptionerrors fromGenerateDataKeyduring traffic peaks. Which TWO actions address the problem? (Select TWO.)- A. Enable data key caching with a caching cryptographic materials manager and short security thresholds.
- B. Switch the wrapping key to an asymmetric RSA KMS key.
- C. Call
Encryptfor every record instead of using data keys. - D. Request a higher
Cryptographic operations (symmetric) request ratequota in Service Quotas. - E. Set the commitment policy to
ForbidEncryptAllowDecrypt.
Answers
- B. One data key per file means one KMS call, no size limit, and plaintext that never leaves the process. A makes over 50,000 calls per file; C stores an unprotected key next to the data; D sends plaintext to S3, and
ReEncryptworks on KMS ciphertext, not on objects. - A, D. Caching reuses data keys so fewer
GenerateDataKeycalls happen, and the shared symmetric quota is adjustable. B has lower quotas, and asymmetric KMS keys can't generate data keys; C multiplies the number of calls; E turns off key commitment without reducing calls.
5. Request quotas throttling and S3 Bucket Keys
Concept
- KMS enforces per-second request quotas for each account and Region. All cryptographic operations on one key type share a single quota, and calls that AWS services make on your behalf (S3 uploads and downloads with SSE-KMS, for example) count against it.
- Above the quota KMS rejects calls with
ThrottlingException(HTTP 400): retry with exponential backoff and jitter, lower the call rate (cache data keys or secrets, use S3 Bucket Keys), or request a higher quota. - S3 Bucket Keys make S3 fetch a short-lived bucket-level key from KMS and derive per-object data keys from it inside S3, so most SSE-KMS requests never reach KMS.
Exam facts
| Quota (requests per second, per account and Region) | Value |
|---|---|
Symmetric cryptographic operations, shared (Encrypt, Decrypt, ReEncrypt, GenerateDataKey, GenerateDataKeyWithoutPlaintext, GenerateRandom, GenerateMac, VerifyMac) |
10,000 default · 20,000 in us-east-2, ap-southeast-1, ap-southeast-2, ap-northeast-1, eu-central-1, eu-west-2, eu-south-2 · 100,000 in us-east-1, us-west-2, eu-west-1 |
| RSA keys (shared) | 1,000 |
| ECC and SM2 keys (shared) | 1,000 |
| ML-DSA keys (shared) | 1,000 |
GenerateDataKeyPair RSA_2048 / ECC curves |
20 / 100 |
| Each custom key store (CloudHSM or external) | 1,800 (not adjustable for CloudHSM key stores) |
| Management APIs (examples) | CreateKey 5 · DescribeKey 2,000 · PutKeyPolicy 15 |
- ⚠️ Changed: the shared symmetric quota is 10,000 per second by default, 20,000 in seven large Regions and 100,000 in us-east-1, us-west-2 and eu-west-1 (raised July 2024). Older material may say 5,500, 10,000 and 30,000 per second.
- ⚠️ Changed: RSA and ECC keys each have a shared quota of 1,000 per second. Older material may say 500 per second for RSA and 300 per second for ECC.
- Each key type's quota is calculated independently; symmetric operations on keys in a custom key store count against both the account quota and the key store's 1,800 per second.
- All KMS request quotas are adjustable through Service Quotas (or AWS Support where Service Quotas doesn't list them), except the CloudHSM key store quota; you can track usage in Service Quotas and alarm on it in CloudWatch.
- Cross-account calls are throttled in the caller's account. AWS owned keys never count against your quotas. Rapid state changes on one key (a "hot key", e.g. repeated enable/disable) are throttled even below the quota.
- SDKs retry throttled calls to a degree; production code adds backoff with jitter (general retry guidance:
notes/cli-sdk-policies-guide.md). - S3 and KMS: without a Bucket Key, every SSE-KMS
PutObjectmakes S3 callGenerateDataKeyand everyGetObjectmakes it callDecrypt; uploaders therefore needkms:GenerateDataKey, readerskms:Decrypt, multipart uploaders both. - S3 Bucket Keys: cut KMS request costs by up to 99 percent; enabled per bucket (
BucketKeyEnabledin the default encryption rule) or per object (headerx-amz-server-side-encryption-bucket-key-enabled: true) with no application change; only new objects use them automatically (convert existing ones withUpdateObjectEncryptionorCopyObject, next bullet); a bucket-level key is fetched at least once per requester; KMS CloudTrail events show the bucket ARN as encryption context, and there are far fewer of them, so policies that match object ARNs inkms:EncryptionContext:aws:s3:arnmust switch to the bucket ARN first; they work with SRR and CRR; they are not supported with DSSE-KMS. - ⚠️ Changed: existing SSE-S3 and SSE-KMS objects no longer need a rewrite to get a Bucket Key.
UpdateObjectEncryption(aws s3api update-object-encryptionwith"BucketKeyEnabled": true, or a Batch Operations Update object encryption job in bulk) switches them to SSE-KMS with a Bucket Key in place: no data copy, and ETag,Last-Modifiedand storage class are kept. It needss3:UpdateObjectEncryptionpluskms:Encrypt,kms:Decrypt,kms:GenerateDataKeyandkms:ReEncrypt*, and a full key ARN (no key ID or alias), by default from the bucket owner's account;BucketKeyEnableddefaults tofalse. Unencrypted, SSE-C and DSSE-KMS objects still needCopyObject(or a Batch Operations Copy job, which doesn't support SSE-C). Older material may say existing objects get a Bucket Key only when they are copied.
Developer angle
# SSE-KMS with an S3 Bucket Key for new objects
aws s3api put-bucket-encryption --bucket orders-bucket --server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"},
"BucketKeyEnabled": true}]}'
# An existing SSE-S3 or SSE-KMS object switches to the Bucket Key in place (no copy; ETag and Last-Modified kept)
aws s3api update-object-encryption --bucket orders-bucket --key 2026/report.csv \
--object-encryption '{"SSEKMS": {
"KMSKeyArn": "arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
"BucketKeyEnabled": true}}'
# Objects that UpdateObjectEncryption rejects (unencrypted, SSE-C, DSSE-KMS) pick it up only when rewritten
# (an SSE-C source also needs --copy-source-sse-customer-algorithm and --copy-source-sse-customer-key)
aws s3api copy-object --bucket orders-bucket --key 2022/report.csv --copy-source orders-bucket/2022/report.csv \
--server-side-encryption aws:kms \
--ssekms-key-id arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab \
--bucket-key-enabled
# Current KMS cryptographic quotas in this Region
aws service-quotas list-service-quotas --service-code kms \
--query 'Quotas[?contains(QuotaName, `Cryptographic operations`)].[QuotaName,Value]'
import boto3
from botocore.config import Config
# Adaptive retry mode adds client-side rate limiting on top of exponential backoff
kms = boto3.client("kms", config=Config(retries={"max_attempts": 10, "mode": "adaptive"}))
Traps
- Trap: "Each KMS operation has its own quota, so heavy
Decrypttraffic can't slowEncrypt." → Reality: symmetric cryptographic operations share one quota. - Trap: "For cross-account calls, the account that owns the key is throttled." → Reality: the calling account is.
- Trap: "Enabling a Bucket Key re-encrypts existing objects." → Reality: only new writes use it automatically; convert existing SSE-S3 or SSE-KMS objects in place with
UpdateObjectEncryption, and copy the others withCopyObject. - Trap: "Bucket Keys need code changes in every uploader." → Reality: the bucket setting is enough.
- Trap: "Combine DSSE-KMS with Bucket Keys to save cost." → Reality: Bucket Keys aren't supported with DSSE-KMS.
- Trap: "Keep
kms:EncryptionContext:aws:s3:arnconditions on object ARNs after enabling Bucket Keys." → Reality: the context becomes the bucket ARN; update the policies first.
Practice questions
-
An ingestion service in eu-west-3 uploads about 12,000 small objects per second to a bucket that uses SSE-KMS with a customer managed key, and uploads fail intermittently with
ThrottlingExceptionerrors from KMS. The team wants the MOST cost-effective fix that requires no application code changes. What should the developer do?- A. Switch the uploader to client-side encryption that calls
Encryptfor each object. - B. Change the bucket's default encryption to DSSE-KMS.
- C. Enable an S3 Bucket Key in the bucket's default encryption configuration.
- D. Move the KMS key into an AWS CloudHSM key store.
- A. Switch the uploader to client-side encryption that calls
-
A developer enables S3 Bucket Keys on a bucket that uses SSE-KMS. Which TWO results should the developer expect? (Select TWO.)
- A. Existing objects are re-encrypted automatically in the background.
- B. KMS CloudTrail events for new objects show the bucket ARN as the encryption context.
- C. Objects can now use DSSE-KMS at a lower KMS cost.
- D. Every
PutObjectcall must now include an extra request header. - E. S3 sends fewer requests to KMS, which lowers KMS request costs.
Answers
- C. A Bucket Key removes most per-object KMS calls without touching code (12,000 per second is above the 10,000 default in eu-west-3). A adds even more KMS calls plus code changes; B isn't compatible with Bucket Keys and still calls KMS per object; D lowers the ceiling to 1,800 per second per key store and adds HSM cost.
- B, E. The bucket-level key cuts traffic to KMS, and CloudTrail records the bucket ARN as the context. A is wrong because only new writes use the Bucket Key automatically; C is unsupported; D is unnecessary because the bucket setting applies by default.
6. Key rotation deletion and multi Region keys
Concept
- Rotation gives a KMS key new key material for future encryption and keeps every earlier version for decrypting older ciphertext. The key ID, ARN, aliases, policy and grants stay the same, so applications need no change, and nothing already encrypted is re-encrypted.
- Deletion is final: after a mandatory waiting period the key and its material are destroyed, and everything encrypted under it becomes unrecoverable. Disabling is the reversible way to stop all use.
- Multi-Region keys are related keys in different Regions that share key ID and key material, so ciphertext produced in one Region decrypts in another without a cross-Region call.
Exam facts
| Key | Automatic rotation | On-demand rotation | Manual rotation (new key + alias) |
|---|---|---|---|
Customer managed, symmetric, AWS_KMS origin |
Optional, off by default; RotationPeriodInDays 90–2,560 (default 365) |
Yes, up to 25 times per key (not adjustable), independent of the schedule | Possible |
Customer managed, symmetric, imported material (EXTERNAL) |
No | Yes, after importing new material (state Pending rotation) | Yes |
| Asymmetric, HMAC, keys in custom key stores | No | No | The only option |
| AWS managed | Every year; can't be changed or triggered | No | No |
| AWS owned | Decided by the owning service | No | No |
- ⚠️ Changed: automatic rotation of customer managed keys has a configurable period of 90–2,560 days (default 365, since April 2024). Older material may say rotation is fixed at once a year.
- ⚠️ Changed:
RotateKeyOnDemandrotates immediately, at most 25 times per key, without changing the automatic schedule (April 2024); it also covers symmetric keys with imported material (June 2025) and multi-Region keys with imported material (November 2025). Older material may say on-demand rotation doesn't exist and that imported keys can only be rotated manually by repointing an alias. - ⚠️ Changed: AWS managed keys rotate every year (since May 2022). Older material may say every three years (about 1,095 days).
- ⚠️ Changed: only the first and second rotations each add $1 per month to a key's price; later rotations are free (April 2024). Older material may say every rotation adds $1 per month.
- Disabled keys and keys pending deletion aren't rotated; a re-enabled key that missed its date rotates at once. Track rotation with
GetKeyRotationStatus(KeyRotationEnabled,RotationPeriodInDays,NextRotationDate,OnDemandRotationStartDate),ListKeyRotations, the CloudTrailRotateKeyevent and the EventBridge eventKMS CMK Rotation. - Manual rotation: create a new key, point the alias at it with
UpdateAlias, keep the old key enabled for decryption. For symmetric ciphertext callDecryptwithoutKeyId(the blob names its key); asymmetric decryption and HMAC verification must name the exact key. - Deletion: only customer managed keys;
ScheduleKeyDeletionwithPendingWindowInDays7–30 (default 30; the actual deletion can come up to 24 hours later); thePendingDeletionstate blocks every cryptographic call (KMSInvalidStateException) and stops the monthly fee;CancelKeyDeletionleaves the keyDisabled, so callEnableKey; a CloudWatch alarm on attempted use during the waiting period exposes forgotten dependencies. - Imported key material (BYOK): supported for symmetric, asymmetric (except ML-DSA), HMAC and multi-Region keys, never in custom key stores; you keep the durable copy; you can set an expiry or call
DeleteImportedKeyMaterial(reversible by importing again); no automatic rotation. - Multi-Region keys: created with
--multi-region(key IDmrk-…) and copied withReplicateKey. Shared: key ID, key material (and every rotation of it), key spec, key usage, origin, rotation settings. Independent per Region: key policy, grants, aliases, tags, description, enabled state. Each Region's key is billed as a key; an existing single-Region key can't be converted; AWS managed keys are always single-Region; no multi-Region keys in custom key stores; rotation is configured and triggered on the primary; a primary is deleted only after its replicas. - ⚠️ Changed: since June 2021 a set of multi-Region keys lets you encrypt in one Region and decrypt in another without re-encrypting or calling across Regions (each Region still holds its own key resource). Older material may say a KMS key can never be used outside the Region where it was created.
- Most AWS services treat multi-Region keys like single-Region keys (S3 replication still re-encrypts in the destination Region); the main beneficiaries are client-side encryption (Encryption SDK, DynamoDB global tables encrypted client-side), disaster recovery and multi-Region signing.
- Regional keys in practice: copying an encrypted EBS snapshot or AMI to another Region re-encrypts it under a key that exists in the destination Region (workflow:
notes/ec2-mastery-guide.md§6).
Developer angle
Management operations take a key ID or key ARN, not an alias:
KEY_ID=1234abcd-12ab-34cd-56ef-1234567890ab
aws kms enable-key-rotation --key-id "$KEY_ID" --rotation-period-in-days 180
aws kms get-key-rotation-status --key-id "$KEY_ID"
aws kms rotate-key-on-demand --key-id "$KEY_ID"
aws kms list-key-rotations --key-id "$KEY_ID"
# Manual rotation for an HMAC key: new key, move the alias, keep the old key enabled
NEW_KEY=$(aws kms create-key --key-spec HMAC_256 --key-usage GENERATE_VERIFY_MAC --query KeyMetadata.KeyId --output text)
aws kms update-alias --alias-name alias/session-mac --target-key-id "$NEW_KEY"
# Safe retirement: disable, watch CloudTrail for DisabledException, then schedule deletion
aws kms disable-key --key-id 0987dcba-09fe-87dc-65ba-ab0987654321
aws kms schedule-key-deletion --key-id 0987dcba-09fe-87dc-65ba-ab0987654321 --pending-window-in-days 30
# Multi-Region: primary in us-east-1, replica in eu-west-1
aws kms create-key --multi-region --description "global orders" --region us-east-1
aws kms replicate-key --key-id mrk-1234abcd12ab34cd56ef1234567890ab --replica-region eu-west-1 --region us-east-1
- IAM:
kms:RotationPeriodInDaysbounds the period principals may set;kms:ScheduleKeyDeletionPendingWindowInDayscan enforce long waiting periods;kms:TrailingDaysWithoutKeyUsagecan denyDisableKey/ScheduleKeyDeletionfor keys used recently.
Traps
- Trap: "After rotation, re-encrypt the old data or it can't be read." → Reality: KMS keeps every version of the material and picks the right one automatically.
- Trap: "Update the key ID in the application after automatic rotation." → Reality: the key ID doesn't change; only manual rotation needs an alias swap.
- Trap: "Delete a compromised key immediately." → Reality: the shortest waiting period is 7 days; disable the key now and schedule deletion.
- Trap: "Rotate the
aws/s3key every 90 days." → Reality: AWS managed keys rotate yearly and their schedule can't be changed. - Trap: "A multi-Region key has one key policy for all Regions." → Reality: every replica has its own policy, grants and aliases.
- Trap: "Convert the existing key to multi-Region for disaster recovery." → Reality: impossible; create a new multi-Region key and re-encrypt.
Practice questions
-
A compliance rule requires the key material of a symmetric customer managed KMS key to change every 180 days. Applications must keep working without code or configuration changes. Which solution meets the requirement with the LEAST operational overhead?
- A. Create an EventBridge schedule that invokes a Lambda function to call
RotateKeyOnDemandevery 180 days. - B. Create a new KMS key every 180 days and move the alias to it with
UpdateAlias. - C. Switch the applications to the AWS managed key of the service.
- D. Run
aws kms enable-key-rotationwith--rotation-period-in-days 180.
- A. Create an EventBridge schedule that invokes a Lambda function to call
-
An application encrypts records client-side in us-east-1 with the AWS Encryption SDK. During a Regional outage it must decrypt the same ciphertext in eu-west-1 without the data having been re-encrypted beforehand. Which TWO steps meet the requirement? (Select TWO.)
- A. Use
ReEncryptto copy the existing single-Region key into eu-west-1. - B. Encrypt with a multi-Region primary key in us-east-1 that is replicated to eu-west-1 with
ReplicateKey. - C. Configure the application in eu-west-1 to decrypt with the replica key in eu-west-1.
- D. Convert the existing single-Region key into a multi-Region key.
- E. Store the ciphertext in an S3 bucket that replicates to eu-west-1 with Cross-Region Replication.
- A. Use
Answers
- D. A custom automatic rotation period does it with no extra components, and the key ID stays the same. A works but adds a scheduler and a function to maintain and burns on-demand rotations (25 per key) on a routine job; B needs work every 180 days and old keys to keep; C can't meet a 180-day rule because AWS managed keys rotate yearly.
- B, C. Related multi-Region keys share key material, so the replica in eu-west-1 decrypts ciphertext produced in us-east-1. A is impossible because
ReEncryptworks on ciphertext, not on keys; D isn't supported; E copies the ciphertext, but decrypting it would still need the key in us-east-1.
7. CloudHSM versus KMS and custom key stores
Concept
- AWS CloudHSM gives you dedicated, single-tenant hardware security modules in a cluster in your VPC. AWS provisions and maintains the hardware; you own the keys and the HSM users (admins and crypto users, distinct from IAM users) and use them through the CloudHSM client SDK (PKCS #11, JCE, OpenSSL Dynamic Engine, KSP/CNG). AWS can't see or recover your keys: lose the credentials and the keys are gone.
- AWS KMS is multi-tenant, fully managed, IAM-integrated and wired into almost every AWS service.
- Custom key stores connect KMS to key material you control: an AWS CloudHSM key store (your cluster) or an external key store (XKS: an HSM or key manager outside AWS, reached through an XKS proxy). Services still call KMS as usual; KMS forwards the cryptographic work to your HSMs.
Exam facts
| AWS KMS | AWS CloudHSM | |
|---|---|---|
| Who owns the HSMs | AWS, shared fleet behind the service | You: dedicated HSMs in your VPC |
| Validation | FIPS 140-3 Level 3 HSMs | hsm2m.medium FIPS 140-3 Level 3 (hsm1.medium FIPS 140-2 Level 3); cluster in FIPS or non-FIPS mode, fixed at creation |
| Access control | Key policies, IAM, grants | HSM users and passwords managed inside the cluster; IAM only governs the cluster-management API |
| AWS service integration | Native | Through a KMS custom key store, or your own software (SSL/TLS offload, Oracle TDE, certificate authority keys, Redshift) |
| Availability | Managed, 99.999 % SLA | You add HSMs; AWS recommends at least two HSMs in different AZs for production |
| Pricing | $1 per key-month + requests; free tier | Per HSM per hour, no upfront fee, no free tier |
- ⚠️ Changed: the AWS KMS HSMs are validated at FIPS 140-3 Level 3. Older material may say FIPS 140-2 Level 3.
- ⚠️ Changed: CloudHSM hsm2m.medium HSMs are FIPS 140-3 Level 3 certified, and clusters run in FIPS or non-FIPS mode (non-FIPS only on hsm2m.medium). Older material may say CloudHSM is FIPS 140-2 Level 3.
- Custom key store limits: symmetric encryption KMS keys only, so no asymmetric or HMAC keys, no automatic or on-demand rotation, no multi-Region keys and no imported material; 1,800 requests per second per key store (on top of the account quota); up to 10 custom key stores per account and Region; you own availability, so a disconnected store or an overloaded cluster fails KMS calls (
CustomKeyStoreInvalidStateException,KMSInvalidStateException,ThrottlingException). - AWS guidance: the standard KMS key store fits nearly every need; to prove control over key material, consider imported key material first; use a CloudHSM key store only when a regulation requires single-tenant HSMs you control; use an external key store only when key material must stay outside AWS (lowest throughput, highest latency, no SLA). Custom key stores are not more secure, only more controlled and more expensive.
- ⚠️ Changed: SSE-C is disabled by default for new S3 general purpose buckets (and for existing buckets in accounts without SSE-C objects) since April 2026; enabling it takes
PutBucketEncryptionwithBlockedEncryptionTypesset toNONE, otherwise SSE-C writes fail with HTTP 403AccessDenied. Older material may say SSE-C (for example with keys held in CloudHSM) works on any bucket.
Developer angle
aws cloudhsmv2 describe-clusters --query 'Clusters[].[ClusterId,State,HsmType,Mode]'
# Connect KMS to the cluster, then create keys whose material lives in your HSMs
aws kms create-custom-key-store --custom-key-store-name orders-hsm \
--cloud-hsm-cluster-id cluster-1a23b4cdefg \
--trust-anchor-certificate file://customerCA.crt --key-store-password "$KMSUSER_PASSWORD"
aws kms connect-custom-key-store --custom-key-store-id cks-1234567890abcdef0
aws kms describe-custom-key-stores --custom-key-store-id cks-1234567890abcdef0 \
--query 'CustomKeyStores[0].ConnectionState'
aws kms create-key --origin AWS_CLOUDHSM --custom-key-store-id cks-1234567890abcdef0 --description "regulated data"
- Integrated services use such a key like any other customer managed key; only throughput, availability and latency change. Keys in custom key stores suit low-rate uses (EBS volumes, RDS databases), not high-rate S3, DynamoDB or analytics traffic.
Traps
- Trap: "Control access to CloudHSM keys with IAM policies." → Reality: IAM controls the cluster API; key use is controlled by HSM users inside the cluster.
- Trap: "AWS Support can restore lost CloudHSM credentials." → Reality: AWS has no access to your keys or credentials.
- Trap: "One HSM is enough for production." → Reality: use at least two HSMs in different AZs.
- Trap: "Move keys to a custom key store for higher throughput." → Reality: it lowers throughput (1,800 per second per store) and adds latency.
- Trap: "Create an HMAC or a multi-Region key in a CloudHSM key store." → Reality: custom key stores hold single-Region symmetric encryption keys only.
Practice questions
-
A regulator requires a bank's encryption keys to be generated and used only in single-tenant HSMs that the bank controls, and the bank wants key operations to stay on the AWS network. EBS volumes and RDS databases must still be encrypted through their normal KMS integration. Which solution meets these requirements?
- A. Create the KMS keys in a custom key store backed by an AWS CloudHSM cluster.
- B. Create customer managed KMS keys with imported key material.
- C. Use the AWS managed keys
aws/ebsandaws/rds. - D. Create an external key store that points to an HSM in the bank's data center.
-
A team is evaluating AWS CloudHSM for a payment application. Which TWO statements are correct? (Select TWO.)
- A. CloudHSM includes a free tier for development clusters.
- B. Access to keys stored in CloudHSM is granted with KMS key policies.
- C. Keys in a CloudHSM key store support automatic rotation.
- D. The team creates and manages the HSM users, and AWS can't recover keys if their credentials are lost.
- E. AWS recommends at least two HSMs in different Availability Zones for production workloads.
Answers
- A. A CloudHSM key store keeps the material in single-tenant HSMs inside AWS while EBS and RDS keep calling KMS. B keeps the material in the multi-tenant KMS HSMs; C gives the bank no control at all; D sends every key operation outside AWS through a proxy.
- D, E. Users and credentials belong to the customer, and high availability needs HSMs in two AZs. A is wrong because there is no free tier; B confuses CloudHSM with KMS; C is wrong because custom key stores don't support automatic rotation.
8. SSM Parameter Store
Concept
- A hierarchical key-value store in AWS Systems Manager for configuration and low-risk secrets. Parameters are
String,StringListorSecureString(value encrypted with a KMS key), named with paths such as/orders/prod/db-url, versioned on every change, authorized with IAM per path, and announced to EventBridge when they change. - Two independent settings: the tier (standard or advanced: storage limits, policies, sharing, price) and the throughput (default or higher: request rate).
- Integrations read parameters for you: CloudFormation (§11), CodeBuild (§12), ECS task definitions (
notes/containers-guide.md), the Lambda extension, EC2 launches from public AMI parameters.
Exam facts
| Standard (default) | Advanced | |
|---|---|---|
| Parameters per account and Region | 10,000 | 100,000 |
| Value size | 4 KB | 8 KB |
| Parameter policies | No | Yes (up to 10 per parameter) |
| Share with other accounts (AWS RAM) | No | Yes (read-only for consumers) |
SecureString encryption |
KMS Encrypt/Decrypt directly |
Envelope encryption with the Encryption SDK (GenerateDataKey) |
| Storage price | No charge | $0.05 per parameter-month (prorated hourly) |
| Change tier | Can be upgraded | Can't be downgraded: delete and recreate |
- The account's default tier setting can be Standard, Advanced or Intelligent-Tiering (advanced only when a parameter needs it); changing it never changes existing parameters.
- Throughput: by default
GetParameter,GetParametersandGetParametersByPathshare 40 transactions per second; with higher throughput turned on,GetParametergets 10,000,GetParameters1,000 andGetParametersByPath100, andPutParameterrises from 3 to 10. Higher throughput, like any API call on an advanced parameter, costs $0.05 per 10,000 API interactions (one interaction per parameter returned, so aGetParameterscall that returns 10 parameters counts 10).ThrottlingException: Rate exceededon reads → enable higher throughput or cache.SecureStringreads are also bounded by KMS quotas. SecureString: default keyaws/ssm(no custom policy possible) or a symmetric customer managed key; encryption contextPARAMETER_ARN= the parameter's ARN; writing needskms:Encrypt(standard) orkms:GenerateDataKey(advanced); reading withWithDecryption=trueneedskms:Decrypt; a disabled key or one pending deletion returnsInvalidKeyId.- Sharing: only advanced parameters, through AWS RAM; a shared
SecureStringmust use a customer managed key that you share separately (AWS managed keys can't be shared); consumers read with the full ARN and can't modify the parameter; CloudFormation template parameters, EC2 launch templates and the Lambda extension can use shared parameters, but CloudFormation dynamic references, CodeBuild environment variables and ECS secrets can't. - Names and paths: up to 15 hierarchy levels;
GetParametersByPathwithRecursivereturns at most 10 parameters per page (MaxResults1–10, followNextToken);GetParameterstakes up to 10 names and lists unknown names inInvalidParametersinstead of failing; IAM resource ARNs such asarn:aws:ssm:us-east-1:111122223333:parameter/orders/prod/*scope access, but access to a path also exposes every level below it through recursive reads. - Versions and labels: up to 100 versions retained; select one with
name:versionorname:label; a label (up to 100 characters, up to 10 per version, can't start with a number,awsorssm) is attached to an existing version and can be moved; asking for a version or label that doesn't exist fails rather than falling back to the latest value. - Parameter policies (advanced only):
Expirationdeletes the parameter at a timestamp;ExpirationNotificationsends an EventBridge event a number of days or hours before that;NoChangeNotificationsends one when the value hasn't changed for a period. They are enforced by periodic asynchronous scans, and sending new policies replaces the existing set. - Validation:
AllowedPatternrejects values that don't match withParameterPatternMismatchException. - Public parameters under
/aws/service/…publish AWS data such as the latest AMI IDs. ⚠️ Changed: Amazon Linux 2 reached end of support on 2026-06-30; use the AL2023 parameters, e.g./aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64. Older material may say to use/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2. - Secrets Manager pass-through:
GetParameter/GetParameterson/aws/reference/secretsmanager/<secret-id>(optionally:<version-stage>or:<version-id>) withWithDecryption=truereturn the secret; the caller needs Secrets Manager permissions;GetParametersByPath,DescribeParameters, writes and history don't work on these names. - No built-in rotation: expiring passwords means EventBridge plus a Lambda function you write; AWS points rotation, cross-account and fine-grained audit needs to Secrets Manager.
Developer angle
aws ssm put-parameter --name /orders/prod/db-url --type String --value "jdbc:mysql://orders-db:3306/orders"
aws ssm put-parameter --name /orders/prod/db-password --type SecureString --key-id alias/orders --value 'example-only'
aws ssm get-parameter --name /orders/prod/db-password --with-decryption --query Parameter.Value --output text
aws ssm get-parameters-by-path --path /orders/prod --recursive --with-decryption
aws ssm label-parameter-version --name /orders/prod/db-url --parameter-version 3 --labels live
aws ssm get-parameter --name /orders/prod/db-url:live
aws ssm put-parameter --name /orders/prod/partner-cert --type SecureString --tier Advanced \
--value file://partner-cert.pem --overwrite \
--policies '[{"Type":"Expiration","Version":"1.0","Attributes":{"Timestamp":"2027-01-31T00:00:00.000Z"}},
{"Type":"ExpirationNotification","Version":"1.0","Attributes":{"Before":"14","Unit":"Days"}}]'
aws ssm update-service-setting --setting-value true \
--setting-id arn:aws:ssm:us-east-1:111122223333:servicesetting/ssm/parameter-store/high-throughput-enabled
Least-privilege read policy for a function that loads one path and decrypts it with one key:
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow",
"Action": ["ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
"Resource": ["arn:aws:ssm:us-east-1:111122223333:parameter/orders/prod",
"arn:aws:ssm:us-east-1:111122223333:parameter/orders/prod/*"] },
{ "Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
"Condition": { "StringLike": {
"kms:EncryptionContext:PARAMETER_ARN": "arn:aws:ssm:us-east-1:111122223333:parameter/orders/prod/*" } } }
]
}
- In Lambda, the AWS Parameters and Secrets Lambda Extension caches values:
GET http://localhost:2773/systemsmanager/parameters/get?name=%2Forders%2Fprod%2Fdb-urlwith the headerX-Aws-Parameters-Secrets-Tokenset to the function's session token; the cache TTLSSM_PARAMETER_STORE_TTLdefaults to, and can't exceed, 300 seconds (details:notes/lambda-guide.md).
Traps
- Trap: "Move an advanced parameter back to standard to stop paying." → Reality: impossible; delete it and recreate it as a standard parameter.
- Trap: "Add an
Expirationpolicy to a standard parameter." → Reality: policies need the advanced tier. - Trap: "Higher throughput is free because standard parameters are free." → Reality: API interactions are charged once it is on.
- Trap: "Read a whole folder of secrets with
GetParametersByPath /aws/reference/secretsmanager." → Reality: references work only withGetParameterandGetParameters. - Trap: "Attach a label when creating the parameter." → Reality: labels go on existing versions.
- Trap: "Share an
aws/ssm-encryptedSecureStringwith another account." → Reality: shared parameters must be advanced and encrypted with a customer managed key that you share too.
Practice questions
-
At deployment, 400 containers start at the same time and each reads 20 parameters with
GetParameter. Many reads fail withThrottlingException: Rate exceeded. Which change fixes the problem with the fewest code changes?- A. Change every parameter to the advanced tier and redeploy the containers.
- B. Move the parameters to Secrets Manager and read them with
BatchGetSecretValue. - C. Turn on the Parameter Store higher throughput setting in the Region.
- D. Rename the parameters into a deeper hierarchy.
-
A team must store a 6 KB partner certificate as an encrypted parameter and receive an EventBridge notification 14 days before the parameter is deleted at its expiry date. Which TWO actions meet the requirements? (Select TWO.)
- A. Create the parameter as an advanced-tier
SecureString. - B. Create the parameter as a standard-tier
SecureStringand attach the policies. - C. Use a
Stringparameter with anAllowedPatternthat matches certificates. - D. Attach a
NoChangeNotificationpolicy withAfterset to 14 days. - E. Attach an
Expirationpolicy and anExpirationNotificationpolicy withBeforeset to 14 days.
- A. Create the parameter as an advanced-tier
Answers
- C. Higher throughput raises
GetParameterfrom the shared 40 per second to 10,000 per second with no code change. A changes storage limits, not the request rate; B requires code changes and still needs caching; D has no effect on throughput. - A, E. 6 KB needs the advanced tier, which also enables parameter policies, and the two policies delete the parameter and warn 14 days ahead. B fails both the 4 KB limit and the policy requirement; C isn't encrypted; D fires when the value hasn't changed for a period, not before expiry.
9. Secrets Manager basics and Parameter Store comparison
Concept
- AWS Secrets Manager is the purpose-built store for credentials and API keys: every secret value is encrypted with KMS (envelope encryption with a fresh data key for each new value), versions are tracked with staging labels, rotation is built in (§10), access is controlled by IAM plus an optional resource policy, and secrets can be replicated to other Regions (§10).
- Applications fetch secrets at runtime with
GetSecretValueinstead of keeping them in code, images or environment variables, and cache them, because every call costs money and latency.
Exam facts
- What's stored:
SecretStringorSecretBinary, up to 65,536 bytes; JSON key-value pairs are the norm (a database secret holdsengine,host,port,username,password,dbname, the structure rotation expects). The secret's name, description, tags, rotation settings and KMS key ARN are not encrypted. - ARN:
arn:aws:secretsmanager:<region>:<account>:secret:<name>-<6 random characters>; a secret recreated with the same name gets a new ARN, so permissions written for the old ARN don't carry over. - Versions: staging labels
AWSCURRENT(returned by default),AWSPREVIOUSandAWSPENDING(during rotation), plus your own (up to 20 labels across a secret's versions); unlabeled versions are removed beyond 100 (never those younger than 24 hours), so don't change a value more often than about every 10 minutes. - Encryption:
CreateSecret/PutSecretValue/UpdateSecretcallGenerateDataKey,GetSecretValuecallsDecrypt; the key isaws/secretsmanageror a symmetric customer managed key; the encryption context isSecretARN+SecretVersionId; changing the key re-encrypts theAWSCURRENT,AWSPENDINGandAWSPREVIOUSversions (if you can still decrypt with the old key). - Cross-account access: a resource policy on the secret (
PutResourcePolicy, with--block-public-policyto refuse public grants) + a customer managed key whose key policy lets the other accountkms:Decrypt(theaws/secretsmanagerkey can't be used across accounts) + an IAM policy in the caller's account; callers use the full secret ARN, and their requests count against the caller's account quotas. - Deletion:
DeleteSecretwith a recovery window of 7–30 days (default 30) orForceDeleteWithoutRecovery; reading a secret scheduled for deletion fails withInvalidRequestExceptionuntilRestoreSecret; a primary secret with replicas can't be deleted until its replicas are removed. - Quotas and price: 500,000 secrets per Region;
GetSecretValue10,000 requests per second,DescribeSecret40,000,BatchGetSecretValue100 per second with up to 20 secrets per call; $0.40 per secret-month plus $0.05 per 10,000 API calls. - Retrieval options: SDK
GetSecretValue(VersionStageorVersionId) orBatchGetSecretValue(needssecretsmanager:BatchGetSecretValueplusGetSecretValueon each secret,ListSecretswhen filtering, andkms:Decryptfor customer managed keys); client-side caching libraries for Java, Python, .NET, Go and Rust (the Python cache refreshes every hour by default); the AWS Parameters and Secrets Lambda Extension (/secretsmanager/get?secretId=…on port 2773,SECRETS_MANAGER_TTLdefault 300 seconds); the AWS Workload Credentials Provider (formerly the Secrets Manager Agent) on EC2, ECS, EKS or Lambda, a local HTTP cache on port 2773 that requires an SSRF token header and refreshes after 300 seconds by default (ttl_seconds0–3,600;refreshNow=trueforces a fetch).
| Need | Parameter Store | Secrets Manager |
|---|---|---|
| Plain configuration, feature values, AMI IDs | Best fit (standard tier is free) | Overkill |
| Encryption | Optional (SecureString) |
Always (KMS) |
| Automatic rotation | No (build it with EventBridge + Lambda) | Built in (managed or Lambda) |
| Database credential integration | No | RDS, Aurora, Redshift, DocumentDB |
| Cross-account access | Advanced parameters through AWS RAM | Resource policy + customer managed key |
| Multi-Region | No replication | Replica secrets |
| Maximum value | 4 KB / 8 KB | 64 KB |
| Price | Free (standard) or $0.05 per advanced parameter-month | $0.40 per secret-month + API calls |
| Reading one through the other | /aws/reference/secretsmanager/<id> returns a secret |
— |
Developer angle
aws secretsmanager create-secret --name prod/orders/db --kms-key-id alias/orders \
--secret-string '{"engine":"mysql","host":"orders-db.cluster-abc.us-east-1.rds.amazonaws.com","port":3306,"username":"app","password":"example-only"}'
aws secretsmanager get-secret-value --secret-id prod/orders/db --query SecretString --output text
aws secretsmanager describe-secret --secret-id prod/orders/db --query VersionIdsToStages
aws secretsmanager put-resource-policy --secret-id prod/orders/db --block-public-policy \
--resource-policy file://allow-reporting-account.json
aws secretsmanager delete-secret --secret-id prod/orders/db --recovery-window-in-days 7
aws secretsmanager restore-secret --secret-id prod/orders/db
import json
import botocore.session
from aws_secretsmanager_caching import SecretCache, SecretCacheConfig
client = botocore.session.get_session().create_client("secretsmanager")
cache = SecretCache(config=SecretCacheConfig(), client=client) # refreshes secrets hourly by default
def handler(event, context):
creds = json.loads(cache.get_secret_string("prod/orders/db"))
# open the database connection with creds["host"], creds["username"] and creds["password"]
return {"db_host": creds["host"]}
- The cache needs
secretsmanager:GetSecretValueandsecretsmanager:DescribeSecret(pluskms:Decryptfor a customer managed key).
Traps
- Trap: "Share a secret with another account while it uses
aws/secretsmanager." → Reality: cross-account access requires a customer managed key. - Trap: "Secret names and tags are encrypted too." → Reality: only the secret value is.
- Trap: "Delete a secret and immediately recreate it with the same name." → Reality: during the recovery window the old secret still exists (bring it back with
RestoreSecret); evenForceDeleteWithoutRecoverydeletes asynchronously, so retry the create with backoff, and the new secret gets a new ARN. - Trap: "Parameter Store rotates database passwords automatically." → Reality: only Secrets Manager has built-in rotation.
- Trap: "Call
GetSecretValueon every request." → Reality: cache; every call costs money and latency, and the per-Region rate is shared. - Trap: "Copy the secret into a Lambda environment variable at deploy time." → Reality: anyone who can read the function configuration sees it, and it goes stale after rotation; fetch it at runtime.
Practice questions
-
A Lambda function calls
GetSecretValueon every invocation to fetch database credentials, and the API cost and latency are now significant. The team wants the smallest change that keeps the credentials out of the function configuration. What should the developer do?- A. Copy the secret into an encrypted Lambda environment variable as part of each deployment.
- B. Add the AWS Parameters and Secrets Lambda Extension and read the secret from localhost.
- C. Move the credentials to a standard-tier
Stringparameter in Parameter Store. - D. Increase the function's memory setting to 1,024 MB.
-
An application in account B must read a secret stored in account A. The application role in account B already has an IAM policy that allows
secretsmanager:GetSecretValueandkms:Decrypton the correct ARNs. Which TWO additional steps are required? (Select TWO.)- A. Replicate the secret into account B with
ReplicateSecretToRegions. - B. Add a
kms:ViaServicecondition for Secrets Manager to the role's IAM policy. - C. Attach a resource policy to the secret that allows the account B role to call
GetSecretValue. - D. Encrypt the secret with a customer managed key whose key policy allows the account B role to use
kms:Decrypt. - E. Keep the
aws/secretsmanagerkey and create a KMS grant for the account B role.
- A. Replicate the secret into account B with
Answers
- B. The extension caches the secret locally (TTL up to 300 seconds), needing only a layer and a localhost call. A exposes the value in the configuration and goes stale after rotation; C stores a credential unencrypted; D may shorten each call but keeps paying for one call per invocation.
- C, D. Cross-account access needs the secret's resource policy and a customer managed key the other account may use. A replicates across Regions, not accounts; B narrows the role's permissions instead of adding the missing ones; E fails because AWS managed keys can't be used by other accounts.
10. Secrets Manager rotation and replication
Concept
- Rotation replaces a credential in both places at once: in the secret and in the system that checks it (a database, an API). Secrets Manager runs it on a schedule inside a rotation window.
- Managed rotation: for secrets owned by another service (RDS, Aurora, Redshift and DocumentDB master credentials, ECS Service Connect TLS certificates, and partner-held "managed external secrets") the service rotates them with no Lambda function.
- Rotation by Lambda function: for everything else; Secrets Manager invokes your function (AWS templates exist for RDS, Aurora, Redshift and DocumentDB, plus a generic one).
- Replication keeps read-only replica secrets in other Regions in sync with a primary secret.
Exam facts
- Schedules:
rate(N hours|days)orcron(0 …)expressions, evaluated in UTC; rotation as often as every 4 hours; a window (Duration, e.g.3h) of at least 1 hour, by default 1 hour for hourly schedules and the rest of the UTC day for daily ones, never overlapping the next window;rate()up to 999 days; a cron expression needs0in the minutes field and*in the year field. The older days-only fieldAutomaticallyAfterDays(1–1,000) still exists but can't be combined withScheduleExpression. - ⚠️ Changed: rotation can run as often as every four hours with
rate()orcron()schedules and a rotation window of at least one hour. Older material may say rotation is configured only in days, at most once a day. - ⚠️ Changed: managed rotation needs no Lambda function for RDS, Aurora, Redshift and DocumentDB master credentials, ECS Service Connect TLS certificates and managed external secrets; only other secrets use a rotation function. Older material may say every rotation runs a Lambda function.
RotateSecretstarts a rotation at once by default (RotateImmediately=true); withfalseit only runs thetestSecretstep to validate the setup. A manualPutSecretValuecounts as a rotation when the next date is calculated.- The four Lambda steps (the
Stepfield of the event; the Python templates handle them in methodscreate_secret…finish_secret):createSecret(new value fromGetRandomPassword, stored asAWSPENDING),setSecret(change the credential in the database or service),testSecret(log in with theAWSPENDINGvalue),finishSecret(moveAWSCURRENTto the new version; the old version becomesAWSPREVIOUS). If a step fails, Secrets Manager retries the whole rotation during the window; a version still labeledAWSPENDINGmakes the nextRotateSecretfail until it is cleared. - Strategies: single user: one user's password changes; simplest; open connections survive, but new logins can fail briefly between the database change and the label move, so retry. Alternating users: the first rotation clones the user (
<user>_clone) and later rotations alternate between the two, so a valid credential always exists; needs a separate superuser secret; not supported by RDS Proxy. - Permissions: the function's execution role needs
secretsmanager:DescribeSecret,GetSecretValue,PutSecretValueandUpdateSecretVersionStageon the secret,secretsmanager:GetRandomPassword, network-interface permissions when it runs in a VPC, andkms:Decrypt/kms:GenerateDataKeyon a customer managed key (scope it withkms:EncryptionContext:SecretARN); the function's resource policy must letsecretsmanager.amazonaws.cominvoke it (lambda add-permission); whoever callsRotateSecretneedssecretsmanager:RotateSecretandlambda:InvokeFunctionon the function. - Network: the function must reach the database (security groups) and a Secrets Manager endpoint, through an interface VPC endpoint (recommended) or a NAT gateway; without either, rotation times out.
- Managed rotation for RDS:
ManageMasterUserPasswordmakes RDS generate the master password, keep it in a secret namedrds!…, rotate it every 7 days by default (schedule adjustable) and delete it with the instance; the KMS key can't be changed afterwards; it isn't supported when creating read replicas (except RDS for SQL Server and Db2), with Blue/Green Deployments, RDS Custom or Oracle Data Guard switchover. Template patterns: §11. - Replication:
ReplicateSecretToRegionsorcreate-secret --add-replica-regions; the replica ARN differs only in the Region; each replica is encrypted with a KMS key in its own Region (defaultaws/secretsmanager); rotation runs in the primary Region and the new value propagates to the replicas;StopReplicationToReplicapromotes a replica to a standalone secret;RemoveRegionsFromReplicationdeletes replicas immediately. Replica secrets are billed (rate ⚠️ unverified). - Use cases for replicas: multi-Region applications, disaster recovery, databases with cross-Region read replicas (add Regional connection keys to the JSON when the endpoints differ).
Developer angle
# Let Secrets Manager invoke the rotation function, then rotate every 6 hours within a 2-hour window
aws lambda add-permission --function-name orders-db-rotator --statement-id SecretsManager \
--action lambda:InvokeFunction --principal secretsmanager.amazonaws.com --source-account 111122223333
aws secretsmanager rotate-secret --secret-id prod/orders/db \
--rotation-lambda-arn arn:aws:lambda:us-east-1:111122223333:function:orders-db-rotator \
--rotation-rules '{"ScheduleExpression": "rate(6 hours)", "Duration": "2h"}'
# When is the next rotation, and which version holds which label?
aws secretsmanager describe-secret --secret-id prod/orders/db \
--query '{Next: NextRotationDate, Stages: VersionIdsToStages}'
# Replicate to a DR Region with a key that lives there; promote the replica if the primary Region fails
aws secretsmanager replicate-secret-to-regions --secret-id prod/orders/db \
--add-replica-regions Region=eu-west-1,KmsKeyId=alias/orders-dr
aws secretsmanager stop-replication-to-replica --secret-id prod/orders/db --region eu-west-1
Traps
- Trap: "Rotate every hour." → Reality: the shortest interval is four hours.
- Trap: "The rotation function runs in private subnets with no endpoint or NAT." → Reality: it can't reach Secrets Manager and times out.
- Trap: "
finish_secretdeletes the old password version." → Reality: it becomesAWSPREVIOUSand stays available. - Trap: "Configure rotation separately on each replica secret." → Reality: rotation runs on the primary and the value propagates.
- Trap: "Single-user rotation drops every open database connection." → Reality: open connections stay; only new logins in a short window can fail.
- Trap: "Write a custom rotation function for RDS master passwords." → Reality:
ManageMasterUserPasswordgives managed rotation with no function to maintain.
Practice questions
-
A rotation function for an Aurora secret runs in private subnets that have no NAT gateway. The function's role and the database security groups are correct, but every rotation attempt times out in the
create_secretstep. Which change fixes the problem MOST securely?- A. Increase the function's timeout and memory.
- B. Move the function to public subnets that route through the VPC's internet gateway.
- C. Switch the secret to the alternating users strategy.
- D. Create an interface VPC endpoint for Secrets Manager in the function's VPC.
-
A high-availability service must rotate its database credentials every 6 hours, and no login may fail during a rotation. Which TWO configuration choices meet these requirements? (Select TWO.)
- A. Set
AutomaticallyAfterDaysto 0.25. - B. Use the alternating users rotation strategy with a separate superuser secret.
- C. Use the single user rotation strategy.
- D. Set the rotation schedule to
rate(6 hours). - E. Set the rotation schedule to
rate(2 hours).
- A. Set
Answers
- D. A private endpoint gives the function a path to Secrets Manager without opening the VPC to the internet. A can't fix missing network access; B doesn't work because Lambda network interfaces never get public IP addresses, and it would expose the function's network; C changes which user rotates, not connectivity.
- B, D. Alternating users always leaves one valid credential, and
rate(6 hours)sets the interval. A is invalid because the days field takes whole days (minimum 1); C allows brief login failures; E is below the four-hour minimum.
11. CloudFormation dynamic references and RDS managed secrets
Concept
- A dynamic reference is a placeholder string,
{{resolve:service:reference}}, that CloudFormation resolves when it creates or updates the resource, so templates, parameters and stack history never contain the secret. - Three services:
ssm(plaintextString/StringListparameters),ssm-secure(SecureStringparameters),secretsmanager(whole secrets or one JSON key). - Two ways to give an RDS database a master password no human ever sees: let RDS manage it in Secrets Manager (
ManageMasterUserPassword), or generate a secret in the template, reference it from the database, and attach the two for rotation.
Exam facts
| Type | Syntax | Notes |
|---|---|---|
ssm |
{{resolve:ssm:/path/name}} or {{resolve:ssm:/path/name:4}} |
Version optional (latest at each create or update); public parameters work (e.g. the AL2023 AMI); a version is mandatory in the Parameters section; no labels; no parameters shared from other accounts |
ssm-secure |
{{resolve:ssm-secure:/path/name}} or {{resolve:ssm-secure:/path/name:3}} |
Only in supported properties (e.g. AWS::RDS::DBInstance/DBCluster MasterUserPassword, AWS::IAM::User LoginProfile Password, AWS::Redshift::Cluster MasterUserPassword, AWS::ElastiCache::ReplicationGroup AuthToken, Directory Service passwords); never stored or returned by CloudFormation; no other accounts, labels or public parameters; not in the China Regions |
secretsmanager |
{{resolve:secretsmanager:secret-id:SecretString:json-key:version-stage:version-id}} |
Any resource property, not just a supported list as for ssm-secure, but the general limits below still apply (not in custom resources, EC2 UserData or AWS::CloudFormation::Init); secret-id is the name (same account) or the full ARN (required for another account); SecretString is the only and default type; omit json-key for the whole string; omit stage and ID for AWSCURRENT |
- ⚠️ Changed: the version segment of
ssmandssm-securereferences is optional; without it CloudFormation uses the latest version at each create or update. Older material may say the version is a required part of the syntax. - General limits: up to 60 dynamic references per template; secure references (
ssm-secure,secretsmanager) aren't supported in custom resources (plainssmreferences are resolved before the custom resource receives them); dynamic references aren't supported inAWS::CloudFormation::Initmetadata or EC2UserData; a reference must not end with a backslash; transforms (AWS::Serverless,AWS::Include) receive the literal string and the value is resolved when the change set runs; keep references out of properties that form a resource's primary identifier. - Staleness: changing the parameter or the secret doesn't update stacks; CloudFormation reads the value only when the resource that contains the reference is created or updated, so plan a stack update (there is no drift detection for dynamic references). Prefer versionless references combined with rotation.
- If a rollback needs a
SecureStringversion that no longer exists, the rollback fails (continue it withCONTINUE_UPDATE_ROLLBACK). - Permissions: the stack's service role (or the caller) needs
ssm:GetParametersorsecretsmanager:GetSecretValue, pluskms:Decryptfor customer managed keys. - Pattern 1, RDS manages the password:
ManageMasterUserPassword: true(optionallyMasterUserSecretwithKmsKeyId); RDS creates therds!secret and rotates it every 7 days by default (§10); enforce the pattern with the IAM condition keyrds:ManageMasterUserPassword. - Pattern 2, the template owns the secret:
AWS::SecretsManager::SecretwithGenerateSecretString(SecretStringTemplate+GenerateStringKey,PasswordLengthdefault 32,ExcludeCharacters),secretsmanagerreferences forMasterUsernameandMasterUserPassword,AWS::SecretsManager::SecretTargetAttachment(adds host, port and engine to the secret, required before database rotation; one target per secret), thenAWS::SecretsManager::RotationScheduleto turn rotation on. WithHostedRotationLambda(RotationTypesuch asMySQLSingleUser), CloudFormation creates the rotation function from an AWS rotation template, so there is no rotation code to write; it needsTransform: AWS::SecretsManager-2024-09-16at the top of the template. - Template mechanics such as
NoEcho,Ref/!Suband stack policies:notes/cloudformation-guide.md.
Developer angle
Resources:
DbSecret:
Type: AWS::SecretsManager::Secret
Properties:
Description: Master credentials for the orders database
KmsKeyId: alias/orders
GenerateSecretString:
SecretStringTemplate: '{"username": "orders_admin"}'
GenerateStringKey: password
PasswordLength: 32
ExcludeCharacters: '"@/\'
OrdersDb:
Type: AWS::RDS::DBInstance
Properties:
Engine: mysql
DBInstanceClass: db.t3.micro
AllocatedStorage: '20'
MasterUsername: !Sub '{{resolve:secretsmanager:${DbSecret}:SecretString:username}}'
MasterUserPassword: !Sub '{{resolve:secretsmanager:${DbSecret}:SecretString:password}}'
DbSecretAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref DbSecret
TargetId: !Ref OrdersDb
TargetType: AWS::RDS::DBInstance
ReportsDb:
Type: AWS::RDS::DBInstance
Properties:
Engine: postgres
DBInstanceClass: db.t3.micro
AllocatedStorage: '20'
MasterUsername: reports_admin
ManageMasterUserPassword: true
OnboardingUser:
Type: AWS::IAM::User
Properties:
LoginProfile:
Password: '{{resolve:ssm-secure:/iam/onboarding/initial-password:3}}'
PasswordResetRequired: true
WebServer:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.micro
ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}'
- A failed resolution shows up as a stack event on the resource (for example a missing parameter version or a denied
GetSecretValue); check the stack role's permissions and the exact name or ARN first.
Traps
- Trap: "Updating the secret value makes CloudFormation push the new password to the database." → Reality: CloudFormation resolves references only while creating or updating that resource; let rotation change the database, or run a stack update.
- Trap: "Use an
ssm-securereference in EC2UserData." → Reality: dynamic references aren't supported inUserData. - Trap: "Reference a secret in another account by its name." → Reality: use the full secret ARN.
- Trap: "A
NoEchoparameter is as good as a dynamic reference." → Reality:NoEchoonly masks the value in stack output; whoever deploys must still know and pass the secret, while a dynamic reference fetches it from the store. - Trap: "Every
ssmreference needs a version number." → Reality: it is optional except in theParameterssection. - Trap: "Pass secrets to a custom resource Lambda function through dynamic references." → Reality: secure references aren't supported in custom resources.
Practice questions
-
A template must set an RDS master password to the
passwordkey of the Secrets Manager secretprod/orders/db, which is in the same account, without the value ever appearing in the template or in stack parameters. Which property value should the developer use?- A.
'{{resolve:secretsmanager:prod/orders/db:SecretString:password}}' - B.
'{{resolve:ssm-secure:prod/orders/db:password}}' - C. A
NoEchostack parameter that holds the password as its default value in the template - D.
!GetAtt DbSecret.SecretString
- A.
-
A team wants RDS for PostgreSQL master credentials that no person ever sees, that rotate automatically with no rotation code to maintain, and that are encrypted with the team's own KMS key. Which TWO template settings meet these requirements? (Select TWO.)
- A. Set
ManageMasterUserPasswordtotrueon theAWS::RDS::DBInstanceresource. - B. Add a custom rotation Lambda function and an
AWS::SecretsManager::RotationSchedulefor the master password. - C. Set
MasterUserSecretwithKmsKeyIdpointing to the team's customer managed key. - D. Reference an
ssm-secureparameter forMasterUserPasswordand rotate it with an EventBridge rule. - E. Pass the password as a
NoEchoparameter at each deployment.
- A. Set
Answers
- A. The
secretsmanagerreference withSecretString:passwordresolves the JSON key at deploy time and nothing is stored in the stack. B pointsssm-secureat a Secrets Manager name with an invalid version segment; C still makes someone supply the password and stores it in the template; D isn't an attribute that returns a secret value. - A, C.
ManageMasterUserPasswordgives RDS-managed generation and rotation, andMasterUserSecret.KmsKeyIdpicks the customer managed key. B adds code to maintain; D needs custom rotation and a human-chosen first value; E exposes the password to whoever deploys.
12. CloudWatch Logs encryption and CodeBuild secrets
Concept
- CloudWatch Logs always encrypts log data at rest with a service-managed AES-256-GCM key. Associating a KMS key with a log group adds customer control: a key policy, CloudTrail records of each use, and the ability to cut off access by disabling the key.
- CodeBuild builds often need passwords and tokens. Plaintext environment variables are visible in the console and the CLI, so builds should pull secrets from Parameter Store or Secrets Manager at build time and, when they need private resources, run inside your VPC.
Exam facts
- Associating a key: at creation with
create-log-group --kms-key-id <key ARN>, later withassociate-kms-key, removed withdisassociate-kms-key; a change takes up to 5 minutes to apply; symmetric keys only; AWS documents that a key can't be associated with an existing log group from the CloudWatch console (use the API or CLI). Whether the console's create form accepts a key is ⚠️ unverified, so plan on the CLI or API. - The key policy must allow the service principal
logs.<region>.amazonaws.com(kms:Encrypt,kms:Decrypt,kms:ReEncrypt*,kms:GenerateDataKey*,kms:Describe*), ideally limited withkms:EncryptionContext:aws:logs:arnset to the log group ARN; the principal that callsCreateLogGroupwith a key orAssociateKmsKeyneedskms:DescribeKey, or the call fails withAccessDeniedException; principals that read or write the encrypted group may also need KMS permissions, granted throughkms:ViaService=logs.<region>.amazonaws.com. - Only data ingested after the association is encrypted with the key; after disassociation, older data stays encrypted with the key and is readable while the key works; if the key is disabled or deleted, or CloudWatch Logs loses access to it, that data can't be retrieved.
- CodeBuild environment variable types:
PLAINTEXT(default; visible in the console and the CLI, so never for secrets),PARAMETER_STORE(value = parameter name),SECRETS_MANAGER(value = secret reference). In the buildspec:env/parameter-store(the service role needsssm:GetParameters, pluskms:Decryptfor a customer managed key) andenv/secrets-managerwithsecret-id:json-key:version-stage:version-id(the service role needssecretsmanager:GetSecretValue; full ARN for another account;AWSCURRENTby default). - CodeBuild masks values that come from Parameter Store and Secrets Manager (and AWS access key IDs) in build logs, but only exact matches: a base64-encoded, reversed or concatenated secret is printed as is.
- Precedence when a name is defined twice:
start-buildoverride > build project > buildspec. Names starting withCODEBUILD_are reserved.exported-variablescan't export Parameter Store or Secrets Manager values, nor names starting withAWS_. - VPC access (VPC ID, private subnets, security groups) lets builds reach RDS, ElastiCache or internal services; internet access then needs a NAT gateway or NAT instance, because an internet gateway doesn't work for the network interfaces CodeBuild creates; shared VPCs aren't supported. Buildspec phases, artifacts and caching:
notes/cicd-guide.md.
Developer angle
KEY_ARN=arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
aws logs create-log-group --log-group-name /orders/api --kms-key-id "$KEY_ARN"
aws logs associate-kms-key --log-group-name /orders/worker --kms-key-id "$KEY_ARN"
aws logs describe-log-groups --log-group-name-prefix /orders --query 'logGroups[].[logGroupName,kmsKeyId]'
aws logs disassociate-kms-key --log-group-name /orders/worker
Key policy statement for CloudWatch Logs, limited to one log group:
{
"Sid": "AllowLogsForOrdersApi",
"Effect": "Allow",
"Principal": { "Service": "logs.us-east-1.amazonaws.com" },
"Action": ["kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:Describe*"],
"Resource": "*",
"Condition": { "ArnEquals": {
"kms:EncryptionContext:aws:logs:arn": "arn:aws:logs:us-east-1:111122223333:log-group:/orders/api" } }
}
Buildspec that pulls its secrets at build time:
version: 0.2
env:
variables:
APP_ENV: staging
parameter-store:
DB_URL: /orders/staging/db-url
secrets-manager:
DB_PASSWORD: staging/orders/db:password
NPM_TOKEN: shared/npm-token:token
phases:
build:
commands:
- ./scripts/run-integration-tests.sh
Traps
- Trap: "Associate the key with the existing log group in the console." → Reality: use
associate-kms-key. - Trap: "Associating a key re-encrypts the old log events." → Reality: only newly ingested data uses the key.
- Trap: "After disassociation the key can be deleted safely." → Reality: older events still need it.
- Trap: "A
PLAINTEXTvariable namedDB_PASSWORDis hidden." → Reality: plaintext variables are readable in the console and the CLI. - Trap: "Base64-encoding a secret in a build command keeps it masked." → Reality: masking matches only the exact stored value.
- Trap: "Give the CodeBuild subnets a route to an internet gateway." → Reality: builds in a VPC need a NAT gateway or NAT instance for internet access.
Practice questions
-
A developer runs
aws logs associate-kms-keyfor an existing log group and receivesAccessDeniedException. The customer managed key's policy contains only the default statement for the account. What should the developer do?- A. Replace the key with an asymmetric RSA key.
- B. Associate the key from the CloudWatch console instead of the CLI, in the log group's settings.
- C. Allow
logs.<region>.amazonaws.comin the key policy; give the callerkms:DescribeKey. - D. Enable automatic rotation on the key and run
associate-kms-keyagain.
-
A buildspec needs a database password from Secrets Manager and an internal API URL from Parameter Store. Neither value may appear in the project configuration, and the password must not appear in build logs. Which TWO steps meet the requirements? (Select TWO.)
- A. Define both values under
env/variablesas plaintext. - B. List the password under
exported-variablesfor the later pipeline stages. - C. Map the password with
env/secrets-managerand allowsecretsmanager:GetSecretValuefor the service role. - D. Print the base64-encoded password in a build command to confirm that it was retrieved.
- E. Map the URL with
env/parameter-storeand allowssm:GetParametersfor the service role.
- A. Define both values under
Answers
- C. CloudWatch Logs needs key policy access for its service principal (scoped by the log group's encryption context), and the caller needs
kms:DescribeKey. A isn't supported because log groups take symmetric keys only; B can't associate a key with an existing group; D grants no permission. - C, E. Both mappings fetch the values at build time with the right service-role permissions, and CodeBuild masks the exact secret value. A exposes the values; B isn't allowed for Secrets Manager values; D prints a transformed value that masking won't catch.
13. Nitro Enclaves and attested KMS requests
Concept
- AWS Nitro Enclaves set aside some of a Nitro-based EC2 instance's vCPUs and memory and boot an isolated, stripped-down VM on them. Nothing in it survives on disk, nobody can log in to it (no SSH), and it has no route to any network: its only link is a local vsock channel to the parent instance, and even root on the parent can't read the enclave's memory.
- Cryptographic attestation proves which code runs inside: the Nitro Hypervisor signs an attestation document with the enclave's measurements (PCRs) and a public key. KMS checks the document, and the key policy can require specific measurements before KMS releases data, encrypted to the enclave's public key so that only the enclave can read it.
- Typical uses: payment card data, personal or health records, private keys, multi-party computation.
Exam facts
- Setup: launch the parent with enclaves enabled (
--enclave-options 'Enabled=true', theEnclaveOptionsparameter); most Intel, AMD and Graviton Nitro instance types qualify (the smallest sizes and many metal sizes don't); the parent runs Linux or Windows (2016 or later), the enclave runs Linux; up to four enclaves per parent; enclaves can't talk to each other; stopping or terminating the parent terminates its enclaves; no hibernation on the same instance; not on Outposts, Local Zones or Wavelength Zones; no charge beyond the instance. - Build and run with the Nitro CLI: package the application as a Docker image →
nitro-cli build-enclaveproduces an enclave image file (.eif) and prints its measurements →nitro-cli run-enclavewith--cpu-count,--memoryand--eif-path→describe-enclaves,console(debug only),terminate-enclave. - Measurements: PCR0 enclave image file, PCR1 kernel and boot ramdisk, PCR2 application, PCR3 IAM role of the parent instance, PCR4 parent instance ID, PCR8 signing certificate of a signed
.eif. --debug-mode(and--attach-console) produce attestation documents whose PCRs are all zeros, so attestation-based key policies deny those requests: debug and production runs are distinguishable by design.- KMS support:
Decrypt,GenerateDataKey,GenerateDataKeyPair,GenerateRandomandDeriveSharedSecretaccept aRecipientparameter (attestation document +RSAES_OAEP_SHA_256); the response carriesCiphertextForRecipient(encrypted to the enclave's key) and an emptyPlaintext. Call them through the Nitro Enclaves SDK (kms-decrypt,kms-generate-data-key,kms-generate-random) or any AWS SDK; NitroTPM attestation is supported too. - Key policy condition keys:
kms:RecipientAttestation:ImageSha384andkms:RecipientAttestation:PCR<n>. A request without an attestation document can't satisfy them, so the parent instance's role on its own can't decrypt. - Typical flow: encrypt the sensitive data outside with a data key (§4) and store the ciphertext (for example in S3); the parent fetches it and passes it over vsock; the enclave asks KMS, through a proxy on the parent, to decrypt the data key with its attestation document, then processes the plaintext inside the enclave.
Developer angle
aws ec2 run-instances --image-id ami-0abcdef1234567890 --instance-type m5.xlarge \
--enclave-options 'Enabled=true' --iam-instance-profile Name=CardProcessorProfile
nitro-cli build-enclave --docker-uri card-processor:latest --output-file card-processor.eif
nitro-cli run-enclave --cpu-count 2 --memory 2048 --eif-path card-processor.eif
nitro-cli describe-enclaves
Key policy statement: the parent's role may decrypt only on behalf of an enclave built from one approved image:
{
"Sid": "DecryptOnlyInsideTheCardEnclave",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/CardProcessorRole" },
"Action": ["kms:Decrypt", "kms:GenerateDataKey"],
"Resource": "*",
"Condition": { "StringEqualsIgnoreCase": {
"kms:RecipientAttestation:ImageSha384": "<image measurement printed by nitro-cli build-enclave>" } }
}
Traps
- Trap: "An enclave is a hardened container on the instance." → Reality: it is an isolated VM that the Nitro Hypervisor runs on vCPUs and memory taken from the parent.
- Trap: "SSH into the enclave to debug it." → Reality: there is no interactive access; debug mode gives a read-only console and zeroed PCRs.
- Trap: "Give the parent's role
kms:Decryptand the data is protected by the enclave." → Reality: without an attestation condition the parent, or anything using its role, can decrypt directly. - Trap: "The enclave calls KMS over its own network interface." → Reality: it has no external networking; traffic goes through the parent over vsock.
- Trap: "Run several enclaves that share data in memory." → Reality: up to four per parent, fully isolated from one another.
Practice questions
-
A payment company processes card numbers inside a Nitro enclave. Only an enclave started from one approved image may decrypt the card data key; code on the parent instance, which uses the same IAM role, must get a denial. What should the developer add to the KMS key policy?
- A. A
kms:RecipientAttestation:ImageSha384condition that matches the approved image. - B. A condition on
kms:ViaServiceset toec2.us-east-1.amazonaws.com. - C. A condition on
aws:SourceVpcthat matches the instance's VPC. - D. A grant for the parent instance role that is limited by an
EncryptionContextEqualsconstraint.
- A. A
-
A developer starts an enclave with
nitro-cli run-enclave --debug-modeto troubleshoot it. EveryDecryptrequest with an attestation document is now denied by a key policy that checks PCR0, although the same image works in production. Which TWO statements are correct? (Select TWO.)- A. The key policy also needs a
kms:ViaServicecondition for Amazon EC2. - B. Enclaves started in debug mode produce attestation documents whose PCRs are all zeros.
- C. The parent needs a larger instance type for attestation to work.
- D. The instance must require IMDSv2 before KMS accepts attestation documents.
- E. Starting the enclave without
--debug-modelets the attestation condition match again.
- A. The key policy also needs a
Answers
- A. An attestation condition is satisfied only by requests that carry a signed document from an enclave built from that image. B concerns requests that a service makes and doesn't identify the enclave; C would also match the parent instance; D can't tell the enclave and the parent apart.
- B, E. Debug-mode enclaves are deliberately non-attestable (zeroed PCRs), so a production-mode launch matches again. A, C and D have nothing to do with the attestation measurements.
Quick decision rules
Choosing the mechanism (§1, §3, §4, §7)
- If you see client-side encryption of data larger than 4 KB, think
GenerateDataKey(envelope encryption) or the Encryption SDK, never chunkedEncryptcalls. - If you see a small value (up to 4 KB) to encrypt with KMS directly, think
Encrypt/Decryptwith an encryption context. - If you see partners without AWS access who must encrypt data only you can read, think an asymmetric RSA
ENCRYPT_DECRYPTkey andGetPublicKey. - If you see "move ciphertext to another key without exposing the plaintext", think
ReEncryptwithkms:ReEncryptFrom+kms:ReEncryptTo. - If you see a regulation that requires single-tenant HSMs you control while AWS services still integrate, think a KMS custom key store backed by CloudHSM.
- If you see "control and audit the key" for a service's data, think customer managed key (AWS owned keys are invisible, AWS managed keys can't be edited or shared).
Permissions and errors (§2, §3)
- If you see an IAM policy that allows
kms:Decryptwhile calls are still denied, think the key policy doesn't delegate to the account or name the principal. - If you see cross-account KMS use, think key policy in the owner account + IAM policy in the caller account + key ARN.
- If you see "only when S3 (or another service) uses the key for us", think
kms:ViaService. - If you see
InvalidCiphertextExceptionafter a code change, think encryption context mismatch. - If you see
KMSInvalidStateException, think a key that is pending deletion, pending import or unavailable (DisabledExceptionfor a disabled key). - If you see a new grant that doesn't work yet, think eventual consistency: pass the grant token.
Throughput (§5)
- If you see
ThrottlingExceptionfrom KMS, think the shared per-account symmetric quota: backoff with jitter, data key caching, S3 Bucket Keys, a Service Quotas increase. - If you see heavy SSE-KMS traffic or KMS cost from S3, think S3 Bucket Keys (new objects;
UpdateObjectEncryptionin place orCopyObjectfor old ones).
Keys over time (§6)
- If you see "rotate every N days (90–2,560) with no code change", think automatic rotation with
--rotation-period-in-days. - If you see "rotate now", think
RotateKeyOnDemand(25 per key); for asymmetric, HMAC or custom key store keys, think manual rotation withUpdateAlias. - If you see "stop all use of a key but keep the option to undo", think disable the key (deletion needs a 7–30 day wait and is irreversible).
- If you see "decrypt the same ciphertext in another Region during an outage", think multi-Region keys.
- If you see configuration values or low-risk secrets at no cost, think Parameter Store standard parameters (
SecureStringfor secrets). - If you see automatic rotation, cross-account access, replication or RDS credentials, think Secrets Manager.
- If you see parameter values above 4 KB, parameter policies or cross-account sharing, think the advanced tier.
- If you see
ThrottlingException: Rate exceededon Parameter Store reads, think higher throughput (or caching). - If you see a Lambda function that reads secrets or parameters on every invocation, think the Parameters and Secrets Lambda Extension or a caching client.
- If you see a rotation function timing out in private subnets, think an interface VPC endpoint for Secrets Manager (or a NAT gateway).
- If you see "no failed logins during rotation", think the alternating users strategy.
- If you see RDS master credentials with the LEAST operational overhead, think
ManageMasterUserPassword. - If you see a secret needed in a CloudFormation property, think
{{resolve:secretsmanager:…}}(orssm-securein the supported properties). - If you see secrets in CodeBuild, think
env/secrets-managerorenv/parameter-storewith service-role permissions, neverPLAINTEXT. - If you see a customer-controlled key for an existing log group, think
aws logs associate-kms-keyplus a key policy forlogs.<region>.amazonaws.com. - If you see "only the approved code inside an enclave may decrypt", think Nitro Enclaves with
kms:RecipientAttestation:*conditions.
Changed vs older material
Learn the Current column; older practice material may still show the Older material may say value. Once you confirm them, the same rows go to notes/current-facts.md with their verification date.
| Topic | Older material may say | Current (verified 2026-10-12) | Source |
|---|---|---|---|
| §1 · AWS managed key charges and status | AWS managed keys are free | No monthly fee, but every use is billed as a KMS request (some services pay it); a legacy type: since 2021 new services encrypt with AWS owned keys | AWS docs |
| §1 · KMS key types | Symmetric AES-256 or asymmetric RSA/ECC only | Also HMAC keys (April 2022), ECC key agreement (June 2024), ML-DSA post-quantum signing (June 2025), Ed25519 (November 2025) | AWS docs |
| §4 · Encryption SDK languages | Java, Python, C, JavaScript (plus the CLI) | C, C#/.NET, Java, JavaScript, Python, Rust, Go (plus the CLI) | AWS docs |
| §5 · Symmetric cryptographic request quota | 5,500/s default; 10,000/s in six Regions; 30,000/s in us-east-1, us-west-2, eu-west-1 | 10,000/s default; 20,000/s in us-east-2, ap-southeast-1, ap-southeast-2, ap-northeast-1, eu-central-1, eu-west-2, eu-south-2; 100,000/s in us-east-1, us-west-2, eu-west-1 (July 2024) | AWS docs |
| §5 · RSA request quota | 500/s | 1,000/s (shared) | AWS docs |
| §5 · ECC request quota | 300/s | 1,000/s (shared, ECC and SM2) | AWS docs |
| §5 · Bucket Keys for existing objects | Existing objects get a Bucket Key only when rewritten with CopyObject |
UpdateObjectEncryption switches SSE-S3 and SSE-KMS objects to SSE-KMS with a Bucket Key in place, keeping ETag and Last-Modified; unencrypted, SSE-C and DSSE-KMS objects still need CopyObject |
AWS docs |
| §6 · AWS managed key rotation | Every three years (about 1,095 days) | Every year (about 365 days) since May 2022; can't be changed or triggered | AWS docs |
| §6 · Customer managed key rotation period | Fixed: once a year | RotationPeriodInDays 90–2,560, default 365 (April 2024); symmetric keys with AWS_KMS material only |
AWS docs |
| §6 · Key rotation pricing | Every rotation adds $1/month | Only the first and second rotations add $1/month each (prorated hourly); later rotations aren't billed (April 2024) | What's New |
| §6 · On-demand rotation | Not available; imported keys only by repointing an alias | RotateKeyOnDemand, at most 25 per key (not adjustable), schedule unchanged (April 2024); also symmetric imported material (June 2025) and multi-Region imported material (November 2025); not asymmetric, HMAC or custom key store keys |
AWS docs |
| §6 · Using a key in another Region | A KMS key can't be used outside its Region | Multi-Region keys (June 2021) share key ID and material, so ciphertext encrypted in one Region decrypts with the related key in another | AWS docs |
| §7 · KMS HSM validation | FIPS 140-2 Level 3 | FIPS 140-3 Level 3 | AWS docs |
| §7 · CloudHSM validation | FIPS 140-2 Level 3 | hsm2m.medium FIPS 140-3 Level 3 (hsm1.medium FIPS 140-2 Level 3); FIPS or non-FIPS cluster mode, fixed at creation | AWS FAQ |
| §7 · SSE-C availability | SSE-C (for example with CloudHSM-held keys) works on any bucket | Disabled by default for new general purpose buckets since April 2026; enable with PutBucketEncryption BlockedEncryptionTypes = NONE, otherwise HTTP 403 |
AWS docs |
| §8 · Amazon Linux public AMI parameter | /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 |
Amazon Linux 2 end of support 2026-06-30; use AL2023 parameters such as al2023-ami-kernel-default-x86_64 |
AWS FAQ |
| §10 · Secrets Manager rotation frequency | Days only, at most once a day | As often as every four hours with rate()/cron(), rotation window at least one hour, UTC; rate() up to 999 days |
AWS docs |
| §10 · Rotation mechanism | Rotation always runs a Lambda function | Managed rotation (no Lambda) for RDS, Aurora, Redshift and DocumentDB master credentials, ECS Service Connect certificates and managed external secrets; Lambda for other secrets | AWS docs |
§11 · ssm/ssm-secure reference version |
The version is part of the syntax | Optional: latest version at each create or update; mandatory only for ssm references in the Parameters section |
AWS docs |
| §12 · CloudWatch Logs key association in the console | The console can't associate a KMS key with a log group | AWS documents that the console can't associate a key with an existing log group (use associate-kms-key); console support at creation ⚠️ unverified |
AWS docs |