@go-to-k/cdkd 0.280.33 → 0.280.35
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/README.md +216 -126
- package/dist/{asg-provider-DrM2HEGB.js → asg-provider-CL2zlhEi.js} +2 -2
- package/dist/{asg-provider-DrM2HEGB.js.map → asg-provider-CL2zlhEi.js.map} +1 -1
- package/dist/cli.js +289 -10
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-Dn60Zkq5.js → deploy-engine-DjdMx6fC.js} +3 -3
- package/dist/{deploy-engine-Dn60Zkq5.js.map → deploy-engine-DjdMx6fC.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as requireConfigObject, $t as AssemblyReader, A as green, At as runDockerForeground, B as disableInstanceApiTermination, Bt as resolveCaptureObservedState, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as StackTerminationProtectionError, Ct as getBootstrapMarkerKey, D as bold, Dt as buildDockerImage, E as formatResourceLine, Et as validateContainerRepoName, F as clearOnUpdateRemoval, Ft as synthesisStatusMessage, G as WAFv2WebACLProvider, Gt as stateBucketExistenceConfirmed, H as IntrinsicFunctionResolver, Ht as resolveStateBucketWithDefault, I as ProviderRegistry, It as getDefaultStateBucketName, J as assertRegionMatch, Jt as CFN_TEMPLATE_URL_LIMIT, K as normalizeAwsTagsToCfn, Kt as warnDeprecatedNoPrefixCliFlag, L as findActionableSilentDrops, Lt as getLegacyStateBucketName, M as yellow, Mt as AssetManifestLoader, N as IAMRoleProvider, Nt as getDockerImageBySourceHash, O as cyan, On as normalizeAwsError, Ot as formatDockerLoginError, P as collectInlinePolicyNamesManagedBySiblings, Pt as Synthesizer, Q as requireConfigArray, Qt as expectedOwnerParam, R as CloudControlProvider, Rt as resolveApp, S as extractDeploymentEventError, Sn as StackHasActiveImportsError, St as ensureAssetStorage, T as renderStatefulReason, Tt as validateAssetBucketName, U as cfnRefValueFromPhysicalId, Ut as resolveStateBucketWithDefaultAndSource, V as isTerminationProtectionPropagationError, Vt as resolveSkipPrefix, W as refStateLookupFromResource, Wt as resolveUseCdkBootstrapAssets, X as readConfigString, Xt as findLargeInlineResources, Y as configStringRefusal, Yt as MIGRATE_TMP_PREFIX, Z as replayWarn, Zt as uploadCfnTemplate, _ as createPreDeleteFinalSnapshot, _n as NestedStackChildDirectDestroyError, _t as createAssetRedirectResolver, a as DeploymentEventsStore, an as resetAwsClients, at as isRetryableTransientError, b as unsupportedFinalSnapshotError, bn as ResourceTimeoutError, bt as AssetModeResolver, c as replayFailedOperations, cn as CdkdError, ct as TemplateParser, d as IMPLICIT_DELETE_DEPENDENCIES, dn as DeployCancelledError, dt as rebuildClientForBucketRegion, en as processStackMessages, et as requireConfigString, f as computeImplicitDeleteEdges, fn as LocalInvokeBuildError$1, ft as shouldRetainResource, g as ccRoutedFinalSnapshotError, gn as MissingCdkCliError, gt as buildAssetRedirectMap, h as buildFinalSnapshotIdentifier, ht as WorkGraph, i as DeploymentEventsReader, in as getAwsClients, it as withRetry, j as red, jt as runDockerStreaming, k as gray, kn as withErrorHandling, kt as getDockerCmd, l as replayRollback, lt as LockManager, m as PRE_DELETE_SNAPSHOT_TYPES, mn as LocalStartServiceError, mt as stringifyValue, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resolveBucketRegion, nt as DiffCalculator, o as planFailedOps, on as setAwsClients, ot as isThrottlingError, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalMigrateError, pt as AssetPublisher, q as resolveExplicitPhysicalId, qt as CFN_TEMPLATE_BODY_LIMIT, r as DeployEngine, rn as AwsClients, rt as describeTypeWithThrottleRetry, s as planRollback, st as DagBuilder, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as applyRoleArnIfSet, u as withResourceDeadline, ut as S3StateBackend, v as isFinalSnapshotError, vn as PartialFailureError, vt as loadPublishableAssetManifest, w as isStatefulRecreateTargetSync, wt as parseBootstrapMarker, x as makeCanonicalizePropertiesFn, xn as ResourceUpdateNotSupportedError, xt as BOOTSTRAP_MARKER_PREFIX, y as refusesFinalSnapshot, yn as ProvisioningError, yt as rewriteTemplateAssetReferences, z as slowCcOperationTimeoutMs, zt as resolveAutoAssetStorage } from "./deploy-engine-
|
|
2
|
+
import { $ as requireConfigObject, $t as AssemblyReader, A as green, At as runDockerForeground, B as disableInstanceApiTermination, Bt as resolveCaptureObservedState, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as StackTerminationProtectionError, Ct as getBootstrapMarkerKey, D as bold, Dt as buildDockerImage, E as formatResourceLine, Et as validateContainerRepoName, F as clearOnUpdateRemoval, Ft as synthesisStatusMessage, G as WAFv2WebACLProvider, Gt as stateBucketExistenceConfirmed, H as IntrinsicFunctionResolver, Ht as resolveStateBucketWithDefault, I as ProviderRegistry, It as getDefaultStateBucketName, J as assertRegionMatch, Jt as CFN_TEMPLATE_URL_LIMIT, K as normalizeAwsTagsToCfn, Kt as warnDeprecatedNoPrefixCliFlag, L as findActionableSilentDrops, Lt as getLegacyStateBucketName, M as yellow, Mt as AssetManifestLoader, N as IAMRoleProvider, Nt as getDockerImageBySourceHash, O as cyan, On as normalizeAwsError, Ot as formatDockerLoginError, P as collectInlinePolicyNamesManagedBySiblings, Pt as Synthesizer, Q as requireConfigArray, Qt as expectedOwnerParam, R as CloudControlProvider, Rt as resolveApp, S as extractDeploymentEventError, Sn as StackHasActiveImportsError, St as ensureAssetStorage, T as renderStatefulReason, Tt as validateAssetBucketName, U as cfnRefValueFromPhysicalId, Ut as resolveStateBucketWithDefaultAndSource, V as isTerminationProtectionPropagationError, Vt as resolveSkipPrefix, W as refStateLookupFromResource, Wt as resolveUseCdkBootstrapAssets, X as readConfigString, Xt as findLargeInlineResources, Y as configStringRefusal, Yt as MIGRATE_TMP_PREFIX, Z as replayWarn, Zt as uploadCfnTemplate, _ as createPreDeleteFinalSnapshot, _n as NestedStackChildDirectDestroyError, _t as createAssetRedirectResolver, a as DeploymentEventsStore, an as resetAwsClients, at as isRetryableTransientError, b as unsupportedFinalSnapshotError, bn as ResourceTimeoutError, bt as AssetModeResolver, c as replayFailedOperations, cn as CdkdError, ct as TemplateParser, d as IMPLICIT_DELETE_DEPENDENCIES, dn as DeployCancelledError, dt as rebuildClientForBucketRegion, en as processStackMessages, et as requireConfigString, f as computeImplicitDeleteEdges, fn as LocalInvokeBuildError$1, ft as shouldRetainResource, g as ccRoutedFinalSnapshotError, gn as MissingCdkCliError, gt as buildAssetRedirectMap, h as buildFinalSnapshotIdentifier, ht as WorkGraph, i as DeploymentEventsReader, in as getAwsClients, it as withRetry, j as red, jt as runDockerStreaming, k as gray, kn as withErrorHandling, kt as getDockerCmd, l as replayRollback, lt as LockManager, m as PRE_DELETE_SNAPSHOT_TYPES, mn as LocalStartServiceError, mt as stringifyValue, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resolveBucketRegion, nt as DiffCalculator, o as planFailedOps, on as setAwsClients, ot as isThrottlingError, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalMigrateError, pt as AssetPublisher, q as resolveExplicitPhysicalId, qt as CFN_TEMPLATE_BODY_LIMIT, r as DeployEngine, rn as AwsClients, rt as describeTypeWithThrottleRetry, s as planRollback, st as DagBuilder, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as applyRoleArnIfSet, u as withResourceDeadline, ut as S3StateBackend, v as isFinalSnapshotError, vn as PartialFailureError, vt as loadPublishableAssetManifest, w as isStatefulRecreateTargetSync, wt as parseBootstrapMarker, x as makeCanonicalizePropertiesFn, xn as ResourceUpdateNotSupportedError, xt as BOOTSTRAP_MARKER_PREFIX, y as refusesFinalSnapshot, yn as ProvisioningError, yt as rewriteTemplateAssetReferences, z as slowCcOperationTimeoutMs, zt as resolveAutoAssetStorage } from "./deploy-engine-DjdMx6fC.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-CL2zlhEi.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";
|
|
@@ -18008,6 +18008,95 @@ var EventBridgeBusProvider = class {
|
|
|
18008
18008
|
}
|
|
18009
18009
|
};
|
|
18010
18010
|
|
|
18011
|
+
//#endregion
|
|
18012
|
+
//#region src/utils/ip-protocol.ts
|
|
18013
|
+
/**
|
|
18014
|
+
* EC2 `IpProtocol` value canonicalization (issue
|
|
18015
|
+
* [#1643](https://github.com/go-to-k/cdkd/issues/1643)).
|
|
18016
|
+
*
|
|
18017
|
+
* EC2 owns the spelling of a security-group rule's protocol: it accepts an
|
|
18018
|
+
* IANA protocol NUMBER or a protocol NAME, rewrites the four numbers it has a
|
|
18019
|
+
* name for before storing the rule, and lower-cases a name it is given. So the
|
|
18020
|
+
* value cdkd SENDS and the value AWS REPORTS are routinely two spellings of
|
|
18021
|
+
* ONE protocol.
|
|
18022
|
+
*
|
|
18023
|
+
* This lives in `src/utils/` rather than beside either consumer because it has
|
|
18024
|
+
* TWO, in different layers: `src/analyzer/drift-protocol-normalize.ts` (the
|
|
18025
|
+
* drift comparator) and `src/provisioning/providers/ec2-provider.ts` (rule
|
|
18026
|
+
* IDENTITY — the standalone-rule lookup, the inline-rule reconcile, and the
|
|
18027
|
+
* write-side revoke/authorize diff). Hosting it in the analyzer made the
|
|
18028
|
+
* provider carry the tree's only `src/provisioning/**` -> `src/analyzer/**`
|
|
18029
|
+
* import, which is both a layering break and a real hazard: an edit made for
|
|
18030
|
+
* the drift comparator would silently change which AWS rule a revoke matches.
|
|
18031
|
+
*
|
|
18032
|
+
* ## The rename set is MEASURED, not guessed
|
|
18033
|
+
*
|
|
18034
|
+
* Probed against real AWS (us-east-1, 2026-08-12) by authorizing one rule per
|
|
18035
|
+
* protocol on a scratch security group and reading `IpProtocol` back, for
|
|
18036
|
+
* ingress AND egress:
|
|
18037
|
+
*
|
|
18038
|
+
* - RENAMED: `1` -> `icmp`, `6` -> `tcp`, `17` -> `udp`, `58` -> `icmpv6`.
|
|
18039
|
+
* - NOT renamed: `-1`, `0`, `2`, `4`, `27`, `41`, `47`, `50`, `51`, `88`,
|
|
18040
|
+
* `89`, `94`, `103`, `112`, `132`, `255` — all read back as the number.
|
|
18041
|
+
*
|
|
18042
|
+
* So the rename set is exactly the four protocols EC2 has a NAME for — a
|
|
18043
|
+
* CLOSED set, not the open-ended IANA list the issue worried about. `-1` is
|
|
18044
|
+
* unaffected, which is why #1633's live test came back clean.
|
|
18045
|
+
*
|
|
18046
|
+
* Case is also AWS's to decide: `TCP` / `Tcp` / `tcp` authorized on the same
|
|
18047
|
+
* port collapsed into ONE `tcp` permission in the readback, so a name is
|
|
18048
|
+
* matched case-insensitively and canonicalized to lower case. Only the four
|
|
18049
|
+
* KNOWN names are lowered — an unrecognized string is left exactly as-is
|
|
18050
|
+
* rather than folded, so this can never make two genuinely different values
|
|
18051
|
+
* compare equal.
|
|
18052
|
+
*
|
|
18053
|
+
* Known bound: only the CANONICAL decimal spelling is recognized. A
|
|
18054
|
+
* zero-padded `'06'` or a `'0x6'` misses the table and is returned verbatim,
|
|
18055
|
+
* so such a template still phantom-drifts. Neither form has been observed in
|
|
18056
|
+
* a CDK-synthesized template, and accepting them would mean parsing rather
|
|
18057
|
+
* than table lookup — which is what keeps the "closed, measured set" property
|
|
18058
|
+
* that makes this safe.
|
|
18059
|
+
*/
|
|
18060
|
+
/**
|
|
18061
|
+
* The protocol numbers AWS rewrites to a name, measured (see the module
|
|
18062
|
+
* docstring). Keyed by the number's STRING form, since that is the shape every
|
|
18063
|
+
* input is normalized to first.
|
|
18064
|
+
*/
|
|
18065
|
+
const PROTOCOL_NUMBER_TO_NAME = /* @__PURE__ */ new Map([
|
|
18066
|
+
["1", "icmp"],
|
|
18067
|
+
["6", "tcp"],
|
|
18068
|
+
["17", "udp"],
|
|
18069
|
+
["58", "icmpv6"]
|
|
18070
|
+
]);
|
|
18071
|
+
/** The four names EC2 canonicalizes to, for the case-insensitive match. */
|
|
18072
|
+
const CANONICAL_PROTOCOL_NAMES = new Set(PROTOCOL_NUMBER_TO_NAME.values());
|
|
18073
|
+
/**
|
|
18074
|
+
* Canonicalize ONE `IpProtocol` value the way EC2 does.
|
|
18075
|
+
*
|
|
18076
|
+
* Returns the input unchanged for any shape that is not a protocol value — an
|
|
18077
|
+
* unresolved intrinsic object (`{Ref: ...}`), `null`, a boolean.
|
|
18078
|
+
*
|
|
18079
|
+
* EVERY number is stringified, not only an integer one: an unquoted YAML
|
|
18080
|
+
* `IpProtocol: 47` parses to a number while AWS always reads back a string, and
|
|
18081
|
+
* `ec2-provider.ts`'s rule-identity key has stringified every number since
|
|
18082
|
+
* #1633 specifically so a legacy record holding the NUMBER `-1` still matches
|
|
18083
|
+
* AWS's `'-1'`. Narrowing that to integers would have silently regressed a
|
|
18084
|
+
* malformed `6.5` from `'6.5'` to the number `6.5`, and collapsed `NaN` /
|
|
18085
|
+
* `Infinity` onto one JSON `null` bucket — the exact merging that key exists
|
|
18086
|
+
* to avoid. The rename lookup is a table hit, so a non-integer simply misses it.
|
|
18087
|
+
*/
|
|
18088
|
+
function canonicalizeIpProtocolValue(value) {
|
|
18089
|
+
let text;
|
|
18090
|
+
if (typeof value === "string") text = value;
|
|
18091
|
+
else if (typeof value === "number") text = String(value);
|
|
18092
|
+
else return value;
|
|
18093
|
+
const renamed = PROTOCOL_NUMBER_TO_NAME.get(text);
|
|
18094
|
+
if (renamed !== void 0) return renamed;
|
|
18095
|
+
const lowered = text.toLowerCase();
|
|
18096
|
+
if (CANONICAL_PROTOCOL_NAMES.has(lowered)) return lowered;
|
|
18097
|
+
return text;
|
|
18098
|
+
}
|
|
18099
|
+
|
|
18011
18100
|
//#endregion
|
|
18012
18101
|
//#region src/provisioning/providers/ec2-provider.ts
|
|
18013
18102
|
/**
|
|
@@ -18113,6 +18202,49 @@ function narrowIngressIpProtocol(properties, onUnusable) {
|
|
|
18113
18202
|
};
|
|
18114
18203
|
}
|
|
18115
18204
|
/**
|
|
18205
|
+
* Fold a rule bag's `IpProtocol` to the spelling AWS will hold (issue #1648).
|
|
18206
|
+
*
|
|
18207
|
+
* DIFF-side only — see the call site in `canonicalizeDesiredProperties` for why
|
|
18208
|
+
* this is not done inside `narrowIngressIpProtocol`, which also feeds the wire.
|
|
18209
|
+
* Returns the original object when nothing changes, so an ordinary rule
|
|
18210
|
+
* compares byte-for-byte as before.
|
|
18211
|
+
*/
|
|
18212
|
+
function canonicalizeSgRuleProtocol(rule) {
|
|
18213
|
+
if (!("IpProtocol" in rule)) return rule;
|
|
18214
|
+
const next = canonicalizeIpProtocolValue(rule["IpProtocol"]);
|
|
18215
|
+
if (next === rule["IpProtocol"]) return rule;
|
|
18216
|
+
return {
|
|
18217
|
+
...rule,
|
|
18218
|
+
IpProtocol: next
|
|
18219
|
+
};
|
|
18220
|
+
}
|
|
18221
|
+
/**
|
|
18222
|
+
* The `AWS::EC2::SecurityGroup` inline-rule twin of
|
|
18223
|
+
* {@link canonicalizeSgRuleProtocol}: fold every element of the two rule lists.
|
|
18224
|
+
* Identity-returns when nothing changes, and leaves a non-array list or a
|
|
18225
|
+
* non-object element alone so a malformed template still reaches AWS's own
|
|
18226
|
+
* validation rather than being reshaped here.
|
|
18227
|
+
*/
|
|
18228
|
+
function canonicalizeSgInlineRuleProtocols(properties) {
|
|
18229
|
+
let out = properties;
|
|
18230
|
+
for (const key of ["SecurityGroupIngress", "SecurityGroupEgress"]) {
|
|
18231
|
+
const list = out[key];
|
|
18232
|
+
if (!Array.isArray(list)) continue;
|
|
18233
|
+
let changed = false;
|
|
18234
|
+
const mapped = list.map((el) => {
|
|
18235
|
+
if (el === null || typeof el !== "object" || Array.isArray(el)) return el;
|
|
18236
|
+
const next = canonicalizeSgRuleProtocol(el);
|
|
18237
|
+
if (next !== el) changed = true;
|
|
18238
|
+
return next;
|
|
18239
|
+
});
|
|
18240
|
+
if (changed) out = {
|
|
18241
|
+
...out,
|
|
18242
|
+
[key]: mapped
|
|
18243
|
+
};
|
|
18244
|
+
}
|
|
18245
|
+
return out;
|
|
18246
|
+
}
|
|
18247
|
+
/**
|
|
18116
18248
|
* AWS EC2 Networking Provider
|
|
18117
18249
|
*
|
|
18118
18250
|
* Implements resource provisioning for EC2 networking resources:
|
|
@@ -19906,7 +20038,7 @@ var EC2Provider = class {
|
|
|
19906
20038
|
const prefixKey = direction === "egress" ? rule["DestinationPrefixListId"] : rule["SourcePrefixListId"];
|
|
19907
20039
|
const peerOwner = direction === "ingress" ? rule["SourceSecurityGroupOwnerId"] : void 0;
|
|
19908
20040
|
return JSON.stringify({
|
|
19909
|
-
p: rule["IpProtocol"]
|
|
20041
|
+
p: sgProtocolKey(rule["IpProtocol"]),
|
|
19910
20042
|
f: rule["FromPort"] ?? null,
|
|
19911
20043
|
t: rule["ToPort"] ?? null,
|
|
19912
20044
|
c4: rule["CidrIp"] ?? null,
|
|
@@ -20293,7 +20425,11 @@ var EC2Provider = class {
|
|
|
20293
20425
|
* `AllocationId`, `ConnectivityType`, `PrivateIpAddress`.
|
|
20294
20426
|
* - **AWS::EC2::RouteTable**: `DescribeRouteTables` for `VpcId`.
|
|
20295
20427
|
* - **AWS::EC2::SecurityGroup**: `DescribeSecurityGroups` for
|
|
20296
|
-
* `GroupName`, `GroupDescription`, `VpcId`,
|
|
20428
|
+
* `GroupName`, `GroupDescription`, `VpcId`, `Tags` (issue #1649 — the
|
|
20429
|
+
* same call returns them in the CFn `[{Key,Value}]` shape, so they are
|
|
20430
|
+
* read rather than declared unreadable the way every sibling standalone
|
|
20431
|
+
* type declares them; reserved `aws:`-prefixed entries are stripped and
|
|
20432
|
+
* the key is omitted entirely when AWS reports none), plus `SecurityGroupIngress`
|
|
20297
20433
|
* and `SecurityGroupEgress` reverse-mapped from AWS's normalized
|
|
20298
20434
|
* `IpPermissions[]` / `IpPermissionsEgress[]` form. Each AWS
|
|
20299
20435
|
* `IpPermission` is flattened into one CFn rule per `IpRanges` /
|
|
@@ -20445,8 +20581,9 @@ var EC2Provider = class {
|
|
|
20445
20581
|
canonicalizeDesiredProperties(resourceType, properties) {
|
|
20446
20582
|
if (resourceType === "AWS::EC2::SecurityGroupIngress") {
|
|
20447
20583
|
const { narrowed } = narrowIngressIpProtocol(properties, () => {});
|
|
20448
|
-
return narrowed;
|
|
20584
|
+
return canonicalizeSgRuleProtocol(narrowed);
|
|
20449
20585
|
}
|
|
20586
|
+
if (resourceType === "AWS::EC2::SecurityGroup") return canonicalizeSgInlineRuleProtocols(properties);
|
|
20450
20587
|
if (resourceType !== "AWS::EC2::Route") return properties;
|
|
20451
20588
|
const { declared, narrowed } = narrowRouteDestinations(properties);
|
|
20452
20589
|
return declared.length > 1 ? narrowed : properties;
|
|
@@ -20516,6 +20653,8 @@ var EC2Provider = class {
|
|
|
20516
20653
|
if (sg.GroupName !== void 0) result["GroupName"] = sg.GroupName;
|
|
20517
20654
|
if (sg.Description !== void 0) result["GroupDescription"] = sg.Description;
|
|
20518
20655
|
if (sg.VpcId !== void 0) result["VpcId"] = sg.VpcId;
|
|
20656
|
+
const tags = normalizeAwsTagsToCfn(sg.Tags);
|
|
20657
|
+
if (tags.length > 0) result["Tags"] = tags;
|
|
20519
20658
|
const stateIngress = Array.isArray(properties?.["SecurityGroupIngress"]) ? properties["SecurityGroupIngress"] : void 0;
|
|
20520
20659
|
result["SecurityGroupIngress"] = reconcileSgRules(flattenIpPermissions(sg.IpPermissions ?? [], "ingress"), stateIngress, "ingress");
|
|
20521
20660
|
const stateEgress = Array.isArray(properties?.["SecurityGroupEgress"]) ? properties["SecurityGroupEgress"] : void 0;
|
|
@@ -20725,7 +20864,8 @@ var EC2Provider = class {
|
|
|
20725
20864
|
const toPort = parts[3] === "-1" ? void 0 : parseInt(parts[3], 10);
|
|
20726
20865
|
const sg = (await this.ec2Client.send(new DescribeSecurityGroupsCommand({ GroupIds: [groupId] }))).SecurityGroups?.[0];
|
|
20727
20866
|
if (!sg) return void 0;
|
|
20728
|
-
const
|
|
20867
|
+
const wantedProtocol = sgProtocolKey(protocol);
|
|
20868
|
+
const candidates = (sg.IpPermissions ?? []).filter((p) => sgProtocolKey(p.IpProtocol) === wantedProtocol && (p.FromPort ?? void 0) === fromPort && (p.ToPort ?? void 0) === toPort);
|
|
20729
20869
|
if (candidates.length === 0) return void 0;
|
|
20730
20870
|
const flat = flattenIpPermissions(candidates, "ingress");
|
|
20731
20871
|
if (properties && Object.keys(properties).length > 0) {
|
|
@@ -20946,8 +21086,7 @@ function flattenIpPermissions(perms, direction) {
|
|
|
20946
21086
|
* non-match honest instead of manufacturing a fake identity.
|
|
20947
21087
|
*/
|
|
20948
21088
|
function sgProtocolKey(value) {
|
|
20949
|
-
|
|
20950
|
-
return value ?? "-1";
|
|
21089
|
+
return canonicalizeIpProtocolValue(value ?? "-1");
|
|
20951
21090
|
}
|
|
20952
21091
|
function sgRuleKey(rule, direction) {
|
|
20953
21092
|
const peerKey = direction === "egress" ? rule["DestinationSecurityGroupId"] : rule["SourceSecurityGroupId"];
|
|
@@ -52818,6 +52957,145 @@ async function canonicalizePrincipalUniqueIds(baseline, aws, resolve) {
|
|
|
52818
52957
|
};
|
|
52819
52958
|
}
|
|
52820
52959
|
|
|
52960
|
+
//#endregion
|
|
52961
|
+
//#region src/analyzer/drift-protocol-normalize.ts
|
|
52962
|
+
/**
|
|
52963
|
+
* EC2 security-group `IpProtocol` VALUE canonicalization for the drift
|
|
52964
|
+
* comparator (issue
|
|
52965
|
+
* [#1643](https://github.com/go-to-k/cdkd/issues/1643)) — a sibling of
|
|
52966
|
+
* `drift-normalize.ts` for a difference that is not about ORDER, in the same
|
|
52967
|
+
* spirit as `drift-principal-normalize.ts`.
|
|
52968
|
+
*
|
|
52969
|
+
* EC2 owns the spelling of `IpProtocol`: it accepts an IANA protocol NUMBER or
|
|
52970
|
+
* a protocol NAME, and rewrites a subset of the numbers to their canonical
|
|
52971
|
+
* name before storing the rule. cdkd records what it SENT (the template's
|
|
52972
|
+
* value, post-#1633), AWS holds the canonical name, and
|
|
52973
|
+
* `readSecurityGroupIngressCurrentState` reads the name back — so the two
|
|
52974
|
+
* sides are two spellings of ONE protocol and every `cdkd drift` run reports
|
|
52975
|
+
* the difference forever. `drift --revert` cannot clear it either: it revokes
|
|
52976
|
+
* and re-authorizes into the same state, which is the permanent phantom drift
|
|
52977
|
+
* the #1591 / #1633 fixes address for their own arms.
|
|
52978
|
+
*
|
|
52979
|
+
* Unlike the send-side narrowing #1633 does, this is a READBACK-normalization
|
|
52980
|
+
* concern, so it lives here and runs on BOTH comparison sides. That placement
|
|
52981
|
+
* is load-bearing for two reasons: the baseline may be the user's TEMPLATE
|
|
52982
|
+
* (a resource deployed before observed-capture), where the value is whatever
|
|
52983
|
+
* they wrote; and it covers the inline
|
|
52984
|
+
* `AWS::EC2::SecurityGroup.SecurityGroupIngress[]` / `SecurityGroupEgress[]`
|
|
52985
|
+
* rules, which #1633 deliberately does not touch.
|
|
52986
|
+
*
|
|
52987
|
+
* ## The rename set is MEASURED, not guessed
|
|
52988
|
+
*
|
|
52989
|
+
* Probed against real AWS (us-east-1, 2026-08-12) by authorizing one rule per
|
|
52990
|
+
* protocol on a scratch security group and reading `IpProtocol` back, for
|
|
52991
|
+
* ingress AND egress:
|
|
52992
|
+
*
|
|
52993
|
+
* - RENAMED: `1` -> `icmp`, `6` -> `tcp`, `17` -> `udp`, `58` -> `icmpv6`.
|
|
52994
|
+
* - NOT renamed: `-1`, `0`, `2`, `4`, `27`, `41`, `47`, `50`, `51`, `88`,
|
|
52995
|
+
* `89`, `94`, `103`, `112`, `132`, `255` — all read back as the number.
|
|
52996
|
+
*
|
|
52997
|
+
* So the rename set is exactly the four protocols EC2 has a NAME for, and it
|
|
52998
|
+
* is a CLOSED set rather than the open-ended IANA list the issue worried
|
|
52999
|
+
* about. `-1` is unaffected, which is why #1633's live test came back clean.
|
|
53000
|
+
*
|
|
53001
|
+
* The measured rename table, the case-folding rule and their bounds live with
|
|
53002
|
+
* the shared value canonicalizer in `src/utils/ip-protocol.ts` — this module
|
|
53003
|
+
* owns only WHERE in a resource's properties that canonicalization applies.
|
|
53004
|
+
*
|
|
53005
|
+
* **This pass matters for the `properties`-fallback baseline, not for a fresh
|
|
53006
|
+
* deploy.** A resource deployed by a current binary captures
|
|
53007
|
+
* `observedProperties` from the same `readCurrentState` the drift run uses, so
|
|
53008
|
+
* both sides already hold AWS's spelling and this is a no-op. The population it
|
|
53009
|
+
* is FOR is the one whose baseline is the user's TEMPLATE: state written before
|
|
53010
|
+
* observed-capture existed, or a provider that captures none. That is also why
|
|
53011
|
+
* the integ fixture has to strip `observedProperties` to exercise it — the
|
|
53012
|
+
* first version of this change asserted a fresh deploy would fail without the
|
|
53013
|
+
* module, and three independent reviews showed it would not.
|
|
53014
|
+
*/
|
|
53015
|
+
/**
|
|
53016
|
+
* The resource types carrying an `IpProtocol` this pass rewrites, mapped to
|
|
53017
|
+
* the property path(s) it lives at. The standalone rule types carry it at the
|
|
53018
|
+
* top level; `AWS::EC2::SecurityGroup` carries it inside each element of its
|
|
53019
|
+
* two inline rule arrays.
|
|
53020
|
+
*
|
|
53021
|
+
* Deliberately a CLOSED table rather than a walk for any key named
|
|
53022
|
+
* `IpProtocol`: a blanket rewrite would turn an unrelated property whose value
|
|
53023
|
+
* happens to be `'6'` into `'tcp'`.
|
|
53024
|
+
*/
|
|
53025
|
+
const IP_PROTOCOL_PATHS = /* @__PURE__ */ new Map([
|
|
53026
|
+
["AWS::EC2::SecurityGroupIngress", ["IpProtocol"]],
|
|
53027
|
+
["AWS::EC2::SecurityGroupEgress", ["IpProtocol"]],
|
|
53028
|
+
["AWS::EC2::SecurityGroup", ["SecurityGroupIngress[].IpProtocol", "SecurityGroupEgress[].IpProtocol"]]
|
|
53029
|
+
]);
|
|
53030
|
+
/**
|
|
53031
|
+
* Rewrite the `IpProtocol` at `path` inside `properties`, returning a copy
|
|
53032
|
+
* when anything changed and the original object otherwise.
|
|
53033
|
+
*
|
|
53034
|
+
* `path` is either a plain key (`'IpProtocol'`) or an array-crossing form
|
|
53035
|
+
* (`'SecurityGroupIngress[].IpProtocol'`) whose left half names an array
|
|
53036
|
+
* whose every element carries the key.
|
|
53037
|
+
*/
|
|
53038
|
+
function rewriteAtPath(properties, path) {
|
|
53039
|
+
if (properties === null || typeof properties !== "object" || Array.isArray(properties)) return properties;
|
|
53040
|
+
const bag = properties;
|
|
53041
|
+
const arrayMarker = path.indexOf("[].");
|
|
53042
|
+
if (arrayMarker === -1) {
|
|
53043
|
+
if (!(path in bag)) return properties;
|
|
53044
|
+
const next = canonicalizeIpProtocolValue(bag[path]);
|
|
53045
|
+
if (next === bag[path]) return properties;
|
|
53046
|
+
return {
|
|
53047
|
+
...bag,
|
|
53048
|
+
[path]: next
|
|
53049
|
+
};
|
|
53050
|
+
}
|
|
53051
|
+
const arrayKey = path.slice(0, arrayMarker);
|
|
53052
|
+
const elementKey = path.slice(arrayMarker + 3);
|
|
53053
|
+
const list = bag[arrayKey];
|
|
53054
|
+
if (!Array.isArray(list)) return properties;
|
|
53055
|
+
let changed = false;
|
|
53056
|
+
const mapped = list.map((element) => {
|
|
53057
|
+
if (element === null || typeof element !== "object" || Array.isArray(element)) return element;
|
|
53058
|
+
const rule = element;
|
|
53059
|
+
if (!(elementKey in rule)) return element;
|
|
53060
|
+
const next = canonicalizeIpProtocolValue(rule[elementKey]);
|
|
53061
|
+
if (next === rule[elementKey]) return element;
|
|
53062
|
+
changed = true;
|
|
53063
|
+
return {
|
|
53064
|
+
...rule,
|
|
53065
|
+
[elementKey]: next
|
|
53066
|
+
};
|
|
53067
|
+
});
|
|
53068
|
+
if (!changed) return properties;
|
|
53069
|
+
return {
|
|
53070
|
+
...bag,
|
|
53071
|
+
[arrayKey]: mapped
|
|
53072
|
+
};
|
|
53073
|
+
}
|
|
53074
|
+
/**
|
|
53075
|
+
* Canonicalize every `IpProtocol` on BOTH drift-comparison sides for the
|
|
53076
|
+
* security-group resource types that carry one.
|
|
53077
|
+
*
|
|
53078
|
+
* A resource type with no `IpProtocol` is returned untouched, so the caller
|
|
53079
|
+
* can invoke this unconditionally.
|
|
53080
|
+
*/
|
|
53081
|
+
function canonicalizeIpProtocols(baseline, aws, resourceType) {
|
|
53082
|
+
const paths = IP_PROTOCOL_PATHS.get(resourceType);
|
|
53083
|
+
if (paths === void 0) return {
|
|
53084
|
+
baseline,
|
|
53085
|
+
aws
|
|
53086
|
+
};
|
|
53087
|
+
let nextBaseline = baseline;
|
|
53088
|
+
let nextAws = aws;
|
|
53089
|
+
for (const path of paths) {
|
|
53090
|
+
nextBaseline = rewriteAtPath(nextBaseline, path);
|
|
53091
|
+
nextAws = rewriteAtPath(nextAws, path);
|
|
53092
|
+
}
|
|
53093
|
+
return {
|
|
53094
|
+
baseline: nextBaseline,
|
|
53095
|
+
aws: nextAws
|
|
53096
|
+
};
|
|
53097
|
+
}
|
|
53098
|
+
|
|
52821
53099
|
//#endregion
|
|
52822
53100
|
//#region src/analyzer/drift-cc-api-deny-list.ts
|
|
52823
53101
|
/**
|
|
@@ -53234,7 +53512,8 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
|
|
|
53234
53512
|
const baseline = useObserved ? resource.observedProperties : resource.properties ?? {};
|
|
53235
53513
|
const observedIgnorePaths = useObserved ? undeclaredEmptyObservedKeys(resource.observedProperties, resource.properties ?? {}) : [];
|
|
53236
53514
|
const normalized = await canonicalizePrincipalUniqueIds(baseline, aws, resolvePrincipalUniqueId);
|
|
53237
|
-
const
|
|
53515
|
+
const protocolNormalized = canonicalizeIpProtocols(normalized.baseline, normalized.aws, resource.resourceType);
|
|
53516
|
+
const changes = calculateResourceDrift(protocolNormalized.baseline, protocolNormalized.aws, {
|
|
53238
53517
|
ignorePaths: observedIgnorePaths.length ? [...ignorePaths, ...observedIgnorePaths] : ignorePaths,
|
|
53239
53518
|
unionWalkObjects: useObserved,
|
|
53240
53519
|
unorderedPaths
|
|
@@ -70984,7 +71263,7 @@ function createMigrateCommand() {
|
|
|
70984
71263
|
*/
|
|
70985
71264
|
function buildProgram() {
|
|
70986
71265
|
const program = new Command();
|
|
70987
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.
|
|
71266
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.35");
|
|
70988
71267
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
70989
71268
|
const { profile } = actionCommand.optsWithGlobals();
|
|
70990
71269
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|