diff --git a/resources/OrganizationAccountAccessRole.yaml b/resources/OrganizationAccountAccessRole.yaml new file mode 100644 index 000000000..24cf59bb5 --- /dev/null +++ b/resources/OrganizationAccountAccessRole.yaml @@ -0,0 +1,42 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: > + Organizational Account Access Role for Cross-Account automation + +Parameters: + RoleName: + Type: String + Description: >- + The name of the Cross-Account role + Default: OrganizationAccountAccessRole + AdministratorAccountId: + Type: String + Description: >- + AWS Account Id of the administrator account + (the account in which StackSets will be created). + MaxLength: 12 + MinLength: 12 + +Resources: + OrganizationAccountAccessRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Ref RoleName + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + AWS: + - !Ref AdministratorAccountId + Action: + - sts:AssumeRole + Path: / + ManagedPolicyArns: + - !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess + +Outputs: + RoleArn: + Description: The ARN of the Organization Account Access Role + Value: !GetAtt OrganizationAccountAccessRole.Arn + Export: + Name: !Sub "${AWS::StackName}-RoleArn" diff --git a/samples/sample-ec2-with-codedeploy/template.yml b/samples/sample-ec2-with-codedeploy/template.yml index 71b47222a..77e09bfd1 100644 --- a/samples/sample-ec2-with-codedeploy/template.yml +++ b/samples/sample-ec2-with-codedeploy/template.yml @@ -166,7 +166,7 @@ Resources: Fn::Sub: ${Environment}-public-subnet-1b - Fn::ImportValue: Fn::Sub: ${Environment}-public-subnet-1c - SecurityGroups: + SecurityGroups: - !Ref 'PublicLoadBalancerSG' ApplicationLoadBalancerHTTPListener: Type: "AWS::ElasticLoadBalancingV2::Listener" @@ -251,4 +251,4 @@ Outputs: Description: The url of the external load balancer Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']] Export: - Name: 'LoadBalancerExternalUrl' \ No newline at end of file + Name: 'LoadBalancerExternalUrl' diff --git a/samples/sample-ecr-repository/template.yml b/samples/sample-ecr-repository/template.yml index 1dd3f69de..0f5226997 100644 --- a/samples/sample-ecr-repository/template.yml +++ b/samples/sample-ecr-repository/template.yml @@ -1,22 +1,22 @@ # // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. # // SPDX-License-Identifier: Apache-2.0 -AWSTemplateFormatVersion: '2010-09-09' +AWSTemplateFormatVersion: "2010-09-09" Description: ADF CloudFormation Sample Template (Shared ECR Repository) Metadata: License: Apache-2.0 Parameters: TestingAccountId: - Description: Testing Accound Id that will pull from this repository + Description: Testing Account Id that will pull from this repository Type: String ProductionAccountId: - Description: Production Accound Id that will pull from this repository + Description: Production Account Id that will pull from this repository Type: String Resources: SampleAppRepository: Type: AWS::ECR::Repository Properties: - RepositoryName: 'sample-node-app' + RepositoryName: "sample-node-app" LifecyclePolicy: LifecyclePolicyText: !Sub - | @@ -48,21 +48,21 @@ Resources: } - DaysToRetainUntaggedContainerImages: 2 MaxTaggedContainerImagesToRetain: 2 - RepositoryPolicyText: + RepositoryPolicyText: Version: "2012-10-17" - Statement: + Statement: - Sid: AllowPull Effect: Allow - Principal: - AWS: - - !Sub "arn:aws:iam::${TestingAccountId}:root" - - !Sub "arn:aws:iam::${ProductionAccountId}:root" - Action: + Principal: + AWS: + - !Sub "arn:${AWS::Partition}:iam::${TestingAccountId}:root" + - !Sub "arn:${AWS::Partition}:iam::${ProductionAccountId}:root" + Action: - "ecr:Get*" - "ecr:Describe*" - "ecr:BatchGetImage" - "ecr:BatchCheckLayerAvailability" -Outputs: +Outputs: SampleAppRepository: Value: !GetAtt SampleAppRepository.Arn diff --git a/samples/sample-expunge-vpc/template.yml b/samples/sample-expunge-vpc/template.yml index f986ec6d9..2859a82f6 100644 --- a/samples/sample-expunge-vpc/template.yml +++ b/samples/sample-expunge-vpc/template.yml @@ -1,30 +1,30 @@ -AWSTemplateFormatVersion: '2010-09-09' +AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: Deploys the Custom Resource for deleting the default VPC in all regions Resources: LambdaVPCPolicyRole: - Type: 'AWS::IAM::Role' + Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: - Version: '2012-10-17' + Version: "2012-10-17" Statement: - Effect: Allow Principal: - Service: 'lambda.amazonaws.com' + Service: "lambda.amazonaws.com" Action: - - 'sts:AssumeRole' - Path: '/' + - "sts:AssumeRole" + Path: "/" ManagedPolicyArns: - - 'arn:aws:iam::aws:policy/AmazonVPCFullAccess' - - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' + - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + - !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonVPCFullAccess" Policies: - PolicyName: ec2 PolicyDocument: Statement: - Effect: Allow Action: - - 'ec2:DescribeRegions' - Resource: '*' + - "ec2:DescribeRegions" + Resource: "*" DeleteVPCLambda: Type: AWS::Serverless::Function Properties: @@ -35,8 +35,8 @@ Resources: Runtime: python3.8 Timeout: 600 Environment: - Variables: - region_name: !Ref "AWS::Region" + Variables: + region_name: !Ref "AWS::Region" DeleteVPCCustom: Type: Custom::DeleteVPC Properties: diff --git a/samples/sample-iam/template.yml b/samples/sample-iam/template.yml index 3f0a4910f..1cb4dc6d1 100644 --- a/samples/sample-iam/template.yml +++ b/samples/sample-iam/template.yml @@ -1,21 +1,21 @@ # // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. # // SPDX-License-Identifier: Apache-2.0 -AWSTemplateFormatVersion: '2010-09-09' +AWSTemplateFormatVersion: "2010-09-09" Description: ADF CloudFormation Sample Template (IAM) Metadata: License: Apache-2.0 Resources: DevelopersIAMGroup: Type: AWS::IAM::Group - Properties: + Properties: GroupName: adf-sample-developers-group ManagedPolicyArns: - - 'arn:aws:iam::aws:policy/AWSServiceCatalogEndUserFullAccess' - - 'arn:aws:iam::aws:policy/AWSCloud9User' - - 'arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess' - - 'arn:aws:iam::aws:policy/AWSCodeCommitFullAccess' - - 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' + - !Sub "arn:${AWS::Partition}:iam::aws:policy/AWSServiceCatalogEndUserFullAccess" + - !Sub "arn:${AWS::Partition}:iam::aws:policy/AWSCloud9User" + - !Sub "arn:${AWS::Partition}:iam::aws:policy/AWSCloudFormationReadOnlyAccess" + - !Sub "arn:${AWS::Partition}:iam::aws:policy/AWSCodeCommitFullAccess" + - !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonS3ReadOnlyAccess" GlobalInstanceProfile: Type: "AWS::IAM::InstanceProfile" Properties: @@ -75,7 +75,7 @@ Resources: Action: - "sts:AssumeRole" ManagedPolicyArns: - - "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole" + - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSCodeDeployRole" RoleName: "codedeploy-service-role" Outputs: DevelopersIAMGroup: diff --git a/samples/sample-service-catalog-product/productX/template.yml b/samples/sample-service-catalog-product/productX/template.yml index d1099a49c..1313d5e69 100644 --- a/samples/sample-service-catalog-product/productX/template.yml +++ b/samples/sample-service-catalog-product/productX/template.yml @@ -5,35 +5,35 @@ AWSTemplateFormatVersion: '2010-09-09' Description: ADF CloudFormation Sample Service Catalog Product Metadata: License: Apache-2.0 -Parameters: +Parameters: Environment: Type: String Default: testing - AllowedValues: + AllowedValues: - testing Description: The environment to use, IDE are only supported in testing - InstanceType: + InstanceType: Type: String Default: t3.micro - AllowedValues: + AllowedValues: - t3.micro - m5.large Description: Enter t3.micro or m5.large. Default is t3.micro. - AutomaticStopTimeInMinutes: + AutomaticStopTimeInMinutes: Type: Number Default: 480 AllowedValues: - 480 - 960 Description: The amount of minutes that this Cloud9 Instance should stop after (8 or 16 hours). - InstanceDescription: + InstanceDescription: Type: String Default: "Development environment used during office hours" Description: The Description of the Cloud9 Instance. - InstanceName: + InstanceName: Type: String Description: The name of the Cloud9 Instance. - UserName: + UserName: Type: String Description: Your IAM UserName that will be used as the OwnerArn in the Cloud9 Instance. Resources: @@ -44,7 +44,7 @@ Resources: Description: !Ref InstanceDescription InstanceType: !Ref InstanceType Name: !Ref InstanceName - OwnerArn: !Sub "arn:aws:iam::${AWS::AccountId}:user/${UserName}" #In this sample case 'sample-developer' from the IAM stack can be used here + OwnerArn: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:user/${UserName}" #In this sample case 'sample-developer' from the IAM stack can be used here SubnetId: Fn::ImportValue: Fn::Sub: ${Environment}-public-subnet-1a # Imported from sample-vpc diff --git a/src/lambda_codebase/account_bootstrap.py b/src/lambda_codebase/account_bootstrap.py index 21dfbefdc..267569820 100644 --- a/src/lambda_codebase/account_bootstrap.py +++ b/src/lambda_codebase/account_bootstrap.py @@ -18,10 +18,12 @@ from cloudformation import CloudFormation from s3 import S3 from sts import STS +from partition import get_partition # Globals taken from the lambda environment variables S3_BUCKET = os.environ["S3_BUCKET_NAME"] REGION_DEFAULT = os.environ["AWS_REGION"] +PARTITION = get_partition(REGION_DEFAULT) LOGGER = configure_logger(__name__) @@ -33,11 +35,13 @@ def configure_generic_account(sts, event, region, role): are required for the global.yml in all target accounts. """ try: + deployment_account_id = event['deployment_account_id'] + cross_account_access_role = event['cross_account_access_role'] + role_arn = f'arn:{PARTITION}:iam::{deployment_account_id}:role/{cross_account_access_role}' + deployment_account_role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format( - event['deployment_account_id'], - event['cross_account_access_role'] - ), 'configure_generic' + role_arn=role_arn, + role_session_name='configure_generic' ) parameter_store_deployment_account = ParameterStore( event['deployment_account_region'], @@ -59,6 +63,7 @@ def configure_generic_account(sts, event, region, role): parameter_store_target_account.put_parameter('bucket_name', bucket_name) parameter_store_target_account.put_parameter('deployment_account_id', event['deployment_account_id']) + def configure_master_account_parameters(event): """ Update the Master account parameter store in us-east-1 with the deployment_account_id @@ -69,6 +74,7 @@ def configure_master_account_parameters(event): parameter_store_deployment_account_region = ParameterStore(event['deployment_account_region'], boto3) parameter_store_deployment_account_region.put_parameter('deployment_account_id', event['account_id']) + def configure_deployment_account_parameters(event, role): """ Applies the Parameters from adfconfig plus other essential @@ -83,16 +89,21 @@ def configure_deployment_account_parameters(event, role): value ) + def is_inter_ou_account_move(event): return not event["source_ou_id"].startswith('r-') and not event["destination_ou_id"].startswith('r-') + def lambda_handler(event, _): sts = STS() + + account_id = event["account_id"] + cross_account_access_role = event["cross_account_access_role"] + role_arn = f'arn:{PARTITION}:iam::{account_id}:role/{cross_account_access_role}' + role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format( - event["account_id"], - event["cross_account_access_role"] - ), 'master_lambda' + role_arn=role_arn, + role_session_name='management_lambda' ) if event['is_deployment_account']: @@ -112,13 +123,14 @@ def lambda_handler(event, _): deployment_account_region=event["deployment_account_region"], role=role, wait=True, - stack_name=None, # Stack name will be automatically defined based on event + # Stack name will be automatically defined based on event + stack_name=None, s3=s3, s3_key_path=event["full_path"], - account_id=event["account_id"] + account_id=account_id ) if is_inter_ou_account_move(event): - cloudformation.delete_all_base_stacks(True) #override Wait + cloudformation.delete_all_base_stacks(True) # override Wait cloudformation.create_stack() if region == event["deployment_account_region"]: cloudformation.create_iam_stack() diff --git a/src/lambda_codebase/cross_region_bucket/main.py b/src/lambda_codebase/cross_region_bucket/main.py index 94ea8c394..8720d40dd 100644 --- a/src/lambda_codebase/cross_region_bucket/main.py +++ b/src/lambda_codebase/cross_region_bucket/main.py @@ -20,6 +20,8 @@ delete, ) +from partition import get_partition + # Type aliases: BucketName = str Data = Mapping[str, str] @@ -143,11 +145,14 @@ def ensure_bucket(region: str, bucket_name_prefix: str) -> Tuple[BucketName, Cre bucket_name_suffix = "".join( secrets.choice(string.ascii_lowercase + string.digits) for _ in range(6) ) - bucket_name = "{0}-{1}".format(bucket_name_prefix, bucket_name_suffix) + bucket_name = f"{bucket_name_prefix}-{bucket_name_suffix}" try: + LOGGER.info('Creating bucket') config = {'Bucket': bucket_name} if region != 'us-east-1': - config["CreateBucketConfiguration"] = {"LocationConstraint": region} + config["CreateBucketConfiguration"] = { + "LocationConstraint": region + } s3_client.create_bucket( **config ) @@ -155,7 +160,8 @@ def ensure_bucket(region: str, bucket_name_prefix: str) -> Tuple[BucketName, Cre return bucket_name, True except s3_client.exceptions.BucketAlreadyExists: LOGGER.info( - "Bucket name %s already taken, trying another one ...", bucket_name + "Bucket name %s already taken, trying another " + "one ...", bucket_name ) @@ -185,11 +191,13 @@ def ensure_bucket_has_no_public_access(bucket_name: str, region: str) -> None: def ensure_bucket_policy(bucket_name: str, region: str, policy: MutableMapping) -> None: + partition = get_partition(region) + s3_client = get_s3_client(region) for action in policy["Statement"]: action["Resource"] = [ - "arn:aws:s3:::{0}".format(bucket_name), - "arn:aws:s3:::{0}/*".format(bucket_name), + f"arn:{partition}:s3:::{bucket_name}", + f"arn:{partition}:s3:::{bucket_name}/*", ] s3_client.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy)) diff --git a/src/lambda_codebase/determine_event.py b/src/lambda_codebase/determine_event.py index 77d6418b1..5118df81a 100644 --- a/src/lambda_codebase/determine_event.py +++ b/src/lambda_codebase/determine_event.py @@ -15,11 +15,10 @@ REGION_DEFAULT = os.environ["AWS_REGION"] + def lambda_handler(event, _): parameters = ParameterStore(region=REGION_DEFAULT, role=boto3) - account_id = event.get( - 'detail').get( - 'requestParameters').get('accountId') + account_id = event.get('detail').get('requestParameters').get('accountId') organizations = Organizations(role=boto3, account_id=account_id) parsed_event = Event( event=event, diff --git a/src/lambda_codebase/generic_account_config.py b/src/lambda_codebase/generic_account_config.py index 7818b56b6..b2041770d 100644 --- a/src/lambda_codebase/generic_account_config.py +++ b/src/lambda_codebase/generic_account_config.py @@ -11,25 +11,32 @@ To include the newly created account. """ +import os + from logger import configure_logger from sts import STS from stepfunctions import StepFunctions +from partition import get_partition LOGGER = configure_logger(__name__) +REGION_DEFAULT = os.getenv('AWS_REGION') def lambda_handler(event, _): sts = STS() + deployment_account_id = event.get('deployment_account_id') + partition = get_partition(REGION_DEFAULT) + cross_account_access_role = event.get('cross_account_access_role') + role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format( - event['deployment_account_id'], - event['cross_account_access_role']), - 'step_function') + f'arn:{partition}:iam::{deployment_account_id}:role/{cross_account_access_role}', + 'step_function' + ) step_functions = StepFunctions( role=role, - deployment_account_id=event['deployment_account_id'], + deployment_account_id=deployment_account_id, deployment_account_region=event['deployment_account_region'], full_path=event['full_path'], regions=event['regions'], diff --git a/src/lambda_codebase/initial_commit/adfconfig.yml.j2 b/src/lambda_codebase/initial_commit/adfconfig.yml.j2 index e59927abf..a4a015678 100644 --- a/src/lambda_codebase/initial_commit/adfconfig.yml.j2 +++ b/src/lambda_codebase/initial_commit/adfconfig.yml.j2 @@ -8,7 +8,7 @@ regions: - {{ Region }} {%- endfor %} config: - main-notification-endpoint: + main-notification-endpoint: - type: {{ NotificationEndpointType }} # slack or email target: {{ NotificationEndpoint }} # Email/Slack channel who receives notifications for the main bootstrapping pipeline protected: diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml index dd6b761dd..af06e893b 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml @@ -6,22 +6,22 @@ Transform: "AWS::Serverless-2016-10-31" Description: ADF CloudFormation Template (Global) for Deployment Account Parameters: ADFVersion: - Type : "AWS::SSM::Parameter::Value" + Type: "AWS::SSM::Parameter::Value" Default: adf_version ADFLogLevel: - Type : "AWS::SSM::Parameter::Value" + Type: "AWS::SSM::Parameter::Value" Default: adf_log_level MasterAccountId: - Type : "AWS::SSM::Parameter::Value" + Type: "AWS::SSM::Parameter::Value" Default: master_account_id SharedModulesBucket: - Type : "AWS::SSM::Parameter::Value" + Type: "AWS::SSM::Parameter::Value" Default: deployment_account_bucket OrganizationId: - Type : "AWS::SSM::Parameter::Value" + Type: "AWS::SSM::Parameter::Value" Default: organization_id CrossAccountAccessRole: - Type : "AWS::SSM::Parameter::Value" + Type: "AWS::SSM::Parameter::Value" Default: cross_account_access_role Image: Description: The Image you wish to use for CodeBuild (defaults to ubuntu). @@ -32,14 +32,14 @@ Parameters: Type: String Default: "BUILD_GENERAL1_LARGE" # For threading with large amounts of pipelines this is the most effective default AllowedValues: - - "BUILD_GENERAL1_SMALL" #3 GB memory, 2 vCPU - - "BUILD_GENERAL1_MEDIUM" #7 GB memory, 4 vCPU - - "BUILD_GENERAL1_LARGE" #15 GB memory, 8 vCPU + - "BUILD_GENERAL1_SMALL" # 3 GB memory, 2 vCPU + - "BUILD_GENERAL1_MEDIUM" # 7 GB memory, 4 vCPU + - "BUILD_GENERAL1_LARGE" # 15 GB memory, 8 vCPU NotificationEndpoint: - Type : "AWS::SSM::Parameter::Value" + Type: "AWS::SSM::Parameter::Value" Default: notification_endpoint NotificationType: - Type : "AWS::SSM::Parameter::Value" + Type: "AWS::SSM::Parameter::Value" Default: notification_type PipelinePrefix: Description: The Prefix that will be attached to pipeline stacks and names for ADF. @@ -78,7 +78,7 @@ Resources: - Sid: Allows admin of the key Effect: Allow Principal: - AWS: !Sub arn:aws:iam::${AWS::AccountId}:root + AWS: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root Action: - "kms:CancelKeyDeletion" - "kms:Create*" @@ -215,10 +215,10 @@ Resources: - s3:List* - s3:PutObject Resource: - - !Sub arn:aws:s3:::${PipelineBucket} - - !Sub arn:aws:s3:::${PipelineBucket}/* - - !Sub arn:aws:s3:::${SharedModulesBucket} - - !Sub arn:aws:s3:::${SharedModulesBucket}/* + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket} + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket}/* + - !Sub arn:${AWS::Partition}:s3:::${SharedModulesBucket} + - !Sub arn:${AWS::Partition}:s3:::${SharedModulesBucket}/* - Effect: Allow Sid: "KMS" Action: @@ -244,27 +244,27 @@ Resources: aws:PrincipalOrgID: !Ref OrganizationId Action: - "secretsmanager:Get*" - Resource: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/adf/*" # Only allow CodeBuild access to secrets that start with /adf/* + Resource: !Sub "arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/adf/*" # Only allow CodeBuild access to secrets that start with /adf/* - Effect: Allow Action: - "ssm:GetParameter" - "ssm:GetParameters" Resource: - - !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/* - - !Sub arn:aws:ssm:${AWS::Region}::parameter/aws/service/* + - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/* + - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}::parameter/aws/service/* - Effect: Deny Action: - "sts:AssumeRole" Resource: - !GetAtt CloudFormationDeploymentRole.Arn - - !Sub arn:aws:iam::${MasterAccountId}:role/${CrossAccountAccessRole} + - !Sub arn:${AWS::Partition}:iam::${MasterAccountId}:role/${CrossAccountAccessRole} - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: - - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/* + - !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/* - Effect: Allow Action: # If you plan on building docker images in CodeBuild you need these - "ecr:GetAuthorizationToken" @@ -305,10 +305,10 @@ Resources: - s3:List* - s3:PutObject Resource: - - !Sub arn:aws:s3:::${PipelineBucket} - - !Sub arn:aws:s3:::${PipelineBucket}/* - - !Sub arn:aws:s3:::${SharedModulesBucket} - - !Sub arn:aws:s3:::${SharedModulesBucket}/* + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket} + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket}/* + - !Sub arn:${AWS::Partition}:s3:::${SharedModulesBucket} + - !Sub arn:${AWS::Partition}:s3:::${SharedModulesBucket}/* - Effect: Allow Sid: "KMS" Action: @@ -322,17 +322,17 @@ Resources: Action: - "sts:AssumeRole" Resource: - - !Sub "arn:aws:iam::${MasterAccountId}:role/${CrossAccountAccessRole}-readonly" - - arn:aws:iam::*:role/adf-automation-role + - !Sub "arn:${AWS::Partition}:iam::${MasterAccountId}:role/${CrossAccountAccessRole}-readonly" + - !Sub "arn:${AWS::Partition}:iam::*:role/adf-automation-role" - Effect: Allow Action: - "secretsmanager:Get*" - Resource: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/adf/*" # Only allow CodeBuild access to secrets that start with /adf/* + Resource: !Sub "arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/adf/*" # Only allow CodeBuild access to secrets that start with /adf/* - Effect: Allow Action: - "events:PutPermission" Resource: - - !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default + - !Sub arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/default - Effect: Allow Action: - "events:PutRule" @@ -342,7 +342,7 @@ Resources: - "events:DeleteRule" - "events:DescribeRule" Resource: - - !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/${PipelinePrefix}* + - !Sub arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/${PipelinePrefix}* - Effect: Allow Action: - "cloudformation:CancelUpdateStack" @@ -360,7 +360,7 @@ Resources: - "cloudformation:UpdateStack" - "cloudformation:UpdateTerminationProtection" Resource: - - !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${PipelinePrefix}*/*" + - !Sub "arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${PipelinePrefix}*/*" - Effect: Allow Action: - "cloudformation:ValidateTemplate" @@ -394,14 +394,14 @@ Resources: - "sns:GetTopicAttributes" - "sns:TagResource" Resource: - - !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${PipelinePrefix}* + - !Sub arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${PipelinePrefix}* - Effect: Allow Action: - "codebuild:CreateProject" - "codebuild:DeleteProject" - "codebuild:UpdateProject" Resource: - - !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/adf-* + - !Sub arn:${AWS::Partition}:codebuild:${AWS::Region}:${AWS::AccountId}:project/adf-* - Effect: Allow Action: - "iam:CreateRole" @@ -413,7 +413,7 @@ Resources: - "iam:PassRole" - "iam:PutRolePolicy" Resource: - - !Sub arn:aws:iam::${AWS::AccountId}:role/* + - !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:role/* Condition: StringEquals: aws:PrincipalOrgID: !Ref OrganizationId @@ -421,7 +421,7 @@ Resources: Action: - "iam:PassRole" # https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_passrole.html Resource: - - arn:aws:iam::*:role/* # This role can pass to any other role in the organization. + - !Sub arn:${AWS::Partition}:iam::*:role/* # This role can pass to any other role in the organization. Condition: StringEquals: aws:PrincipalOrgID: !Ref OrganizationId @@ -439,8 +439,8 @@ Resources: - "codepipeline:TagResource" - "codepipeline:UpdatePipeline" Resource: - - !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:webhook:adf-webhook-* - - !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${PipelinePrefix}* + - !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:webhook:adf-webhook-* + - !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${PipelinePrefix}* - Effect: Allow Action: - "codestar-connections:GetConnection" @@ -566,7 +566,7 @@ Resources: - s3:Get* - s3:List* Resource: - - !Sub "arn:aws:s3:::${PipelineBucket}/adf-build/templates/*" + - !Sub "arn:${AWS::Partition}:s3:::${PipelineBucket}/adf-build/templates/*" - Effect: Allow Sid: "CloudFormation" Action: @@ -584,8 +584,8 @@ Resources: - "cloudformation:UpdateStack" - "cloudformation:UpdateTerminationProtection" Resource: - - !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/adf-codecommit-*/*" - - !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/adf-event-rule-${AWS::AccountId}-*/*" + - !Sub "arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/adf-codecommit-*/*" + - !Sub "arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/adf-event-rule-${AWS::AccountId}-*/*" - Effect: Allow Sid: "CodeCommit" Action: @@ -613,9 +613,9 @@ Resources: - "ssm:GetParameters" - "ssm:GetParameter" Resource: - - "arn:aws:ssm:*:*:parameter/bucket_name" - - "arn:aws:ssm:*:*:parameter/deployment_account_id" - - "arn:aws:ssm:*:*:parameter/kms_arn" + - !Sub "arn:${AWS::Partition}:ssm:*:*:parameter/bucket_name" + - !Sub "arn:${AWS::Partition}:ssm:*:*:parameter/deployment_account_id" + - !Sub "arn:${AWS::Partition}:ssm:*:*:parameter/kms_arn" Roles: - !Ref AdfAutomationRole CodeCommitRepository: @@ -624,12 +624,12 @@ Resources: RepositoryName: "aws-deployment-framework-pipelines" RepositoryDescription: !Sub "CodeCommit Repo for all pipelines in ${AWS::AccountId}" Triggers: - - Name: Email - DestinationArn: !Ref PipelineSNSTopic - Branches: - - master - Events: - - all + - Name: Email + DestinationArn: !Ref PipelineSNSTopic + Branches: + - master + Events: + - all CodeBuildProject: Type: AWS::CodeBuild::Project Properties: @@ -680,7 +680,7 @@ Resources: - cdk --version - chmod 755 adf-build/cdk/execute_pipeline_stacks.py adf-build/cdk/generate_pipeline_inputs.py adf-build/cdk/generate_pipeline_stacks.py adf-build/cdk/clean_pipelines.py - python adf-build/cdk/generate_pipeline_inputs.py - - cdk synth --app adf-build/cdk/generate_pipeline_stacks.py 1> /dev/null + - cdk synth --no-version-reporting --app adf-build/cdk/generate_pipeline_stacks.py 1> /dev/null - python adf-build/cdk/execute_pipeline_stacks.py post_build: commands: @@ -718,16 +718,16 @@ Resources: Actions: - Name: CreateOrUpdate ActionTypeId: - Category: Build - Owner: AWS - Version: "1" - Provider: CodeBuild + Category: Build + Owner: AWS + Version: "1" + Provider: CodeBuild OutputArtifacts: - Name: "aws-deployment-pipelines-build" InputArtifacts: - Name: "Source" Configuration: - ProjectName: !Ref CodeBuildProject + ProjectName: !Ref CodeBuildProject RunOrder: 1 PipelineSNSTopic: Type: AWS::SNS::Topic @@ -769,16 +769,16 @@ Resources: Id: !Sub "${AWS::StackName}" Version: "2012-10-17" Statement: - - Effect: Allow - Principal: - Service: - - codecommit.amazonaws.com - - events.amazonaws.com - - states.amazonaws.com - Action: sns:Publish - Resource: "*" + - Effect: Allow + Principal: + Service: + - codecommit.amazonaws.com + - events.amazonaws.com + - states.amazonaws.com + Action: sns:Publish + Resource: "*" Topics: - - !Ref PipelineSNSTopic + - !Ref PipelineSNSTopic CodePipelineRole: Type: AWS::IAM::Role Properties: @@ -869,8 +869,8 @@ Resources: Action: - sts:AssumeRole Resource: - - arn:aws:iam::*:role/adf-cloudformation-role - - arn:aws:iam::*:role/adf-codecommit-role + - !Sub arn:${AWS::Partition}:iam::*:role/adf-cloudformation-role + - !Sub arn:${AWS::Partition}:iam::*:role/adf-codecommit-role Condition: StringEquals: aws:PrincipalOrgID: !Ref OrganizationId @@ -884,8 +884,8 @@ Resources: - s3:ReplicateObject - s3:ReplicateTags Resource: - - !Sub arn:aws:s3:::${PipelineBucket} - - !Sub arn:aws:s3:::${PipelineBucket}/* + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket} + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket}/* - Effect: Allow Sid: "CodeCommit" Action: @@ -899,15 +899,15 @@ Resources: - Effect: Allow Sid: "PassRole" Action: - - 'iam:PassRole' - Resource: '*' + - "iam:PassRole" + Resource: "*" Condition: - StringEqualsIfExists: - 'iam:PassedToService': - - cloudformation.amazonaws.com - - elasticbeanstalk.amazonaws.com - - ec2.amazonaws.com - - ecs-tasks.amazonaws.com + StringEqualsIfExists: + "iam:PassedToService": + - cloudformation.amazonaws.com + - elasticbeanstalk.amazonaws.com + - ec2.amazonaws.com + - ecs-tasks.amazonaws.com - Effect: Allow Sid: "AllowCodeStarConnections" Action: @@ -936,8 +936,8 @@ Resources: StringEquals: aws:PrincipalOrgID: !Ref OrganizationId Resource: - - !Sub arn:aws:s3:::${PipelineBucket} - - !Sub arn:aws:s3:::${PipelineBucket}/* + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket} + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket}/* Principal: AWS: "*" SendSlackNotification: @@ -945,7 +945,7 @@ Resources: Properties: CodeUri: lambda_codebase/ Layers: - - !Ref LambdaLayerVersion + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - Send Slack Notification" FunctionName: SendSlackNotification Handler: slack.lambda_handler @@ -961,7 +961,7 @@ Resources: Properties: CodeUri: lambda_codebase/ Layers: - - !Ref LambdaLayerVersion + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - EnableCrossAccountAccess" MemorySize: 1024 Environment: @@ -979,7 +979,7 @@ Resources: Properties: CodeUri: lambda_codebase/ Layers: - - !Ref LambdaLayerVersion + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - CheckPipelineStatus" Environment: Variables: @@ -1046,18 +1046,18 @@ Resources: - Effect: Allow Action: - "secretsmanager:Get*" - Resource: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/adf/*" # Only allow Lambda access to get secrets that start with /adf/* + Resource: !Sub "arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/adf/*" # Only allow Lambda access to get secrets that start with /adf/* - Effect: "Allow" Action: - "s3:Get*" - "s3:Put*" - "s3:List*" Resource: - - !Sub arn:aws:s3:::${PipelineBucket} - - !Sub arn:aws:s3:::${PipelineBucket}/* + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket} + - !Sub arn:${AWS::Partition}:s3:::${PipelineBucket}/* - Effect: "Allow" Action: "logs:*" - Resource: "arn:aws:logs:*:*:*" + Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*" StatesExecutionRole: Type: "AWS::IAM::Role" Properties: @@ -1097,76 +1097,76 @@ Resources: Properties: StateMachineName: "EnableCrossAccountAccess" DefinitionString: !Sub |- - { - "Comment": "Enable Cross Account Access from Deployment Account", - "StartAt": "DetermineEvent", - "States": { - "DetermineEvent": { - "Type": "Choice", - "Choices": [{ - "Variable": "$.update_only", - "NumericEquals": 1, - "Next": "UpdateDeploymentPipelines" - }, - { - "Not": { - "Variable": "$.error", - "NumericEquals": 0 - }, - "Next": "NotifyFailure" - } - ], - "Default": "EnableCrossAccountAccess" - }, - "EnableCrossAccountAccess": { - "Type": "Task", - "Resource": "${EnableCrossAccountAccess.Arn}", - "Next": "UpdateDeploymentPipelines", - "TimeoutSeconds": 900 - }, - "UpdateDeploymentPipelines": { - "Type": "Task", - "Resource": "${CheckPipelineStatus.Arn}", - "TimeoutSeconds": 60, - "Next": "NeedToNotifySuccess?" - }, - "NeedToNotifySuccess?": { - "Type": "Choice", - "Choices": [{ + { + "Comment": "Enable Cross Account Access from Deployment Account", + "StartAt": "DetermineEvent", + "States": { + "DetermineEvent": { + "Type": "Choice", + "Choices": [{ "Variable": "$.update_only", "NumericEquals": 1, - "Next": "Success" - }], - "Default": "NotifySuccess" - }, - "Success": { - "Type": "Succeed" - }, - "Failure": { - "Type": "Fail" - }, - "NotifySuccess": { - "Type": "Task", - "Resource": "arn:aws:states:::sns:publish", - "Parameters": { - "TopicArn": "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${PipelineSNSTopic.TopicName}", - "Message.$": "$.message", - "Subject": "Success - AWS Deployment Framework Bootstrap" + "Next": "UpdateDeploymentPipelines" }, + { + "Not": { + "Variable": "$.error", + "NumericEquals": 0 + }, + "Next": "NotifyFailure" + } + ], + "Default": "EnableCrossAccountAccess" + }, + "EnableCrossAccountAccess": { + "Type": "Task", + "Resource": "${EnableCrossAccountAccess.Arn}", + "Next": "UpdateDeploymentPipelines", + "TimeoutSeconds": 900 + }, + "UpdateDeploymentPipelines": { + "Type": "Task", + "Resource": "${CheckPipelineStatus.Arn}", + "TimeoutSeconds": 60, + "Next": "NeedToNotifySuccess?" + }, + "NeedToNotifySuccess?": { + "Type": "Choice", + "Choices": [{ + "Variable": "$.update_only", + "NumericEquals": 1, "Next": "Success" + }], + "Default": "NotifySuccess" + }, + "Success": { + "Type": "Succeed" + }, + "Failure": { + "Type": "Fail" + }, + "NotifySuccess": { + "Type": "Task", + "Resource": "arn:${AWS::Partition}:states:::sns:publish", + "Parameters": { + "TopicArn": "arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${PipelineSNSTopic.TopicName}", + "Message.$": "$.message", + "Subject": "Success - AWS Deployment Framework Bootstrap" }, - "NotifyFailure": { - "Type": "Task", - "Resource": "arn:aws:states:::sns:publish", - "Parameters": { - "TopicArn": "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${PipelineSNSTopic.TopicName}", - "Message.$": "$.error", - "Subject": "Failure - AWS Deployment Framework Bootstrap" - }, - "Next": "Failure" - } + "Next": "Success" + }, + "NotifyFailure": { + "Type": "Task", + "Resource": "arn:${AWS::Partition}:states:::sns:publish", + "Parameters": { + "TopicArn": "arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${PipelineSNSTopic.TopicName}", + "Message.$": "$.error", + "Subject": "Failure - AWS Deployment Framework Bootstrap" + }, + "Next": "Failure" } } + } RoleArn: !GetAtt StatesExecutionRole.Arn InitialCommit: Type: Custom::InitialCommit @@ -1205,25 +1205,26 @@ Resources: Type: "String" Value: !GetAtt KMSKey.Arn PipelineCloudWatchEventRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Principal: - Service: - - events.amazonaws.com - Action: sts:AssumeRole - Path: / - Policies: - - PolicyName: adf-pipelines-execute-cwe - PolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Action: codepipeline:StartPipelineExecution - Resource: !Join [ '', [ 'arn:aws:codepipeline:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref CodePipeline ] ] + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - events.amazonaws.com + Action: sts:AssumeRole + Path: / + Policies: + - PolicyName: adf-pipelines-execute-cwe + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: codepipeline:StartPipelineExecution + Resource: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}" + PipelineCloudWatchEventRule: Type: AWS::Events::Rule Properties: @@ -1231,9 +1232,8 @@ Resources: source: - aws.codecommit detail-type: - - 'CodeCommit Repository State Change' - resources: - - !Join [ '', [ 'arn:aws:codecommit:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !GetAtt CodeCommitRepository.Name ] ] + - "CodeCommit Repository State Change" + resources: !Sub "arn:${AWS::Partition}:codecommit:${AWS::Region}:${AWS::AccountId}:${CodeCommitRepository.Name}" detail: event: - referenceCreated @@ -1243,10 +1243,10 @@ Resources: referenceName: - master Targets: - - Arn: - !Join [ '', [ 'arn:aws:codepipeline:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref CodePipeline ] ] + - Arn: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}" RoleArn: !GetAtt PipelineCloudWatchEventRole.Arn Id: adf-codepipeline-trigger-pipeline + Outputs: ADFVersionNumber: Value: !Ref ADFVersion diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/enable_cross_account_access.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/enable_cross_account_access.py index 42b8862ee..eeb58e1d6 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/enable_cross_account_access.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/enable_cross_account_access.py @@ -17,12 +17,15 @@ from parameter_store import ParameterStore from sts import STS from iam import IAM +from partition import get_partition KEY_ID = os.environ['KMS_KEY_ID'] S3_BUCKET = os.environ['S3_BUCKET_NAME'] +REGION_DEFAULT = os.getenv('AWS_REGION') LOGGER = configure_logger(__name__) + def update_iam(role, s3_bucket, kms_key_arn, role_policies): iam = IAM(role) iam.update_iam_roles( @@ -31,6 +34,7 @@ def update_iam(role, s3_bucket, kms_key_arn, role_policies): role_policies ) + def lambda_handler(event, _): target_role_policies = { 'adf-cloudformation-deployment-role': 'adf-cloudformation-deployment-role-policy-kms', @@ -44,6 +48,8 @@ def lambda_handler(event, _): } sts = STS() + partition = get_partition(REGION_DEFAULT) + parameter_store = ParameterStore( region=event.get('deployment_account_region'), role=boto3 @@ -59,10 +65,8 @@ def lambda_handler(event, _): for account_id in event.get('account_ids'): try: role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format( - account_id, - 'adf-cloudformation-deployment-role' - ), 'base_cfn_role' + f'arn:{partition}:iam::{account_id}:role/adf-cloudformation-deployment-role', + 'base_cfn_role' ) LOGGER.debug("Role has been assumed for %s", account_id) update_iam(role, s3_bucket, kms_key_arn, target_role_policies) diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/regional.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/regional.yml index d28909919..a39a0fc12 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/regional.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/regional.yml @@ -1,11 +1,11 @@ # // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. # // SPDX-License-Identifier: Apache-2.0 -AWSTemplateFormatVersion: '2010-09-09' +AWSTemplateFormatVersion: "2010-09-09" Description: ADF CloudFormation Template (Regional) for Deployment Account Parameters: OrganizationId: - Type : 'AWS::SSM::Parameter::Value' + Type: "AWS::SSM::Parameter::Value" Default: organization_id Resources: DeploymentFrameworkRegionalS3Bucket: @@ -39,8 +39,8 @@ Resources: StringEquals: aws:PrincipalOrgID: !Ref OrganizationId Resource: - - !Sub arn:aws:s3:::${DeploymentFrameworkRegionalS3Bucket} - - !Sub arn:aws:s3:::${DeploymentFrameworkRegionalS3Bucket}/* + - !Sub arn:${AWS::Partition}:s3:::${DeploymentFrameworkRegionalS3Bucket} + - !Sub arn:${AWS::Partition}:s3:::${DeploymentFrameworkRegionalS3Bucket}/* Principal: AWS: "*" DeploymentFrameworkRegionalKMSKey: @@ -55,7 +55,7 @@ Resources: - Sid: Allows admin of the key Effect: Allow Principal: - AWS: !Sub arn:aws:iam::${AWS::AccountId}:root + AWS: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root Action: - "kms:CancelKeyDeletion" - "kms:Create*" diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/example-global-iam.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/example-global-iam.yml index c3649bcfb..26706f9da 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/example-global-iam.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/example-global-iam.yml @@ -48,8 +48,8 @@ Resources: # Principal: # AWS: # # This would allow all codebuild projects to be able to assume this role - # # - !Sub arn:aws:iam::${DeploymentAccountId}:role/adf-codebuild-role - # - !Sub arn:aws:iam::${DeploymentAccountId}:role/my-custom-codebuild-role + # # - !Sub arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-codebuild-role + # - !Sub arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/my-custom-codebuild-role # # The above role would be created on the deployment account for the purpose deploying this custom resource via codebuild # Action: # - sts:AssumeRole @@ -68,4 +68,4 @@ Resources: # Resource: # - "*" # Roles: - # - !Ref MyExampleCustomRole \ No newline at end of file + # - !Ref MyExampleCustomRole diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/global.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/global.yml index 62d55ec71..522eb249c 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/global.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/global.yml @@ -29,7 +29,7 @@ Resources: Statement: - Effect: Allow Principal: - AWS: !Sub arn:aws:iam::${DeploymentAccountId}:role/adf-codepipeline-role + AWS: !Sub arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-codepipeline-role Action: - sts:AssumeRole - Effect: Allow @@ -63,8 +63,8 @@ Resources: - "s3:List*" - "s3:Put*" Resource: - - !Sub arn:aws:s3:::${DeploymentAccountBucketName} - - !Sub arn:aws:s3:::${DeploymentAccountBucketName}/* + - !Sub arn:${AWS::Partition}:s3:::${DeploymentAccountBucketName} + - !Sub arn:${AWS::Partition}:s3:::${DeploymentAccountBucketName}/* - Effect: Allow Action: - "kms:Decrypt" @@ -104,8 +104,8 @@ Resources: - s3:List* - s3:Put* Resource: - - !Sub arn:aws:s3:::${DeploymentAccountBucketName} - - !Sub arn:aws:s3:::${DeploymentAccountBucketName}/* + - !Sub arn:${AWS::Partition}:s3:::${DeploymentAccountBucketName} + - !Sub arn:${AWS::Partition}:s3:::${DeploymentAccountBucketName}/* - Effect: Allow Sid: "KMS" Action: @@ -127,8 +127,8 @@ Resources: - Effect: Allow Principal: AWS: - - !Sub arn:aws:iam::${DeploymentAccountId}:role/adf-codepipeline-role - - !Sub arn:aws:iam::${DeploymentAccountId}:role/adf-cloudformation-role + - !Sub arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-codepipeline-role + - !Sub arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-cloudformation-role Action: - sts:AssumeRole Path: / @@ -172,19 +172,19 @@ Resources: Sid: "AssumeRoleLambda" Principal: AWS: - - !Sub arn:aws:iam::${DeploymentAccountId}:role/adf-lambda-role + - !Sub arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-lambda-role Action: - sts:AssumeRole - Effect: Allow Sid: "AssumeRole" Principal: AWS: - - !Sub arn:aws:iam::${DeploymentAccountId}:role/adf-codepipeline-role + - !Sub arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-codepipeline-role Action: - sts:AssumeRole Condition: - ArnEquals: - 'aws:SourceArn': !Sub 'arn:aws:codepipeline:${AWS::Region}:${DeploymentAccountId}:*' + ArnEquals: + "aws:SourceArn": !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${DeploymentAccountId}:*" Path: / AdfAutomationRole: # This role is used by CodeBuild on the Deployment Account when creating new Codepipeline Pipelines. @@ -199,7 +199,7 @@ Resources: Sid: "AssumeRole" Principal: AWS: - - !Sub arn:aws:iam::${DeploymentAccountId}:role/adf-pipeline-provisioner-codebuild-role + - !Sub arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-pipeline-provisioner-codebuild-role Action: - sts:AssumeRole Path: / @@ -216,7 +216,7 @@ Resources: - s3:Get* - s3:List* Resource: - - !Sub "arn:aws:s3:::${DeploymentAccountBucketName}/adf-build/templates/*" + - !Sub "arn:${AWS::Partition}:s3:::${DeploymentAccountBucketName}/adf-build/templates/*" - Effect: Allow Sid: "CloudFormation" Action: @@ -234,8 +234,8 @@ Resources: - "cloudformation:UpdateStack" - "cloudformation:UpdateTerminationProtection" Resource: - - !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/adf-codecommit-*/*" - - !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/adf-event-rule-${AWS::AccountId}-*/*" + - !Sub "arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/adf-codecommit-*/*" + - !Sub "arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/adf-event-rule-${AWS::AccountId}-*/*" - Effect: Allow Sid: "CodeCommit" Action: @@ -263,9 +263,9 @@ Resources: - "ssm:GetParameters" - "ssm:GetParameter" Resource: - - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/bucket_name" - - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/deployment_account_id" - - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/kms_arn" + - !Sub "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/bucket_name" + - !Sub "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/deployment_account_id" + - !Sub "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/kms_arn" - Effect: Allow Sid: "IAM" Action: @@ -278,7 +278,7 @@ Resources: - "iam:PassRole" - "iam:PutRolePolicy" Resource: - - !Sub "arn:aws:iam::${AWS::AccountId}:role/adf-event-rule-${AWS::AccountId}-${DeploymentAccountId}-EventRole-*" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/adf-event-rule-${AWS::AccountId}-${DeploymentAccountId}-EventRole-*" Roles: - !Ref AdfAutomationRole ReadOnlyAutomationRole: @@ -295,7 +295,7 @@ Resources: Sid: "AssumeRole" Principal: AWS: - - !Sub arn:aws:iam::${DeploymentAccountId}:role/adf-codebuild-role + - !Sub arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-codebuild-role Action: - sts:AssumeRole Path: / diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/global.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/global.yml index 76a0cb9b5..ae354d157 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/global.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/global.yml @@ -23,9 +23,9 @@ Resources: - Effect: Allow Principal: AWS: - - !Sub "arn:aws:iam::${AWS::AccountId}:role/adf-codebuild-role" - - !Sub "arn:aws:iam::${DeploymentAccountId}:role/adf-codebuild-role" - - !Sub "arn:aws:iam::${DeploymentAccountId}:role/adf-pipeline-provisioner-codebuild-role" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/adf-codebuild-role" + - !Sub "arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-codebuild-role" + - !Sub "arn:${AWS::Partition}:iam::${DeploymentAccountId}:role/adf-pipeline-provisioner-codebuild-role" Action: - sts:AssumeRole Path: / @@ -57,7 +57,7 @@ Resources: - Effect: Allow Principal: AWS: - - !Sub "arn:aws:iam::${AWS::AccountId}:root" # To update the master account + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" # To update the master account Action: - sts:AssumeRole Path: / @@ -85,13 +85,13 @@ Resources: - cloudformation:UpdateStack - cloudformation:UpdateTerminationProtection Resource: - - !Sub "arn:aws:cloudformation:*:${AWS::AccountId}:stack/*" + - !Sub "arn:${AWS::Partition}:cloudformation:*:${AWS::AccountId}:stack/*" - Effect: Allow Action: - cloudformation:ValidateTemplate - ssm:PutParameter - - ssm:GetParameters - - ssm:GetParameter + - ssm:GetParameters + - ssm:GetParameter Resource: - "*" - Effect: Allow @@ -106,12 +106,11 @@ Resources: - iam:PutRolePolicy - iam:DeleteRolePolicy Resource: - - !Sub "arn:aws:iam::${AWS::AccountId}:role/adf-cloudformation-role" - - !Sub "arn:aws:iam::${AWS::AccountId}:role/adf-cloudformation-deployment-role" - - !Sub "arn:aws:iam::${AWS::AccountId}:role/adf-codecommit-role" - - !Sub "arn:aws:iam::${AWS::AccountId}:role/adf-automation-role" - - !Sub "arn:aws:iam::${AWS::AccountId}:role/adf-readonly-automation-role" - - !Sub "arn:aws:iam::${AWS::AccountId}:role/${CrossAccountAccessRole}" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/adf-cloudformation-role" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/adf-cloudformation-deployment-role" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/adf-codecommit-role" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/adf-automation-role" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/adf-readonly-automation-role" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${CrossAccountAccessRole}" Roles: - !Ref OrganizationsRole - diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/main.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/main.py index f42bdfdd9..0f5bf1d6d 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/main.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/main.py @@ -20,12 +20,14 @@ from errors import GenericAccountConfigureError, ParameterNotFoundError from sts import STS from s3 import S3 +from partition import get_partition from config import Config from organization_policy import OrganizationPolicy S3_BUCKET_NAME = os.environ["S3_BUCKET"] REGION_DEFAULT = os.environ["AWS_REGION"] +PARTITION = get_partition(REGION_DEFAULT) ACCOUNT_ID = os.environ["MASTER_ACCOUNT_ID"] ADF_VERSION = os.environ["ADF_VERSION"] ADF_LOG_LEVEL = os.environ["ADF_LOG_LEVEL"] @@ -55,9 +57,8 @@ def ensure_generic_account_can_be_setup(sts, config, account_id): """ try: return sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format( - account_id, - config.cross_account_access_role), + f'arn:{PARTITION}:iam::{account_id}:role/' + f'{config.cross_account_access_role}', 'base_update' ) except ClientError as error: @@ -105,9 +106,8 @@ def prepare_deployment_account(sts, deployment_account_id, config): to access the deployment account """ deployment_account_role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format( - deployment_account_id, - config.cross_account_access_role), + f'arn:{PARTITION}:iam::{deployment_account_id}:role/' + f'{config.cross_account_access_role}', 'master' ) for region in list( @@ -141,8 +141,10 @@ def prepare_deployment_account(sts, deployment_account_id, config): ) if '@' not in config.notification_endpoint: config.notification_channel = config.notification_endpoint - config.notification_endpoint = "arn:aws:lambda:{0}:{1}:function:SendSlackNotification".format( - config.deployment_account_region, deployment_account_id) + config.notification_endpoint = ( + f"arn:{PARTITION}:lambda:{config.deployment_account_region}:" + f"{deployment_account_id}:function:SendSlackNotification" + ) for item in ( 'cross_account_access_role', 'notification_type', diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/organization_policy.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/organization_policy.py index 7748675bd..4c7343cf8 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/organization_policy.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/organization_policy.py @@ -6,11 +6,14 @@ import glob import ast +import os + from organizations import Organizations from errors import ParameterNotFoundError from logger import configure_logger LOGGER = configure_logger(__name__) +REGION_DEFAULT = os.getenv('AWS_REGION') class OrganizationPolicy: @@ -42,6 +45,15 @@ def _trim_scp_file_name(policy): def _trim_tagging_policy_file_name(policy): return policy[1:][:-19] if policy[1:][:-19] == '/' else policy[2:][:-20] + @staticmethod + def _is_govcloud(region: str) -> bool: + """Evaluates the region to determine if it is part of GovCloud. + + :param region: a region (us-east-1, us-gov-west-1) + :return: Returns True if the region is GovCloud, False otherwise. + """ + return region.startswith('us-gov') + @staticmethod def set_scp_attachment( access_identifer, @@ -97,7 +109,16 @@ def apply(self, organizations, parameter_store, config): # pylint: disable=R091 'Determining if Organization Policy changes are required. (Tagging or Service Controls)') organization_mapping = organizations.get_organization_map( {'/': organizations.get_ou_root_id()}) - for policy in ['scp', 'tagging-policy']: + + supported_policies = [ + 'scp', + 'tagging-policy' + ] + + if self._is_govcloud(REGION_DEFAULT): + supported_policies = ['scp'] + + for policy in supported_policies: _type = 'SERVICE_CONTROL_POLICY' if policy == 'scp' else 'TAG_POLICY' organizations.enable_organization_policies(_type) _policies = OrganizationPolicy._find_all(policy) diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/provisioner/main.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/provisioner/main.py index 415d4f996..54c7c870b 100755 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/provisioner/main.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/provisioner/main.py @@ -13,9 +13,11 @@ from organizations import Organizations from logger import configure_logger from parameter_store import ParameterStore +from partition import get_partition from sts import STS from src import read_config_files, delete_default_vpc, Support +REGION_DEFAULT = os.getenv('AWS_REGION') LOGGER = configure_logger(__name__) ACCOUNTS_FOLDER = os.path.abspath(os.path.join( @@ -26,14 +28,18 @@ def main(): accounts = read_config_files(ACCOUNTS_FOLDER) if not bool(accounts): LOGGER.info( - f"Found {len(accounts)} account(s) in configuration file(s). Account provisioning will not continue.") + f"Found {len(accounts)} account(s) in configuration file(s). " + f"Account provisioning will not continue." + ) return LOGGER.info(f"Found {len(accounts)} account(s) in configuration file(s).") organizations = Organizations(boto3) support = Support(boto3) all_accounts = organizations.get_accounts() parameter_store = ParameterStore( - os.environ.get('AWS_REGION', 'us-east-1'), boto3) + os.environ.get('AWS_REGION', 'us-east-1'), + boto3 + ) adf_role_name = parameter_store.fetch_parameter( 'cross_account_access_role') for account in accounts: @@ -58,15 +64,16 @@ def create_or_update_account(org_session, support_session, account, adf_role_nam support_session.set_support_level_for_account(account, account_id) sts = STS() + partition = get_partition(REGION_DEFAULT) role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format( - account_id, - adf_role_name - ), 'adf_account_provisioning' + f'arn:{partition}:iam::{account_id}:role/{adf_role_name}', + 'adf_account_provisioning' ) LOGGER.info( - f'Ensuring account {account_id} (alias {account.alias}) is in OU {account.ou_path}') + f'Ensuring account {account_id} (alias {account.alias}) is in ' + f'OU {account.ou_path}' + ) org_session.move_account(account_id, account.ou_path) if account.delete_default_vpc: ec2_client = role.client('ec2') diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_codebuild.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_codebuild.py index 3fbf85dce..1729425c1 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_codebuild.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_codebuild.py @@ -22,19 +22,21 @@ DEFAULT_BUILD_SPEC_FILENAME = 'buildspec.yml' DEFAULT_DEPLOY_SPEC_FILENAME = 'deployspec.yml' + class CodeBuild(core.Construct): # pylint: disable=no-value-for-parameter def __init__(self, scope: core.Construct, id: str, shared_modules_bucket: str, deployment_region_kms: str, map_params: dict, target, **kwargs): #pylint: disable=W0622 super().__init__(scope, id, **kwargs) - ADF_DEFAULT_BUILD_ROLE = 'arn:aws:iam::{0}:role/adf-codebuild-role'.format(ADF_DEPLOYMENT_ACCOUNT_ID) + stack = core.Stack.of(self) + + ADF_DEFAULT_BUILD_ROLE = f'arn:{stack.partition}:iam::{ADF_DEPLOYMENT_ACCOUNT_ID}:role/adf-codebuild-role' ADF_DEFAULT_BUILD_TIMEOUT = 20 + # if CodeBuild is being used as a deployment action we want to allow target specific values. if target: - _build_role = 'arn:aws:iam::{0}:role/{1}'.format( - ADF_DEPLOYMENT_ACCOUNT_ID, - target.get('properties', {}).get('role') - ) if target.get('properties', {}).get('role') else ADF_DEFAULT_BUILD_ROLE + _role_name = target.get('properties', {}).get('role') + _build_role = f'arn:{stack.partition}:iam::{ADF_DEPLOYMENT_ACCOUNT_ID}:role/{_role_name}' if _role_name else ADF_DEFAULT_BUILD_ROLE _timeout = target.get('properties', {}).get('timeout') or map_params['default_providers']['deploy'].get('properties', {}).get('timeout') or ADF_DEFAULT_BUILD_TIMEOUT _env = _codebuild.BuildEnvironment( build_image=CodeBuild.determine_build_image(scope, target, map_params), @@ -74,10 +76,9 @@ def __init__(self, scope: core.Construct, id: str, shared_modules_bucket: str, d action_name="{0}".format(id) ).config else: - _build_role = 'arn:aws:iam::{0}:role/{1}'.format( - ADF_DEPLOYMENT_ACCOUNT_ID, - map_params['default_providers']['build'].get('properties', {}).get('role') - ) if map_params['default_providers']['build'].get('properties', {}).get('role') else ADF_DEFAULT_BUILD_ROLE + _role_name = map_params['default_providers']['build'].get( + 'properties', {}).get('role') + _build_role = f'arn:{stack.partition}:iam::{ADF_DEPLOYMENT_ACCOUNT_ID}:role/{_role_name}' if _role_name else ADF_DEFAULT_BUILD_ROLE _timeout = map_params['default_providers']['build'].get('properties', {}).get('timeout') or ADF_DEFAULT_BUILD_TIMEOUT _env = _codebuild.BuildEnvironment( build_image=CodeBuild.determine_build_image(scope, target, map_params), @@ -85,8 +86,8 @@ def __init__(self, scope: core.Construct, id: str, shared_modules_bucket: str, d environment_variables=CodeBuild.generate_build_env_variables(_codebuild, shared_modules_bucket, map_params), privileged=map_params['default_providers']['build'].get('properties', {}).get('privileged', False) ) - if map_params['default_providers']['build'].get('properties', {}).get('role'): - ADF_DEFAULT_BUILD_ROLE = 'arn:aws:iam::{0}:role/{1}'.format(ADF_DEPLOYMENT_ACCOUNT_ID, map_params['default_providers']['build'].get('properties', {}).get('role')) + if _role_name: + ADF_DEFAULT_BUILD_ROLE = f'arn:{stack.partition}:iam::{ADF_DEPLOYMENT_ACCOUNT_ID}:role/{_role_name}' build_spec = CodeBuild.determine_build_spec( id, map_params['default_providers']['build'].get('properties', {}) diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_codepipeline.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_codepipeline.py index cf1eb739a..549ebe943 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_codepipeline.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_codepipeline.py @@ -24,6 +24,23 @@ LOGGER = configure_logger(__name__) + +def get_partition(region_name: str) -> str: + """Given the region, this function will return the appropriate partition. + + :param region_name: The name of the region (us-east-1, us-gov-west-1) + :return: Returns the partition name as a string. + """ + + if region_name.startswith('us-gov'): + return 'aws-us-gov' + + return 'aws' + + +ADF_DEPLOYMENT_PARTITION = get_partition(ADF_DEPLOYMENT_REGION) + + class Action: _version = "1" @@ -53,7 +70,7 @@ def _generate_role_arn(self): specific_role = self.target.get('properties', {}).get('role') or default_provider.get('properties', {}).get('role') if specific_role: account_id = self.account_id if self.provider == 'CodeBuild' else self.target['id'] - return 'arn:aws:iam::{0}:role/{1}'.format(account_id, specific_role) + return f'arn:{ADF_DEPLOYMENT_PARTITION}:iam::{account_id}:role/{specific_role}' return None def _generate_configuration(self): #pylint: disable=R0912, R0911, R0915 @@ -170,7 +187,7 @@ def _generate_configuration(self): #pylint: disable=R0912, R0911, R0915 region=self.region, ), "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", - "RoleArn": "arn:aws:iam::{0}:role/adf-cloudformation-deployment-role".format(self.target['id']) if not self.role_arn else self.role_arn + "RoleArn": "arn:{0}:iam::{1}:role/adf-cloudformation-deployment-role".format(ADF_DEPLOYMENT_PARTITION, self.target['id']) if not self.role_arn else self.role_arn } if self.map_params.get('default_providers', {}).get('build', {}).get('properties', {}).get('environment_variables', {}).get('CONTAINS_TRANSFORM'): _props["TemplatePath"] = "{input_artifact}::{path_prefix}template_{region}.yml".format( @@ -227,7 +244,7 @@ def _generate_configuration(self): #pylint: disable=R0912, R0911, R0915 'properties', {}).get( 'product_id') or self.map_params['default_providers']['deploy'].get( 'properties', {}).get( - 'product_id') # product_id is required for Service Catalog, meaning the product must already exist. + 'product_id') # product_id is required for Service Catalog, meaning the product must already exist. } if self.provider == "CodeDeploy": return { @@ -257,9 +274,11 @@ def _generate_configuration(self): #pylint: disable=R0912, R0911, R0915 } raise Exception("{0} is not a valid provider".format(self.provider)) - def _generate_codepipeline_access_role(self): #pylint: disable=R0911 + def _generate_codepipeline_access_role(self): # pylint: disable=R0911 + account_id = self.map_params['default_providers']['source']['properties']['account_id'] + if self.provider == "CodeCommit": - return "arn:aws:iam::{0}:role/adf-codecommit-role".format(self.map_params['default_providers']['source']['properties']['account_id']) + return f"arn:{ADF_DEPLOYMENT_PARTITION}:iam::{account_id}:role/adf-codecommit-role" if self.provider == "GitHub": return None if self.provider == "CodeStarSourceConnection": @@ -268,21 +287,21 @@ def _generate_codepipeline_access_role(self): #pylint: disable=R0911 return None if self.provider == "S3" and self.category == "Source": # This could be changed to use a new role that is bootstrapped, ideally we rename adf-cloudformation-role to a generic deployment role name - return "arn:aws:iam::{0}:role/adf-codecommit-role".format(self.map_params['default_providers']['source']['properties']['account_id']) + return f"arn:{ADF_DEPLOYMENT_PARTITION}:iam::{account_id}:role/adf-codecommit-role" if self.provider == "S3" and self.category == "Deploy": # This could be changed to use a new role that is bootstrapped, ideally we rename adf-cloudformation-role to a generic deployment role name - return "arn:aws:iam::{0}:role/adf-cloudformation-role".format(self.target['id']) + return f"arn:{ADF_DEPLOYMENT_PARTITION}:iam::{self.target['id']}:role/adf-cloudformation-role" if self.provider == "ServiceCatalog": # This could be changed to use a new role that is bootstrapped, ideally we rename adf-cloudformation-role to a generic deployment role name - return "arn:aws:iam::{0}:role/adf-cloudformation-role".format(self.target['id']) + return f"arn:{ADF_DEPLOYMENT_PARTITION}:iam::{self.target['id']}:role/adf-cloudformation-role" if self.provider == "CodeDeploy": # This could be changed to use a new role that is bootstrapped, ideally we rename adf-cloudformation-role to a generic deployment role name - return "arn:aws:iam::{0}:role/adf-cloudformation-role".format(self.target['id']) + return f"arn:{ADF_DEPLOYMENT_PARTITION}:iam::{self.target['id']}:role/adf-cloudformation-role" if self.provider == "Lambda": # This could be changed to use a new role that is bootstrapped, ideally we rename adf-cloudformation-role to a generic deployment role name return None if self.provider == "CloudFormation": - return "arn:aws:iam::{0}:role/adf-cloudformation-role".format(self.target['id']) + return f"arn:{ADF_DEPLOYMENT_PARTITION}:iam::{self.target['id']}:role/adf-cloudformation-role" if self.provider == "Manual": return None raise Exception('Invalid Provider {0}'.format(self.provider)) @@ -413,13 +432,11 @@ def __init__(self, scope: core.Construct, id: str, map_params: dict, ssm_params: **_pipeline_args ) adf_events.Events(self, 'events', { - "pipeline": 'arn:aws:codepipeline:{0}:{1}:{2}'.format( - ADF_DEPLOYMENT_REGION, - ADF_DEPLOYMENT_ACCOUNT_ID, - "{0}{1}".format( - os.environ.get( - "ADF_PIPELINE_PREFIX"), - map_params['name'])), + "pipeline": ( + f'arn:{ADF_DEPLOYMENT_PARTITION}:codepipeline:{ADF_DEPLOYMENT_REGION}:' + f'{ADF_DEPLOYMENT_ACCOUNT_ID}:' + f'{os.getenv("ADF_PIPELINE_PREFIX")}{map_params["name"]}' + ), "topic_arn": map_params.get('topic_arn'), "name": map_params['name'], "completion_trigger": map_params.get('completion_trigger'), diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_events.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_events.py index 76092e452..63702d0e8 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_events.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_events.py @@ -14,15 +14,18 @@ core ) + ADF_DEPLOYMENT_REGION = os.environ["AWS_REGION"] ADF_DEPLOYMENT_ACCOUNT_ID = os.environ["ACCOUNT_ID"] ADF_DEFAULT_BUILD_TIMEOUT = 20 ADF_PIPELINE_PREFIX = os.environ.get("ADF_PIPELINE_PREFIX", "") + class Events(core.Construct): - def __init__(self, scope: core.Construct, id: str, params: dict, **kwargs): #pylint: disable=W0622 + def __init__(self, scope: core.Construct, id: str, params: dict, **kwargs): # pylint: disable=W0622 super().__init__(scope, id, **kwargs) # pylint: disable=no-value-for-parameter + stack = core.Stack.of(self) _pipeline = _codepipeline.Pipeline.from_pipeline_arn(self, 'pipeline', params["pipeline"]) _source_account = params.get('source', {}).get('account_id') _provider = params.get('source', {}).get('provider') @@ -32,14 +35,19 @@ def __init__(self, scope: core.Construct, id: str, params: dict, **kwargs): #pyl and params.get('source', {}).get('trigger_on_changes') and not params.get('source', {}).get('poll_for_changes') ) + + name = params.get('name') + account_id = params['source']['account_id'] + repo_name = params['source']['repo_name'] + if _add_trigger_on_changes: _event = _events.Rule( self, - 'trigger_{0}'.format(params["name"]), - description="Triggers {0} on changes in source CodeCommit repository".format(params["name"]), + 'trigger_{0}'.format(name), + description=f'Triggers {name} on changes in source CodeCommit repository', event_pattern=_events.EventPattern( resources=[ - "arn:aws:codecommit:{0}:{1}:{2}".format(ADF_DEPLOYMENT_REGION, params['source']['account_id'], params['source']['repo_name']) + f'arn:{stack.partition}:codecommit:{ADF_DEPLOYMENT_REGION}:{account_id}:{repo_name}' ], source=["aws.codecommit"], detail_type=[ @@ -69,8 +77,8 @@ def __init__(self, scope: core.Construct, id: str, params: dict, **kwargs): #pyl _topic = _sns.Topic.from_topic_arn(self, 'topic_arn', params["topic_arn"]) _event = _events.Rule( self, - 'pipeline_state_{0}'.format(params["name"]), - description="{0} | Trigger notifications based on pipeline state changes".format(params["name"]), + 'pipeline_state_{0}'.format(name), + description="{0} | Trigger notifications based on pipeline state changes".format(name), enabled=True, event_pattern=_events.EventPattern( detail={ @@ -80,7 +88,7 @@ def __init__(self, scope: core.Construct, id: str, params: dict, **kwargs): #pyl "SUCCEEDED" ], "pipeline": [ - "{0}{1}".format(ADF_PIPELINE_PREFIX, params["name"]) + "{0}{1}".format(ADF_PIPELINE_PREFIX, name) ] }, detail_type=[ @@ -94,7 +102,7 @@ def __init__(self, scope: core.Construct, id: str, params: dict, **kwargs): #pyl topic=_topic, message=_events.RuleTargetInput.from_text( "The pipeline {0} from account {1} has {2} at {3}.".format( - _events.EventField.from_path('$.detail.pipeline'), # Need to parse and get the pipeline: "$.detail.pipeline" state: "$.detail.state" + _events.EventField.from_path('$.detail.pipeline'), # Need to parse and get the pipeline: "$.detail.pipeline" state: "$.detail.state" _events.EventField.account, _events.EventField.from_path('$.detail.state'), _events.EventField.time @@ -116,7 +124,7 @@ def __init__(self, scope: core.Construct, id: str, params: dict, **kwargs): #pyl "SUCCEEDED" ], "pipeline": [ - "{0}{1}".format(ADF_PIPELINE_PREFIX, params["name"]) + "{0}{1}".format(ADF_PIPELINE_PREFIX, name) ] }, detail_type=[ @@ -129,10 +137,10 @@ def __init__(self, scope: core.Construct, id: str, params: dict, **kwargs): #pyl _completion_pipeline = _codepipeline.Pipeline.from_pipeline_arn( self, 'pipeline-{0}'.format(index), - "arn:aws:codepipeline:{0}:{1}:{2}".format(ADF_DEPLOYMENT_REGION, ADF_DEPLOYMENT_ACCOUNT_ID, "{0}{1}".format( - ADF_PIPELINE_PREFIX, - pipeline - ))) + f'arn:{stack.partition}:codepipeline:' + f'{ADF_DEPLOYMENT_REGION}:{ADF_DEPLOYMENT_ACCOUNT_ID}:' + f'{ADF_PIPELINE_PREFIX}{pipeline}' + ) _event.add_target( _targets.CodePipeline( pipeline=_completion_pipeline diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_notifications.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_notifications.py index b8d1ff939..ce44dfb7d 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_notifications.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_constructs/adf_notifications.py @@ -19,18 +19,18 @@ LOGGER = configure_logger(__name__) + class Notifications(core.Construct): def __init__(self, scope: core.Construct, id: str, map_params: dict, **kwargs): #pylint: disable=W0622 super().__init__(scope, id, **kwargs) LOGGER.debug('Notification configuration required for %s', map_params['name']) + stack = core.Stack.of(self) # pylint: disable=no-value-for-parameter _slack_func = _lambda.Function.from_function_arn( self, 'slack_lambda_function', - 'arn:aws:lambda:{0}:{1}:function:SendSlackNotification'.format( - ADF_DEPLOYMENT_REGION, - ADF_DEPLOYMENT_ACCOUNT_ID - ) + f'arn:{stack.partition}:lambda:{ADF_DEPLOYMENT_REGION}:' + f'{ADF_DEPLOYMENT_ACCOUNT_ID}:function:SendSlackNotification' ) _topic = _sns.Topic(self, 'PipelineTopic') _statement = _iam.PolicyStatement( diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_stacks/tests/test_pipeline_creation.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_stacks/tests/test_pipeline_creation.py index fba72ecb6..aa080fc43 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_stacks/tests/test_pipeline_creation.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/cdk_stacks/tests/test_pipeline_creation.py @@ -44,7 +44,7 @@ def test_pipeline_generation_works_when_no_type_specified(mock): def test_pipeline_creation_outputs_as_expected_when_source_is_s3_and_build_is_codebuild(): region_name = "eu-central-1" - acount_id = "123456789012" + account_id = "123456789012" stack_input = { "input": {"params": {}, "default_providers": {}, "regions": {}}, @@ -64,7 +64,7 @@ def test_pipeline_creation_outputs_as_expected_when_source_is_s3_and_build_is_co stack_input["ssm_params"][region_name] = { "modules": "fake-bucket-name", - "kms": f"arn:aws:kms:{region_name}:{acount_id}:key/my-unique-kms-key-id", + "kms": f"arn:aws:kms:{region_name}:{account_id}:key/my-unique-kms-key-id", } app = core.App() PipelineStack(app, stack_input) diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/generate_pipeline_inputs.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/generate_pipeline_inputs.py index 18acbaa25..226a11a60 100755 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/generate_pipeline_inputs.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/cdk/generate_pipeline_inputs.py @@ -17,6 +17,7 @@ from logger import configure_logger from organizations import Organizations from parameter_store import ParameterStore +from partition import get_partition from pipeline import Pipeline from repo import Repo from rule import Rule @@ -152,11 +153,12 @@ def main(): ADF_PIPELINE_PREFIX ) sts = STS() + partition = get_partition(DEPLOYMENT_ACCOUNT_REGION) + cross_account_access_role = parameter_store.fetch_parameter('cross_account_access_role') role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}-readonly'.format( - MASTER_ACCOUNT_ID, - parameter_store.fetch_parameter('cross_account_access_role') - ), 'pipeline' + f'arn:{partition}:iam::{MASTER_ACCOUNT_ID}:role/' + f'{cross_account_access_role}-readonly', + 'pipeline' ) organizations = Organizations(role) ensure_event_bus_status(ORGANIZATION_ID) diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/retrieve_organization_accounts.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/retrieve_organization_accounts.py index 6d0b3fc08..45c1cd06b 100755 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/retrieve_organization_accounts.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/retrieve_organization_accounts.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- + """ retrieve_organization_accounts.py @@ -64,6 +65,7 @@ retrieve_organization_accounts.py -v -f Id -f Email -o src/lambda/dat.json """ + import sys import logging import json @@ -148,6 +150,19 @@ def main(): return 0 +def _get_partition(region_name: str) -> str: + """Given the region, this function will return the appropriate partition. + + :param region_name: The name of the region (us-east-1, us-gov-west-1) + :return: Returns the partition name as a string. + """ + + if region_name.startswith('us-gov'): + return 'aws-us-gov' + + return 'aws' + + def _get_billing_account_id(): """ Retrieve the Billing/Root AWS Account Id of the organization. @@ -245,11 +260,15 @@ def _request_sts_credentials(billing_account_id, options): required to use the STS role. """ try: - sts_client = boto3.client('sts') - role_arn = "arn:aws:iam::{billing_account_id}:role/{role_name}".format( - billing_account_id=billing_account_id, - role_name=options['--role-name'], - ) + + # Setup Session + session = boto3.session.Session() + region_name = session.region_name + partition = _get_partition(region_name) + sts_client = session.client('sts') + + role_name = options['--role-name'] + role_arn = f'arn:{partition}:iam::{billing_account_id}:role/{role_name}' response = sts_client.assume_role( RoleArn=role_arn, RoleSessionName=options['--session-name'], diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/iam.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/iam.py index 190696257..091374af8 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/iam.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/iam.py @@ -5,13 +5,18 @@ """ import json +import os + from logger import configure_logger +from partition import get_partition LOGGER = configure_logger(__name__) +REGION_DEFAULT = os.getenv('AWS_REGION') +PARTITION = get_partition(REGION_DEFAULT) + class IAM: - """Class used for modeling IAM - """ + """Class used for modeling IAM.""" def __init__(self, role): self.client = role.client('iam') @@ -70,13 +75,10 @@ def _update_iam_policy_bucket(self, bucket_name): for statement in _policy.get('Statement', None): if statement['Sid'] == 'S3': LOGGER.debug('calling _update_iam_policy_bucket for bucket_name %s', bucket_name) - if "arn:aws:s3:::{0}".format( - bucket_name) not in statement['Resource']: + if f"arn:{PARTITION}:s3:::{bucket_name}" not in statement['Resource']: LOGGER.info('Updating Role %s to access %s', self.role_name, bucket_name) - statement['Resource'].append( - "arn:aws:s3:::{0}".format(bucket_name)) - statement['Resource'].append( - "arn:aws:s3:::{0}/*".format(bucket_name)) + statement['Resource'].append(f"arn:{PARTITION}:s3:::{bucket_name}") + statement['Resource'].append(f"arn:{PARTITION}:s3:::{bucket_name}/*") self._set_policy(_policy) diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/organizations.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/organizations.py index 17aab3219..d45860247 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/organizations.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/organizations.py @@ -5,16 +5,21 @@ """ import json +import os + from time import sleep from botocore.config import Config from botocore.exceptions import ClientError + from errors import RootOUIDError from logger import configure_logger from paginator import paginator LOGGER = configure_logger(__name__) +REGION_DEFAULT = os.getenv('AWS_REGION') + -class Organizations: # pylint: disable=R0904 +class Organizations: # pylint: disable=R0904 """Class used for modeling Organizations """ @@ -23,11 +28,13 @@ class Organizations: # pylint: disable=R0904 def __init__(self, role, account_id=None): self.client = role.client( 'organizations', - config=Organizations._config) + config=Organizations._config + ) self.tags_client = role.client( 'resourcegroupstaggingapi', - region_name='us-east-1', - config=Organizations._config) + region_name=REGION_DEFAULT, + config=Organizations._config + ) self.account_id = account_id self.account_ids = [] self.root_id = None @@ -39,7 +46,7 @@ def get_parent_info(self): "ou_parent_type": response.get('Type') } - def enable_organization_policies(self, policy_type='SERVICE_CONTROL_POLICY'): # or 'TAG_POLICY' + def enable_organization_policies(self, policy_type='SERVICE_CONTROL_POLICY'): # or 'TAG_POLICY' try: self.client.enable_policy_type( RootId=self.get_ou_root_id(), diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/partition.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/partition.py new file mode 100644 index 000000000..309c5e02c --- /dev/null +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/partition.py @@ -0,0 +1,23 @@ +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 + +"""Partition. + +A partition is a group of AWS Regions. This module provides a helper function +to help determine the proper partition given a region name. For more details +on partitions, see: +https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces +""" + + +def get_partition(region_name: str) -> str: + """Given the region, this function will return the appropriate partition. + + :param region_name: The name of the region (us-east-1, us-gov-west-1) + :return: Returns the partition name as a string. + """ + + if region_name.startswith('us-gov'): + return 'aws-us-gov' + + return 'aws' diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/rule.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/rule.py index c4d875584..5333c139f 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/rule.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/rule.py @@ -10,6 +10,7 @@ from cloudformation import CloudFormation from s3 import S3 from sts import STS +from partition import get_partition from logger import configure_logger LOGGER = configure_logger(__name__) @@ -24,14 +25,16 @@ S3_BUCKET_NAME ) + class Rule: def __init__(self, source_account_id): self.source_account_id = source_account_id self.stack_name = 'adf-event-rule-{0}-{1}'.format(source_account_id, DEPLOYMENT_ACCOUNT_ID) + self.partition = get_partition(DEPLOYMENT_ACCOUNT_REGION) # Requirement adf-automation-role to exist on target self.role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/adf-automation-role'.format(source_account_id), - 'create_rule_{0}'.format(source_account_id) + f'arn:{self.partition}:iam::{source_account_id}:role/adf-automation-role', + f'create_rule_{source_account_id}' ) def create_update(self): diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/stepfunctions.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/stepfunctions.py index 5222653f1..a26f163ca 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/stepfunctions.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/stepfunctions.py @@ -7,7 +7,7 @@ import json from time import sleep from logger import configure_logger - +from partition import get_partition LOGGER = configure_logger(__name__) @@ -52,10 +52,13 @@ def _start_statemachine(self): """Executes the Update Cross Account IAM StepFunction in the Deployment Account """ + partition = get_partition(self.deployment_account_region) + self.execution_arn = self.client.start_execution( - stateMachineArn="arn:aws:states:{0}:{1}:stateMachine:EnableCrossAccountAccess".format( - self.deployment_account_region, - self.deployment_account_id), + stateMachineArn=( + f"arn:{partition}:states:{self.deployment_account_region}:" + f"{self.deployment_account_id}:stateMachine:EnableCrossAccountAccess" + ), input=json.dumps({ "deployment_account_region": self.deployment_account_region, "deployment_account_id": self.deployment_account_id, diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/test_partition.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/test_partition.py new file mode 100644 index 000000000..1ee0d82e4 --- /dev/null +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/tests/test_partition.py @@ -0,0 +1,30 @@ +"""Tests for partition.py + +Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" + +import pytest + +from partition import get_partition + +_us_commercial_regions = [ + 'us-east-1', + 'us-west-1', + 'us-west-2' +] + +_govcloud_regions = [ + 'us-gov-west-1', + 'us-gov-east-1' +] + + +@pytest.mark.parametrize('region', _govcloud_regions) +def test_partition_govcloud_regions(region): + assert get_partition(region) == 'aws-us-gov' + + +@pytest.mark.parametrize('region', _us_commercial_regions) +def test_partition_us_commercial_regions(region): + assert get_partition(region) == 'aws' diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/repo.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/repo.py index 259334ce1..c183d6464 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/repo.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/repo.py @@ -11,6 +11,7 @@ from s3 import S3 from sts import STS from logger import configure_logger +from partition import get_partition LOGGER = configure_logger(__name__) TARGET_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) @@ -22,6 +23,8 @@ DEPLOYMENT_ACCOUNT_REGION, S3_BUCKET_NAME ) + + class Repo: def __init__(self, account_id, name, description=''): self.name = name @@ -30,8 +33,9 @@ def __init__(self, account_id, name, description=''): self.description = description self.stack_name = "{0}-{1}".format('adf-codecommit', self.name) self.account_id = account_id + self.partition = get_partition(DEPLOYMENT_ACCOUNT_REGION) self.session = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/adf-automation-role'.format(account_id), + f'arn:{self.partition}:iam::{account_id}:role/adf-automation-role', 'create_repo_{0}'.format(account_id) ) diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/resolver.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/resolver.py index 3fea010df..adccc236e 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/resolver.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/resolver.py @@ -10,6 +10,7 @@ from botocore.exceptions import ClientError from s3 import S3 from parameter_store import ParameterStore +from partition import get_partition from cloudformation import CloudFormation from cache import Cache from errors import ParameterNotFoundError @@ -33,6 +34,7 @@ def _is_optional(value): return value.endswith('?') def fetch_stack_output(self, value, key, optional=False): # pylint: disable=too-many-statements + partition = get_partition(DEFAULT_REGION) try: [_, account_id, region, stack_name, output_key] = str(value).split(':') except ValueError as error: @@ -48,9 +50,7 @@ def fetch_stack_output(self, value, key, optional=False): # pylint: disable=too- output_key = output_key[:-1] if optional else output_key try: role = self.sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format( - account_id, - 'adf-readonly-automation-role'), + f'arn:{partition}:iam::{account_id}:role/adf-readonly-automation-role', 'importer' ) cloudformation = CloudFormation( diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/templates/events.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/templates/events.yml index 41633f089..c8355b30d 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/templates/events.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/templates/events.yml @@ -26,16 +26,16 @@ Resources: Statement: - Effect: Allow Action: events:PutEvents - Resource: '*' - EventRule: + Resource: "*" + EventRule: Type: AWS::Events::Rule - Properties: + Properties: Name: !Sub adf-cc-event-from-${AWS::AccountId}-to-${DeploymentAccountId} EventPattern: source: - aws.codecommit detail-type: - - 'CodeCommit Repository State Change' + - "CodeCommit Repository State Change" detail: event: - referenceCreated @@ -43,6 +43,6 @@ Resources: referenceType: - branch Targets: - - Arn: !Sub arn:aws:events:${AWS::Region}:${DeploymentAccountId}:event-bus/default + - Arn: !Sub arn:${AWS::Partition}:events:${AWS::Region}:${DeploymentAccountId}:event-bus/default RoleArn: !GetAtt EventRole.Arn Id: codecommit-push-event diff --git a/src/lambda_codebase/moved_to_root.py b/src/lambda_codebase/moved_to_root.py index ca389e089..94b002a7e 100644 --- a/src/lambda_codebase/moved_to_root.py +++ b/src/lambda_codebase/moved_to_root.py @@ -14,6 +14,7 @@ from sts import STS from parameter_store import ParameterStore +from partition import get_partition from logger import configure_logger from cloudformation import CloudFormation @@ -23,9 +24,12 @@ def worker_thread(sts, region, account_id, role, event): + partition = get_partition(REGION_DEFAULT) + role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format(account_id, role), - 'remove_base') + f'arn:{partition}:iam::{account_id}:role/{role}', + 'remove_base' + ) parameter_store = ParameterStore(region, role) paginator = parameter_store.client.get_paginator('describe_parameters') diff --git a/src/lambda_codebase/organization/main.py b/src/lambda_codebase/organization/main.py index cc4d80287..ffe469bf1 100644 --- a/src/lambda_codebase/organization/main.py +++ b/src/lambda_codebase/organization/main.py @@ -11,7 +11,7 @@ import os import json import boto3 -from cfn_custom_resource import ( # pylint: disable=unused-import +from cfn_custom_resource import ( # pylint: disable=unused-import lambda_handler, create, update, @@ -61,8 +61,17 @@ def as_cfn_response(self) -> Tuple[PhysicalResourceId, Data]: @create() def create_(_event: Mapping[str, Any], _context: Any) -> CloudFormationResponse: - if os.environ["AWS_REGION"] != 'us-east-1': - raise Exception("Deployment of ADF is only available via the us-east-1 region.") + approved_regions = [ + 'us-east-1', + 'us-gov-west-1' + ] + region = os.getenv('AWS_REGION') + + if region not in approved_regions: + raise Exception( + "Deployment of ADF is only available via the us-east-1 " + "and us-gov-west-1 regions." + ) organization_id, created = ensure_organization() organization_root_id = get_organization_root_id() return PhysicalResource( @@ -112,7 +121,8 @@ def ensure_organization() -> Tuple[OrganizationId, Created]: if describe_organization["Organization"]["FeatureSet"] != "ALL": raise Exception( - "Existing organization is only set up for CONSOLIDATED_BILLING, but ADF needs ALL features" + "Existing organization is only set up for CONSOLIDATED_BILLING, " + "but ADF needs ALL features" ) organization_id = describe_organization["Organization"]["Id"] LOGGER.info( diff --git a/src/lambda_codebase/organization_unit/main.py b/src/lambda_codebase/organization_unit/main.py index 2f53ebc5a..38fd23a2d 100644 --- a/src/lambda_codebase/organization_unit/main.py +++ b/src/lambda_codebase/organization_unit/main.py @@ -11,7 +11,7 @@ import json import time import boto3 -from cfn_custom_resource import ( # pylint: disable=unused-import +from cfn_custom_resource import ( # pylint: disable=unused-import lambda_handler, create, update, diff --git a/src/lambda_codebase/wait_until_complete.py b/src/lambda_codebase/wait_until_complete.py index 5d2011f55..f8097c65f 100644 --- a/src/lambda_codebase/wait_until_complete.py +++ b/src/lambda_codebase/wait_until_complete.py @@ -17,6 +17,7 @@ from errors import RetryError from logger import configure_logger from cloudformation import CloudFormation +from partition import get_partition S3_BUCKET = os.environ["S3_BUCKET_NAME"] REGION_DEFAULT = os.environ["AWS_REGION"] @@ -50,16 +51,17 @@ def update_deployment_account_output_parameters( value ) + def lambda_handler(event, _): """Main Lambda Entry point """ sts = STS() + account_id = event.get('account_id') + partition = get_partition(REGION_DEFAULT) + cross_account_access_role = event.get('cross_account_access_role') role = sts.assume_cross_account_role( - 'arn:aws:iam::{0}:role/{1}'.format( - event['account_id'], - event['cross_account_access_role'], - ), + f'arn:{partition}:iam::{account_id}:role/{cross_account_access_role}', 'master' ) @@ -75,7 +77,7 @@ def lambda_handler(event, _): stack_name=None, s3=s3, s3_key_path=event['ou_name'], - account_id=event['account_id'] + account_id=account_id ) status = cloudformation.get_stack_status() @@ -92,9 +94,10 @@ def lambda_handler(event, _): 'ROLLBACK_COMPLETE' ): raise Exception("Account Bootstrap Failed - Account: {0} Region: {1} Status: {2}".format( - event['account_id'], + account_id, region, - status)) + status) + ) if event.get('is_deployment_account'): update_deployment_account_output_parameters( diff --git a/src/template.yml b/src/template.yml index ee39a390e..c2774abc3 100644 --- a/src/template.yml +++ b/src/template.yml @@ -1,8 +1,8 @@ # // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. # // SPDX-License-Identifier: Apache-2.0 -AWSTemplateFormatVersion: '2010-09-09' -Transform: 'AWS::Serverless-2016-10-31' +AWSTemplateFormatVersion: "2010-09-09" +Transform: "AWS::Serverless-2016-10-31" Description: ADF CloudFormation Initial Base Stack for the Master Account in the us-east-1 region. Metadata: AWS::ServerlessRepo::Application: @@ -12,7 +12,8 @@ Metadata: SpdxLicenseId: Apache-2.0 LicenseUrl: ../LICENSE.txt ReadmeUrl: ../docs/serverless-application-repo.md - Labels: ['adf', 'aws-deployment-framework', 'multi-account', 'cicd', 'devops'] + Labels: + ["adf", "aws-deployment-framework", "multi-account", "cicd", "devops"] HomePageUrl: https://github.com/awslabs/aws-deployment-framework SemanticVersion: 3.1.2 SourceCodeUrl: https://github.com/awslabs/aws-deployment-framework @@ -47,11 +48,11 @@ Parameters: DeploymentAccountMainRegion: Type: String Default: "" - Description: "Example -> eu-west-1" + Description: "Example -> us-east-1, us-gov-west-1, eu-west-1" DeploymentAccountTargetRegions: Type: CommaDelimitedList Default: "" - Description: "(Optional) Example -> us-west-1,eu-west-3" + Description: "(Optional) Example -> us-east-1, us-west-1, eu-west-3" ProtectedOUs: Description: "(Optional) Example -> ou-123,ou-234" Type: CommaDelimitedList @@ -72,16 +73,16 @@ Resources: StringEquals: aws:PrincipalOrgID: !GetAtt Organization.OrganizationId Resource: - - !Sub arn:aws:s3:::${BootstrapTemplatesBucket} - - !Sub arn:aws:s3:::${BootstrapTemplatesBucket}/* + - !Sub arn:${AWS::Partition}:s3:::${BootstrapTemplatesBucket} + - !Sub arn:${AWS::Partition}:s3:::${BootstrapTemplatesBucket}/* Principal: AWS: "*" - Action: - s3:PutObject* Effect: Allow Resource: - - !Sub arn:aws:s3:::${BootstrapTemplatesBucket} - - !Sub arn:aws:s3:::${BootstrapTemplatesBucket}/* + - !Sub arn:${AWS::Partition}:s3:::${BootstrapTemplatesBucket} + - !Sub arn:${AWS::Partition}:s3:::${BootstrapTemplatesBucket}/* Principal: AWS: !Ref AWS::AccountId BootstrapArtifactStorageBucket: @@ -176,20 +177,19 @@ Resources: Resource: !GetAtt BootstrapTemplatesBucket.Arn - Effect: "Allow" Action: "s3:GetObject" - Resource: - !Join - - '' - - - !GetAtt BootstrapTemplatesBucket.Arn - - '/*' + Resource: !Join + - "" + - - !GetAtt BootstrapTemplatesBucket.Arn + - "/*" Roles: - !Ref LambdaRole StackWaiterFunction: - Type: 'AWS::Serverless::Function' + Type: "AWS::Serverless::Function" Properties: Handler: wait_until_complete.lambda_handler CodeUri: lambda_codebase/ Layers: - - !Ref LambdaLayerVersion + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - StackWaiterFunction" Environment: Variables: @@ -197,19 +197,19 @@ Resources: TERMINATION_PROTECTION: false MASTER_ACCOUNT_ID: !Ref AWS::AccountId ORGANIZATION_ID: !GetAtt Organization.OrganizationId - ADF_VERSION: !FindInMap ['Metadata', 'ADF', 'Version'] + ADF_VERSION: !FindInMap ["Metadata", "ADF", "Version"] ADF_LOG_LEVEL: INFO FunctionName: StackWaiter Role: !GetAtt LambdaRole.Arn Runtime: python3.8 Timeout: 300 DetermineEventFunction: - Type: 'AWS::Serverless::Function' + Type: "AWS::Serverless::Function" Properties: Handler: determine_event.lambda_handler CodeUri: lambda_codebase/ Layers: - - !Ref LambdaLayerVersion + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - DetermineEvent" Environment: Variables: @@ -218,19 +218,19 @@ Resources: DEPLOYMENT_ACCOUNT_BUCKET: !GetAtt SharedModulesBucketName.Value MASTER_ACCOUNT_ID: !Ref AWS::AccountId ORGANIZATION_ID: !GetAtt Organization.OrganizationId - ADF_VERSION: !FindInMap ['Metadata', 'ADF', 'Version'] + ADF_VERSION: !FindInMap ["Metadata", "ADF", "Version"] ADF_LOG_LEVEL: INFO FunctionName: DetermineEventFunction Role: !GetAtt LambdaRole.Arn Runtime: python3.8 Timeout: 300 CrossAccountExecuteFunction: - Type: 'AWS::Serverless::Function' + Type: "AWS::Serverless::Function" Properties: Handler: account_bootstrap.lambda_handler CodeUri: lambda_codebase/ Layers: - - !Ref LambdaLayerVersion + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - CrossAccountExecuteFunction" Environment: Variables: @@ -239,64 +239,64 @@ Resources: DEPLOYMENT_ACCOUNT_BUCKET: !GetAtt SharedModulesBucketName.Value MASTER_ACCOUNT_ID: !Ref AWS::AccountId ORGANIZATION_ID: !GetAtt Organization.OrganizationId - ADF_VERSION: !FindInMap ['Metadata', 'ADF', 'Version'] + ADF_VERSION: !FindInMap ["Metadata", "ADF", "Version"] ADF_LOG_LEVEL: INFO FunctionName: CrossAccountExecuteFunction Role: !GetAtt LambdaRole.Arn Runtime: python3.8 Timeout: 600 RoleStackDeploymentFunction: - Type: 'AWS::Serverless::Function' + Type: "AWS::Serverless::Function" Properties: Handler: deployment_account_config.lambda_handler CodeUri: lambda_codebase/ Layers: - - !Ref LambdaLayerVersion + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - RoleStackDeploymentFunction" Environment: Variables: S3_BUCKET_NAME: !Ref BootstrapTemplatesBucket TERMINATION_PROTECTION: false MASTER_ACCOUNT_ID: !Ref AWS::AccountId - ADF_VERSION: !FindInMap ['Metadata', 'ADF', 'Version'] + ADF_VERSION: !FindInMap ["Metadata", "ADF", "Version"] ADF_LOG_LEVEL: INFO FunctionName: RoleStackDeploymentFunction Role: !GetAtt LambdaRole.Arn Runtime: python3.8 Timeout: 300 MovedToRootActionFunction: - Type: 'AWS::Serverless::Function' + Type: "AWS::Serverless::Function" Properties: Handler: moved_to_root.lambda_handler CodeUri: lambda_codebase/ Layers: - - !Ref LambdaLayerVersion + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - MovedToRootActionFunction" Environment: Variables: S3_BUCKET_NAME: !Ref BootstrapTemplatesBucket TERMINATION_PROTECTION: false MASTER_ACCOUNT_ID: !Ref AWS::AccountId - ADF_VERSION: !FindInMap ['Metadata', 'ADF', 'Version'] + ADF_VERSION: !FindInMap ["Metadata", "ADF", "Version"] ADF_LOG_LEVEL: INFO FunctionName: MovedToRootActionFunction Role: !GetAtt LambdaRole.Arn Runtime: python3.8 Timeout: 900 UpdateResourcePoliciesFunction: - Type: 'AWS::Serverless::Function' + Type: "AWS::Serverless::Function" Properties: Handler: generic_account_config.lambda_handler CodeUri: lambda_codebase/ Layers: - - !Ref LambdaLayerVersion + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - UpdateResourcePoliciesFunction" Environment: Variables: S3_BUCKET_NAME: !Ref BootstrapTemplatesBucket TERMINATION_PROTECTION: false MASTER_ACCOUNT_ID: !Ref AWS::AccountId - ADF_VERSION: !FindInMap ['Metadata', 'ADF', 'Version'] + ADF_VERSION: !FindInMap ["Metadata", "ADF", "Version"] ADF_LOG_LEVEL: INFO FunctionName: UpdateResourcePoliciesFunction Role: !GetAtt LambdaRole.Arn @@ -356,12 +356,12 @@ Resources: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - - Effect: "Allow" - Principal: - Service: - - "codebuild.amazonaws.com" - Action: - - "sts:AssumeRole" + - Effect: "Allow" + Principal: + Service: + - "codebuild.amazonaws.com" + Action: + - "sts:AssumeRole" ManagedPolicyArns: - !Ref "CodeBuildPolicy" RoleName: "adf-codebuild-role" @@ -425,9 +425,9 @@ Resources: - "cloudformation:SignalResource" - "cloudformation:UpdateTerminationProtection" Resource: - - "arn:aws:cloudformation:*:*:stack/adf-global-base-*/*" - - "arn:aws:cloudformation:*:*:stack/adf-regional-base-*/*" - - !Sub "arn:aws:cloudformation:*:${AWS::AccountId}:stack/adf-global-base-adf-build/*" + - !Sub "arn:${AWS::Partition}:cloudformation:*:*:stack/adf-global-base-*/*" + - !Sub "arn:${AWS::Partition}:cloudformation:*:*:stack/adf-regional-base-*/*" + - !Sub "arn:${AWS::Partition}:cloudformation:*:${AWS::AccountId}:stack/adf-global-base-adf-build/*" - Effect: "Allow" Action: - "s3:DeleteObject" @@ -436,17 +436,17 @@ Resources: - "s3:ListBucket" - "s3:PutObject" Resource: - - "arn:aws:s3:::serverlessrepo-aws-deplo-bootstraptemplatesbucket-*" - - "arn:aws:s3:::serverlessrepo-aws-deplo-bootstraptemplatesbucket-*/*" + - !Sub "arn:${AWS::Partition}:s3:::serverlessrepo-aws-deplo-bootstraptemplatesbucket-*" + - !Sub "arn:${AWS::Partition}:s3:::serverlessrepo-aws-deplo-bootstraptemplatesbucket-*/*" - !GetAtt BootstrapArtifactStorageBucket.Arn - !Sub "${BootstrapArtifactStorageBucket.Arn}/*" - - "arn:aws:s3:::adf-shared-modules-*-*" - - "arn:aws:s3:::adf-shared-modules-*-*/*" + - !Sub "arn:${AWS::Partition}:s3:::adf-shared-modules-*-*" + - !Sub "arn:${AWS::Partition}:s3:::adf-shared-modules-*-*/*" - Effect: "Allow" Action: - "codebuild:*" Resource: - - !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/aws-deployment-framework-base-templates" + - !Sub "arn:${AWS::Partition}:codebuild:${AWS::Region}:${AWS::AccountId}:project/aws-deployment-framework-base-templates" - Effect: "Allow" Action: - "iam:CreatePolicy" @@ -457,12 +457,12 @@ Resources: - "iam:PutRolePolicy" - "iam:UpdateAssumeRolePolicy" Resource: - - !Sub "arn:aws:iam::${AWS::AccountId}:role/${CrossAccountAccessRoleName}" - - !Sub "arn:aws:iam::${AWS::AccountId}:role/${CrossAccountAccessRoleName}-readonly" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${CrossAccountAccessRoleName}" + - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${CrossAccountAccessRoleName}-readonly" CodeCommitRepository: Type: AWS::CodeCommit::Repository Properties: - RepositoryName: 'aws-deployment-framework-bootstrap' + RepositoryName: "aws-deployment-framework-bootstrap" RepositoryDescription: !Sub "CodeCommit Repo for AWS Deployment Framework base in ${AWS::AccountId}" CodeBuildProject: Type: AWS::CodeBuild::Project @@ -476,11 +476,11 @@ Resources: Image: "aws/codebuild/standard:5.0" EnvironmentVariables: - Name: ADF_VERSION - Value: !FindInMap ['Metadata', 'ADF', 'Version'] + Value: !FindInMap ["Metadata", "ADF", "Version"] - Name: TERMINATION_PROTECTION Value: false - Name: PYTHONPATH - Value: './adf-build/shared/python' + Value: "./adf-build/shared/python" - Name: S3_BUCKET Value: !Ref BootstrapTemplatesBucket - Name: MASTER_ACCOUNT_ID @@ -492,7 +492,7 @@ Resources: - Name: ADF_LOG_LEVEL Value: INFO Type: LINUX_CONTAINER - Name: 'aws-deployment-framework-base-templates' + Name: "aws-deployment-framework-base-templates" ServiceRole: !Ref CodeBuildRole Source: BuildSpec: !Sub | @@ -524,7 +524,7 @@ Resources: Type: S3 Location: !Ref BootstrapArtifactStorageBucket RoleArn: !GetAtt CodePipelineRole.Arn - Name: 'aws-deployment-framework-bootstrap-pipeline' + Name: "aws-deployment-framework-bootstrap-pipeline" Stages: - Name: CodeCommit Actions: @@ -532,29 +532,29 @@ Resources: ActionTypeId: Category: Source Owner: AWS - Version: '1' + Version: "1" Provider: CodeCommit OutputArtifacts: - Name: "TemplateSource" Configuration: BranchName: "master" - RepositoryName: 'aws-deployment-framework-bootstrap' + RepositoryName: "aws-deployment-framework-bootstrap" PollForSourceChanges: false RunOrder: 1 - Name: UploadAndUpdateBaseStacks Actions: - Name: UploadAndUpdateBaseStacks ActionTypeId: - Category: Build - Owner: AWS - Version: '1' - Provider: CodeBuild + Category: Build + Owner: AWS + Version: "1" + Provider: CodeBuild OutputArtifacts: - Name: "aws-deployment-framework-bootstrap-build" InputArtifacts: - Name: "TemplateSource" Configuration: - ProjectName: !Ref CodeBuildProject + ProjectName: !Ref CodeBuildProject RunOrder: 1 CodePipelineRole: Type: AWS::IAM::Role @@ -611,7 +611,7 @@ Resources: - Effect: Allow Action: - "codepipeline:StartPipelineExecution" - Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline} + Resource: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}" Roles: - !Ref OrgEventCodePipelineRole StatesExecutionRole: @@ -723,30 +723,85 @@ Resources: "Choices": [{ "Variable": "$.is_deployment_account", "NumericEquals": 1, - "Next": "DeploymentAccountConfig" - }], - "Default": "ExecuteDeploymentAccountStateMachine" - }, - "DeploymentAccountConfig": { - "Type": "Task", - "Resource": "${RoleStackDeploymentFunction.Arn}", - "End": true, - "TimeoutSeconds": 900 - }, - "ExecuteDeploymentAccountStateMachine": { - "Type": "Task", - "Resource": "${UpdateResourcePoliciesFunction.Arn}", - "End": true, - "TimeoutSeconds": 900 - } + "Next": "MovedToRootAction" + } + ], + "Default": "CreateOrUpdateBaseStack" + }, + "CreateOrUpdateBaseStack": { + "Type": "Task", + "Resource": "${CrossAccountExecuteFunction.Arn}", + "Next": "WaitUntilBootstrapComplete", + "Catch": [{ + "ErrorEquals": ["States.ALL"], + "Next": "ExecuteDeploymentAccountStateMachine", + "ResultPath": "$.error" + }], + "TimeoutSeconds": 600 + }, + "MovedToRootAction": { + "Type": "Task", + "Resource": "${MovedToRootActionFunction.Arn}", + "Retry": [{ + "ErrorEquals": ["RetryError"], + "IntervalSeconds": 10, + "BackoffRate": 1.0, + "MaxAttempts": 20 + }], + "Catch": [{ + "ErrorEquals": ["States.ALL"], + "Next": "ExecuteDeploymentAccountStateMachine", + "ResultPath": "$.error" + }], + "Next": "ExecuteDeploymentAccountStateMachine", + "TimeoutSeconds": 900 + }, + "WaitUntilBootstrapComplete": { + "Type": "Task", + "Resource": "${StackWaiterFunction.Arn}", + "Retry": [{ + "ErrorEquals": ["RetryError"], + "IntervalSeconds": 10, + "BackoffRate": 1.0, + "MaxAttempts": 500 + }], + "Catch": [{ + "ErrorEquals": ["States.ALL"], + "Next": "ExecuteDeploymentAccountStateMachine", + "ResultPath": "$.error" + }], + "Next": "DeploymentAccount?", + "TimeoutSeconds": 900 + }, + "DeploymentAccount?": { + "Type": "Choice", + "Choices": [{ + "Variable": "$.is_deployment_account", + "NumericEquals": 1, + "Next": "DeploymentAccountConfig" + }], + "Default": "ExecuteDeploymentAccountStateMachine" + }, + "DeploymentAccountConfig": { + "Type": "Task", + "Resource": "${RoleStackDeploymentFunction.Arn}", + "End": true, + "TimeoutSeconds": 900 + }, + "ExecuteDeploymentAccountStateMachine": { + "Type": "Task", + "Resource": "${UpdateResourcePoliciesFunction.Arn}", + "End": true, + "TimeoutSeconds": 900 } } + } RoleArn: !GetAtt StatesExecutionRole.Arn InitialCommit: Type: Custom::InitialCommit Properties: ServiceToken: !GetAtt InitialCommitHandler.Arn - Version: !FindInMap ['Metadata', 'ADF', 'Version'] + Version: !FindInMap ["Metadata", "ADF", "Version"] RepositoryArn: !GetAtt CodeCommitRepository.Arn DirectoryName: bootstrap_repository ExistingAccountId: !Ref DeploymentAccountId @@ -785,7 +840,7 @@ Resources: ServiceToken: !GetAtt CrossRegionBucketHandler.Arn Region: !Ref DeploymentAccountMainRegion BucketNamePrefix: !Sub "adf-shared-modules-${DeploymentAccountMainRegion}" - Version: !FindInMap ['Metadata', 'ADF', 'Version'] + Version: !FindInMap ["Metadata", "ADF", "Version"] PolicyDocument: Statement: - Action: @@ -794,12 +849,7 @@ Resources: - s3:PutObject Effect: Allow Principal: - AWS: - - Fn::Join: - - "" - - - "arn:aws:iam::" - - !GetAtt DeploymentAccount.AccountId - - ":root" + AWS: !Sub "arn:${AWS::Partition}:iam::${DeploymentAccount.AccountId}:root" Service: - codebuild.amazonaws.com - lambda.amazonaws.com @@ -823,6 +873,8 @@ Resources: Properties: Handler: handler.lambda_handler CodeUri: lambda_codebase/cross_region_bucket + Layers: + - !Ref LambdaLayerVersion Description: "ADF Lambda Function - Create Deployment Bucket in Main Deployment Region" Policies: - Version: "2012-10-17" @@ -836,12 +888,12 @@ Resources: - s3:PutEncryptionConfiguration - s3:PutBucketPolicy - s3:PutBucketPublicAccessBlock - Resource: "arn:aws:s3:::adf-shared-modules-*" + Resource: !Sub "arn:${AWS::Partition}:s3:::adf-shared-modules-*" - Effect: Allow Action: ssm:GetParameter Resource: - - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/shared_modules_bucket" - - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/deployment_account_region" + - !Sub "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/shared_modules_bucket" + - !Sub "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/deployment_account_region" FunctionName: CrossRegionBucketHandler Runtime: python3.8 Timeout: 300 @@ -867,7 +919,7 @@ Resources: Resource: "*" - Effect: Allow Action: "iam:CreateServiceLinkedRole" - Resource: "arn:aws:iam::*:role/aws-service-role/*" + Resource: !Sub "arn:${AWS::Partition}:iam::*:role/aws-service-role/*" FunctionName: AwsOrganizationsHandler Runtime: python3.8 Timeout: 300 @@ -920,30 +972,30 @@ Resources: Resource: "*" - Effect: Allow Action: ssm:GetParameter - Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/deployment_account_id" + Resource: !Sub "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/deployment_account_id" FunctionName: AccountHandler Runtime: python3.8 Timeout: 300 PipelineCloudWatchEventRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Principal: - Service: - - events.amazonaws.com - Action: sts:AssumeRole - Path: / - Policies: - - PolicyName: adf-bootstrap-execute-cwe - PolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Action: codepipeline:StartPipelineExecution - Resource: !Join [ '', [ 'arn:aws:codepipeline:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref CodePipeline ] ] + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - events.amazonaws.com + Action: sts:AssumeRole + Path: / + Policies: + - PolicyName: adf-bootstrap-execute-cwe + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: codepipeline:StartPipelineExecution + Resource: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}" PipelineCloudWatchEventRule: Type: AWS::Events::Rule Properties: @@ -951,9 +1003,9 @@ Resources: source: - aws.codecommit detail-type: - - 'CodeCommit Repository State Change' + - "CodeCommit Repository State Change" resources: - - !Join [ '', [ 'arn:aws:codecommit:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !GetAtt CodeCommitRepository.Name ] ] + - !Sub "arn:${AWS::Partition}:codecommit:${AWS::Region}:${AWS::AccountId}:${CodeCommitRepository.Name}" detail: event: - referenceCreated @@ -963,13 +1015,12 @@ Resources: referenceName: - master Targets: - - Arn: - !Join [ '', [ 'arn:aws:codepipeline:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref CodePipeline ] ] + - Arn: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipeline}" RoleArn: !GetAtt PipelineCloudWatchEventRole.Arn Id: adf-codepipeline-trigger-bootstrap Outputs: ADFVersionNumber: - Value: !FindInMap ['Metadata', 'ADF', 'Version'] + Value: !FindInMap ["Metadata", "ADF", "Version"] Export: Name: "ADFVersionNumber" LayerArn: