前回投稿ではパイプラインなしでAWS クロスアカウントデプロイをやった。次はパイプラインを使ってやってみる。長くなるので前半/後半に分ける。
やりたいこと
AWSの異なるアカウント間で、CodePipelineによりCodeDeployからec2インスタンスにリソースをデプロイする。ソースはリソース配布側のCodeCommit。この記事では配布元を開発環境/アカウントA、配布先を検証環境/アカウントBとして話を進める。(ec2はオートスケールもなくただ単に配布するだけなので単一アカウントだったら簡単な話なんだが、アカウント跨ぐとなるとめっちゃ面倒くさい…)
主な参考ページ
他のリソースを使用するパイプラインを CodePipeline で作成するAWSアカウント
基本的にこのページの通りにやればOK。アカウントA側で一度単一アカウント用の適当なパイプラインを作成して、そのJSON定義を取得。それをクロスアカウント用に編集してCLIからアップデートする。ちなみに上記リンクは日本語版だが機械翻訳の文章がまともな日本語ではなくイラッとくるので、ほぼオリジナルの英語版を参考にした。
参考までに、以下クラメソさんの記事。当初これのBuildをDeployに置き換えてやってみたが失敗した。不足か誤りがあるんだろうがいきなりやったこともありわけがわからなすぎて頓挫。先述のAWS公式の方がやりたいことに近かったため仕切り直しした。
クロスアカウントCodeBuild + パイプライン例
CodePipelineでアカウントをまたいだパイプラインを作成してみる
制約事項
- クロスアカウントのパイプラインはマネジメントコンソールから作成不可のため、aws cliから作成/更新する
- CodeDeployの定義とデプロイ先のec2は同一アカウントであること
- クロスアカウントでパイプラインを組む場合、アーティファクト格納用S3バケットの暗号化キーはKMSを使用する(AWS デフォルトの暗号化キーはNG)
主な構成要素
2アカウント間で各種アイテムを用意することになり、混乱しがちなのでまとめておく。前回投稿では配布先となるアカウントB側にS3バケットがある構成だったが、今回は逆。ただし構成的にはこちらの方が自然かと思う。
1-資材配布元(アカウントA)
① CodeCommitリポジトリ(ec2にローカルリポジトリを作成〜資材格納)
② KMSキー (両方のアカウントにアクセス許可する)
③ S3バケット (アカウントBにアクセス許可するバケットポリシーを付与)
④ CodePipelineが使用するサービスロール
⑤ CodePipleline定義(コンソールで作成したパイプライン定義JSONをCLIから更新)
JSON取得コマンド
$ aws codepipeline get-pipeline --name [パイプライン名] > [パイプライン名].json
2-資材配布先(アカウントB)
① CodeDeploy定義(アプリケーション/デプロイメントグループ)
② ec2用のIAMロール(CodeDeployがアカウントAのKMSキー、S3にアクセスするためのポリシーを付与)
③ ②のIAMロールをアタッチしたデプロイ先ec2
④ クロスアカウント用サービスロール(CodeDeployとS3操作にassumeする)
作業概要
上記各リソースを作成済として、以下の作業を行う。
アカウントAの作業用端末またはec2にログイン。1-⑤のパイプライン定義JSONを適当なパスに配置し、パイプラインをアップデートする
$ cd /path/to/json
$ aws codepipeline update-pipeline --cli-input-json file://[パイプライン名].json
アップデートしたパイプラインを実行する
$ aws codepipeline start-pipeline-execution --name [パイプライン名]
アカウントBでは特に作業なし。デプロイステータスが成功になったら、ec2に資材がデプロイされていることを確認する。
クロスアカウントパイプラインの処理中の見え方
アカウントAのマネジメントコンソール:パイプライン全体の処理状況は見える。デプロイステージの詳細は見れない。
アカウントBのコンソール : デプロイの詳細が見れる
各種アイテムのサンプル
AWS公式でも基本内容は網羅されているが自分用メモとしてここにも載せておく。
アカウントA側アイテム
1-② KMSキーポリシー
アーティファクト用S3バケットの暗号化キーポリシー
{
"Version": "2012-10-17",
"Id": "[key-policy-name]",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[アカウントAのID]:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow access for Key Administrators",
"Effect": "Allow",
"Principal": {
"AWS": "[KMSの暗号化キーの管理ユーザのARN]" #アカウントA側のキーのオーナーを指定
},
"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": [
"[アカウントAのパイプライン用サービスロールのARN]", #(注1)
"arn:aws:iam::[アカウントBのID]:root"
]
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "Allow attachment of persistent resources",
"Effect": "Allow",
"Principal": {
"AWS": [
"[アカウントAのパイプライン用サービスロールのARN]", #(注1)
"arn:aws:iam::[アカウントBのID]:root"
]
},
"Action": [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
],
"Resource": "*",
"Condition": {
"Bool": {
"kms:GrantIsForAWSResource": "true"
}
}
}
]
}
(注1) 構文
arn:aws:iam::[アカウントAのID]:role/[パイプラインロール名]
1-③ S3バケットポリシー
{
"Version": "2012-10-17",
"Id": "SSEAndSSLPolicy",
"Statement": [
{
"Sid": "DenyUnEncryptedObjectUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::[S3バケット名]/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "DenyInsecureConnections",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::[S3バケット名]/*",
"Condition": {
"Bool": {
"aws:SecureTransport": false
}
}
},
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[アカウントBのID]:root"
},
"Action": [
"s3:Get*",
"s3:Put*"
],
"Resource": "arn:aws:s3:::[S3バケット名]/*"
},
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[アカウントBのID]:root"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::[S3バケット名]"
}
]
}
1-④ CodePipelineが使用するサービスロール(IAM)
このロールには以下のポリシーを割り当てる。1.はロール作成前に作成可能。コンソールからロール作成する時に選択可能なサービスにCodePipelineがないので一旦CodeDeployで作成して、後から信頼ポリシー編集した。
- 通常のCodePipeline作業用ポリシー
- アカウントBのassume用インラインポリシー
- 信頼ポリシー(編集)
以下CodePipeline作業用ポリシー。AWSが自動で付与するポリシーは他のパーミッションも多く含まれているがそこから削って最小限にしたのがこれ。autoscalingは使わないんだけど一応残す。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"iam:PassRole"
],
"Resource": "*",
"Effect": "Allow",
"Condition": {
"StringEqualsIfExists": {
"iam:PassedToService": [
"ec2.amazonaws.com"
]
}
}
},
{
"Action": [
"codecommit:CancelUploadArchive",
"codecommit:GetBranch",
"codecommit:GetCommit",
"codecommit:GetUploadArchiveStatus",
"codecommit:UploadArchive"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"codedeploy:CreateDeployment",
"codedeploy:GetApplication",
"codedeploy:GetApplicationRevision",
"codedeploy:GetDeployment",
"codedeploy:GetDeploymentConfig",
"codedeploy:RegisterApplicationRevision"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"ec2:*",
"elasticloadbalancing:*",
"autoscaling:*",
"cloudwatch:*",
"s3:*",
"tag:*",
"logs:*"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
アカウントBのassume用インラインポリシー
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::[アカウントBのID]:role/*"
]
}
}
信頼ポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"codepipeline.amazonaws.com",
"codedeploy.amazonaws.com",
"ec2.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
1-⑤ CodePiplelineのJSON定義
冒頭のAWS公式ページにもポイントは記載されているが、今回のケースでまとめたのがこれ。繰り返しになるが、「1.コンソールで単体アカウント用にパイプラインを作成 2. そのJSON定義を取得してクロスアカウント向けに編集 3. パイプラインをアップデート」という流れになる。最初にパイプラインを作成するときにCodeDeploy用定義が必要なため、事前にアカウントA内で適当なCodeDeployアプリケーション/デプロイメントグループのペアを用意しておく。
以下のJSON後半でアカウントB側のアイテム定義名をいくつか記載しており、当然アカウントB側に実体が存在する前提だが、編集時点では存在していなくても構わない。
{
"pipeline": {
"name": "A_crossdeploy_pipeline",
"roleArn": "arn:aws:iam::[アカウントAのID]:role/[クロスアカウントパイプライン用IAMロール名]",
"artifactStore": {
"type": "S3",
"location": "[S3バケット名]"
"encryptionKey": {
"id": "arn:aws:kms:us-east-1:[アカウントAのID]:key/[キーのID]", #KMSキーのARN
"type": "KMS"
},
"stages": [
{
"name": "Source",
"actions": [
{
"name": "Source",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "CodeCommit",
"version": "1"
},
"runOrder": 1,
"configuration": {
"BranchName": "master",
"OutputArtifactFormat": "CODE_ZIP",
"PollForSourceChanges": "true",
"RepositoryName": "[ソースリポジトリ名]"
},
"outputArtifacts": [
{
"name": "SourceArtifact"
}
],
"roleArn" : "arn:aws:iam::[アカウントAのID]:role/[クロスアカウントパイプライン用IAMロール名]", #冒頭で指定したIAMと同じ
"inputArtifacts": [],
"region": "ap-northeast-1",
"namespace": "SourceVariables"
}
]
},
{
"name": "Deploy",
"actions": [
{
"name": "ExternalDeploy", #Deploy --> ExternalDeployに変更
"actionTypeId": {
"category": "Deploy",
"owner": "AWS",
"provider": "CodeDeploy",
"version": "1"
},
"runOrder": 1,
"configuration": {
"ApplicationName": "[アカウントBのコードデプロイアプリケーション名]",
"DeploymentGroupName": "[アカウントBのコードデプロイデプロイメントグループ名]"
},
"outputArtifacts": [],
"inputArtifacts": [
{
"name": "SourceArtifact"
}
],
"roleArn" : "arn:aws:iam::[アカウントBのID]:role/[アカウントBのクロスデプロイ用IAMロール名]",
"region": "ap-northeast-1",
"namespace": "DeployVariables"
}
]
}
],
"version": 1
}
}
やっとここまできた…長かった。しかしまだ道は続く。次回、アカウントB側の設定内容を書く。
続き
AWS CodeDeployでクロスアカウントデプロイ実行(パイプラインあり-2)