From db043b2e45ef6e7db694ff074bf4dea4b9f0150a Mon Sep 17 00:00:00 2001 From: Amir Szekely Date: Thu, 30 Jun 2022 09:55:41 -0700 Subject: [PATCH] feat: Support self-signed certificates (#67) --- API.md | 129 +++++++++++++++++- .../codebuild/linux-arm64/Dockerfile | 6 +- .../codebuild/linux-x64/Dockerfile | 6 +- .../fargate/linux-arm64/Dockerfile | 6 +- .../fargate/linux-x64/Dockerfile | 6 +- .../lambda/linux-arm64/Dockerfile | 4 + .../docker-images/lambda/linux-x64/Dockerfile | 4 + src/providers/image-builders/codebuild.ts | 17 ++- src/runner.ts | 88 ++++++++---- 9 files changed, 230 insertions(+), 36 deletions(-) diff --git a/API.md b/API.md index 9304fd52..10bb6f69 100644 --- a/API.md +++ b/API.md @@ -68,6 +68,7 @@ new CodeBuildImageBuilder(scope: Construct, id: string, props: CodeBuildImageBui | **Name** | **Description** | | --- | --- | | toString | Returns a string representation of this construct. | +| addExtraCertificates | Add extra trusted certificates. This helps deal with self-signed certificates for GitHub Enterprise Server. | | addFiles | Uploads a folder to the build server at a given folder name. | | addPolicyStatement | Add a policy statement to the builder to access resources required to the image build. | | addPostBuildCommand | Adds a command that runs after `docker build` and `docker push`. | @@ -85,6 +86,24 @@ public toString(): string Returns a string representation of this construct. +##### `addExtraCertificates` + +```typescript +public addExtraCertificates(path: string): void +``` + +Add extra trusted certificates. This helps deal with self-signed certificates for GitHub Enterprise Server. + +All first party Dockerfiles support this. Others may not. + +###### `path`Required + +- *Type:* string + +path to directory containing a file called certs.pem containing all the required certificates. + +--- + ##### `addFiles` ```typescript @@ -814,20 +833,20 @@ It creates a webhook, secrets, and a step function to orchestrate all runs. Secr By default, this will create a runner provider of each available type with the defaults. This is good enough for the initial setup stage when you just want to get GitHub integration working. ```typescript -new GitHubRunners(stack, 'runners', {}); +new GitHubRunners(this, 'runners'); ``` Usually you'd want to configure the runner providers so the runners can run in a certain VPC or have certain permissions. ```typescript -const vpc = ec2.Vpc.fromLookup(stack, 'vpc', { vpcId: 'vpc-1234567' }); -const runnerSg = new ec2.SecurityGroup(stack, 'runner security group', { vpc: vpc }); -const dbSg = ec2.SecurityGroup.fromSecurityGroupId(stack, 'database security group', 'sg-1234567'); -const bucket = new s3.Bucket(stack, 'runner bucket'); +const vpc = ec2.Vpc.fromLookup(this, 'vpc', { vpcId: 'vpc-1234567' }); +const runnerSg = new ec2.SecurityGroup(this, 'runner security group', { vpc: vpc }); +const dbSg = ec2.SecurityGroup.fromSecurityGroupId(this, 'database security group', 'sg-1234567'); +const bucket = new s3.Bucket(this, 'runner bucket'); // create a custom CodeBuild provider const myProvider = new CodeBuildRunner( - stack, 'codebuild runner', + this, 'codebuild runner', { label: 'my-codebuild', vpc: vpc, @@ -840,7 +859,7 @@ dbSg.connections.allowFrom(runnerSg, ec2.Port.tcp(3306), 'allow runners to conne // create the runner infrastructure new GitHubRunners( - stack, + this, 'runners', { providers: [myProvider], @@ -1962,7 +1981,61 @@ const gitHubRunnersProps: GitHubRunnersProps = { ... } | **Name** | **Type** | **Description** | | --- | --- | --- | +| allowPublicSubnet | boolean | Allow management functions to run in public subnets. | +| extraCertificates | string | Path to a directory containing a file named certs.pem containing any additional certificates required to trust GitHub Enterprise Server. Use this when GitHub Enterprise Server certificates are self-signed. | | providers | IRunnerProvider[] | List of runner providers to use. | +| securityGroup | aws-cdk-lib.aws_ec2.ISecurityGroup | Security group attached to all management functions. | +| vpc | aws-cdk-lib.aws_ec2.IVpc | VPC used for all management functions. | +| vpcSubnets | aws-cdk-lib.aws_ec2.SubnetSelection | VPC subnets used for all management functions. | + +--- + +##### `allowPublicSubnet`Optional + +```typescript +public readonly allowPublicSubnet: boolean; +``` + +- *Type:* boolean +- *Default:* false + +Allow management functions to run in public subnets. + +Lambda Functions in a public subnet can NOT access the internet. + +--- + +##### `extraCertificates`Optional + +```typescript +public readonly extraCertificates: string; +``` + +- *Type:* string + +Path to a directory containing a file named certs.pem containing any additional certificates required to trust GitHub Enterprise Server. Use this when GitHub Enterprise Server certificates are self-signed. + +You may also want to use custom images for your runner providers that contain the same certificates. See {@link CodeBuildImageBuilder.addCertificates}. + +```typescript +const imageBuilder = new CodeBuildImageBuilder(this, 'Image Builder with Certs', { + dockerfilePath: CodeBuildRunner.LINUX_X64_DOCKERFILE_PATH, +}); +imageBuilder.addExtraCertificates('path-to-my-extra-certs-folder'); + +const provider = new CodeBuildRunner(this, 'CodeBuild', { + imageBuilder: imageBuilder, +}); + +new GitHubRunners( + this, + 'runners', + { + providers: [provider], + extraCertificates: 'path-to-my-extra-certs-folder', + } +); +``` --- @@ -1981,6 +2054,48 @@ At least one provider is required. Provider will be selected when its label matc --- +##### `securityGroup`Optional + +```typescript +public readonly securityGroup: ISecurityGroup; +``` + +- *Type:* aws-cdk-lib.aws_ec2.ISecurityGroup + +Security group attached to all management functions. + +Use this with to provide access to GitHub Enterprise Server hosted inside a VPC. + +--- + +##### `vpc`Optional + +```typescript +public readonly vpc: IVpc; +``` + +- *Type:* aws-cdk-lib.aws_ec2.IVpc + +VPC used for all management functions. + +Use this with GitHub Enterprise Server hosted that's inaccessible from outside the VPC. + +--- + +##### `vpcSubnets`Optional + +```typescript +public readonly vpcSubnets: SubnetSelection; +``` + +- *Type:* aws-cdk-lib.aws_ec2.SubnetSelection + +VPC subnets used for all management functions. + +Use this with GitHub Enterprise Server hosted that's inaccessible from outside the VPC. + +--- + ### LambdaRunnerProps #### Initializer diff --git a/src/providers/docker-images/codebuild/linux-arm64/Dockerfile b/src/providers/docker-images/codebuild/linux-arm64/Dockerfile index f9325d1d..9bb23480 100644 --- a/src/providers/docker-images/codebuild/linux-arm64/Dockerfile +++ b/src/providers/docker-images/codebuild/linux-arm64/Dockerfile @@ -6,10 +6,14 @@ RUN addgroup runner && adduser --system --disabled-password --home /home/runner # add dependencies and sudo ARG EXTRA_PACKAGES="" -RUN apt-get update && apt-get upgrade -y && apt-get install -y curl sudo jq bash zip unzip iptables software-properties-common $EXTRA_PACKAGES && \ +RUN apt-get update && apt-get upgrade -y && apt-get install -y curl sudo jq bash zip unzip iptables software-properties-common ca-certificates $EXTRA_PACKAGES && \ usermod -aG sudo runner && \ echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/runner +# install extra certificates +COPY extra_certs/. /tmp/certs/ +RUN if [ -f /tmp/certs/certs.pem ]; then cp /tmp/certs/certs.pem /usr/local/share/ca-certificates/github-enterprise-server.crt; update-ca-certificates; else echo no self-signed certificates; fi + # add latest git RUN add-apt-repository ppa:git-core/ppa && apt update && apt-get install -y git diff --git a/src/providers/docker-images/codebuild/linux-x64/Dockerfile b/src/providers/docker-images/codebuild/linux-x64/Dockerfile index 96c799bb..166495fc 100644 --- a/src/providers/docker-images/codebuild/linux-x64/Dockerfile +++ b/src/providers/docker-images/codebuild/linux-x64/Dockerfile @@ -6,10 +6,14 @@ RUN addgroup runner && adduser --system --disabled-password --home /home/runner # add dependencies and sudo ARG EXTRA_PACKAGES="" -RUN apt-get update && apt-get upgrade -y && apt-get install -y curl sudo jq bash zip unzip iptables software-properties-common $EXTRA_PACKAGES && \ +RUN apt-get update && apt-get upgrade -y && apt-get install -y curl sudo jq bash zip unzip iptables software-properties-common ca-certificates $EXTRA_PACKAGES && \ usermod -aG sudo runner && \ echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/runner +# install extra certificates +COPY extra_certs/. /tmp/certs/ +RUN if [ -f /tmp/certs/certs.pem ]; then cp /tmp/certs/certs.pem /usr/local/share/ca-certificates/github-enterprise-server.crt; update-ca-certificates; else echo no self-signed certificates; fi + # add latest git RUN add-apt-repository ppa:git-core/ppa && apt update && apt-get install -y git diff --git a/src/providers/docker-images/fargate/linux-arm64/Dockerfile b/src/providers/docker-images/fargate/linux-arm64/Dockerfile index 56f06651..65d4e232 100644 --- a/src/providers/docker-images/fargate/linux-arm64/Dockerfile +++ b/src/providers/docker-images/fargate/linux-arm64/Dockerfile @@ -6,10 +6,14 @@ RUN addgroup runner && adduser --system --disabled-password --home /home/runner # add dependencies and sudo ARG EXTRA_PACKAGES="" -RUN apt-get update && apt-get upgrade -y && apt-get install -y curl sudo jq bash zip unzip software-properties-common $EXTRA_PACKAGES && \ +RUN apt-get update && apt-get upgrade -y && apt-get install -y curl sudo jq bash zip unzip software-properties-common ca-certificates $EXTRA_PACKAGES && \ usermod -aG sudo runner && \ echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/runner +# install extra certificates +COPY extra_certs/. /tmp/certs/ +RUN if [ -f /tmp/certs/certs.pem ]; then cp /tmp/certs/certs.pem /usr/local/share/ca-certificates/github-enterprise-server.crt; update-ca-certificates; else echo no self-signed certificates; fi + # add latest git RUN add-apt-repository ppa:git-core/ppa && apt update && apt-get install -y git diff --git a/src/providers/docker-images/fargate/linux-x64/Dockerfile b/src/providers/docker-images/fargate/linux-x64/Dockerfile index 6db7d73c..afc5c41d 100644 --- a/src/providers/docker-images/fargate/linux-x64/Dockerfile +++ b/src/providers/docker-images/fargate/linux-x64/Dockerfile @@ -6,10 +6,14 @@ RUN addgroup runner && adduser --system --disabled-password --home /home/runner # add dependencies and sudo ARG EXTRA_PACKAGES="" -RUN apt-get update && apt-get upgrade -y && apt-get install -y curl sudo jq bash zip unzip software-properties-common $EXTRA_PACKAGES && \ +RUN apt-get update && apt-get upgrade -y && apt-get install -y curl sudo jq bash zip unzip software-properties-common ca-certificates $EXTRA_PACKAGES && \ usermod -aG sudo runner && \ echo "%sudo ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/runner +# install extra certificates +COPY extra_certs/. /tmp/certs/ +RUN if [ -f /tmp/certs/certs.pem ]; then cp /tmp/certs/certs.pem /usr/local/share/ca-certificates/github-enterprise-server.crt; update-ca-certificates; else echo no self-signed certificates; fi + # add latest git RUN add-apt-repository ppa:git-core/ppa && apt update && apt-get install -y git diff --git a/src/providers/docker-images/lambda/linux-arm64/Dockerfile b/src/providers/docker-images/lambda/linux-arm64/Dockerfile index f729031c..bd1f48d7 100644 --- a/src/providers/docker-images/lambda/linux-arm64/Dockerfile +++ b/src/providers/docker-images/lambda/linux-arm64/Dockerfile @@ -5,6 +5,10 @@ FROM $BASE_IMAGE WORKDIR /runner +# install extra certificates +COPY extra_certs/. /tmp/certs/ +RUN if [ -f /tmp/certs/certs.pem ]; then cp /tmp/certs/certs.pem /etc/pki/ca-trust/source/anchors/ghe.crt; update-ca-trust; else echo no self-signed certificates; fi + # add dependencies ARG EXTRA_PACKAGES="" RUN yum update -y && yum install -y jq tar gzip bzip2 which binutils git zip unzip $EXTRA_PACKAGES diff --git a/src/providers/docker-images/lambda/linux-x64/Dockerfile b/src/providers/docker-images/lambda/linux-x64/Dockerfile index bc02dc59..8afdedb9 100644 --- a/src/providers/docker-images/lambda/linux-x64/Dockerfile +++ b/src/providers/docker-images/lambda/linux-x64/Dockerfile @@ -5,6 +5,10 @@ FROM $BASE_IMAGE WORKDIR /runner +# install extra certificates +COPY extra_certs/. /tmp/certs/ +RUN if [ -f /tmp/certs/certs.pem ]; then cp /tmp/certs/certs.pem /etc/pki/ca-trust/source/anchors/ghe.crt; update-ca-trust; else echo no self-signed certificates; fi + # add dependencies ARG EXTRA_PACKAGES="" RUN yum update -y && yum install -y jq tar gzip bzip2 which binutils git zip unzip $EXTRA_PACKAGES diff --git a/src/providers/image-builders/codebuild.ts b/src/providers/image-builders/codebuild.ts index 85fa5b84..e88964ca 100644 --- a/src/providers/image-builders/codebuild.ts +++ b/src/providers/image-builders/codebuild.ts @@ -209,7 +209,7 @@ export class CodeBuildImageBuilder extends Construct implements IImageBuilder { const asset = new s3_assets.Asset(this, destName, { path: sourcePath }); this.secondaryAssets.set(destName, asset); - this.preBuild.push(`ln -s "$CODEBUILD_SRC_DIR_${destName}" "${destName}"`); + this.preBuild.push(`rm -rf "${destName}" && cp -r "$CODEBUILD_SRC_DIR_${destName}" "${destName}"`); // symlinks don't work with docker } /** @@ -261,6 +261,20 @@ export class CodeBuildImageBuilder extends Construct implements IImageBuilder { this.policyStatements.push(statement); } + /** + * Add extra trusted certificates. This helps deal with self-signed certificates for GitHub Enterprise Server. + * + * All first party Dockerfiles support this. Others may not. + * + * @param path path to directory containing a file called certs.pem containing all the required certificates + */ + public addExtraCertificates(path: string) { + if (this.boundImage) { + throw new Error('Image is already bound. Use this method before passing the builder to a runner provider.'); + } + this.addFiles(path, 'extra_certs'); + } + /** * Called by IRunnerProvider to finalize settings and create the image builder. */ @@ -378,6 +392,7 @@ export class CodeBuildImageBuilder extends Construct implements IImageBuilder { phases: { pre_build: { commands: this.preBuild.concat([ + 'mkdir -p extra_certs', '$(aws ecr get-login --no-include-email --region "$AWS_DEFAULT_REGION")', ]), }, diff --git a/src/runner.ts b/src/runner.ts index 0c94a7d7..6c0e5e93 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -1,5 +1,11 @@ import * as cdk from 'aws-cdk-lib'; -import { aws_ec2 as ec2, aws_iam as iam, aws_stepfunctions as stepfunctions, aws_stepfunctions_tasks as stepfunctions_tasks } from 'aws-cdk-lib'; +import { + aws_ec2 as ec2, + aws_iam as iam, + aws_lambda as lambda, + aws_stepfunctions as stepfunctions, + aws_stepfunctions_tasks as stepfunctions_tasks, +} from 'aws-cdk-lib'; import { FunctionUrlAuthType } from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; import { CodeBuildRunner } from './providers/codebuild'; @@ -42,6 +48,33 @@ export interface GitHubRunnersProps { * Security group attached to all management functions. Use this with to provide access to GitHub Enterprise Server hosted inside a VPC. */ readonly securityGroup?: ec2.ISecurityGroup; + + /** + * Path to a directory containing a file named certs.pem containing any additional certificates required to trust GitHub Enterprise Server. Use this when GitHub Enterprise Server certificates are self-signed. + * + * You may also want to use custom images for your runner providers that contain the same certificates. See {@link CodeBuildImageBuilder.addCertificates}. + * + * ```typescript + * const imageBuilder = new CodeBuildImageBuilder(this, 'Image Builder with Certs', { + * dockerfilePath: CodeBuildRunner.LINUX_X64_DOCKERFILE_PATH, + * }); + * imageBuilder.addExtraCertificates('path-to-my-extra-certs-folder'); + * + * const provider = new CodeBuildRunner(this, 'CodeBuild', { + * imageBuilder: imageBuilder, + * }); + * + * new GitHubRunners( + * this, + * 'runners', + * { + * providers: [provider], + * extraCertificates: 'path-to-my-extra-certs-folder', + * } + * ); + * ``` + */ + readonly extraCertificates?: string; } /** @@ -50,20 +83,20 @@ export interface GitHubRunnersProps { * By default, this will create a runner provider of each available type with the defaults. This is good enough for the initial setup stage when you just want to get GitHub integration working. * * ```typescript - * new GitHubRunners(stack, 'runners', {}); + * new GitHubRunners(this, 'runners'); * ``` * * Usually you'd want to configure the runner providers so the runners can run in a certain VPC or have certain permissions. * * ```typescript - * const vpc = ec2.Vpc.fromLookup(stack, 'vpc', { vpcId: 'vpc-1234567' }); - * const runnerSg = new ec2.SecurityGroup(stack, 'runner security group', { vpc: vpc }); - * const dbSg = ec2.SecurityGroup.fromSecurityGroupId(stack, 'database security group', 'sg-1234567'); - * const bucket = new s3.Bucket(stack, 'runner bucket'); + * const vpc = ec2.Vpc.fromLookup(this, 'vpc', { vpcId: 'vpc-1234567' }); + * const runnerSg = new ec2.SecurityGroup(this, 'runner security group', { vpc: vpc }); + * const dbSg = ec2.SecurityGroup.fromSecurityGroupId(this, 'database security group', 'sg-1234567'); + * const bucket = new s3.Bucket(this, 'runner bucket'); * * // create a custom CodeBuild provider * const myProvider = new CodeBuildRunner( - * stack, 'codebuild runner', + * this, 'codebuild runner', * { * label: 'my-codebuild', * vpc: vpc, @@ -76,7 +109,7 @@ export interface GitHubRunnersProps { * * // create the runner infrastructure * new GitHubRunners( - * stack, + * this, * 'runners', * { * providers: [myProvider], @@ -101,12 +134,27 @@ export class GitHubRunners extends Construct { private readonly webhook: GithubWebhookHandler; private readonly orchestrator: stepfunctions.StateMachine; private readonly setupUrl: string; + private readonly extraLambdaEnv: {[p: string]: string} = {}; + private readonly extraLambdaProps: lambda.FunctionOptions; constructor(scope: Construct, id: string, props?: GitHubRunnersProps) { super(scope, id); this.props = props ?? {}; this.secrets = new Secrets(this, 'Secrets'); + this.extraLambdaProps = { + vpc: this.props.vpc, + vpcSubnets: this.props.vpcSubnets, + allowPublicSubnet: this.props.allowPublicSubnet, + securityGroups: this.props.securityGroup ? [this.props.securityGroup] : undefined, + layers: this.props.extraCertificates ? [new lambda.LayerVersion(scope, 'Certificate Layer', { + description: 'Layer containing GitHub Enterprise Server certificate for cdk-github-runners', + code: lambda.Code.fromAsset(this.props.extraCertificates), + })] : undefined, + }; + if (this.props.extraCertificates) { + this.extraLambdaEnv.NODE_EXTRA_CA_CERTS = '/opt/certs.pem'; + } if (this.props.providers) { this.providers = this.props.providers; @@ -209,12 +257,10 @@ export class GitHubRunners extends Construct { environment: { GITHUB_SECRET_ARN: this.secrets.github.secretArn, GITHUB_PRIVATE_KEY_SECRET_ARN: this.secrets.githubPrivateKey.secretArn, + ...this.extraLambdaEnv, }, timeout: cdk.Duration.seconds(30), - vpc: this.props.vpc, - vpcSubnets: this.props.vpcSubnets, - allowPublicSubnet: this.props.allowPublicSubnet, - securityGroups: this.props.securityGroup ? [this.props.securityGroup] : undefined, + ...this.extraLambdaProps, }, ); @@ -233,12 +279,10 @@ export class GitHubRunners extends Construct { environment: { GITHUB_SECRET_ARN: this.secrets.github.secretArn, GITHUB_PRIVATE_KEY_SECRET_ARN: this.secrets.githubPrivateKey.secretArn, + ...this.extraLambdaEnv, }, timeout: cdk.Duration.seconds(30), - vpc: this.props.vpc, - vpcSubnets: this.props.vpcSubnets, - allowPublicSubnet: this.props.allowPublicSubnet, - securityGroups: this.props.securityGroup ? [this.props.securityGroup] : undefined, + ...this.extraLambdaProps, }, ); @@ -274,12 +318,10 @@ export class GitHubRunners extends Construct { WEBHOOK_HANDLER_ARN: this.webhook.handler.latestVersion.functionArn, STEP_FUNCTION_ARN: this.orchestrator.stateMachineArn, SETUP_FUNCTION_URL: this.setupUrl, + ...this.extraLambdaEnv, }, timeout: cdk.Duration.minutes(3), - vpc: this.props.vpc, - vpcSubnets: this.props.vpcSubnets, - allowPublicSubnet: this.props.allowPublicSubnet, - securityGroups: this.props.securityGroup ? [this.props.securityGroup] : undefined, + ...this.extraLambdaProps, }, ); @@ -310,12 +352,10 @@ export class GitHubRunners extends Construct { GITHUB_SECRET_ARN: this.secrets.github.secretArn, GITHUB_PRIVATE_KEY_SECRET_ARN: this.secrets.githubPrivateKey.secretArn, WEBHOOK_URL: this.webhook.url, + ...this.extraLambdaEnv, }, timeout: cdk.Duration.minutes(3), - vpc: this.props.vpc, - vpcSubnets: this.props.vpcSubnets, - allowPublicSubnet: this.props.allowPublicSubnet, - securityGroups: this.props.securityGroup ? [this.props.securityGroup] : undefined, + ...this.extraLambdaProps, }, );