@go-to-k/cdkd 0.278.37 → 0.278.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{asg-provider-BTeSlton.js → asg-provider-CaulrRij.js} +2 -2
- package/dist/{asg-provider-BTeSlton.js.map → asg-provider-CaulrRij.js.map} +1 -1
- package/dist/cli.js +273 -7
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-bEhHOGVR.js → deploy-engine-DIvnBeMj.js} +3 -3
- package/dist/{deploy-engine-bEhHOGVR.js.map → deploy-engine-DIvnBeMj.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as describeTypeWithThrottleRetry, $t as AwsClients, A as red, At as Synthesizer, B as isTerminationProtectionPropagationError, Bt as resolveUseCdkBootstrapAssets, C as isStatefulRecreateTargetSync, Ct as buildDockerImage, D as cyan, Dt as runDockerStreaming, E as bold, Et as runDockerForeground, F as ProviderRegistry, Ft as resolveAutoAssetStorage, G as normalizeAwsTagsToCfn, Gt as MIGRATE_TMP_PREFIX, H as cfnRefValueFromPhysicalId, Ht as warnDeprecatedNoPrefixCliFlag, I as findActionableSilentDrops, It as resolveCaptureObservedState, J as readConfigString, Jt as expectedOwnerParam, K as resolveExplicitPhysicalId, Kt as findLargeInlineResources, L as CloudControlProvider, Lt as resolveSkipPrefix, M as IAMRoleProvider, Mt as getDefaultStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as getLegacyStateBucketName, O as gray, Ot as AssetManifestLoader, P as clearOnUpdateRemoval, Pt as resolveApp, Q as DiffCalculator, Qt as resolveBucketRegion, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefault, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateContainerRepoName, T as formatResourceLine, Tn as withErrorHandling, Tt as getDockerCmd, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_BODY_LIMIT, V as IntrinsicFunctionResolver, Vt as stateBucketExistenceConfirmed, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_URL_LIMIT, X as requireConfigString, Xt as processStackMessages, Y as requireConfigArray, Yt as AssemblyReader, Z as applyRoleArnIfSet, _ as createPreDeleteFinalSnapshot, _n as ResourceUpdateNotSupportedError, _t as BOOTSTRAP_MARKER_PREFIX, a as DeploymentEventsStore, at as LockManager, b as unsupportedFinalSnapshotError, bt as parseBootstrapMarker, c as replayFailedOperations, cn as LocalInvokeBuildError$1, ct as shouldRetainResource, d as IMPLICIT_DELETE_DEPENDENCIES, dt as WorkGraph, en as getAwsClients, et as withRetry, f as computeImplicitDeleteEdges, fn as MissingCdkCliError, ft as buildAssetRedirectMap, g as ccRoutedFinalSnapshotError, gn as ResourceTimeoutError, gt as AssetModeResolver, h as buildFinalSnapshotIdentifier, hn as ProvisioningError, ht as rewriteTemplateAssetReferences, i as DeploymentEventsReader, in as CdkdError, it as TemplateParser, j as yellow, jt as synthesisStatusMessage, k as green, kt as getDockerImageBySourceHash, l as replayRollback, ln as LocalMigrateError, lt as AssetPublisher, m as PRE_DELETE_SNAPSHOT_TYPES, mn as PartialFailureError, mt as loadPublishableAssetManifest, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as setAwsClients, nt as isThrottlingError, o as planFailedOps, ot as S3StateBackend, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as NestedStackChildDirectDestroyError, pt as createAssetRedirectResolver, q as assertRegionMatch, qt as uploadCfnTemplate, r as DeployEngine, rt as DagBuilder, s as planRollback, sn as DeployCancelledError, st as rebuildClientForBucketRegion, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resetAwsClients, tt as isRetryableTransientError, u as withResourceDeadline, un as LocalStartServiceError, ut as stringifyValue, v as isFinalSnapshotError, vn as StackHasActiveImportsError, vt as ensureAssetStorage, w as renderStatefulReason, wn as normalizeAwsError, wt as formatDockerLoginError, x as extractDeploymentEventError, xt as validateAssetBucketName, y as refusesFinalSnapshot, yn as StackTerminationProtectionError, yt as getBootstrapMarkerKey, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefaultAndSource } from "./deploy-engine-
|
|
2
|
+
import { $ as describeTypeWithThrottleRetry, $t as AwsClients, A as red, At as Synthesizer, B as isTerminationProtectionPropagationError, Bt as resolveUseCdkBootstrapAssets, C as isStatefulRecreateTargetSync, Ct as buildDockerImage, D as cyan, Dt as runDockerStreaming, E as bold, Et as runDockerForeground, F as ProviderRegistry, Ft as resolveAutoAssetStorage, G as normalizeAwsTagsToCfn, Gt as MIGRATE_TMP_PREFIX, H as cfnRefValueFromPhysicalId, Ht as warnDeprecatedNoPrefixCliFlag, I as findActionableSilentDrops, It as resolveCaptureObservedState, J as readConfigString, Jt as expectedOwnerParam, K as resolveExplicitPhysicalId, Kt as findLargeInlineResources, L as CloudControlProvider, Lt as resolveSkipPrefix, M as IAMRoleProvider, Mt as getDefaultStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as getLegacyStateBucketName, O as gray, Ot as AssetManifestLoader, P as clearOnUpdateRemoval, Pt as resolveApp, Q as DiffCalculator, Qt as resolveBucketRegion, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefault, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateContainerRepoName, T as formatResourceLine, Tn as withErrorHandling, Tt as getDockerCmd, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_BODY_LIMIT, V as IntrinsicFunctionResolver, Vt as stateBucketExistenceConfirmed, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_URL_LIMIT, X as requireConfigString, Xt as processStackMessages, Y as requireConfigArray, Yt as AssemblyReader, Z as applyRoleArnIfSet, _ as createPreDeleteFinalSnapshot, _n as ResourceUpdateNotSupportedError, _t as BOOTSTRAP_MARKER_PREFIX, a as DeploymentEventsStore, at as LockManager, b as unsupportedFinalSnapshotError, bt as parseBootstrapMarker, c as replayFailedOperations, cn as LocalInvokeBuildError$1, ct as shouldRetainResource, d as IMPLICIT_DELETE_DEPENDENCIES, dt as WorkGraph, en as getAwsClients, et as withRetry, f as computeImplicitDeleteEdges, fn as MissingCdkCliError, ft as buildAssetRedirectMap, g as ccRoutedFinalSnapshotError, gn as ResourceTimeoutError, gt as AssetModeResolver, h as buildFinalSnapshotIdentifier, hn as ProvisioningError, ht as rewriteTemplateAssetReferences, i as DeploymentEventsReader, in as CdkdError, it as TemplateParser, j as yellow, jt as synthesisStatusMessage, k as green, kt as getDockerImageBySourceHash, l as replayRollback, ln as LocalMigrateError, lt as AssetPublisher, m as PRE_DELETE_SNAPSHOT_TYPES, mn as PartialFailureError, mt as loadPublishableAssetManifest, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as setAwsClients, nt as isThrottlingError, o as planFailedOps, ot as S3StateBackend, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as NestedStackChildDirectDestroyError, pt as createAssetRedirectResolver, q as assertRegionMatch, qt as uploadCfnTemplate, r as DeployEngine, rt as DagBuilder, s as planRollback, sn as DeployCancelledError, st as rebuildClientForBucketRegion, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resetAwsClients, tt as isRetryableTransientError, u as withResourceDeadline, un as LocalStartServiceError, ut as stringifyValue, v as isFinalSnapshotError, vn as StackHasActiveImportsError, vt as ensureAssetStorage, w as renderStatefulReason, wn as normalizeAwsError, wt as formatDockerLoginError, x as extractDeploymentEventError, xt as validateAssetBucketName, y as refusesFinalSnapshot, yn as StackTerminationProtectionError, yt as getBootstrapMarkerKey, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefaultAndSource } from "./deploy-engine-DIvnBeMj.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as withSkipPrefix, i as runStackBuffered, n as getLogger, o as PATTERN_B_NAME_OPTIONS, p as withStackName, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-BYMEE-BS.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-CaulrRij.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
8
|
-
import { AddRoleToInstanceProfileCommand, AddUserToGroupCommand, AttachGroupPolicyCommand, AttachRolePolicyCommand, AttachUserPolicyCommand, CreateAccessKeyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreatePolicyCommand, CreatePolicyVersionCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeletePolicyCommand, DeletePolicyVersionCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachRolePolicyCommand, DetachUserPolicyCommand, GetAccessKeyLastUsedCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetPolicyCommand, GetPolicyVersionCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListEntitiesForPolicyCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListPoliciesCommand, ListPolicyTagsCommand, ListPolicyVersionsCommand, ListUserPoliciesCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagPolicyCommand, TagUserCommand, UntagPolicyCommand, UntagUserCommand, UpdateAccessKeyCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
|
|
8
|
+
import { AddRoleToInstanceProfileCommand, AddUserToGroupCommand, AttachGroupPolicyCommand, AttachRolePolicyCommand, AttachUserPolicyCommand, CreateAccessKeyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreatePolicyCommand, CreatePolicyVersionCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeletePolicyCommand, DeletePolicyVersionCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachRolePolicyCommand, DetachUserPolicyCommand, GetAccessKeyLastUsedCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetPolicyCommand, GetPolicyVersionCommand, GetRoleCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListEntitiesForPolicyCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListPoliciesCommand, ListPolicyTagsCommand, ListPolicyVersionsCommand, ListUserPoliciesCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagPolicyCommand, TagUserCommand, UntagPolicyCommand, UntagUserCommand, UpdateAccessKeyCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
|
|
9
9
|
import { CreateQueueCommand, DeleteQueueCommand, GetQueueAttributesCommand, GetQueueUrlCommand, ListQueueTagsCommand, QueueDoesNotExist, SQSClient, SetQueueAttributesCommand, TagQueueCommand, UntagQueueCommand } from "@aws-sdk/client-sqs";
|
|
10
10
|
import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesCommand, GetTopicAttributesCommand, InvalidParameterException, ListSubscriptionsByTopicCommand, ListTagsForResourceCommand, ListTopicsCommand, NotFoundException, SNSClient, SetTopicAttributesCommand, SubscribeCommand, TagResourceCommand, UnsubscribeCommand, UntagResourceCommand } from "@aws-sdk/client-sns";
|
|
11
11
|
import { AddPermissionCommand, CreateEventSourceMappingCommand, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteEventSourceMappingCommand, DeleteFunctionCommand, DeleteFunctionConcurrencyCommand, DeleteFunctionEventInvokeConfigCommand, DeleteFunctionUrlConfigCommand, DeleteLayerVersionCommand, GetEventSourceMappingCommand, GetFunctionCommand, GetFunctionConcurrencyCommand, GetFunctionEventInvokeConfigCommand, GetFunctionRecursionConfigCommand, GetFunctionUrlConfigCommand, GetLayerVersionByArnCommand, GetPolicyCommand as GetPolicyCommand$1, LambdaClient, ListTagsCommand, PublishLayerVersionCommand, PutFunctionConcurrencyCommand, PutFunctionEventInvokeConfigCommand, PutFunctionRecursionConfigCommand, RemovePermissionCommand, ResourceNotFoundException, TagResourceCommand as TagResourceCommand$1, UntagResourceCommand as UntagResourceCommand$1, UpdateEventSourceMappingCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
|
|
@@ -49069,6 +49069,208 @@ function isPlainObject(value) {
|
|
|
49069
49069
|
return typeof value === "object" && value !== null;
|
|
49070
49070
|
}
|
|
49071
49071
|
|
|
49072
|
+
//#endregion
|
|
49073
|
+
//#region src/analyzer/drift-principal-normalize.ts
|
|
49074
|
+
/**
|
|
49075
|
+
* IAM principal-form canonicalization for the drift comparator (issue #1515).
|
|
49076
|
+
*
|
|
49077
|
+
* AWS renders an IAM role / user principal inside a resource policy in TWO
|
|
49078
|
+
* forms and picks between them on its own schedule:
|
|
49079
|
+
*
|
|
49080
|
+
* - the ARN — `arn:aws:iam::<acct>:role/<name>`
|
|
49081
|
+
* - the principal's UNIQUE ID — `AROA…` (role) / `AIDA…` (user)
|
|
49082
|
+
*
|
|
49083
|
+
* The unique-id form is substituted when the referenced principal is
|
|
49084
|
+
* transiently unresolvable at write time (it is being created or replaced) and
|
|
49085
|
+
* AWS re-renders the ARN once it resolves. cdkd's deploy-time
|
|
49086
|
+
* `observedProperties` capture stores whichever form the read returned at that
|
|
49087
|
+
* moment, so a later `cdkd drift` can compare `AROA…` against the ARN for the
|
|
49088
|
+
* very same principal and report PERMANENT phantom drift — `--revert` writes
|
|
49089
|
+
* the recorded form back, AWS re-canonicalizes on write, and the next run
|
|
49090
|
+
* reports the identical difference (the class of issues #1096 / #1498, on a
|
|
49091
|
+
* different property).
|
|
49092
|
+
*
|
|
49093
|
+
* Unlike the order-normalization passes in `drift-normalize.ts`, this one
|
|
49094
|
+
* CANNOT be pure: the mapping between the two forms is not derivable from the
|
|
49095
|
+
* string. So the shape detection here is pure and an injected
|
|
49096
|
+
* {@link PrincipalUniqueIdResolver} does the one lookup, which lets the whole
|
|
49097
|
+
* pass be unit-tested without AWS and keeps the API cost at zero for the
|
|
49098
|
+
* overwhelmingly common case — a policy with no unique-id principal makes NO
|
|
49099
|
+
* AWS call at all.
|
|
49100
|
+
*
|
|
49101
|
+
* Both sides are rewritten to the ARN form, never one side only. The race that
|
|
49102
|
+
* produces the mismatch is symmetric (the 2026-08-10 evidence on the
|
|
49103
|
+
* `drift-revert` fixture had the baseline holding `AROA…` and AWS the ARN, but
|
|
49104
|
+
* the reverse ordering is equally reachable), and normalizing a single side is
|
|
49105
|
+
* the mistake `drift-normalize.ts`'s header already records for the unordered
|
|
49106
|
+
* passes — it manufactures drift on the `properties`-fallback baseline.
|
|
49107
|
+
*
|
|
49108
|
+
* FAILING IS VISIBLE, never silent: when the resolver cannot answer (the role
|
|
49109
|
+
* was deleted — which is exactly when AWS keeps the unique id forever — the
|
|
49110
|
+
* caller lacks `iam:GetRole`, or the ARN names another ACCOUNT / IAM PATH, for
|
|
49111
|
+
* which the name-only `GetRole` API would otherwise answer about the caller's
|
|
49112
|
+
* own same-named role), both sides are left untouched and the drift is still
|
|
49113
|
+
* reported. This pass only ever removes a difference it has PROVEN to be two
|
|
49114
|
+
* spellings of one principal; it can never hide a real principal change.
|
|
49115
|
+
*
|
|
49116
|
+
* One downstream note: because the comparator's inputs are canonicalized,
|
|
49117
|
+
* `cdkd drift --accept` records the ARN form for a policy subtree that drifts
|
|
49118
|
+
* for some OTHER reason while also carrying a canonicalized principal — i.e.
|
|
49119
|
+
* the accepted baseline can hold a spelling AWS did not literally return. That
|
|
49120
|
+
* is benign (it is the form AWS re-canonicalizes to, and a later run
|
|
49121
|
+
* normalizes either way) but it is a real difference from "state records
|
|
49122
|
+
* exactly what AWS said", so it is written down rather than left to be
|
|
49123
|
+
* rediscovered.
|
|
49124
|
+
*
|
|
49125
|
+
* Known bound: a policy carried as a JSON STRING rather than a parsed object is
|
|
49126
|
+
* not walked. Re-serializing a parsed string would risk changing its formatting
|
|
49127
|
+
* and manufacturing a different phantom drift, so the pass is scoped to the
|
|
49128
|
+
* object shape — which is what `AWS::S3::BucketPolicy` and the other
|
|
49129
|
+
* `PolicyDocument`-typed properties carry in cdkd state.
|
|
49130
|
+
*/
|
|
49131
|
+
/**
|
|
49132
|
+
* An IAM unique id as it appears in a rendered policy. The prefix set is
|
|
49133
|
+
* deliberately narrow — `AROA` (role) and `AIDA` (user) are the only two that
|
|
49134
|
+
* can stand in for an ARN at a `Principal.AWS` position; `AGPA` (group),
|
|
49135
|
+
* `AKIA` (access key) and friends are never rendered there.
|
|
49136
|
+
*/
|
|
49137
|
+
const PRINCIPAL_UNIQUE_ID_RE = /^A(?:ROA|IDA)[A-Z0-9]{6,}$/;
|
|
49138
|
+
/** An IAM role / user ARN — the two shapes a unique id can stand in for. */
|
|
49139
|
+
const IAM_PRINCIPAL_ARN_RE = /^arn:[a-z0-9-]+:iam::\d{12}:(role|user)\/(.+)$/;
|
|
49140
|
+
/**
|
|
49141
|
+
* Split an IAM role / user principal ARN into what the lookup API needs, or
|
|
49142
|
+
* `undefined` when the string is not one (a service principal, an
|
|
49143
|
+
* `assumed-role` STS ARN, `*`, …).
|
|
49144
|
+
*
|
|
49145
|
+
* Exported so the resolver in `drift.ts` recognizes exactly the ARNs
|
|
49146
|
+
* {@link collectPrincipalArns} collects — one regex, so the collector and the
|
|
49147
|
+
* lookup cannot drift apart and leave an ARN gathered but never resolvable.
|
|
49148
|
+
*/
|
|
49149
|
+
function parseIamPrincipalArn(arn) {
|
|
49150
|
+
const match = IAM_PRINCIPAL_ARN_RE.exec(arn);
|
|
49151
|
+
if (!match) return void 0;
|
|
49152
|
+
const name = match[2].split("/").pop();
|
|
49153
|
+
if (!name) return void 0;
|
|
49154
|
+
return {
|
|
49155
|
+
kind: match[1],
|
|
49156
|
+
name
|
|
49157
|
+
};
|
|
49158
|
+
}
|
|
49159
|
+
/** Every string at a `Principal` / `NotPrincipal` position, at any depth. */
|
|
49160
|
+
function collectPrincipalStrings(value, out) {
|
|
49161
|
+
if (Array.isArray(value)) {
|
|
49162
|
+
for (const element of value) collectPrincipalStrings(element, out);
|
|
49163
|
+
return;
|
|
49164
|
+
}
|
|
49165
|
+
if (!value || typeof value !== "object") return;
|
|
49166
|
+
for (const [key, member] of Object.entries(value)) {
|
|
49167
|
+
if (key === "Principal" || key === "NotPrincipal") pushPrincipalValue(member, out);
|
|
49168
|
+
collectPrincipalStrings(member, out);
|
|
49169
|
+
}
|
|
49170
|
+
}
|
|
49171
|
+
/**
|
|
49172
|
+
* The strings a `Principal` / `NotPrincipal` value contributes: the bare string
|
|
49173
|
+
* form, and the `AWS` member of the object form (string or array). `Service` /
|
|
49174
|
+
* `Federated` / `CanonicalUser` are deliberately NOT read — none of them can
|
|
49175
|
+
* carry an IAM unique id or a role ARN.
|
|
49176
|
+
*/
|
|
49177
|
+
function pushPrincipalValue(value, out) {
|
|
49178
|
+
if (typeof value === "string") {
|
|
49179
|
+
out.push(value);
|
|
49180
|
+
return;
|
|
49181
|
+
}
|
|
49182
|
+
if (Array.isArray(value)) {
|
|
49183
|
+
for (const element of value) if (typeof element === "string") out.push(element);
|
|
49184
|
+
return;
|
|
49185
|
+
}
|
|
49186
|
+
if (!value || typeof value !== "object") return;
|
|
49187
|
+
const awsMember = value["AWS"];
|
|
49188
|
+
if (typeof awsMember === "string") out.push(awsMember);
|
|
49189
|
+
else if (Array.isArray(awsMember)) {
|
|
49190
|
+
for (const element of awsMember) if (typeof element === "string") out.push(element);
|
|
49191
|
+
}
|
|
49192
|
+
}
|
|
49193
|
+
/**
|
|
49194
|
+
* Both interesting principal kinds in ONE walk — the pass runs per resource on
|
|
49195
|
+
* both comparison sides, so walking the bag twice to answer two questions about
|
|
49196
|
+
* the same strings is pure cost.
|
|
49197
|
+
*/
|
|
49198
|
+
function collectPrincipalForms(value) {
|
|
49199
|
+
const strings = [];
|
|
49200
|
+
collectPrincipalStrings(value, strings);
|
|
49201
|
+
const uniqueIds = /* @__PURE__ */ new Set();
|
|
49202
|
+
const arns = /* @__PURE__ */ new Set();
|
|
49203
|
+
for (const s of strings) if (PRINCIPAL_UNIQUE_ID_RE.test(s)) uniqueIds.add(s);
|
|
49204
|
+
else if (IAM_PRINCIPAL_ARN_RE.test(s)) arns.add(s);
|
|
49205
|
+
return {
|
|
49206
|
+
uniqueIds,
|
|
49207
|
+
arns
|
|
49208
|
+
};
|
|
49209
|
+
}
|
|
49210
|
+
/**
|
|
49211
|
+
* Replace every principal string that is a KNOWN unique id with the ARN it was
|
|
49212
|
+
* proven to stand for. Only `Principal` / `NotPrincipal` positions are touched
|
|
49213
|
+
* — a unique id appearing in, say, a `Condition` value is left alone, where the
|
|
49214
|
+
* two forms are NOT interchangeable.
|
|
49215
|
+
*/
|
|
49216
|
+
function rewritePrincipalUniqueIds(value, arnByUniqueId) {
|
|
49217
|
+
if (Array.isArray(value)) return value.map((el) => rewritePrincipalUniqueIds(el, arnByUniqueId));
|
|
49218
|
+
if (!value || typeof value !== "object") return value;
|
|
49219
|
+
const out = {};
|
|
49220
|
+
for (const [key, member] of Object.entries(value)) out[key] = key === "Principal" || key === "NotPrincipal" ? rewritePrincipalValue(member, arnByUniqueId) : rewritePrincipalUniqueIds(member, arnByUniqueId);
|
|
49221
|
+
return out;
|
|
49222
|
+
}
|
|
49223
|
+
function rewritePrincipalValue(value, arnByUniqueId) {
|
|
49224
|
+
const swap = (s) => typeof s === "string" ? arnByUniqueId.get(s) ?? s : rewritePrincipalUniqueIds(s, arnByUniqueId);
|
|
49225
|
+
if (typeof value === "string") return swap(value);
|
|
49226
|
+
if (Array.isArray(value)) return value.map(swap);
|
|
49227
|
+
if (!value || typeof value !== "object") return value;
|
|
49228
|
+
const out = {};
|
|
49229
|
+
for (const [key, member] of Object.entries(value)) {
|
|
49230
|
+
if (key !== "AWS") {
|
|
49231
|
+
out[key] = rewritePrincipalUniqueIds(member, arnByUniqueId);
|
|
49232
|
+
continue;
|
|
49233
|
+
}
|
|
49234
|
+
out[key] = Array.isArray(member) ? member.map(swap) : swap(member);
|
|
49235
|
+
}
|
|
49236
|
+
return out;
|
|
49237
|
+
}
|
|
49238
|
+
/**
|
|
49239
|
+
* Canonicalize the two comparison sides so that a principal AWS rendered as a
|
|
49240
|
+
* unique id on one side and as an ARN on the other stops reading as drift.
|
|
49241
|
+
*
|
|
49242
|
+
* Short-circuits — with NO resolver call — unless BOTH a unique id and an IAM
|
|
49243
|
+
* ARN are present across the two sides, since a unique id with no candidate ARN
|
|
49244
|
+
* opposite it has nothing to be equal to.
|
|
49245
|
+
*/
|
|
49246
|
+
async function canonicalizePrincipalUniqueIds(baseline, aws, resolve) {
|
|
49247
|
+
const baselineForms = collectPrincipalForms(baseline);
|
|
49248
|
+
const awsForms = collectPrincipalForms(aws);
|
|
49249
|
+
const uniqueIds = /* @__PURE__ */ new Set([...baselineForms.uniqueIds, ...awsForms.uniqueIds]);
|
|
49250
|
+
if (uniqueIds.size === 0) return {
|
|
49251
|
+
baseline,
|
|
49252
|
+
aws
|
|
49253
|
+
};
|
|
49254
|
+
const arns = /* @__PURE__ */ new Set([...baselineForms.arns, ...awsForms.arns]);
|
|
49255
|
+
if (arns.size === 0) return {
|
|
49256
|
+
baseline,
|
|
49257
|
+
aws
|
|
49258
|
+
};
|
|
49259
|
+
const arnByUniqueId = /* @__PURE__ */ new Map();
|
|
49260
|
+
for (const arn of arns) {
|
|
49261
|
+
const uniqueId = await resolve(arn);
|
|
49262
|
+
if (uniqueId !== void 0 && uniqueIds.has(uniqueId)) arnByUniqueId.set(uniqueId, arn);
|
|
49263
|
+
}
|
|
49264
|
+
if (arnByUniqueId.size === 0) return {
|
|
49265
|
+
baseline,
|
|
49266
|
+
aws
|
|
49267
|
+
};
|
|
49268
|
+
return {
|
|
49269
|
+
baseline: rewritePrincipalUniqueIds(baseline, arnByUniqueId),
|
|
49270
|
+
aws: rewritePrincipalUniqueIds(aws, arnByUniqueId)
|
|
49271
|
+
};
|
|
49272
|
+
}
|
|
49273
|
+
|
|
49072
49274
|
//#endregion
|
|
49073
49275
|
//#region src/analyzer/drift-cc-api-deny-list.ts
|
|
49074
49276
|
/**
|
|
@@ -49283,11 +49485,12 @@ async function driftCommand(stacks, options) {
|
|
|
49283
49485
|
registerAllProviders(providerRegistry);
|
|
49284
49486
|
providerRegistry.setCustomResourceResponseBucket(bucket);
|
|
49285
49487
|
const ccApiFallback = new CloudControlProvider();
|
|
49488
|
+
const resolvePrincipalUniqueId = createIamPrincipalUniqueIdResolver(awsClients);
|
|
49286
49489
|
const targetRefs = resolveTargetRefs(stacks, await stateBackend.listStacks(), options);
|
|
49287
49490
|
const reports = [];
|
|
49288
49491
|
for (const ref of targetRefs) {
|
|
49289
49492
|
if (!ref.region) throw new Error(`Stack '${ref.stackName}' has only a legacy state record without a region. Run 'cdkd deploy ${ref.stackName}' (or any cdkd write) to migrate it to the region-scoped layout, then re-run drift detection.`);
|
|
49290
|
-
const report = await runDriftForStack(ref.stackName, ref.region, stateBackend, providerRegistry, ccApiFallback);
|
|
49493
|
+
const report = await runDriftForStack(ref.stackName, ref.region, stateBackend, providerRegistry, ccApiFallback, resolvePrincipalUniqueId);
|
|
49291
49494
|
reports.push(report);
|
|
49292
49495
|
}
|
|
49293
49496
|
if (options.json) writeJsonReport(reports);
|
|
@@ -49349,13 +49552,75 @@ function resolveTargetRefs(stacks, stateRefs, options) {
|
|
|
49349
49552
|
return out;
|
|
49350
49553
|
}
|
|
49351
49554
|
/**
|
|
49555
|
+
* The live half of the #1515 principal canonicalization: resolve an IAM role /
|
|
49556
|
+
* user ARN to that principal's unique id (`AROA…` / `AIDA…`).
|
|
49557
|
+
*
|
|
49558
|
+
* Results are cached per ARN for the whole command — INCLUDING the failures, so
|
|
49559
|
+
* a deleted role (the case where AWS keeps the unique id forever, and therefore
|
|
49560
|
+
* the likeliest one to appear here) costs exactly one API call rather than one
|
|
49561
|
+
* per resource that references it.
|
|
49562
|
+
*
|
|
49563
|
+
* Every failure resolves to `undefined` rather than throwing: a missing
|
|
49564
|
+
* `iam:GetRole` permission, a cross-account principal, or a deleted role must
|
|
49565
|
+
* leave the comparison exactly as it was — the drift is then reported, which is
|
|
49566
|
+
* the safe direction. Drift detection must never fail because a cosmetic
|
|
49567
|
+
* normalization could not run.
|
|
49568
|
+
*
|
|
49569
|
+
* **The response ARN is compared back to the requested one, and that check is
|
|
49570
|
+
* what makes the "never hides a real change" claim true rather than aspirational.**
|
|
49571
|
+
* `GetRole` / `GetUser` are account-local and take a NAME, not an ARN, so the
|
|
49572
|
+
* lookup silently answers about the CALLER's account and about the wrong IAM
|
|
49573
|
+
* path: `arn:aws:iam::<other-acct>:role/Foo` and
|
|
49574
|
+
* `arn:aws:iam::<self>:role/prod/Foo` would both come back as this account's
|
|
49575
|
+
* root-path `Foo`, and the pass would then "prove" two DIFFERENT principals
|
|
49576
|
+
* equal and collapse a genuine drift. Round-tripping the ARN costs nothing and
|
|
49577
|
+
* turns both cases into an unresolved lookup, i.e. drift reported.
|
|
49578
|
+
*/
|
|
49579
|
+
function createIamPrincipalUniqueIdResolver(awsClients) {
|
|
49580
|
+
const cache = /* @__PURE__ */ new Map();
|
|
49581
|
+
let warnedOnDenied = false;
|
|
49582
|
+
return async (arn) => {
|
|
49583
|
+
if (cache.has(arn)) return cache.get(arn);
|
|
49584
|
+
let uniqueId;
|
|
49585
|
+
const principal = parseIamPrincipalArn(arn);
|
|
49586
|
+
if (principal) try {
|
|
49587
|
+
let entityArn;
|
|
49588
|
+
let entityId;
|
|
49589
|
+
if (principal.kind === "role") {
|
|
49590
|
+
const role = (await awsClients.iam.send(new GetRoleCommand({ RoleName: principal.name }))).Role;
|
|
49591
|
+
entityArn = role?.Arn;
|
|
49592
|
+
entityId = role?.RoleId;
|
|
49593
|
+
} else {
|
|
49594
|
+
const user = (await awsClients.iam.send(new GetUserCommand({ UserName: principal.name }))).User;
|
|
49595
|
+
entityArn = user?.Arn;
|
|
49596
|
+
entityId = user?.UserId;
|
|
49597
|
+
}
|
|
49598
|
+
if (entityArn !== void 0 && entityArn.toLowerCase() === arn.toLowerCase()) uniqueId = entityId;
|
|
49599
|
+
else getLogger().debug(`Principal ${arn} resolved to ${entityArn ?? "no entity"} — same name in this account or under another IAM path; leaving the policy principal comparison untouched.`);
|
|
49600
|
+
} catch (error) {
|
|
49601
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49602
|
+
const name = error instanceof Error ? error.name : "";
|
|
49603
|
+
const status = error?.$metadata?.httpStatusCode;
|
|
49604
|
+
const denied = /^AccessDenied|^NotAuthorized/i.test(name) || status === 403;
|
|
49605
|
+
if (!warnedOnDenied && denied) {
|
|
49606
|
+
warnedOnDenied = true;
|
|
49607
|
+
getLogger().warn(`Cannot read IAM principals (${name || "access denied"}). A resource policy whose principal AWS rendered as a unique id (AROA…/AIDA…) may therefore report drift that is only a spelling difference; grant iam:GetRole / iam:GetUser to resolve it, or re-run with --verbose for the full error.`);
|
|
49608
|
+
}
|
|
49609
|
+
getLogger().debug(`Could not resolve the unique id of principal ${arn} (${message}); leaving the policy principal comparison untouched.`);
|
|
49610
|
+
if (isThrottlingError(error)) return void 0;
|
|
49611
|
+
}
|
|
49612
|
+
cache.set(arn, uniqueId);
|
|
49613
|
+
return uniqueId;
|
|
49614
|
+
};
|
|
49615
|
+
}
|
|
49616
|
+
/**
|
|
49352
49617
|
* Run drift detection for one stack and shape the per-resource outcomes
|
|
49353
49618
|
* into a {@link StackDriftReport}. The state object + etag are stored on
|
|
49354
49619
|
* the report so `--accept` can write back without a re-read, and
|
|
49355
49620
|
* `--revert` can pass the captured AWS-current snapshot to
|
|
49356
49621
|
* `provider.update` as the `previousProperties` argument.
|
|
49357
49622
|
*/
|
|
49358
|
-
async function runDriftForStack(stackName, region, stateBackend, providerRegistry, ccApiFallback) {
|
|
49623
|
+
async function runDriftForStack(stackName, region, stateBackend, providerRegistry, ccApiFallback, resolvePrincipalUniqueId) {
|
|
49359
49624
|
const result = await stateBackend.getState(stackName, region);
|
|
49360
49625
|
if (!result) throw new Error(`No state found for stack '${stackName}' (${region}). Run 'cdkd state list' to see available stacks.`);
|
|
49361
49626
|
return await withStackName(stackName, async () => {
|
|
@@ -49421,7 +49686,8 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
|
|
|
49421
49686
|
const useObserved = resource.observedProperties !== void 0;
|
|
49422
49687
|
const baseline = useObserved ? resource.observedProperties : resource.properties ?? {};
|
|
49423
49688
|
const observedIgnorePaths = useObserved ? undeclaredEmptyObservedKeys(resource.observedProperties, resource.properties ?? {}) : [];
|
|
49424
|
-
const
|
|
49689
|
+
const normalized = await canonicalizePrincipalUniqueIds(baseline, aws, resolvePrincipalUniqueId);
|
|
49690
|
+
const changes = calculateResourceDrift(normalized.baseline, normalized.aws, {
|
|
49425
49691
|
ignorePaths: observedIgnorePaths.length ? [...ignorePaths, ...observedIgnorePaths] : ignorePaths,
|
|
49426
49692
|
unionWalkObjects: useObserved,
|
|
49427
49693
|
unorderedPaths
|
|
@@ -66897,7 +67163,7 @@ function createMigrateCommand() {
|
|
|
66897
67163
|
*/
|
|
66898
67164
|
function buildProgram() {
|
|
66899
67165
|
const program = new Command();
|
|
66900
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.
|
|
67166
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.38");
|
|
66901
67167
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
66902
67168
|
const { profile } = actionCommand.optsWithGlobals();
|
|
66903
67169
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|