Skip to content

Commit

Permalink
Added a minimal ECR image for the signing build environment (#3239)
Browse files Browse the repository at this point in the history
* added a dockerfile that can build a minimal signing environment image

* added a buildspec that tells the codebuild project how to build a minimal signing image

* added a stack that builds a minimal image, puts it in ecr and periodically triggers if selected

* added an option to provide a custom signer image if necessary

* added some details about the minimal build as well as details about the options

* had the wrong role in the wrong place ☠️
  • Loading branch information
YashdalfTheGray authored Jun 6, 2022
1 parent 90dcc9f commit cba1b38
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 6 deletions.
14 changes: 14 additions & 0 deletions build-infrastructure/Dockerfile.signer
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Pull from Public ECR because less likely to get throttled
FROM public.ecr.aws/amazonlinux/amazonlinux:2

# Assign a build tag because if we ever run into issues with
# this image, we'll be able to look back at the image tag
# and figure out what went wrong
# To build this image, run
# docker run -t <your_repo>:<tag_you_want> --build-arg builddate=$(date +%Y%m%d) .
# For reference date +%Y%m%d outputs today's date in YYYYMMDD format
# We also tag this image with the same tag in ECR
ARG builddate
ENV ECS_AGENT_SIGNING_IMAGE_TAG="build-${builddate}"

RUN yum install -y awscli gpg jq
21 changes: 17 additions & 4 deletions build-infrastructure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,24 @@ gpg --full-generate-key
gpg --output private.gpg --armor --export-secret-key <email>
```

A couple of things to keep in mind. If you're using Amazon Linux, it comes with a slightly older `gpg` executable so it doesn't _yet_ support ECC keys; best bet is to pick RSA 4096-bit. You will also need a passphrase for the key because it's better security.
A couple of things to keep in mind. If you're using Amazon Linux (or the AL2 container image), it comes with a slightly older `gpg` executable so it doesn't _yet_ support ECC keys; best bet is to pick RSA 4096-bit. You will also need a passphrase for the key because it's better security.

You will then have to import this key into secrets manager. To set expectations, plaintext in the context of creating secrets means unstructured (non JSON formatted) data, rather than unencrypted data. Be sure to pick plaintext and just paste the contents of the key into the textbox without JSON encoding it. Secrets Manager encourages the use of JSON structured data but it is not required. You will also need to create a Secrets Manager secret for the passphrase the same way as above.
You will then have to import this key into secrets manager. To set expectations, plaintext in the context of creating Secrets Manager secrets means unstructured (non JSON formatted) data, rather than unencrypted data.

As for the codestar connection, you can generate one of those by logging into the AWS Console, going to any of the CodeSuite services, clicking on the Settings left nav item and clicking connections. And then clicking the new button. You'll need the ARN of that connection.
Be sure to pick plaintext and just paste the contents of the key into the textbox without JSON encoding it. Secrets Manager encourages the use of JSON structured data but it is not required. You will also need to create a Secrets Manager secret for the passphrase the same way as above.

As for the codestar connection, you can generate one of those by logging into the AWS Console, going to any of the CodeSuite services, clicking on the Settings left nav item and clicking connections. And then clicking the new button. The wizard will walk you through generating the connection. You'll need the ARN of that connection.

## Release pipeline

Grab the `release-pipeline-stack.yml` file and either upload it to CloudFormation or paste it in the CloudFormation designer and create a stack. The stack will ask you for some of the information that you've created above and will generate the following resources

1. A CloudWatch Logs group for the whole CodePipeline
1. An S3 bucket used to move artifacts between the CodePipeline stages
1. A builder CodeBuild project and corresponding IAM role
1. Several builder CodeBuild projects and corresponding IAM roles
1. A signer CodeBuild project and corresponding IAM role
1. A copier CodeBuild project and corresponding IAM role
1. A log collector CodeBuild project and corresponding IAM role
1. A CodePipeline to pull these all together and corresponding IAM role

Once the stack is successful, by default, the project is configured to use `buildspecs/<stage>.yml` as the CodeBuild buildspec file but you can change that by manually editing the buildspec for each CodeBuild project or putting the buildspec files in this repository in a `buildspecs` folder in the root of the repository you're building.
Expand All @@ -52,6 +55,8 @@ buildspecs/

Everything else is already set up for you.

You can specify a minimal build environment for the signing portion of this process by providing an ECR repository based image URI to the `SigningCodeBuildProjectCustomImageUri` parameter in this template. If it is left blank, the default CodeBuild build environment will be used. Read below for more information.

## Adding a new artifact get signed and copied

There are a few changes that need to be made to add another artifact that needs to be signed and copied to the CodePipeline. They are as follows,
Expand Down Expand Up @@ -88,6 +93,14 @@ There are a few changes that need to be made to add another artifact that needs
1. You have to export that out of the signing environment for it to be picked up by the Copy to S3 CodeBuild project.
1. The Copy to S3 CodeBuild project is already written to handle multiple files so no changes are required there.

## Minimal build environment for signing

The default CodeBuild image that we use to build all of our projects is a [very well equipped general purpose build environment](https://github.com/aws/aws-codebuild-docker-images) which is not necessary for our artifact signing process. So to help with this, a new stack (specified by `minimal-signing-build-stack.yml`) is included as a way to optionally minimize the build environment for signing.

The stack includes a CodeBuild project that builds the minimal image using the `Dockerfile.signer` file, stores it in ECR, and additionally includes a trigger to build this image on a periodic basis. This ECR image can then be provided to the `release-pipeline-stack.yml` stack as a parameter and the right permissions and options will be selected to allow the use of this.

The periodic trigger is controlled by a cron expression and documentation on supported cron options can be found [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html). If the cron expression parameter is left empty, a periodic trigger will not be generated.

## Secrets Manager access logs

There is a separate template called `audit-logs-stack.yml` that contains audit logging for the key stored in AWS Secrets Manager. You can use CloudTrail to find the `GetSecretValue` events using the Event Name filter or using `secretsmanager.amazonaws.com` as the Event Source. This applies for the last 90 days.
Expand Down
233 changes: 233 additions & 0 deletions build-infrastructure/minimal-signing-build-stack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
---
AWSTemplateFormatVersion: '2010-09-09'
Description: A template that creates a minimal signing CodeBuild environment and stores it in ECR

Parameters:
SignerImageRepositoryName:
Type: String
Description: the name of the ECR repository to upload the signer image to
Default: ecs-agent-signer
RepositoryImageRententionPeriodInDays:
Type: Number
Default: 180
ImageCodeBuildProjectName:
Type: String
Description: the name of the CodeBuild project that builds the signing Docker image
Default: ecs-agent-signer-image-build
DockerBuildLogsGroupName:
Type: String
Description: The name of the log group to store the signing Docker image logs in
Default: signer-image-build-logs
CodeStarConnectionArn:
Type: String
Description: The ARN of the connection to use to connect to GitHub
GitHubRepositoryUrl:
Type: String
Description: The repository to pull the Dockerfile from so that we can build the signer image
Default: https://github.com/aws/amazon-ecs-agent
GitHubBranchName:
Type: String
Description: The branch to use from the repository mentioned above
Default: master
ImageBuildFrequencyCronExpression:
Type: String
Description: A cron expression to periodically build the signing image, can be left blank to disable periodic builds. See https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html
Default: 30 10 ? * 4 *
PeriodicBuildTriggerName:
Type: String
Description: The name of the CloudWatch Event rule, ignored if ImageBuildFrequencyCronExpression is blank
Default: PeriodicallyTriggerSigningImageBuild
LogGroupRetentionPeriodInDays:
Type: Number
Description: The number of days to retain cloudwatch logs
Default: 180
AllowedValues:
- 1
- 3
- 5
- 7
- 14
- 30
- 60
- 90
- 120
- 150
- 180
- 365
- 400
- 545
- 731
- 1827
- 3653

Conditions:
GeneratePeriodicTrigger:
!Not [!Equals [!Ref 'ImageBuildFrequencyCronExpression', '']]

Resources:
DockerBuildLogsGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref DockerBuildLogsGroupName
RetentionInDays: !Ref LogGroupRetentionPeriodInDays

SignerImageRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Ref SignerImageRepositoryName
ImageTagMutability: MUTABLE
LifecyclePolicy:
LifecyclePolicyText: !Sub |
{
"rules": [
{
"rulePriority": 1,
"description": "Only keep build-yyyymmdd images for ${RepositoryImageRententionPeriodInDays} days",
"selection": {
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": ${RepositoryImageRententionPeriodInDays},
"tagStatus": "tagged",
"tagPrefixList": [
"build"
]
},
"action": {
"type": "expire"
}
}
]
}
ImageCodeBuildProjectServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'image-codebuild-project-service-role-${AWS::Region}'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: codebuild-image-build-base-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: EcrPushImageAccess
Effect: Allow
Action:
- ecr:CompleteLayerUpload
- ecr:UploadLayerPart
- ecr:InitiateLayerUpload
- ecr:BatchCheckLayerAvailability
- ecr:PutImage
Resource: !GetAtt SignerImageRepository.Arn
- Sid: EcrGetAuthTokenAccess
Effect: Allow
Action:
- ecr:GetAuthorizationToken
Resource: '*'
- Sid: CodeBuildCodeStarConnectionAccess
Effect: Allow
Resource:
- !Ref CodeStarConnectionArn
Action:
- codestar-connections:UseConnection
- Sid: CloudWatchLogsAccess
Effect: Allow
Resource:
- !GetAtt DockerBuildLogsGroup.Arn
- !Sub '${DockerBuildLogsGroup.Arn}:*'
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- Sid: CodeBuildCreateReportAccess
Effect: Allow
Resource:
- !Sub 'arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/${ImageCodeBuildProjectName}-*'
Action:
- codebuild:CreateReportGroup
- codebuild:CreateReport
- codebuild:UpdateReport
- codebuild:BatchPutTestCases
- codebuild:BatchPutCodeCoverages

ImageCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Ref ImageCodeBuildProjectName
Description: A CodeBuild project that signs artifacts that were built earlier
ConcurrentBuildLimit: 10
ServiceRole: !GetAtt ImageCodeBuildProjectServiceRole.Arn
Artifacts:
Type: NO_ARTIFACTS
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
ImagePullCredentialsType: CODEBUILD
Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
PrivilegedMode: true
EnvironmentVariables:
- Name: SIGNER_ECR_REPO
Type: PLAINTEXT
Value: !GetAtt SignerImageRepository.RepositoryUri
Source:
BuildSpec: buildspecs/signing-image-build.yml
Type: GITHUB
Location: !Ref GitHubRepositoryUrl
GitSubmodulesConfig:
FetchSubmodules: true
SourceVersion: !Ref GitHubBranchName
TimeoutInMinutes: 60
QueuedTimeoutInMinutes: 480
LogsConfig:
CloudWatchLogs:
GroupName: !Ref DockerBuildLogsGroupName
Status: ENABLED
StreamName: !Ref ImageCodeBuildProjectName

ImageBuildPeriodicTriggerRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'image-periodic-trigger-service-role-${AWS::Region}'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: events.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: build-event-trigger-base-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: CodeBuildStartBuildAccess
Effect: Allow
Action:
- codebuild:StartBuild
Resource: !GetAtt ImageCodeBuildProject.Arn

PeriodicBuildTrigger:
Condition: GeneratePeriodicTrigger
Type: AWS::Events::Rule
Properties:
Description: Trigger the image build periodically based on a cron expression
Name: !Ref PeriodicBuildTriggerName
RoleArn: !GetAtt ImageBuildPeriodicTriggerRole.Arn
ScheduleExpression: !Sub 'cron(${ImageBuildFrequencyCronExpression})'
State: ENABLED
Targets:
- Arn: !GetAtt ImageCodeBuildProject.Arn
Id: !Sub 'codebuild-target-${PeriodicBuildTriggerName}'
RoleArn: !GetAtt ImageBuildPeriodicTriggerRole.Arn

Outputs:
EcrRepositoryUri:
Description: The URI of the Agent Signer image ECR repository
Value: !GetAtt SignerImageRepository.RepositoryUri
Export:
Name: !Sub '${AWS::StackName}-${AWS::Region}-SignerImageEcrRepositoryUri'
46 changes: 44 additions & 2 deletions build-infrastructure/release-pipeline-stack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ Parameters:
Type: String
Description: The name of the signing project
Default: artifact-sign
SigningCodeBuildProjectCustomImageUri:
Type: String
Description: A custom ECR image to use with the signing project, can be left blank to use the default Codebuild image
Default: ''
SigningCodeBuildProjectCustomImageRepositoryArn:
Type: String
Description: The ARN of the ECR repository where the custom image is stored, ignored if SigningCodeBuildProjectCustomImageUri is blank
Default: ''
CopyCodeBuildProjectName:
Type: String
Description: The name of the copy project
Expand Down Expand Up @@ -90,6 +98,10 @@ Parameters:
Type: String
Description: The ARN of the passphrase

Conditions:
UseCustomSigningImage:
!Not [!Equals [!Ref 'SigningCodeBuildProjectCustomImageUri', '']]

Resources:
CodeBuildLogGroup:
Type: AWS::Logs::LogGroup
Expand Down Expand Up @@ -553,6 +565,30 @@ Resources:
Resource:
- !Ref SecretKeyArn
- !Ref PassphraseArn
- !If
- UseCustomSigningImage
- PolicyName: codebuild-custom-ecr-image-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: EcrGetAuthTokenAccess
Effect: Allow
Action:
- ecr:GetAuthorizationToken
Resource: '*'
- Sid: EcrImageAccess
Effect: Allow
Action:
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:PutImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
Resource:
- !Ref SigningCodeBuildProjectCustomImageRepositoryArn
- !Ref AWS::NoValue

SigningCodeBuildProject:
Type: AWS::CodeBuild::Project
Expand All @@ -566,8 +602,14 @@ Resources:
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
ImagePullCredentialsType: CODEBUILD
Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
ImagePullCredentialsType: !If
- UseCustomSigningImage
- SERVICE_ROLE
- CODEBUILD
Image: !If
- UseCustomSigningImage
- !Ref SigningCodeBuildProjectCustomImageUri
- aws/codebuild/amazonlinux2-x86_64-standard:3.0
EnvironmentVariables:
- Name: PASSPHRASE
Type: SECRETS_MANAGER
Expand Down
21 changes: 21 additions & 0 deletions buildspecs/signing-image-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: 0.2

phases:
pre_build:
commands:
- BUILD_DATE=$(date +%Y%m%d)
- $(aws --region $AWS_REGION ecr get-login --no-include-email)
build:
commands:
# Go into the right folder
- cd build-infrastructure
- echo "Building ecs-agent-signer version $BUILD_DATE"
# build the image dictated by the Dockerfile.signer file
- docker build -f Dockerfile.signer -t ecs-agent-signer:latest --build-arg builddate=$BUILD_DATE .
# Tag the built image with latest as well as the build date
- echo "Tagging and pushing Docker image to ECR"
- docker tag ecs-agent-signer:latest $SIGNER_ECR_REPO:latest
- docker tag ecs-agent-signer:latest $SIGNER_ECR_REPO:build-$BUILD_DATE
# push the image to ECR
- docker push $SIGNER_ECR_REPO:latest
- docker push $SIGNER_ECR_REPO:build-$BUILD_DATE

0 comments on commit cba1b38

Please sign in to comment.