You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/changelog-cdkd.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,6 +16,10 @@ The CLAUDE.md `## Known Limitations` section retains the load-bearing summary
16
16
17
17
---
18
18
19
+
**Recently Implemented** (2026-06-15):
20
+
21
+
- ✅ **Fix: canonicalize CloudFront OAI grant principals in `S3BucketPolicyProvider.readCurrentState` (closes the phantom drift on an S3 BucketPolicy granting an OAI, issue [#872](https://github.com/go-to-k/cdkd/issues/872))** — `src/provisioning/providers/s3-bucket-policy-provider.ts`. **Bug:** a bucket policy statement that grants a CloudFront Origin Access Identity (OAI) read access stores its principal as the OAI's S3 canonical user id (`{ CanonicalUser: <64-hex> }`, what CDK's `Fn::GetAtt [<OAI>, S3CanonicalUserId]` resolves to). But `s3:GetBucketPolicy` returns that same principal in TWO other unstable forms over the policy's lifetime: a transient `{ AWS: <IAM-unique-id> }` (e.g. `AIDA…`) right after `PutBucketPolicy`, then the settled `{ AWS: arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity <id> }`. The drift comparator saw these three equivalent representations as different and fired a guaranteed false positive on every `cdkd drift` run for any stack with an OAI + S3 bucket policy (the deploy-time `observedProperties` capture caught the transient unique-id form; the later drift read got the ARN form — neither matched the template's canonical user id). **Fix:** `readCurrentState` now canonicalizes every recognizable OAI principal back to `{ CanonicalUser: <id> }`. For the settled `arn:…:cloudfront:user/…<oaiId>` form it maps the OAI id to its `S3CanonicalUserId`, **preferring the same-stack sibling OAI resource's already-read state attribute** (zero AWS call / no `cloudfront:GetCloudFrontOriginAccessIdentity` IAM grant — the OAI's `S3CanonicalUserId` is a readOnly attribute cdkd already resolved at deploy time, threaded into the drift read via the new `ReadCurrentStateContext.siblings[].attributes` + `.physicalId` fields) and falling back to `GetCloudFrontOriginAccessIdentity(<oaiId>)` only when the OAI is NOT a same-stack sibling (an imported / external OAI). The transient bare-IAM-unique-id form (which carries no recoverable link to the OAI) is canonicalized only by matching the corresponding template statement (same Effect / Action / Resource) carrying a `{ CanonicalUser }` principal — safe because a user cannot author a bare IAM unique id as a bucket-policy principal, so it only ever fires for AWS's transient rendering. Both the deploy-time capture and the drift read run through the same normalization, so both converge to the canonical user id and compare equal; a genuinely-different OAI resolves to a different canonical id so real drift is still detected (strict reconcile — never a blanket suppress). Non-OAI principals (`*`, service principals, normal role ARNs) are untouched. The `ReadCurrentStateContext.siblings` shape gained optional `physicalId` + `attributes` (populated by `buildReadCurrentStateContext` in `src/cli/commands/drift.ts`) so any provider can reconcile a sibling's computed identity from already-read state without an extra AWS call. 13 unit tests (ARN→CanonicalUser via sibling state attribute with NO CloudFront call; strict-reconcile: a DIFFERENT OAI resolves to a DIFFERENT canonical id so real drift is NOT suppressed; AWS-array principal + ambiguous-template-match left unchanged; ARN→CanonicalUser via `GetCloudFrontOriginAccessIdentity` fallback when not a sibling; bare-unique-id→template-match; no-template-match left unchanged; best-effort ARN-lookup-failure left unchanged; non-OAI principals untouched; one CloudFront call cached across statements). Validated against real AWS by extending the `s3-cloudfront` integ's `verify.sh` to assert the `AWS::S3::BucketPolicy` reports clean (in the `clean` drift bucket, not `drifted`) on a fresh deploy — the distribution-scoping caveat the CloudFront PR (#871) added is removed now that the bucket policy no longer phantom-drifts. (Approach mirrors the sibling cdk-real-drift project's fix per the #872 cross-note: read the OAI's `S3CanonicalUserId` from already-read state rather than re-fetching it.)
22
+
19
23
**Recently Implemented** (2026-06-14):
20
24
21
25
- ✅ **Fix: `AWS::DynamoDB::Table` in-place `BillingMode` / `ProvisionedThroughput` UPDATE (was a silent drop)** — `src/provisioning/providers/dynamodb-table-provider.ts`. **Bug:** `update()` handled OnDemandThroughput / WarmThroughput / PITR / TTL / ResourcePolicy / Kinesis / ContributorInsights / Tags but issued NO `UpdateTable` for `BillingMode` or `ProvisionedThroughput`, even though both are declared in `handledProperties` and both are mutable (CFn createOnly = only `TableName` + `ImportSourceSpecification`). So a pure capacity change (e.g. RCU 5→100, mode unchanged) or a pure billing-mode switch (PROVISIONED↔PAY_PER_REQUEST) was silently dropped — `update()` returned `{ wasReplaced: false }` with no AWS call while cdkd recorded the new value into state as if applied, so the next deploy saw no diff and the AWS-side capacity / mode stayed stale forever (the silent-drift failure mode documented in `feedback_tags_on_update_must_throw`). **Fix:** a new branch, ordered BEFORE the OnDemand/Warm throughput branches, fires a SINGLE `UpdateTable` whenever `BillingMode` OR `ProvisionedThroughput` changed. It forwards `BillingMode` when present and `ProvisionedThroughput` only when the (desired) mode is not `PAY_PER_REQUEST` (AWS rejects caps on on-demand; PROVISIONED requires them), coercing the string-typed capacity values to numbers via `Number()` (matching `create()`; CFn emits numerics as strings). A combined switch-to-PROVISIONED-with-caps now works because both fields ride one call before the OnDemand branch (closing the pre-existing fail-loud caveat the old comment documented). After the call it waits for ACTIVE via the existing `waitForTableActiveAfterUpdate` helper so later branches don't race a still-UPDATING table. Per-index `GlobalSecondaryIndexes` ProvisionedThroughput is explicitly NOT handled (a documented deferred gap, not a silent one — needs `GlobalSecondaryIndexUpdates`). 6 unit tests (pure capacity change PROVISIONED→PROVISIONED; string-numeric coercion; switch to PAY_PER_REQUEST drops caps; PAY_PER_REQUEST switch ignores stale template caps; switch to PROVISIONED sends both in one call; no-change makes no billing UpdateTable). Integ: the `dynamodb-ondemand` fixture gains a standalone PROVISIONED table whose capacity flips RCU 5→20 / WCU 5→10 under `CDKD_TEST_UPDATE=true`, and `verify.sh` adds a Phase-1.5 re-deploy + `describe-table` assertion that AWS reflects the new ProvisionedThroughput (plus a destroy-cleanup poll for the new table).
0 commit comments