AWS

リポジトリポリシーを利用してCodePipeline内のECRへの手動プッシュを防止する

スポンサーリンク
リポジトリポリシーを利用してCodePipeline内のECRへの手動プッシュを防止する AWS
スポンサーリンク
スポンサーリンク

リポジトリポリシーを利用して、CodePipeline内のECRへの手動プッシュを防止する

以下のページで、ECRリポジトリポリシーについて取り上げました。

本ページでは、リポジトリポリシーを使用して、CodePipeline内のECRリポジトリへの手動プッシュ/プルを防止することを目標とします。

構築する環境

Diagram of use repository policies to prevent manual pushes to the ECR in CodePipeline

CodePipelineを構成し、3つのリソースを連携させます。

1つ目はCodeCommitです。
CodeCommitはCodePipelineのソースステージを担当します。
Gitリポジトリとして使用します。

2つ目はCodeBuildです。
CodeBuildはCodePipelineのビルドステージを担当します。
CodeCommitにプッシュされたコードから、Dockerイメージをビルドします。
ビルドしたイメージをECRにプッシュします。

3つ目はLambda関数です。
この関数の働きは、ECS(Fargate)サービスのタスク希望数を変更することです。
具体的には希望数を0から1に変更します。

CodePipelineにデプロイステージを作成します。
後述のFargateにデプロイするように設定します。

SSMパラメータストアにDockerHubアカウント情報を保存します。
DockerBuildでイメージを生成する際に、DockerHubにサインインした上でベースイメージをプルするために、これらを使用します。

CodePipelineが開始されるきっかけですが、CodeCommitへのプッシュを条件とします。
具体的には、EventBridgeに上記を満たすルールを用意します。

プライベートサブネットに、FargateタイプのECSを作成します。

ECRリポジトリにリポジトリポリシーを設定します。

  • CodeBuild用IAMロール:イメージのプッシュを許可する
  • ECSタスク実行用IAMロール:イメージのプルを許可する
  • それ以外のリソース:全てのアクションを拒否する

EC2インスタンスを作成します。
Fargate上に作成されたコンテナにアクセスするためのクライアントして使用します。
加えて、ECRに関するフルアクセス権限を付与します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/084 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

本ページはリポジトリポリシーを使用して、CodePipeline内のECRリポジトリへの手動プッシュ/プルを防止する方法を取り上げます。

CodePipelineに関する基本的な事項については、以下のページをご確認ください。

CodePipelineにデプロイステージを作成し、ECS(Fargate)にデプロイする方法については、以下のページをご確認ください。

CodePipeline内でLambda関数を呼び出すアクションを定義し、ECS(Fargate)サービスの希望数を変更する方法については、以下のページをご確認ください。

プライベートサブネットにFargateを構築する方法については、以下のページをご確認ください。

CloudFormationカスタムリソースを使用して、CloudFormationスタック削除時に、自動的にS3バケット内のオブジェクトと、ECRリポジトリ内のイメージを削除します。
詳細につきましては、以下のページをご確認ください。

IAMロール

CodeBuild用IAMロール

Resources: CodeBuildRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codebuild.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser Policies: - PolicyName: PipelineExecutionPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ssm:GetParameters Resource: - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SSMParameterDockerHubPassword}" - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SSMParameterDockerHubUsername}" - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:GetObjectVersion - s3:GetBucketAcl - s3:GetBucketLocation Resource: - !Sub "arn:aws:s3:::${BucketName}" - !Sub "arn:aws:s3:::${BucketName}/*"
Code language: YAML (yaml)

AWS管理ポリシーAmazonEC2ContainerRegistryPowerUserをアタッチして、ECRリポジトリへのプッシュ/プル等を許可します。
ちなみに本ポリシーの内容は以下の通りです。

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:GetRepositoryPolicy", "ecr:DescribeRepositories", "ecr:ListImages", "ecr:DescribeImages", "ecr:BatchGetImage", "ecr:GetLifecyclePolicy", "ecr:GetLifecyclePolicyPreview", "ecr:ListTagsForResource", "ecr:DescribeImageScanFindings", "ecr:InitiateLayerUpload", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", "ecr:PutImage" ], "Resource": "*" } ] }
Code language: JSON / JSON with Comments (json)

ECSタスク実行用IAMロール

Resources: FargateTaskExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ecs-tasks.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Code language: YAML (yaml)

AWS管理ポリシーAmazonECSTaskExecutionRolePolicyをアタッチして、ECRリポジトリへのプル等を許可します。
ちなみに本ポリシーの内容は以下の通りです。

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "*" } ] }
Code language: JSON / JSON with Comments (json)

EC2インスタンス用IAMロール

Resources: InstanceRole: Type: AWS::IAM::Role DeletionPolicy: Delete Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: sts:AssumeRole Principal: Service: - ec2.amazonaws.com ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess
Code language: JSON / JSON with Comments (json)

AWS管理ポリシーAmazonEC2ContainerRegistryFullAccessをアタッチして、ECRリポジトリへのフルアクセスを許可します。
ちなみに本ポリシーの内容は以下の通りです。

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecr:*", "cloudtrail:LookupEvents" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "iam:CreateServiceLinkedRole" ], "Resource": "*", "Condition": { "StringEquals": { "iam:AWSServiceName": [ "replication.ecr.amazonaws.com" ] } } } ] }
Code language: JSON / JSON with Comments (json)

ECR

Resources: ECRRepository: Type: AWS::ECR::Repository Properties: RepositoryName: !Ref Prefix RepositoryPolicyText: Version: 2012-10-17 Statement: - Effect: Deny Principal: "*" Action: - ecr:BatchCheckLayerAvailability - ecr:CompleteLayerUpload - ecr:InitiateLayerUpload - ecr:PutImage - ecr:UploadLayerPart Condition: ArnLike: aws:SourceArn: CodeBuildRoleArn - Effect: Deny Principal: "*" Action: - ecr:BatchGetImage - ecr:GetDownloadUrlForLayer Condition: ArnLike: aws:SourceArn: FargateTaskExecutionRoleArn - Effect: Deny Principal: "*" NotAction: - ecr:BatchDeleteImage - ecr:ListImages Condition: ArnEquals: aws:PrincipalArn: !GetAtt ECRFunctionRole.Arn - Effect: Deny Principal: "*" Action: "*" Condition: ArnNotEquals: aws:PrincipalArn: - !Ref CodeBuildRoleArn - !Ref FargateTaskExecutionRoleArn - !GetAtt ECRFunctionRole.Arn
Code language: YAML (yaml)

RepositoryPolicyTextプロパティでリポジトリポリシーを設定します。

リポジトリポリシーの1, 2, 4番目のステートメントがポイントです。

1番目のステートメントはCodeBuild用です。
Condition要素でArnEqualsおよびaws:PrincipalArnを使用して、CodeBuildにアタッチしたIAMロールのARNを指定します。
Effect要素に「Deny」を指定した上で、NotAction要素を使用することによって、NotActionで指定されたアクション以外を拒否することになります。
NotActionにはイメージプッシュに必要なアクションを指定しましたので、CodeBuildはプッシュ以外のアクションが拒否されます。

2番目のステートメントはECSタスク実行用です。
1つ目と同様の記法で、ECSタスク実行にイメージプル以外のアクションを拒否します。

3番目のステートメントはLambda関数用です。
ECRリポジトリ内のイメージを削除するためのCloudFormationカスタムリソース用の関数ですから、イメージを削除する以外のアクションを拒否します。

4番目のステートメントは上記以外のリソースからアクセス用です。
Condition要素でArnNotEqualsおよびaws:PrincipalArnを使用して、上述のIAMロールのARNを指定し、これら以外のリソースを対象とします。
Effect要素に「Deny」を、Actionに「*」を指定することによって、全てのアクションを拒否することになります。
つまり3つのリソース以外のアクセスの場合は、全て拒否されるということです。
ですからEC2インスタンスからのアクセスは全て拒否されます。

(参照)アプリコンテナ

Dockerfile

FROM amazonlinux RUN yum update -y && yum install python3 python3-pip -y RUN pip3 install bottle COPY main.py ./ CMD ["python3", "main.py"] EXPOSE 8080
Code language: Dockerfile (dockerfile)

アプリコンテナ用のイメージは、Amazon Linux 2をベースとして作成します。

Python製WebフレームワークのBottleを使用します。
ですからPythonおよびpipをインストール後、これをインストールします。

アプリロジックを記載したPythonスクリプト(main.py)をコピーし、これを実行するように設定します。

先述の通り、アプリは8080/tcpでHTTPリクエストを待ち受けますので、このポートを公開します。

main.py

from bottle import route, run @route('/') def hello(): return 'Hello CodePipeline.' if __name__ == '__main__': run(host='0.0.0.0', port=8080)
Code language: Python (python)

Bottleを使用して、簡易的なWebサーバを構築します。
8080/tcpでHTTPリクエストを待ち受け、「Hello CodePipeline.」を返すという単純な構成です。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • ECR:fa-084
  • CodeCommit:fa-084
  • CodeBuild:fa-084
  • CodePipeline:fa-084
  • EC2インスタンス:i-0fc9dd4b8452544b4

作成されたリソースをAWS Management Consoleから確認します。
ECRを確認します。

Detail of ECR 1.

ECRリポジトリが作成されています。

リポジトリポリシーを確認します。

Detail of ECR 2.

CloudFormationテンプレートで定義した通りに、リポジトリポリシーが作成されていることがわかります。

ECS(Fargate)のクラスター・サービスを確認します。

Detail of ECS 1.
Detail of ECS 2.

正常にクラスター・サービスが作成されています。
ポイントは希望数が0となっている点です。
つまりFargateの初期構築時は、1つもタスクが起動していません。

CodePipelineを確認します。

Detail of CodePipeline 1.

パイプラインの実行に失敗しています。
これはCloudFormationスタック作成時に、CodeCommitが作成されたことがきっかけとして、パイプラインが実行されたためです。
現時点ではCodeCommitにコードをプッシュしていないため、パイプライン実行の過程でエラーが発生しました。

作成されているステージに注目します。
Invokeという名前で、Lambda関数を呼び出すステージが、パイプラインの一番最後に用意されています。
つまりCodeCommitによってプッシュされたコードから、Dockerイメージをビルドして、Fargateにイメージをデプロイした後に、Fargateタスクの希望数を変更するという流れになります。

動作確認

準備が整いましたので、CodeCommitにコードをプッシュします。

まずCodeCommitをプルします。

$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/fa-084 Cloning into 'fa-084'...
Code language: Bash (bash)

空のリポジトリがプルされました。

リポジトリにDockerfileおよびmain.pyを加えます。

$ ls -al total 8 drwxrwxr-x 3 ec2-user ec2-user 51 Sep 19 05:29 . drwxrwxr-x 3 ec2-user ec2-user 20 Sep 19 05:29 .. -rw-rw-r-- 1 ec2-user ec2-user 187 Aug 12 11:01 Dockerfile drwxrwxr-x 7 ec2-user ec2-user 119 Sep 19 05:28 .git -rw-rw-r-- 1 ec2-user ec2-user 681 Sep 19 05:25 main.py
Code language: Bash (bash)

2ファイルをCodeCommitにプッシュします。

$ git add . $ git commit -m "first commit" [master (root-commit) 34a0238] first commit ... 2 files changed, 39 insertions(+) create mode 100644 Dockerfile create mode 100644 main.py $ git push ... * [new branch] master -> master
Code language: Bash (bash)

しばらく待機した後、改めてCodePipelineを確認します。

Detail of CodePipeline 2.

パイプラインが正常に完了しました。

Fargateを確認します。

Detail of ECS 3.
Detail of ECS 4.

希望数が1になりました。
Lambda関数が実行されて希望数が変更されたということです。
希望数が1となったため、ECSタスクが自動生成されました。
このタスクの詳細を見ると、割り当てられたプライベートアドレスが「10.0.2.234」であることがわかります。

コンテナにHTTPリクエストを行うために、EC2インスタンスにアクセスします。
インスタンスへのアクセスはSSM Session Managerを使用します。

% aws ssm start-session --target i-0fc9dd4b8452544b4 Starting session with SessionId: root-01ce255b73c1d7489 sh-4.2$
Code language: Bash (bash)

SSM Session Managerの詳細につきましては、以下のページをご確認ください。

curlコマンドを使って、タスク内のコンテナにアクセスします。

sh-4.2$ curl http://10.0.3.70:8080 Hello CodePipeline.
Code language: Bash (bash)

コンテナから応答がありました。
このことからリポジトリポリシーに従って、CodeBuildによるイメージのプッシュと、ECSタスク実行時のイメージのプルが、正常に行われたことがわかります。

手動でイメージプッシュ/プル

EC2インスタンスからイメージのプッシュ/プルを試みて、リポジトリポリシーの動作を確認します。
まずイメージのプッシュです。

sh-4.2$ sudo su --login ec2-user [ec2-user@ip-10-0-2-214 ~]$ echo "FROM amazonlinux" > Dockerfile [ec2-user@ip-10-0-2-214 ~]$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin [account-id].dkr.ecr.ap-northeast-1.amazonaws.com ... Login Succeeded [ec2-user@ip-10-0-2-214 ~]$ docker build -t fa-084 . Sending build context to Docker daemon 341kB Step 1/1 : FROM amazonlinux latest: Pulling from library/amazonlinux 12df598bc31e: Pull complete Digest: sha256:cb8a67164376ecca3b9993e6bb7d81dd868b7836d2631582becd140c8edf27bf Status: Downloaded newer image for amazonlinux:latest ---> 06c59c262be8 Successfully built 06c59c262be8 Successfully tagged fa-084:latest [ec2-user@ip-10-0-2-214 ~]$ docker tag fa-084:latest [account-id].dkr.ecr.ap-northeast-1.amazonaws.com/fa-084:latest [ec2-user@ip-10-0-2-214 ~]$ docker push [account-id].dkr.ecr.ap-northeast-1.amazonaws.com/fa-084:latest The push refers to repository [[account-id].dkr.ecr.ap-northeast-1.amazonaws.com/fa-084] bb99a8750875: Retrying in 1 second EOF
Code language: Bash (bash)

ECRリポジトリにイメージをプッシュするコマンドを実行した結果、プッシュ時にタイムアウトして失敗しました。

続いてイメージのプルです。

[ec2-user@ip-10-0-2-214 ~]$ docker pull [account-id].dkr.ecr.ap-northeast-1.amazonaws.com/fa-084:latest Error response from daemon: pull access denied for [account-id].dkr.ecr.ap-northeast-1.amazonaws.com/fa-084, repository does not exist or may require 'docker login': denied: User: arn:aws:sts::[account-id]:assumed-role/fa-084-EC2Stack-1JOEIS9JU31P0-InstanceRole-GI0SW6JN8M66/i-0fc9dd4b8452544b4 is not authorized to perform: ecr:BatchGetImage on resource: arn:aws:ecr:ap-northeast-1:[account-id]:repository/fa-084 with an explicit deny in a resource-based policy
Code language: Bash (bash)

イメージのプルも失敗しました。

EC2インスタンスにアタッチしたIAMロールでは、ECRへのフルアクセスを許可しています。
しかしリポジトリポリシーによって、手動によるイメージのプッシュ/プルのアクションは拒否されたということです。

まとめ

リポジトリポリシーを使用して、CodePipeline内のECRリポジトリへの手動プッシュ/プルを防止する方法を確認しました。

タイトルとURLをコピーしました