Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sign stage for NuGet code signing with AWS Signer #1466

Merged
merged 15 commits into from
Oct 6, 2023
240 changes: 240 additions & 0 deletions lib/__tests__/signing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import { App, Stack } from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import { Repository } from 'aws-cdk-lib/aws-codecommit';
import { Role } from 'aws-cdk-lib/aws-iam';
import { Function } from 'aws-cdk-lib/aws-lambda';
import { Bucket } from 'aws-cdk-lib/aws-s3';
import { Pipeline } from '../pipeline';
import { CodeCommitRepo } from '../repo';

describe('with standard pipeline', () => {
let stack: Stack;
let pipeline: Pipeline;
beforeEach(() => {
const app = new App();
stack = new Stack(app, 'TestStack');

pipeline = new Pipeline(stack, 'TestPipeline', {
repo: new CodeCommitRepo(new Repository(stack, 'Repo', { repositoryName: 'test' })),
});
});

test('can configure project and sign stage for NuGet signing', () => {
// GIVEN
const signingBucket = Bucket.fromBucketName(stack, 'SigningBucket', 'signing-bucket');
const signingLambda = Function.fromFunctionName(stack, 'SigningLambda', 'signing-lambda');
const signingAccessRole = Role.fromRoleName(stack, 'SigningAccessRole', 'signing-access-role');

// WHEN
pipeline.signNuGetWithSigner({
signingBucket,
signingLambda,
signingAccessRole,
});

// THEN
// verify the sign codebuild project is configured correctly
Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', {
Artifacts: {
Type: 'NO_ARTIFACTS',
},
Environment: {
ComputeType: 'BUILD_GENERAL1_MEDIUM',
EnvironmentVariables: [
{
Name: 'SCRIPT_S3_BUCKET',
Type: 'PLAINTEXT',
Value: {
'Fn::Sub': 'cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}',
},
},
{
Name: 'SCRIPT_S3_KEY',
Type: 'PLAINTEXT',
Value: '1ccec5da3e38e2c229307a68b2feb159deb67f714ce2209b3c680115486b707f.zip',
},
{
Name: 'SIGNING_BUCKET_NAME',
Type: 'PLAINTEXT',
Value: 'signing-bucket',
},
{
Name: 'SIGNING_LAMBDA_ARN',
Type: 'PLAINTEXT',
Value: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':lambda:',
{
Ref: 'AWS::Region',
},
':',
{
Ref: 'AWS::AccountId',
},
':function:signing-lambda',
],
],
},
},
{
Name: 'SIGNING_ACCESS_ROLE_ARN',
Type: 'PLAINTEXT',
Value: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':iam::',
{
Ref: 'AWS::AccountId',
},
':role/signing-access-role',
],
],
},
},
],
Image: 'public.ecr.aws/jsii/superchain:1-buster-slim-node18',
ImagePullCredentialsType: 'SERVICE_ROLE',
PrivilegedMode: false,
Type: 'LINUX_CONTAINER',
},
ServiceRole: {
'Fn::GetAtt': [
'TestPipelineNuGetSigningRole00994E45',
'Arn',
],
},
Source: {
BuildSpec: '{\n \"version\": \"0.2\",\n \"phases\": {\n \"install\": {\n \"commands\": [\n \"command -v yarn > /dev/null || npm install --global yarn\"\n ]\n },\n \"pre_build\": {\n \"commands\": [\n \"echo \\\"Downloading scripts from s3://${SCRIPT_S3_BUCKET}/${SCRIPT_S3_KEY}\\\"\",\n \"aws s3 cp s3://${SCRIPT_S3_BUCKET}/${SCRIPT_S3_KEY} /tmp\",\n \"mkdir -p /tmp/scriptdir\",\n \"unzip /tmp/$(basename $SCRIPT_S3_KEY) -d /tmp/scriptdir\"\n ]\n },\n \"build\": {\n \"commands\": [\n \"export SCRIPT_DIR=/tmp/scriptdir\",\n \"echo \\\"Running sign.sh\\\"\",\n \"/bin/bash /tmp/scriptdir/sign.sh\"\n ]\n }\n },\n \"artifacts\": {\n \"files\": [\n \"**/*\"\n ],\n \"base-directory\": \"../src\"\n }\n}',
Type: 'NO_SOURCE',
},
Cache: {
Type: 'NO_CACHE',
},
EncryptionKey: {
'Fn::GetAtt': [
'TestPipelineBuildPipelineArtifactsBucketEncryptionKeyCD151124',
'Arn',
],
},
});

// verify the sign stage is added to pipeline
Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', {
Stages: [
{
Actions: [
{
ActionTypeId: {
Category: 'Source',
Owner: 'AWS',
Provider: 'CodeCommit',
Version: '1',
},
Configuration: {
RepositoryName: {
'Fn::GetAtt': [
'Repo02AC86CF',
'Name',
],
},
BranchName: 'master',
PollForSourceChanges: false,
},
Name: 'Pull',
OutputArtifacts: [
{
Name: 'Source',
},
],
RoleArn: {
'Fn::GetAtt': [
'TestPipelineBuildPipelineSourcePullCodePipelineActionRoleE3FDD1B5',
'Arn',
],
},
RunOrder: 1,
},
],
Name: 'Source',
},
{
Actions: [
{
ActionTypeId: {
Category: 'Build',
Owner: 'AWS',
Provider: 'CodeBuild',
Version: '1',
},
Configuration: {
ProjectName: {
Ref: 'TestPipelineBuildProject799CEA07',
},
},
InputArtifacts: [
{
Name: 'Source',
},
],
RoleArn: {
'Fn::GetAtt': [
'TestPipelineBuildPipelineBuildCodePipelineActionRole7BE59F77',
'Arn',
],
},
RunOrder: 1,
},
],
Name: 'Build',
},
{
Actions: [
{
ActionTypeId: {
Category: 'Build',
Owner: 'AWS',
Provider: 'CodeBuild',
Version: '1',
},
Configuration: {
ProjectName: {
Ref: 'TestPipelineNuGetSigningCE9AB81F',
},
},
InputArtifacts: [
{
Name: 'Artifact_Build_Build',
},
],
Name: 'NuGetSigningSign',
OutputArtifacts: [
{
Name: 'Artifact_Sign_NuGetSigningSign',
},
],
RoleArn: {
'Fn::GetAtt': [
'TestPipelineBuildPipelineSignNuGetSigningSignCodePipelineActionRoleDD2CA5AF',
'Arn',
],
},
RunOrder: 1,
},
],
Name: 'Sign',
},
],
});
});
});
26 changes: 26 additions & 0 deletions lib/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import { AutoBump, AutoMergeBack, AutoBumpProps } from './pull-request';
import { AutoMergeBackPipelineOptions } from './pull-request/merge-back';
import { IRepo, WritableGitHubRepo } from './repo';
import { Shellable, ShellableProps } from './shellable';
import * as signing from './signing';
import { determineRunOrder, flatMap } from './util';

const PUBLISH_STAGE_NAME = 'Publish';
const SIGINING_STAGE_NAME = 'Sign';
const TEST_STAGE_NAME = 'Test';
const METRIC_NAMESPACE = 'CDK/Delivlib';
const FAILURE_METRIC_NAME = 'Failures';
Expand Down Expand Up @@ -206,6 +208,7 @@ export class Pipeline extends Construct {
private readonly branch: string;
private readonly notify?: sns.Topic;
private stages: { [name: string]: cpipeline.IStage } = { };
private _signingOutput?: cpipeline.Artifact;

private readonly concurrency?: number;
private readonly repo: IRepo;
Expand Down Expand Up @@ -282,6 +285,13 @@ export class Pipeline extends Construct {
}
}

/**
* Signing output artifact
*/
public get signingOutput() {
return this._signingOutput;
}

public notifyOnFailure(notification: IPipelineNotification) {
notification.bind({
pipeline: this,
Expand Down Expand Up @@ -357,6 +367,22 @@ export class Pipeline extends Construct {
});
}

public addSigning(signer: signing.ISigner, options: signing.AddSigningOptions = {}) {
const signingStageName = options.stageName ?? SIGINING_STAGE_NAME;
const stage = this.getOrCreateStage(signingStageName);

this._signingOutput = signer.addToPipeline(stage, `${signer.node.id}Sign`, {
inputArtifact: options.inputArtifact || this.buildOutput,
runOrder: this.determineRunOrderForNewAction(stage),
});
}

public signNuGetWithSigner(options: signing.SignNuGetWithSignerProps & signing.AddSigningOptions) {
this.addSigning(new signing.SignNuGetWithSigner(this, 'NuGetSigning', {
...options,
}), options);
}

public publishToNpm(options: publishing.PublishToNpmProjectProps & AddPublishOptions) {
this.addPublish(new publishing.PublishToNpmProject(this, 'Npm', {
dryRun: this.dryRun,
Expand Down
Loading