Codepipelineで別アカウントにあるCodeCommitを使用してCI/CD設定をしてみた

2024.03.31

Codepipelineを設定中に別AWSアカウントにあるCodeCommitを使用する機会があったのでブログに残します。

やること

AWSアカウントAにあるCodeCommitにgit pushされたらAWSアカウントBにあるCodePipelineを使用してEC2に自動デプロイする構成を作成します。
簡単にはなりますが構成図は以下になります。

AWSアカウントAにあるCodeCommitにgit pushするとEventBridgeで検知してAWSアカウントBにあるEventBridge Busに連携する構成としています。
構成図には記載していませんが、アーティファクト用のS3バケットとKMSキーをAWSアカウントBに作成しています。
CodePipelineからCodeCommitへアクセスするためAWSアカウントAにクロスアカウントIAMロールを作成します。
こちらのIAMロールの内容は以下のブログを参考に設定しています。
かなり細かく説明が記載されているのでご確認いただければと思います。

作成してみた

AWSアカウントBでデプロイ先のEC2、アーティファクト用S3、CodePipelineの使用するIAMロール作成

まずはAWSアカウントAでデプロイ先のEC2などを作成していきます。
リソース作成は以下のCloudFormationで行っています。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: CI/CD test Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for Policy
        Parameters:
          - AccountAId
      - Label: 
          default: Parameters for VPC
        Parameters:
          - VPCCIDR
      - Label: 
          default: Parameters for Subnet
        Parameters:
          - PublicSubnet01CIDR
      - Label: 
          default: Parameters for ec2
        Parameters:
          - EC2VolumeSize
          - EC2VolumeIOPS
          - EC2AMI
          - EC2InstanceType

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  AccountAId:
    Type: String

  VPCCIDR:
    Default: 172.30.0.0/16
    Type: String

  PublicSubnet01CIDR:
    Default: 172.30.3.0/24
    Type: String

  EC2VolumeSize:
    Default: 32
    Type: Number

  EC2VolumeIOPS:
    Default: 3000
    Type: Number

  EC2AMI:
    Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64'
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>

  EC2InstanceType:
    Default: t3.micro
    Type: String

Resources:
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------# 
  S3:
    Type: AWS::S3::Bucket
    Properties: 
      BucketEncryption: 
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      BucketName: !Sub ${AWS::StackName}-${AWS::AccountId}-artifact
      OwnershipControls:
        Rules: 
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-${AWS::AccountId}-artifact

  S3ArtifactBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: s3:ListBucket
            Resource: !Sub arn:aws:s3:::${S3}
            Principal:
              AWS:
                - !Sub arn:aws:iam::${AccountAId}:root
          - Effect: Allow
            Action:
              - s3:Get*
              - s3:Put*
            Resource: !Sub arn:aws:s3:::${S3}/*
            Principal:
              AWS:
                - !Sub arn:aws:iam::${AccountAId}:root
          - Effect: Deny
            Action: s3:PutObject
            Resource: !Sub arn:aws:s3:::${S3}/*
            Principal: "*"
            Condition: 
              StringNotEquals: 
                s3:x-amz-server-side-encryption: aws:kms
          - Effect: Deny
            Action: s3:*
            Resource: !Sub arn:aws:s3:::${S3}/*
            Principal: "*"
            Condition:
              Bool:
                aws:SecureTransport: false

# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------# 
  EC2IAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties: 
      PolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Action:
              - "s3:GetObject"
              - "s3:ListBucket"
            Resource: 
              - !Join 
                - ''
                - - !GetAtt S3.Arn
                  - '/*'
              - !GetAtt S3.Arn
      ManagedPolicyName: iam-policy-deploy-ec2-al2023

  EC2IAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - ec2.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - !Ref EC2IAMPolicy
      RoleName: iam-role-ec2-al2023
      Tags:
        - Key: Name
          Value: iam-role-ec2-al2023

  EC2IAMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: iam-instanceprofile-ec2-al2023
      Roles: 
        - !Ref EC2IAMRole

  CodeBuildIAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties: 
      PolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Action:
              - 's3:PutObject'
              - 's3:GetObject'
            Resource: 
              - !Join 
                - ''
                - - !GetAtt S3.Arn
                  - '/*'
          - Effect: Allow
            Action: 
              - 'codecommit:GitPull'
            Resource: "*"
          - Effect: Allow
            Action: 
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
            Resource: "*"
      ManagedPolicyName: iam-policy-codebuild

  CodeBuildIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - codebuild.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      ManagedPolicyArns: 
        - !Ref CodeBuildIAMPolicy
      RoleName: iam-role-codebuild
      Tags:
        - Key: Name
          Value: iam-role-codebuild

  CodeDeployIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - codedeploy.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole
      RoleName: iam-role-codedeploy
      Tags:
        - Key: Name
          Value: iam-role-codedeploy

  CodePipelineIAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties: 
      PolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Action:
              - "codebuild:BatchGetBuilds"
              - "codebuild:StartBuild"
            Resource: 
              - "*"
          - Effect: Allow
            Action:
              - "codedeploy:CreateDeployment"
              - "codedeploy:GetApplication"
              - "codedeploy:GetApplicationRevision"
              - "codedeploy:GetDeployment"
              - "codedeploy:GetDeploymentConfig"
              - "codedeploy:RegisterApplicationRevision"
            Resource: 
              - "*"
          - Effect: Allow
            Action:
              - "s3:GetObject"
              - "s3:PutObject"
              - "s3:ListBucket"
            Resource: 
              - !Join 
                - ''
                - - !GetAtt S3.Arn
                  - '/*'
              - !GetAtt S3.Arn
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Resource:
              - !Sub arn:aws:iam::${AccountAId}:role/*
      ManagedPolicyName: iam-policy-codepipeline

  CodePipelineIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - codepipeline.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      ManagedPolicyArns: 
        - !Ref CodePipelineIAMPolicy
      RoleName: iam-role-codepipeline
      Tags:
        - Key: Name
          Value: iam-role-codepipeline

# ------------------------------------------------------------#
# KMS
# ------------------------------------------------------------# 
  KMSKey:
    Type: AWS::KMS::Key
    Properties:
      KeyPolicy:
        Version: "2012-10-17"
        Id: key-policy
        Statement:
          - Effect: Allow
            Action: kms:*
            Sid: Enable IAM User Permissions
            Principal:
              AWS:
                - !Sub arn:aws:iam::${AWS::AccountId}:root
            Resource: "*"
          - Effect: Allow
            Action:
              - kms:DescribeKey
              - kms:Encrypt
              - kms:Decrypt
              - kms:ReEncrypt*
              - kms:GenerateDataKey
              - kms:GenerateDataKeyWithoutPlaintext
            Sid: Allow use of the key
            Principal:
              AWS:
                - !Sub arn:aws:iam::${AccountAId}:root
                - !GetAtt CodeBuildIAMRole.Arn
                - !GetAtt CodePipelineIAMRole.Arn
                - !GetAtt EC2IAMRole.Arn
            Resource: "*"
          - Effect: Allow
            Sid: Allow attachment of persistent resources
            Action:
              - kms:CreateGrant
              - kms:ListGrants
              - kms:RevokeGrant
            Principal:
              AWS:
                - !Sub arn:aws:iam::${AccountAId}:root
                - !GetAtt CodeBuildIAMRole.Arn
                - !GetAtt CodePipelineIAMRole.Arn
                - !GetAtt EC2IAMRole.Arn
            Resource: "*"
            Condition:
              Bool: 
                kms:GrantIsForAWSResource: true
  KMSAlias:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: alias/CodePipelineS3Bucket
      TargetKeyId:
        Ref: KMSKey

# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------# 
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags: 
        - Key: Name
          Value: test-vpc

# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------# 
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: !Sub test-igw

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------# 
  PublicSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet01CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name
          Value: !Sub test-public-subnet-01
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: test-public-rtb

  PublicRouteTableRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref PublicRouteTable

  PublicRtAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet01

# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------# 
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for ec2
      GroupName: test-sg-ec2
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress:
        - FromPort: 80
          IpProtocol: tcp
          CidrIp: 0.0.0.0/0
          ToPort: 80
      Tags: 
        - Key: Name
          Value: test-sg-ec2
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------# 
  EC2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: !Ref EC2VolumeIOPS
            VolumeSize: !Ref EC2VolumeSize
            VolumeType: gp3
      DisableApiTermination: false
      IamInstanceProfile: !Ref EC2IAMInstanceProfile
      ImageId: !Ref EC2AMI
      InstanceType: !Ref EC2InstanceType
      NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeleteOnTermination: true
          DeviceIndex: 0
          GroupSet: 
            - !Ref EC2SG
          SubnetId: !Ref PublicSubnet01
      Tags:
        - Key: Name
          Value: test-ec2
      UserData: !Base64 |
        #!/bin/bash
        yum update
        yum install ruby -y
        yum install wget -y
        wget https://aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/install
        cd
        chmod +x ./install
        ./install auto
        service codedeploy-agent start
        yum install httpd -y
        echo "CodeDeploy Test" > /var/www/html/index.html
        systemctl start httpd
        systemctl enable httpd

Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  CodeBuildIAMRoleARN:
    Value: !GetAtt CodeBuildIAMRole.Arn
    Export: 
      Name: iam-role-codebuild-arn

  CodeDeployIAMRoleARN:
    Value: !GetAtt CodeDeployIAMRole.Arn
    Export: 
      Name: iam-role-codedeploy-arn

  CodePipelineIAMRoleARN:
    Value: !GetAtt CodePipelineIAMRole.Arn
    Export: 
      Name: iam-role-codepipeline-arn

  S3Name:
    Value: !Ref S3
    Export: 
      Name: S3Name

66~120行目でアーティファクト用S3バケットを作成しています。
アーティファクト用S3バケットはAWSアカウントAからアクセスできるようにするためにバケットポリシーを設定しています。
125~292行目でEC2やCodePipelineで使用するIAMロールを作成しています。
IAMロールで重要になるのが、CodePipelineでAWSアカウントAのCodeCommitへアクセスするためにAssumeRoleを行えるポリシーを設定しています。(268~272行目)
297~348行目でKMSキーを作成しています。
KMSキーはキーポリシーでCodePipelineのIAMロール、CodeBuildのIAMロール、EC2のIAMロールとAWSアカウントAからアクセスできるように設定してます。
EC2やVPCの詳細な説明は省かせていただきますが、353~481行目で作成を行っています。

デプロイは以下のAWS CLIコマンドを使用します。
VPCやサブネットのCIDRを変更したい場合は「--parameters」オプションでパラメータを指定してください。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=AccountAId,ParameterValue=AWSアカウントAのID --capabilities CAPABILITY_NAMED_IAM

AWSアカウントAでCodeCommitアクセス用IAMロール、CodeCommit、EventBridgeルールの作成

AWSアカウントBでEC2などが作成できたらAWSアカウントAでCodeCommitなどを作成します。
リソース作成は以下のCloudFormationで行っています。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: CodeCommit Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for Policy
        Parameters:
          - AccountBId
          - KMSARN
          - ArtifactS3ARN
      - Label: 
          default: Parameters for CodeCommit
        Parameters:
          - RepositoryDescription
          - RepositoryName

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  AccountBId:
    Type: String

  KMSARN:
    Type: String

  ArtifactS3ARN:
    Type: String

  RepositoryDescription:
    MaxLength: 4000
    Type: String

  RepositoryName:
    MaxLength: 100
    Type: String

Resources:
# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------# 
  EventBridgeIAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties: 
      PolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Action:
              - "events:PutEvents"
            Resource: 
              - !Sub arn:aws:events:${AWS::Region}:${AccountBId}:event-bus/codepipeline
      ManagedPolicyName: iam-policy-eventbridge

  EventBridgeIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - events.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      ManagedPolicyArns: 
        - !Ref EventBridgeIAMPolicy
      RoleName: iam-role-eventbridge
      Tags:
        - Key: Name
          Value: iam-role-eventbridge

  CodePipelineAssumePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties: 
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: 
              - "s3:PutObject"
              - "s3:PutObjectAcl"
            Resource:
              - !Sub ${ArtifactS3ARN}/*
          - Effect: Allow
            Action: 
              - "kms:DescribeKey"
              - "kms:GenerateDataKey*"
              - "kms:Encrypt"
              - "kms:ReEncrypt*"
              - "kms:Decrypt"
            Resource:
              - !Sub ${KMSARN}
          - Effect: Allow
            Action: 
              - "codecommit:GetBranch"
              - "codecommit:GetCommit"
              - "codecommit:UploadArchive"
              - "codecommit:GetUploadArchiveStatus"
              - "codecommit:CancelUploadArchive"
            Resource:
              - !GetAtt CodeCommit.Arn
      ManagedPolicyName: iam-policy-codepipelineassume

  CodePipelineAssumeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS:
              - !Sub arn:aws:iam::${AccountBId}:root
            Action:
              - sts:AssumeRole
      ManagedPolicyArns: 
        - !Ref CodePipelineAssumePolicy
      RoleName: iam-role-codepipelineassumerole
      Tags:
        - Key: Name
          Value: iam-role-codepipelineassumerole

# ------------------------------------------------------------#
# CodeCommit
# ------------------------------------------------------------# 
  CodeCommit:
    Type: AWS::CodeCommit::Repository
    Properties: 
      RepositoryDescription: !Ref RepositoryDescription
      RepositoryName: !Ref RepositoryName

# ------------------------------------------------------------#
# EventBridge
# ------------------------------------------------------------# 
  EventBridge:
    Type: AWS::Events::Rule
    Properties:
      Description: for codepipeline
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - 'CodeCommit Repository State Change'
        resources:
          - !GetAtt CodeCommit.Arn
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - main
      Name: eventbridge-codepipeline-cross-account
      State: ENABLED
      Targets:
        - Arn: !Sub arn:aws:events:${AWS::Region}:${AccountBId}:event-bus/codepipeline
          RoleArn: !GetAtt EventBridgeIAMRole.Arn
          Id: EventBridge

Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  CodeCommitRepositoryName:
    Value: !GetAtt CodeCommit.Name
    Export: 
      Name: codecommit-repository-name

  CodeCommitRepositoryARN:
    Value: !GetAtt CodeCommit.Arn
    Export: 
      Name: codecommit-repository-arn

48~129行目でEventBridgeとCodeCommitへアクセスするためのIAMロールを作成しています。
EventBridgeのIAMロールに設定するIAMポリシーはAWSアカウントBのEventBridge Busへイベントを送れるように"events:PutEvents"を許可しています。
CodeCommitアクセス用IAMロールはAWSアカウントBのCodePipelineからアクセスできるように信頼ポリシーでAWSアカウントBからのアクセスを許可しています。
134~138行目でCodeCommitを作成しています。
143~167行目でEventBridgeルールを作成しています。
EventBridgeルールではCodeCommitのmainブランチで変更があった時に反応するように設定しています。
また、ターゲットとしてAWSアカウントBのEventBridge Busを設定しています。

デプロイは以下のAWS CLIコマンドを使用します。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=AccountBId,ParameterValue=AWSアカウントBのID ParameterKey=KMSARN,ParameterValue=AWSアカウントAで作成したKMSキーのARN ParameterKey=ArtifactS3ARN,ParameterValue=アーティファクト用S3バケットARN ParameterKey=RepositoryDescription,ParameterValue=CodeCommitリポジトリの説明 ParameterKey=RepositoryName,ParameterValue=CodeCommitリポジトリ名 --capabilities CAPABILITY_NAMED_IAM

AWSアカウントBでCodeBuild、CodeDeploy、CodePipeline、EventBridgeの作成

最後にAWSアカウントBでCodePipelineなどを作成していきます。
リソース作成は以下のCloudFormationで行っています。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: CodeBuild Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for CodeBuild
        Parameters:
          - CodeBuildDescription
          - CodeBuildName
      - Label: 
          default: Parameters for CodeDeploy
        Parameters:
          - ApplicationName
          - DeploymentGroupName
      - Label: 
          default: Parameters for CodePipeline
        Parameters:
          - KMSARN
          - CodePipelineName
          - CodeCommitName
          - CodePipelineAssumeRole
      - Label: 
          default: Parameters for EventBridge
        Parameters:
          - AccountAId

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  CodeBuildDescription:
    MaxLength: 255
    Type: String

  CodeBuildName:
    MaxLength: 255
    Type: String

  ApplicationName:
    MaxLength: 100
    Type: String
  
  DeploymentGroupName:
    MaxLength: 100
    Type: String

  KMSARN:
    Type: String

  CodePipelineName:
    MaxLength: 100
    Type: String

  CodeCommitName:
    Type: String

  CodePipelineAssumeRole:
    Type: String

  AccountAId:
    Type: String

Resources:
# ------------------------------------------------------------#
# CodeBuild
# ------------------------------------------------------------# 
  CodeBuild:
    Type: AWS::CodeBuild::Project
    Properties: 
      Artifacts:
        Type: CODEPIPELINE
      Description: !Ref CodeBuildDescription
      EncryptionKey: alias/CodePipelineS3Bucket
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
        Type: LINUX_CONTAINER
      Name: !Ref CodeBuildName
      ServiceRole: !ImportValue iam-role-codebuild-arn
      Source: 
        BuildSpec: buildspec.yml
        Type: CODEPIPELINE
      Tags:
        - Key: Name
          Value: test-build

# ------------------------------------------------------------#
# CodeDeploy
# ------------------------------------------------------------# 
  CodeDeployApplication:
    Type: AWS::CodeDeploy::Application
    Properties:
      ApplicationName: !Ref ApplicationName
      ComputePlatform: Server
      Tags: 
        - Key: Name
          Value: !Ref ApplicationName

  CodeDeployGroup:
    Type: AWS::CodeDeploy::DeploymentGroup
    Properties:
      ApplicationName: !Ref CodeDeployApplication
      AutoRollbackConfiguration: 
        Enabled: true
        Events:
          - DEPLOYMENT_FAILURE
      DeploymentConfigName: CodeDeployDefault.AllAtOnce
      DeploymentGroupName: !Ref DeploymentGroupName
      Ec2TagFilters: 
        - Key: Name
          Type: KEY_AND_VALUE
          Value: test-ec2
      ServiceRoleArn: !ImportValue iam-role-codedeploy-arn
      Tags:
        - Key: Name
          Value: !Ref DeploymentGroupName

# ------------------------------------------------------------#
# CodePipeline
# ------------------------------------------------------------# 
  CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !ImportValue S3Name
        Type: S3
        EncryptionKey: 
          Id: !Ref KMSARN
          Type: KMS
      Name: !Ref CodePipelineName
      RoleArn: !ImportValue iam-role-codepipeline-arn
      Stages: 
        - Actions:
          - ActionTypeId: 
              Category: Source
              Owner: AWS
              Provider: CodeCommit
              Version: 1
            Configuration:
              RepositoryName: !Ref CodeCommitName
              BranchName: main
              PollForSourceChanges: false
              OutputArtifactFormat: CODE_ZIP
            Name: Source
            Namespace: SourceVariables
            OutputArtifacts:
              - Name: SourceArtifact
            Region: ap-northeast-1
            RunOrder: 1
            RoleArn: !Ref CodePipelineAssumeRole
          Name: Source
        - Actions:
          - ActionTypeId:
              Category: Build
              Owner: AWS
              Provider: CodeBuild
              Version: 1
            Configuration:
              ProjectName: !Ref CodeBuildName
            InputArtifacts: 
              - Name: SourceArtifact
            Name: Build
            Namespace: BuildVariables
            OutputArtifacts: 
              - Name: BuildArtifact
            Region: ap-northeast-1
            RunOrder: 1
          Name: Build
        - Actions:
          - ActionTypeId: 
              Category: Deploy
              Owner: AWS
              Provider: CodeDeploy
              Version: 1
            Configuration:
              ApplicationName: !Ref ApplicationName
              DeploymentGroupName: !Ref DeploymentGroupName
            Name: Deploy
            Namespace: DeployVariables
            InputArtifacts:
              - Name: BuildArtifact
            Region: ap-northeast-1
            RunOrder: 1
          Name: Deploy
      Tags:
        - Key: Name
          Value: !Ref CodePipelineName

# ------------------------------------------------------------#
# EventBridge
# ------------------------------------------------------------# 
  EventBus:
    Type : AWS::Events::EventBus
    Properties:
      Name: codepipeline

  EventBusPolicy:
    Type: AWS::Events::EventBusPolicy
    Properties:
      StatementId: !Sub codepipeline
      EventBusName: codepipeline
      Statement:
        Effect: Allow
        Action: events:PutEvents
        Resource: !GetAtt EventBus.Arn
        Principal:
            AWS: !Sub arn:aws:iam::${AccountAId}:root

  EventBridgeIAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties: 
      PolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Action:
              - "codepipeline:StartPipelineExecution"
            Resource: 
              - !Join 
                - ''
                - - !Sub 'arn:aws:codepipeline:${AWS::Region}:'
                  - !Sub '${AWS::AccountId}:'
                  - !Ref CodePipeline
      ManagedPolicyName: iam-policy-eventbridge

  EventBridgeIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - events.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      ManagedPolicyArns: 
        - !Ref EventBridgeIAMPolicy
      RoleName: iam-role-eventbridge
      Tags:
        - Key: Name
          Value: iam-role-eventbridge

  EventBridge:
    Type: AWS::Events::Rule
    Properties:
      Description: for codepipeline
      EventBusName: !GetAtt EventBus.Name
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - 'CodeCommit Repository State Change'
        resources:
          - !Sub arn:aws:codecommit:${AWS::Region}:${AccountAId}:${CodeCommitName}
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - main
      Name: eventbridge-codepipeline
      State: ENABLED
      Targets:
        - Arn: !Join 
            - ''
            - - !Sub 'arn:aws:codepipeline:${AWS::Region}:'
              - !Sub '${AWS::AccountId}:'
              - !Ref CodePipeline
          Id: CodePipeline
          RoleArn: !GetAtt EventBridgeIAMRole.Arn

73~91行目でCodeBuildを作成しています。
96~122行目でCodeDeployを作成しています。
127~193行目でCodePipelineを作成しています。
198~280行目でEventBridgeとEventBridgeで使用するIAMロールを作成しています。
EventBridge BusはAWSアカウントAからイベントを受け取れるようにするためにAWSアカウントAからのアクセスを許可するポリシーを設定してます。(203~213行目)

デプロイは以下のAWS CLIコマンドを使用します。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --parameters ParameterKey=CodeBuildDescription,ParameterValue=CodeBuildの説明 ParameterKey=CodeBuildName,ParameterValue=CodeBuildの名前 ParameterKey=ApplicationName,ParameterValue=CodeDeployアプリケーション名 ParameterKey=DeploymentGroupName,ParameterValue=CodeDeployデプロイグループ名 ParameterKey=KMSARN,ParameterValue=KMSキーのARN ParameterKey=CodePipelineName,ParameterValue=CodePipelineの名前 ParameterKey=CodeCommitName,ParameterValue=CodeCommitリポジトリ名 ParameterKey=CodePipelineAssumeRole,ParameterValue=AWSアカウントAで作成したCodePipelineAssumeRole ParameterKey=AccountAId,ParameterValue=AWSアカウントAのID --capabilities CAPABILITY_NAMED_IAM

動作確認

リソースの作成が完了したらappspec.yml、buildspec.yml、index.htmlを作成します。
CodeCommitリポジトリを以下のドキュメントの手順でクローンしてください。
AWS CodeCommit リポジトリに接続する
クローンしたリポジトリで以下のファイルを作成していきます。

appspec.ymlは以下の内容で作成します。

version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/html
file_exists_behavior: OVERWRITE
permissions:
  - object: /var/www/html
    owner: apache
    group: apache
    mode: 755
    type:
      - file
      - directory
#hooks:
#      デプロイする際に動かすシェルスクリプトなどを設定するセクション

buildspec.ymlは以下の内容で作成します。

version: 0.2

phases:
  install:
    on-failure: ABORT
    commands:
      - echo "install phases"
      - echo "yum install などでビルドに使うパッケージをインストールするのに使用"
  pre_build:
    on-failure: ABORT
    commands:
      - echo "pre_build phases"
      - echo "Amazon ECR にサインインしたりするのに使用"
  build:
    on-failure: ABORT
    commands:
      - echo "build phases"
      - echo "ビルド、テストなどを実行するのに使用"
  post_build:
    on-failure: ABORT
    commands:
      - echo "post_build phases"
      - echo "Docker イメージをAmazon ECR にプッシュしたりZIPとかにアーカイブするのに使用"
artifacts:
  files:
    - "**/*"

index.htmlの中身は好きな内容でよいですが、今回は以下のようにしました。

<html>
<head>
<meta charset="UTF-8">
<title>テストページ</title>
</head>
<body bgcolor="#10100E" text="#cccccc">
小林 陸<br/>
</body>
</html>

ディレクトリ内は以下のようになります。

ls -la
total 24
drwxr-xr-x. 3 cloudshell-user cloudshell-user 4096 Mar 31 13:02 .
drwxrwxrwx. 5 cloudshell-user cloudshell-user 4096 Mar 31 13:02 ..
-rw-r--r--. 1 cloudshell-user cloudshell-user  360 Mar 30 16:13 appspec.yml
-rw-r--r--. 1 cloudshell-user cloudshell-user  762 Mar 30 16:13 buildspec.yml
drwxr-xr-x. 8 cloudshell-user cloudshell-user 4096 Mar 30 16:15 .git
-rw-r--r--. 1 cloudshell-user cloudshell-user  161 Mar 31 13:02 index.html

ファイルの作成が完了したら以下のコマンドでgit push を行います。

git checkout -b mani
git add .
git commit -m "test"
git push origin main

git pushに成功すると以下のようにCodePipelineが動きだしていることが確認できます。

デプロイが完了すると以下のようにEC2の「/var/www/html/」内にファイルが配置されることが確認できます。

ls -la /var/www/html/
total 12
drwxr-xr-x. 2 root   root    64 Mar 31 13:12 .
drwxr-xr-x. 4 root   root    33 Mar 30 16:44 ..
-rwxr-xr-x. 1 apache apache 360 Mar 31 13:11 appspec.yml
-rwxr-xr-x. 1 apache apache 762 Mar 31 13:11 buildspec.yml
-rwxr-xr-x. 1 apache apache 161 Mar 31 13:11 index.html

さいごに

クロスアカウントのデプロイになるとKMSやクロスアカウントロールが必要になるため設定が複雑になります。
GitHubやGitLabをご利用されている場合はCodePipelineのソースとして使用が可能なためこちらをご利用されるのもよいと思います。
GitHub 接続
GitLab セルフマネージド の接続