From e218f9d5204914786da1f817ac42c44eecc031f8 Mon Sep 17 00:00:00 2001 From: Kaixiang Zhao Date: Fri, 7 Jun 2019 16:31:07 -0700 Subject: [PATCH] feat(codebuild): allow specifying principals and credentials for pulling build images. When using an image that is hosted in a private Docker registry, you have to pass the appropriate credentials in order to authenticate against that registry. This change allows passing those credentials when creating a custom build image. It also introduces the concept of the principal that CodeBuild will use to pull the image - previously, CodeBuild would always use its own identity when pulling images, which meant using it with an ECR-hosted image required changing the resource policy of the repository to trust CodeBuild's service principal. Now, the default is to use the project's role when doing the pull of the image. Fixes #2175 BREAKING CHANGE: codebuild.LinuxBuildImage.fromDockerHub() has been renamed to fromDockerRegistry() * codebuild.WindowsBuildImage.fromDockerHub() has been renamed to fromDockerRegistry() --- allowed-breaking-changes.txt | 45 +++ packages/@aws-cdk/aws-codebuild/README.md | 7 +- .../@aws-cdk/aws-codebuild/lib/artifacts.ts | 2 +- .../@aws-cdk/aws-codebuild/lib/project.ts | 278 +++++++++++++----- packages/@aws-cdk/aws-codebuild/lib/source.ts | 10 +- packages/@aws-cdk/aws-codebuild/package.json | 4 +- .../test/integ.docker-asset.lit.expected.json | 69 +++-- .../integ.docker-registry.lit.expected.json | 148 ++++++++++ .../test/integ.docker-registry.lit.ts | 36 +++ .../test/integ.ecr.lit.expected.json | 53 ++-- 10 files changed, 509 insertions(+), 143 deletions(-) create mode 100644 packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.expected.json create mode 100644 packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.ts diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index e33478256fb7f..1adbf52a6d17d 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -1,4 +1,49 @@ removed:@aws-cdk/aws-ec2.Port.toRuleJSON +change-return-type:@aws-cdk/aws-codebuild.LinuxBuildImage.fromAsset +removed:@aws-cdk/aws-codebuild.LinuxBuildImage.fromDockerHub +change-return-type:@aws-cdk/aws-codebuild.LinuxBuildImage.fromEcrRepository +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_OPEN_JDK_9 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.STANDARD_1_0 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.STANDARD_2_0 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_ANDROID_JAVA8_24_4_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_ANDROID_JAVA8_26_1_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_BASE +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_DOCKER_17_09_0 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_DOCKER_18_09_0 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_DOTNET_CORE_1_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_DOTNET_CORE_2_0 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_DOTNET_CORE_2_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_GOLANG_1_10 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_GOLANG_1_11 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_10_1_0 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_10_14_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_6_3_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_8_11_0 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_OPEN_JDK_11 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_OPEN_JDK_8 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_PHP_5_6 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_PHP_7_0 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_PHP_7_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_PYTHON_2_7_12 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_PYTHON_3_3_6 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_PYTHON_3_4_5 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_PYTHON_3_5_2 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_PYTHON_3_6_5 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_PYTHON_3_7_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_RUBY_2_2_5 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_RUBY_2_3_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_RUBY_2_5_1 +changed-type:@aws-cdk/aws-codebuild.LinuxBuildImage.UBUNTU_14_04_RUBY_2_5_3 +change-return-type:@aws-cdk/aws-codebuild.WindowsBuildImage.fromAsset +removed:@aws-cdk/aws-codebuild.WindowsBuildImage.fromDockerHub +change-return-type:@aws-cdk/aws-codebuild.WindowsBuildImage.fromEcrRepository +changed-type:@aws-cdk/aws-codebuild.WindowsBuildImage.WIN_SERVER_CORE_2016_BASE +change-return-type:@aws-cdk/aws-codebuild.Source.bitBucket +change-return-type:@aws-cdk/aws-codebuild.Source.codeCommit +change-return-type:@aws-cdk/aws-codebuild.Source.gitHub +change-return-type:@aws-cdk/aws-codebuild.Source.gitHubEnterprise +change-return-type:@aws-cdk/aws-codebuild.Source.s3 +change-return-type:@aws-cdk/aws-codebuild.Artifacts.s3 change-return-type:@aws-cdk/aws-codebuild.PipelineProject.addSecondaryArtifact change-return-type:@aws-cdk/aws-codebuild.Project.addSecondaryArtifact removed:@aws-cdk/aws-ec2.Connections.allowFromAnyIPv4 diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 9b634febe5fc1..071bb226ce43e 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -186,8 +186,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[, { secretsManagerCredentials }])` 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 @@ -201,6 +200,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 diff --git a/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts b/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts index a751372821c00..10741cc33f72a 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts @@ -53,7 +53,7 @@ export interface ArtifactsProps { * Artifacts definition for a CodeBuild Project. */ export abstract class Artifacts implements IArtifacts { - public static s3(props: S3ArtifactsProps): Artifacts { + public static s3(props: S3ArtifactsProps): IArtifacts { return new S3Artifacts(props); } diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 2d44917c69eac..ab0d33a0fca9e 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -5,6 +5,7 @@ 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 secretsmanager = require('@aws-cdk/aws-secretsmanager'); import { Aws, CfnResource, Construct, Duration, IResource, Lazy, PhysicalName, Resource, Stack } from '@aws-cdk/core'; import { IArtifacts } from './artifacts'; import { BuildSpec } from './build-spec'; @@ -797,9 +798,32 @@ export class Project extends ProjectBase { throw new Error("Invalid CodeBuild environment: " + errors.join('\n')); } + const imagePullPrincipalType = this.buildImage.imagePullPrincipalType === ImagePullPrincipalType.CODEBUILD + ? undefined + : ImagePullPrincipalType.SERVICE_ROLE; + if (this.buildImage.repository) { + if (imagePullPrincipalType === ImagePullPrincipalType.SERVICE_ROLE) { + this.buildImage.repository.grantPull(this); + } else { + const statement = new iam.PolicyStatement({ + principals: [new iam.ServicePrincipal('codebuild.amazonaws.com')], + actions: ['ecr:GetDownloadUrlForLayer', 'ecr:BatchGetImage', 'ecr:BatchCheckLayerAvailability'], + }); + statement.sid = 'CodeBuild'; + this.buildImage.repository.addToResourcePolicy(statement); + } + } + return { type: this.buildImage.type, image: this.buildImage.imageId, + imagePullCredentialsType: imagePullPrincipalType, + registryCredential: this.buildImage.secretsManagerCredentials + ? { + credentialProvider: 'SECRETS_MANAGER', + credential: this.buildImage.secretsManagerCredentials.secretArn, + } + : undefined, privilegedMode: env.privileged || false, computeType: env.computeType || this.buildImage.defaultComputeType, environmentVariables: !hasEnvironmentVars ? undefined : Object.keys(vars).map(name => ({ @@ -924,6 +948,25 @@ export enum ComputeType { LARGE = 'BUILD_GENERAL1_LARGE' } +/** + * The type of principal CodeBuild will use to pull your build Docker image. + */ +export enum ImagePullPrincipalType { + /** + * CODEBUILD specifies that CodeBuild uses its own identity when pulling the image. + * This means the resource policy of the ECR repository that hosts the image will be modified to trust + * CodeBuild's service principal. + * This is the required principal type when using CodeBuild's pre-defined images. + */ + CODEBUILD = 'CODEBUILD', + + /** + * SERVICE_ROLE specifies that AWS CodeBuild uses the project's role when pulling the image. + * The role will be granted pull permissions on the ECR repository hosting the image. + */ + SERVICE_ROLE = 'SERVICE_ROLE' +} + export interface BuildEnvironment { /** * The image used for the builds. @@ -982,6 +1025,27 @@ export interface IBuildImage { */ readonly defaultComputeType: ComputeType; + /** + * The type of principal that CodeBuild will use to pull this build Docker image. + * + * @default ImagePullPrincipalType.SERVICE_ROLE + */ + readonly imagePullPrincipalType?: ImagePullPrincipalType; + + /** + * The secretsManagerCredentials for access to a private registry. + * + * @default no credentials will be used + */ + readonly secretsManagerCredentials?: secretsmanager.ISecret; + + /** + * An optional ECR repository that the image is hosted in. + * + * @default no repository + */ + readonly repository?: ecr.IRepository; + /** * Allows the image a chance to validate whether the passed configuration is correct. * @@ -995,6 +1059,33 @@ export interface IBuildImage { runScriptBuildspec(entrypoint: string): BuildSpec; } +/** + * The options when creating a CodeBuild Docker build image + * using {@link LinuxBuildImage.fromDockerRegistry} + * or {@link WindowsBuildImage.fromDockerRegistry}. + */ +export interface DockerImageOptions { + /** + * The credentials, stored in Secrets Manager, + * used for accessing the repository holding the image, + * if the repository is private. + * + * @default no credentials will be used (we assume the repository is public) + */ + readonly secretsManagerCredentials?: secretsmanager.ISecret; +} + +/** + * Construction properties of {@link LinuxBuildImage}. + * Module-private, as the constructor of {@link LinuxBuildImage} is private. + */ +interface LinuxBuildImageProps { + readonly imageId: string; + readonly imagePullPrincipalType?: ImagePullPrincipalType; + readonly secretsManagerCredentials?: secretsmanager.ISecret; + readonly repository?: ecr.IRepository; +} + /** * A CodeBuild image running Linux. * @@ -1002,7 +1093,7 @@ export interface IBuildImage { * * You can also specify a custom image using one of the static methods: * - * - LinuxBuildImage.fromDockerHub(image) + * - LinuxBuildImage.fromDockerRegistry(image[, { secretsManagerCredentials }]) * - LinuxBuildImage.fromEcrRepository(repo[, tag]) * - LinuxBuildImage.fromAsset(parent, id, props) * @@ -1010,44 +1101,48 @@ export interface IBuildImage { * @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html */ export class LinuxBuildImage implements IBuildImage { - public static readonly STANDARD_1_0 = new LinuxBuildImage('aws/codebuild/standard:1.0'); - public static readonly STANDARD_2_0 = new LinuxBuildImage('aws/codebuild/standard:2.0'); - public static readonly UBUNTU_14_04_BASE = new LinuxBuildImage('aws/codebuild/ubuntu-base:14.04'); - public static readonly UBUNTU_14_04_ANDROID_JAVA8_24_4_1 = new LinuxBuildImage('aws/codebuild/android-java-8:24.4.1'); - public static readonly UBUNTU_14_04_ANDROID_JAVA8_26_1_1 = new LinuxBuildImage('aws/codebuild/android-java-8:26.1.1'); - public static readonly UBUNTU_14_04_DOCKER_17_09_0 = new LinuxBuildImage('aws/codebuild/docker:17.09.0'); - public static readonly UBUNTU_14_04_DOCKER_18_09_0 = new LinuxBuildImage('aws/codebuild/docker:18.09.0'); - public static readonly UBUNTU_14_04_GOLANG_1_10 = new LinuxBuildImage('aws/codebuild/golang:1.10'); - public static readonly UBUNTU_14_04_GOLANG_1_11 = new LinuxBuildImage('aws/codebuild/golang:1.11'); - public static readonly UBUNTU_14_04_OPEN_JDK_8 = new LinuxBuildImage('aws/codebuild/java:openjdk-8'); - public static readonly UBUNTU_14_04_OPEN_JDK_9 = new LinuxBuildImage('aws/codebuild/java:openjdk-9'); - public static readonly UBUNTU_14_04_OPEN_JDK_11 = new LinuxBuildImage('aws/codebuild/java:openjdk-11'); - public static readonly UBUNTU_14_04_NODEJS_10_14_1 = new LinuxBuildImage('aws/codebuild/nodejs:10.14.1'); - public static readonly UBUNTU_14_04_NODEJS_10_1_0 = new LinuxBuildImage('aws/codebuild/nodejs:10.1.0'); - public static readonly UBUNTU_14_04_NODEJS_8_11_0 = new LinuxBuildImage('aws/codebuild/nodejs:8.11.0'); - public static readonly UBUNTU_14_04_NODEJS_6_3_1 = new LinuxBuildImage('aws/codebuild/nodejs:6.3.1'); - public static readonly UBUNTU_14_04_PHP_5_6 = new LinuxBuildImage('aws/codebuild/php:5.6'); - public static readonly UBUNTU_14_04_PHP_7_0 = new LinuxBuildImage('aws/codebuild/php:7.0'); - public static readonly UBUNTU_14_04_PHP_7_1 = new LinuxBuildImage('aws/codebuild/php:7.1'); - public static readonly UBUNTU_14_04_PYTHON_3_7_1 = new LinuxBuildImage('aws/codebuild/python:3.7.1'); - public static readonly UBUNTU_14_04_PYTHON_3_6_5 = new LinuxBuildImage('aws/codebuild/python:3.6.5'); - public static readonly UBUNTU_14_04_PYTHON_3_5_2 = new LinuxBuildImage('aws/codebuild/python:3.5.2'); - public static readonly UBUNTU_14_04_PYTHON_3_4_5 = new LinuxBuildImage('aws/codebuild/python:3.4.5'); - public static readonly UBUNTU_14_04_PYTHON_3_3_6 = new LinuxBuildImage('aws/codebuild/python:3.3.6'); - public static readonly UBUNTU_14_04_PYTHON_2_7_12 = new LinuxBuildImage('aws/codebuild/python:2.7.12'); - public static readonly UBUNTU_14_04_RUBY_2_5_3 = new LinuxBuildImage('aws/codebuild/ruby:2.5.3'); - public static readonly UBUNTU_14_04_RUBY_2_5_1 = new LinuxBuildImage('aws/codebuild/ruby:2.5.1'); - public static readonly UBUNTU_14_04_RUBY_2_3_1 = new LinuxBuildImage('aws/codebuild/ruby:2.3.1'); - public static readonly UBUNTU_14_04_RUBY_2_2_5 = new LinuxBuildImage('aws/codebuild/ruby:2.2.5'); - public static readonly UBUNTU_14_04_DOTNET_CORE_1_1 = new LinuxBuildImage('aws/codebuild/dot-net:core-1'); - public static readonly UBUNTU_14_04_DOTNET_CORE_2_0 = new LinuxBuildImage('aws/codebuild/dot-net:core-2.0'); - public static readonly UBUNTU_14_04_DOTNET_CORE_2_1 = new LinuxBuildImage('aws/codebuild/dot-net:core-2.1'); + public static readonly STANDARD_1_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:1.0'); + public static readonly STANDARD_2_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/standard:2.0'); + public static readonly UBUNTU_14_04_BASE = LinuxBuildImage.codeBuildImage('aws/codebuild/ubuntu-base:14.04'); + public static readonly UBUNTU_14_04_ANDROID_JAVA8_24_4_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/android-java-8:24.4.1'); + public static readonly UBUNTU_14_04_ANDROID_JAVA8_26_1_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/android-java-8:26.1.1'); + public static readonly UBUNTU_14_04_DOCKER_17_09_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/docker:17.09.0'); + public static readonly UBUNTU_14_04_DOCKER_18_09_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/docker:18.09.0'); + public static readonly UBUNTU_14_04_GOLANG_1_10 = LinuxBuildImage.codeBuildImage('aws/codebuild/golang:1.10'); + public static readonly UBUNTU_14_04_GOLANG_1_11 = LinuxBuildImage.codeBuildImage('aws/codebuild/golang:1.11'); + public static readonly UBUNTU_14_04_OPEN_JDK_8 = LinuxBuildImage.codeBuildImage('aws/codebuild/java:openjdk-8'); + public static readonly UBUNTU_14_04_OPEN_JDK_9 = LinuxBuildImage.codeBuildImage('aws/codebuild/java:openjdk-9'); + public static readonly UBUNTU_14_04_OPEN_JDK_11 = LinuxBuildImage.codeBuildImage('aws/codebuild/java:openjdk-11'); + public static readonly UBUNTU_14_04_NODEJS_10_14_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/nodejs:10.14.1'); + public static readonly UBUNTU_14_04_NODEJS_10_1_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/nodejs:10.1.0'); + public static readonly UBUNTU_14_04_NODEJS_8_11_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/nodejs:8.11.0'); + public static readonly UBUNTU_14_04_NODEJS_6_3_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/nodejs:6.3.1'); + public static readonly UBUNTU_14_04_PHP_5_6 = LinuxBuildImage.codeBuildImage('aws/codebuild/php:5.6'); + public static readonly UBUNTU_14_04_PHP_7_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/php:7.0'); + public static readonly UBUNTU_14_04_PHP_7_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/php:7.1'); + public static readonly UBUNTU_14_04_PYTHON_3_7_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.7.1'); + public static readonly UBUNTU_14_04_PYTHON_3_6_5 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.6.5'); + public static readonly UBUNTU_14_04_PYTHON_3_5_2 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.5.2'); + public static readonly UBUNTU_14_04_PYTHON_3_4_5 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.4.5'); + public static readonly UBUNTU_14_04_PYTHON_3_3_6 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:3.3.6'); + public static readonly UBUNTU_14_04_PYTHON_2_7_12 = LinuxBuildImage.codeBuildImage('aws/codebuild/python:2.7.12'); + public static readonly UBUNTU_14_04_RUBY_2_5_3 = LinuxBuildImage.codeBuildImage('aws/codebuild/ruby:2.5.3'); + public static readonly UBUNTU_14_04_RUBY_2_5_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/ruby:2.5.1'); + public static readonly UBUNTU_14_04_RUBY_2_3_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/ruby:2.3.1'); + public static readonly UBUNTU_14_04_RUBY_2_2_5 = LinuxBuildImage.codeBuildImage('aws/codebuild/ruby:2.2.5'); + public static readonly UBUNTU_14_04_DOTNET_CORE_1_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/dot-net:core-1'); + public static readonly UBUNTU_14_04_DOTNET_CORE_2_0 = LinuxBuildImage.codeBuildImage('aws/codebuild/dot-net:core-2.0'); + public static readonly UBUNTU_14_04_DOTNET_CORE_2_1 = LinuxBuildImage.codeBuildImage('aws/codebuild/dot-net:core-2.1'); /** * @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, options: DockerImageOptions = {}): IBuildImage { + return new LinuxBuildImage({ + ...options, + imageId: name, + imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, + }); } /** @@ -1061,30 +1156,45 @@ export class LinuxBuildImage implements IBuildImage { * @param repository The ECR repository * @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; + public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest'): IBuildImage { + return new LinuxBuildImage({ + imageId: repository.repositoryUriForTag(tag), + imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, + repository, + }); } /** * Uses an Docker image asset as a Linux build image. */ - public static fromAsset(scope: Construct, id: string, props: DockerImageAssetProps): LinuxBuildImage { + public static fromAsset(scope: Construct, id: string, props: DockerImageAssetProps): IBuildImage { 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 new LinuxBuildImage({ + imageId: asset.imageUri, + imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, + repository: asset.repository, + }); + } - return image; + private static codeBuildImage(name: string): IBuildImage { + return new LinuxBuildImage({ + imageId: name, + imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD, + }); } public readonly type = 'LINUX_CONTAINER'; public readonly defaultComputeType = ComputeType.SMALL; - - private constructor(public readonly imageId: string) { + public readonly imageId: string; + public readonly imagePullPrincipalType?: ImagePullPrincipalType; + public readonly secretsManagerCredentials?: secretsmanager.ISecret; + public readonly repository?: ecr.IRepository; + + private constructor(props: LinuxBuildImageProps) { + this.imageId = props.imageId; + this.imagePullPrincipalType = props.imagePullPrincipalType; + this.secretsManagerCredentials = props.secretsManagerCredentials; + this.repository = props.repository; } public validate(_: BuildEnvironment): string[] { @@ -1120,6 +1230,17 @@ export class LinuxBuildImage implements IBuildImage { } } +/** + * Construction properties of {@link WindowsBuildImage}. + * Module-private, as the constructor of {@link WindowsBuildImage} is private. + */ +interface WindowsBuildImageProps { + readonly imageId: string; + readonly imagePullPrincipalType?: ImagePullPrincipalType; + readonly secretsManagerCredentials?: secretsmanager.ISecret; + readonly repository?: ecr.IRepository; +} + /** * A CodeBuild image running Windows. * @@ -1127,20 +1248,27 @@ export class LinuxBuildImage implements IBuildImage { * * You can also specify a custom image using one of the static methods: * - * - WindowsBuildImage.fromDockerHub(image) + * - WindowsBuildImage.fromDockerRegistry(image[, { secretsManagerCredentials }]) * - WindowsBuildImage.fromEcrRepository(repo[, tag]) * - WindowsBuildImage.fromAsset(parent, id, props) * * @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html */ export class WindowsBuildImage implements IBuildImage { - public static readonly WIN_SERVER_CORE_2016_BASE = new WindowsBuildImage('aws/codebuild/windows-base:1.0'); + public static readonly WIN_SERVER_CORE_2016_BASE: IBuildImage = new WindowsBuildImage({ + imageId: 'aws/codebuild/windows-base:1.0', + imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD, + }); /** * @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, options: DockerImageOptions): IBuildImage { + return new WindowsBuildImage({ + ...options, + imageId: name, + imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, + }); } /** @@ -1154,29 +1282,38 @@ export class WindowsBuildImage implements IBuildImage { * @param repository The ECR repository * @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; + public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest'): IBuildImage { + return new WindowsBuildImage({ + imageId: repository.repositoryUriForTag(tag), + imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, + repository, + }); } /** * Uses an Docker image asset as a Windows build image. */ - public static fromAsset(scope: Construct, id: string, props: DockerImageAssetProps): WindowsBuildImage { + public static fromAsset(scope: Construct, id: string, props: DockerImageAssetProps): IBuildImage { 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({ + imageId: asset.imageUri, + imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, + repository: asset.repository, + }); } + public readonly type = 'WINDOWS_CONTAINER'; public readonly defaultComputeType = ComputeType.MEDIUM; - - private constructor(public readonly imageId: string) { + public readonly imageId: string; + public readonly imagePullPrincipalType?: ImagePullPrincipalType; + public readonly secretsManagerCredentials?: secretsmanager.ISecret; + public readonly repository?: ecr.IRepository; + + private constructor(props: WindowsBuildImageProps) { + this.imageId = props.imageId; + this.imagePullPrincipalType = props.imagePullPrincipalType; + this.secretsManagerCredentials = props.secretsManagerCredentials; + this.repository = props.repository; } public validate(buildEnvironment: BuildEnvironment): string[] { @@ -1238,12 +1375,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; -} diff --git a/packages/@aws-cdk/aws-codebuild/lib/source.ts b/packages/@aws-cdk/aws-codebuild/lib/source.ts index 1bc03d1cdd127..3cd171287b572 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/source.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/source.ts @@ -50,23 +50,23 @@ export interface SourceProps { * Source provider definition for a CodeBuild Project. */ export abstract class Source implements ISource { - public static s3(props: S3SourceProps): Source { + public static s3(props: S3SourceProps): ISource { return new S3Source(props); } - public static codeCommit(props: CodeCommitSourceProps): Source { + public static codeCommit(props: CodeCommitSourceProps): ISource { return new CodeCommitSource(props); } - public static gitHub(props: GitHubSourceProps): Source { + public static gitHub(props: GitHubSourceProps): ISource { return new GitHubSource(props); } - public static gitHubEnterprise(props: GitHubEnterpriseSourceProps): Source { + public static gitHubEnterprise(props: GitHubEnterpriseSourceProps): ISource { return new GitHubEnterpriseSource(props); } - public static bitBucket(props: BitBucketSourceProps): Source { + public static bitBucket(props: BitBucketSourceProps): ISource { return new BitBucketSource(props); } diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 00f927c63ffaf..53b1c87a824fc 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -90,6 +90,7 @@ "@aws-cdk/aws-kms": "^0.37.0", "@aws-cdk/aws-s3": "^0.37.0", "@aws-cdk/aws-s3-assets": "^0.37.0", + "@aws-cdk/aws-secretsmanager": "^0.37.0", "@aws-cdk/core": "^0.37.0" }, "homepage": "https://github.com/awslabs/aws-cdk", @@ -105,6 +106,7 @@ "@aws-cdk/aws-kms": "^0.37.0", "@aws-cdk/aws-s3": "^0.37.0", "@aws-cdk/aws-s3-assets": "^0.37.0", + "@aws-cdk/aws-secretsmanager": "^0.37.0", "@aws-cdk/core": "^0.37.0" }, "engines": { @@ -118,4 +120,4 @@ ] }, "stability": "stable" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json index d4b6b0e10e84b..5a66818ed4c7f 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.docker-asset.lit.expected.json @@ -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": [ @@ -262,6 +235,45 @@ "Properties": { "PolicyDocument": { "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "MyImageAdoptRepository6CA902F6", + "RepositoryName" + ] + } + ] + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + }, { "Action": [ "logs:CreateLogGroup", @@ -439,6 +451,7 @@ ] ] }, + "ImagePullCredentialsType": "SERVICE_ROLE", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, @@ -455,4 +468,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.expected.json new file mode 100644 index 0000000000000..b8cd00a66ffb2 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.expected.json @@ -0,0 +1,148 @@ +{ + "Resources": { + "MyProjectRole9BBE5233": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "codebuild.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyProjectRoleDefaultPolicyB19B7C29": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + }, + ":*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", + "Roles": [ + { + "Ref": "MyProjectRole9BBE5233" + } + ] + } + }, + "MyProject39F7B0AE": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "my-registry/my-repo", + "ImagePullCredentialsType": "SERVICE_ROLE", + "PrivilegedMode": false, + "RegistryCredential": { + "Credential": { + "Fn::Join": [ + "", + [ + "arn:aws:secretsmanager:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":secret:my-secrets-123456" + ] + ] + }, + "CredentialProvider": "SECRETS_MANAGER" + }, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"build\": {\n \"commands\": [\n \"ls\"\n ]\n }\n }\n}", + "Type": "NO_SOURCE" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.ts b/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.ts new file mode 100644 index 0000000000000..69639cd196efe --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.docker-registry.lit.ts @@ -0,0 +1,36 @@ +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); +import cdk = require('@aws-cdk/core'); +import codebuild = require('../lib'); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string) { + super(scope, id); + + const secrets = secretsmanager.Secret.fromSecretArn(this, "MySecrets", + `arn:aws:secretsmanager:${this.region}:${this.account}:secret:my-secrets-123456`); + + new codebuild.Project(this, 'MyProject', { + buildSpec: codebuild.BuildSpec.fromObject({ + version: "0.2", + phases: { + build: { + commands: [ 'ls' ] + } + } + }), + /// !show + environment: { + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('my-registry/my-repo', { + secretsManagerCredentials: secrets, + }), + }, + /// !hide + }); + } +} + +const app = new cdk.App(); + +new TestStack(app, 'test-codebuild-docker-asset'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.ecr.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.ecr.lit.expected.json index 5bac318649a12..ae939917ab996 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.ecr.lit.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.ecr.lit.expected.json @@ -1,37 +1,8 @@ { "Resources": { "MyRepoF4F48043": { - "DeletionPolicy": "Retain", "Type": "AWS::ECR::Repository", - "Properties": { - "RepositoryPolicyText": { - "Statement": [ - { - "Action": [ - "ecr:GetDownloadUrlForLayer", - "ecr:BatchGetImage", - "ecr:BatchCheckLayerAvailability" - ], - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "codebuild.", - { - "Ref": "AWS::URLSuffix" - } - ] - ] - } - }, - "Sid": "CodeBuild" - } - ], - "Version": "2012-10-17" - } - } + "DeletionPolicy": "Retain" }, "MyProjectRole9BBE5233": { "Type": "AWS::IAM::Role", @@ -65,6 +36,25 @@ "Properties": { "PolicyDocument": { "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyRepoF4F48043", + "Arn" + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + }, { "Action": [ "logs:CreateLogGroup", @@ -186,6 +176,7 @@ ] ] }, + "ImagePullCredentialsType": "SERVICE_ROLE", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, @@ -202,4 +193,4 @@ } } } -} \ No newline at end of file +}