Skip to content

Commit

Permalink
feat(codebuild): add functionality to allow using private registry an…
Browse files Browse the repository at this point in the history
…d cross-account ECR repository as build image

Fixes aws#2175
  • Loading branch information
Kaixiang-AWS authored and skinny85 committed Jul 2, 2019
1 parent 99ae5e7 commit a6cc355
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 98 deletions.
7 changes: 5 additions & 2 deletions packages/@aws-cdk/aws-codebuild/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,7 @@ of the constants such as `WindowsBuildImage.WIN_SERVER_CORE_2016_BASE` or
Alternatively, you can specify a custom image using one of the static methods on
`XxxBuildImage`:

* Use `.fromDockerHub(image)` to reference an image publicly available in Docker
Hub.
* Use `.fromDockerRegistry(image[, secretsManagerCredential])` to reference an image in any public or private Docker registry.
* Use `.fromEcrRepository(repo[, tag])` to reference an image available in an
ECR repository.
* Use `.fromAsset(directory)` to use an image created from a
Expand All @@ -205,6 +204,10 @@ The following example shows how to define an image from an ECR repository:

[ECR example](./test/integ.ecr.lit.ts)

The following example shows how to define an image from a private docker registry:

[Docker Registry example](./test/integ.docker-registry.lit.ts)

## Events

CodeBuild projects can be used either as a source for events or be triggered
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
removed:@aws-cdk/aws-codebuild.LinuxBuildImage.fromDockerHub
removed:@aws-cdk/aws-codebuild.WindowsBuildImage.fromDockerHub
102 changes: 64 additions & 38 deletions packages/@aws-cdk/aws-codebuild/lib/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { DockerImageAsset, DockerImageAssetProps } from '@aws-cdk/aws-ecr-assets
import events = require('@aws-cdk/aws-events');
import iam = require('@aws-cdk/aws-iam');
import kms = require('@aws-cdk/aws-kms');
import { Aws, CfnResource, Construct, Duration, IResource, Lazy, PhysicalName, Resource, Stack } from '@aws-cdk/core';
import secretsmanager = require('@aws-cdk/aws-secretsmanager');
import { Aws, CfnResource, Construct, Duration, IResource, Lazy, PhysicalName, Resource, Stack, Token } from '@aws-cdk/core';
import { IArtifacts } from './artifacts';
import { BuildSpec } from './build-spec';
import { Cache } from './cache';
Expand Down Expand Up @@ -775,6 +776,18 @@ export class Project extends ProjectBase {
});
}

private attachEcrPermission() {
this.addToRolePolicy(new iam.PolicyStatement({
resources: ['*'],
actions: [
'ecr:GetAutheticationToken',
'ecr:GetDownloadUrlForLayer',
'ecr:BatchGetImage',
'ecr:BatchCheckLayerAvailability'
]
}));
}

private renderEnvironment(env: BuildEnvironment = {},
projectVars: { [name: string]: BuildEnvironmentVariable } = {}): CfnProject.EnvironmentProperty {
const vars: { [name: string]: BuildEnvironmentVariable } = {};
Expand All @@ -792,6 +805,11 @@ export class Project extends ProjectBase {

const hasEnvironmentVars = Object.keys(vars).length > 0;

// An image id is a token if and only if it's an ECR image
if (Token.isUnresolved(this.buildImage.imageId)) {
this.attachEcrPermission();
}

const errors = this.buildImage.validate(env);
if (errors.length > 0) {
throw new Error("Invalid CodeBuild environment: " + errors.join('\n'));
Expand All @@ -800,6 +818,12 @@ export class Project extends ProjectBase {
return {
type: this.buildImage.type,
image: this.buildImage.imageId,
imagePullCredentialsType: this.buildImage.imagePullCredentialsType,
registryCredential: this.buildImage.secretsManagerCredential ?
{
credentialProvider: 'SECRETS_MANAGER',
credential: this.buildImage.secretsManagerCredential.secretArn
} : undefined,
privilegedMode: env.privileged || false,
computeType: env.computeType || this.buildImage.defaultComputeType,
environmentVariables: !hasEnvironmentVars ? undefined : Object.keys(vars).map(name => ({
Expand Down Expand Up @@ -924,6 +948,17 @@ export enum ComputeType {
LARGE = 'BUILD_GENERAL1_LARGE'
}

/**
* The type of credentials AWS CodeBuild uses to pull images in your build. There are two valid values:
* - CODEBUILD specifies that AWS CodeBuild uses its own credentials.
* This requires that you modify your ECR repository policy to trust AWS CodeBuild's service principal.
* - SERVICE_ROLE specifies that AWS CodeBuild uses your build project's service role.
*/
export enum ImagePullCredentialsType {
CODEBUILD = 'CODEBUILD',
SERVICE_ROLE = 'SERVICE_ROLE'
}

export interface BuildEnvironment {
/**
* The image used for the builds.
Expand Down Expand Up @@ -982,6 +1017,16 @@ export interface IBuildImage {
*/
readonly defaultComputeType: ComputeType;

/**
* The type of credentials AWS CodeBuild uses to pull images in your build.
*/
readonly imagePullCredentialsType?: ImagePullCredentialsType;

/**
* The credentials for access to a private registry.
*/
readonly secretsManagerCredential?: secretsmanager.ISecret;

/**
* Allows the image a chance to validate whether the passed configuration is correct.
*
Expand All @@ -1002,7 +1047,7 @@ export interface IBuildImage {
*
* You can also specify a custom image using one of the static methods:
*
* - LinuxBuildImage.fromDockerHub(image)
* - LinuxBuildImage.fromDockerRegistry(image[, secretsManagerCredential])
* - LinuxBuildImage.fromEcrRepository(repo[, tag])
* - LinuxBuildImage.fromAsset(parent, id, props)
*
Expand Down Expand Up @@ -1046,8 +1091,8 @@ export class LinuxBuildImage implements IBuildImage {
/**
* @returns a Linux build image from a Docker Hub image.
*/
public static fromDockerHub(name: string): LinuxBuildImage {
return new LinuxBuildImage(name);
public static fromDockerRegistry(name: string, secretsManagerCredential?: secretsmanager.ISecret): LinuxBuildImage {
return new LinuxBuildImage(name, ImagePullCredentialsType.SERVICE_ROLE, secretsManagerCredential);
}

/**
Expand All @@ -1062,29 +1107,24 @@ export class LinuxBuildImage implements IBuildImage {
* @param tag Image tag (default "latest")
*/
public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest'): LinuxBuildImage {
const image = new LinuxBuildImage(repository.repositoryUriForTag(tag));
repository.addToResourcePolicy(ecrAccessForCodeBuildService());
return image;
return new LinuxBuildImage(repository.repositoryUriForTag(tag), ImagePullCredentialsType.SERVICE_ROLE);
}

/**
* Uses an Docker image asset as a Linux build image.
*/
public static fromAsset(scope: Construct, id: string, props: DockerImageAssetProps): LinuxBuildImage {
const asset = new DockerImageAsset(scope, id, props);
const image = new LinuxBuildImage(asset.imageUri);

// allow this codebuild to pull this image (CodeBuild doesn't use a role, so
// we can't use `asset.grantUseImage()`.
asset.repository.addToResourcePolicy(ecrAccessForCodeBuildService());

return image;
return new LinuxBuildImage(asset.imageUri, ImagePullCredentialsType.SERVICE_ROLE);
}

public readonly type = 'LINUX_CONTAINER';
public readonly defaultComputeType = ComputeType.SMALL;

private constructor(public readonly imageId: string) {
private constructor(
public readonly imageId: string,
public readonly imagePullCredentialsType?: ImagePullCredentialsType,
public readonly secretsManagerCredential?: secretsmanager.ISecret) {
}

public validate(_: BuildEnvironment): string[] {
Expand Down Expand Up @@ -1127,7 +1167,7 @@ export class LinuxBuildImage implements IBuildImage {
*
* You can also specify a custom image using one of the static methods:
*
* - WindowsBuildImage.fromDockerHub(image)
* - WindowsBuildImage.fromDockerRegistry(image[, secretsManagerCredential])
* - WindowsBuildImage.fromEcrRepository(repo[, tag])
* - WindowsBuildImage.fromAsset(parent, id, props)
*
Expand All @@ -1139,8 +1179,8 @@ export class WindowsBuildImage implements IBuildImage {
/**
* @returns a Windows build image from a Docker Hub image.
*/
public static fromDockerHub(name: string): WindowsBuildImage {
return new WindowsBuildImage(name);
public static fromDockerRegistry(name: string, secretsManagerCredential?: secretsmanager.ISecret): WindowsBuildImage {
return new WindowsBuildImage(name, ImagePullCredentialsType.SERVICE_ROLE, secretsManagerCredential);
}

/**
Expand All @@ -1155,28 +1195,23 @@ export class WindowsBuildImage implements IBuildImage {
* @param tag Image tag (default "latest")
*/
public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest'): WindowsBuildImage {
const image = new WindowsBuildImage(repository.repositoryUriForTag(tag));
repository.addToResourcePolicy(ecrAccessForCodeBuildService());
return image;
return new WindowsBuildImage(repository.repositoryUriForTag(tag), ImagePullCredentialsType.SERVICE_ROLE);
}

/**
* Uses an Docker image asset as a Windows build image.
*/
public static fromAsset(scope: Construct, id: string, props: DockerImageAssetProps): WindowsBuildImage {
const asset = new DockerImageAsset(scope, id, props);
const image = new WindowsBuildImage(asset.imageUri);

// allow this codebuild to pull this image (CodeBuild doesn't use a role, so
// we can't use `asset.grantUseImage()`.
asset.repository.addToResourcePolicy(ecrAccessForCodeBuildService());

return image;
return new WindowsBuildImage(asset.imageUri, ImagePullCredentialsType.SERVICE_ROLE);
}
public readonly type = 'WINDOWS_CONTAINER';
public readonly defaultComputeType = ComputeType.MEDIUM;

private constructor(public readonly imageId: string) {
private constructor(
public readonly imageId: string,
public readonly imagePullCredentialsType?: ImagePullCredentialsType,
public readonly secretsManagerCredential?: secretsmanager.ISecret) {
}

public validate(buildEnvironment: BuildEnvironment): string[] {
Expand Down Expand Up @@ -1238,12 +1273,3 @@ export enum BuildEnvironmentVariableType {
*/
PARAMETER_STORE = 'PARAMETER_STORE'
}

function ecrAccessForCodeBuildService(): iam.PolicyStatement {
const s = new iam.PolicyStatement({
principals: [new iam.ServicePrincipal('codebuild.amazonaws.com')],
actions: ['ecr:GetDownloadUrlForLayer', 'ecr:BatchGetImage', 'ecr:BatchCheckLayerAvailability'],
});
s.sid = 'CodeBuild';
return s;
}
4 changes: 3 additions & 1 deletion packages/@aws-cdk/aws-codebuild/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"@aws-cdk/aws-kms": "^0.36.1",
"@aws-cdk/aws-s3": "^0.36.1",
"@aws-cdk/aws-s3-assets": "^0.36.1",
"@aws-cdk/aws-secretsmanager": "^0.36.1",
"@aws-cdk/core": "^0.36.1"
},
"homepage": "https://github.com/awslabs/aws-cdk",
Expand All @@ -103,6 +104,7 @@
"@aws-cdk/aws-kms": "^0.36.1",
"@aws-cdk/aws-s3": "^0.36.1",
"@aws-cdk/aws-s3-assets": "^0.36.1",
"@aws-cdk/aws-secretsmanager": "^0.36.1",
"@aws-cdk/core": "^0.36.1"
},
"engines": {
Expand All @@ -116,4 +118,4 @@
]
},
"stability": "experimental"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,6 @@
]
}
]
},
"PolicyDocument": {
"Statement": [
{
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
],
"Effect": "Allow",
"Principal": {
"Service": {
"Fn::Join": [
"",
[
"codebuild.",
{
"Ref": "AWS::URLSuffix"
}
]
]
}
},
"Sid": "CodeBuild"
}
],
"Version": "2012-10-17"
}
},
"DependsOn": [
Expand Down Expand Up @@ -262,6 +235,16 @@
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": [
"ecr:GetAutheticationToken",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": [
"logs:CreateLogGroup",
Expand Down Expand Up @@ -439,6 +422,7 @@
]
]
},
"ImagePullCredentialsType": "SERVICE_ROLE",
"PrivilegedMode": false,
"Type": "LINUX_CONTAINER"
},
Expand Down
Loading

0 comments on commit a6cc355

Please sign in to comment.