Skip to content

Lab 01 — KMS + S3 data protection

✅ Done 🧭 Data Protection 💲 ≈ $0.004
Services: KMS, S3, IAM, STS, CloudTrail

In one sentence

See how a KMS key policy, IAM policies, grants and an S3 bucket policy combine on one request — the single most-tested area in Data Protection.

The idea

 you (lab-admin, admin)
   ├── assume ──> sec1-writer-role   S3 put/get  +  named in key policy (only via S3)
   └── assume ──> sec1-reader-role   S3 get only, NOT in key policy  → denied until a grant
                          │
                          ▼
   s3://sec1-data-111111111111   default SSE-KMS + bucket key, policy denies non-TLS / wrong key
                          │
                          ▼
   KMS key alias/sec1-data       key policy: root (enables IAM) · admin · writer (kms:ViaService=S3)

The writer's KMS access lives only in the key policy, and only when the call comes through S3:

{
  "Sid": "WriterRoleUseOnlyThroughS3",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::111111111111:role/sec1-writer-role" },
  "Action": ["kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey"],
  "Resource": "*",
  "Condition": { "StringEquals": { "kms:ViaService": "s3.us-east-2.amazonaws.com" } }
}

What happened in the lab run

Writer role (in the key policy, via S3 only)

Test Result
Upload with no encryption header (bucket default) ✅ encrypted with alias/sec1-data, bucket key on
Upload with --sse AES256 (SSE-S3) ❌ denied by bucket policy (DenyNonKmsEncryption)
Upload with the AWS managed key aws/s3 ❌ denied by bucket policy (DenyWrongKmsKey)
kms encrypt called directly, not through S3 ❌ denied — kms:ViaService doesn't match

Reader role (S3 read only, not in the key policy)

Test Result
GetObject on the SSE-KMS object ❌ AccessDenied — S3 calls kms:Decrypt as the reader
Same call after create-grant (Decrypt) ✅ works (after a few seconds — grants are eventually consistent)
revoke-grant access removed again

Encryption context and CloudTrail

Test Result
Encrypt with context project=sec1,env=lab ✅
Decrypt without the context ❌ InvalidCiphertextException
Decrypt with the exact same context (no --key-id needed) ✅ plaintext back
GenerateDataKey ✅ plaintext + encrypted data key (envelope encryption)
Reader's failed Decrypt in CloudTrail ✅ found, invokedBy: fas.s3.amazonaws.com — a forward access session using the reader's identity

Key lessons

  1. Same account: a key policy that names a principal is enough on its own. The writer role had no kms:* in IAM at all. The EnableIAMPolicies (account root) statement doesn't mean "root user only" — it delegates to IAM, so IAM policies in the account start to count for this key.
  2. S3 permissions are not KMS permissions. For SSE-KMS objects, S3 calls KMS with the caller's identity (FAS). s3:GetObject without kms:Decrypt = AccessDenied on SSE-KMS objects only.
  3. kms:ViaService pins key use to a service — the same role can use the key through S3 but not directly.
  4. Grants give temporary, programmatic access without editing the key policy; AWS services (EBS, RDS…) use them behind the scenes.
  5. Encryption context is additional authenticated data: decrypt needs the exact same context, and it's logged in CloudTrail in plaintext.

Exam notes

  • "User has s3:GetObject but gets Access Denied on some objects only" → those objects are SSE-KMS and the user lacks kms:Decrypt. SSE-S3 objects download fine.
  • Remove EnableIAMPolicies and even an AdministratorAccess user can't use the key — only principals named in the key policy. CreateKey refuses a lockout policy unless you pass the bypass flag; an unmanageable key needs AWS Support.
  • Principal ARNs in key/bucket policies resolve to unique IDs at save time. Delete and recreate the role → the policy shows an AROA… ID and no longer matches.
  • Bucket policy condition gotcha: when a key is absent from the request, StringNotEquals and …IfExists both evaluate to true. Add "Null": {"<key>": "false"} to deny only when the header is present and wrong.
  • The bucket policy compares x-amz-server-side-encryption-aws-kms-key-id to the key ARN string — a key ID or alias for the right key is a different string and is denied.
  • --sse aws:kms with no key ID → S3 uses the AWS managed key aws/s3.
  • Bucket keys cut KMS requests by up to ~99% (cost + throttling), but change the encryption context to the bucket ARN — conditions on the object-ARN context break.
  • Direct Encrypt is limited to 4 KB; real data uses envelope encryption (GenerateDataKey). Symmetric ciphertext embeds the key reference, so Decrypt needs no key ID.
  • Grants: use a grant token to use a grant before it propagates. retire-grant = grantee gives it up; revoke-grant = key admin takes it away. Key policy size limit is 32 KB.
  • Key deletion: 7–30 days waiting period (default 30), key unusable and every attempted use logged → alarm on it and cancel-key-deletion. Disabling is the reversible alternative.
  • Rotation: customer managed keys support automatic rotation (configurable period, 90–2560 days) and on-demand rotation; old key material is kept so old ciphertext still decrypts.

Review questions

Question 1

Cross-account: Account B's role needs to decrypt with Account A's key. What two policies must allow it?

Show answer

The key policy in Account A must allow Account B (its root, or the specific role), and an IAM policy in Account B must allow the KMS actions on Account A's key ARN. Cross-account access always needs both sides: the resource owner grants it to the account, then B's admin delegates it to the principal. For cross-account S3 + SSE-KMS it's three: bucket policy + key policy in A, IAM policy in B.

Question 2

You need SSE where AWS never holds your key material at all. Options?

Show answer

SSE-C — you send the key with every request (HTTPS only); S3 uses it in memory to encrypt/decrypt, stores only a salted HMAC to validate later requests, and discards the key. You must manage and keep the key yourself; lose it and the object is gone. The strictest option is client-side encryption, where AWS only ever sees ciphertext. SSE-KMS doesn't qualify — KMS holds the key material (even imported material is stored by KMS).

Question 3

A company wants keys in single-tenant FIPS 140-3 Level 3 HSMs it fully controls. Which service?

Show answer

AWS CloudHSM — dedicated, single-tenant HSMs in your VPC where you manage users and keys and AWS has no access to the key material. KMS HSMs are validated too, but they're multi-tenant and AWS-managed. If they still want the KMS API and integrations, use a KMS custom key store backed by CloudHSM; for keys that must stay outside AWS entirely, an external key store (XKS).

Question 4

Imported key material (BYOK): can you enable automatic rotation?

Show answer

No. Automatic rotation isn't supported for keys with imported material (nor for asymmetric, HMAC or custom-key-store keys). Rotate manually: create a new key, import new material, and re-point the alias to it. (AWS later added on-demand rotation for imported material, but automatic rotation still isn't available.)

Question 5

What breaks if you encrypt an EBS snapshot with the aws/ebs key and try to share it cross-account?

Show answer

You can't share it usefully: the other account needs permission to use the key, and the key policy of an AWS managed key can't be edited, so you can't grant it. Copy the snapshot re-encrypting with a customer managed key, allow the target account in that key's policy, share the new snapshot, and have the target account copy it into its own account (ideally re-encrypting with its own key).

Weak spots

SSE-C vs SSE-KMS (who holds the key, who can audit it) · why AWS managed keys block cross-account sharing (their key policy can't be edited, so no other account can be granted use of them).