From a11d144f188992623ecdee2fe6d89d0e8a6cc0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Gonz=C3=A1lez=20Di=20Antonio?= Date: Tue, 26 May 2026 10:14:08 +0200 Subject: [PATCH] fix(template): use bucket ARN for SSE-KMS encryption context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #533 scoped kms:EncryptionContext:aws:s3:arn to the object ARN (/). That works for plain SSE-KMS, but the state bucket has BucketKeyEnabled: true, so S3 generates a short-lived bucket-level data key and derives per-object keys locally — the encryption context S3 forwards to KMS is the bucket ARN, not the object ARN. The condition therefore never matched at runtime and every sync failed with: AccessDenied: ... is not authorized to perform: kms:Decrypt on resource: arn:aws:kms:...:key/... because no identity-based policy allows the kms:Decrypt action (IAM reports a condition-mismatched Allow as "not allowed".) Change both KMSGetDataPolicy and KMSDecryptPolicy to pin the bucket ARN. The role can still only reach the single state object via the S3ObjectPolicy ARN scoping; kms:ViaService still forces all KMS access through S3. Also update docs/Whats-New.md to describe the correct behavior and link to the S3 Bucket Keys docs. Refs: #521, #533 Ref: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/Whats-New.md | 4 ++-- template.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 07b63ef..c383376 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -78,11 +78,11 @@ Tightens the Lambda execution role in `template.yaml` so it can only touch the s Both now carry the AWS-recommended SSE-KMS scoping conditions: * `kms:ViaService = s3..amazonaws.com` — the CMK can only be used through requests S3 forwards on the role's behalf, not via direct `kms:Decrypt` / `kms:Encrypt` calls. -* `kms:EncryptionContext:aws:s3:arn = arn:${Partition}:s3:::/` — the KMS grant only applies when S3 passes that exact object ARN as encryption context. S3 always populates this context for SSE-KMS objects, so legitimate reads/writes of the state file continue to work; any other object path is rejected by KMS. +* `kms:EncryptionContext:aws:s3:arn = arn:${Partition}:s3:::` — the KMS grant only applies when S3 passes that exact **bucket** ARN as encryption context. The bucket ARN (not the object ARN) is the correct value here because the state bucket has `BucketKeyEnabled: true`: S3 generates a short-lived bucket-level data key once and derives per-object keys locally from it, so the encryption context S3 forwards to KMS is the bucket ARN. The role can still only reach the single state object through the `S3ObjectPolicy` ARN scoping. See [AWS docs — S3 Bucket Keys](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html). These are belt-and-braces additions on top of the existing bucket policy, public-access block, `aws:SecureTransport` deny, and SSE-KMS-with-bucket-key configuration. -References: [AWS docs — kms:ViaService](https://docs.aws.amazon.com/kms/latest/developerguide/policy-conditions.html#conditions-kms-via-service), [AWS docs — SSE-KMS encryption context](https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-kms-encryption.html). +References: [AWS docs — kms:ViaService](https://docs.aws.amazon.com/kms/latest/developerguide/policy-conditions.html#conditions-kms-via-service), [AWS docs — SSE-KMS encryption context](https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-kms-encryption.html), [AWS docs — S3 Bucket Keys](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html). ### OpenSSF Scorecard Hardening (Phase 4) — Fuzzing diff --git a/template.yaml b/template.yaml index f1dadb4..8d7f9ee 100644 --- a/template.yaml +++ b/template.yaml @@ -371,7 +371,7 @@ Resources: Condition: StringEquals: kms:ViaService: !Sub "s3.${AWS::Region}.amazonaws.com" - kms:EncryptionContext:aws:s3:arn: !Sub "arn:${AWS::Partition}:s3:::${BucketNamePrefix}-${AWS::AccountId}-${AWS::Region}/${BucketKey}" + kms:EncryptionContext:aws:s3:arn: !Sub "arn:${AWS::Partition}:s3:::${BucketNamePrefix}-${AWS::AccountId}-${AWS::Region}" - Sid: KMSDecryptPolicy Effect: Allow Action: @@ -383,7 +383,7 @@ Resources: Condition: StringEquals: kms:ViaService: !Sub "s3.${AWS::Region}.amazonaws.com" - kms:EncryptionContext:aws:s3:arn: !Sub "arn:${AWS::Partition}:s3:::${BucketNamePrefix}-${AWS::AccountId}-${AWS::Region}/${BucketKey}" + kms:EncryptionContext:aws:s3:arn: !Sub "arn:${AWS::Partition}:s3:::${BucketNamePrefix}-${AWS::AccountId}-${AWS::Region}" AWSGWSServiceAccountFileSecret: Type: AWS::SecretsManager::Secret