diff --git a/aws_sra_examples/solutions/common/common_cfct_setup/README.md b/aws_sra_examples/solutions/common/common_cfct_setup/README.md index 113cfce98..f85b450be 100644 --- a/aws_sra_examples/solutions/common/common_cfct_setup/README.md +++ b/aws_sra_examples/solutions/common/common_cfct_setup/README.md @@ -32,8 +32,27 @@ factory, the solution ensures that all resources attached to the account's OUs w - All resources are deployed via AWS CloudFormation as a Stack within the management account. - For parameter details, review the AWS [CloudFormation templates](templates/). -#### 1.2 Customizations for AWS Control Tower CloudFormation Template +#### 1.2 Lambda Execution IAM Role +- IAM role used by the Lambda function to perform the start operation for the sra-codebuild AWS CodeBuild project. + +#### 1.3 AWS Lambda Function + +The Lambda function to perform the start operation for the sra-codebuild AWS CodeBuild project. + +#### 1.4 Lambda CloudWatch Log Group + +- All the `AWS Lambda Function` logs are sent to a CloudWatch Log Group `` to help with debugging and traceability of the actions performed. +- By default the `AWS Lambda Function` will create the CloudWatch Log Group with a `Retention` (Never expire) and are encrypted with a CloudWatch Logs service managed encryption key. + +#### 1.5 AWS CodeBuild Project Created +The sra-codebuild AWS CodeBuild project is designed and created to download the latest customizations-for-aws-control-tower.template template from GitHub and upload it to the AWS SRA code library staging S3 bucket. + +#### 1.6 CodeBuild IAM Role + +- IAM role used by the CodeBuild project. + +#### 1.7 Customizations for AWS Control Tower CloudFormation Template - The [Customizations for AWS Control Tower](https://aws.amazon.com/solutions/implementations/customizations-for-aws-control-tower/) (CFCT) solution to support deploying customizations easily to your AWS Control Tower landing zone. - Defaults updated per SRA recommendations: - Latest template downloaded from [GitHub - customizations-for-aws-control-tower.template](https://github.com/aws-solutions/aws-control-tower-customizations/blob/main/customizations-for-aws-control-tower.template) diff --git a/aws_sra_examples/solutions/common/common_cfct_setup/documentation/common-cfct-setup.png b/aws_sra_examples/solutions/common/common_cfct_setup/documentation/common-cfct-setup.png index cd5227753..cec8ef97b 100644 Binary files a/aws_sra_examples/solutions/common/common_cfct_setup/documentation/common-cfct-setup.png and b/aws_sra_examples/solutions/common/common_cfct_setup/documentation/common-cfct-setup.png differ diff --git a/aws_sra_examples/solutions/common/common_cfct_setup/documentation/common-cfct-setup.pptx b/aws_sra_examples/solutions/common/common_cfct_setup/documentation/common-cfct-setup.pptx index ce392c0b4..972028c1c 100644 Binary files a/aws_sra_examples/solutions/common/common_cfct_setup/documentation/common-cfct-setup.pptx and b/aws_sra_examples/solutions/common/common_cfct_setup/documentation/common-cfct-setup.pptx differ diff --git a/aws_sra_examples/solutions/common/common_cfct_setup/templates/customizations-for-aws-control-tower.template b/aws_sra_examples/solutions/common/common_cfct_setup/templates/customizations-for-aws-control-tower.template deleted file mode 100644 index 35eaaa2fc..000000000 --- a/aws_sra_examples/solutions/common/common_cfct_setup/templates/customizations-for-aws-control-tower.template +++ /dev/null @@ -1,3137 +0,0 @@ -# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). -# You may not use this file except in compliance with the License. -# A copy of the License is located at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# or in the "license" file accompanying this file. This file is distributed -# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -# express or implied. See the License for the specific language governing -# permissions and limitations under the License. - -AWSTemplateFormatVersion: '2010-09-09' -Description: '(SO0089) - customizations-for-aws-control-tower Solution. Version: v2.5.3' - -Parameters: - PipelineApprovalStage: - Description: Do you want to add a manual approval stage to the Custom Control Tower Configuration Pipeline? - AllowedValues: - - 'Yes' - - 'No' - Default: 'No' - Type: String - - PipelineApprovalEmail: - Description: (Not required if Pipeline Approval Stage = 'No') Email for notifying that the CustomControlTower pipeline is waiting for an Approval - Type: String - - CodePipelineSource: - Description: Which AWS CodePipeline source provider do you want to select? - AllowedValues: - - 'Amazon S3' - - 'AWS CodeCommit' - Default: 'Amazon S3' - Type: String - - CodeCommitRepositoryName: - Description: Name of the CodeCommit repository that contains custom Control Tower configuration. The suffix .git is prohibited. - Default: custom-control-tower-configuration - Type: String - AllowedPattern: ^[\w\.-]+ - - CodeCommitBranchName: - Description: Name of the branch in CodeCommit repository that contains custom Control Tower configuration. - Default: main - Type: String - - ExistingRepository: - Description: Are you using an existing CodeCommit repository that already contains custom Control Tower configuration? - Default: 'No' - Type: String - AllowedValues: - - 'Yes' - - 'No' - - RegionConcurrencyType: - Description: Select the the concurrency type of deploying StackSets operations in Regions. - Default: 'PARALLEL' - Type: String - AllowedValues: - - 'PARALLEL' - - 'SEQUENTIAL' - - MaxConcurrentPercentage: - Description: The maximum percentage of accounts in which to perform this operation at one time. - Default: 100 - Type: String - - FailureTolerancePercentage: - Description: The percentage of accounts, per Region, for which this stack operation can fail before AWS CloudFormation stops the operation in that Region. - Default: 10 - Type: String - - EnforceSuccessfulStackInstances: - Description: By default, CfCT's deployment pipeline defers to Stack Sets to report failures based on the combination of concurrency and fault tolerance you choose. Setting this parameter to true will consider a Stack Set deployment that contains failed stack instance deployments to be a failure in the deployment pipeline, regardless of fault tolerance you specify. This allows for you to specify 100% concurrency, but stop the pipeline post-deployment if stack instances fail to deploy. - Default: false - Type: String - AllowedValues: - - true - - false - -Metadata: - AWS::CloudFormation::Interface: - ParameterGroups: - - Label: - default: Pipeline Configuration - Parameters: - - PipelineApprovalStage - - PipelineApprovalEmail - - CodePipelineSource - - Label: - default: AWS CodeCommit Setup (Applicable if 'AWS CodeCommit' was selected as the CodePipeline Source) - Parameters: - - ExistingRepository - - CodeCommitRepositoryName - - CodeCommitBranchName - - Label: - default: AWS CloudFormation StackSets Configuration - Parameters: - - RegionConcurrencyType - - MaxConcurrentPercentage - - FailureTolerancePercentage - - ParameterLabels: - PipelineApprovalStage: - default: Pipeline Approval Stage - PipelineApprovalEmail: - default: Pipeline Approval Email Address - CodePipelineSource: - default: AWS CodePipeline Source - ExistingRepository: - default: Existing CodeCommit Repository? - CodeCommitRepositoryName: - default: CodeCommit Repository Name - CodeCommitBranchName: - default: CodeCommit Branch Name - RegionConcurrencyType: - default: Region Concurrency Type - MaxConcurrentPercentage: - default: Max Concurrent Percentage - FailureTolerancePercentage: - default: Failure Tolerance Percentage - -Mappings: - BucketConfiguration: - SourceBucketName: - Name: control-tower-cfct-assets-prod - SourceKeyName: - Name: customizations-for-aws-control-tower/v2.5.3/custom-control-tower-configuration.zip - CustomControlTowerPipelineS3TriggerKey: - Name: custom-control-tower-configuration.zip - CustomControlTowerPipelineS3NonTriggerKey: - Name: _custom-control-tower-configuration.zip - CodePipelineSource: - CodeCommit: - RepoName: /Customizations-for-aws-control-tower/CodeCommitRepoName - BranchName: /Customizations-for-aws-control-tower/CodeCommitBranchName - KMS: - Alias: - Name: CustomControlTowerKMSKey - Solution: - Metrics: - SendAnonymousData: 'Yes' - SolutionID: 'SO0089' - MetricsURL: 'https://metrics.awssolutionsbuilder.com/generic' - Data: - AddonTemplate: 'https://s3.amazonaws.com/control-tower-cfct-assets-prod/customizations-for-aws-control-tower/v2.5.3/custom-control-tower-initiation.template' - AWSControlTower: - ExecutionRole: - Name: "AWSControlTowerExecution" - LambdaFunction: - Logging: - Level: 'info' - FindReplace: - Values: - NoneType: 'null' - BoolType: 'yes,no,Yes,No,True,False,true,false' # no spaces are allowed in this string, comma is the only allowed delimiter - AutoBuild: - CustomControlTower: - Flag: 'No' - ControlTowerBaselineConfigStackset: - Info: - Name: 'AWSControlTowerBP-BASELINE-CONFIG' - - -Conditions: - IsPipelineApprovalStageCondition: !Equals [!Ref PipelineApprovalStage, 'Yes'] - IsBuildCustomControlTowerCondition: !Equals [!FindInMap [AutoBuild, CustomControlTower, Flag], 'Yes'] - IsCodeCommitPipelineSource: !Equals [!Ref CodePipelineSource, 'AWS CodeCommit'] - IsExistingRepository: !Equals [!Ref ExistingRepository, 'Yes'] - IsNewCodeCommitRepository: !And [!Not [!Condition IsExistingRepository], !Condition IsCodeCommitPipelineSource] - -Resources: - - PipelineApprovalTopic: - Type: AWS::SNS::Topic - Condition: IsPipelineApprovalStageCondition - Properties: - KmsMasterKeyId: alias/aws/sns - Subscription: - - Endpoint: !Ref PipelineApprovalEmail - Protocol: email - - CustomControlTowerPipelineS3Bucket: - Type: AWS::S3::Bucket - DeletionPolicy: Retain - UpdateReplacePolicy: Retain - Properties: - BucketName: !Sub custom-control-tower-configuration-${AWS::AccountId}-${AWS::Region} - BucketEncryption: - ServerSideEncryptionConfiguration: - - ServerSideEncryptionByDefault: - SSEAlgorithm: AES256 - VersioningConfiguration: - Status: Enabled - LoggingConfiguration: - DestinationBucketName: !Ref CustomControlTowerS3AccessLogsBucket - PublicAccessBlockConfiguration: - BlockPublicAcls: True - BlockPublicPolicy: True - IgnorePublicAcls: True - RestrictPublicBuckets: True - - CustomControlTowerPipelineS3BucketPolicy: - Type: AWS::S3::BucketPolicy - Properties: - Bucket: !Ref CustomControlTowerPipelineS3Bucket - PolicyDocument: - Statement: - - Sid: DenyDeleteBucket - Effect: Deny - Principal: "*" - Action: s3:DeleteBucket - Resource: !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineS3Bucket} - - CustomControlTowerPipelineArtifactS3Bucket: - Type: AWS::S3::Bucket - DeletionPolicy: Retain - UpdateReplacePolicy: Retain - Properties: - VersioningConfiguration: - Status: Enabled - LoggingConfiguration: - DestinationBucketName: !Ref CustomControlTowerS3AccessLogsBucket - BucketEncryption: - ServerSideEncryptionConfiguration: - - ServerSideEncryptionByDefault: - SSEAlgorithm: AES256 - PublicAccessBlockConfiguration: - BlockPublicAcls: True - BlockPublicPolicy: True - IgnorePublicAcls: True - RestrictPublicBuckets: True - - CustomControlTowerPipelineArtifactS3BucketPolicy: - Type: AWS::S3::BucketPolicy - Properties: - Bucket: !Ref CustomControlTowerPipelineArtifactS3Bucket - PolicyDocument: - Statement: - - Sid: DenyDeleteBucket - Effect: Deny - Principal: "*" - Action: s3:DeleteBucket - Resource: !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineArtifactS3Bucket} - - # Create buckets using S3-SSE keys for default encryption - CustomControlTowerS3AccessLogsBucket: - DeletionPolicy: Retain - UpdateReplacePolicy: Retain - Type: AWS::S3::Bucket - Metadata: - cfn_nag: - rules_to_suppress: - - id: W35 - reason: "This S3 bucket is used as the destination for 'CustomControlTowerPipelineS3Bucket' and 'CustomControlTowerPipelineArtifactS3Bucket'" - checkov: - skip: - - id: CKV_AWS_18 - comment: S3 access logging is not enabled. - Properties: - AccessControl: LogDeliveryWrite - VersioningConfiguration: - Status: Enabled - BucketEncryption: - ServerSideEncryptionConfiguration: - - ServerSideEncryptionByDefault: - SSEAlgorithm: AES256 - PublicAccessBlockConfiguration: - BlockPublicAcls: True - BlockPublicPolicy: True - IgnorePublicAcls: True - RestrictPublicBuckets: True - - CustomControlTowerS3AccessLogsBucketPolicy: - Type: AWS::S3::BucketPolicy - Properties: - Bucket: !Ref CustomControlTowerS3AccessLogsBucket - PolicyDocument: - Statement: - - Sid: DenyDeleteBucket - Effect: Deny - Principal: "*" - Action: s3:DeleteBucket - Resource: !Sub "arn:${AWS::Partition}:s3:::${CustomControlTowerS3AccessLogsBucket}" - - Sid: EnableS3AccessLoggingForPipelineS3Bucket - Effect: Allow - Principal: - Service: logging.s3.amazonaws.com - Action: - - s3:PutObject - Resource: !Sub "arn:${AWS::Partition}:s3:::${CustomControlTowerS3AccessLogsBucket}/*" - Condition: - ArnLike: - "aws:SourceArn": !Sub "arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineS3Bucket}" - StringEquals: - "aws:SourceAccount": !Ref AWS::AccountId - - Sid: EnableS3AccessLoggingForPipelineArtifactS3Bucket - Effect: Allow - Principal: - Service: logging.s3.amazonaws.com - Action: - - s3:PutObject - Resource: !Sub "arn:${AWS::Partition}:s3:::${CustomControlTowerS3AccessLogsBucket}/*" - Condition: - ArnLike: - "aws:SourceArn": !Sub "arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineArtifactS3Bucket}" - StringEquals: - "aws:SourceAccount": !Ref AWS::AccountId - - CustomControlTowerCodeCommit: - Type: AWS::CodeCommit::Repository - DeletionPolicy: Retain - UpdateReplacePolicy: Retain - Condition: IsNewCodeCommitRepository - Properties: - RepositoryDescription: Configuration for Customizations for AWS Control Tower solution - RepositoryName: !Ref CodeCommitRepositoryName - Code: - S3: - Bucket: control-tower-cfct-assets-prod - Key: !Sub customizations-for-aws-control-tower/v2.5.3/custom-control-tower-configuration-${AWS::Region}.zip - - # SSM Parameter to store the git repository name - CustomControlTowerRepoNameParameter: - Type: AWS::SSM::Parameter - Properties: - Name: - Fn::FindInMap: - - CodePipelineSource - - CodeCommit - - RepoName - Description: Contains the name of the CodeCommit repository - Type: String - Value: !Ref CodeCommitRepositoryName - - # SSM Parameter to store the git repository branch name - CustomControlTowerBranchNameParameter: - Type: AWS::SSM::Parameter - Properties: - Name: - Fn::FindInMap: - - CodePipelineSource - - CodeCommit - - BranchName - Description: Contains the name of the CodeCommit repository branch - Type: String - Value: !Ref CodeCommitBranchName - - CustomControlTowerCodePipelineRole: - Type: AWS::IAM::Role - Metadata: - cfn_nag: - rules_to_suppress: - - id: W28 - reason: "The role name is defined to identify Custom Control Tower resources." - - id: W11 - reason: "Allow Resource * for KMS/SSM API. KMS Service only support all resources. Key ID is generated by the service. SSM parameters are customer defined." - Properties: - RoleName: CustomControlTowerCodePipelineRole - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Principal: - Service: - - "codepipeline.amazonaws.com" - Action: - - "sts:AssumeRole" - Path: "/" - Policies: - - PolicyName: "Custom-Control-Tower-CodePipeline-Policy" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Action: - - s3:GetBucketVersioning - Resource: - - !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineArtifactS3Bucket} - - !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineS3Bucket} - - Effect: "Allow" - Action: - - s3:PutObject - - s3:GetObject - - s3:GetObjectVersion - Resource: - - !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineArtifactS3Bucket}/* - - !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineS3Bucket}/* - - Effect: Allow - Action: - - ssm:PutParameter - - ssm:GetParameter - - ssm:DeleteParameter - - ssm:GetParametersByPath - - ssm:DescribeParameters - Resource: '*' - - Effect: "Allow" - Action: - - "codebuild:BatchGetBuilds" - - "codebuild:StartBuild" - Resource: - - !Sub arn:${AWS::Partition}:codebuild:${AWS::Region}:${AWS::AccountId}:project/${CustomControlTowerCodeBuild} - - !Sub arn:${AWS::Partition}:codebuild:${AWS::Region}:${AWS::AccountId}:project/${SCPCodeBuild} - - !Sub arn:${AWS::Partition}:codebuild:${AWS::Region}:${AWS::AccountId}:project/${StackSetCodeBuild} - - Effect: "Allow" - Action: - - codecommit:GetBranch - - codecommit:GetCommit - - codecommit:UploadArchive - - codecommit:GetUploadArchiveStatus - - codecommit:CancelUploadArchive - Resource: "*" - - Effect: "Allow" - Action: - - lambda:ListFunctions - - lambda:ListVersionsByFunction - Resource: "*" - - !If - - IsPipelineApprovalStageCondition - - Effect: "Allow" - Action: - - "sns:Publish" - Resource: !Ref PipelineApprovalTopic - - !Ref AWS::NoValue - - CustomControlTowerCodePipeline: - Type: AWS::CodePipeline::Pipeline - Properties: - Name: Custom-Control-Tower-CodePipeline - RoleArn: !GetAtt CustomControlTowerCodePipelineRole.Arn - ArtifactStore: - Location: !Ref CustomControlTowerPipelineArtifactS3Bucket - Type: S3 - Stages: - - Name: Source - Actions: - - Name: Source - ActionTypeId: - !If - - IsCodeCommitPipelineSource - - Category: Source - Owner: AWS - Version: "1" - Provider: CodeCommit - - Category: Source - Owner: AWS - Version: "1" - Provider: S3 - OutputArtifacts: - - Name: SourceApp - Configuration: - !If - - IsCodeCommitPipelineSource - - RepositoryName: !Ref CodeCommitRepositoryName - BranchName: !Ref CodeCommitBranchName - - S3Bucket: !Ref CustomControlTowerPipelineS3Bucket - S3ObjectKey: !FindInMap [BucketConfiguration, CustomControlTowerPipelineS3TriggerKey, Name] - RunOrder: 1 - - Name: Build - Actions: - - Name: CodeBuild - InputArtifacts: - - Name: SourceApp - ActionTypeId: - Category: Build - Owner: AWS - Version: "1" - Provider: CodeBuild - OutputArtifacts: - - Name: BuiltApp - Configuration: - ProjectName: !Ref CustomControlTowerCodeBuild - - !If - - IsPipelineApprovalStageCondition - - Name: Approval - Actions: - - Name: Approval - ActionTypeId: - Category: Approval - Owner: AWS - Version: "1" - Provider: Manual - RunOrder: 1 - Configuration: - NotificationArn: !Ref PipelineApprovalTopic - - !Ref AWS::NoValue - - Name: ServiceControlPolicy - Actions: - - Name: CodeBuild - InputArtifacts: - - Name: BuiltApp - ActionTypeId: - Category: Build - Owner: AWS - Version: "1" - Provider: CodeBuild - Configuration: - ProjectName: !Ref SCPCodeBuild - - Name: CloudformationResource - Actions: - - Name: CodeBuild - InputArtifacts: - - Name: BuiltApp - ActionTypeId: - Category: Build - Owner: AWS - Version: "1" - Provider: CodeBuild - Configuration: - ProjectName: !Ref StackSetCodeBuild - - CustomControlTowerCodeBuildRole: - Type: "AWS::IAM::Role" - Metadata: - cfn_nag: - rules_to_suppress: - - id: W11 - reason: "Allow Resource * for Cloudformation/SSM API: needs to support user defined cfn templates and ssm parameter names." - checkov: - skip: - - id: CKV_AWS_108 - comment: "Allow Resource * for Cloudformation/SSM API: needs to support user defined cfn templates and ssm parameter names." - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Principal: - Service: - - "codebuild.amazonaws.com" - Action: - - "sts:AssumeRole" - Path: "/" - Policies: - - PolicyName: "Custom-Control-Tower-CodeBuild-Policy" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - Resource: - - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/* - - Effect: "Allow" - Action: - - s3:PutObject - - s3:GetObjectVersion - - s3:DeleteObject - Resource: - - !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineArtifactS3Bucket}/* - - Effect: Allow - Action: - - s3:GetObject - - cloudformation:ValidateTemplate - Resource: "*" - - Effect: "Allow" - Action: - - s3:GetObject - Resource: - - !Sub arn:${AWS::Partition}:s3:::control-tower-cfct-assets-prod/* - - Effect: Allow - Action: - - ssm:GetParameter - - ssm:GetParametersByPath - Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/* - - Effect: Allow - Action: - - ssm:DescribeParameters - Resource: '*' # The APIs above only support '*' resource. - - CustomControlTowerCodeBuild: - Type: AWS::CodeBuild::Project - DependsOn: CustomControlTowerDeploymentLambda - Properties: - Name: Custom-Control-Tower-CodeBuild - ServiceRole: !GetAtt CustomControlTowerCodeBuildRole.Arn - EncryptionKey: !Sub - - alias/${KMSKeyName} - - {KMSKeyName: !FindInMap [KMS, Alias, Name]} - Source: - Type: CODEPIPELINE - BuildSpec: "version: 0.2\nphases:\n install:\n runtime-versions:\n python: 3.8\n ruby: 2.6\n commands:\n - export current=$(pwd)\n - if [ -f manifest.yaml ];then export current=$(pwd);else if [ -f custom-control-tower-configuration/manifest.yaml ]; then export current=$(pwd)/custom-control-tower-configuration; else echo 'manifest.yaml does not exist at the root level of custom-control-tower-configuration.zip or inside custom-control-tower-configuration folder, please check the ZIP file'; exit 1; fi; fi;\n - apt-get -q update 1> /dev/null\n - apt-get -q install zip wget python3-pip libyaml-dev -y 1>/dev/null\n - export LC_ALL='en_US.UTF-8'\n - locale-gen en_US en_US.UTF-8\n - dpkg-reconfigure locales --frontend noninteractive\n pre_build:\n commands:\n - cd $current\n - echo 'Download CustomControlTower Scripts'\n - aws s3 cp --quiet s3://control-tower-cfct-assets-prod/customizations-for-aws-control-tower/v2.5.3/custom-control-tower-scripts.zip $current\n - unzip -q -o $current/custom-control-tower-scripts.zip -d $current\n - cp codebuild_scripts/* .\n - bash install_stage_dependencies.sh $STAGE_NAME\n build:\n commands:\n - echo 'Starting build $(date) in $(pwd)'\n - echo 'bash execute_stage_scripts.sh $STAGE_NAME $LOG_LEVEL $WAIT_TIME $SM_ARN $ARTIFACT_BUCKET $KMS_KEY_ALIAS_NAME $BOOL_VALUES $NONE_TYPE_VALUES'\n - bash execute_stage_scripts.sh $STAGE_NAME $LOG_LEVEL $WAIT_TIME $SM_ARN $ARTIFACT_BUCKET $KMS_KEY_ALIAS_NAME $BOOL_VALUES $NONE_TYPE_VALUES \n - echo 'Running build scripts completed $(date)'\n post_build:\n commands:\n - echo 'Starting post build $(date) in $(pwd)'\n - echo 'build completed on $(date)'\n\nartifacts:\n files:\n - '**/*'\n\n" - Environment: - ComputeType: BUILD_GENERAL1_SMALL - Image: "aws/codebuild/standard:5.0" - Type: LINUX_CONTAINER - EnvironmentVariables: - - Name: ARTIFACT_BUCKET - Value: !Ref CustomControlTowerPipelineArtifactS3Bucket - - Name: NONE_TYPE_VALUES - Value: !FindInMap [FindReplace, Values, NoneType] - - Name: BOOL_VALUES - Value: !FindInMap [FindReplace, Values, BoolType] - - Name: STAGE_NAME - Value: "build" - - Name: SM_ARN - Value: "NA" - - Name: LOG_LEVEL - Value: !FindInMap [LambdaFunction, Logging, Level] - - Name: WAIT_TIME - Value: "15" - - Name: KMS_KEY_ALIAS_NAME - Value: !FindInMap [KMS, Alias, Name] - - Name: SOLUTION_ID - Value: !FindInMap [ Solution, Metrics, SolutionID ] - - Name: SOLUTION_VERSION - Value: v2.5.3 - Artifacts: - Name: !Sub ${CustomControlTowerPipelineArtifactS3Bucket}-Built - Type: CODEPIPELINE - - SCPCodeBuildRole: - Type: "AWS::IAM::Role" - Metadata: - cfn_nag: - rules_to_suppress: - - id: W11 - reason: "Allow * for Organizations APIs to list/describe/move user created child accounts in the AWS Organizations" - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Principal: - Service: - - "codebuild.amazonaws.com" - Action: - - "sts:AssumeRole" - Path: "/" - Policies: - - PolicyName: "Custom-Control-Tower-SCP-CodeBuild-Policy-Logs" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - Resource: - - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/* - - PolicyName: "Custom-Control-Tower-SCP-CodeBuild-Policy-S3" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Action: - - s3:GetObject - - s3:PutObject - Resource: - - !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineArtifactS3Bucket}/* - - Effect: "Allow" - Action: - - s3:GetObject - Resource: - - !Sub arn:${AWS::Partition}:s3:::*/* # needed to support validation of remotely sourced templates feature. The host S3 bucket can be created by the customers or partners. - - PolicyName: "Custom-Control-Tower-SCP-CodeBuild-Policy-StepFunctions" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - states:ListExecutions - - states:StartExecution - - states:StopExecution - - states:DescribeStateMachine - Resource: - - !Ref ServiceControlPolicyMachine - - Effect: Allow - Action: - - states:DescribeStateMachineForExecution - - states:DescribeExecution - Resource: - - !Sub arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:execution:${ServiceControlPolicyMachine.Name}:* - - PolicyName: "Custom-Control-Tower-SCP-CodeBuild-Policy-Organizations" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - organizations:ListRoots - - organizations:ListOrganizationalUnitsForParent - - organizations:ListAccountsForParent - Resource: '*' # The APIs above only support '*' resource. - - PolicyName: "Custom-Control-Tower-SCP-CodeBuild-Policy-SSM" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - ssm:GetParameter - - ssm:GetParametersByPath - Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/* - - Effect: Allow - Action: - - ssm:DescribeParameters - Resource: '*' # The APIs above only support '*' resource. - - SCPCodeBuild: - Type: AWS::CodeBuild::Project - DependsOn: CustomControlTowerDeploymentLambda - Properties: - Name: Custom-Control-Tower-SCP-CodeBuild - ServiceRole: !GetAtt SCPCodeBuildRole.Arn - EncryptionKey: !Sub - - alias/${KMSKeyName} - - {KMSKeyName: !FindInMap [KMS, Alias, Name]} - Source: - Type: CODEPIPELINE - BuildSpec: "version: 0.2\nphases:\n install:\n runtime-versions:\n python: 3.8\n ruby: 2.6\n commands:\n - export current=$(pwd)\n - if [ -f manifest.yaml ];then export current=$(pwd);else if [ -f custom-control-tower-configuration/manifest.yaml ]; then export current=$(pwd)/custom-control-tower-configuration; else echo 'manifest.yaml does not exist at the root level of custom-control-tower-configuration.zip or inside custom-control-tower-configuration folder, please check the ZIP file'; exit 1; fi; fi;\n - apt-get -q update 1> /dev/null\n - apt-get -q install zip wget python3-pip libyaml-dev -y 1> /dev/null \n pre_build:\n commands:\n - cd $current\n - echo 'Download CustomControlTower Scripts'\n - aws s3 cp --quiet s3://control-tower-cfct-assets-prod/customizations-for-aws-control-tower/v2.5.3/custom-control-tower-scripts.zip $current\n - unzip -q -o $current/custom-control-tower-scripts.zip -d $current\n - cp codebuild_scripts/* .\n - bash install_stage_dependencies.sh $STAGE_NAME\n build:\n commands:\n - echo 'Starting build $(date) in $(pwd)'\n - echo 'bash execute_stage_scripts.sh $STAGE_NAME $LOG_LEVEL $WAIT_TIME $SM_ARN $ARTIFACT_BUCKET $KMS_KEY_ALIAS_NAME $BOOL_VALUES $NONE_TYPE_VALUES'\n - bash execute_stage_scripts.sh $STAGE_NAME $LOG_LEVEL $WAIT_TIME $SM_ARN $ARTIFACT_BUCKET $KMS_KEY_ALIAS_NAME $BOOL_VALUES $NONE_TYPE_VALUES\n - echo 'Running build scripts completed $(date)'\n post_build:\n commands:\n - echo 'Starting post build $(date) in $(pwd)'\n - echo 'build completed on $(date)'\n\nartifacts:\n files:\n - '**/*'\n" - Environment: - ComputeType: BUILD_GENERAL1_SMALL - Image: "aws/codebuild/standard:5.0" - Type: LINUX_CONTAINER - EnvironmentVariables: - - Name: SM_ARN - Value: !Ref ServiceControlPolicyMachine - - Name: LOG_LEVEL - Value: !FindInMap [LambdaFunction, Logging, Level] - - Name: WAIT_TIME - Value: "15" - - Name: STAGE_NAME - Value: "scp" - - Name: ARTIFACT_BUCKET - Value: !Ref CustomControlTowerPipelineArtifactS3Bucket - - Name: KMS_KEY_ALIAS_NAME - Value: !FindInMap [KMS, Alias, Name] - - Name: SOLUTION_ID - Value: !FindInMap [ Solution, Metrics, SolutionID ] - - Name: SOLUTION_VERSION - Value: v2.5.3 - Artifacts: - Name: !Sub ${CustomControlTowerPipelineArtifactS3Bucket}-Built - Type: CODEPIPELINE - TimeoutInMinutes: 60 - - StackSetCodeBuildRole: - Type: "AWS::IAM::Role" - Metadata: - cfn_nag: - rules_to_suppress: - - id: W11 - reason: "Allow * for Organizations APIs to list/describe/move user created child accounts in the AWS Organizations" - - id: W11 - reason: "Allow * for ec2 APIs because information like account, region, etc. are dynamically determined by custom configuration" - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Principal: - Service: - - "codebuild.amazonaws.com" - Action: - - "sts:AssumeRole" - Path: "/" - Policies: - - PolicyName: "Custom-Control-Tower-StackSet-CodeBuild-Policy-Logs" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - Resource: - - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/* - - PolicyName: "Custom-Control-Tower-StackSet-CodeBuild-Policy-S3" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Action: - - s3:GetObject - - s3:PutObject - Resource: - - !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineArtifactS3Bucket}/* - - Effect: "Allow" - Action: - - s3:GetObject - Resource: - - !Sub arn:${AWS::Partition}:s3:::*/* # needed to support validation of remotely sourced templates feature. The host S3 bucket can be created by the customers or partners. - - PolicyName: "Custom-Control-Tower-StackSet-CodeBuild-Policy-StepFunctions" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - states:ListExecutions - - states:StartExecution - - states:StopExecution - - states:DescribeStateMachine - Resource: - - !Ref StackSetStateMachine - - Effect: Allow - Action: - - states:DescribeStateMachineForExecution - - states:DescribeExecution - Resource: - - !Sub arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:execution:${StackSetStateMachine.Name}:* - - PolicyName: "Custom-Control-Tower-StackSet-CodeBuild-Policy-Organizations" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - organizations:ListRoots - - organizations:ListOrganizationalUnitsForParent - - organizations:ListAccountsForParent - - organizations:ListAccounts - - organizations:DescribeOrganization - Resource: '*' # The APIs above only support '*' resource. - - PolicyName: "Custom-Control-Tower-StackSet-CodeBuild-Policy-SSM" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - ssm:GetParameter - - ssm:PutParameter - - ssm:GetParametersByPath - Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/* - - Effect: Allow - Action: - - ssm:DescribeParameters - Resource: '*' # The APIs above only support '*' resource. - - PolicyName: "Custom-Control-Tower-StackSet-CodeBuild-Policy-KMS" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - kms:Encrypt - - kms:Decrypt - - kms:ReEncryptFrom - - kms:ReEncryptTo - - kms:GenerateDataKey - - kms:GenerateDataKeyWithoutPlaintext - - kms:DescribeKey - Resource: - - !Sub arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/* - - PolicyName: "Custom-Control-Tower-StackSet-CodeBuild-Policy-STS" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - sts:AssumeRole - Resource: !Sub - - arn:${AWS::Partition}:iam::*:role/${CustomControlTowerExecutionRole} - - {CustomControlTowerExecutionRole: !FindInMap [AWSControlTower, ExecutionRole, Name]} - - PolicyName: "Custom-Control-Tower-StackSet-CodeBuild-Policy-EC2" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - ec2:DescribeAvailabilityZones - Resource: - - '*' # The APIs above only support '*' resource. - - PolicyName: "Custom-Control-Tower-StackSet-CodeBuild-Policy-CloudFormation" - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - cloudformation:DescribeStackSet - - cloudformation:ListStackSets - - cloudformation:ListStackInstances - - cloudformation:ListStackSetOperations - Resource: - - !Sub arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stackset/* - - StackSetCodeBuild: - Type: AWS::CodeBuild::Project - DependsOn: CustomControlTowerDeploymentLambda - Properties: - Name: Custom-Control-Tower-StackSet-CodeBuild - ServiceRole: !GetAtt StackSetCodeBuildRole.Arn - EncryptionKey: !Sub - - alias/${KMSKeyName} - - {KMSKeyName: !FindInMap [KMS, Alias, Name]} - Source: - Type: CODEPIPELINE - BuildSpec: "version: 0.2\nphases:\n install:\n runtime-versions:\n python: 3.8\n ruby: 2.6\n commands:\n - export current=$(pwd)\n - if [ -f manifest.yaml ];then export current=$(pwd);else if [ -f custom-control-tower-configuration/manifest.yaml ]; then export current=$(pwd)/custom-control-tower-configuration; else echo 'manifest.yaml does not exist at the root level of custom-control-tower-configuration.zip or inside custom-control-tower-configuration folder, please check the ZIP file'; exit 1; fi; fi;\n - apt-get -q update 1> /dev/null\n - apt-get -q install zip wget python3-pip libyaml-dev -y 1> /dev/null\n pre_build:\n commands:\n - cd $current\n - echo 'Download CustomControlTower Scripts'\n - aws s3 cp --quiet s3://control-tower-cfct-assets-prod/customizations-for-aws-control-tower/v2.5.3/custom-control-tower-scripts.zip $current\n - unzip -q -o $current/custom-control-tower-scripts.zip -d $current\n - cp codebuild_scripts/* .\n - bash install_stage_dependencies.sh $STAGE_NAME\n build:\n commands:\n - echo 'Starting build $(date) in $(pwd)'\n - echo 'bash execute_stage_scripts.sh $STAGE_NAME $LOG_LEVEL $WAIT_TIME $SM_ARN $ARTIFACT_BUCKET $KMS_KEY_ALIAS_NAME $BOOL_VALUES $NONE_TYPE_VALUES'\n - bash execute_stage_scripts.sh $STAGE_NAME $LOG_LEVEL $WAIT_TIME $SM_ARN $ARTIFACT_BUCKET $KMS_KEY_ALIAS_NAME $BOOL_VALUES $NONE_TYPE_VALUES\n - echo 'Running build scripts completed $(date)'\n post_build:\n commands:\n - echo 'Starting post build $(date) in $(pwd)'\n - echo 'build completed on $(date)'\n\nartifacts:\n files:\n - '**/*'\n" - Environment: - ComputeType: BUILD_GENERAL1_SMALL - Image: "aws/codebuild/standard:5.0" - Type: LINUX_CONTAINER - EnvironmentVariables: - - Name: SM_ARN - Value: !Ref StackSetStateMachine - - Name: LOG_LEVEL - Value: !FindInMap [LambdaFunction, Logging, Level] - - Name: WAIT_TIME - Value: "15" - - Name: STAGE_NAME - Value: "stackset" - - Name: ARTIFACT_BUCKET - Value: !Ref CustomControlTowerPipelineArtifactS3Bucket - - Name: KMS_KEY_ALIAS_NAME - Value: !FindInMap [KMS, Alias, Name] - - Name: ENFORCE_SUCCESSFUL_STACK_INSTANCES - Value: !Ref EnforceSuccessfulStackInstances - - Name: EXECUTION_ROLE_NAME - Value: !FindInMap [AWSControlTower, ExecutionRole, Name] - - Name: SOLUTION_ID - Value: !FindInMap [Solution, Metrics, SolutionID] - - Name: SOLUTION_VERSION - Value: v2.5.3 - - Name: METRICS_URL - Value: !FindInMap [Solution, Metrics, MetricsURL] - - Name: CONTROL_TOWER_BASELINE_CONFIG_STACKSET - Value: !FindInMap [ControlTowerBaselineConfigStackset, Info, Name] - Artifacts: - Name: !Sub ${CustomControlTowerPipelineArtifactS3Bucket}-Built - Type: CODEPIPELINE - TimeoutInMinutes: 480 - - CustomControlTowerDeploymentLambdaRole: - Type: AWS::IAM::Role - Metadata: - cfn_nag: - rules_to_suppress: - - id: W11 - reason: "Allow Resource * for KMS API. KMS Service only support all resources. Key ID is generated by the service." - - id: W28 - reason: "The role name is defined to identify Custom Control Tower resources." - Properties: - RoleName: CustomControlTowerDeploymentLambdaRole - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Principal: - Service: lambda.amazonaws.com - Action: sts:AssumeRole - Path: / - Policies: - - PolicyName: Custom-Control-Tower-DeploymentLambda-Logs - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - Resource: - - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/* - - Effect: Allow - Action: - - xray:PutTraceSegments - - xray:PutTelemetryRecords - Resource: '*' - - PolicyName: Custom-Control-Tower-DeploymentLambda-KMS - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: "Allow" - Action: - - kms:DescribeKey - - kms:TagResource - - kms:PutKeyPolicy - - kms:GetKeyRotationStatus - - kms:EnableKeyRotation - Resource: - - !Sub arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/* - - Effect: "Allow" - Action: - - kms:CreateKey - - kms:ListAliases - Resource: "*" - - Effect: "Allow" - Action: - - kms:CreateAlias - Resource: - - !Sub arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:alias/* - - !Sub arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/* - - PolicyName: Custom-Control-Tower-DeploymentLambda-S3 - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: "Allow" - Action: - - s3:GetEncryptionConfiguration - - s3:PutEncryptionConfiguration - Resource: - - !GetAtt CustomControlTowerPipelineS3Bucket.Arn - - Effect: "Allow" - Action: - - s3:GetObject - Resource: - - !Sub arn:${AWS::Partition}:s3:::control-tower-cfct-assets-prod/* - - Effect: "Allow" - Action: - - s3:GetObject - - s3:PutObject - Resource: - - !Sub arn:${AWS::Partition}:s3:::${CustomControlTowerPipelineS3Bucket}/* - - PolicyName: Custom-Control-Tower-DeploymentLambda-SSM - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - ssm:PutParameter - - ssm:GetParameter - - ssm:DeleteParameter - - ssm:GetParametersByPath - Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/* - - Effect: Allow - Action: - - ssm:DescribeParameters - Resource: '*' # The APIs above only support '*' resource. - - CustomControlTowerDeploymentLambda: - Type: AWS::Lambda::Function - Metadata: - cfn_nag: - rules_to_suppress: - - id: W58 - reason: "Permission for writing cloudwatch logs is defined in the lambda role" - - id: W89 - reason: "This lambda function does not need access to VPC resources" - - id: W92 - reason: "This use case does not need to set the ReservedConcurrentExecutions" - checkov: - skip: - - id: CKV_AWS_115 - comment: Lambda does not need reserved concurrent executions. - - id: CKV_AWS_116 - comment: DLQ not needed, as Lambda function only triggered by CloudFormation events. - - id: CKV_AWS_117 - comment: Lambda does not need to communicate with VPC resources. - - id: CKV_AWS_173 - comment: Environment variables are not sensitive - Properties: - Environment: - Variables: - LOG_LEVEL: !FindInMap [LambdaFunction, Logging, Level] - SOLUTION_ID: !FindInMap [Solution, Metrics, SolutionID] - SOLUTION_VERSION: v2.5.3 - Code: - S3Bucket: !Sub "control-tower-cfct-assets-prod-${AWS::Region}" - S3Key: customizations-for-aws-control-tower/v2.5.3/custom-control-tower-config-deployer.zip - FunctionName: CustomControlTowerDeploymentLambda - Description: Custom Control Tower Deployment Lambda - Handler: config_deployer.lambda_handler - MemorySize: 512 - Role: !GetAtt 'CustomControlTowerDeploymentLambdaRole.Arn' - Runtime: python3.8 - Timeout: 300 - TracingConfig: - Mode: Active - - CustomControlTowerConfigDeployer: - Type: Custom::ConfigDeployer - Properties: - MetricsFlag: !FindInMap [Solution, Metrics, SendAnonymousData] - BucketConfig: - DestinationBucketName: !Ref CustomControlTowerPipelineS3Bucket - DestinationS3Key: !If [IsBuildCustomControlTowerCondition, !FindInMap [BucketConfiguration, CustomControlTowerPipelineS3TriggerKey, Name], !FindInMap [BucketConfiguration, CustomControlTowerPipelineS3NonTriggerKey, Name]] - SourceBucketName: !FindInMap [BucketConfiguration, SourceBucketName, Name] - SourceS3Key: !FindInMap [BucketConfiguration, SourceKeyName, Name] - KMSConfig: - KMSKeyAlias: !Sub - - alias/${KMSKeyName} - - {KMSKeyName: !FindInMap [KMS, Alias, Name]} - KMSKeyPolicy: - Version: "2012-10-17" - Id: "key-CustomControlTower-1" - Statement: - - - Sid: "Allow administration of the key" - Effect: "Allow" - Principal: - AWS: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root - 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" - Resource: "*" - - - Sid: "Allow use of the key" - Effect: "Allow" - Principal: - AWS: - - Fn::Sub: ${CustomControlTowerStateMachineLambdaRole.Arn} - - Fn::Sub: ${CustomControlTowerDeploymentLambdaRole.Arn} - - Fn::Sub: ${CustomControlTowerCodePipelineRole.Arn} - - Fn::Sub: ${CustomControlTowerCodeBuildRole.Arn} - - Fn::Sub: ${SCPCodeBuildRole.Arn} - - Fn::Sub: ${StackSetCodeBuildRole.Arn} - - Fn::Sub: ${CustomControlTowerLELambdaRole.Arn} - Service: - - "events.amazonaws.com" - Action: - - "kms:Encrypt" - - "kms:Decrypt" - - "kms:ReEncrypt*" - - "kms:GenerateDataKey*" - - "kms:DescribeKey" - Resource: "*" - FindReplace: - - FileName: manifest.yaml.j2 - Parameters: - region: !Sub ${AWS::Region} - ServiceToken: !GetAtt CustomControlTowerDeploymentLambda.Arn - - CustomControlTowerStateMachineLambdaRole: - Type: AWS::IAM::Role - Metadata: - cfn_nag: - rules_to_suppress: - - id: F38 - reason: "PassRole action is required to make changes to all (*) the Service Catalog Resources" - - id: W28 - reason: "The role name is defined to identify Custom Control Tower resources." - - id: W11 - reason: "Allow Resource * for KMS/SSM/Org/SC/CFN API. Key ID is generated by the service. Other resources are customer defined." - checkov: - skip: - - id: CKV_AWS_108 - comment: "Allow Resource * for KMS/SSM/Org/SC/CFN API. Key ID is generated by the service. Other resources are customer defined." - - id: CKV_AWS_109 - comment: "Allow Resource * for KMS/SSM/Org/SC/CFN API. Key ID is generated by the service. Other resources are customer defined." - - id: CKV_AWS_111 - comment: "Allow Resource * for KMS/SSM/Org/SC/CFN API. Key ID is generated by the service. Other resources are customer defined." - Properties: - RoleName: CustomControlTowerStateMachineLambdaRole - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Principal: - Service: lambda.amazonaws.com - Action: sts:AssumeRole - Path: / - Policies: - - PolicyName: State-Machine-Lambda-Policy-Logs - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - Resource: - - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/* - - Effect: Allow - Action: - - xray:PutTraceSegments - - xray:PutTelemetryRecords - Resource: '*' - - PolicyName: State-Machine-Lambda-Policy-IAM - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - iam:GetRole - Resource: '*' - - Effect: Allow - Action: - - iam:PassRole - Resource: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:role/service-role/AWSControlTowerStackSetRole - - PolicyName: State-Machine-Lambda-Policy-Organizations - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - organizations:CreateOrganization - - organizations:CreateOrganizationalUnit - - organizations:ListPolicies - - organizations:ListPoliciesForTarget - - organizations:ListTargetsForPolicy - - organizations:ListParents - - organizations:ListRoots - - organizations:ListAccounts - - organizations:ListOrganizationalUnitsForParent - - organizations:ListAccountsForParent - - organizations:EnablePolicyType - - organizations:CreatePolicy - - organizations:UpdatePolicy - - organizations:DeletePolicy - - organizations:DetachPolicy - - organizations:AttachPolicy - - organizations:CreateAccount - - organizations:DescribeAccount - - organizations:DescribeCreateAccountStatus - - organizations:DescribeOrganization - - organizations:UpdateOrganizationalUnit - Resource: '*' # The APIs above only support '*' resource. - - PolicyName: State-Machine-Lambda-Policy-CloudFormation - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - cloudformation:CreateStackSet - - cloudformation:CreateStack - - cloudformation:DeleteStack - - cloudformation:DeleteStackSet - - cloudformation:CreateStackInstances - - cloudformation:DeleteStackInstances - - cloudformation:DescribeStackInstance - - cloudformation:DescribeStackSetOperation - - cloudformation:DescribeStackSet - - cloudformation:UpdateStackSet - - cloudformation:UpdateStackInstances - - cloudformation:TagResource - - cloudformation:ListStackInstances - - cloudformation:GetTemplateSummary - - cloudformation:DescribeStacks - Resource: - - !Sub arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/* - - !Sub arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stackset/* - - Effect: Allow - Action: - - cloudformation:ValidateTemplate - Resource: '*' - - PolicyName: State-Machine-Lambda-Policy-SSM - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - ssm:PutParameter - - ssm:GetParameter - - ssm:GetParameters - - ssm:DeleteParameter - - ssm:GetParametersByPath - Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/* - - Effect: Allow - Action: - - ssm:DescribeParameters - Resource: '*' - - PolicyName: State-Machine-Lambda-Policy-KMS - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - kms:Encrypt - - kms:Decrypt - - kms:ReEncryptFrom - - kms:ReEncryptTo - - kms:GenerateDataKey - - kms:GenerateDataKeyWithoutPlaintext - - kms:DescribeKey - Resource: - - !Sub arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/* - - PolicyName: State-Machine-Lambda-Policy-S3 - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - s3:PutObject - - s3:GetObject - - s3:ListBucketByTags - - s3:ListBucketMultipartUploads - - s3:ListAllMyBuckets - - s3:PutBucketLogging - - s3:ListBucketVersions - - s3:PutBucketPolicy - - s3:CreateBucket - - s3:ListBucket - - s3:GetBucketPolicy - Resource: '*' # supports remotely sourced templates feature. The host S3 bucket can be created by the customer. - - PolicyName: State-Machine-Lambda-Policy-EC2 - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - ec2:DescribeRegions - Resource: '*' - - PolicyName: State-Machine-Lambda-Policy-STS - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - sts:AssumeRole - Resource: !Sub - - arn:${AWS::Partition}:iam::*:role/${CustomControlTowerExecutionRole} - - {CustomControlTowerExecutionRole: !FindInMap [AWSControlTower, ExecutionRole, Name]} - - StateMachineLambda: - Type: AWS::Lambda::Function - Metadata: - cfn_nag: - rules_to_suppress: - - id: W58 - reason: "Permission for writing cloudwatch logs is defined in the lambda role" - - id: W89 - reason: "This lambda function does not need access to VPC resources" - - id: W92 - reason: "This use case does not need to set the ReservedConcurrentExecutions" - checkov: - skip: - - id: CKV_AWS_115 - comment: Lambda does not need reserved concurrent executions. - - id: CKV_AWS_116 - comment: DLQ not needed, as Lambda function only triggered by CloudFormation events. - - id: CKV_AWS_117 - comment: Lambda does not need to communicate with VPC resources. - - id: CKV_AWS_173 - comment: Environment variables are not sensitive - Properties: - Environment: - Variables: - LOG_LEVEL: !FindInMap [LambdaFunction, Logging, Level] - KMS_KEY_ALIAS_NAME: !FindInMap [KMS, Alias, Name] - ADMINISTRATION_ROLE_ARN: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:role/service-role/AWSControlTowerStackSetRole - EXECUTION_ROLE_NAME: !FindInMap [AWSControlTower, ExecutionRole, Name] - SOLUTION_ID: !FindInMap [Solution, Metrics, SolutionID] - SOLUTION_VERSION: v2.5.3 - METRICS_URL: !FindInMap [Solution, Metrics, MetricsURL] - MAX_CONCURRENT_PERCENT: !Ref MaxConcurrentPercentage - FAILED_TOLERANCE_PERCENT: !Ref FailureTolerancePercentage - REGION_CONCURRENCY_TYPE: !Ref RegionConcurrencyType - Code: - S3Bucket: !Sub "control-tower-cfct-assets-prod-${AWS::Region}" - S3Key: customizations-for-aws-control-tower/v2.5.3/custom-control-tower-state-machine.zip - FunctionName: CustomControlTowerStateMachineLambda - Description: Custom Control Tower State Machine Handler - Handler: state_machine_router.lambda_handler - MemorySize: 1024 - Role: !GetAtt 'CustomControlTowerStateMachineLambdaRole.Arn' - Runtime: python3.8 - Timeout: 300 - TracingConfig: - Mode: Active - - StateMachineRole: - Type: "AWS::IAM::Role" - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - - Effect: "Allow" - Principal: - Service: - - !Sub "states.${AWS::Region}.amazonaws.com" - Action: - - "sts:AssumeRole" - Path: "/" - Policies: - - PolicyName: State-Machine-Invoke-Lambda - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Action: - - "lambda:InvokeFunction" - Resource: !GetAtt StateMachineLambda.Arn - - ServiceControlPolicyMachine: - Type: 'AWS::StepFunctions::StateMachine' - Properties: - StateMachineName: CustomControlTowerServiceControlPolicyMachine - RoleArn: !GetAtt 'StateMachineRole.Arn' - DefinitionString: - Fn::Sub: |- - { - "Comment": "A state machine that manages the Service Control Policies.", - "StartAt": "Metrics Pass", - "States": { - "Metrics Pass": { - "Type": "Pass", - "Result": { - "ClassName": "StackSetSMRequests", - "FunctionName": "send_execution_data" - }, - "ResultPath": "$.params", - "Next": "Metrics" - }, - "Metrics": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Create/Delete or Attach/Detach Policy?" - }, - "Create/Delete or Attach/Detach Policy?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.ResourceProperties.AccountId", - "StringEquals": "", - "Next": "Enable Policy Type params" - }, - { - "Variable": "$.ResourceProperties.AccountId", - "StringGreaterThan": "", - "Next": "Attach/Detach Policy params" - } - ] - }, - "Enable Policy Type params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "enable_policy_type" - }, - "ResultPath": "$.params", - "Next": "Enable Policy Type" - }, - "Enable Policy Type": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Wait" - }, - "Wait": { - "Type": "Wait", - "Seconds": 10, - "Next": "Create/Delete Policy params" - }, - "Create/Delete Policy params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "list_policies" - }, - "ResultPath": "$.params", - "Next": "Check If Policy Exist?" - }, - "Check If Policy Exist?": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Create or Delete Policy?" - }, - "Create or Delete Policy?": { - "Type": "Choice", - "Choices": [ - { - "And": [ - { - "Or": [ - { - "Variable": "$.RequestType", - "StringEquals": "Create" - }, - { - "Variable": "$.RequestType", - "StringEquals": "Update" - } - ] - }, - { - "Variable": "$.PolicyExist", - "StringEquals": "no" - } - ], - "Next": "Create Policy Params" - }, - { - "And": [ - { - "Or": [ - { - "Variable": "$.RequestType", - "StringEquals": "Create" - }, - { - "Variable": "$.RequestType", - "StringEquals": "Update" - } - ] - }, - { - "Variable": "$.PolicyExist", - "StringEquals": "yes" - } - ], - "Next": "Update Policy Params" - }, - { - "And": [ - { - "Variable": "$.RequestType", - "StringEquals": "Delete" - }, - { - "Variable": "$.PolicyExist", - "StringEquals": "yes" - } - ], - "Next": "Detach Policy from All Accounts Params" - }, - { - "And": [ - { - "Variable": "$.RequestType", - "StringEquals": "Delete" - }, - { - "Variable": "$.PolicyExist", - "StringEquals": "no" - } - ], - "Next": "Finish" - } - ] - }, - "Create Policy Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "create_policy" - }, - "ResultPath": "$.params", - "Next": "Create Policy" - }, - "Create Policy": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "ConfigureCount2 params" - }, - "Update Policy Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "update_policy" - }, - "ResultPath": "$.params", - "Next": "Update Policy" - }, - "Update Policy": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "ConfigureCount2 params" - }, - "ConfigureCount2 params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "configure_count_2" - }, - "ResultPath": "$.params", - "Next": "ConfigureCount2" - }, - "ConfigureCount2": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Iterator2 params" - }, - "Iterator2 params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "iterator2" - }, - "ResultPath": "$.params", - "Next": "Iterator2" - }, - "Iterator2": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "Next": "IsCountReached2" - }, - "IsCountReached2": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.Continue", - "BooleanEquals": true, - "Next": "List Policies For OU Params" - } - ], - "Default": "Finish" - }, - "List Policies For OU Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "list_policies_for_ou" - }, - "ResultPath": "$.params", - "Next": "List Policies For OU" - }, - "List Policies For OU": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Attach or Detach Policy to OU Choice" - }, - "Attach or Detach Policy to OU Choice": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.Operation", - "StringEquals": "Attach", - "Next": "Check if Policy is attached to OU?" - }, - { - "Variable": "$.Operation", - "StringEquals": "Detach", - "Next": "Check if Policy is detached from OU?" - } - ], - "Default": "Invalid Operation2" - }, - "Invalid Operation2": { - "Type": "Fail", - "Cause": "Invalid Operation Type, valid choices are [Attach, Detach]", - "Error": "Returning NULL in the response." - }, - "Check if Policy is attached to OU?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.PolicyAttached", - "StringEquals": "yes", - "Next": "Iterator2 params" - }, - { - "Variable": "$.PolicyAttached", - "StringEquals": "no", - "Next": "Attach Policy to OU Params" - } - ], - "Default": "Invalid Operation2" - }, - "Attach Policy to OU Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "attach_policy" - }, - "ResultPath": "$.params", - "Next": "Attach Policy to OU" - }, - "Attach Policy to OU": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Iterator2 params" - }, - "Check if Policy is detached from OU?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.PolicyAttached", - "StringEquals": "yes", - "Next": "Detach Policy from OU Params" - }, - { - "Variable": "$.PolicyAttached", - "StringEquals": "no", - "Next": "Iterator2 params" - } - ], - "Default": "Invalid Operation2" - }, - "Detach Policy from OU Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "detach_policy" - }, - "ResultPath": "$.params", - "Next": "Detach Policy from OU" - }, - "Detach Policy from OU": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Iterator2 params" - }, - "Detach Policy from All Accounts Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "detach_policy_from_all_accounts" - }, - "ResultPath": "$.params", - "Next": "Detach Policy from All Accounts" - }, - "Detach Policy from All Accounts": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Delete Policy Params" - }, - "Delete Policy Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "delete_policy" - }, - "ResultPath": "$.params", - "Next": "Delete Policy" - }, - "Delete Policy": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Finish" - }, - "Attach/Detach Policy params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "configure_count" - }, - "ResultPath": "$.params", - "Next": "ConfigureCount" - }, - "ConfigureCount": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Iterator params" - }, - "Iterator params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "iterator" - }, - "ResultPath": "$.params", - "Next": "Iterator" - }, - "Iterator": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "Next": "IsCountReached" - }, - "IsCountReached": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.Continue", - "BooleanEquals": true, - "Next": "List Policy Params" - } - ], - "Default": "Finish" - }, - "List Policy Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "list_policies" - }, - "ResultPath": "$.params", - "Next": "List Policy" - }, - "List Policy": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "List Policies For Account Params" - }, - "List Policies For Account Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "list_policies_for_account" - }, - "ResultPath": "$.params", - "Next": "List Policies For Account" - }, - "List Policies For Account": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Attach or Detach Policy Choice" - }, - "Attach or Detach Policy Choice": { - "Type": "Choice", - "Choices": [ - { - "And": [ - { - "Or": [ - { - "Variable": "$.RequestType", - "StringEquals": "Create" - }, - { - "Variable": "$.RequestType", - "StringEquals": "Update" - } - ] - }, - { - "Variable": "$.ResourceProperties.Operation", - "StringEquals": "Attach" - } - ], - "Next": "Check if Policy is attached?" - }, - { - "And": [ - { - "Variable": "$.RequestType", - "StringEquals": "Delete" - }, - { - "Variable": "$.ResourceProperties.Operation", - "StringEquals": "Attach" - } - ], - "Next": "Check if Policy is detached?" - }, - { - "Variable": "$.ResourceProperties.Operation", - "StringEquals": "Detach", - "Next": "Check if Policy is detached?" - } - ], - "Default": "Invalid Operation" - }, - "Invalid Operation": { - "Type": "Fail", - "Cause": "Invalid Operation Type, valid choices are [Attach, Detach]", - "Error": "Returning NULL in the response." - }, - "Check if Policy is attached?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.PolicyAttached", - "StringEquals": "yes", - "Next": "Iterator params" - }, - { - "Variable": "$.PolicyAttached", - "StringEquals": "no", - "Next": "Attach Policy Params" - } - ], - "Default": "Invalid Operation" - }, - "Attach Policy Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "attach_policy" - }, - "ResultPath": "$.params", - "Next": "Attach Policy" - }, - "Attach Policy": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Iterator params" - }, - "Check if Policy is detached?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.PolicyAttached", - "StringEquals": "yes", - "Next": "Detach Policy Params" - }, - { - "Variable": "$.PolicyAttached", - "StringEquals": "no", - "Next": "Iterator params" - } - ], - "Default": "Invalid Operation" - }, - "Detach Policy Params": { - "Type": "Pass", - "Result": { - "ClassName": "SCP", - "FunctionName": "detach_policy" - }, - "ResultPath": "$.params", - "Next": "Detach Policy" - }, - "Detach Policy": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Iterator params" - }, - "Finish": { - "Type": "Succeed" - } - } - } - - StackSetStateMachine: - Type: 'AWS::StepFunctions::StateMachine' - Properties: - StateMachineName: CustomControlTowerStackSetStateMachine - RoleArn: !GetAtt 'StateMachineRole.Arn' - DefinitionString: - Fn::Sub: |- - { - "Comment": "A state machine that manages the CloudFormation stacks in multiple accounts using StackSet APIs.", - "StartAt": "Metrics Pass", - "States": { - "Metrics Pass": { - "Type": "Pass", - "Result": { - "ClassName": "StackSetSMRequests", - "FunctionName": "send_execution_data" - }, - "ResultPath": "$.params", - "Next": "Metrics" - }, - "Metrics": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Describe StackSet Pass" - }, - "Describe StackSet Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "describe_stack_set" - }, - "ResultPath": "$.params", - "Next": "Check StackSet Existence" - }, - "Check StackSet Existence": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "StackSets: Create or Delete?" - }, - "StackSets: Create or Delete?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.RequestType", - "StringEquals": "Create", - "Next": "Skip StackSets?" - }, - { - "Variable": "$.RequestType", - "StringEquals": "Update", - "Next": "Skip StackSets?" - }, - { - "Variable": "$.RequestType", - "StringEquals": "Delete", - "Next": "Describe StackSet" - } - ], - "Default": "Undefined Request Type" - }, - "Undefined Request Type": { - "Type": "Pass", - "Next": "Failed" - }, - "Skip StackSets?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.ResourceProperties.TemplateURL", - "StringEquals": "", - "Next": "Check Instance Pass" - } - ], - "Default": "Does StackSet Exist?" - }, - "Does StackSet Exist?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.StackSetExist", - "StringEquals": "no", - "Next": "Deploy StackSet Pass" - }, - { - "Variable": "$.StackSetExist", - "StringEquals": "yes", - "Next": "List StackInstances Accounts Pass" - } - ], - "Default": "Unable to describe StackSet" - }, - "Unable to describe StackSet": { - "Type": "Pass", - "Next": "Failed" - }, - "Deploy StackSet Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "create_stack_set" - }, - "ResultPath": "$.params", - "Next": "Deploy StackSet" - }, - "Deploy StackSet": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "StackSet Deployed?" - }, - "StackSet Deployed?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.StackSetStatus", - "StringEquals": "success", - "Next": "Deploy Stack Instance?" - }, - { - "Variable": "$.StackSetStatus", - "StringEquals": "failure", - "Next": "StackSet Deployment Failed" - } - ], - "Default": "StackSet Deployment Failed" - }, - "StackSet Deployment Failed": { - "Type": "Pass", - "Next": "Failed" - }, - "Deploy Stack Instance?": { - "Type": "Choice", - "Choices": [ - { - "And": [ - { - "Variable": "$.ResourceProperties.AccountList", - "StringLessThan": "1" - }, - { - "Variable": "$.ResourceProperties.RegionList", - "StringLessThan": "1" - } - ], - "Next": "StackSet Deployed" - } - ], - "Default": "Create or Delete Stack Instance?" - }, - "Create or Delete Stack Instance?": { - "Type": "Choice", - "Choices": [ - { - "And": [ - { - "Variable": "$.CreateInstance", - "StringEquals": "no" - }, - { - "Variable": "$.DeleteInstance", - "StringEquals": "yes" - } - ], - "Next": "Delete Stack Instances Pass" - }, - { - "Variable": "$.CreateInstance", - "StringEquals": "yes", - "Next": "Deploy Stack Instance Pass" - }, - { - "And": [ - { - "Variable": "$.CreateInstance", - "StringEquals": "no" - }, - { - "Variable": "$.DeleteInstance", - "StringEquals": "no" - } - ], - "Next": "Export Stack Output Pass" - } - ], - "Default": "Deploy Stack Instance Pass" - }, - "StackSet Deployed": { - "Type": "Pass", - "Next": "Export Stack Output Pass" - }, - "Deploy Stack Instance Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "create_stack_instances" - }, - "ResultPath": "$.params", - "Next": "Deploy Stack Instance" - }, - "Deploy Stack Instance": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Create Operation ID?" - }, - "Create Operation ID?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.OperationId", - "StringEquals": "OperationInProgressException", - "Next": "Waiting on create... OperationInProgress" - } - ], - "Default": "Create Task Running" - }, - "Waiting on create... OperationInProgress": { - "Type": "Wait", - "Seconds": 30, - "Next": "Deploy Stack Instance" - }, - "Create Task Running": { - "Type": "Wait", - "Seconds": 10, - "Next": "Create Task Pass" - }, - "Create Task Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "describe_stack_set_operation" - }, - "ResultPath": "$.params", - "Next": "Create Task Status?" - }, - "Create Task Status?": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Create Task Completed?" - }, - "Create Task Completed?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.OperationStatus", - "StringEquals": "SUCCEEDED", - "Next": "Create Task Completed" - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "RUNNING", - "Next": "Create Task Running" - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "FAILED", - "Next": "Create Task Failed" - } - ], - "Default": "Create Task Failed" - }, - "Create Task Completed": { - "Type": "Pass", - "Next": "Export Stack Output Pass" - }, - "Create Task Failed": { - "Type": "Pass", - "Next": "Failed" - }, - "List StackInstances Accounts Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "list_stack_instances_account_ids" - }, - "ResultPath": "$.params", - "Next": "List StackInstances Accounts" - }, - "List StackInstances Accounts": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Check List StackInstances Accounts Complete?" - }, - "Check List StackInstances Accounts Complete?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.NextToken", - "StringEquals": "Complete", - "Next": "Skip Update StackSet?" - } - ], - "Default": "Check List StackInstances Accounts Wait" - }, - "Check List StackInstances Accounts Wait": { - "Type": "Wait", - "Seconds": 5, - "Next": "List StackInstances Accounts" - }, - "Skip Update StackSet?": { - "Type": "Choice", - "Choices": [ - { - "Or": [ - { - "Variable": "$.LoopFlag", - "StringEquals": "yes" - }, - { - "Variable": "$.SkipUpdateStackSet", - "StringEquals": "yes" - } - ], - "Next": "Check Instance Pass" - } - ], - "Default": "Update StackSet Pass" - }, - "Update StackSet Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "update_stack_set" - }, - "ResultPath": "$.params", - "Next": "Update StackSet" - }, - "Update StackSet": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Update Operation ID?" - }, - "Update Operation ID?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.OperationId", - "StringEquals": "OperationInProgressException", - "Next": "Waiting on update... OperationInProgress" - } - ], - "Default": "Update Task Running" - }, - "Waiting on update... OperationInProgress": { - "Type": "Wait", - "Seconds": 30, - "Next": "Update StackSet" - }, - "Update Task Running": { - "Type": "Wait", - "Seconds": 10, - "Next": "Update Task Pass" - }, - "Update Task Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "describe_stack_set_operation" - }, - "ResultPath": "$.params", - "Next": "Update Task Status?" - }, - "Update Task Status?": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Update Task Completed?" - }, - "Update Task Completed?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.OperationStatus", - "StringEquals": "SUCCEEDED", - "Next": "Check Instance Pass" - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "RUNNING", - "Next": "Update Task Running" - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "STOPPED", - "Next": "Update Task Completed" - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "STOPPING", - "Next": "Update Task Running" - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "FAILED", - "Next": "Update Task Failed" - } - ], - "Default": "Update Task Failed" - }, - "Update Task Completed": { - "Type": "Pass", - "Next": "Export Stack Output Pass" - }, - "Update Task Failed": { - "Type": "Pass", - "Next": "Failed" - }, - "Check Instance Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "list_stack_instances" - }, - "ResultPath": "$.params", - "Next": "Check Instance" - }, - "Check Instance": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "Next": "Check Complete?" - }, - "Check Complete?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.NextToken", - "StringEquals": "Complete", - "Next": "Create or Update Instance?" - } - ], - "Default": "Check Instance Wait" - }, - "Check Instance Wait": { - "Type": "Wait", - "Seconds": 5, - "Next": "Check Instance" - }, - "Create or Update Instance?": { - "Type": "Choice", - "Choices": [ - { - "Or": [ - { - "Variable": "$.CreateInstance", - "StringEquals": "yes" - }, - { - "Variable": "$.DeleteInstance", - "StringEquals": "yes" - } - ], - "Next": "Deploy Stack Instance?" - }, - { - "And": [ - { - "Variable": "$.CreateInstance", - "StringEquals": "no" - }, - { - "Variable": "$.RequestType", - "StringEquals": "Create" - } - ], - "Next": "Export Stack Output Pass" - }, - { - "And": [ - { - "Variable": "$.CreateInstance", - "StringEquals": "no" - }, - { - "Variable": "$.RequestType", - "StringEquals": "Update" - } - ], - "Next": "Update Stack Instance?" - } - ], - "Default": "Export Stack Output Pass" - }, - "Update Stack Instance?": { - "Type": "Choice", - "Choices": [ - { - "And": [ - { - "Variable": "$.ResourceProperties.AccountList", - "StringLessThan": "1" - }, - { - "Variable": "$.ResourceProperties.RegionList", - "StringLessThan": "1" - } - ], - "Next": "StackSet Updated" - }, - { - "Variable": "$.OverrideParametersExist", - "StringEquals": "no", - "Next": "Override parameters do not exist in the event" - } - ], - "Default": "Update Stack Instance Pass" - }, - "StackSet Updated": { - "Type": "Pass", - "Next": "Export Stack Output Pass" - }, - "Override parameters do not exist in the event": { - "Type": "Pass", - "Next": "Export Stack Output Pass" - }, - "Update Stack Instance Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "update_stack_instances" - }, - "ResultPath": "$.params", - "Next": "Update Stack Instance" - }, - "Update Stack Instance": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Update Instance Operation ID?" - }, - "Update Instance Operation ID?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.OperationId", - "StringEquals": "OperationInProgressException", - "Next": "Waiting on Update... OperationInProgress" - } - ], - "Default": "Update Instance Task Running" - }, - "Waiting on Update... OperationInProgress": { - "Type": "Wait", - "Seconds": 30, - "Next": "Update Stack Instance" - }, - "Update Instance Task Running": { - "Type": "Wait", - "Seconds": 10, - "Next": "Update Instance Task Pass" - }, - "Update Instance Task Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "describe_stack_set_operation" - }, - "ResultPath": "$.params", - "Next": "Update Instance Task Status?" - }, - "Update Instance Task Status?": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Update Instance Task Completed?" - }, - "Update Instance Task Completed?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.OperationStatus", - "StringEquals": "SUCCEEDED", - "Next": "Update Instance Task Completed" - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "RUNNING", - "Next": "Update Instance Task Running" - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "FAILED", - "Next": "Update Instance Task Failed" - } - ], - "Default": "Update Task Failed" - }, - "Update Instance Task Completed": { - "Type": "Pass", - "Next": "Export Stack Output Pass" - }, - "Update Instance Task Failed": { - "Type": "Pass", - "Next": "Failed" - }, - "Describe StackSet": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "describe_stack_set" - }, - "ResultPath": "$.params", - "Next": "Describe StackSet Function" - }, - "Describe StackSet Function": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Check StackSet Existence?" - }, - "Check StackSet Existence?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.StackSetExist", - "StringEquals": "no", - "Next": "StackSet Not Found" - }, - { - "Variable": "$.StackSetExist", - "StringEquals": "yes", - "Next": "List Stack Instances Pass" - } - ], - "Default": "Unable to find StackSet" - }, - "Unable to find StackSet": { - "Type": "Pass", - "Next": "Failed" - }, - "StackSet Not Found": { - "Type": "Pass", - "Next": "Success" - }, - "List Stack Instances Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "list_stack_instances" - }, - "ResultPath": "$.params", - "Next": "List Stack Instances" - }, - "List Stack Instances": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Does Stack Instance Exist?" - }, - "Does Stack Instance Exist?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.InstanceExist", - "StringEquals": "yes", - "Next": "Delete Stack Instances Pass" - }, - { - "Variable": "$.InstanceExist", - "StringEquals": "no", - "Next": "Event from CloudFormation?" - } - ], - "Default": "Unable to list stack instances" - }, - "Event from CloudFormation?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.ResourceProperties.TemplateURL", - "StringEquals": "", - "Next": "Success" - } - ], - "Default": "Delete StackSet Pass" - }, - "Unable to list stack instances": { - "Type": "Pass", - "Next": "Failed" - }, - "Delete Stack Instances Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "delete_stack_instances" - }, - "ResultPath": "$.params", - "Next": "Delete Stack Instance Function" - }, - "Delete Stack Instance Function": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Delete Operation ID?" - }, - "Delete Operation ID?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.OperationId", - "StringEquals": "OperationInProgressException", - "Next": "Waiting on delete... OperationInProgress" - } - ], - "Default": "Delete Task Running" - }, - "Waiting on delete... OperationInProgress": { - "Type": "Wait", - "Seconds": 30, - "Next": "Delete Stack Instance Function" - }, - "Delete Task Running": { - "Type": "Wait", - "Seconds": 10, - "Next": "Delete Task Pass" - }, - "Delete Task Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "describe_stack_set_operation" - }, - "ResultPath": "$.params", - "Next": "Delete Task Status?" - }, - "Delete Task Status?": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Delete Task Completed?" - }, - "Delete Task Completed?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.OperationStatus", - "StringEquals": "SUCCEEDED", - "Next": "List Stack Remaining Instances Pass" - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "RUNNING", - "Next": "Delete Task Running" - }, - { - "And": [ - { - "Variable": "$.RetryDeleteFlag", - "BooleanEquals": false - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "FAILED" - } - ], - "Next": "Delete Task Failed" - }, - { - "And": [ - { - "Variable": "$.RetryDeleteFlag", - "BooleanEquals": true - }, - { - "Variable": "$.OperationStatus", - "StringEquals": "FAILED" - } - ], - "Next": "Delete Stack Instances Pass" - } - ], - "Default": "Delete Task Failed" - }, - "List Stack Remaining Instances Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "list_stack_instances" - }, - "ResultPath": "$.params", - "Next": "List Stack Instances Again" - }, - "List Stack Instances Again": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Check Remaining Stack Instance?" - }, - "Check Remaining Stack Instance?": { - "Type": "Choice", - "Choices": [ - { - "Or": [ - { - "Variable": "$.InstanceExist", - "StringEquals": "yes" - }, - { - "Variable": "$.ResourceProperties.TemplateURL", - "StringEquals": "" - } - ], - "Next": "Stack Instance Deleted" - }, - { - "Variable": "$.InstanceExist", - "StringEquals": "no", - "Next": "Delete StackSet Pass" - } - ], - "Default": "Failed" - }, - "Stack Instance Deleted": { - "Type": "Pass", - "Next": "Both Account and Region Lists Changes?" - }, - "Delete Task Failed": { - "Type": "Pass", - "Next": "Failed" - }, - "Delete StackSet Pass": { - "Type": "Pass", - "Result": { - "ClassName": "CloudFormation", - "FunctionName": "delete_stack_set" - }, - "ResultPath": "$.params", - "Next": "Delete StackSet Function" - }, - "Delete StackSet Function": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "InputPath": "$", - "Next": "Deleted StackSet" - }, - "Deleted StackSet": { - "Type": "Pass", - "Next": "Success" - }, - "Export Stack Output Pass": { - "Type": "Pass", - "Result": { - "ClassName": "StackSetSMRequests", - "FunctionName": "export_cfn_output" - }, - "ResultPath": "$.params", - "Next": "Export Stack Output" - }, - "Export Stack Output": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "SSM Parameter Store Pass" - }, - "SSM Parameter Store Pass": { - "Type": "Pass", - "Result": { - "ClassName": "StackSetSMRequests", - "FunctionName": "ssm_put_parameters" - }, - "ResultPath": "$.params", - "Next": "Put Parameters" - }, - "Put Parameters": { - "Type": "Task", - "Resource": "${StateMachineLambda.Arn}", - "TimeoutSeconds": 300, - "HeartbeatSeconds": 60, - "Next": "Delete Stack Instance or Finish?" - }, - "Delete Stack Instance or Finish?": { - "Type": "Choice", - "Choices": [ - { - "And": [ - { - "Variable": "$.CreateInstance", - "StringEquals": "yes" - }, - { - "Variable": "$.DeleteInstance", - "StringEquals": "yes" - } - ], - "Next": "Delete Stack Instances Pass" - } - ], - "Default": "Both Account and Region Lists Changes?" - }, - - "Both Account and Region Lists Changes?": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.LoopFlag", - "StringEquals": "yes", - "Next": "List StackInstances Accounts Pass" - } - ], - "Default": "Success" - }, - "Success": { - "Type": "Succeed" - }, - "Failed": { - "Type": "Fail" - } - } - } - - -# -# Lifecycle Event (LE) Resources -# - CustomControlTowerLELambdaRole: - Type: AWS::IAM::Role - Metadata: - cfn_nag: - rules_to_suppress: - - id: W11 - reason: "Allow Resource * for XRay APIs" - - id: W28 - reason: "The role name is defined to identify Custom Control Tower resources." - Properties: - RoleName: CustomControlTowerLELambdaRole - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Principal: - Service: lambda.amazonaws.com - Action: sts:AssumeRole - Path: / - Policies: - - PolicyName: Custom-Control-Tower-LELambdaPolicy-Logs - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - logs:CreateLogGroup - - logs:CreateLogStream - - logs:PutLogEvents - Resource: - - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/* - - Effect: Allow - Action: - - xray:PutTraceSegments - - xray:PutTelemetryRecords - Resource: '*' - - PolicyName: Custom-Control-Tower-LELambdaPolicy-SQS - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - sqs:ReceiveMessage - - sqs:DeleteMessage - - sqs:ListQueues - - sqs:GetQueueAttributes - Resource: !GetAtt CustomControlTowerLEFIFOQueue.Arn - - PolicyName: Custom-Control-Tower-LELambdaPolicy-CodePipeline - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - codepipeline:StartPipelineExecution - Resource: !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CustomControlTowerCodePipeline} - - # Lambda function to process messages (lifecycle events) from SQS - CustomControlTowerLELambda: - Type: AWS::Lambda::Function - Metadata: - cfn_nag: - rules_to_suppress: - - id: W58 - reason: "Permission for writing cloudwatch logs is defined in the lambda role" - - id: W89 - reason: "This lambda function does not need access to VPC resources" - - id: W92 - reason: "This use case does not need to set the ReservedConcurrentExecutions" - checkov: - skip: - - id: CKV_AWS_115 - comment: Lambda does not need reserved concurrent executions. - - id: CKV_AWS_116 - comment: DLQ not needed, as Lambda function only triggered by CloudFormation events. - - id: CKV_AWS_117 - comment: Lambda does not need to communicate with VPC resources. - - id: CKV_AWS_173 - comment: Environment variables are not sensitive - Properties: - Environment: - Variables: - LOG_LEVEL: !FindInMap [LambdaFunction, Logging, Level] - CODE_PIPELINE_NAME: !Ref CustomControlTowerCodePipeline - SOLUTION_ID: !FindInMap [ Solution, Metrics, SolutionID ] - SOLUTION_VERSION: v2.5.3 - Code: - S3Bucket: !Sub "control-tower-cfct-assets-prod-${AWS::Region}" - S3Key: customizations-for-aws-control-tower/v2.5.3/custom-control-tower-lifecycle-event-handler.zip - Description: Custom Control Tower Lifecyle event Lambda to handle lifecycle events - Handler: lifecycle_event_handler.lambda_handler - MemorySize: 512 - Role: !GetAtt 'CustomControlTowerLELambdaRole.Arn' - Runtime: python3.8 - Timeout: 30 - TracingConfig: - Mode: Active - - # FIFO SQS Dead Letter Queue for storing Lifecycle Events (LE) that can't be processed (consumed) successfully - CustomControlTowerLEFIFODLQueue: - Type: "AWS::SQS::Queue" - DependsOn: CustomControlTowerDeploymentLambda - Metadata: - cfn_nag: - rules_to_suppress: - - id: W28 - reason: "The queue name is defined in order not to exceed the limit on the length of SQS queue name." - Properties: - QueueName: CustomControlTowerLEFIFODLQueue.fifo - ContentBasedDeduplication: True - FifoQueue: True - MessageRetentionPeriod: 1209600 #1209600 seconds (14 days) - KmsDataKeyReusePeriodSeconds: 300 - KmsMasterKeyId: !Sub - - alias/${KMSKeyName} - - {KMSKeyName: !FindInMap [KMS, Alias, Name]} - ReceiveMessageWaitTimeSeconds: 10 - - # FIFO SQS Queue for storing Lifecycle Events (LE) - CustomControlTowerLEFIFOQueue: - Type: "AWS::SQS::Queue" - DependsOn: CustomControlTowerDeploymentLambda - Metadata: - cfn_nag: - rules_to_suppress: - - id: W28 - reason: "The queue name is defined in order not to exceed the limit on the length of SQS queue name." - Properties: - QueueName: CustomControlTowerLEFIFOQueue.fifo - ContentBasedDeduplication: True - FifoQueue: True - KmsDataKeyReusePeriodSeconds: 300 - KmsMasterKeyId: !Sub - - alias/${KMSKeyName} - - {KMSKeyName: !FindInMap [KMS, Alias, Name]} - MessageRetentionPeriod: 345600 #345600 seconds (4 days) - ReceiveMessageWaitTimeSeconds: 20 - VisibilityTimeout: 30 #30 seconds - RedrivePolicy: - deadLetterTargetArn: !GetAtt CustomControlTowerLEFIFODLQueue.Arn - maxReceiveCount: 5 - - # Create event source mapping between the lifecycle event FIFO queue and lambda function to make the queue as the lambda trigger - CustomControlTowerLEQueueLambdaEventMapping: - Type: AWS::Lambda::EventSourceMapping - Properties: - BatchSize: 10 - Enabled: true - EventSourceArn: !GetAtt CustomControlTowerLEFIFOQueue.Arn - FunctionName: !Ref CustomControlTowerLELambda - - CustomControlTowerPipelineTriggerRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: "Allow" - Principal: - Service: - - "events.amazonaws.com" - Action: - - "sts:AssumeRole" - - CustomControlTowerCodeCommitPipelineTriggerCWEventRule: - Type: AWS::Events::Rule - Condition: IsCodeCommitPipelineSource - Properties: - Description: Custom Control Tower - Rule for triggering CodePipeline from CodeCommit - EventPattern: - { - "source": [ - "aws.codecommit" - ], - "detail-type": [ - "CodeCommit Repository State Change" - ], - "resources": [ - !Sub "arn:${AWS::Partition}:codecommit:${AWS::Region}:${AWS::AccountId}:${CodeCommitRepositoryName}" - ], - "detail": { - "event": [ - "referenceCreated", - "referenceUpdated" - ], - "referenceType": [ - "branch" - ], - "referenceName": [ - !Ref CodeCommitBranchName - ] - } - } - State: ENABLED - Targets: - - Arn: !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CustomControlTowerCodePipeline} - Id: "CustomControlTower_Pipeline_Trigger" - RoleArn: !GetAtt CustomControlTowerPipelineTriggerRole.Arn - - # Cloudwatch Event Rule for Lifecycle Event (LE): triggered by LE events and send events to SQS - CustomControlTowerLECWEventRule: - Type: AWS::Events::Rule - Properties: - Description: Custom Control Tower - Rule for lifecycle events from Control Tower Service - EventPattern: - { - "detail-type": [ - "AWS Service Event via CloudTrail" - ], - "source": [ - "aws.controltower" - ], - "detail": { - "eventName": [ - "CreateManagedAccount" - ], - "serviceEventDetails": { - "createManagedAccountStatus": { - "state": [ - "SUCCEEDED" - ] - } - } - } - } - State: ENABLED - Targets: - - Arn: !GetAtt CustomControlTowerLEFIFOQueue.Arn - Id: "CustomControlTower_Lifecycle_Event_FIFO_Queue" - SqsParameters: - MessageGroupId: CustomControlTower_Lifecycle_Event - - # Lifecycle event SQS Policy - CustomControlTowerLEQueuePolicy: - Type: AWS::SQS::QueuePolicy - Properties: - Queues: - - !Ref CustomControlTowerLEFIFOQueue - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Principal: - Service: events.amazonaws.com - Action: sqs:SendMessage - Resource: !GetAtt CustomControlTowerLEFIFOQueue.Arn - Condition: - ArnEquals: - aws:SourceArn: !GetAtt CustomControlTowerLECWEventRule.Arn - -Outputs: - CustomControlTowerCodePipeline: - Description: Custom Control Tower CodePipieline - Value: !Ref CustomControlTowerCodePipeline - CustomControlTowerPipelineS3Bucket: - Description: Custom Control Tower Configuration Bucket - Value: !Ref CustomControlTowerPipelineS3Bucket - CustomControlTowerSolutionVersion: - Description: Version Number - Value: "v2.5.3" - Export: - Name: Custom-Control-Tower-Version diff --git a/aws_sra_examples/solutions/common/common_cfct_setup/templates/sra-common-cfct-setup-main.yaml b/aws_sra_examples/solutions/common/common_cfct_setup/templates/sra-common-cfct-setup-main.yaml index 94af01ffd..418bdd8ad 100644 --- a/aws_sra_examples/solutions/common/common_cfct_setup/templates/sra-common-cfct-setup-main.yaml +++ b/aws_sra_examples/solutions/common/common_cfct_setup/templates/sra-common-cfct-setup-main.yaml @@ -20,8 +20,19 @@ Metadata: - Label: default: General Properties Parameters: + - pSRASolutionTagKey - pSRASolutionName - pSRAStagingS3BucketName + - Label: + default: CodeBuild Properties + Parameters: + - pCodeBuildProjectName + - pCodeBuildRoleName + - Label: + default: AWS Code Build Project - Lambda Function Properties + Parameters: + - pCodeBuildProjectLambdaRoleName + - pCodeBuildProjectLambdaFunctionName - Label: default: CFCT - Pipeline Configuration Parameters: @@ -40,18 +51,41 @@ Metadata: - pRegionConcurrencyType - pMaxConcurrentPercentage - pFailureTolerancePercentage + - Label: + default: General Lambda Function Properties + Parameters: + - pCreateLambdaLogGroup + - pLambdaLogGroupRetention + - pLambdaLogGroupKmsKey + - pLambdaLogLevel ParameterLabels: pCodeCommitBranchName: default: CodeCommit Branch Name pCodeCommitRepositoryName: default: CodeCommit Repository Name + pCodeBuildProjectName: + default: SRA CodeBuild Project Name + pCodeBuildRoleName: + default: SRA CodeBuild Role Name + pCodeBuildProjectLambdaRoleName: + default: SRA CodeBuild Project Lambda Role Name + pCodeBuildProjectLambdaFunctionName: + default: SRA CodeBuild Project Lambda Function Name pCodePipelineSource: default: AWS CodePipeline Source + pCreateLambdaLogGroup: + default: pCreateLambdaLogGroup pExistingRepository: default: Existing CodeCommit Repository? pFailureTolerancePercentage: default: Failure Tolerance Percentage + pLambdaLogGroupKmsKey: + default: (Optional) Lambda Log Group KMS Key + pLambdaLogGroupRetention: + default: Lambda Log Group Retention + pLambdaLogLevel: + default: Lambda Log Level pMaxConcurrentPercentage: default: Max Concurrent Percentage pPipelineApprovalEmail: @@ -78,11 +112,42 @@ Parameters: Default: custom-control-tower-configuration Description: Name of the CodeCommit repository that contains custom Control Tower configuration. Type: String + pCodeBuildProjectName: + AllowedValues: [sra-cfct-codebuild-project] + Default: sra-cfct-codebuild-project + Description: + SRA CodeBuild project name + Type: String + pCodeBuildRoleName: + AllowedValues: [sra-cfct-codebuild-role] + Default: sra-cfct-codebuild-role + Description: + SRA CodeBuild role name + Type: String + pCodeBuildProjectLambdaRoleName: + AllowedPattern: '^[\w+=,.@-]{1,64}$' + ConstraintDescription: Max 64 alphanumeric characters. Also special characters supported [+, =, ., @, -]. + Default: sra-cfct-codebuild-project-lambda-role + Description: Lambda execution role for starting the code build project + Type: String + pCodeBuildProjectLambdaFunctionName: + AllowedPattern: '^[\w-]{1,64}$' + ConstraintDescription: Max 64 alphanumeric characters. Also special characters supported [_, -] + Default: sra-cfct-codebuild-project-lambda + Description: Lambda function name for starting the code build project + Type: String pCodePipelineSource: AllowedValues: [Amazon S3, AWS CodeCommit] Default: AWS CodeCommit Description: Which AWS CodePipeline source provider do you want to select? Type: String + pCreateLambdaLogGroup: + AllowedValues: ['Yes', 'No'] + Default: 'No' + Description: + Indicates whether a CloudWatch Log Group should be explicitly created for the Lambda function, to allow for setting a Log Retention and/or KMS + Key for encryption. + Type: String pExistingRepository: AllowedValues: ['Yes', 'No'] Default: 'No' @@ -95,6 +160,24 @@ Parameters: MaxValue: 100 MinValue: 0 Type: Number + pLambdaLogGroupKmsKey: + AllowedPattern: '^$|^arn:(aws[a-zA-Z-]*){1}:kms:[a-z0-9-]+:\d{12}:key\/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$' + ConstraintDescription: 'Key ARN example: arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab' + Default: '' + Description: + (Optional) KMS Key ARN to use for encrypting the Lambda logs data. If empty, encryption is enabled with CloudWatch Logs managing the server-side + encryption keys. + Type: String + pLambdaLogGroupRetention: + AllowedValues: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653] + Default: 14 + Description: Specifies the number of days you want to retain log events + Type: String + pLambdaLogLevel: + AllowedValues: [INFO, ERROR, DEBUG] + Default: INFO + Description: Lambda Function Logging Level + Type: String pMaxConcurrentPercentage: Default: 100 Description: The maximum percentage of accounts in which to perform this operation at one time. @@ -122,6 +205,11 @@ Parameters: Default: sra-common-cfct-setup Description: The SRA solution name. The Description value is the folder name of the solution Type: String + pSRASolutionTagKey: + AllowedValues: [sra-solution] + Default: sra-solution + Description: The SRA solution tag key applied to all resources created by the solution that support tagging. The value is the pSRASolutionName. + Type: String pSRAStagingS3BucketName: AllowedPattern: '^([\w.-]{1,900})$|^(\/[\w.-]{1,900})*[\w.-]{1,900}$' ConstraintDescription: @@ -139,9 +227,302 @@ Rules: - AssertDescription: "'Pipeline Approval Email Address' parameter is required if the 'Pipeline Approval Stage' parameter is set to 'Yes'." Assert: !Equals [!Ref pPipelineApprovalStage, 'No'] +Conditions: + cCreateLambdaLogGroup: !Equals [!Ref pCreateLambdaLogGroup, 'Yes'] + cUsingKmsKey: !Not [!Equals [!Ref pLambdaLogGroupKmsKey, '']] + cUseGraviton: !Or + - !Equals [!Ref 'AWS::Region', ap-northeast-1] + - !Equals [!Ref 'AWS::Region', ap-south-1] + - !Equals [!Ref 'AWS::Region', ap-southeast-1] + - !Equals [!Ref 'AWS::Region', ap-southeast-2] + - !Equals [!Ref 'AWS::Region', eu-central-1] + - !Equals [!Ref 'AWS::Region', eu-west-1] + - !Equals [!Ref 'AWS::Region', eu-west-2] + - !Equals [!Ref 'AWS::Region', us-east-1] + - !Equals [!Ref 'AWS::Region', us-east-2] + - !Equals [!Ref 'AWS::Region', us-west-2] + Resources: + rCodeBuildProject: + Type: AWS::CodeBuild::Project + Properties: + Name: !Sub '${pCodeBuildProjectName}' + Artifacts: + Type: NO_ARTIFACTS + Description: "Codebuild project to get SRA code from github" + Environment: + ComputeType: BUILD_GENERAL1_SMALL + EnvironmentVariables: + - Name: AWS_DEFAULT_REGION + Value: !Ref AWS::Region + - Name: AWS_ACCOUNT_ID + Value: !Ref "AWS::AccountId" + Image: "aws/codebuild/standard:5.0" + PrivilegedMode: true + Type: "LINUX_CONTAINER" + ServiceRole: !GetAtt rCodeBuildRole.Arn + TimeoutInMinutes: 120 + Source: + Type: NO_SOURCE + BuildSpec: !Sub | + version: 0.2 + phases: + pre_build: + commands: + - echo Build started on `date`... + build: + commands: + - echo Build started on `date` in ${AWS::Region} region + - echo Downloading customizations-for-aws-control-tower.template template... + - wget https://raw.githubusercontent.com/aws-solutions/aws-control-tower-customizations/main/customizations-for-aws-control-tower.template + - echo Listing current directory... + - ls + - echo Showing current caller identity... + - aws sts get-caller-identity + - echo Copying customizations-for-aws-control-tower.template template to SRA staging bucket cloudformation template... + - aws s3 cp customizations-for-aws-control-tower.template s3://sra-staging-$AWS_ACCOUNT_ID-$AWS_DEFAULT_REGION/sra-common-cfct-setup/templates/ + - git clone https://github.com/aws-samples/aws-security-reference-architecture-examples.git + post_build: + commands: + - echo Build completed on `date` + + rCodeBuildRole: + Type: AWS::IAM::Role + Metadata: + cfn_nag: + rules_to_suppress: + - id: W11 + reason: Allow * in resource when required + - id: W28 + reason: The role name is defined to identify automation resources + Properties: + RoleName: !Sub '${pCodeBuildRoleName}' + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - codebuild.amazonaws.com + Action: + - "sts:AssumeRole" + Policies: + - PolicyName: "logs-access" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: + - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*" + - PolicyName: "s3-staging-bucket-access" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: + - s3:PutObject + Resource: + - !Sub "arn:${AWS::Partition}:s3:::${pSRAStagingS3BucketName}/${pSRASolutionName}/templates" + - !Sub "arn:${AWS::Partition}:s3:::${pSRAStagingS3BucketName}/${pSRASolutionName}/templates/*" + + rStartCodeBuildProjectCustomResource: + DependsOn: rCodeBuildProject + Type: Custom::LambdaCustomResource + Version: '1.0' + Properties: + ServiceToken: !GetAtt rStartCodeBuildProjectLambdaFunction.Arn + + rStartCodeBuildProjectLambdaFunction: + Metadata: + cfn_nag: + rules_to_suppress: + - id: W58 + reason: Lambda role provides access to CloudWatch Logs + - id: W89 + reason: Lambda does not need to communicate with VPC resources. + - id: W92 + reason: Lambda does not need reserved concurrent executions. + checkov: + skip: + - id: CKV_AWS_115 + comment: Lambda does not need reserved concurrent executions. + - id: CKV_AWS_116 + comment: DLQ not needed, as Lambda function only triggered by CloudFormation events. + - id: CKV_AWS_117 + comment: Lambda does not need to communicate with VPC resources. + - id: CKV_AWS_173 + comment: Environment variables are not sensitive. + Type: AWS::Lambda::Function + Properties: + FunctionName: !Ref pCodeBuildProjectLambdaFunctionName + Description: Start SRA codebuild project + Architectures: !If + - cUseGraviton + - [arm64] + - !Ref AWS::NoValue + Handler: index.lambda_handler + Role: !GetAtt rStartCodeBuildProjectLambdaRole.Arn + Runtime: python3.9 + Timeout: 900 + Environment: + Variables: + LOG_LEVEL: !Ref pLambdaLogLevel + CODE_BUILD_PROJECT_NAME: !Ref pCodeBuildProjectName + Tags: + - Key: !Ref pSRASolutionTagKey + Value: !Ref pSRASolutionName + Code: + ZipFile: | + # type: ignore + """Custom Resource to start codebuild project. + + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: MIT-0 + """ + import logging + import os + + import boto3 + import cfnresponse + import time + from botocore.exceptions import ClientError + + LOGGER = logging.getLogger(__name__) + log_level: str = os.environ.get("LOG_LEVEL", "INFO") + LOGGER.setLevel(log_level) + CODE_BUILD_PROJECT_NAME: str = os.environ.get("CODE_BUILD_PROJECT_NAME") + + def start_build(): + """Start build job. + + Returns: + Response data for custom resource + """ + management_account_session = boto3.Session() + codebuild_client = management_account_session.client("codebuild") + response = codebuild_client.start_build(projectName=CODE_BUILD_PROJECT_NAME) + LOGGER.info({"API_Call": "codebuild:StartBuild", "API_Response": response}) + buildId = response["build"]["id"] + return wait_for_build([buildId], codebuild_client) + + + def wait_for_build(BuildId, client): + buildWaitStatus = "FAILURE_WAIT_TIMEOUT" + counter = 0 + while counter < 30: + time.sleep(10) + counter = counter + 1 + buildStatus = get_build_status(BuildId, client) + if buildStatus == "SUCCEEDED": + buildWaitStatus = "SUCCESS" + break + elif buildStatus == "FAILED" or buildStatus == "FAULT" or buildStatus == "STOPPED" or buildStatus == "TIMED_OUT": + buildWaitStatus = "BUILD " + buildStatus + " (check codebuild project cloudwatch log group for details)" + break + return buildWaitStatus + + + def get_build_status(buildId, client): + build = client.batch_get_builds(ids=buildId) + return build["builds"][0]["buildStatus"] + + + def create_event(event, context): + try: + data = {"data": start_build()} + if data["data"] == "SUCCESS": + cfnresponse.send(event, context, cfnresponse.SUCCESS, data, "CustomResourcePhysicalID") + else: + reason = f"See the details in CloudWatch Log Stream: '{context.log_group_name} and CloudFormation Events'" + cfnresponse.send(event, context, cfnresponse.FAILED, data, "CustomResourcePhysicalID") + except Exception: + LOGGER.exception("Unexpected!") + reason = f"See the details in CloudWatch Log Stream: '{context.log_group_name}'" + cfnresponse.send(event, context, cfnresponse.FAILED, {}, "CustomResourcePhysicalID", reason=reason) + return "CustomResourcePhysicalID" + + + def delete_event(event, context): + cfnresponse.send(event, context, cfnresponse.SUCCESS, {"delete_operation": "succeeded deleting"}, "CustomResourcePhysicalID") + + + def lambda_handler(event, context): + LOGGER.info(event) + if event["RequestType"] == "Create": + LOGGER.info("CREATE EVENT!!") + create_event(event, context) + if event["RequestType"] == "Update": + LOGGER.info("UPDATE EVENT!!") + if event["RequestType"] == "Delete": + LOGGER.info("DELETE EVENT!!") + delete_event(event, context) + + rStartCodeBuildProjectLambdaLogGroup: + DeletionPolicy: Retain + Type: AWS::Logs::LogGroup + UpdateReplacePolicy: Retain + Condition: cCreateLambdaLogGroup + Properties: + LogGroupName: !Sub /aws/lambda/${pCodeBuildProjectLambdaFunctionName} + KmsKeyId: !If + - cUsingKmsKey + - !Ref pLambdaLogGroupKmsKey + - !Ref AWS::NoValue + RetentionInDays: !Ref pLambdaLogGroupRetention + + rStartCodeBuildProjectLambdaRole: + Type: AWS::IAM::Role + Metadata: + cfn_nag: + rules_to_suppress: + # - id: W11 + # reason: Allow * in resource when required + - id: W28 + reason: The role name is defined to identify automation resources + Properties: + RoleName: !Ref pCodeBuildProjectLambdaRoleName + Description: !Sub Role for '${pCodeBuildProjectLambdaRoleName}' Lambda function + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: sts:AssumeRole + Principal: + Service: + - lambda.amazonaws.com + Tags: + - Key: !Ref pSRASolutionTagKey + Value: !Ref pSRASolutionName + Policies: + - PolicyName: codebuild-access + PolicyDocument: + Version: 2012-10-17 + Statement: + - Sid: codebuildStartBuild + Effect: Allow + Action: + - codebuild:StartBuild + - codebuild:BatchGetBuilds + Resource: !GetAtt rCodeBuildProject.Arn + - PolicyName: CloudWatchLogGroup-access + PolicyDocument: + Version: 2012-10-17 + Statement: + - Sid: CloudWatchLogs + Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${pCodeBuildProjectLambdaFunctionName}:log-stream:* + rCFCTStack: Type: AWS::CloudFormation::Stack + DependsOn: rStartCodeBuildProjectCustomResource Properties: TemplateURL: !Sub https://${pSRAStagingS3BucketName}.s3.${AWS::Region}.${AWS::URLSuffix}/${pSRASolutionName}/templates/customizations-for-aws-control-tower.template # TemplateURL: https://s3.amazonaws.com/solutions-reference/customizations-for-aws-control-tower/latest/custom-control-tower-initiation.template @@ -158,8 +539,3 @@ Resources: PipelineApprovalEmail: !Ref pPipelineApprovalEmail PipelineApprovalStage: !Ref pPipelineApprovalStage RegionConcurrencyType: !Ref pRegionConcurrencyType - -Outputs: - CustomControlTowerSolutionVersion: - Description: Version Number - Value: 'v2.5.3'