Use repository policies to prevent manual pushes to ECR in CodePipeline
The following pages cover the ECR Repository Policies.
The goal of this page is to use repository policies to prevent manual push/pull to the ECR repository within CodePipeline.
Environment
We will configure CodePipeline to link three resources.
The first is CodeCommit.
CodeCommit is responsible for the source stage of CodePipeline.
It is used as a Git repository.
The second is CodeBuild.
CodeBuild is in charge of the build stage of CodePipeline.
It builds a Docker image from code pushed to CodeCommit.
The built image is pushed to ECR.
The third is the Lambda function.
The function’s job is to change the desired number of tasks for the ECS (Fargate) service.
Specifically, it changes the desired number from 0 to 1.
Create a deploy stage in CodePipeline.
Configure it to deploy to Fargate as described below.
Save your DockerHub account information in the SSM parameter store.
These will be used to pull the base image when generating images with DockerBuild, after signing in to DockerHub.
The trigger for CodePipeline to be started is conditional on a push to CodeCommit.
Specifically, we will have a rule in EventBridge that satisfies the above.
Create a Fargate type ECS on a private subnet.
Set up a repository policies for the ECR repository.
- IAM role for CodeBuild: allow image push
- IAM role for ECS task execution: allow image pull
- All other resources: deny all actions
Create an EC2 instance.
Use it as a client to access containers created on Fargate.
In addition, grant full access rights to the ECR.
CloudFormation template files
The above configuration is built using CloudFormation.
The CloudFormation templates are located at the following URL
https://github.com/awstut-an-r/awstut-fa/tree/main/084
Explanation of key points of the template files
This page covers how to use repository policies to prevent manual push/pull to the ECR repository in CodePipeline.
For basic information on CodePipeline, please refer to the following page
For information on how to create a deployment stage in CodePipeline and deploy to ECS (Fargate), please check the following page
For information on how to define an action that calls a Lambda function in CodePipeline and change the desired number of ECS (Fargate) services, please see the following page
For information on how to build Fargate on a private subnet, please check the following page
Use CloudFormation custom resources to automatically delete objects in S3 buckets and images in ECR repositories when deleting the CloudFormation stack.
For more information, please see the following page
IAM Roles
IAM Roles for CodeBuild
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)
Attach the AWS management policy AmazonEC2ContainerRegistryPowerUser to allow push/pull, etc. to the ECR repository.
For your information, the contents of this policy are as follows
{
"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)
IAM role for ECS task execution
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)
Attach the AWS management policy AmazonECSTaskExecutionRolePolicy to allow pulls, etc. to the ECR repository.
For your information, the contents of this policy are as follows
{
"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)
IAM Roles for EC2 Instance
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)
Attach the AWS management policy AmazonEC2ContainerRegistryFullAccess to allow full access to the ECR repository.
For your information, the contents of this policy are as follows
{
"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)
Set the repository policy with the RepositoryPolicyText property.
The first, second, and fourth statements of the repository policy are the key points.
The first statement is for CodeBuild.
Use ArnEquals and aws:PrincipalArn in the Condition element to specify the ARN of the IAM role attached to CodeBuild.
Specify “Deny” in the Effect element and use the NotAction element to deny any action other than the one specified in NotAction.
Since we specified the action required for the image push in NotAction, CodeBuild will reject any action other than the push.
The second statement is for ECS task execution.
With the same notation as the first, it rejects any action other than an image pull for ECS task execution.
The third statement is for Lambda functions.
Since this is a function for a CloudFormation custom resource to delete an image in the ECR repository, it rejects any action other than deleting the image.
The fourth statement is for access from resources other than those listed above.
Use ArnNotEquals and aws:PrincipalArn in the Condition element to specify the ARN of the IAM role above and target resources other than those listed above.
By specifying “Deny” for the Effect element and “*” for the Action, all actions will be denied.
This means that all accesses other than those to the three resources will be denied.
So all accesses from EC2 instances will be denied.
(Reference) App Container
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)
The image for the app container will be based on Amazon Linux 2.
We will use Bottle, a Python web framework.
So after installing Python and pip, install this.
Copy the Python script (main.py) describing the app logic and set this to run.
As mentioned earlier, the app listens for HTTP requests on 8080/tcp, so expose this port.
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)
Use Bottle to build a simple web server.
The simple configuration is to listen for HTTP requests at 8080/tcp and return “Hello CodePipeline.
Architecting
We will use CloudFormation to build this environment and check the actual behavior.
Create CloudFormation stacks and check resources in stacks
Create CloudFormation stacks.
For information on how to create stacks and check each stack, please refer to the following page
After checking the resources in each stack, information on the main resources created this time is as follows
- ECR: fa-084
- CodeCommit: fa-084
- CodeBuild: fa-084
- CodePipeline: fa-084
- EC2 instance: i-0fc9dd4b8452544b4
Confirm the created resource from the AWS Management Console.
Check the ECR.
The ECR repository has been created.
Check the repository policies.
You can see that the repository policies has been created as defined in the CloudFormation templates.
Check the ECS (Fargate) cluster service.
The cluster service has been successfully created.
The key point is that the desired number is 0.
In other words, during the initial build of Fargate, not a single task was started.
Check CodePipeline.
The pipeline has failed to execute.
This is because the pipeline was triggered by the creation of CodeCommit when the CloudFormation stack was created.
Since we are not pushing code to CodeCommit at this time, an error occurred during the pipeline execution process.
Note the stage that is being created.
The stage named Invoke, which calls the Lambda function, is prepared at the very end of the pipeline.
This means that the code pushed by CodeCommit will be used to build a Docker image, deploy the image to Fargate, and then change the desired number of Fargate tasks.
Check Action
Now that we are ready, we push the code to CodeCommit.
First, pull CodeCommit.
$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/fa-084
Cloning into 'fa-084'...
Code language: Bash (bash)
An empty repository has been pulled.
Add the Dockerfile and main.py to the repository.
$ 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)
Push the two files to 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)
After waiting for a while, check CodePipeline again.
Pipeline completed successfully.
Check Fargate.
The desired number is now 1.
This means that the Lambda function has been executed and the desired number has been changed.
Since the desired number is now 1, an ECS task is automatically generated.
Looking at the details of this task, we see that the assigned private address is “10.0.2.234”.
It accesses the EC2 instance in order to make an HTTP request to the container.
The instance is accessed using SSM Session Manager.
% aws ssm start-session --target i-0fc9dd4b8452544b4
Starting session with SessionId: root-01ce255b73c1d7489
sh-4.2$
Code language: Bash (bash)
For more information on SSM Session Manager, please refer to the following page.
Use the curl command to access the container in the task.
sh-4.2$ curl http://10.0.3.70:8080
Hello CodePipeline.
Code language: Bash (bash)
The container responded.
This indicates that CodeBuild successfully pushed the image and pulled the image when the ECS task was executed, according to the repository policy.
Manually push/pull image
Checking the Operation of the Repository Policies by attempting to push/pull an image from an EC2 instance.
The first step is to push an image.
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)
The command to push an image to the ECR repository failed because it timed out during the push.
The next step is to pull the image.
[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)
Image pull also failed.
The IAM role attached to the EC2 instance allows full access to the ECR.
However, the repository policy denies the manual image push/pull action.
Summary
We have seen how repository policies can be used to prevent manual push/pull to the ECR repository within CodePipeline.