diff --git a/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts b/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts index 619ca8b96175c..934531f45ffcb 100644 --- a/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts +++ b/packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts @@ -233,6 +233,13 @@ export async function cloneDirectory(source: string, target: string, output?: No } interface CommonCdkBootstrapCommandOptions { + /** + * Path to a custom bootstrap template. + * + * @default - the default CDK bootstrap template. + */ + readonly bootstrapTemplate?: string; + readonly toolkitStackName: string; /** @@ -422,6 +429,9 @@ export class TestFixture extends ShellHelper { if (options.usePreviousParameters === false) { args.push('--no-previous-parameters'); } + if (options.bootstrapTemplate) { + args.push('--template', options.bootstrapTemplate); + } return this.cdk(args, { ...options.cliOptions, diff --git a/packages/@aws-cdk-testing/cli-integ/resources/bootstrap-templates/session-tags.all-roles-deny-all.yaml b/packages/@aws-cdk-testing/cli-integ/resources/bootstrap-templates/session-tags.all-roles-deny-all.yaml new file mode 100644 index 0000000000000..e2b80c535d615 --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/resources/bootstrap-templates/session-tags.all-roles-deny-all.yaml @@ -0,0 +1,703 @@ +Description: This stack includes resources needed to deploy AWS CDK apps into this + environment +Parameters: + TrustedAccounts: + Description: List of AWS accounts that are trusted to publish assets and deploy + stacks to this environment + Default: '' + Type: CommaDelimitedList + TrustedAccountsForLookup: + Description: List of AWS accounts that are trusted to look up values in this + environment + Default: '' + Type: CommaDelimitedList + CloudFormationExecutionPolicies: + Description: List of the ManagedPolicy ARN(s) to attach to the CloudFormation + deployment role + Default: '' + Type: CommaDelimitedList + FileAssetsBucketName: + Description: The name of the S3 bucket used for file assets + Default: '' + Type: String + FileAssetsBucketKmsKeyId: + Description: Empty to create a new key (default), 'AWS_MANAGED_KEY' to use a managed + S3 key, or the ID/ARN of an existing key. + Default: '' + Type: String + ContainerAssetsRepositoryName: + Description: A user-provided custom name to use for the container assets ECR repository + Default: '' + Type: String + Qualifier: + Description: An identifier to distinguish multiple bootstrap stacks in the same environment + Default: hnb659fds + Type: String + # "cdk-(qualifier)-image-publishing-role-(account)-(region)" needs to be <= 64 chars + # account = 12, region <= 14, 10 chars for qualifier and 28 for rest of role name + AllowedPattern: "[A-Za-z0-9_-]{1,10}" + ConstraintDescription: Qualifier must be an alphanumeric identifier of at most 10 characters + PublicAccessBlockConfiguration: + Description: Whether or not to enable S3 Staging Bucket Public Access Block Configuration + Default: 'true' + Type: 'String' + AllowedValues: ['true', 'false'] + InputPermissionsBoundary: + Description: Whether or not to use either the CDK supplied or custom permissions boundary + Default: '' + Type: 'String' + UseExamplePermissionsBoundary: + Default: 'false' + AllowedValues: [ 'true', 'false' ] + Type: String + BootstrapVariant: + Type: String + Default: 'AWS CDK: Default Resources' + Description: Describe the provenance of the resources in this bootstrap + stack. Change this when you customize the template. To prevent accidents, + the CDK CLI will not overwrite bootstrap stacks with a different variant. +Conditions: + HasTrustedAccounts: + Fn::Not: + - Fn::Equals: + - '' + - Fn::Join: + - '' + - Ref: TrustedAccounts + HasTrustedAccountsForLookup: + Fn::Not: + - Fn::Equals: + - '' + - Fn::Join: + - '' + - Ref: TrustedAccountsForLookup + HasCloudFormationExecutionPolicies: + Fn::Not: + - Fn::Equals: + - '' + - Fn::Join: + - '' + - Ref: CloudFormationExecutionPolicies + HasCustomFileAssetsBucketName: + Fn::Not: + - Fn::Equals: + - '' + - Ref: FileAssetsBucketName + CreateNewKey: + Fn::Equals: + - '' + - Ref: FileAssetsBucketKmsKeyId + UseAwsManagedKey: + Fn::Equals: + - 'AWS_MANAGED_KEY' + - Ref: FileAssetsBucketKmsKeyId + ShouldCreatePermissionsBoundary: + Fn::Equals: + - 'true' + - Ref: UseExamplePermissionsBoundary + PermissionsBoundarySet: + Fn::Not: + - Fn::Equals: + - '' + - Ref: InputPermissionsBoundary + HasCustomContainerAssetsRepositoryName: + Fn::Not: + - Fn::Equals: + - '' + - Ref: ContainerAssetsRepositoryName + UsePublicAccessBlockConfiguration: + Fn::Equals: + - 'true' + - Ref: PublicAccessBlockConfiguration +Resources: + FileAssetsBucketEncryptionKey: + Type: AWS::KMS::Key + Properties: + KeyPolicy: + Statement: + - Action: + - kms:Create* + - kms:Describe* + - kms:Enable* + - kms:List* + - kms:Put* + - kms:Update* + - kms:Revoke* + - kms:Disable* + - kms:Get* + - kms:Delete* + - kms:ScheduleKeyDeletion + - kms:CancelKeyDeletion + - kms:GenerateDataKey + - kms:TagResource + - kms:UntagResource + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + Resource: "*" + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Principal: + # Not actually everyone -- see below for Conditions + AWS: "*" + Resource: "*" + Condition: + StringEquals: + # See https://docs.aws.amazon.com/kms/latest/developerguide/policy-conditions.html#conditions-kms-caller-account + kms:CallerAccount: + Ref: AWS::AccountId + kms:ViaService: + - Fn::Sub: s3.${AWS::Region}.amazonaws.com + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Principal: + AWS: + Fn::Sub: "${FilePublishingRole.Arn}" + Resource: "*" + Condition: CreateNewKey + FileAssetsBucketEncryptionKeyAlias: + Condition: CreateNewKey + Type: AWS::KMS::Alias + Properties: + AliasName: + Fn::Sub: "alias/cdk-${Qualifier}-assets-key" + TargetKeyId: + Ref: FileAssetsBucketEncryptionKey + StagingBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: + Fn::If: + - HasCustomFileAssetsBucketName + - Fn::Sub: "${FileAssetsBucketName}" + - Fn::Sub: cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region} + AccessControl: Private + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: aws:kms + KMSMasterKeyID: + Fn::If: + - CreateNewKey + - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}" + - Fn::If: + - UseAwsManagedKey + - Ref: AWS::NoValue + - Fn::Sub: "${FileAssetsBucketKmsKeyId}" + PublicAccessBlockConfiguration: + Fn::If: + - UsePublicAccessBlockConfiguration + - BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + - Ref: AWS::NoValue + VersioningConfiguration: + Status: Enabled + LifecycleConfiguration: + Rules: + # Exising objects will never be overwritten but Security Hub wants this rule to exist + - Id: CleanupOldVersions + Status: Enabled + NoncurrentVersionExpiration: + NoncurrentDays: 365 + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + StagingBucketPolicy: + Type: 'AWS::S3::BucketPolicy' + Properties: + Bucket: { Ref: 'StagingBucket' } + PolicyDocument: + Id: 'AccessControl' + Version: '2012-10-17' + Statement: + - Sid: 'AllowSSLRequestsOnly' + Action: 's3:*' + Effect: 'Deny' + Resource: + - { 'Fn::Sub': '${StagingBucket.Arn}' } + - { 'Fn::Sub': '${StagingBucket.Arn}/*' } + Condition: + Bool: { 'aws:SecureTransport': 'false' } + Principal: '*' + ContainerAssetsRepository: + Type: AWS::ECR::Repository + Properties: + ImageTagMutability: IMMUTABLE + # Untagged images should never exist but Security Hub wants this rule to exist + LifecyclePolicy: + LifecyclePolicyText: | + { + "rules": [ + { + "rulePriority": 1, + "description": "Untagged images should not exist, but expire any older than one year", + "selection": { + "tagStatus": "untagged", + "countType": "sinceImagePushed", + "countUnit": "days", + "countNumber": 365 + }, + "action": { "type": "expire" } + } + ] + } + RepositoryName: + Fn::If: + - HasCustomContainerAssetsRepositoryName + - Fn::Sub: "${ContainerAssetsRepositoryName}" + - Fn::Sub: cdk-${Qualifier}-container-assets-${AWS::AccountId}-${AWS::Region} + RepositoryPolicyText: + Version: "2012-10-17" + Statement: + # Necessary for Lambda container images + # https://docs.aws.amazon.com/lambda/latest/dg/configuration-images.html#configuration-images-permissions + - Sid: LambdaECRImageRetrievalPolicy + Effect: Allow + Principal: { Service: "lambda.amazonaws.com" } + Action: + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + Condition: + StringLike: + "aws:sourceArn": { "Fn::Sub": "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:*" } + FilePublishingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + # The TagSession action is required to be able to assume this role with session tags. + # Without this trust policy, attemping to assume this role with session tags will fail. + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: file-publishing + ImagePublishingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + # The TagSession action is required to be able to assume this role with session tags. + # Without this trust policy, attemping to assume this role with session tags will fail. + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: image-publishing + LookupRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + # The TagSession action is required to be able to assume this role with session tags. + # Without this trust policy, attemping to assume this role with session tags will fail. + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccountsForLookup + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccountsForLookup + - Ref: AWS::NoValue + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-lookup-role-${AWS::AccountId}-${AWS::Region} + Policies: + - PolicyDocument: + Statement: + - Sid: AllowEc2OnlyIfEngineeringDepartement + Effect: Allow + Action: + - ec2:* + Resource: "*" + Condition: + StringEquals: + aws:PrincipalTag/Department: "Engineering" + Version: '2012-10-17' + PolicyName: LookupRolePolicy + Tags: + - Key: aws-cdk:bootstrap-role + Value: lookup + FilePublishingRoleDefaultPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: + - Action: + - s3:GetObject* + - s3:GetBucket* + - s3:GetEncryptionConfiguration + - s3:List* + - s3:DeleteObject* + - s3:PutObject* + - s3:Abort* + Resource: + - Fn::Sub: "${StagingBucket.Arn}" + - Fn::Sub: "${StagingBucket.Arn}/*" + # This condition requires that the File Publishing Role is assumed with the session tags + # 'Department: Engineering'; if these tags are not passed in, the role will + # not be able to perform these S3 actions. + Condition: + StringEquals: + aws:ResourceAccount: + - Fn::Sub: ${AWS::AccountId} + aws:PrincipalTag/Department: "Engineering" + Effect: Allow + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Resource: + Fn::If: + - CreateNewKey + - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}" + - Fn::Sub: arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${FileAssetsBucketKmsKeyId} + Version: '2012-10-17' + Roles: + - Ref: FilePublishingRole + PolicyName: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + ImagePublishingRoleDefaultPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: + - Action: + - ecr:PutImage + - ecr:InitiateLayerUpload + - ecr:UploadLayerPart + - ecr:CompleteLayerUpload + - ecr:BatchCheckLayerAvailability + - ecr:DescribeRepositories + - ecr:DescribeImages + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + Resource: + Fn::Sub: "${ContainerAssetsRepository.Arn}" + Effect: Allow + # This condition requires that the Image Publishing Role is assumed with the session tags + # 'Department: Engineering'; if these tags are not passed in, the role will + # not be able to perform these ECR actions. + Condition: + StringEquals: + aws:PrincipalTag/Department: "Engineering" + - Action: + - ecr:GetAuthorizationToken + Resource: "*" + Effect: Allow + Version: '2012-10-17' + Roles: + - Ref: ImagePublishingRole + PolicyName: + Fn::Sub: cdk-${Qualifier}-image-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + DeploymentActionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + # The TagSession action is required to be able to assume this role with session tags. + # Without this trust policy, attemping to assume this role with session tags will fail. + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + Policies: + - PolicyDocument: + Statement: + - Sid: CloudFormationPermissions + Effect: Allow + Action: + - cloudformation:CreateChangeSet + - cloudformation:DeleteChangeSet + - cloudformation:DescribeChangeSet + - cloudformation:DescribeStacks + - cloudformation:ExecuteChangeSet + - cloudformation:CreateStack + - cloudformation:UpdateStack + Resource: "*" + # This condition requires that the Deploy Role is assumed with the session tags + # 'Department: Engineering'; if these tags are not passed in, the Deploy Role will + # not be able to perform these CloudFormation actions. + Condition: + StringEquals: + aws:PrincipalTag/Department: "Engineering" + - Sid: PipelineCrossAccountArtifactsBucket + # Read/write buckets in different accounts. Permissions to buckets in + # same account are granted by bucket policies. + # + # Write permissions necessary to write outputs to the cross-region artifact replication bucket + # https://aws.amazon.com/premiumsupport/knowledge-center/codepipeline-deploy-cloudformation/. + Effect: Allow + Action: + - s3:GetObject* + - s3:GetBucket* + - s3:List* + - s3:Abort* + - s3:DeleteObject* + - s3:PutObject* + Resource: "*" + Condition: + StringNotEquals: + s3:ResourceAccount: + Ref: 'AWS::AccountId' + - Sid: PipelineCrossAccountArtifactsKey + # Use keys only for the purposes of reading encrypted files from S3. + Effect: Allow + Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Resource: "*" + Condition: + StringEquals: + kms:ViaService: + Fn::Sub: s3.${AWS::Region}.amazonaws.com + - Action: iam:PassRole + Resource: + Fn::Sub: "${CloudFormationExecutionRole.Arn}" + Effect: Allow + - Sid: CliPermissions + Action: + # Permissions needed by the CLI when doing `cdk deploy`. + # Our CI/CD does not need DeleteStack, + # but we also want to use this role from the CLI, + # and there you can call `cdk destroy` + - cloudformation:DescribeStackEvents + - cloudformation:GetTemplate + - cloudformation:DeleteStack + - cloudformation:UpdateTerminationProtection + - sts:GetCallerIdentity + # `cdk import` + - cloudformation:GetTemplateSummary + Resource: "*" + Effect: Allow + - Sid: CliStagingBucket + Effect: Allow + Action: + - s3:GetObject* + - s3:GetBucket* + - s3:List* + Resource: + - Fn::Sub: ${StagingBucket.Arn} + - Fn::Sub: ${StagingBucket.Arn}/* + - Sid: ReadVersion + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters # CreateChangeSet uses this to evaluate any SSM parameters (like `CdkBootstrapVersion`) + Resource: + - Fn::Sub: "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter${CdkBootstrapVersion}" + Version: '2012-10-17' + PolicyName: default + RoleName: + Fn::Sub: cdk-${Qualifier}-deploy-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: deploy + CloudFormationExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + Service: cloudformation.amazonaws.com + Version: '2012-10-17' + ManagedPolicyArns: + Fn::If: + - HasCloudFormationExecutionPolicies + - Ref: CloudFormationExecutionPolicies + - Fn::If: + - HasTrustedAccounts + # The CLI will prevent this case from occurring + - Ref: AWS::NoValue + # The CLI will advertise that we picked this implicitly + - - Fn::Sub: "arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess" + RoleName: + Fn::Sub: cdk-${Qualifier}-cfn-exec-role-${AWS::AccountId}-${AWS::Region} + PermissionsBoundary: + Fn::If: + - PermissionsBoundarySet + - Fn::Sub: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${InputPermissionsBoundary}' + - Ref: AWS::NoValue + CdkBoostrapPermissionsBoundaryPolicy: + # Edit the template prior to boostrap in order to have this example policy created + Condition: ShouldCreatePermissionsBoundary + Type: AWS::IAM::ManagedPolicy + Properties: + PolicyDocument: + Statement: + # If permission boundaries do not have an explicit `allow`, then the effect is `deny` + - Sid: ExplicitAllowAll + Action: + - "*" + Effect: Allow + Resource: "*" + # Default permissions to prevent privilege escalation + - Sid: DenyAccessIfRequiredPermBoundaryIsNotBeingApplied + Action: + - iam:CreateUser + - iam:CreateRole + - iam:PutRolePermissionsBoundary + - iam:PutUserPermissionsBoundary + Condition: + StringNotEquals: + iam:PermissionsBoundary: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Effect: Deny + Resource: "*" + # Forbid the policy itself being edited + - Sid: DenyPermBoundaryIAMPolicyAlteration + Action: + - iam:CreatePolicyVersion + - iam:DeletePolicy + - iam:DeletePolicyVersion + - iam:SetDefaultPolicyVersion + Effect: Deny + Resource: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + # Forbid removing the permissions boundary from any user or role that has it associated + - Sid: DenyRemovalOfPermBoundaryFromAnyUserOrRole + Action: + - iam:DeleteUserPermissionsBoundary + - iam:DeleteRolePermissionsBoundary + Effect: Deny + Resource: "*" + # Add your specific organizational security policy here + # Uncomment the example to deny access to AWS Config + #- Sid: OrganizationalSecurityPolicy + # Action: + # - "config:*" + # Effect: Deny + # Resource: "*" + Version: "2012-10-17" + Description: "Bootstrap Permission Boundary" + ManagedPolicyName: + Fn::Sub: cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Path: / + # The SSM parameter is used in pipeline-deployed templates to verify the version + # of the bootstrap resources. + CdkBootstrapVersion: + Type: AWS::SSM::Parameter + Properties: + Type: String + Name: + Fn::Sub: '/cdk-bootstrap/${Qualifier}/version' + Value: '22' +Outputs: + BucketName: + Description: The name of the S3 bucket owned by the CDK toolkit stack + Value: + Fn::Sub: "${StagingBucket}" + BucketDomainName: + Description: The domain name of the S3 bucket owned by the CDK toolkit stack + Value: + Fn::Sub: "${StagingBucket.RegionalDomainName}" + # @deprecated - This Export can be removed at some future point in time. + # We can't do it today because if there are stacks that use it, the bootstrap + # stack cannot be updated. Not used anymore by apps >= 1.60.0 + FileAssetKeyArn: + Description: The ARN of the KMS key used to encrypt the asset bucket (deprecated) + Value: + Fn::If: + - CreateNewKey + - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}" + - Fn::Sub: "${FileAssetsBucketKmsKeyId}" + Export: + Name: + Fn::Sub: CdkBootstrap-${Qualifier}-FileAssetKeyArn + ImageRepositoryName: + Description: The name of the ECR repository which hosts docker image assets + Value: + Fn::Sub: "${ContainerAssetsRepository}" + # The Output is used by the CLI to verify the version of the bootstrap resources. + BootstrapVersion: + Description: The version of the bootstrap resources that are currently mastered + in this stack + Value: + Fn::GetAtt: [CdkBootstrapVersion, Value] diff --git a/packages/@aws-cdk-testing/cli-integ/resources/bootstrap-templates/session-tags.deploy-role-deny-sqs.yaml b/packages/@aws-cdk-testing/cli-integ/resources/bootstrap-templates/session-tags.deploy-role-deny-sqs.yaml new file mode 100644 index 0000000000000..94e789a0493ce --- /dev/null +++ b/packages/@aws-cdk-testing/cli-integ/resources/bootstrap-templates/session-tags.deploy-role-deny-sqs.yaml @@ -0,0 +1,700 @@ +Description: This stack includes resources needed to deploy AWS CDK apps into this + environment +Parameters: + TrustedAccounts: + Description: List of AWS accounts that are trusted to publish assets and deploy + stacks to this environment + Default: '' + Type: CommaDelimitedList + TrustedAccountsForLookup: + Description: List of AWS accounts that are trusted to look up values in this + environment + Default: '' + Type: CommaDelimitedList + CloudFormationExecutionPolicies: + Description: List of the ManagedPolicy ARN(s) to attach to the CloudFormation + deployment role + Default: '' + Type: CommaDelimitedList + FileAssetsBucketName: + Description: The name of the S3 bucket used for file assets + Default: '' + Type: String + FileAssetsBucketKmsKeyId: + Description: Empty to create a new key (default), 'AWS_MANAGED_KEY' to use a managed + S3 key, or the ID/ARN of an existing key. + Default: '' + Type: String + ContainerAssetsRepositoryName: + Description: A user-provided custom name to use for the container assets ECR repository + Default: '' + Type: String + Qualifier: + Description: An identifier to distinguish multiple bootstrap stacks in the same environment + Default: hnb659fds + Type: String + # "cdk-(qualifier)-image-publishing-role-(account)-(region)" needs to be <= 64 chars + # account = 12, region <= 14, 10 chars for qualifier and 28 for rest of role name + AllowedPattern: "[A-Za-z0-9_-]{1,10}" + ConstraintDescription: Qualifier must be an alphanumeric identifier of at most 10 characters + PublicAccessBlockConfiguration: + Description: Whether or not to enable S3 Staging Bucket Public Access Block Configuration + Default: 'true' + Type: 'String' + AllowedValues: ['true', 'false'] + InputPermissionsBoundary: + Description: Whether or not to use either the CDK supplied or custom permissions boundary + Default: '' + Type: 'String' + UseExamplePermissionsBoundary: + Default: 'false' + AllowedValues: [ 'true', 'false' ] + Type: String + BootstrapVariant: + Type: String + Default: 'AWS CDK: Default Resources' + Description: Describe the provenance of the resources in this bootstrap + stack. Change this when you customize the template. To prevent accidents, + the CDK CLI will not overwrite bootstrap stacks with a different variant. +Conditions: + HasTrustedAccounts: + Fn::Not: + - Fn::Equals: + - '' + - Fn::Join: + - '' + - Ref: TrustedAccounts + HasTrustedAccountsForLookup: + Fn::Not: + - Fn::Equals: + - '' + - Fn::Join: + - '' + - Ref: TrustedAccountsForLookup + HasCloudFormationExecutionPolicies: + Fn::Not: + - Fn::Equals: + - '' + - Fn::Join: + - '' + - Ref: CloudFormationExecutionPolicies + HasCustomFileAssetsBucketName: + Fn::Not: + - Fn::Equals: + - '' + - Ref: FileAssetsBucketName + CreateNewKey: + Fn::Equals: + - '' + - Ref: FileAssetsBucketKmsKeyId + UseAwsManagedKey: + Fn::Equals: + - 'AWS_MANAGED_KEY' + - Ref: FileAssetsBucketKmsKeyId + ShouldCreatePermissionsBoundary: + Fn::Equals: + - 'true' + - Ref: UseExamplePermissionsBoundary + PermissionsBoundarySet: + Fn::Not: + - Fn::Equals: + - '' + - Ref: InputPermissionsBoundary + HasCustomContainerAssetsRepositoryName: + Fn::Not: + - Fn::Equals: + - '' + - Ref: ContainerAssetsRepositoryName + UsePublicAccessBlockConfiguration: + Fn::Equals: + - 'true' + - Ref: PublicAccessBlockConfiguration +Resources: + FileAssetsBucketEncryptionKey: + Type: AWS::KMS::Key + Properties: + KeyPolicy: + Statement: + - Action: + - kms:Create* + - kms:Describe* + - kms:Enable* + - kms:List* + - kms:Put* + - kms:Update* + - kms:Revoke* + - kms:Disable* + - kms:Get* + - kms:Delete* + - kms:ScheduleKeyDeletion + - kms:CancelKeyDeletion + - kms:GenerateDataKey + - kms:TagResource + - kms:UntagResource + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + Resource: "*" + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Principal: + # Not actually everyone -- see below for Conditions + AWS: "*" + Resource: "*" + Condition: + StringEquals: + # See https://docs.aws.amazon.com/kms/latest/developerguide/policy-conditions.html#conditions-kms-caller-account + kms:CallerAccount: + Ref: AWS::AccountId + kms:ViaService: + - Fn::Sub: s3.${AWS::Region}.amazonaws.com + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Principal: + AWS: + Fn::Sub: "${FilePublishingRole.Arn}" + Resource: "*" + Condition: CreateNewKey + FileAssetsBucketEncryptionKeyAlias: + Condition: CreateNewKey + Type: AWS::KMS::Alias + Properties: + AliasName: + Fn::Sub: "alias/cdk-${Qualifier}-assets-key" + TargetKeyId: + Ref: FileAssetsBucketEncryptionKey + StagingBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: + Fn::If: + - HasCustomFileAssetsBucketName + - Fn::Sub: "${FileAssetsBucketName}" + - Fn::Sub: cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region} + AccessControl: Private + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: aws:kms + KMSMasterKeyID: + Fn::If: + - CreateNewKey + - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}" + - Fn::If: + - UseAwsManagedKey + - Ref: AWS::NoValue + - Fn::Sub: "${FileAssetsBucketKmsKeyId}" + PublicAccessBlockConfiguration: + Fn::If: + - UsePublicAccessBlockConfiguration + - BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + - Ref: AWS::NoValue + VersioningConfiguration: + Status: Enabled + LifecycleConfiguration: + Rules: + # Exising objects will never be overwritten but Security Hub wants this rule to exist + - Id: CleanupOldVersions + Status: Enabled + NoncurrentVersionExpiration: + NoncurrentDays: 365 + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + StagingBucketPolicy: + Type: 'AWS::S3::BucketPolicy' + Properties: + Bucket: { Ref: 'StagingBucket' } + PolicyDocument: + Id: 'AccessControl' + Version: '2012-10-17' + Statement: + - Sid: 'AllowSSLRequestsOnly' + Action: 's3:*' + Effect: 'Deny' + Resource: + - { 'Fn::Sub': '${StagingBucket.Arn}' } + - { 'Fn::Sub': '${StagingBucket.Arn}/*' } + Condition: + Bool: { 'aws:SecureTransport': 'false' } + Principal: '*' + ContainerAssetsRepository: + Type: AWS::ECR::Repository + Properties: + ImageTagMutability: IMMUTABLE + # Untagged images should never exist but Security Hub wants this rule to exist + LifecyclePolicy: + LifecyclePolicyText: | + { + "rules": [ + { + "rulePriority": 1, + "description": "Untagged images should not exist, but expire any older than one year", + "selection": { + "tagStatus": "untagged", + "countType": "sinceImagePushed", + "countUnit": "days", + "countNumber": 365 + }, + "action": { "type": "expire" } + } + ] + } + RepositoryName: + Fn::If: + - HasCustomContainerAssetsRepositoryName + - Fn::Sub: "${ContainerAssetsRepositoryName}" + - Fn::Sub: cdk-${Qualifier}-container-assets-${AWS::AccountId}-${AWS::Region} + RepositoryPolicyText: + Version: "2012-10-17" + Statement: + # Necessary for Lambda container images + # https://docs.aws.amazon.com/lambda/latest/dg/configuration-images.html#configuration-images-permissions + - Sid: LambdaECRImageRetrievalPolicy + Effect: Allow + Principal: { Service: "lambda.amazonaws.com" } + Action: + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + Condition: + StringLike: + "aws:sourceArn": { "Fn::Sub": "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:*" } + FilePublishingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + # The TagSession action is required to be able to assume this role with session tags. + # Without this trust policy, attemping to assume this role with session tags will fail. + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: file-publishing + ImagePublishingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + # The TagSession action is required to be able to assume this role with session tags. + # Without this trust policy, attemping to assume this role with session tags will fail. + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: image-publishing + LookupRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + # The TagSession action is required to be able to assume this role with session tags. + # Without this trust policy, attemping to assume this role with session tags will fail. + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccountsForLookup + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccountsForLookup + - Ref: AWS::NoValue + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-lookup-role-${AWS::AccountId}-${AWS::Region} + ManagedPolicyArns: + - Fn::Sub: "arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess" + Policies: + - PolicyDocument: + Statement: + - Sid: DontReadSecrets + Effect: Deny + Action: + - kms:Decrypt + Resource: "*" + Version: '2012-10-17' + PolicyName: LookupRolePolicy + Tags: + - Key: aws-cdk:bootstrap-role + Value: lookup + FilePublishingRoleDefaultPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: + - Action: + - s3:GetObject* + - s3:GetBucket* + - s3:GetEncryptionConfiguration + - s3:List* + - s3:DeleteObject* + - s3:PutObject* + - s3:Abort* + Resource: + - Fn::Sub: "${StagingBucket.Arn}" + - Fn::Sub: "${StagingBucket.Arn}/*" + Condition: + StringEquals: + aws:ResourceAccount: + - Fn::Sub: ${AWS::AccountId} + Effect: Allow + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Resource: + Fn::If: + - CreateNewKey + - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}" + - Fn::Sub: arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${FileAssetsBucketKmsKeyId} + Version: '2012-10-17' + Roles: + - Ref: FilePublishingRole + PolicyName: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + ImagePublishingRoleDefaultPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: + - Action: + - ecr:PutImage + - ecr:InitiateLayerUpload + - ecr:UploadLayerPart + - ecr:CompleteLayerUpload + - ecr:BatchCheckLayerAvailability + - ecr:DescribeRepositories + - ecr:DescribeImages + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + Resource: + Fn::Sub: "${ContainerAssetsRepository.Arn}" + Effect: Allow + - Action: + - ecr:GetAuthorizationToken + Resource: "*" + Effect: Allow + Version: '2012-10-17' + Roles: + - Ref: ImagePublishingRole + PolicyName: + Fn::Sub: cdk-${Qualifier}-image-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + DeploymentActionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + # The TagSession action is required to be able to assume this role with session tags. + # Without this trust policy, attemping to assume this role with session tags will fail. + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + Policies: + - PolicyDocument: + Statement: + - Sid: CloudFormationPermissions + Effect: Allow + Action: + - cloudformation:CreateChangeSet + - cloudformation:DeleteChangeSet + - cloudformation:DescribeChangeSet + - cloudformation:DescribeStacks + - cloudformation:ExecuteChangeSet + - cloudformation:CreateStack + - cloudformation:UpdateStack + Resource: "*" + - Sid: PipelineCrossAccountArtifactsBucket + # Read/write buckets in different accounts. Permissions to buckets in + # same account are granted by bucket policies. + # + # Write permissions necessary to write outputs to the cross-region artifact replication bucket + # https://aws.amazon.com/premiumsupport/knowledge-center/codepipeline-deploy-cloudformation/. + Effect: Allow + Action: + - s3:GetObject* + - s3:GetBucket* + - s3:List* + - s3:Abort* + - s3:DeleteObject* + - s3:PutObject* + Resource: "*" + Condition: + StringNotEquals: + s3:ResourceAccount: + Ref: 'AWS::AccountId' + - Sid: PipelineCrossAccountArtifactsKey + # Use keys only for the purposes of reading encrypted files from S3. + Effect: Allow + Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Resource: "*" + Condition: + StringEquals: + kms:ViaService: + Fn::Sub: s3.${AWS::Region}.amazonaws.com + - Action: iam:PassRole + Resource: + Fn::Sub: "${CloudFormationExecutionRole.Arn}" + Effect: Allow + # Permissions to allow the Deploy Role to perform SQS Actions. + # Users of this bootstrap template intend to uses the Deploy Role + # instead of the CFN ExecutionRole, so the deploy role needs permissions + # to perform CFN actions; in this simple case, we only permit SQS Actions. + - Sid: SQSPermissions + Action: sqs:* + Resource: "*" + Effect: Allow + # This condition requires that the Deploy Role is assumed with the session tags + # 'Department: Engineering'; if these tags are not passed in, the DeployRole will + # not be able to perform SQS actions. + Condition: + StringEquals: + aws:PrincipalTag/Department: "Engineering" + - Sid: CliPermissions + Action: + # Permissions needed by the CLI when doing `cdk deploy`. + # Our CI/CD does not need DeleteStack, + # but we also want to use this role from the CLI, + # and there you can call `cdk destroy` + - cloudformation:DescribeStackEvents + - cloudformation:GetTemplate + - cloudformation:DeleteStack + - cloudformation:UpdateTerminationProtection + - sts:GetCallerIdentity + # `cdk import` + - cloudformation:GetTemplateSummary + Resource: "*" + Effect: Allow + - Sid: CliStagingBucket + Effect: Allow + Action: + - s3:GetObject* + - s3:GetBucket* + - s3:List* + Resource: + - Fn::Sub: ${StagingBucket.Arn} + - Fn::Sub: ${StagingBucket.Arn}/* + - Sid: ReadVersion + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters # CreateChangeSet uses this to evaluate any SSM parameters (like `CdkBootstrapVersion`) + Resource: + - Fn::Sub: "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter${CdkBootstrapVersion}" + Version: '2012-10-17' + PolicyName: default + RoleName: + Fn::Sub: cdk-${Qualifier}-deploy-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: deploy + CloudFormationExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + Service: cloudformation.amazonaws.com + Version: '2012-10-17' + ManagedPolicyArns: + Fn::If: + - HasCloudFormationExecutionPolicies + - Ref: CloudFormationExecutionPolicies + - Fn::If: + - HasTrustedAccounts + # The CLI will prevent this case from occurring + - Ref: AWS::NoValue + # The CLI will advertise that we picked this implicitly + - - Fn::Sub: "arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess" + RoleName: + Fn::Sub: cdk-${Qualifier}-cfn-exec-role-${AWS::AccountId}-${AWS::Region} + PermissionsBoundary: + Fn::If: + - PermissionsBoundarySet + - Fn::Sub: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${InputPermissionsBoundary}' + - Ref: AWS::NoValue + CdkBoostrapPermissionsBoundaryPolicy: + # Edit the template prior to boostrap in order to have this example policy created + Condition: ShouldCreatePermissionsBoundary + Type: AWS::IAM::ManagedPolicy + Properties: + PolicyDocument: + Statement: + # If permission boundaries do not have an explicit `allow`, then the effect is `deny` + - Sid: ExplicitAllowAll + Action: + - "*" + Effect: Allow + Resource: "*" + # Default permissions to prevent privilege escalation + - Sid: DenyAccessIfRequiredPermBoundaryIsNotBeingApplied + Action: + - iam:CreateUser + - iam:CreateRole + - iam:PutRolePermissionsBoundary + - iam:PutUserPermissionsBoundary + Condition: + StringNotEquals: + iam:PermissionsBoundary: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Effect: Deny + Resource: "*" + # Forbid the policy itself being edited + - Sid: DenyPermBoundaryIAMPolicyAlteration + Action: + - iam:CreatePolicyVersion + - iam:DeletePolicy + - iam:DeletePolicyVersion + - iam:SetDefaultPolicyVersion + Effect: Deny + Resource: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + # Forbid removing the permissions boundary from any user or role that has it associated + - Sid: DenyRemovalOfPermBoundaryFromAnyUserOrRole + Action: + - iam:DeleteUserPermissionsBoundary + - iam:DeleteRolePermissionsBoundary + Effect: Deny + Resource: "*" + # Add your specific organizational security policy here + # Uncomment the example to deny access to AWS Config + #- Sid: OrganizationalSecurityPolicy + # Action: + # - "config:*" + # Effect: Deny + # Resource: "*" + Version: "2012-10-17" + Description: "Bootstrap Permission Boundary" + ManagedPolicyName: + Fn::Sub: cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Path: / + # The SSM parameter is used in pipeline-deployed templates to verify the version + # of the bootstrap resources. + CdkBootstrapVersion: + Type: AWS::SSM::Parameter + Properties: + Type: String + Name: + Fn::Sub: '/cdk-bootstrap/${Qualifier}/version' + Value: '22' +Outputs: + BucketName: + Description: The name of the S3 bucket owned by the CDK toolkit stack + Value: + Fn::Sub: "${StagingBucket}" + BucketDomainName: + Description: The domain name of the S3 bucket owned by the CDK toolkit stack + Value: + Fn::Sub: "${StagingBucket.RegionalDomainName}" + # @deprecated - This Export can be removed at some future point in time. + # We can't do it today because if there are stacks that use it, the bootstrap + # stack cannot be updated. Not used anymore by apps >= 1.60.0 + FileAssetKeyArn: + Description: The ARN of the KMS key used to encrypt the asset bucket (deprecated) + Value: + Fn::If: + - CreateNewKey + - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}" + - Fn::Sub: "${FileAssetsBucketKmsKeyId}" + Export: + Name: + Fn::Sub: CdkBootstrap-${Qualifier}-FileAssetKeyArn + ImageRepositoryName: + Description: The name of the ECR repository which hosts docker image assets + Value: + Fn::Sub: "${ContainerAssetsRepository}" + # The Output is used by the CLI to verify the version of the bootstrap resources. + BootstrapVersion: + Description: The version of the bootstrap resources that are currently mastered + in this stack + Value: + Fn::GetAtt: [CdkBootstrapVersion, Value] \ No newline at end of file diff --git a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js index 9074ba75961f3..c0d6307db8f57 100755 --- a/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js +++ b/packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js @@ -424,6 +424,67 @@ class LambdaStack extends cdk.Stack { } } +class SessionTagsStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, { + ...props, + synthesizer: new DefaultStackSynthesizer({ + deployRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }] + }, + fileAssetPublishingRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }] + }, + imageAssetPublishingRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }] + }, + lookupRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }] + } + }) + }); + + // VPC lookup to test LookupRole + ec2.Vpc.fromLookup(this, 'DefaultVPC', { isDefault: true }); + + // Lambda Function to test AssetPublishingRole + const fn = new lambda.Function(this, 'my-function', { + code: lambda.Code.asset(path.join(__dirname, 'lambda')), + runtime: lambda.Runtime.NODEJS_LATEST, + handler: 'index.handler' + }); + + // DockerImageAsset to test ImageAssetPublishingRole + new docker.DockerImageAsset(this, 'image', { + directory: path.join(__dirname, 'docker') + }); + } +} + +class NoExecutionRoleCustomSynthesizer extends cdk.DefaultStackSynthesizer { + + emitArtifact(session, options) { + super.emitArtifact(session, { + ...options, + cloudFormationExecutionRoleArn: undefined, + }) + } +} + +class SessionTagsWithNoExecutionRoleCustomSynthesizerStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, { + ...props, + synthesizer: new NoExecutionRoleCustomSynthesizer({ + deployRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }] + }, + }) + }); + + new sqs.Queue(this, 'sessionTagsQueue'); + } +} class LambdaHotswapStack extends cdk.Stack { constructor(parent, id, props) { super(parent, id, props); @@ -708,6 +769,14 @@ switch (stackSet) { new MissingSSMParameterStack(app, `${stackPrefix}-missing-ssm-parameter`, { env: defaultEnv }); new LambdaStack(app, `${stackPrefix}-lambda`); + + if (process.env.ENABLE_VPC_TESTING == 'IMPORT') { + // this stack performs a VPC lookup so we gate synth + const env = { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }; + new SessionTagsStack(app, `${stackPrefix}-session-tags`, { env }); + } + + new SessionTagsWithNoExecutionRoleCustomSynthesizerStack(app, `${stackPrefix}-session-tags-with-custom-synthesizer`); new LambdaHotswapStack(app, `${stackPrefix}-lambda-hotswap`); new EcsHotswapStack(app, `${stackPrefix}-ecs-hotswap`); new AppSyncHotswapStack(app, `${stackPrefix}-appsync-hotswap`); diff --git a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/bootstrapping.integtest.ts b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/bootstrapping.integtest.ts index 5e8d1304b4973..1056faeb3a223 100644 --- a/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/bootstrapping.integtest.ts +++ b/packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/bootstrapping.integtest.ts @@ -87,6 +87,43 @@ integTest('can and deploy if omitting execution policies', withoutBootstrap(asyn }); })); +integTest('can deploy with session tags on the deploy, lookup, file asset, and image asset publishing roles', withoutBootstrap(async (fixture) => { + const bootstrapStackName = fixture.bootstrapStackName; + + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + bootstrapTemplate: path.join(__dirname, '..', '..', 'resources', 'bootstrap-templates', 'session-tags.all-roles-deny-all.yaml'), + }); + + await fixture.cdkDeploy('session-tags', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--context', `@aws-cdk/core:bootstrapQualifier=${fixture.qualifier}`, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + modEnv: { + ENABLE_VPC_TESTING: 'IMPORT', + }, + }); +})); + +integTest('can deploy without execution role and with session tags on deploy role', withoutBootstrap(async (fixture) => { + const bootstrapStackName = fixture.bootstrapStackName; + + await fixture.cdkBootstrapModern({ + toolkitStackName: bootstrapStackName, + bootstrapTemplate: path.join(__dirname, '..', '..', 'resources', 'bootstrap-templates', 'session-tags.deploy-role-deny-sqs.yaml'), + }); + + await fixture.cdkDeploy('session-tags-with-custom-synthesizer', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + '--context', `@aws-cdk/core:bootstrapQualifier=${fixture.qualifier}`, + '--context', '@aws-cdk/core:newStyleStackSynthesis=1', + ], + }); +})); + integTest('deploy new style synthesis to new style bootstrap', withoutBootstrap(async (fixture) => { const bootstrapStackName = fixture.bootstrapStackName; diff --git a/packages/@aws-cdk/cx-api/FEATURE_FLAGS.md b/packages/@aws-cdk/cx-api/FEATURE_FLAGS.md deleted file mode 100644 index beadd60aa4ed2..0000000000000 --- a/packages/@aws-cdk/cx-api/FEATURE_FLAGS.md +++ /dev/null @@ -1,1401 +0,0 @@ -# CDK Feature Flags - -[CDK Feature Flags](https://docs.aws.amazon.com/cdk/v2/guide/featureflags.html) are a mechanism that allows the CDK to evolve and change the behavior of certain classes and methods, without causing disruption to existing deployed infrastructure. - -Feature flags are [context values](https://docs.aws.amazon.com/cdk/v2/guide/context.html) and can be configured using any of the context management methods, at any level of the construct tree. Commonly, they are specified in the `cdk.json` file. -`cdk init` will create new CDK projects with a `cdk.json` file containing all recommended feature flags enabled. - -## Current list of feature flags - -Flags come in three types: - -- **Default change**: The default behavior of an API has been changed in order to improve its ergonomics. The old behavior can still be achieved, but requires source changes. -- **Fix/deprecation**: The old behavior was incorrect or not recommended for new users. The only way to keep it is to not set this flag. -- **Config**: Configurable behavior that we recommend you turn on. - - - -| Flag | Summary | Since | Type | -| ----- | ----- | ----- | ----- | -| [@aws-cdk/core:newStyleStackSynthesis](#aws-cdkcorenewstylestacksynthesis) | Switch to new stack synthesis method which enables CI/CD | 2.0.0 | (fix) | -| [@aws-cdk/core:stackRelativeExports](#aws-cdkcorestackrelativeexports) | Name exports based on the construct paths relative to the stack, rather than the global construct path | 2.0.0 | (fix) | -| [@aws-cdk/aws-rds:lowercaseDbIdentifier](#aws-cdkaws-rdslowercasedbidentifier) | Force lowercasing of RDS Cluster names in CDK | 2.0.0 | (fix) | -| [@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId](#aws-cdkaws-apigatewayusageplankeyorderinsensitiveid) | Allow adding/removing multiple UsagePlanKeys independently | 2.0.0 | (fix) | -| [@aws-cdk/aws-lambda:recognizeVersionProps](#aws-cdkaws-lambdarecognizeversionprops) | Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`. | 2.0.0 | (fix) | -| [@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2\_2021](#aws-cdkaws-cloudfrontdefaultsecuritypolicytlsv12_2021) | Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default. | 2.0.0 | (fix) | -| [@aws-cdk/core:target-partitions](#aws-cdkcoretarget-partitions) | What regions to include in lookup tables of environment agnostic stacks | 2.4.0 | (config) | -| [@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver](#aws-cdk-containersecs-service-extensionsenabledefaultlogdriver) | ECS extensions will automatically add an `awslogs` driver if no logging is specified | 2.8.0 | (default) | -| [@aws-cdk/aws-ec2:uniqueImdsv2TemplateName](#aws-cdkaws-ec2uniqueimdsv2templatename) | Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names. | 2.8.0 | (fix) | -| [@aws-cdk/aws-iam:minimizePolicies](#aws-cdkaws-iamminimizepolicies) | Minimize IAM policies by combining Statements | 2.18.0 | (config) | -| [@aws-cdk/core:checkSecretUsage](#aws-cdkcorechecksecretusage) | Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations | 2.21.0 | (config) | -| [@aws-cdk/aws-lambda:recognizeLayerVersion](#aws-cdkaws-lambdarecognizelayerversion) | Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`. | 2.27.0 | (fix) | -| [@aws-cdk/core:validateSnapshotRemovalPolicy](#aws-cdkcorevalidatesnapshotremovalpolicy) | Error on snapshot removal policies on resources that do not support it. | 2.28.0 | (default) | -| [@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName](#aws-cdkaws-codepipelinecrossaccountkeyaliasstacksaferesourcename) | Generate key aliases that include the stack name | 2.29.0 | (fix) | -| [@aws-cdk/aws-s3:createDefaultLoggingPolicy](#aws-cdkaws-s3createdefaultloggingpolicy) | Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist. | 2.31.0 | (fix) | -| [@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption](#aws-cdkaws-sns-subscriptionsrestrictsqsdescryption) | Restrict KMS key policy for encrypted Queues a bit more | 2.32.0 | (fix) | -| [@aws-cdk/aws-ecs:arnFormatIncludesClusterName](#aws-cdkaws-ecsarnformatincludesclustername) | ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID. | 2.35.0 | (fix) | -| [@aws-cdk/aws-apigateway:disableCloudWatchRole](#aws-cdkaws-apigatewaydisablecloudwatchrole) | Make default CloudWatch Role behavior safe for multiple API Gateways in one environment | 2.38.0 | (fix) | -| [@aws-cdk/core:enablePartitionLiterals](#aws-cdkcoreenablepartitionliterals) | Make ARNs concrete if AWS partition is known | 2.38.0 | (fix) | -| [@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker](#aws-cdkaws-ecsdisableexplicitdeploymentcontrollerforcircuitbreaker) | Avoid setting the "ECS" deployment controller when adding a circuit breaker | 2.51.0 | (fix) | -| [@aws-cdk/aws-events:eventsTargetQueueSameAccount](#aws-cdkaws-eventseventstargetqueuesameaccount) | Event Rules may only push to encrypted SQS queues in the same account | 2.51.0 | (fix) | -| [@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName](#aws-cdkaws-iamimportedrolestacksafedefaultpolicyname) | Enable this feature to by default create default policy names for imported roles that depend on the stack the role is in. | 2.60.0 | (fix) | -| [@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy](#aws-cdkaws-s3serveraccesslogsusebucketpolicy) | Use S3 Bucket Policy instead of ACLs for Server Access Logging | 2.60.0 | (fix) | -| [@aws-cdk/customresources:installLatestAwsSdkDefault](#aws-cdkcustomresourcesinstalllatestawssdkdefault) | Whether to install the latest SDK by default in AwsCustomResource | 2.60.0 | (default) | -| [@aws-cdk/aws-route53-patters:useCertificate](#aws-cdkaws-route53-pattersusecertificate) | Use the official `Certificate` resource instead of `DnsValidatedCertificate` | 2.61.0 | (default) | -| [@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup](#aws-cdkaws-codedeployremovealarmsfromdeploymentgroup) | Remove CloudWatch alarms from deployment group | 2.65.0 | (fix) | -| [@aws-cdk/aws-rds:databaseProxyUniqueResourceName](#aws-cdkaws-rdsdatabaseproxyuniqueresourcename) | Use unique resource name for Database Proxy | 2.65.0 | (fix) | -| [@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId](#aws-cdkaws-apigatewayauthorizerchangedeploymentlogicalid) | Include authorizer configuration in the calculation of the API deployment logical ID. | 2.66.0 | (fix) | -| [@aws-cdk/aws-ec2:launchTemplateDefaultUserData](#aws-cdkaws-ec2launchtemplatedefaultuserdata) | Define user data for a launch template by default when a machine image is provided. | 2.67.0 | (fix) | -| [@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments](#aws-cdkaws-secretsmanageruseattachedsecretresourcepolicyforsecrettargetattachments) | SecretTargetAttachments uses the ResourcePolicy of the attached Secret. | 2.67.0 | (fix) | -| [@aws-cdk/aws-redshift:columnId](#aws-cdkaws-redshiftcolumnid) | Whether to use an ID to track Redshift column changes | 2.68.0 | (fix) | -| [@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2](#aws-cdkaws-stepfunctions-tasksenableemrservicepolicyv2) | Enable AmazonEMRServicePolicy_v2 managed policies | 2.72.0 | (fix) | -| [@aws-cdk/aws-apigateway:requestValidatorUniqueId](#aws-cdkaws-apigatewayrequestvalidatoruniqueid) | Generate a unique id for each RequestValidator added to a method | 2.78.0 | (fix) | -| [@aws-cdk/aws-ec2:restrictDefaultSecurityGroup](#aws-cdkaws-ec2restrictdefaultsecuritygroup) | Restrict access to the VPC default security group | 2.78.0 | (default) | -| [@aws-cdk/aws-kms:aliasNameRef](#aws-cdkaws-kmsaliasnameref) | KMS Alias name and keyArn will have implicit reference to KMS Key | 2.83.0 | (fix) | -| [@aws-cdk/core:includePrefixInUniqueNameGeneration](#aws-cdkcoreincludeprefixinuniquenamegeneration) | Include the stack prefix in the stack name generation process | 2.84.0 | (fix) | -| [@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig](#aws-cdkaws-autoscalinggeneratelaunchtemplateinsteadoflaunchconfig) | Generate a launch template when creating an AutoScalingGroup | 2.88.0 | (fix) | -| [@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby](#aws-cdkaws-opensearchserviceenableopensearchmultiazwithstandby) | Enables support for Multi-AZ with Standby deployment for opensearch domains | 2.88.0 | (default) | -| [@aws-cdk/aws-efs:denyAnonymousAccess](#aws-cdkaws-efsdenyanonymousaccess) | EFS denies anonymous clients accesses | 2.93.0 | (default) | -| [@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId](#aws-cdkaws-efsmounttargetorderinsensitivelogicalid) | When enabled, mount targets will have a stable logicalId that is linked to the associated subnet. | 2.93.0 | (fix) | -| [@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion](#aws-cdkaws-lambda-nodejsuselatestruntimeversion) | Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default | 2.93.0 | (default) | -| [@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier](#aws-cdkaws-appsyncusearnforsourceapiassociationidentifier) | When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id. | 2.97.0 | (fix) | -| [@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters](#aws-cdkaws-rdsauroraclusterchangescopeofinstanceparametergroupwitheachparameters) | When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change. | 2.97.0 | (fix) | -| [@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials](#aws-cdkaws-rdspreventrenderingdeprecatedcredentials) | When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials. | 2.98.0 | (fix) | -| [@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource](#aws-cdkaws-codepipeline-actionsusenewdefaultbranchforcodecommitsource) | When enabled, the CodeCommit source action is using the default branch name 'main'. | 2.103.1 | (fix) | -| [@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction](#aws-cdkaws-cloudwatch-actionschangelambdapermissionlogicalidforlambdaaction) | When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID. | 2.124.0 | (fix) | -| [@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse](#aws-cdkaws-codepipelinecrossaccountkeysdefaultvaluetofalse) | Enables Pipeline to set the default value for crossAccountKeys to false. | 2.127.0 | (default) | -| [@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2](#aws-cdkaws-codepipelinedefaultpipelinetypetov2) | Enables Pipeline to set the default pipeline type to V2. | 2.133.0 | (default) | -| [@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope](#aws-cdkaws-kmsreducecrossaccountregionpolicyscope) | When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only. | 2.134.0 | (fix) | -| [@aws-cdk/aws-eks:nodegroupNameAttribute](#aws-cdkaws-eksnodegroupnameattribute) | When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix. | 2.139.0 | (fix) | -| [@aws-cdk/aws-ec2:ebsDefaultGp3Volume](#aws-cdkaws-ec2ebsdefaultgp3volume) | When enabled, the default volume type of the EBS volume will be GP3 | 2.140.0 | (default) | -| [@aws-cdk/pipelines:reduceAssetRoleTrustScope](#aws-cdkpipelinesreduceassetroletrustscope) | Remove the root account principal from PipelineAssetsFileRole trust policy | 2.141.0 | (default) | -| [@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm](#aws-cdkaws-ecsremovedefaultdeploymentalarm) | When enabled, remove default deployment alarm settings | 2.143.0 | (default) | -| [@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault](#aws-cdkcustom-resourceslogapiresponsedatapropertytruedefault) | When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default | 2.145.0 | (fix) | -| [@aws-cdk/aws-s3:keepNotificationInImportedBucket](#aws-cdkaws-s3keepnotificationinimportedbucket) | When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack. | 2.155.0 | (fix) | -| [@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask](#aws-cdkaws-stepfunctions-tasksusenews3uriparametersforbedrockinvokemodeltask) | When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model. | 2.156.0 | (fix) | -| [@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions](#aws-cdkaws-ecsreduceec2fargatecloudwatchpermissions) | When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration | 2.159.0 | (fix) | - - - -## Currently recommended cdk.json - -The following json shows the current recommended set of flags, as `cdk init` would generate it for new projects. - - -```json -{ - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": [ - "aws", - "aws-cn" - ], - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, - "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, - "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, - "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, - "@aws-cdk/aws-route53-patters:useCertificate": true, - "@aws-cdk/customresources:installLatestAwsSdkDefault": false, - "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, - "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, - "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, - "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, - "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, - "@aws-cdk/aws-redshift:columnId": true, - "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, - "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, - "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, - "@aws-cdk/aws-kms:aliasNameRef": true, - "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, - "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, - "@aws-cdk/aws-efs:denyAnonymousAccess": true, - "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, - "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, - "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, - "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, - "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, - "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, - "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, - "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, - "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, - "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, - "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, - "@aws-cdk/aws-eks:nodegroupNameAttribute": true, - "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, - "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, - "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, - "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, - "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true - } -} -``` - - -## Flags removed in v2 - -These **default change** flags have been removed in v2. These used to be configurable in v1, but in v2 their -behavior has become the default. Remove these from your `cdk.json` file. If the old behavior is important -for your infrastructure, see the flag's description on how to achieve it. - - - -| Flag | Summary | Type | Since | -| ----- | ----- | ----- | ----- | -| [@aws-cdk/core:enableStackNameDuplicates](#aws-cdkcoreenablestacknameduplicates) | Allow multiple stacks with the same name | (default) | 1.16.0 | -| [aws-cdk:enableDiffNoFail](#aws-cdkenablediffnofail) | Make `cdk diff` not fail when there are differences | (default) | 1.19.0 | -| [@aws-cdk/aws-ecr-assets:dockerIgnoreSupport](#aws-cdkaws-ecr-assetsdockerignoresupport) | DockerImageAsset properly supports `.dockerignore` files by default | (default) | 1.73.0 | -| [@aws-cdk/aws-secretsmanager:parseOwnedSecretName](#aws-cdkaws-secretsmanagerparseownedsecretname) | Fix the referencing of SecretsManager names from ARNs | (default) | 1.77.0 | -| [@aws-cdk/aws-kms:defaultKeyPolicies](#aws-cdkaws-kmsdefaultkeypolicies) | Tighten default KMS key policies | (default) | 1.78.0 | -| [@aws-cdk/aws-s3:grantWriteWithoutAcl](#aws-cdkaws-s3grantwritewithoutacl) | Remove `PutObjectAcl` from Bucket.grantWrite | (default) | 1.85.0 | -| [@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount](#aws-cdkaws-ecs-patternsremovedefaultdesiredcount) | Do not specify a default DesiredCount for ECS services | (default) | 1.92.0 | -| [@aws-cdk/aws-efs:defaultEncryptionAtRest](#aws-cdkaws-efsdefaultencryptionatrest) | Enable this feature flag to have elastic file systems encrypted at rest by default. | (default) | 1.98.0 | - - - -## Flags with a different default in v2 - -These **fix/deprecation** flags are still configurable in v2, but their default has changed compared to v1. If you -are migrating a v1 CDK project to v2, explicitly set any of these flags which does not currently appear in your -`cdk.json` to `false`, to avoid unexpected infrastructure changes. - - - -| Flag | Summary | Type | Since | v1 default | v2 default | -| ----- | ----- | ----- | ----- | ----- | ----- | -| [@aws-cdk/core:newStyleStackSynthesis](#aws-cdkcorenewstylestacksynthesis) | Switch to new stack synthesis method which enables CI/CD | (fix) | 1.39.0 | `false` | `true` | -| [@aws-cdk/core:stackRelativeExports](#aws-cdkcorestackrelativeexports) | Name exports based on the construct paths relative to the stack, rather than the global construct path | (fix) | 1.58.0 | `false` | `true` | -| [@aws-cdk/aws-rds:lowercaseDbIdentifier](#aws-cdkaws-rdslowercasedbidentifier) | Force lowercasing of RDS Cluster names in CDK | (fix) | 1.97.0 | `false` | `true` | -| [@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId](#aws-cdkaws-apigatewayusageplankeyorderinsensitiveid) | Allow adding/removing multiple UsagePlanKeys independently | (fix) | 1.98.0 | `false` | `true` | -| [@aws-cdk/aws-lambda:recognizeVersionProps](#aws-cdkaws-lambdarecognizeversionprops) | Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`. | (fix) | 1.106.0 | `false` | `true` | -| [@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2\_2021](#aws-cdkaws-cloudfrontdefaultsecuritypolicytlsv12_2021) | Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default. | (fix) | 1.117.0 | `false` | `true` | -| [@aws-cdk/pipelines:reduceAssetRoleTrustScope](#aws-cdkpipelinesreduceassetroletrustscope) | Remove the root account principal from PipelineAssetsFileRole trust policy | (default) | | `false` | `true` | -| [@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask](#aws-cdkaws-stepfunctions-tasksusenews3uriparametersforbedrockinvokemodeltask) | When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model. | (fix) | | `false` | `true` | - - - -Here is an example of a `cdk.json` file that restores v1 behavior for these flags: - - -```json -{ - "context": { - "@aws-cdk/core:newStyleStackSynthesis": false, - "@aws-cdk/core:stackRelativeExports": false, - "@aws-cdk/aws-rds:lowercaseDbIdentifier": false, - "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": false, - "@aws-cdk/aws-lambda:recognizeVersionProps": false, - "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": false, - "@aws-cdk/pipelines:reduceAssetRoleTrustScope": false, - "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": false - } -} -``` - -## Feature flag details - -Here are more details about each of the flags: - - -### @aws-cdk/core:enableStackNameDuplicates - -*Allow multiple stacks with the same name* (default) - -If this is set, multiple stacks can use the same stack name (e.g. deployed to -different environments). This means that the name of the synthesized template -file will be based on the construct path and not on the defined `stackName` -of the stack. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.16.0 | `false` | `true` | -| (default in v2) | `true` | | - -**Compatibility with old behavior:** Pass stack identifiers to the CLI instead of stack names. - - -### aws-cdk:enableDiffNoFail - -*Make `cdk diff` not fail when there are differences* (default) - -Determines what status code `cdk diff` should return when the specified stack -differs from the deployed stack or the local CloudFormation template: - -* `aws-cdk:enableDiffNoFail=true` => status code == 0 -* `aws-cdk:enableDiffNoFail=false` => status code == 1 - -You can override this behavior with the --fail flag: - -* `--fail` => status code == 1 -* `--no-fail` => status code == 0 - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.19.0 | `false` | `true` | -| (default in v2) | `true` | | - -**Compatibility with old behavior:** Specify `--fail` to the CLI. - - -### @aws-cdk/aws-ecr-assets:dockerIgnoreSupport - -*DockerImageAsset properly supports `.dockerignore` files by default* (default) - -If this flag is not set, the default behavior for `DockerImageAsset` is to use -glob semantics for `.dockerignore` files. If this flag is set, the default behavior -is standard Docker ignore semantics. - -This is a feature flag as the old behavior was technically incorrect but -users may have come to depend on it. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.73.0 | `false` | `true` | -| (default in v2) | `true` | | - -**Compatibility with old behavior:** Update your `.dockerignore` file to match standard Docker ignore rules, if necessary. - - -### @aws-cdk/aws-secretsmanager:parseOwnedSecretName - -*Fix the referencing of SecretsManager names from ARNs* (default) - -Secret.secretName for an "owned" secret will attempt to parse the secretName from the ARN, -rather than the default full resource name, which includes the SecretsManager suffix. - -If this flag is not set, Secret.secretName will include the SecretsManager suffix, which cannot be directly -used by SecretsManager.DescribeSecret, and must be parsed by the user first (e.g., Fn:Join, Fn:Select, Fn:Split). - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.77.0 | `false` | `true` | -| (default in v2) | `true` | | - -**Compatibility with old behavior:** Use `parseArn(secret.secretName).resourceName` to emulate the incorrect old parsing. - - -### @aws-cdk/aws-kms:defaultKeyPolicies - -*Tighten default KMS key policies* (default) - -KMS Keys start with a default key policy that grants the account access to administer the key, -mirroring the behavior of the KMS SDK/CLI/Console experience. Users may override the default key -policy by specifying their own. - -If this flag is not set, the default key policy depends on the setting of the `trustAccountIdentities` -flag. If false (the default, for backwards-compatibility reasons), the default key policy somewhat -resembles the default admin key policy, but with the addition of 'GenerateDataKey' permissions. If -true, the policy matches what happens when this feature flag is set. - -Additionally, if this flag is not set and the user supplies a custom key policy, this will be appended -to the key's default policy (rather than replacing it). - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.78.0 | `false` | `true` | -| (default in v2) | `true` | | - -**Compatibility with old behavior:** Pass `trustAccountIdentities: false` to `Key` construct to restore the old behavior. - - -### @aws-cdk/aws-s3:grantWriteWithoutAcl - -*Remove `PutObjectAcl` from Bucket.grantWrite* (default) - -Change the old 's3:PutObject*' permission to 's3:PutObject' on Bucket, -as the former includes 's3:PutObjectAcl', -which could be used to grant read/write object access to IAM principals in other accounts. -Use a feature flag to make sure existing customers who might be relying -on the overly-broad permissions are not broken. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.85.0 | `false` | `true` | -| (default in v2) | `true` | | - -**Compatibility with old behavior:** Call `bucket.grantPutAcl()` in addition to `bucket.grantWrite()` to grant ACL permissions. - - -### @aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount - -*Do not specify a default DesiredCount for ECS services* (default) - -ApplicationLoadBalancedServiceBase, ApplicationMultipleTargetGroupServiceBase, -NetworkLoadBalancedServiceBase, NetworkMultipleTargetGroupServiceBase, and -QueueProcessingServiceBase currently determine a default value for the desired count of -a CfnService if a desiredCount is not provided. The result of this is that on every -deployment, the service count is reset to the fixed value, even if it was autoscaled. - -If this flag is not set, the default behaviour for CfnService.desiredCount is to set a -desiredCount of 1, if one is not provided. If true, a default will not be defined for -CfnService.desiredCount and as such desiredCount will be undefined, if one is not provided. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.92.0 | `false` | `true` | -| (default in v2) | `true` | | - -**Compatibility with old behavior:** You can pass `desiredCount: 1` explicitly, but you should never need this. - - -### @aws-cdk/aws-efs:defaultEncryptionAtRest - -*Enable this feature flag to have elastic file systems encrypted at rest by default.* (default) - -Encryption can also be configured explicitly using the `encrypted` property. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.98.0 | `false` | `true` | -| (default in v2) | `true` | | - -**Compatibility with old behavior:** Pass the `encrypted: false` property to the `FileSystem` construct to disable encryption. - - -### @aws-cdk/core:newStyleStackSynthesis - -*Switch to new stack synthesis method which enables CI/CD* (fix) - -If this flag is specified, all `Stack`s will use the `DefaultStackSynthesizer` by -default. If it is not set, they will use the `LegacyStackSynthesizer`. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.39.0 | `false` | `true` | -| 2.0.0 | `true` | `true` | - - -### @aws-cdk/core:stackRelativeExports - -*Name exports based on the construct paths relative to the stack, rather than the global construct path* (fix) - -Combined with the stack name this relative construct path is good enough to -ensure uniqueness, and makes the export names robust against refactoring -the location of the stack in the construct tree (specifically, moving the Stack -into a Stage). - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.58.0 | `false` | `true` | -| 2.0.0 | `true` | `true` | - - -### @aws-cdk/aws-rds:lowercaseDbIdentifier - -*Force lowercasing of RDS Cluster names in CDK* (fix) - -Cluster names must be lowercase, and the service will lowercase the name when the cluster -is created. However, CDK did not use to know about this, and would use the user-provided name -referencing the cluster, which would fail if it happened to be mixed-case. - -With this flag, lowercase the name in CDK so we can reference it properly. - -Must be behind a permanent flag because changing a name from mixed case to lowercase between deployments -would lead CloudFormation to think the name was changed and would trigger a cluster replacement -(losing data!). - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.97.0 | `false` | `true` | -| 2.0.0 | `true` | `true` | - - -### @aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId - -*Allow adding/removing multiple UsagePlanKeys independently* (fix) - -The UsagePlanKey resource connects an ApiKey with a UsagePlan. API Gateway does not allow more than one UsagePlanKey -for any given UsagePlan and ApiKey combination. For this reason, CloudFormation cannot replace this resource without -either the UsagePlan or ApiKey changing. - -The feature addition to support multiple UsagePlanKey resources - 142bd0e2 - recognized this and attempted to keep -existing UsagePlanKey logical ids unchanged. -However, this intentionally caused the logical id of the UsagePlanKey to be sensitive to order. That is, when -the 'first' UsagePlanKey resource is removed, the logical id of the 'second' assumes what was originally the 'first', -which again is disallowed. - -In effect, there is no way to get out of this mess in a backwards compatible way, while supporting existing stacks. -This flag changes the logical id layout of UsagePlanKey to not be sensitive to order. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.98.0 | `false` | `true` | -| 2.0.0 | `true` | `true` | - - -### @aws-cdk/aws-lambda:recognizeVersionProps - -*Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.* (fix) - -The previous calculation incorrectly considered properties of the `AWS::Lambda::Function` resource that did -not constitute creating a new Version. - -See 'currentVersion' section in the aws-lambda module's README for more details. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.106.0 | `false` | `true` | -| 2.0.0 | `true` | `true` | - - -### @aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021 - -*Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default.* (fix) - -The security policy can also be configured explicitly using the `minimumProtocolVersion` property. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.117.0 | `false` | `true` | -| 2.0.0 | `true` | `true` | - - -### @aws-cdk/core:target-partitions - -*What regions to include in lookup tables of environment agnostic stacks* (config) - -Has no effect on stacks that have a defined region, but will limit the amount -of unnecessary regions included in stacks without a known region. - -The type of this value should be a list of strings. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.137.0 | `false` | `["aws","aws-cn"]` | -| 2.4.0 | `false` | `["aws","aws-cn"]` | - - -### @aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver - -*ECS extensions will automatically add an `awslogs` driver if no logging is specified* (default) - -Enable this feature flag to configure default logging behavior for the ECS Service Extensions. This will enable the -`awslogs` log driver for the application container of the service to send the container logs to CloudWatch Logs. - -This is a feature flag as the new behavior provides a better default experience for the users. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.140.0 | `false` | `true` | -| 2.8.0 | `false` | `true` | - -**Compatibility with old behavior:** Specify a log driver explicitly. - - -### @aws-cdk/aws-ec2:uniqueImdsv2TemplateName - -*Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names.* (fix) - -Previously, the generated Launch Template names were only unique within a stack because they were based only on the -`Instance` construct ID. If another stack that has an `Instance` with the same construct ID is deployed in the same -account and region, the deployments would always fail as the generated Launch Template names were the same. - -The new implementation addresses this issue by generating the Launch Template name with the `Names.uniqueId` method. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.140.0 | `false` | `true` | -| 2.8.0 | `false` | `true` | - - -### @aws-cdk/aws-iam:minimizePolicies - -*Minimize IAM policies by combining Statements* (config) - -Minimize IAM policies by combining Principals, Actions and Resources of two -Statements in the policies, as long as it doesn't change the meaning of the -policy. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.150.0 | `false` | `true` | -| 2.18.0 | `false` | `true` | - - -### @aws-cdk/core:checkSecretUsage - -*Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations* (config) - -With this flag enabled, `SecretValue` instances can only be passed to -constructs that accept `SecretValue`s; otherwise, `unsafeUnwrap()` must be -called to use it as a regular string. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.153.0 | `false` | `true` | -| 2.21.0 | `false` | `true` | - - -### @aws-cdk/aws-lambda:recognizeLayerVersion - -*Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.* (fix) - -This flag correct incorporates Lambda Layer properties into the Lambda Function Version. - -See 'currentVersion' section in the aws-lambda module's README for more details. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| 1.159.0 | `false` | `true` | -| 2.27.0 | `false` | `true` | - - -### @aws-cdk/core:validateSnapshotRemovalPolicy - -*Error on snapshot removal policies on resources that do not support it.* (default) - -Makes sure we do not allow snapshot removal policy on resources that do not support it. -If supplied on an unsupported resource, CloudFormation ignores the policy altogether. -This flag will reduce confusion and unexpected loss of data when erroneously supplying -the snapshot removal policy. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.28.0 | `false` | `true` | - -**Compatibility with old behavior:** The old behavior was incorrect. Update your source to not specify SNAPSHOT policies on resources that do not support it. - - -### @aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName - -*Generate key aliases that include the stack name* (fix) - -Enable this feature flag to have CodePipeline generate a unique cross account key alias name using the stack name. - -Previously, when creating multiple pipelines with similar naming conventions and when crossAccountKeys is true, -the KMS key alias name created for these pipelines may be the same due to how the uniqueId is generated. - -This new implementation creates a stack safe resource name for the alias using the stack name instead of the stack ID. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.29.0 | `false` | `true` | - - -### @aws-cdk/aws-s3:createDefaultLoggingPolicy - -*Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist.* (fix) - -For example, in order to send VPC flow logs to an S3 bucket, there is a specific Bucket Policy -that needs to be attached to the bucket. If you create the bucket without a policy and then add the -bucket as the flow log destination, the service will automatically create the bucket policy with the -necessary permissions. If you were to then try and add your own bucket policy CloudFormation will throw -and error indicating that a bucket policy already exists. - -In cases where we know what the required policy is we can go ahead and create the policy so we can -remain in control of it. - -@see https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AWS-logs-and-resource-policy.html#AWS-logs-infrastructure-S3 - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.31.0 | `false` | `true` | - - -### @aws-cdk/aws-sns-subscriptions:restrictSqsDescryption - -*Restrict KMS key policy for encrypted Queues a bit more* (fix) - -Enable this feature flag to restrict the decryption of a SQS queue, which is subscribed to a SNS topic, to -only the topic which it is subscribed to and not the whole SNS service of an account. - -Previously the decryption was only restricted to the SNS service principal. To make the SQS subscription more -secure, it is a good practice to restrict the decryption further and only allow the connected SNS topic to decryption -the subscribed queue. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.32.0 | `false` | `true` | - - -### @aws-cdk/aws-ecs:arnFormatIncludesClusterName - -*ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID.* (fix) - -If this flag is not set, the old ARN format (without cluster name) for ECS is used. -If this flag is set, the new ARN format (with cluster name) for ECS is used. - -This is a feature flag as the old format is still valid for existing ECS clusters. - -See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-account-settings.html#ecs-resource-ids - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.35.0 | `false` | `true` | - - -### @aws-cdk/aws-apigateway:disableCloudWatchRole - -*Make default CloudWatch Role behavior safe for multiple API Gateways in one environment* (fix) - -Enable this feature flag to change the default behavior for aws-apigateway.RestApi and aws-apigateway.SpecRestApi -to _not_ create a CloudWatch role and Account. There is only a single ApiGateway account per AWS -environment which means that each time you create a RestApi in your account the ApiGateway account -is overwritten. If at some point the newest RestApi is deleted, the ApiGateway Account and CloudWatch -role will also be deleted, breaking any existing ApiGateways that were depending on them. - -When this flag is enabled you should either create the ApiGateway account and CloudWatch role -separately _or_ only enable the cloudWatchRole on a single RestApi. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.38.0 | `false` | `true` | - - -### @aws-cdk/core:enablePartitionLiterals - -*Make ARNs concrete if AWS partition is known* (fix) - -Enable this feature flag to get partition names as string literals in Stacks with known regions defined in -their environment, such as "aws" or "aws-cn". Previously the CloudFormation intrinsic function -"Ref: AWS::Partition" was used. For example: - -```yaml -Principal: - AWS: - Fn::Join: - - "" - - - "arn:" - - Ref: AWS::Partition - - :iam::123456789876:root -``` - -becomes: - -```yaml -Principal: - AWS: "arn:aws:iam::123456789876:root" -``` - -The intrinsic function will still be used in Stacks where no region is defined or the region's partition -is unknown. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.38.0 | `false` | `true` | - - -### @aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker - -*Avoid setting the "ECS" deployment controller when adding a circuit breaker* (fix) - -Enable this feature flag to avoid setting the "ECS" deployment controller when adding a circuit breaker to an -ECS Service, as this will trigger a full replacement which fails to deploy when using set service names. -This does not change any behaviour as the default deployment controller when it is not defined is ECS. - -This is a feature flag as the new behavior provides a better default experience for the users. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.51.0 | `false` | `true` | - - -### @aws-cdk/aws-events:eventsTargetQueueSameAccount - -*Event Rules may only push to encrypted SQS queues in the same account* (fix) - -This flag applies to SQS Queues that are used as the target of event Rules. When enabled, only principals -from the same account as the Rule can send messages. If a queue is unencrypted, this restriction will -always apply, regardless of the value of this flag. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.51.0 | `false` | `true` | - - -### @aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName - -*Enable this feature to by default create default policy names for imported roles that depend on the stack the role is in.* (fix) - -Without this, importing the same role in multiple places could lead to the permissions given for one version of the imported role -to overwrite permissions given to the role at a different place where it was imported. This was due to all imported instances -of a role using the same default policy name. - -This new implementation creates default policy names based on the constructs node path in their stack. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.60.0 | `false` | `true` | - - -### @aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy - -*Use S3 Bucket Policy instead of ACLs for Server Access Logging* (fix) - -Enable this feature flag to use S3 Bucket Policy for granting permission fo Server Access Logging -rather than using the canned `LogDeliveryWrite` ACL. ACLs do not work when Object Ownership is -enabled on the bucket. - -This flag uses a Bucket Policy statement to allow Server Access Log delivery, following best -practices for S3. - -@see https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-server-access-logging.html - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.60.0 | `false` | `true` | - - -### @aws-cdk/customresources:installLatestAwsSdkDefault - -*Whether to install the latest SDK by default in AwsCustomResource* (default) - -This was originally introduced and enabled by default to not be limited by the SDK version -that's installed on AWS Lambda. However, it creates issues for Lambdas bound to VPCs that -do not have internet access, or in environments where 'npmjs.com' is not available. - -The recommended setting is to disable the default installation behavior, and pass the -flag on a resource-by-resource basis to enable it if necessary. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.60.0 | `false` | `false` | - -**Compatibility with old behavior:** Set installLatestAwsSdk: true on all resources that need it. - - -### @aws-cdk/aws-route53-patters:useCertificate - -*Use the official `Certificate` resource instead of `DnsValidatedCertificate`* (default) - -Enable this feature flag to use the official CloudFormation supported `Certificate` resource instead -of the deprecated `DnsValidatedCertificate` construct. If this flag is enabled and you are creating -the stack in a region other than us-east-1 then you must also set `crossRegionReferences=true` on the -stack. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.61.0 | `false` | `true` | - -**Compatibility with old behavior:** Define a `DnsValidatedCertificate` explicitly and pass in the `certificate` property - - -### @aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup - -*Remove CloudWatch alarms from deployment group* (fix) - -Enable this flag to be able to remove all CloudWatch alarms from a deployment group by removing -the alarms from the construct. If this flag is not set, removing all alarms from the construct -will still leave the alarms configured for the deployment group. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.65.0 | `false` | `true` | - - -### @aws-cdk/aws-rds:databaseProxyUniqueResourceName - -*Use unique resource name for Database Proxy* (fix) - -If this flag is not set, the default behavior for `DatabaseProxy` is -to use `id` of the constructor for `dbProxyName` when it's not specified in the argument. -In this case, users can't deploy `DatabaseProxy`s that have the same `id` in the same region. - -If this flag is set, the default behavior is to use unique resource names for each `DatabaseProxy`. - -This is a feature flag as the old behavior was technically incorrect, but users may have come to depend on it. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.65.0 | `false` | `true` | - - -### @aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId - -*Include authorizer configuration in the calculation of the API deployment logical ID.* (fix) - -The logical ID of the AWS::ApiGateway::Deployment resource is calculated by hashing -the API configuration, including methods, and resources, etc. Enable this feature flag -to also include the configuration of any authorizer attached to the API in the -calculation, so any changes made to an authorizer will create a new deployment. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.66.0 | `false` | `true` | - - -### @aws-cdk/aws-ec2:launchTemplateDefaultUserData - -*Define user data for a launch template by default when a machine image is provided.* (fix) - -The ec2.LaunchTemplate construct did not define user data when a machine image is -provided despite the document. If this is set, a user data is automatically defined -according to the OS of the machine image. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.67.0 | `false` | `true` | - - -### @aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments - -*SecretTargetAttachments uses the ResourcePolicy of the attached Secret.* (fix) - -Enable this feature flag to make SecretTargetAttachments use the ResourcePolicy of the attached Secret. -SecretTargetAttachments are created to connect a Secret to a target resource. -In CDK code, they behave like regular Secret and can be used as a stand-in in most situations. -Previously, adding to the ResourcePolicy of a SecretTargetAttachment did attempt to create a separate ResourcePolicy for the same Secret. -However Secrets can only have a single ResourcePolicy, causing the CloudFormation deployment to fail. - -When enabling this feature flag for an existing Stack, ResourcePolicies created via a SecretTargetAttachment will need replacement. -This won't be possible without intervention due to limitation outlined above. -First remove all permissions granted to the Secret and deploy without the ResourcePolicies. -Then you can re-add the permissions and deploy again. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.67.0 | `false` | `true` | - - -### @aws-cdk/aws-redshift:columnId - -*Whether to use an ID to track Redshift column changes* (fix) - -Redshift columns are identified by their `name`. If a column is renamed, the old column -will be dropped and a new column will be created. This can cause data loss. - -This flag enables the use of an `id` attribute for Redshift columns. If this flag is enabled, the -internal CDK architecture will track changes of Redshift columns through their `id`, rather -than their `name`. This will prevent data loss when columns are renamed. - -**NOTE** - Enabling this flag comes at a **risk**. When enabled, update the `id`s of all columns, -**however** do not change the `names`s of the columns. If the `name`s of the columns are changed during -initial deployment, the columns will be dropped and recreated, causing data loss. After the initial deployment -of the `id`s, the `name`s of the columns can be changed without data loss. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.68.0 | `false` | `true` | - - -### @aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2 - -*Enable AmazonEMRServicePolicy_v2 managed policies* (fix) - -If this flag is not set, the default behavior for `EmrCreateCluster` is -to use `AmazonElasticMapReduceRole` managed policies. - -If this flag is set, the default behavior is to use the new `AmazonEMRServicePolicy_v2` -managed policies. - -This is a feature flag as the old behavior will be deprecated, but some resources may require manual -intervention since they might not have the appropriate tags propagated automatically. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.72.0 | `false` | `true` | - - -### @aws-cdk/aws-apigateway:requestValidatorUniqueId - -*Generate a unique id for each RequestValidator added to a method* (fix) - -This flag allows multiple RequestValidators to be added to a RestApi when -providing the `RequestValidatorOptions` in the `addMethod()` method. - -If the flag is not set then only a single RequestValidator can be added in this way. -Any additional RequestValidators have to be created directly with `new RequestValidator`. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.78.0 | `false` | `true` | - - -### @aws-cdk/aws-ec2:restrictDefaultSecurityGroup - -*Restrict access to the VPC default security group* (default) - -Enable this feature flag to remove the default ingress/egress rules from the -VPC default security group. - -When a VPC is created, a default security group is created as well and this cannot -be deleted. The default security group is created with ingress/egress rules that allow -_all_ traffic. [AWS Security best practices recommend](https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-2) -removing these ingress/egress rules in order to restrict access to the default security group. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.78.0 | `false` | `true` | - -**Compatibility with old behavior:** - To allow all ingress/egress traffic to the VPC default security group you - can set the `restrictDefaultSecurityGroup: false`. - - - -### @aws-cdk/aws-kms:aliasNameRef - -*KMS Alias name and keyArn will have implicit reference to KMS Key* (fix) - -This flag allows an implicit dependency to be created between KMS Alias and KMS Key -when referencing key.aliasName or key.keyArn. - -If the flag is not set then a raw string is passed as the Alias name and no -implicit dependencies will be set. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.83.0 | `false` | `true` | - - -### @aws-cdk/core:includePrefixInUniqueNameGeneration - -*Include the stack prefix in the stack name generation process* (fix) - -This flag prevents the prefix of a stack from making the stack's name longer than the 128 character limit. - -If the flag is set, the prefix is included in the stack name generation process. -If the flag is not set, then the prefix of the stack is prepended to the generated stack name. - -**NOTE** - Enabling this flag comes at a **risk**. If you have already deployed stacks, changing the status of this -feature flag can lead to a change in stacks' name. Changing a stack name mean recreating the whole stack, which -is not viable in some productive setups. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.84.0 | `false` | `true` | - - -### @aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig - -*Generate a launch template when creating an AutoScalingGroup* (fix) - -Enable this flag to allow AutoScalingGroups to generate a launch template when being created. -Launch configurations have been deprecated and cannot be created in AWS Accounts created after -December 31, 2023. Existing 'AutoScalingGroup' properties used for creating a launch configuration -will now create an equivalent 'launchTemplate'. Alternatively, users can provide an explicit -'launchTemplate' or 'mixedInstancesPolicy'. When this flag is enabled a 'launchTemplate' will -attempt to set user data according to the OS of the machine image if explicit user data is not -provided. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.88.0 | `false` | `true` | - -**Compatibility with old behavior:** - If backwards compatibility needs to be maintained due to an existing autoscaling group - using a launch config, set this flag to false. - - - -### @aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby - -*Enables support for Multi-AZ with Standby deployment for opensearch domains* (default) - -If this is set, an opensearch domain will automatically be created with -multi-az with standby enabled. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.88.0 | `false` | `true` | - -**Compatibility with old behavior:** Pass `capacity.multiAzWithStandbyEnabled: false` to `Domain` construct to restore the old behavior. - - -### @aws-cdk/aws-efs:denyAnonymousAccess - -*EFS denies anonymous clients accesses* (default) - -This flag adds the file system policy that denies anonymous clients -access to `efs.FileSystem`. - -If this flag is not set, `efs.FileSystem` will allow all anonymous clients -that can access over the network. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.93.0 | `false` | `true` | - -**Compatibility with old behavior:** You can pass `allowAnonymousAccess: true` so allow anonymous clients access. - - -### @aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId - -*When enabled, mount targets will have a stable logicalId that is linked to the associated subnet.* (fix) - -When this feature flag is enabled, each mount target will have a stable -logicalId that is linked to the associated subnet. If the flag is set to -false then the logicalIds of the mount targets can change if the number of -subnets changes. - -Set this flag to false for existing mount targets. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.93.0 | `false` | `true` | - - -### @aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion - -*Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default* (default) - -If this is set, and a `runtime` prop is not passed to, Lambda NodeJs -functions will us the latest version of the runtime provided by the Lambda -service. Do not use this if you your lambda function is reliant on dependencies -shipped as part of the runtime environment. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.93.0 | `false` | `true` | - -**Compatibility with old behavior:** Pass `runtime: lambda.Runtime.NODEJS_16_X` to `Function` construct to restore the previous behavior. - - -### @aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier - -*When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id.* (fix) - -When this feature flag is enabled, we use the IGraphqlApi ARN rather than ID when creating or updating CfnSourceApiAssociation in -the GraphqlApi construct. Using the ARN allows the association to support an association with a source api or merged api in another account. -Note that for existing source api associations created with this flag disabled, enabling the flag will lead to a resource replacement. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.97.0 | `false` | `true` | - - -### @aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters - -*When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change.* (fix) - -When this feature flag is enabled, a scope of `InstanceParameterGroup` for -`AuroraClusterInstance` with each parameters will change to AuroraClusterInstance -from AuroraCluster. - -If the flag is set to false then it can only make one `AuroraClusterInstance` -with each `InstanceParameterGroup` in the AuroraCluster. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.97.0 | `false` | `true` | - - -### @aws-cdk/aws-rds:preventRenderingDeprecatedCredentials - -*When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials.* (fix) - -The `credentials` property on the `DatabaseClusterFromSnapshotProps` -interface was deprecated with the new `snapshotCredentials` property being -recommended. Before deprecating `credentials`, a secret would be generated -while rendering credentials if the `credentials` property was undefined or -if a secret wasn't provided via the `credentials` property. This behavior -is replicated with the new `snapshotCredentials` property, but the original -`credentials` secret can still be created resulting in an extra database -secret. - -Set this flag to prevent rendering deprecated `credentials` and creating an -extra database secret when only using `snapshotCredentials` to create an RDS -database cluster from a snapshot. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.98.0 | `false` | `true` | - - -### @aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource - -*When enabled, the CodeCommit source action is using the default branch name 'main'.* (fix) - -When setting up a CodeCommit source action for the source stage of a pipeline, please note that the -default branch is 'master'. -However, with the activation of this feature flag, the default branch is updated to 'main'. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.103.1 | `false` | `true` | - - -### @aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction - -*When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID.* (fix) - -When this feature flag is enabled, a logical ID of `LambdaPermission` for a -`LambdaAction` will include an alarm ID. Therefore multiple alarms for the same Lambda -can be created with `LambdaAction`. - -If the flag is set to false then it can only make one alarm for the Lambda with -`LambdaAction`. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.124.0 | `false` | `true` | - - -### @aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse - -*Enables Pipeline to set the default value for crossAccountKeys to false.* (default) - -When this feature flag is enabled, and the `crossAccountKeys` property is not provided in a `Pipeline` -construct, the construct automatically defaults the value of this property to false. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.127.0 | `false` | `true` | - -**Compatibility with old behavior:** Pass `crossAccountKeys: true` to `Pipeline` construct to restore the previous behavior. - - -### @aws-cdk/aws-codepipeline:defaultPipelineTypeToV2 - -*Enables Pipeline to set the default pipeline type to V2.* (default) - -When this feature flag is enabled, and the `pipelineType` property is not provided in a `Pipeline` -construct, the construct automatically defaults the value of this property to `PipelineType.V2`. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.133.0 | `false` | `true` | - -**Compatibility with old behavior:** Pass `pipelineType: PipelineType.V1` to `Pipeline` construct to restore the previous behavior. - - -### @aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope - -*When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only.* (fix) - -When this feature flag is enabled and calling KMS key grant method, the created IAM policy will reduce the resource scope from -'*' to this specific granting KMS key. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.134.0 | `false` | `true` | - - -### @aws-cdk/aws-eks:nodegroupNameAttribute - -*When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix.* (fix) - -When this feature flag is enabled, the nodegroupName attribute will be exactly the name of the nodegroup without -any prefix. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.139.0 | `false` | `true` | - - -### @aws-cdk/aws-ec2:ebsDefaultGp3Volume - -*When enabled, the default volume type of the EBS volume will be GP3* (default) - -When this featuer flag is enabled, the default volume type of the EBS volume will be `EbsDeviceVolumeType.GENERAL_PURPOSE_SSD_GP3`. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.140.0 | `false` | `true` | - -**Compatibility with old behavior:** Pass `volumeType: EbsDeviceVolumeType.GENERAL_PURPOSE_SSD` to `Volume` construct to restore the previous behavior. - - -### @aws-cdk/pipelines:reduceAssetRoleTrustScope - -*Remove the root account principal from PipelineAssetsFileRole trust policy* (default) - -When this feature flag is enabled, the root account principal will not be added to the trust policy of asset role. -When this feature flag is disabled, it will keep the root account principal in the trust policy. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.141.0 | `true` | `true` | - -**Compatibility with old behavior:** Disable the feature flag to add the root account principal back - - -### @aws-cdk/aws-ecs:removeDefaultDeploymentAlarm - -*When enabled, remove default deployment alarm settings* (default) - -When this featuer flag is enabled, remove the default deployment alarm settings when creating a AWS ECS service. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.143.0 | `false` | `true` | - -**Compatibility with old behavior:** Set AWS::ECS::Service 'DeploymentAlarms' manually to restore the previous behavior. - - -### @aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault - -*When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default* (fix) - -This results in 'logApiResponseData' being passed as true to the custom resource provider. This will cause the custom resource handler to receive an 'Update' event. If you don't -have an SDK call configured for the 'Update' event and you're dependent on specific SDK call response data, you will see this error from CFN: - -CustomResource attribute error: Vendor response doesn't contain attribute in object. See https://github.com/aws/aws-cdk/issues/29949) for more details. - -Unlike most feature flags, we don't recommend setting this feature flag to true. However, if you're using the 'AwsCustomResource' construct with 'logApiResponseData' as true in -the event object, then setting this feature flag will keep this behavior. Otherwise, setting this feature flag to false will trigger an 'Update' event by removing the 'logApiResponseData' -property from the event object. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.145.0 | `false` | `false` | - - -### @aws-cdk/aws-s3:keepNotificationInImportedBucket - -*When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack.* (fix) - -Currently, adding notifications to a bucket where it was created by ourselves will override notification added where it is imported. - -When this feature flag is enabled, adding notifications to a bucket in the current stack will only update notification defined in this stack. -Other notifications that are not managed by this stack will be kept. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.155.0 | `false` | `false` | - - -### @aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask - -*When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.* (fix) - -Currently, 'inputPath' and 'outputPath' from the TaskStateBase Props is being used under BedrockInvokeModelProps to define S3URI under 'input' and 'output' fields -of State Machine Task definition. - -When this feature flag is enabled, specify newly introduced props 's3InputUri' and -'s3OutputUri' to populate S3 uri under input and output fields in state machine task definition for Bedrock invoke model. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.156.0 | `true` | `true` | - -**Compatibility with old behavior:** Disable the feature flag to use input and output path fields for s3 URI - - -### @aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions - -*When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration* (fix) - -Currently, we automatically add a number of cloudwatch permissions to the task role when no cloudwatch log group is -specified as logConfiguration and it will grant 'Resources': ['*'] to the task role. - -When this feature flag is enabled, we will only grant the necessary permissions when users specify cloudwatch log group. - - -| Since | Default | Recommended | -| ----- | ----- | ----- | -| (not in v1) | | | -| 2.159.0 | `false` | `true` | - -**Compatibility with old behavior:** Disable the feature flag to continue grant permissions to log group when no log group is specified - - - diff --git a/packages/aws-cdk-lib/core/README.md b/packages/aws-cdk-lib/core/README.md index aac7abe87c167..e61ca8b0ce6ab 100644 --- a/packages/aws-cdk-lib/core/README.md +++ b/packages/aws-cdk-lib/core/README.md @@ -155,6 +155,86 @@ new MyStack(app, 'MyStack', { For more information on bootstrapping accounts and customizing synthesis, see [Bootstrapping in the CDK Developer Guide](https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html). +### STS Role Options + +You can configure STS options that instruct the CDK CLI on which configuration should it use when assuming +the various roles that are involved in a deployment operation. + +> See https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping-env.html#bootstrapping-env-roles + +These options are available via the `DefaultStackSynthesizer` properties: + +```ts +class MyStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, { + ...props, + synthesizer: new DefaultStackSynthesizer({ + deployRoleExternalId: '', + deployRoleAdditionalOptions: { + // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_RequestParameters + }, + fileAssetPublishingExternalId: '', + fileAssetPublishingRoleAdditionalOptions: { + // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_RequestParameters + }, + imageAssetPublishingExternalId: '', + imageAssetPublishingRoleAdditionalOptions: { + // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_RequestParameters + }, + lookupRoleExternalId: '', + lookupRoleAdditionalOptions: { + // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_RequestParameters + }, + }) + }); + } +} +``` + +> Note that the `*additionalOptions` property does not allow passing `ExternalId` or `RoleArn`, as these options +> have dedicated properties that configure them. + +#### Session Tags + +STS session tags are used to implement [Attribute-Based Access Control](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction_attribute-based-access-control.html) (ABAC). + +> See [IAM tutorial: Define permissions to access AWS resources based on tags](https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_attribute-based-access-control.html) + +You can pass session tags for each [role created during bootstrap](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping-env.html#bootstrapping-env-roles) via the `*additionalOptions` property: + +```ts +class MyStack extends cdk.Stack { + constructor(parent, id, props) { + super(parent, id, { + ...props, + synthesizer: new DefaultStackSynthesizer({ + deployRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }] + }, + fileAssetPublishingRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }] + }, + imageAssetPublishingRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }] + }, + lookupRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }] + }, + }) + }); + } +} +``` + +This will cause the CDK CLI to include session tags when assuming each of these roles during deployment. +Note that the trust policy of the role must contain permissions for the `sts:TagSession` action. + +> See https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_permissions-required + +- If you are using a custom bootstrap template, make sure the template includes these permissions. +- If you are using the default bootstrap template from a CDK version lower than XXXX, you will need to rebootstrap your enviroment (once). + ## Nested Stacks [Nested stacks](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html) are stacks created as part of other stacks. You create a nested stack within another stack by using the `NestedStack` construct. diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/asset-manifest-builder.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/asset-manifest-builder.ts index 3cd3924872995..fc743591cfb9c 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/asset-manifest-builder.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/asset-manifest-builder.ts @@ -44,6 +44,7 @@ export class AssetManifestBuilder { region: resolvedOr(stack.region, undefined), assumeRoleArn: target.role?.assumeRoleArn, assumeRoleExternalId: target.role?.assumeRoleExternalId, + assumeRoleAdditionalOptions: target.role?.assumeRoleAdditionalOptions, }); } @@ -82,6 +83,7 @@ export class AssetManifestBuilder { region: resolvedOr(stack.region, undefined), assumeRoleArn: target.role?.assumeRoleArn, assumeRoleExternalId: target.role?.assumeRoleExternalId, + assumeRoleAdditionalOptions: target.role?.assumeRoleAdditionalOptions, }); } @@ -231,6 +233,19 @@ export interface RoleOptions { * @default - No external ID */ readonly assumeRoleExternalId?: string; + + /** + * Additional options to pass to STS when assuming the role for cloudformation deployments. + * + * - `RoleArn` should not be used. Use the dedicated `assumeRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. + * - `TransitiveTagKeys` defaults to use all keys (if any) specified in `Tags`. E.g, all tags are transitive by default. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly assumeRoleAdditionalOptions?: { [key: string]: any }; + } function validateFileAssetSource(asset: FileAssetSource) { diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/default-synthesizer.ts index 4430c9bdd2090..bdbbdc21bfa47 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/default-synthesizer.ts @@ -23,6 +23,14 @@ const MIN_BOOTSTRAP_STACK_VERSION = 6; */ const MIN_LOOKUP_ROLE_BOOTSTRAP_STACK_VERSION = 8; +/** + * Session tags require `sts:TagSession` permissions on roles during bootstrap. + * This is the first version of the bootstrap templates that adds these permissions. + * + * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_permissions-required + */ +const MIN_SESSION_TAGS_BOOTSTRAP_STACK_VERSION = 22; + /** * Configuration properties for DefaultStackSynthesizer */ @@ -211,6 +219,54 @@ export interface DefaultStackSynthesizerProps { * @default DefaultStackSynthesizer.DEFAULT_BOOTSTRAP_STACK_VERSION_SSM_PARAMETER */ readonly bootstrapStackVersionSsmParameter?: string; + + /** + * Additional options to pass to STS when assuming the deploy role. + * + * - `RoleArn` should not be used. Use the dedicated `deployRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `deployRoleExternalId` instead. + * - `TransitiveTagKeys` defaults to use all keys (if any) specified in `Tags`. E.g, all tags are transitive by default. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly deployRoleAdditionalOptions?: { [key: string]: any }; + + /** + * Additional options to pass to STS when assuming the lookup role. + * + * - `RoleArn` should not be used. Use the dedicated `lookupRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `lookupRoleExternalId` instead. + * - `TransitiveTagKeys` defaults to use all keys (if any) specified in `Tags`. E.g, all tags are transitive by default. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly lookupRoleAdditionalOptions?: { [key: string]: any }; + + /** + * Additional options to pass to STS when assuming the file asset publishing. + * + * - `RoleArn` should not be used. Use the dedicated `fileAssetPublishingRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `fileAssetPublishingExternalId` instead. + * - `TransitiveTagKeys` defaults to use all keys (if any) specified in `Tags`. E.g, all tags are transitive by default. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly fileAssetPublishingRoleAdditionalOptions?: { [key: string]: any }; + + /** + * Additional options to pass to STS when assuming the image asset publishing. + * + * - `RoleArn` should not be used. Use the dedicated `imageAssetPublishingRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `imageAssetPublishingExternalId` instead. + * - `TransitiveTagKeys` defaults to use all keys (if any) specified in `Tags`. E.g, all tags are transitive by default. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly imageAssetPublishingRoleAdditionalOptions?: { [key: string]: any }; } /** @@ -382,6 +438,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer implements IReusab role: this.fileAssetPublishingRoleArn ? { assumeRoleArn: this.fileAssetPublishingRoleArn, assumeRoleExternalId: this.props.fileAssetPublishingExternalId, + assumeRoleAdditionalOptions: this.props.fileAssetPublishingRoleAdditionalOptions, } : undefined, }); return this.cloudFormationLocationFromFileAsset(location); @@ -396,6 +453,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer implements IReusab role: this.imageAssetPublishingRoleArn ? { assumeRoleArn: this.imageAssetPublishingRoleArn, assumeRoleExternalId: this.props.imageAssetPublishingExternalId, + assumeRoleAdditionalOptions: this.props.imageAssetPublishingRoleAdditionalOptions, } : undefined, }); return this.cloudFormationLocationFromDockerImageAsset(location); @@ -405,7 +463,7 @@ export class DefaultStackSynthesizer extends StackSynthesizer implements IReusab * Synthesize the stack template to the given session, passing the configured lookup role ARN */ protected synthesizeStackTemplate(stack: Stack, session: ISynthesisSession) { - stack._synthesizeTemplate(session, this.lookupRoleArn); + stack._synthesizeTemplate(session, this.lookupRoleArn, this.props.lookupRoleExternalId, this.props.lookupRoleAdditionalOptions); } /** @@ -429,34 +487,58 @@ export class DefaultStackSynthesizer extends StackSynthesizer implements IReusab // If it's done AFTER _synthesizeTemplate(), then the template won't contain the // right constructs. if (this.props.generateBootstrapVersionRule ?? true) { - this.addBootstrapVersionRule(MIN_BOOTSTRAP_STACK_VERSION, this.bootstrapStackVersionSsmParameter!); + this.addBootstrapVersionRule(this._requiredBootstrapVersionForDeployment, this.bootstrapStackVersionSsmParameter!); } - const templateAssetSource = this.synthesizeTemplate(session, this.lookupRoleArn); + const templateAssetSource = this.synthesizeTemplate(session, this.lookupRoleArn, + this.props.lookupRoleExternalId, this.props.lookupRoleAdditionalOptions); const templateAsset = this.addFileAsset(templateAssetSource); const assetManifestId = this.assetManifest.emitManifest(this.boundStack, session, { - requiresBootstrapStackVersion: MIN_BOOTSTRAP_STACK_VERSION, + requiresBootstrapStackVersion: this._requiredBoostrapVersionForAssets, bootstrapStackVersionSsmParameter: this.bootstrapStackVersionSsmParameter, }); this.emitArtifact(session, { assumeRoleExternalId: this.props.deployRoleExternalId, assumeRoleArn: this._deployRoleArn, + assumeRoleAdditionalOptions: this.props.deployRoleAdditionalOptions, cloudFormationExecutionRoleArn: this._cloudFormationExecutionRoleArn, stackTemplateAssetObjectUrl: templateAsset.s3ObjectUrlWithPlaceholders, - requiresBootstrapStackVersion: MIN_BOOTSTRAP_STACK_VERSION, + requiresBootstrapStackVersion: this._requiredBootstrapVersionForDeployment, bootstrapStackVersionSsmParameter: this.bootstrapStackVersionSsmParameter, additionalDependencies: [assetManifestId], lookupRole: this.useLookupRoleForStackOperations && this.lookupRoleArn ? { arn: this.lookupRoleArn, assumeRoleExternalId: this.props.lookupRoleExternalId, - requiresBootstrapStackVersion: MIN_LOOKUP_ROLE_BOOTSTRAP_STACK_VERSION, + assumeRoleAdditionalOptions: this.props.lookupRoleAdditionalOptions, + requiresBootstrapStackVersion: this._requiredBootstrapVersionForLookup, bootstrapStackVersionSsmParameter: this.bootstrapStackVersionSsmParameter, } : undefined, }); } + private get _requiredBoostrapVersionForAssets() { + if (this.props.fileAssetPublishingRoleAdditionalOptions?.Tags || this.props.imageAssetPublishingRoleAdditionalOptions?.Tags) { + return MIN_SESSION_TAGS_BOOTSTRAP_STACK_VERSION; + } + return MIN_BOOTSTRAP_STACK_VERSION; + } + + private get _requiredBootstrapVersionForDeployment() { + if (this.props.deployRoleAdditionalOptions?.Tags) { + return MIN_SESSION_TAGS_BOOTSTRAP_STACK_VERSION; + } + return MIN_BOOTSTRAP_STACK_VERSION; + } + + private get _requiredBootstrapVersionForLookup() { + if (this.props.lookupRoleAdditionalOptions?.Tags) { + return MIN_SESSION_TAGS_BOOTSTRAP_STACK_VERSION; + } + return MIN_LOOKUP_ROLE_BOOTSTRAP_STACK_VERSION; + } + /** * Returns the ARN of the deploy Role. */ diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/stack-synthesizer.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/stack-synthesizer.ts index a261c877c5008..4d4d01d0af039 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/stack-synthesizer.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/stack-synthesizer.ts @@ -107,8 +107,11 @@ export abstract class StackSynthesizer implements IStackSynthesizer { * the credentials will be the same identity that is doing the `UpdateStack` * call, which may not have the right permissions to write to S3. */ - protected synthesizeTemplate(session: ISynthesisSession, lookupRoleArn?: string): FileAssetSource { - this.boundStack._synthesizeTemplate(session, lookupRoleArn); + protected synthesizeTemplate(session: ISynthesisSession, + lookupRoleArn?: string, + lookupRoleExternalId?: string, + lookupRoleAdditionalOptions?: { [key: string]: any }): FileAssetSource { + this.boundStack._synthesizeTemplate(session, lookupRoleArn, lookupRoleExternalId, lookupRoleAdditionalOptions); return stackTemplateFileAsset(this.boundStack, session); } @@ -240,6 +243,18 @@ export interface SynthesizeStackArtifactOptions { */ readonly assumeRoleExternalId?: string; + /** + * Additional options to pass to STS when assuming the role for cloudformation deployments. + * + * - `RoleArn` should not be used. Use the dedicated `assumeRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. + * - `TransitiveTagKeys` defaults to use all keys (if any) specified in `Tags`. E.g, all tags are transitive by default. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly assumeRoleAdditionalOptions?: { [key: string]: any }; + /** * The role that is passed to CloudFormation to execute the change set * diff --git a/packages/aws-cdk-lib/core/lib/stack.ts b/packages/aws-cdk-lib/core/lib/stack.ts index a12ca414491fc..a31610fdfa00c 100644 --- a/packages/aws-cdk-lib/core/lib/stack.ts +++ b/packages/aws-cdk-lib/core/lib/stack.ts @@ -1083,7 +1083,10 @@ export class Stack extends Construct implements ITaggable { * Synthesizes the cloudformation template into a cloud assembly. * @internal */ - public _synthesizeTemplate(session: ISynthesisSession, lookupRoleArn?: string): void { + public _synthesizeTemplate(session: ISynthesisSession, + lookupRoleArn?: string, + lookupRoleExternalId?: string, + lookupRoleAdditionalOptions?: { [key: string]: any }): void { // In principle, stack synthesis is delegated to the // StackSynthesis object. // @@ -1126,11 +1129,17 @@ export class Stack extends Construct implements ITaggable { fs.writeFileSync(outPath, templateData); for (const ctx of this._missingContext) { - if (lookupRoleArn != null) { - builder.addMissing({ ...ctx, props: { ...ctx.props, lookupRoleArn } }); - } else { - builder.addMissing(ctx); - } + + // 'account' and 'region' are added to the schema at tree instantiation time. + // these options however are only known at synthesis, so are added here. + // see https://github.com/aws/aws-cdk/blob/v2.158.0/packages/aws-cdk-lib/core/lib/context-provider.ts#L71 + const queryLookupOptions: Omit = { + lookupRoleArn, + lookupRoleExternalId, + assumeRoleAdditionalOptions: lookupRoleAdditionalOptions, + }; + + builder.addMissing({ ...ctx, props: { ...ctx.props, ...queryLookupOptions } }); } } diff --git a/packages/aws-cdk-lib/core/test/stack-synthesis/new-style-synthesis.test.ts b/packages/aws-cdk-lib/core/test/stack-synthesis/new-style-synthesis.test.ts index f39c577d82be9..9b6d7542a3561 100644 --- a/packages/aws-cdk-lib/core/test/stack-synthesis/new-style-synthesis.test.ts +++ b/packages/aws-cdk-lib/core/test/stack-synthesis/new-style-synthesis.test.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as cxschema from '../../../cloud-assembly-schema'; import { ArtifactType } from '../../../cloud-assembly-schema'; import * as cxapi from '../../../cx-api'; -import { App, Aws, CfnResource, ContextProvider, DefaultStackSynthesizer, FileAssetPackaging, Stack, NestedStack } from '../../lib'; +import { App, Aws, CfnResource, ContextProvider, DefaultStackSynthesizer, FileAssetPackaging, Stack, NestedStack, DockerImageAssetLocation, DockerImageAssetSource, FileAssetLocation, FileAssetSource, SynthesizeStackArtifactOptions } from '../../lib'; import { ISynthesisSession } from '../../lib/stack-synthesizers/types'; import { evaluateCFN } from '../evaluate-cfn'; @@ -105,6 +105,66 @@ describe('new style synthesis', () => { }); + test('can set role additional options tags on default stack synthesizer', () => { + // GIVEN + stack = new Stack(app, 'SessionTagsStack', { + synthesizer: new DefaultStackSynthesizer({ + deployRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering-DeployRoleTag' }], + }, + fileAssetPublishingRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering-FileAssetTag' }], + }, + imageAssetPublishingRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering-ImageAssetTag' }], + }, + lookupRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering-LookupRoleTag' }], + }, + }), + env: { + account: '111111111111', region: 'us-east-1', + }, + }); + + stack.synthesizer.addFileAsset({ + fileName: __filename, + packaging: FileAssetPackaging.FILE, + sourceHash: 'fileHash', + }); + + stack.synthesizer.addDockerImageAsset({ + directoryName: '.', + sourceHash: 'dockerHash', + }); + + ContextProvider.getValue(stack, { + provider: cxschema.ContextProvider.VPC_PROVIDER, + props: {}, + dummyValue: undefined, + }).value; + + // THEN + const asm = app.synth(); + const manifest = asm.getStackByName('SessionTagsStack').manifest; + // Validates that the deploy and lookup role session tags were set in the Manifest: + expect((manifest.properties as cxschema.AwsCloudFormationStackProperties).assumeRoleAdditionalOptions?.Tags).toEqual([{ Key: 'Department', Value: 'Engineering-DeployRoleTag' }]); + expect((manifest.properties as cxschema.AwsCloudFormationStackProperties).lookupRole?.assumeRoleAdditionalOptions?.Tags).toEqual([{ Key: 'Department', Value: 'Engineering-LookupRoleTag' }]); + + const assetManifest = getAssetManifest(asm); + const assetManifestJSON = readAssetManifest(assetManifest); + + // Validates that the image and file asset session tags were set in the asset manifest: + expect(assetManifestJSON.dockerImages?.dockerHash.destinations['111111111111-us-east-1'].assumeRoleAdditionalOptions?.Tags).toEqual([{ Key: 'Department', Value: 'Engineering-ImageAssetTag' }]); + expect(assetManifestJSON.files?.fileHash.destinations['111111111111-us-east-1'].assumeRoleAdditionalOptions?.Tags).toEqual([{ Key: 'Department', Value: 'Engineering-FileAssetTag' }]); + + // assert that lookup role options are added to the missing lookup context + expect(asm.manifest.missing![0].props.assumeRoleAdditionalOptions).toEqual({ + Tags: [{ Key: 'Department', Value: 'Engineering-LookupRoleTag' }], + }); + + }); + test('customize version parameter', () => { // GIVEN const myapp = new App(); @@ -188,6 +248,29 @@ describe('new style synthesis', () => { }); + test('generates missing context with the lookup role external id as one of the missing context properties', () => { + // GIVEN + stack = new Stack(app, 'Stack2', { + synthesizer: new DefaultStackSynthesizer({ + generateBootstrapVersionRule: false, + lookupRoleExternalId: 'External', + }), + env: { + account: '111111111111', region: 'us-east-1', + }, + }); + ContextProvider.getValue(stack, { + provider: cxschema.ContextProvider.VPC_PROVIDER, + props: {}, + dummyValue: undefined, + }).value; + + // THEN + const assembly = app.synth(); + expect(assembly.manifest.missing![0].props.lookupRoleExternalId).toEqual('External'); + + }); + test('nested Stack uses the lookup role ARN of the parent stack', () => { // GIVEN const myapp = new App(); diff --git a/packages/aws-cdk-lib/core/test/synthesis.test.ts b/packages/aws-cdk-lib/core/test/synthesis.test.ts index 4e61b05f0a9cc..8b67b371e0be8 100644 --- a/packages/aws-cdk-lib/core/test/synthesis.test.ts +++ b/packages/aws-cdk-lib/core/test/synthesis.test.ts @@ -283,6 +283,85 @@ describe('synthesis', () => { jest.restoreAllMocks(); }); + + test('when deploy role session tags are configured, required stack bootstrap version is 22', () => { + + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'stack', { + synthesizer: new cdk.DefaultStackSynthesizer({ + deployRoleAdditionalOptions: { + Tags: [{ Key: 'Departement', Value: 'Engineering' }], + }, + }), + }); + + const assembly = app.synth(); + const stackArtifact = assembly.getStackByName('stack'); + + expect(stackArtifact.requiresBootstrapStackVersion).toEqual(22); + + const versionRule = stack.node.findChild('CheckBootstrapVersion') as cdk.CfnRule; + + // ugly - but no more than using the snapshot of the resource... + const bootstrapVersions = (versionRule._toCloudFormation() as any).Rules[versionRule.logicalId].Assertions[0].Assert['Fn::Not'][0]['Fn::Contains'][0]; + expect(bootstrapVersions).toContain('21'); + + }); + + test('when lookup role session tags are configured, required lookup bootstrap version is 22', () => { + + const app = new cdk.App(); + new cdk.Stack(app, 'stack', { + synthesizer: new cdk.DefaultStackSynthesizer({ + lookupRoleAdditionalOptions: { + Tags: [{ Key: 'Departement', Value: 'Engineering' }], + }, + }), + }); + + const assembly = app.synth(); + const stackArtifact = assembly.getStackByName('stack'); + + expect(stackArtifact.lookupRole?.requiresBootstrapStackVersion).toEqual(22); + + }); + + test('when file asset role session tags are configured, required assets bootstrap version is 22', () => { + + const app = new cdk.App(); + new cdk.Stack(app, 'stack', { + synthesizer: new cdk.DefaultStackSynthesizer({ + fileAssetPublishingRoleAdditionalOptions: { + Tags: [{ Key: 'Departement', Value: 'Engineering' }], + }, + }), + }); + + const assembly = app.synth(); + const assetsArtifact = assembly.tryGetArtifact('stack.assets') as cxapi.AssetManifestArtifact; + + expect(assetsArtifact.requiresBootstrapStackVersion).toEqual(22); + + }); + + test('when image asset role session tags are configured, required assets bootstrap version is 22', () => { + + const app = new cdk.App(); + new cdk.Stack(app, 'stack', { + synthesizer: new cdk.DefaultStackSynthesizer({ + imageAssetPublishingRoleAdditionalOptions: { + Tags: [{ Key: 'Departement', Value: 'Engineering' }], + }, + }), + }); + + const assembly = app.synth(); + const assetsArtifact = assembly.tryGetArtifact('stack.assets') as cxapi.AssetManifestArtifact; + + expect(assetsArtifact.requiresBootstrapStackVersion).toEqual(22); + + }); + }); function list(outdir: string) { diff --git a/packages/aws-cdk-lib/cx-api/lib/artifacts/cloudformation-artifact.ts b/packages/aws-cdk-lib/cx-api/lib/artifacts/cloudformation-artifact.ts index d73e2a5b33dd7..8fcea525b4907 100644 --- a/packages/aws-cdk-lib/cx-api/lib/artifacts/cloudformation-artifact.ts +++ b/packages/aws-cdk-lib/cx-api/lib/artifacts/cloudformation-artifact.ts @@ -4,7 +4,6 @@ import * as cxschema from '../../../cloud-assembly-schema'; import { CloudArtifact } from '../cloud-artifact'; import type { CloudAssembly } from '../cloud-assembly'; import { Environment, EnvironmentUtils } from '../environment'; - const CLOUDFORMATION_STACK_ARTIFACT_SYM = Symbol.for('@aws-cdk/cx-api.CloudFormationStackArtifact'); export class CloudFormationStackArtifact extends CloudArtifact { @@ -97,6 +96,18 @@ export class CloudFormationStackArtifact extends CloudArtifact { */ readonly assumeRoleExternalId?: string; + /** + * Additional options to pass to STS when assuming the role for cloudformation deployments. + * + * - `RoleArn` should not be used. Use the dedicated `assumeRoleArn` property instead. + * - `ExternalId` should not be used. Use the dedicated `assumeRoleExternalId` instead. + * - `TransitiveTagKeys` defaults to use all keys (if any) specified in `Tags`. E.g, all tags are transitive by default. + * + * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/STS.html#assumeRole-property + * @default - No additional options. + */ + readonly assumeRoleAdditionalOptions?: { [key: string]: any }; + /** * The role that is passed to CloudFormation to execute the change set * @@ -166,6 +177,7 @@ export class CloudFormationStackArtifact extends CloudArtifact { this.notificationArns = properties.notificationArns ?? []; this.assumeRoleArn = properties.assumeRoleArn; this.assumeRoleExternalId = properties.assumeRoleExternalId; + this.assumeRoleAdditionalOptions = properties.assumeRoleAdditionalOptions; this.cloudFormationExecutionRoleArn = properties.cloudFormationExecutionRoleArn; this.stackTemplateAssetObjectUrl = properties.stackTemplateAssetObjectUrl; this.requiresBootstrapStackVersion = properties.requiresBootstrapStackVersion; diff --git a/packages/aws-cdk-lib/pipelines/lib/blueprint/stage-deployment.ts b/packages/aws-cdk-lib/pipelines/lib/blueprint/stage-deployment.ts index 813e7aacf9f12..0ad9d60082243 100644 --- a/packages/aws-cdk-lib/pipelines/lib/blueprint/stage-deployment.ts +++ b/packages/aws-cdk-lib/pipelines/lib/blueprint/stage-deployment.ts @@ -61,6 +61,9 @@ export class StageDeployment { const stepFromArtifact = new Map(); for (const artifact of assembly.stacks) { + if (artifact.assumeRoleAdditionalOptions?.Tags && artifact.assumeRoleArn) { + throw new Error(`Deployment of stack ${artifact.stackName} requires assuming the role ${artifact.assumeRoleArn} with session tags, but assuming roles with session tags is not supported by CodePipeline.`); + } const step = StackDeployment.fromArtifact(artifact); stepFromArtifact.set(artifact, step); } diff --git a/packages/aws-cdk-lib/pipelines/test/codepipeline/codepipeline.test.ts b/packages/aws-cdk-lib/pipelines/test/codepipeline/codepipeline.test.ts index ed265b78327f0..28821733c27f2 100644 --- a/packages/aws-cdk-lib/pipelines/test/codepipeline/codepipeline.test.ts +++ b/packages/aws-cdk-lib/pipelines/test/codepipeline/codepipeline.test.ts @@ -523,8 +523,30 @@ test('artifactBucket can be overridden', () => { }); }); +test('throws when deploy role session tags are used', () => { + + const synthesizer = new cdk.DefaultStackSynthesizer({ + deployRoleAdditionalOptions: { + Tags: [{ Key: 'Departement', Value: 'Enginerring' }], + }, + }); + + expect(() => { + new ReuseCodePipelineStack(app, 'PipelineStackA', { + env: PIPELINE_ENV, + reuseStageProps: { + reuseStackProps: { + synthesizer, + }, + }, + }); + }).toThrow('Deployment of stack SampleStage-123456789012-us-east-1-SampleStack requires assuming the role arn:${AWS::Partition}:iam::123456789012:role/cdk-hnb659fds-deploy-role-123456789012-us-east-1 with session tags, but assuming roles with session tags is not supported by CodePipeline.'); + +}); + interface ReuseCodePipelineStackProps extends cdk.StackProps { reuseCrossRegionSupportStacks?: boolean; + reuseStageProps?: ReuseStageProps; } class ReuseCodePipelineStack extends cdk.Stack { public constructor(scope: Construct, id: string, props: ReuseCodePipelineStackProps ) { @@ -559,6 +581,7 @@ class ReuseCodePipelineStack extends cdk.Stack { `SampleStage-${testStageEnv.account}-${testStageEnv.region}`, { env: testStageEnv, + ...(props.reuseStageProps ?? {}), }, ); pipeline.addStage(stage); @@ -566,10 +589,14 @@ class ReuseCodePipelineStack extends cdk.Stack { } } +interface ReuseStageProps extends cdk.StageProps { + readonly reuseStackProps?: cdk.StackProps; +} + class ReuseStage extends cdk.Stage { - public constructor(scope: Construct, id: string, props: cdk.StageProps) { + public constructor(scope: Construct, id: string, props: ReuseStageProps) { super(scope, id, props); - new ReuseStack(this, 'SampleStack', {}); + new ReuseStack(this, 'SampleStack', props.reuseStackProps ?? {}); } } diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index 3b3b8cb1722b7..6d8d341d7a8c5 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -1,5 +1,6 @@ import * as os from 'os'; import * as path from 'path'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; import type { ConfigurationOptions } from 'aws-sdk/lib/config-base'; @@ -13,6 +14,12 @@ import { ISDK, SDK, isUnrecoverableAwsError } from './sdk'; import { rootDir } from '../../util/directories'; import { traceMethods } from '../../util/tracing'; +// Partial because `RoleSessionName` is required in STS, but we have a default value for it. +export type AssumeRoleAdditionalOptions = Partial< +// cloud-assembly-schema validates that `ExternalId` and `RoleArn` are not configured +Omit +>; + // Some configuration that can only be achieved by setting // environment variables. process.env.AWS_STS_REGIONAL_ENDPOINTS = 'regional'; @@ -195,7 +202,8 @@ export class SdkProvider { } // We will proceed to AssumeRole using whatever we've been given. - const sdk = await this.withAssumedRole(baseCreds, options.assumeRoleArn, options.assumeRoleExternalId, env.region); + const sdk = await this.withAssumedRole(baseCreds, options.assumeRoleArn, + options.assumeRoleExternalId, options.assumeRoleAdditionalOptions, env.region); // Exercise the AssumeRoleCredentialsProvider we've gotten at least once so // we can determine whether the AssumeRole call succeeds or not. @@ -358,16 +366,20 @@ export class SdkProvider { masterCredentials: Exclude, roleArn: string, externalId: string | undefined, + additionalOptions: AssumeRoleAdditionalOptions | undefined, region: string | undefined) { debug(`Assuming role '${roleArn}'.`); region = region ?? this.defaultRegion; - const creds = new AWS.ChainableTemporaryCredentials({ params: { RoleArn: roleArn, - ...externalId ? { ExternalId: externalId } : {}, + ExternalId: externalId, RoleSessionName: `aws-cdk-${safeUsername()}`, + TransitiveTagKeys: additionalOptions?.Tags + ? additionalOptions.Tags.map((t) => t.Key) + : undefined, + ...(additionalOptions ?? {}), }, stsConfig: { region, @@ -513,6 +525,11 @@ export interface CredentialsOptions { * External ID required to assume the given role. */ readonly assumeRoleExternalId?: string; + + /** + * Session tags required to assume the given role. + */ + readonly assumeRoleAdditionalOptions?: AssumeRoleAdditionalOptions; } /** @@ -575,3 +592,21 @@ function fmtObtainedCredentials( return msg.join(''); } } + +/** + * Instantiate an SDK for context providers. This function ensures that all + * lookup assume role options are used when context providers perform lookups. + */ +export async function initContextProviderSdk(aws: SdkProvider, options: cxschema.ContextLookupRoleOptions): Promise { + + const account = options.account; + const region = options.region; + + const creds: CredentialsOptions = { + assumeRoleArn: options.lookupRoleArn, + assumeRoleExternalId: options.lookupRoleExternalId, + assumeRoleAdditionalOptions: options.assumeRoleAdditionalOptions, + }; + + return (await aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, creds)).sdk; +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml index dace6e7977a4a..8ed4bb8595446 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml @@ -277,6 +277,13 @@ Resources: Properties: AssumeRolePolicyDocument: Statement: + # allows this role to be assumed with session tags. + # see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_permissions-required + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId - Action: sts:AssumeRole Effect: Allow Principal: @@ -300,6 +307,13 @@ Resources: Properties: AssumeRolePolicyDocument: Statement: + # allows this role to be assumed with session tags. + # see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_permissions-required + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId - Action: sts:AssumeRole Effect: Allow Principal: @@ -323,6 +337,13 @@ Resources: Properties: AssumeRolePolicyDocument: Statement: + # allows this role to be assumed with session tags. + # see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_permissions-required + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId - Action: sts:AssumeRole Effect: Allow Principal: @@ -431,6 +452,13 @@ Resources: Properties: AssumeRolePolicyDocument: Statement: + # allows this role to be assumed with session tags. + # see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_permissions-required + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId - Action: sts:AssumeRole Effect: Allow Principal: @@ -623,7 +651,7 @@ Resources: Type: String Name: Fn::Sub: '/cdk-bootstrap/${Qualifier}/version' - Value: '21' + Value: '22' Outputs: BucketName: Description: The name of the S3 bucket owned by the CDK toolkit stack diff --git a/packages/aws-cdk/lib/api/deployments.ts b/packages/aws-cdk/lib/api/deployments.ts index 706e22db595a5..482c9fe20b408 100644 --- a/packages/aws-cdk/lib/api/deployments.ts +++ b/packages/aws-cdk/lib/api/deployments.ts @@ -492,6 +492,7 @@ export class Deployments { const stackSdk = await this.cachedSdkForEnvironment(resolvedEnvironment, mode, { assumeRoleArn: arns.assumeRoleArn, assumeRoleExternalId: stack.assumeRoleExternalId, + assumeRoleAdditionalOptions: stack.assumeRoleAdditionalOptions, }); return { @@ -538,6 +539,7 @@ export class Deployments { const stackSdk = await this.cachedSdkForEnvironment(resolvedEnvironment, Mode.ForReading, { assumeRoleArn: arns.lookupRoleArn, assumeRoleExternalId: stack.lookupRole?.assumeRoleExternalId, + assumeRoleAdditionalOptions: stack.lookupRole?.assumeRoleAdditionalOptions, }); const envResources = this.environmentResources.for(resolvedEnvironment, stackSdk.sdk); @@ -671,13 +673,19 @@ export class Deployments { mode: Mode, options?: CredentialsOptions, ) { - const cacheKey = [ + const cacheKeyElements = [ environment.account, environment.region, `${mode}`, options?.assumeRoleArn ?? '', options?.assumeRoleExternalId ?? '', - ].join(':'); + ]; + + if (options?.assumeRoleAdditionalOptions) { + cacheKeyElements.push(JSON.stringify(options.assumeRoleAdditionalOptions)); + } + + const cacheKey = cacheKeyElements.join(':'); const existing = this.sdkCache.get(cacheKey); if (existing) { return existing; diff --git a/packages/aws-cdk/lib/context-providers/ami.ts b/packages/aws-cdk/lib/context-providers/ami.ts index 062cb445e29ac..9c511cb6569a6 100644 --- a/packages/aws-cdk/lib/context-providers/ami.ts +++ b/packages/aws-cdk/lib/context-providers/ami.ts @@ -1,7 +1,5 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; -import { Mode } from '../api/aws-auth/credentials'; -import { SdkProvider } from '../api/aws-auth/sdk-provider'; +import { SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import { ContextProviderPlugin } from '../api/plugin'; import { debug, print } from '../logging'; @@ -21,8 +19,7 @@ export class AmiContextProviderPlugin implements ContextProviderPlugin { print(`Searching for AMI in ${account}:${region}`); debug(`AMI search parameters: ${JSON.stringify(args)}`); - const options = { assumeRoleArn: args.lookupRoleArn }; - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).sdk.ec2(); + const ec2 = (await initContextProviderSdk(this.aws, args)).ec2(); const response = await ec2.describeImages({ Owners: args.owners, Filters: Object.entries(args.filters).map(([key, values]) => ({ diff --git a/packages/aws-cdk/lib/context-providers/availability-zones.ts b/packages/aws-cdk/lib/context-providers/availability-zones.ts index 9572875052ae5..f1910c37709f2 100644 --- a/packages/aws-cdk/lib/context-providers/availability-zones.ts +++ b/packages/aws-cdk/lib/context-providers/availability-zones.ts @@ -1,7 +1,5 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; -import { Mode } from '../api/aws-auth/credentials'; -import { SdkProvider } from '../api/aws-auth/sdk-provider'; +import { SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import { ContextProviderPlugin } from '../api/plugin'; import { debug } from '../logging'; @@ -16,8 +14,7 @@ export class AZContextProviderPlugin implements ContextProviderPlugin { const region = args.region; const account = args.account; debug(`Reading AZs for ${account}:${region}`); - const options = { assumeRoleArn: args.lookupRoleArn }; - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).sdk.ec2(); + const ec2 = (await initContextProviderSdk(this.aws, args)).ec2(); const response = await ec2.describeAvailabilityZones().promise(); if (!response.AvailabilityZones) { return []; } const azs = response.AvailabilityZones.filter(zone => zone.State === 'available').map(zone => zone.ZoneName); diff --git a/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts b/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts index d8054c4ab9400..79f96d3cb94f4 100644 --- a/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts +++ b/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts @@ -1,6 +1,5 @@ -import * as cxapi from '@aws-cdk/cx-api'; -import { Mode } from '../api/aws-auth/credentials'; -import { SdkProvider } from '../api/aws-auth/sdk-provider'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import { SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import { ContextProviderPlugin } from '../api/plugin'; import { debug } from '../logging'; @@ -11,13 +10,12 @@ export class EndpointServiceAZContextProviderPlugin implements ContextProviderPl constructor(private readonly aws: SdkProvider) { } - public async getValue(args: { [key: string]: any }) { + public async getValue(args: cxschema.EndpointServiceAvailabilityZonesContextQuery) { const region = args.region; const account = args.account; const serviceName = args.serviceName; debug(`Reading AZs for ${account}:${region}:${serviceName}`); - const options = { assumeRoleArn: args.lookupRoleArn }; - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).sdk.ec2(); + const ec2 = (await initContextProviderSdk(this.aws, args)).ec2(); const response = await ec2.describeVpcEndpointServices({ ServiceNames: [serviceName] }).promise(); // expect a service in the response diff --git a/packages/aws-cdk/lib/context-providers/hosted-zones.ts b/packages/aws-cdk/lib/context-providers/hosted-zones.ts index 34d8c8657623a..fe5a4be52c4a0 100644 --- a/packages/aws-cdk/lib/context-providers/hosted-zones.ts +++ b/packages/aws-cdk/lib/context-providers/hosted-zones.ts @@ -1,7 +1,5 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; -import { Mode } from '../api/aws-auth/credentials'; -import { SdkProvider } from '../api/aws-auth/sdk-provider'; +import { SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import { ContextProviderPlugin } from '../api/plugin'; import { debug } from '../logging'; @@ -18,8 +16,7 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { } const domainName = args.domainName; debug(`Reading hosted zone ${account}:${region}:${domainName}`); - const options = { assumeRoleArn: args.lookupRoleArn }; - const r53 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).sdk.route53(); + const r53 = (await initContextProviderSdk(this.aws, args)).route53(); const response = await r53.listHostedZonesByName({ DNSName: domainName }).promise(); if (!response.HostedZones) { throw new Error(`Hosted Zone not found in account ${account}, region ${region}: ${domainName}`); diff --git a/packages/aws-cdk/lib/context-providers/keys.ts b/packages/aws-cdk/lib/context-providers/keys.ts index c0ec05a57d665..52c5dd08f140d 100644 --- a/packages/aws-cdk/lib/context-providers/keys.ts +++ b/packages/aws-cdk/lib/context-providers/keys.ts @@ -2,8 +2,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; import { PromiseResult } from 'aws-sdk/lib/request'; -import { Mode } from '../api/aws-auth/credentials'; -import { SdkProvider } from '../api/aws-auth/sdk-provider'; +import { SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import { ContextProviderPlugin } from '../api/plugin'; import { debug } from '../logging'; @@ -13,11 +12,7 @@ export class KeyContextProviderPlugin implements ContextProviderPlugin { } public async getValue(args: cxschema.KeyContextQuery) { - const account: string = args.account!; - const region: string = args.region!; - - const options = { assumeRoleArn: args.lookupRoleArn }; - const kms = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).sdk.kms(); + const kms = (await initContextProviderSdk(this.aws, args)).kms(); const aliasListEntry = await this.findKey(kms, args); diff --git a/packages/aws-cdk/lib/context-providers/load-balancers.ts b/packages/aws-cdk/lib/context-providers/load-balancers.ts index 959755d8e7c8b..0d5095f277e51 100644 --- a/packages/aws-cdk/lib/context-providers/load-balancers.ts +++ b/packages/aws-cdk/lib/context-providers/load-balancers.ts @@ -1,8 +1,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; -import { Mode } from '../api/aws-auth/credentials'; -import { SdkProvider } from '../api/aws-auth/sdk-provider'; +import { SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import { ContextProviderPlugin } from '../api/plugin'; /** @@ -13,8 +12,7 @@ export class LoadBalancerContextProviderPlugin implements ContextProviderPlugin } async getValue(query: cxschema.LoadBalancerContextQuery): Promise { - const options = { assumeRoleArn: query.lookupRoleArn }; - const elbv2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(query.account, query.region), Mode.ForReading, options)).sdk.elbv2(); + const elbv2 = (await initContextProviderSdk(this.aws, query)).elbv2(); if (!query.loadBalancerArn && !query.loadBalancerTags) { throw new Error('The load balancer lookup query must specify either `loadBalancerArn` or `loadBalancerTags`'); @@ -59,8 +57,7 @@ export class LoadBalancerListenerContextProviderPlugin implements ContextProvide } async getValue(query: LoadBalancerListenerQuery): Promise { - const options = { assumeRoleArn: query.lookupRoleArn }; - const elbv2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(query.account, query.region), Mode.ForReading, options)).sdk.elbv2(); + const elbv2 = (await initContextProviderSdk(this.aws, query)).elbv2(); if (!query.listenerArn && !query.loadBalancerArn && !query.loadBalancerTags) { throw new Error('The load balancer listener query must specify at least one of: `listenerArn`, `loadBalancerArn` or `loadBalancerTags`'); diff --git a/packages/aws-cdk/lib/context-providers/security-groups.ts b/packages/aws-cdk/lib/context-providers/security-groups.ts index 19372df9af842..a801841ca9ecf 100644 --- a/packages/aws-cdk/lib/context-providers/security-groups.ts +++ b/packages/aws-cdk/lib/context-providers/security-groups.ts @@ -1,8 +1,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; -import { Mode } from '../api/aws-auth/credentials'; -import { SdkProvider } from '../api/aws-auth/sdk-provider'; +import { SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import { ContextProviderPlugin } from '../api/plugin'; export class SecurityGroupContextProviderPlugin implements ContextProviderPlugin { @@ -10,8 +9,6 @@ export class SecurityGroupContextProviderPlugin implements ContextProviderPlugin } async getValue(args: cxschema.SecurityGroupContextQuery): Promise { - const account: string = args.account!; - const region: string = args.region!; if (args.securityGroupId && args.securityGroupName) { throw new Error('\'securityGroupId\' and \'securityGroupName\' can not be specified both when looking up a security group'); @@ -21,8 +18,7 @@ export class SecurityGroupContextProviderPlugin implements ContextProviderPlugin throw new Error('\'securityGroupId\' or \'securityGroupName\' must be specified to look up a security group'); } - const options = { assumeRoleArn: args.lookupRoleArn }; - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).sdk.ec2(); + const ec2 = (await initContextProviderSdk(this.aws, args)).ec2(); const filters: AWS.EC2.FilterList = []; if (args.vpcId) { diff --git a/packages/aws-cdk/lib/context-providers/ssm-parameters.ts b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts index 1d06448798023..f85fc4c96f117 100644 --- a/packages/aws-cdk/lib/context-providers/ssm-parameters.ts +++ b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts @@ -1,8 +1,6 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; -import { Mode } from '../api/aws-auth/credentials'; -import { SdkProvider } from '../api/aws-auth/sdk-provider'; +import { SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import { ContextProviderPlugin } from '../api/plugin'; import { debug } from '../logging'; @@ -23,7 +21,7 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { const parameterName = args.parameterName; debug(`Reading SSM parameter ${account}:${region}:${parameterName}`); - const response = await this.getSsmParameterValue(account, region, parameterName, args.lookupRoleArn); + const response = await this.getSsmParameterValue(args); const parameterNotFound: boolean = !response.Parameter || response.Parameter.Value === undefined; const suppressError = 'ignoreErrorOnMissingContext' in args && args.ignoreErrorOnMissingContext as boolean; if (parameterNotFound && suppressError && 'dummyValue' in args) { @@ -47,12 +45,11 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { * * @throws Error if a service error (other than ``ParameterNotFound``) occurs. */ - private async getSsmParameterValue(account: string, region: string, parameterName: string, lookupRoleArn?: string) + private async getSsmParameterValue(args: cxschema.SSMParameterContextQuery) : Promise { - const options = { assumeRoleArn: lookupRoleArn }; - const ssm = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).sdk.ssm(); + const ssm = (await initContextProviderSdk(this.aws, args)).ssm(); try { - return await ssm.getParameter({ Name: parameterName }).promise(); + return await ssm.getParameter({ Name: args.parameterName }).promise(); } catch (e: any) { if (e.code === 'ParameterNotFound') { return {}; diff --git a/packages/aws-cdk/lib/context-providers/vpcs.ts b/packages/aws-cdk/lib/context-providers/vpcs.ts index 74349e1b25748..d578df659b4ec 100644 --- a/packages/aws-cdk/lib/context-providers/vpcs.ts +++ b/packages/aws-cdk/lib/context-providers/vpcs.ts @@ -1,8 +1,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; -import { Mode } from '../api/aws-auth/credentials'; -import { SdkProvider } from '../api/aws-auth/sdk-provider'; +import { SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import { ContextProviderPlugin } from '../api/plugin'; import { debug } from '../logging'; @@ -12,11 +11,7 @@ export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin { } public async getValue(args: cxschema.VpcContextQuery) { - const account: string = args.account!; - const region: string = args.region!; - - const options = { assumeRoleArn: args.lookupRoleArn }; - const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading, options)).sdk.ec2(); + const ec2 = (await initContextProviderSdk(this.aws, args)).ec2(); const vpcId = await this.findVpc(ec2, args); diff --git a/packages/aws-cdk/lib/util/asset-publishing.ts b/packages/aws-cdk/lib/util/asset-publishing.ts index 47d83eb5c691c..1a7c5a0c9fc78 100644 --- a/packages/aws-cdk/lib/util/asset-publishing.ts +++ b/packages/aws-cdk/lib/util/asset-publishing.ts @@ -166,12 +166,18 @@ export class PublishingAws implements cdk_assets.IAws { region: options.region ?? this.targetEnv.region, // Default: same region as the stack }; - const cacheKey = JSON.stringify({ + const cacheKeyMap: any = { env, // region, name, account assumeRuleArn: options.assumeRoleArn, assumeRoleExternalId: options.assumeRoleExternalId, quiet: options.quiet, - }); + }; + + if (options.assumeRoleAdditionalOptions) { + cacheKeyMap.assumeRoleAdditionalOptions = options.assumeRoleAdditionalOptions; + } + + const cacheKey = JSON.stringify(cacheKeyMap); const maybeSdk = this.sdkCache.get(cacheKey); if (maybeSdk) { @@ -181,6 +187,7 @@ export class PublishingAws implements cdk_assets.IAws { const sdk = (await this.aws.forEnvironment(env, Mode.ForWriting, { assumeRoleArn: options.assumeRoleArn, assumeRoleExternalId: options.assumeRoleExternalId, + assumeRoleAdditionalOptions: options.assumeRoleAdditionalOptions, }, options.quiet)).sdk; this.sdkCache.set(cacheKey, sdk); diff --git a/packages/aws-cdk/test/api/fake-sts.ts b/packages/aws-cdk/test/api/fake-sts.ts index 026caecb2810f..26447de20cf02 100644 --- a/packages/aws-cdk/test/api/fake-sts.ts +++ b/packages/aws-cdk/test/api/fake-sts.ts @@ -21,6 +21,8 @@ interface AssumedRole { readonly serialNumber: string; readonly tokenCode: string; readonly roleSessionName: string; + readonly tags?: AWS.STS.Tag[]; + readonly transitiveTagKeys?: string[]; } /** @@ -158,6 +160,28 @@ export class FakeSts { }; } + /** + * Maps have a funky encoding to them when sent to STS. + * + * @see https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html + */ + private decodeMapFromRequestBody(parameter: string, body: Record): AWS.STS.Tag[] { + return Object.entries(body) + .filter(([key, _]) => key.startsWith(`${parameter}.member.`) && key.endsWith('.Key')) + .map(([key, tagKey]) => ({ Key: tagKey, Value: body[`${parameter}.member.${key.split('.')[2]}.Value`] })); + } + + /** + * Lists have a funky encoding when sent to STS. + * + * @see https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html + */ + private decodeListKeysFromRequestBody(parameter: string, body: Record): string[] { + return Object.entries(body) + .filter(([key]) => key.startsWith(`${parameter}.member.`)) + .map(([, value]) => value); + } + private handleAssumeRole(identity: RegisteredIdentity, mockRequest: MockRequest): Record { this.checkForFailure(mockRequest.parsedBody.RoleArn); @@ -166,6 +190,8 @@ export class FakeSts { roleSessionName: mockRequest.parsedBody.RoleSessionName, serialNumber: mockRequest.parsedBody.SerialNumber, tokenCode: mockRequest.parsedBody.TokenCode, + tags: this.decodeMapFromRequestBody('Tags', mockRequest.parsedBody), + transitiveTagKeys: this.decodeListKeysFromRequestBody('TransitiveTagKeys', mockRequest.parsedBody), }); const roleArn = mockRequest.parsedBody.RoleArn; @@ -255,6 +281,7 @@ interface MockRequest { readonly uri: string; readonly headers: Record; readonly parsedBody: Record; + readonly sessionTags?: { [key: string]: string }; } function urldecode(body: string): Record { diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index 22bf095de4ec7..05f3cba57b4bf 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -342,6 +342,39 @@ describe('with intercepted network calls', () => { }); }); + test('session tags can be passed when assuming a role', async () => { + // GIVEN + prepareCreds({ + fakeSts, + config: { + default: { aws_access_key_id: 'foo', $account: '11111' }, + }, + }); + + await withMocked(os, 'userInfo', async (userInfo) => { + userInfo.mockReturnValue({ username: 'skål', uid: 1, gid: 1, homedir: '/here', shell: '/bin/sh' }); + + // WHEN + const provider = await providerFromProfile(undefined); + + const sdk = (await provider.forEnvironment(env(uniq('88888')), Mode.ForReading, + { + assumeRoleArn: 'arn:aws:role', + assumeRoleExternalId: 'bruh', + assumeRoleAdditionalOptions: { + Tags: [{ Key: 'Department', Value: 'Engineering' }], + }, + })).sdk as SDK; + await sdk.currentAccount(); + + // THEN + expect(fakeSts.assumedRoles[0]).toEqual(expect.objectContaining({ + tags: [{ Key: 'Department', Value: 'Engineering' }], + transitiveTagKeys: ['Department'], + })); + }); + }); + test('assuming a role does not fail when OS username cannot be read', async () => { // GIVEN prepareCreds({