DEVELOPER BLOG

開発者ブログ

HOME > 開発者ブログ > AWSのCodePipelineとCodeCommitで、クロスアカウントにデプロイする - PrismScaler

AWSのCodePipelineとCodeCommitで、クロスアカウントにデプロイする - PrismScaler

1. はじめに

こんにちは!株式会社Definerの阪本です!
今回は、AWS CodePipelineとCodeCommitを使ってクロスアカウントにCI/CDパイプラインをデプロイする方法をご紹介したいと思います。

2. 目的・ユースケース

この記事では、AWSアカウントを跨いだCI/CD構築という目的に向けて、CodePipelineという技術を活用します。
ITの現場で、AWS CodePipelineとCodeCommitを使ってクロスアカウントにCI/CDパイプラインをデプロイしたいときに、参考になる情報やプラクティスをまとめています。

3. セットアップ1. KMSとS3

<アカウントA>CodeCommitへのPushをトリガーに、<アカウントB>CodePipelineでデプロイを行う機構を実装します。
今回は、クロスアカウントの権限周りを中心に説明していきます。
 
前提として、デプロイされるECSサービスやパイプラインの設定ファイルを保存するS3、CodeCommitは既に存在するものとします。
この辺りの構築については、「Codepipelineを使ってECSの自動デプロイ(CD)を実装する」を参考にしてください。
 
①<アカウントB>S3のバケットポリシー設定
以下のポリシーをS3に適用しました。
 
 
{
    "Version": "2012-10-17",
    "Id": "S3Policy",
    "Statement": [
        {
            "Sid": "CrossAccountS3GetPutPolicy",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::${アカウントA}:root"
            },
            "Action": [
                "s3:Get*",
                "s3:Put*"
            ],
            "Resource": "arn:aws:s3:::test-codepipeline-account-b/*"
        },
        {
            "Sid": "CrossAccountS3ListPolicy",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::${アカウントA}:root"
            },
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::test-codepipeline-account-b"
        }
    ]
}                
 

②<アカウントB>KMSのポリシー設定

KMSを作成し、以下のキーポリシーを適用しました。

 
{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy-3",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::${自分のアカウント(B)}:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow access for Key Administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::${自分のアカウント(B)}:user/${ユーザ名}"
            },
            "Action": [
                "kms:Create*",
                "kms:Describe*",
                "kms:Enable*",
                "kms:List*",
                "kms:Put*",
                "kms:Update*",
                "kms:Revoke*",
                "kms:Disable*",
                "kms:Get*",
                "kms:Delete*",
                "kms:TagResource",
                "kms:UntagResource",
                "kms:ScheduleKeyDeletion",
                "kms:CancelKeyDeletion"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow use of the key",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::${自分のアカウント(B)}:user/${ユーザ名}"
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow use of the key",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::${アカウントA}:root",
                    "arn:aws:iam::${自分のアカウント(B)}:role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-test-cicd"
                ]
            },
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncrypt*",
                "kms:GenerateDataKey*",
                "kms:DescribeKey"
            ],
            "Resource": "*"
        },
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::${アカウントA}:root",
                    "arn:aws:iam::${自分のアカウント(B)}:role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-test-cicd"
                ]
            },
            "Action": [
                "kms:CreateGrant",
                "kms:ListGrants",
                "kms:RevokeGrant"
            ],
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "kms:GrantIsForAWSResource": "true"
                }
            }
        },
        {
            "Sid": "Allow attachment of persistent resources",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::${自分のアカウント()}:user/${ユーザ名}"
            },
            "Action": [
                "kms:CreateGrant",
                "kms:ListGrants",
                "kms:RevokeGrant"
            ],
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "kms:GrantIsForAWSResource": "true"
                }
            }
        }
    ]
}                  

4. セットアップ2. IAMロール

続いて、IAMを設定していきます。
 
①<アカウントB>CodePipelineのIAMロールの設定
以下のポリシーを使用しました。
{
    "Statement": [
        {
            "Action": [
                "iam:PassRole"
            ],
            "Resource": "*",
            "Effect": "Allow",
            "Condition": {
                "StringEqualsIfExists": {
                    "iam:PassedToService": [
                        "cloudformation.amazonaws.com",
                        "elasticbeanstalk.amazonaws.com",
                        "ec2.amazonaws.com",
                        "ecs-tasks.amazonaws.com"
                    ]
                }
            }
        },
        {
            "Action": [
                "codecommit:CancelUploadArchive",
                "codecommit:GetBranch",
                "codecommit:GetCommit",
                "codecommit:GetRepository",
                "codecommit:GetUploadArchiveStatus",
                "codecommit:UploadArchive"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Sid": "AssumeRolePolicy",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": [
                "arn:aws:iam::201920823605:role/*"
            ]
        },
        {
            "Action": [
                "codedeploy:CreateDeployment",
                "codedeploy:GetApplication",
                "codedeploy:GetApplicationRevision",
                "codedeploy:GetDeployment",
                "codedeploy:GetDeploymentConfig",
                "codedeploy:RegisterApplicationRevision"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "codestar-connections:UseConnection"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "elasticbeanstalk:*",
                "ec2:*",
                "elasticloadbalancing:*",
                "autoscaling:*",
                "cloudwatch:*",
                "s3:*",
                "sns:*",
                "cloudformation:*",
                "rds:*",
                "sqs:*",
                "ecs:*"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "lambda:InvokeFunction",
                "lambda:ListFunctions"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "opsworks:CreateDeployment",
                "opsworks:DescribeApps",
                "opsworks:DescribeCommands",
                "opsworks:DescribeDeployments",
                "opsworks:DescribeInstances",
                "opsworks:DescribeStacks",
                "opsworks:UpdateApp",
                "opsworks:UpdateStack"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "cloudformation:CreateStack",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeStacks",
                "cloudformation:UpdateStack",
                "cloudformation:CreateChangeSet",
                "cloudformation:DeleteChangeSet",
                "cloudformation:DescribeChangeSet",
                "cloudformation:ExecuteChangeSet",
                "cloudformation:SetStackPolicy",
                "cloudformation:ValidateTemplate"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "codebuild:BatchGetBuilds",
                "codebuild:StartBuild",
                "codebuild:BatchGetBuildBatches",
                "codebuild:StartBuildBatch"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Effect": "Allow",
            "Action": [
                "devicefarm:ListProjects",
                "devicefarm:ListDevicePools",
                "devicefarm:GetRun",
                "devicefarm:GetUpload",
                "devicefarm:CreateUpload",
                "devicefarm:ScheduleRun"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "servicecatalog:ListProvisioningArtifacts",
                "servicecatalog:CreateProvisioningArtifact",
                "servicecatalog:DescribeProvisioningArtifact",
                "servicecatalog:DeleteProvisioningArtifact",
                "servicecatalog:UpdateProduct"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:ValidateTemplate"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ecr:DescribeImages"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "states:DescribeExecution",
                "states:DescribeStateMachine",
                "states:StartExecution"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "appconfig:StartDeployment",
                "appconfig:StopDeployment",
                "appconfig:GetDeployment"
            ],
            "Resource": "*"
        }
    ],
    "Version": "2012-10-17"
}                
 

②<アカウントA>CodeCommitのIAMロールの作成

以下のポリシーを設定しました。

 
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "UploadArtifactPolicy",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::test-codepipeline-account-b/*"
            ]
        },
        {
            "Sid": "KMSAccessPolicy",
            "Effect": "Allow",
            "Action": [
                "kms:DescribeKey",
                "kms:GenerateDataKey*",
                "kms:Encrypt",
                "kms:ReEncrypt*",
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:${アカウントB}:key/${KMSのID}"
            ]
        },
        {
            "Sid": "CodeCommitAccessPolicy",
            "Effect": "Allow",
            "Action": [
                "codecommit:GetBranch",
                "codecommit:GetCommit",
                "codecommit:UploadArchive",
                "codecommit:GetUploadArchiveStatus",
                "codecommit:CancelUploadArchive"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}                  

5. パイプライン構築

続いて、パイプラインを構築していきます。
 
①<アカウントB>CodePipeline構築
クロスアカウントはコンソールからは設定できないようなので、CLIで作成します。
インプットとして、「input.son」ファイルを用意します。
 
パイプライン構築後、実際にアカウントBのCodeCommitにPushすると、アカウントAのパイプラインが動くことが確認できました!
 
## CodePipeline作成コマンド
aws codepipeline update-pipeline --cli-input-json file://input.json                
 

input.jsonファイル
{
    "pipeline": {
        "name": "test-cicd-cross-account-b",
        "roleArn": "arn:aws:iam::${アカウントB}:role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-test-cicd",
        "artifactStore": {
            "type": "S3",
            "location": "test-codepipeline-account-b",
            "encryptionKey": {
                "id": "arn:aws:kms:ap-northeast-1:${アカウントB}:alias/test-codepipeline",
                "type": "KMS"
            }
        },
        "stages": [
            {
                "name": "Source",
                "actions": [
                    {
                        "name": "Source",
                        "actionTypeId": {
                            "category": "Source",
                            "owner": "AWS",
                            "provider": "CodeCommit",
                            "version": "1"
                        },
                        "runOrder": 1,
                        "configuration": {
                            "RepositoryName": "test-account-a",
                            "BranchName": "main"
                        }, 
                        "outputArtifacts": [
                            {
                                "name": "SourceArtifact"
                            }
                        ],
                        "inputArtifacts": [],
                        "roleArn": "arn:aws:iam::${アカウントA}:role/codecommit-account-a"
                    },
                    {
                        "name": "s3",
                        "actionTypeId": {
                            "category": "Source",
                            "owner": "AWS",
                            "provider": "S3",
                            "version": "1"
                        },
                        "runOrder": 1,
                        "configuration": {
                            "PollForSourceChanges": "false",
                            "S3Bucket": "test-codepipeline-account-b",
                            "S3ObjectKey": "imagedefinitions.json.zip"
                        },
                        "outputArtifacts": [
                            {
                                "name": "s3output"
                            }
                        ],
                        "inputArtifacts": [],
                        "region": "ap-northeast-1"
                    }
                ]
            },
            {
                "name": "Deploy",
                "actions": [
                    {
                        "name": "Deploy",
                        "actionTypeId": {
                            "category": "Deploy",
                            "owner": "AWS",
                            "provider": "ECS",
                            "version": "1"
                        },
                        "runOrder": 1,
                        "configuration": {
                            "ClusterName": "default",
                            "ServiceName": "test-nginx"
                        },
                        "outputArtifacts": [],
                        "inputArtifacts": [
                            {
                                "name": "s3output"
                            }
                        ],
                        "region": "ap-northeast-1",
                        "namespace": "DeployVariables"
                    }
                ]
            }
        ],
        "version": 2
    }
}                  

6. 引用・参考記事

7. 独自ソリューション「PrismScaler」について

・ PrismScalerは、開発・運用を要さずにたった3ステップで、AWSやAzure、GCPなどのマルチクラウド基盤構築を実現するWebサービスです。
・ クラウド基盤にまつわる「自動構築」「自動監視」「問題検知」「構成可視化」などの効率化を実現し、クラウドエンジニア、SRE/DevOpsエンジニアが行う泥臭く大変な作業を肩代わりするソリューションです。
・ クラウド基盤構築/クラウド移行や、クラウドの保守運用・コスト最適化など幅広い利用シーンを想定しており、IaaSやPaaSを適切に組み合わせた数百を超える高品質な汎用クラウド基盤を容易に実現できます。
 

8. お問合せ

本記事では、入門編として有益な情報を無料公開しています。ご相談やお問い合わせは「株式会社Definer」へ。

9. Definerに関して。

・ Definer Incは、ITの上流から下流まで一気通貫のワンストップソリューションをご提供しております。
・ AIやクラウドのITインフラなど、先進的なIT技術のコンサルティングから要件定義 / 設計開発 / 実装、保守運用に至るまでの統合的な支援にコミットしています。
・ DevOpsとCI/CDコンサルティングにより「少ないエンジニアで事業が成長する仕組みづくり」「エンジニアが喜ぶ、採用しやすい環境づくり」「高速なアジャイル開発環境」を実現しています。
・ また、自社プロダクトとしてPrismScalerを展開しております。PrismScalerは、AWS、Azure、GCPなどのマルチクラウド / ITインフラの高品質かつ迅速な、「自動構築」「自動監視」「問題検知」「構成可視化」を実現します。