@go-to-k/cdkd 0.154.0 → 0.156.0
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/cli.js +548 -8
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-Yb3E5e9J.js → deploy-engine-YQwoPaCE.js} +2046 -6
- package/dist/deploy-engine-YQwoPaCE.js.map +1 -0
- package/dist/{docker-cmd-EtWSTAje.js → docker-cmd-iDMcWcre.js} +7 -1
- package/dist/docker-cmd-iDMcWcre.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/deploy-engine-Yb3E5e9J.js.map +0 -1
- package/dist/docker-cmd-EtWSTAje.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as withSkipPrefix, a as runDockerStreaming, c as getLogger, d as getLiveRenderer, f as PATTERN_B_NAME_PROPERTIES, g as generateResourceNameWithFallback, h as generateResourceName, i as runDockerForeground, n as formatDockerLoginError, p as PATTERN_B_RESOURCE_TYPES, r as getDockerCmd, u as runStackBuffered, v as withStackName } from "./docker-cmd-
|
|
3
|
-
import { $ as CdkdError, A as shouldRetainResource, B as resolveSkipPrefix, C as IntrinsicFunctionResolver, D as TemplateParser, E as DagBuilder, F as Synthesizer, G as CFN_TEMPLATE_URL_LIMIT, H as resolveStateBucketWithDefaultAndSource, I as getDefaultStateBucketName, J as uploadCfnTemplate, K as MIGRATE_TMP_PREFIX, L as getLegacyStateBucketName, M as stringifyValue, N as WorkGraph, O as LockManager, P as buildDockerImage, R as resolveApp, S as assertRegionMatch, T as DiffCalculator, U as warnDeprecatedNoPrefixCliFlag, V as resolveStateBucketWithDefault, W as CFN_TEMPLATE_BODY_LIMIT, Y as AssemblyReader, Z as resolveBucketRegion, _ as matchesCdkPath, a as withRetry, b as ProviderRegistry, bt as withErrorHandling, c as bold, ct as PartialFailureError, d as green, dt as ResourceUpdateNotSupportedError, f as red, ft as RouteDiscoveryError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, i as withResourceDeadline, it as LocalStartServiceError, j as AssetPublisher, k as S3StateBackend, l as cyan, lt as ProvisioningError, m as IAMRoleProvider, mt as StackTerminationProtectionError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as LocalInvokeBuildError, o as IMPLICIT_DELETE_DEPENDENCIES, ot as MissingCdkCliError, p as yellow, pt as StackHasActiveImportsError, q as findLargeInlineResources, r as DeployEngine, rt as LocalMigrateError, s as formatResourceLine, st as NestedStackChildDirectDestroyError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ResourceTimeoutError, v as normalizeAwsTagsToCfn, w as applyRoleArnIfSet, x as CloudControlProvider, y as resolveExplicitPhysicalId, yt as normalizeAwsError, z as resolveCaptureObservedState } from "./deploy-engine-
|
|
2
|
+
import { _ as withSkipPrefix, a as runDockerStreaming, c as getLogger, d as getLiveRenderer, f as PATTERN_B_NAME_PROPERTIES, g as generateResourceNameWithFallback, h as generateResourceName, i as runDockerForeground, n as formatDockerLoginError, p as PATTERN_B_RESOURCE_TYPES, r as getDockerCmd, u as runStackBuffered, v as withStackName } from "./docker-cmd-iDMcWcre.js";
|
|
3
|
+
import { $ as CdkdError, A as shouldRetainResource, B as resolveSkipPrefix, C as IntrinsicFunctionResolver, D as TemplateParser, E as DagBuilder, F as Synthesizer, G as CFN_TEMPLATE_URL_LIMIT, H as resolveStateBucketWithDefaultAndSource, I as getDefaultStateBucketName, J as uploadCfnTemplate, K as MIGRATE_TMP_PREFIX, L as getLegacyStateBucketName, M as stringifyValue, N as WorkGraph, O as LockManager, P as buildDockerImage, R as resolveApp, S as assertRegionMatch, T as DiffCalculator, U as warnDeprecatedNoPrefixCliFlag, V as resolveStateBucketWithDefault, W as CFN_TEMPLATE_BODY_LIMIT, Y as AssemblyReader, Z as resolveBucketRegion, _ as matchesCdkPath, a as withRetry, b as ProviderRegistry, bt as withErrorHandling, c as bold, ct as PartialFailureError, d as green, dt as ResourceUpdateNotSupportedError, f as red, ft as RouteDiscoveryError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, i as withResourceDeadline, it as LocalStartServiceError, j as AssetPublisher, k as S3StateBackend, l as cyan, lt as ProvisioningError, m as IAMRoleProvider, mt as StackTerminationProtectionError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as LocalInvokeBuildError, o as IMPLICIT_DELETE_DEPENDENCIES, ot as MissingCdkCliError, p as yellow, pt as StackHasActiveImportsError, q as findLargeInlineResources, r as DeployEngine, rt as LocalMigrateError, s as formatResourceLine, st as NestedStackChildDirectDestroyError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ResourceTimeoutError, v as normalizeAwsTagsToCfn, w as applyRoleArnIfSet, x as CloudControlProvider, y as resolveExplicitPhysicalId, yt as normalizeAwsError, z as resolveCaptureObservedState } from "./deploy-engine-YQwoPaCE.js";
|
|
4
4
|
import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-BF03Alpe.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, createHmac, createPublicKey, createVerify, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListBucketsCommand, ListDirectoryBucketsCommand, 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, AttachUserPolicyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachUserPolicyCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListInstanceProfilesCommand, ListUserPoliciesCommand, ListUserTagsCommand, ListUsersCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagUserCommand, UntagUserCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
|
|
8
|
+
import { AddRoleToInstanceProfileCommand, AddUserToGroupCommand, AttachGroupPolicyCommand, AttachRolePolicyCommand, AttachUserPolicyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreatePolicyCommand, CreatePolicyVersionCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeletePolicyCommand, DeletePolicyVersionCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachRolePolicyCommand, DetachUserPolicyCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetPolicyCommand, GetPolicyVersionCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListEntitiesForPolicyCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListInstanceProfilesCommand, ListPoliciesCommand, ListPolicyTagsCommand, ListPolicyVersionsCommand, ListUserPoliciesCommand, ListUserTagsCommand, ListUsersCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagPolicyCommand, TagUserCommand, UntagPolicyCommand, UntagUserCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
|
|
9
9
|
import { CreateQueueCommand, DeleteQueueCommand, GetQueueAttributesCommand, GetQueueUrlCommand, ListQueueTagsCommand, ListQueuesCommand, QueueDoesNotExist, SQSClient, SetQueueAttributesCommand, TagQueueCommand, UntagQueueCommand } from "@aws-sdk/client-sqs";
|
|
10
10
|
import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesCommand, GetTopicAttributesCommand, ListTagsForResourceCommand, ListTopicsCommand, NotFoundException, SNSClient, SetTopicAttributesCommand, SubscribeCommand, TagResourceCommand, UnsubscribeCommand, UntagResourceCommand } from "@aws-sdk/client-sns";
|
|
11
|
-
import { AddPermissionCommand, CreateEventSourceMappingCommand, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteEventSourceMappingCommand, DeleteFunctionCommand, DeleteFunctionUrlConfigCommand, DeleteLayerVersionCommand, GetEventSourceMappingCommand, GetFunctionCommand, GetFunctionUrlConfigCommand, GetLayerVersionByArnCommand, GetPolicyCommand, LambdaClient, ListFunctionsCommand, ListLayersCommand, ListTagsCommand, PublishLayerVersionCommand, RemovePermissionCommand, ResourceNotFoundException, TagResourceCommand as TagResourceCommand$1, UntagResourceCommand as UntagResourceCommand$1, UpdateEventSourceMappingCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
|
|
11
|
+
import { AddPermissionCommand, CreateEventSourceMappingCommand, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteEventSourceMappingCommand, DeleteFunctionCommand, DeleteFunctionUrlConfigCommand, DeleteLayerVersionCommand, GetEventSourceMappingCommand, GetFunctionCommand, GetFunctionUrlConfigCommand, GetLayerVersionByArnCommand, GetPolicyCommand as GetPolicyCommand$1, LambdaClient, ListFunctionsCommand, ListLayersCommand, ListTagsCommand, PublishLayerVersionCommand, RemovePermissionCommand, ResourceNotFoundException, TagResourceCommand as TagResourceCommand$1, UntagResourceCommand as UntagResourceCommand$1, UpdateEventSourceMappingCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
|
|
12
12
|
import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
13
13
|
import { AssociateRouteTableCommand, AttachInternetGatewayCommand, AuthorizeSecurityGroupEgressCommand, AuthorizeSecurityGroupIngressCommand, CreateInternetGatewayCommand, CreateNatGatewayCommand, CreateNetworkAclCommand, CreateNetworkAclEntryCommand, CreateRouteCommand, CreateRouteTableCommand, CreateSecurityGroupCommand, CreateSubnetCommand, CreateTagsCommand, CreateVpcCommand, DeleteInternetGatewayCommand, DeleteNatGatewayCommand, DeleteNetworkAclCommand, DeleteNetworkAclEntryCommand, DeleteNetworkInterfaceCommand, DeleteRouteCommand, DeleteRouteTableCommand, DeleteSecurityGroupCommand, DeleteSubnetCommand, DeleteTagsCommand, DeleteVpcCommand, DescribeAvailabilityZonesCommand, DescribeInstanceAttributeCommand, DescribeInstancesCommand, DescribeInternetGatewaysCommand, DescribeNatGatewaysCommand, DescribeNetworkAclsCommand, DescribeNetworkInterfacesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVolumesCommand, DescribeVpcAttributeCommand, DescribeVpcsCommand, DetachInternetGatewayCommand, DisassociateRouteTableCommand, EC2Client, ModifyInstanceAttributeCommand, ModifySubnetAttributeCommand, ModifyVpcAttributeCommand, ReplaceNetworkAclAssociationCommand, RevokeSecurityGroupEgressCommand, RevokeSecurityGroupIngressCommand, RunInstancesCommand, TerminateInstancesCommand, waitUntilInstanceRunning, waitUntilInstanceTerminated, waitUntilNatGatewayAvailable, waitUntilNatGatewayDeleted } from "@aws-sdk/client-ec2";
|
|
14
14
|
import { CreateTableCommand, DeleteTableCommand, DescribeContinuousBackupsCommand, DescribeContributorInsightsCommand, DescribeKinesisStreamingDestinationCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DynamoDBClient, ListTablesCommand, ListTagsOfResourceCommand, ResourceNotFoundException as ResourceNotFoundException$1, TagResourceCommand as TagResourceCommand$2, UntagResourceCommand as UntagResourceCommand$2, UpdateTableCommand, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
|
|
@@ -395,6 +395,33 @@ function parseAllowUnsupportedTypesToken(value, previous) {
|
|
|
395
395
|
return [...previous ?? [], ...parsed];
|
|
396
396
|
}
|
|
397
397
|
const allowUnsupportedTypesOption = new Option("--allow-unsupported-types <types>", "Comma-separated resource types to attempt via Cloud Control even though cdkd reports them unsupported (AWS NON_PROVISIONABLE). Escape hatch — Cloud Control will likely still fail. Example: --allow-unsupported-types AWS::Foo::Bar,AWS::Baz::Qux").argParser(parseAllowUnsupportedTypesToken);
|
|
398
|
+
/**
|
|
399
|
+
* Escape hatch for the property-level silent-drop pre-flight reject.
|
|
400
|
+
* Comma-separated (and repeatable) `<ResourceType>:<PropertyName>` tokens
|
|
401
|
+
* the user explicitly accepts as silently dropped at deploy time. Per
|
|
402
|
+
* type+property pair (not blanket) so each silent drop is acknowledged
|
|
403
|
+
* by name.
|
|
404
|
+
*
|
|
405
|
+
* Format-checks each token against `<Namespace>::<Service>::<Type>:<Prop>`
|
|
406
|
+
* with both halves PascalCase, so a typo aborts at parse time instead of
|
|
407
|
+
* being silently added to the allowlist with no effect.
|
|
408
|
+
*
|
|
409
|
+
* The check is Tier-1-only by design (Cloud Control forwards every property
|
|
410
|
+
* to AWS, so there is no write-side silent drop for Tier 2 / Custom). A
|
|
411
|
+
* `Custom::Foo:Bar` token is therefore always a user mistake — it would be
|
|
412
|
+
* added to the allowlist but never consulted at runtime. Reject it at parse
|
|
413
|
+
* time so the user sees the error immediately.
|
|
414
|
+
*/
|
|
415
|
+
const RESOURCE_PROPERTY_FORMAT = /^[A-Z][A-Za-z0-9]+(::[A-Z][A-Za-z0-9]+)+:[A-Z][A-Za-z0-9]*$/;
|
|
416
|
+
function parseAllowUnsupportedPropertiesToken(value, previous) {
|
|
417
|
+
const parsed = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
418
|
+
for (const token of parsed) {
|
|
419
|
+
if (!RESOURCE_PROPERTY_FORMAT.test(token)) throw new Error(`Invalid --allow-unsupported-properties value "${token}": expected <ResourceType>:<PropertyName> with PascalCase on both halves (e.g. AWS::Lambda::Function:LoggingConfig).`);
|
|
420
|
+
if (token.startsWith("Custom::")) throw new Error(`Invalid --allow-unsupported-properties value "${token}": Custom:: resources are routed through cfn-response and have no write-side silent drop at cdkd, so the flag would have no effect. Use --allow-unsupported-types for type-level escape hatches instead.`);
|
|
421
|
+
}
|
|
422
|
+
return [...previous ?? [], ...parsed];
|
|
423
|
+
}
|
|
424
|
+
const allowUnsupportedPropertiesOption = new Option("--allow-unsupported-properties <entries>", "Comma-separated <ResourceType>:<PropertyName> tokens to accept as silently dropped at deploy time. Escape hatch — the property will NOT be written to AWS, the deployed resource will be missing the field. Example: --allow-unsupported-properties AWS::Lambda::Function:LoggingConfig,AWS::RDS::DBInstance:CACertificateIdentifier").argParser(parseAllowUnsupportedPropertiesToken);
|
|
398
425
|
const deployOptions = [
|
|
399
426
|
new Option("--concurrency <number>", "Maximum concurrent resource operations").default(10).argParser((value) => parseInt(value, 10)),
|
|
400
427
|
new Option("--stack-concurrency <number>", "Maximum concurrent stack deployments").default(4).argParser((value) => parseInt(value, 10)),
|
|
@@ -409,6 +436,7 @@ const deployOptions = [
|
|
|
409
436
|
aggressiveVpcParallelOption,
|
|
410
437
|
new Option("-e, --exclusively", "Only deploy requested stacks, do not include dependencies").default(false),
|
|
411
438
|
allowUnsupportedTypesOption,
|
|
439
|
+
allowUnsupportedPropertiesOption,
|
|
412
440
|
...resourceTimeoutOptions
|
|
413
441
|
];
|
|
414
442
|
/**
|
|
@@ -1532,6 +1560,517 @@ var IAMPolicyProvider = class {
|
|
|
1532
1560
|
}
|
|
1533
1561
|
};
|
|
1534
1562
|
|
|
1563
|
+
//#endregion
|
|
1564
|
+
//#region src/provisioning/providers/iam-managed-policy-provider.ts
|
|
1565
|
+
/**
|
|
1566
|
+
* AWS IAM Managed Policy Provider
|
|
1567
|
+
*
|
|
1568
|
+
* Implements resource provisioning for AWS::IAM::ManagedPolicy using the IAM SDK.
|
|
1569
|
+
* Cloud Control API does support this type, but a dedicated SDK provider wins
|
|
1570
|
+
* via Tier 1 of the provider registry and gives cdkd direct control over:
|
|
1571
|
+
* - PolicyDocument updates (via CreatePolicyVersion + SetDefaultPolicyVersion +
|
|
1572
|
+
* prune oldest non-default when at the 5-version limit)
|
|
1573
|
+
* - Attachment fan-out (Groups / Roles / Users) on create + update
|
|
1574
|
+
* - Detach-before-delete cleanup (a ManagedPolicy with attached principals
|
|
1575
|
+
* or with non-default versions cannot be deleted directly)
|
|
1576
|
+
*
|
|
1577
|
+
* Physical id is the policy ARN (`arn:aws:iam::<account>:policy/<path><name>`)
|
|
1578
|
+
* since path is part of the identity — two policies with the same name in
|
|
1579
|
+
* different paths are distinct.
|
|
1580
|
+
*/
|
|
1581
|
+
var IAMManagedPolicyProvider = class {
|
|
1582
|
+
iamClient;
|
|
1583
|
+
logger = getLogger().child("IAMManagedPolicyProvider");
|
|
1584
|
+
handledProperties = new Map([["AWS::IAM::ManagedPolicy", new Set([
|
|
1585
|
+
"ManagedPolicyName",
|
|
1586
|
+
"Description",
|
|
1587
|
+
"Path",
|
|
1588
|
+
"PolicyDocument",
|
|
1589
|
+
"Groups",
|
|
1590
|
+
"Roles",
|
|
1591
|
+
"Users",
|
|
1592
|
+
"Tags"
|
|
1593
|
+
])]]);
|
|
1594
|
+
constructor() {
|
|
1595
|
+
const awsClients = getAwsClients();
|
|
1596
|
+
this.iamClient = awsClients.iam;
|
|
1597
|
+
}
|
|
1598
|
+
async create(logicalId, resourceType, properties) {
|
|
1599
|
+
this.logger.debug(`Creating IAM managed policy ${logicalId}`);
|
|
1600
|
+
const policyName = generateResourceNameWithFallback(properties["ManagedPolicyName"], logicalId, { maxLength: 128 });
|
|
1601
|
+
const policyDocument = properties["PolicyDocument"];
|
|
1602
|
+
if (!policyDocument) throw new ProvisioningError(`PolicyDocument is required for IAM managed policy ${logicalId}`, resourceType, logicalId);
|
|
1603
|
+
const policyDoc = typeof policyDocument === "string" ? policyDocument : JSON.stringify(policyDocument);
|
|
1604
|
+
try {
|
|
1605
|
+
const createParams = {
|
|
1606
|
+
PolicyName: policyName,
|
|
1607
|
+
PolicyDocument: policyDoc
|
|
1608
|
+
};
|
|
1609
|
+
if (properties["Description"]) createParams.Description = properties["Description"];
|
|
1610
|
+
if (properties["Path"]) createParams.Path = properties["Path"];
|
|
1611
|
+
const tags = properties["Tags"];
|
|
1612
|
+
if (tags && Array.isArray(tags) && tags.length > 0) createParams.Tags = tags;
|
|
1613
|
+
const policyArn = (await this.iamClient.send(new CreatePolicyCommand(createParams))).Policy?.Arn;
|
|
1614
|
+
if (!policyArn) throw new ProvisioningError(`CreatePolicy succeeded but no Arn returned for ${logicalId}`, resourceType, logicalId, policyName);
|
|
1615
|
+
this.logger.debug(`Created IAM managed policy: ${policyArn}`);
|
|
1616
|
+
try {
|
|
1617
|
+
await this.attachToPrincipals(policyArn, properties["Groups"], properties["Roles"], properties["Users"]);
|
|
1618
|
+
} catch (innerError) {
|
|
1619
|
+
try {
|
|
1620
|
+
await this.detachAllPrincipals(policyArn);
|
|
1621
|
+
await this.deleteAllNonDefaultVersions(policyArn);
|
|
1622
|
+
await this.iamClient.send(new DeletePolicyCommand({ PolicyArn: policyArn }));
|
|
1623
|
+
this.logger.debug(`Cleaned up partially-created managed policy ${logicalId} (${policyArn}) after attachment failure`);
|
|
1624
|
+
} catch (cleanupError) {
|
|
1625
|
+
this.logger.warn(`Failed to clean up partially-created managed policy ${logicalId} (${policyArn}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required: detach principals (aws iam list-entities-for-policy --policy-arn ${policyArn}), delete versions (aws iam list-policy-versions --policy-arn ${policyArn} then aws iam delete-policy-version), then aws iam delete-policy --policy-arn ${policyArn}`);
|
|
1626
|
+
}
|
|
1627
|
+
throw innerError;
|
|
1628
|
+
}
|
|
1629
|
+
return {
|
|
1630
|
+
physicalId: policyArn,
|
|
1631
|
+
attributes: { PolicyArn: policyArn }
|
|
1632
|
+
};
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
const cause = error instanceof Error ? error : void 0;
|
|
1635
|
+
throw new ProvisioningError(`Failed to create IAM managed policy ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, policyName, cause);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
1639
|
+
this.logger.debug(`Updating IAM managed policy ${logicalId}: ${physicalId}`);
|
|
1640
|
+
const newPolicyName = generateResourceNameWithFallback(properties["ManagedPolicyName"], logicalId, { maxLength: 128 });
|
|
1641
|
+
const oldPolicyName = derivePolicyNameFromArn(physicalId);
|
|
1642
|
+
const newPath = properties["Path"] || "/";
|
|
1643
|
+
const oldPath = previousProperties["Path"] || "/";
|
|
1644
|
+
const newDescription = properties["Description"];
|
|
1645
|
+
const oldDescription = previousProperties["Description"];
|
|
1646
|
+
if (newPolicyName !== oldPolicyName || newPath !== oldPath || (newDescription ?? "") !== (oldDescription ?? "")) {
|
|
1647
|
+
const reason = newPolicyName !== oldPolicyName ? "ManagedPolicyName" : newPath !== oldPath ? "Path" : "Description";
|
|
1648
|
+
this.logger.debug(`${reason} changed, replacing managed policy: ${physicalId} (${reason} mutation)`);
|
|
1649
|
+
const createResult = await this.create(logicalId, resourceType, properties);
|
|
1650
|
+
try {
|
|
1651
|
+
await this.delete(logicalId, physicalId, resourceType, previousProperties);
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
this.logger.warn(`Failed to delete old managed policy ${physicalId} during replacement: ${String(error)}. The old policy may be orphaned and require manual cleanup.`);
|
|
1654
|
+
}
|
|
1655
|
+
const result = {
|
|
1656
|
+
physicalId: createResult.physicalId,
|
|
1657
|
+
wasReplaced: true
|
|
1658
|
+
};
|
|
1659
|
+
if (createResult.attributes) result.attributes = createResult.attributes;
|
|
1660
|
+
return result;
|
|
1661
|
+
}
|
|
1662
|
+
try {
|
|
1663
|
+
const newDocument = properties["PolicyDocument"];
|
|
1664
|
+
const oldDocument = previousProperties["PolicyDocument"];
|
|
1665
|
+
if (newDocument) {
|
|
1666
|
+
const newDocStr = typeof newDocument === "string" ? newDocument : JSON.stringify(newDocument);
|
|
1667
|
+
if (newDocStr !== (oldDocument ? typeof oldDocument === "string" ? oldDocument : JSON.stringify(oldDocument) : "")) {
|
|
1668
|
+
await this.ensureVersionCapacity(physicalId);
|
|
1669
|
+
await this.iamClient.send(new CreatePolicyVersionCommand({
|
|
1670
|
+
PolicyArn: physicalId,
|
|
1671
|
+
PolicyDocument: newDocStr,
|
|
1672
|
+
SetAsDefault: true
|
|
1673
|
+
}));
|
|
1674
|
+
this.logger.debug(`Updated PolicyDocument for ${physicalId}`);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
await this.updatePrincipals(physicalId, properties["Groups"], previousProperties["Groups"], properties["Roles"], previousProperties["Roles"], properties["Users"], previousProperties["Users"]);
|
|
1678
|
+
await this.updateTags(physicalId, properties["Tags"], previousProperties["Tags"]);
|
|
1679
|
+
return {
|
|
1680
|
+
physicalId,
|
|
1681
|
+
wasReplaced: false,
|
|
1682
|
+
attributes: { PolicyArn: physicalId }
|
|
1683
|
+
};
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
const cause = error instanceof Error ? error : void 0;
|
|
1686
|
+
throw new ProvisioningError(`Failed to update IAM managed policy ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
async delete(logicalId, physicalId, resourceType, _properties, context) {
|
|
1690
|
+
this.logger.debug(`Deleting IAM managed policy ${logicalId}: ${physicalId}`);
|
|
1691
|
+
try {
|
|
1692
|
+
try {
|
|
1693
|
+
await this.iamClient.send(new GetPolicyCommand({ PolicyArn: physicalId }));
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
if (error instanceof NoSuchEntityException) {
|
|
1696
|
+
assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
1697
|
+
this.logger.debug(`Managed policy ${physicalId} does not exist, skipping deletion`);
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
throw error;
|
|
1701
|
+
}
|
|
1702
|
+
await this.detachAllPrincipals(physicalId);
|
|
1703
|
+
await this.deleteAllNonDefaultVersions(physicalId);
|
|
1704
|
+
await this.iamClient.send(new DeletePolicyCommand({ PolicyArn: physicalId }));
|
|
1705
|
+
this.logger.debug(`Successfully deleted IAM managed policy ${logicalId}`);
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
const cause = error instanceof Error ? error : void 0;
|
|
1708
|
+
throw new ProvisioningError(`Failed to delete IAM managed policy ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
1712
|
+
if (attributeName === "PolicyArn") return physicalId;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Read the AWS-current managed policy configuration in CFn-property shape.
|
|
1716
|
+
*
|
|
1717
|
+
* Coverage:
|
|
1718
|
+
* - `ManagedPolicyName`, `Description`, `Path` — straight from `GetPolicy`.
|
|
1719
|
+
* - `PolicyDocument` — fetched via `GetPolicyVersion` on the default
|
|
1720
|
+
* version, URL-decoded + JSON-parsed.
|
|
1721
|
+
* - `Groups` / `Roles` / `Users` — string arrays from
|
|
1722
|
+
* `ListEntitiesForPolicy`.
|
|
1723
|
+
* - `Tags` — via `ListPolicyTags`, with the `aws:cdk:path` etc. filtered
|
|
1724
|
+
* out by `normalizeAwsTagsToCfn`.
|
|
1725
|
+
*
|
|
1726
|
+
* Returns `undefined` when the policy is gone (`NoSuchEntityException`).
|
|
1727
|
+
*/
|
|
1728
|
+
async readCurrentState(physicalId, _logicalId, _resourceType, _properties) {
|
|
1729
|
+
let policy;
|
|
1730
|
+
try {
|
|
1731
|
+
policy = (await this.iamClient.send(new GetPolicyCommand({ PolicyArn: physicalId }))).Policy;
|
|
1732
|
+
} catch (err) {
|
|
1733
|
+
if (err instanceof NoSuchEntityException) return void 0;
|
|
1734
|
+
throw err;
|
|
1735
|
+
}
|
|
1736
|
+
if (!policy) return void 0;
|
|
1737
|
+
const result = {};
|
|
1738
|
+
if (policy.PolicyName !== void 0) result["ManagedPolicyName"] = policy.PolicyName;
|
|
1739
|
+
result["Description"] = policy.Description ?? "";
|
|
1740
|
+
if (policy.Path !== void 0) result["Path"] = policy.Path;
|
|
1741
|
+
if (policy.DefaultVersionId) try {
|
|
1742
|
+
const doc = (await this.iamClient.send(new GetPolicyVersionCommand({
|
|
1743
|
+
PolicyArn: physicalId,
|
|
1744
|
+
VersionId: policy.DefaultVersionId
|
|
1745
|
+
}))).PolicyVersion?.Document;
|
|
1746
|
+
if (typeof doc === "string") try {
|
|
1747
|
+
result["PolicyDocument"] = JSON.parse(decodeURIComponent(doc));
|
|
1748
|
+
} catch {
|
|
1749
|
+
result["PolicyDocument"] = doc;
|
|
1750
|
+
}
|
|
1751
|
+
} catch (err) {
|
|
1752
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
1753
|
+
}
|
|
1754
|
+
try {
|
|
1755
|
+
const groups = [];
|
|
1756
|
+
const roles = [];
|
|
1757
|
+
const users = [];
|
|
1758
|
+
let marker;
|
|
1759
|
+
while (true) {
|
|
1760
|
+
const resp = await this.iamClient.send(new ListEntitiesForPolicyCommand({
|
|
1761
|
+
PolicyArn: physicalId,
|
|
1762
|
+
...marker ? { Marker: marker } : {}
|
|
1763
|
+
}));
|
|
1764
|
+
for (const g of resp.PolicyGroups ?? []) if (g.GroupName) groups.push(g.GroupName);
|
|
1765
|
+
for (const r of resp.PolicyRoles ?? []) if (r.RoleName) roles.push(r.RoleName);
|
|
1766
|
+
for (const u of resp.PolicyUsers ?? []) if (u.UserName) users.push(u.UserName);
|
|
1767
|
+
if (!resp.IsTruncated) break;
|
|
1768
|
+
marker = resp.Marker;
|
|
1769
|
+
}
|
|
1770
|
+
result["Groups"] = groups;
|
|
1771
|
+
result["Roles"] = roles;
|
|
1772
|
+
result["Users"] = users;
|
|
1773
|
+
} catch (err) {
|
|
1774
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
1775
|
+
}
|
|
1776
|
+
try {
|
|
1777
|
+
const collected = [];
|
|
1778
|
+
let marker;
|
|
1779
|
+
while (true) {
|
|
1780
|
+
const tagsResp = await this.iamClient.send(new ListPolicyTagsCommand({
|
|
1781
|
+
PolicyArn: physicalId,
|
|
1782
|
+
...marker ? { Marker: marker } : {}
|
|
1783
|
+
}));
|
|
1784
|
+
for (const t of tagsResp.Tags ?? []) collected.push({
|
|
1785
|
+
Key: t.Key,
|
|
1786
|
+
Value: t.Value
|
|
1787
|
+
});
|
|
1788
|
+
if (!tagsResp.IsTruncated) break;
|
|
1789
|
+
marker = tagsResp.Marker;
|
|
1790
|
+
}
|
|
1791
|
+
result["Tags"] = normalizeAwsTagsToCfn(collected);
|
|
1792
|
+
} catch (err) {
|
|
1793
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
1794
|
+
}
|
|
1795
|
+
return result;
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Adopt an existing IAM managed policy into cdkd state.
|
|
1799
|
+
*
|
|
1800
|
+
* Lookup order:
|
|
1801
|
+
* 1. `--resource` override or `Properties.ManagedPolicyName` → walk
|
|
1802
|
+
* `ListPolicies(Scope: 'Local')` to find the matching ARN.
|
|
1803
|
+
* 2. `ListPolicies(Scope: 'Local')` → for each candidate, `ListPolicyTags`
|
|
1804
|
+
* and match against `aws:cdk:path`.
|
|
1805
|
+
*
|
|
1806
|
+
* Scope is forced to `'Local'` (customer-managed policies) — adopting an
|
|
1807
|
+
* AWS-managed policy would let cdkd delete it on next destroy, which would
|
|
1808
|
+
* be a major footgun.
|
|
1809
|
+
*/
|
|
1810
|
+
async import(input) {
|
|
1811
|
+
const explicit = resolveExplicitPhysicalId(input, "ManagedPolicyName");
|
|
1812
|
+
if (explicit) {
|
|
1813
|
+
if (explicit.startsWith("arn:")) {
|
|
1814
|
+
if (explicit.startsWith("arn:aws:iam::aws:")) throw new Error(`Refusing to import AWS-managed policy ${explicit}: cdkd only adopts customer-managed policies. If you need to attach an AWS-managed policy to a role / user / group, reference it via ManagedPolicyArns on the principal instead.`);
|
|
1815
|
+
try {
|
|
1816
|
+
await this.iamClient.send(new GetPolicyCommand({ PolicyArn: explicit }));
|
|
1817
|
+
return {
|
|
1818
|
+
physicalId: explicit,
|
|
1819
|
+
attributes: { PolicyArn: explicit }
|
|
1820
|
+
};
|
|
1821
|
+
} catch (err) {
|
|
1822
|
+
if (err instanceof NoSuchEntityException) return null;
|
|
1823
|
+
throw err;
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
const arnByName = await this.findPolicyArnByName(explicit);
|
|
1827
|
+
if (arnByName) return {
|
|
1828
|
+
physicalId: arnByName,
|
|
1829
|
+
attributes: { PolicyArn: arnByName }
|
|
1830
|
+
};
|
|
1831
|
+
return null;
|
|
1832
|
+
}
|
|
1833
|
+
if (!input.cdkPath) return null;
|
|
1834
|
+
let marker;
|
|
1835
|
+
do {
|
|
1836
|
+
const list = await this.iamClient.send(new ListPoliciesCommand({
|
|
1837
|
+
Scope: "Local",
|
|
1838
|
+
...marker ? { Marker: marker } : {}
|
|
1839
|
+
}));
|
|
1840
|
+
for (const policy of list.Policies ?? []) {
|
|
1841
|
+
if (!policy.Arn) continue;
|
|
1842
|
+
try {
|
|
1843
|
+
if (matchesCdkPath((await this.iamClient.send(new ListPolicyTagsCommand({ PolicyArn: policy.Arn }))).Tags, input.cdkPath)) return {
|
|
1844
|
+
physicalId: policy.Arn,
|
|
1845
|
+
attributes: { PolicyArn: policy.Arn }
|
|
1846
|
+
};
|
|
1847
|
+
} catch (err) {
|
|
1848
|
+
if (err instanceof NoSuchEntityException) continue;
|
|
1849
|
+
throw err;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
marker = list.IsTruncated ? list.Marker : void 0;
|
|
1853
|
+
} while (marker);
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1856
|
+
async attachToPrincipals(policyArn, groups, roles, users) {
|
|
1857
|
+
if (groups && Array.isArray(groups)) for (const groupName of groups) {
|
|
1858
|
+
await this.iamClient.send(new AttachGroupPolicyCommand({
|
|
1859
|
+
GroupName: groupName,
|
|
1860
|
+
PolicyArn: policyArn
|
|
1861
|
+
}));
|
|
1862
|
+
this.logger.debug(`Attached ${policyArn} to group ${groupName}`);
|
|
1863
|
+
}
|
|
1864
|
+
if (roles && Array.isArray(roles)) for (const roleName of roles) {
|
|
1865
|
+
await this.iamClient.send(new AttachRolePolicyCommand({
|
|
1866
|
+
RoleName: roleName,
|
|
1867
|
+
PolicyArn: policyArn
|
|
1868
|
+
}));
|
|
1869
|
+
this.logger.debug(`Attached ${policyArn} to role ${roleName}`);
|
|
1870
|
+
}
|
|
1871
|
+
if (users && Array.isArray(users)) for (const userName of users) {
|
|
1872
|
+
await this.iamClient.send(new AttachUserPolicyCommand({
|
|
1873
|
+
UserName: userName,
|
|
1874
|
+
PolicyArn: policyArn
|
|
1875
|
+
}));
|
|
1876
|
+
this.logger.debug(`Attached ${policyArn} to user ${userName}`);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
async updatePrincipals(policyArn, newGroups, oldGroups, newRoles, oldRoles, newUsers, oldUsers) {
|
|
1880
|
+
const newGroupSet = new Set(newGroups || []);
|
|
1881
|
+
const oldGroupSet = new Set(oldGroups || []);
|
|
1882
|
+
for (const g of newGroupSet) if (!oldGroupSet.has(g)) {
|
|
1883
|
+
await this.iamClient.send(new AttachGroupPolicyCommand({
|
|
1884
|
+
GroupName: g,
|
|
1885
|
+
PolicyArn: policyArn
|
|
1886
|
+
}));
|
|
1887
|
+
this.logger.debug(`Attached ${policyArn} to group ${g}`);
|
|
1888
|
+
}
|
|
1889
|
+
for (const g of oldGroupSet) if (!newGroupSet.has(g)) try {
|
|
1890
|
+
await this.iamClient.send(new DetachGroupPolicyCommand({
|
|
1891
|
+
GroupName: g,
|
|
1892
|
+
PolicyArn: policyArn
|
|
1893
|
+
}));
|
|
1894
|
+
this.logger.debug(`Detached ${policyArn} from group ${g}`);
|
|
1895
|
+
} catch (err) {
|
|
1896
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
1897
|
+
}
|
|
1898
|
+
const newRoleSet = new Set(newRoles || []);
|
|
1899
|
+
const oldRoleSet = new Set(oldRoles || []);
|
|
1900
|
+
for (const r of newRoleSet) if (!oldRoleSet.has(r)) {
|
|
1901
|
+
await this.iamClient.send(new AttachRolePolicyCommand({
|
|
1902
|
+
RoleName: r,
|
|
1903
|
+
PolicyArn: policyArn
|
|
1904
|
+
}));
|
|
1905
|
+
this.logger.debug(`Attached ${policyArn} to role ${r}`);
|
|
1906
|
+
}
|
|
1907
|
+
for (const r of oldRoleSet) if (!newRoleSet.has(r)) try {
|
|
1908
|
+
await this.iamClient.send(new DetachRolePolicyCommand({
|
|
1909
|
+
RoleName: r,
|
|
1910
|
+
PolicyArn: policyArn
|
|
1911
|
+
}));
|
|
1912
|
+
this.logger.debug(`Detached ${policyArn} from role ${r}`);
|
|
1913
|
+
} catch (err) {
|
|
1914
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
1915
|
+
}
|
|
1916
|
+
const newUserSet = new Set(newUsers || []);
|
|
1917
|
+
const oldUserSet = new Set(oldUsers || []);
|
|
1918
|
+
for (const u of newUserSet) if (!oldUserSet.has(u)) {
|
|
1919
|
+
await this.iamClient.send(new AttachUserPolicyCommand({
|
|
1920
|
+
UserName: u,
|
|
1921
|
+
PolicyArn: policyArn
|
|
1922
|
+
}));
|
|
1923
|
+
this.logger.debug(`Attached ${policyArn} to user ${u}`);
|
|
1924
|
+
}
|
|
1925
|
+
for (const u of oldUserSet) if (!newUserSet.has(u)) try {
|
|
1926
|
+
await this.iamClient.send(new DetachUserPolicyCommand({
|
|
1927
|
+
UserName: u,
|
|
1928
|
+
PolicyArn: policyArn
|
|
1929
|
+
}));
|
|
1930
|
+
this.logger.debug(`Detached ${policyArn} from user ${u}`);
|
|
1931
|
+
} catch (err) {
|
|
1932
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
async detachAllPrincipals(policyArn) {
|
|
1936
|
+
try {
|
|
1937
|
+
let marker;
|
|
1938
|
+
while (true) {
|
|
1939
|
+
const resp = await this.iamClient.send(new ListEntitiesForPolicyCommand({
|
|
1940
|
+
PolicyArn: policyArn,
|
|
1941
|
+
...marker ? { Marker: marker } : {}
|
|
1942
|
+
}));
|
|
1943
|
+
for (const g of resp.PolicyGroups ?? []) {
|
|
1944
|
+
if (!g.GroupName) continue;
|
|
1945
|
+
try {
|
|
1946
|
+
await this.iamClient.send(new DetachGroupPolicyCommand({
|
|
1947
|
+
GroupName: g.GroupName,
|
|
1948
|
+
PolicyArn: policyArn
|
|
1949
|
+
}));
|
|
1950
|
+
} catch (err) {
|
|
1951
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
for (const r of resp.PolicyRoles ?? []) {
|
|
1955
|
+
if (!r.RoleName) continue;
|
|
1956
|
+
try {
|
|
1957
|
+
await this.iamClient.send(new DetachRolePolicyCommand({
|
|
1958
|
+
RoleName: r.RoleName,
|
|
1959
|
+
PolicyArn: policyArn
|
|
1960
|
+
}));
|
|
1961
|
+
} catch (err) {
|
|
1962
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
for (const u of resp.PolicyUsers ?? []) {
|
|
1966
|
+
if (!u.UserName) continue;
|
|
1967
|
+
try {
|
|
1968
|
+
await this.iamClient.send(new DetachUserPolicyCommand({
|
|
1969
|
+
UserName: u.UserName,
|
|
1970
|
+
PolicyArn: policyArn
|
|
1971
|
+
}));
|
|
1972
|
+
} catch (err) {
|
|
1973
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
if (!resp.IsTruncated) break;
|
|
1977
|
+
marker = resp.Marker;
|
|
1978
|
+
}
|
|
1979
|
+
} catch (err) {
|
|
1980
|
+
if (err instanceof NoSuchEntityException) return;
|
|
1981
|
+
throw err;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Delete every non-default version of the policy. Required before
|
|
1986
|
+
* `DeletePolicy` — AWS refuses to delete a policy that still has
|
|
1987
|
+
* non-default versions.
|
|
1988
|
+
*/
|
|
1989
|
+
async deleteAllNonDefaultVersions(policyArn) {
|
|
1990
|
+
try {
|
|
1991
|
+
let marker;
|
|
1992
|
+
while (true) {
|
|
1993
|
+
const resp = await this.iamClient.send(new ListPolicyVersionsCommand({
|
|
1994
|
+
PolicyArn: policyArn,
|
|
1995
|
+
...marker ? { Marker: marker } : {}
|
|
1996
|
+
}));
|
|
1997
|
+
for (const v of resp.Versions ?? []) {
|
|
1998
|
+
if (v.IsDefaultVersion) continue;
|
|
1999
|
+
if (!v.VersionId) continue;
|
|
2000
|
+
try {
|
|
2001
|
+
await this.iamClient.send(new DeletePolicyVersionCommand({
|
|
2002
|
+
PolicyArn: policyArn,
|
|
2003
|
+
VersionId: v.VersionId
|
|
2004
|
+
}));
|
|
2005
|
+
} catch (err) {
|
|
2006
|
+
if (!(err instanceof NoSuchEntityException)) throw err;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
if (!resp.IsTruncated) break;
|
|
2010
|
+
marker = resp.Marker;
|
|
2011
|
+
}
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
if (err instanceof NoSuchEntityException) return;
|
|
2014
|
+
throw err;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* AWS caps managed policies at 5 versions. Before creating a new version,
|
|
2019
|
+
* prune the oldest non-default version if at the cap.
|
|
2020
|
+
*/
|
|
2021
|
+
async ensureVersionCapacity(policyArn) {
|
|
2022
|
+
const versions = (await this.iamClient.send(new ListPolicyVersionsCommand({ PolicyArn: policyArn }))).Versions ?? [];
|
|
2023
|
+
if (versions.length < 5) return;
|
|
2024
|
+
const victim = versions.filter((v) => !v.IsDefaultVersion && v.VersionId).sort((a, b) => (a.CreateDate?.getTime() ?? 0) - (b.CreateDate?.getTime() ?? 0))[0];
|
|
2025
|
+
if (!victim?.VersionId) return;
|
|
2026
|
+
await this.iamClient.send(new DeletePolicyVersionCommand({
|
|
2027
|
+
PolicyArn: policyArn,
|
|
2028
|
+
VersionId: victim.VersionId
|
|
2029
|
+
}));
|
|
2030
|
+
this.logger.debug(`Pruned oldest non-default version ${victim.VersionId} of ${policyArn}`);
|
|
2031
|
+
}
|
|
2032
|
+
async updateTags(policyArn, newTags, oldTags) {
|
|
2033
|
+
const newTagMap = new Map((newTags || []).map((t) => [t.Key, t.Value]));
|
|
2034
|
+
const oldTagMap = new Map((oldTags || []).map((t) => [t.Key, t.Value]));
|
|
2035
|
+
const tagsToRemove = [];
|
|
2036
|
+
for (const key of oldTagMap.keys()) if (!newTagMap.has(key)) tagsToRemove.push(key);
|
|
2037
|
+
const tagsToAdd = [];
|
|
2038
|
+
for (const [key, value] of newTagMap) if (oldTagMap.get(key) !== value) tagsToAdd.push({
|
|
2039
|
+
Key: key,
|
|
2040
|
+
Value: value
|
|
2041
|
+
});
|
|
2042
|
+
if (tagsToRemove.length > 0) await this.iamClient.send(new UntagPolicyCommand({
|
|
2043
|
+
PolicyArn: policyArn,
|
|
2044
|
+
TagKeys: tagsToRemove
|
|
2045
|
+
}));
|
|
2046
|
+
if (tagsToAdd.length > 0) await this.iamClient.send(new TagPolicyCommand({
|
|
2047
|
+
PolicyArn: policyArn,
|
|
2048
|
+
Tags: tagsToAdd
|
|
2049
|
+
}));
|
|
2050
|
+
}
|
|
2051
|
+
async findPolicyArnByName(policyName) {
|
|
2052
|
+
let marker;
|
|
2053
|
+
do {
|
|
2054
|
+
const resp = await this.iamClient.send(new ListPoliciesCommand({
|
|
2055
|
+
Scope: "Local",
|
|
2056
|
+
...marker ? { Marker: marker } : {}
|
|
2057
|
+
}));
|
|
2058
|
+
for (const p of resp.Policies ?? []) if (p.PolicyName === policyName && p.Arn) return p.Arn;
|
|
2059
|
+
marker = resp.IsTruncated ? resp.Marker : void 0;
|
|
2060
|
+
} while (marker);
|
|
2061
|
+
}
|
|
2062
|
+
};
|
|
2063
|
+
/**
|
|
2064
|
+
* Recover the policy name from `arn:aws:iam::<account>:policy/<path><name>`.
|
|
2065
|
+
* Used to decide whether `ManagedPolicyName` was mutated relative to the
|
|
2066
|
+
* physical id we recorded — name + path are immutable on AWS, so any
|
|
2067
|
+
* difference is a replacement signal.
|
|
2068
|
+
*/
|
|
2069
|
+
function derivePolicyNameFromArn(arn) {
|
|
2070
|
+
const ix = arn.lastIndexOf("/");
|
|
2071
|
+
return ix >= 0 ? arn.slice(ix + 1) : arn;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
1535
2074
|
//#endregion
|
|
1536
2075
|
//#region src/provisioning/providers/iam-instance-profile-provider.ts
|
|
1537
2076
|
/**
|
|
@@ -6684,7 +7223,7 @@ var LambdaPermissionProvider = class {
|
|
|
6684
7223
|
const statementId = physicalId.includes("|") ? physicalId.split("|").pop() : physicalId;
|
|
6685
7224
|
let policyDoc;
|
|
6686
7225
|
try {
|
|
6687
|
-
policyDoc = (await this.lambdaClient.send(new GetPolicyCommand({ FunctionName: functionName }))).Policy;
|
|
7226
|
+
policyDoc = (await this.lambdaClient.send(new GetPolicyCommand$1({ FunctionName: functionName }))).Policy;
|
|
6688
7227
|
} catch (err) {
|
|
6689
7228
|
if (err instanceof ResourceNotFoundException) return void 0;
|
|
6690
7229
|
throw err;
|
|
@@ -31995,6 +32534,7 @@ var NestedStackProvider = class {
|
|
|
31995
32534
|
function registerAllProviders(registry) {
|
|
31996
32535
|
registry.register("AWS::IAM::Role", new IAMRoleProvider());
|
|
31997
32536
|
registry.register("AWS::IAM::Policy", new IAMPolicyProvider());
|
|
32537
|
+
registry.register("AWS::IAM::ManagedPolicy", new IAMManagedPolicyProvider());
|
|
31998
32538
|
registry.register("AWS::IAM::InstanceProfile", new IAMInstanceProfileProvider());
|
|
31999
32539
|
const iamUserGroupProvider = new IAMUserGroupProvider();
|
|
32000
32540
|
registry.register("AWS::IAM::User", iamUserGroupProvider);
|
|
@@ -32392,6 +32932,7 @@ async function deployCommand(stacks, options) {
|
|
|
32392
32932
|
registerAllProviders(stackProviderRegistry);
|
|
32393
32933
|
stackProviderRegistry.setCustomResourceResponseBucket(stateBucket, baseRegion);
|
|
32394
32934
|
if (options.allowUnsupportedTypes?.length) stackProviderRegistry.allowUnsupportedTypes(options.allowUnsupportedTypes);
|
|
32935
|
+
if (options.allowUnsupportedProperties?.length) stackProviderRegistry.allowUnsupportedProperties(options.allowUnsupportedProperties);
|
|
32395
32936
|
try {
|
|
32396
32937
|
if (skipPrefix) {
|
|
32397
32938
|
const existing = await stackStateBackend.getState(stackInfo.stackName, stackRegion);
|
|
@@ -33870,7 +34411,6 @@ function isPlainObject(value) {
|
|
|
33870
34411
|
* `Record<string, string>` for trivial JSON serialization if ever needed).
|
|
33871
34412
|
*/
|
|
33872
34413
|
const CC_API_FALLBACK_DENY_LIST = {
|
|
33873
|
-
"AWS::IAM::ManagedPolicy": "PolicyDocument is URL-encoded JSON in CC API responses, but cdkd state stores it as a parsed object — needs per-type decode",
|
|
33874
34414
|
"AWS::ApiGateway::RestApi": "Body / BodyS3Location are write-only inputs not returned by CC API GetResource; cdkd state preserves them",
|
|
33875
34415
|
"AWS::CloudFormation::Stack": "CC API returns runtime stack state (outputs/status), not the template parameters cdkd state stores",
|
|
33876
34416
|
"AWS::EC2::LaunchTemplate": "CC API returns version-bumped LaunchTemplateData with synthetic LatestVersionNumber that diverges from the CFn input shape"
|
|
@@ -55560,7 +56100,7 @@ function pickEssentialContainerId(instance, service) {
|
|
|
55560
56100
|
const defaultWaitForExitImpl = async (containerId) => {
|
|
55561
56101
|
const { execFile } = await import("node:child_process");
|
|
55562
56102
|
const { promisify } = await import("node:util");
|
|
55563
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
56103
|
+
const { getDockerCmd } = await import("./docker-cmd-iDMcWcre.js").then((n) => n.t);
|
|
55564
56104
|
const { stdout } = await promisify(execFile)(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
55565
56105
|
const code = parseInt(stdout.trim(), 10);
|
|
55566
56106
|
return Number.isFinite(code) ? code : -1;
|
|
@@ -58069,7 +58609,7 @@ function reorderArgs(argv) {
|
|
|
58069
58609
|
*/
|
|
58070
58610
|
async function main() {
|
|
58071
58611
|
const program = new Command();
|
|
58072
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
58612
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.156.0");
|
|
58073
58613
|
program.addCommand(createBootstrapCommand());
|
|
58074
58614
|
program.addCommand(createSynthCommand());
|
|
58075
58615
|
program.addCommand(createListCommand());
|