Skip to content

Commit

Permalink
feat(lambda): code signing config (#12656)
Browse files Browse the repository at this point in the history
closes #12216 

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
Masaharu Komuro authored Feb 25, 2021
1 parent 09723f5 commit 778ea27
Show file tree
Hide file tree
Showing 13 changed files with 635 additions and 12 deletions.
24 changes: 24 additions & 0 deletions packages/@aws-cdk/aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,27 @@ Language-specific higher level constructs are provided in separate modules:

* `@aws-cdk/aws-lambda-nodejs`: [Github](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-lambda-nodejs) & [CDK Docs](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-nodejs-readme.html)
* `@aws-cdk/aws-lambda-python`: [Github](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-lambda-python) & [CDK Docs](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-python-readme.html)

## Code Signing

Code signing for AWS Lambda helps to ensure that only trusted code runs in your Lambda functions.
When enabled, AWS Lambda checks every code deployment and verifies that the code package is signed by a trusted source.
For more information, see [Configuring code signing for AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/configuration-codesigning.html).
The following code configures a function with code signing.

```typescript
import * as signer from '@aws-cdk/aws-signer';

const signerProfile = signer.SigningProfile(this, 'SigningProfile', {
platform: Platform.AWS_LAMBDA_SHA384_ECDSA
});

const codeSigningConfig = new lambda.CodeSigningConfig(stack, 'CodeSigningConfig', {
signingProfiles: [signingProfile],
});

new lambda.Function(this, 'Function', {
codeSigningConfig,
// ...
});
```
120 changes: 120 additions & 0 deletions packages/@aws-cdk/aws-lambda/lib/code-signing-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { ISigningProfile } from '@aws-cdk/aws-signer';
import { IResource, Resource, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnCodeSigningConfig } from './lambda.generated';

/**
* Code signing configuration policy for deployment validation failure.
*/
export enum UntrustedArtifactOnDeployment {
/**
* Lambda blocks the deployment request if signature validation checks fail.
*/
ENFORCE = 'enforce',

/**
* Lambda allows the deployment of the code package, but issues a warning.
* Lambda issues a new Amazon CloudWatch metric, called a signature validation error and also stores the warning in CloudTrail.
*/
WARN = 'warn',
}

/**
* A Code Signing Config
*/
export interface ICodeSigningConfig extends IResource {
/**
* The ARN of Code Signing Config
* @attribute
*/
readonly codeSigningConfigArn: string;

/**
* The id of Code Signing Config
* @attribute
*/
readonly codeSigningConfigId: string;
}

/**
* Construction properties for a Code Signing Config object
*/
export interface CodeSigningConfigProps {
/**
* List of signing profiles that defines a
* trusted user who can sign a code package.
*/
readonly signingProfiles: ISigningProfile[],

/**
* Code signing configuration policy for deployment validation failure.
* If you set the policy to Enforce, Lambda blocks the deployment request
* if signature validation checks fail.
* If you set the policy to Warn, Lambda allows the deployment and
* creates a CloudWatch log.
*
* @default UntrustedArtifactOnDeployment.WARN
*/
readonly untrustedArtifactOnDeployment?: UntrustedArtifactOnDeployment,

/**
* Code signing configuration description.
*
* @default - No description.
*/
readonly description?: string,
}

/**
* Defines a Code Signing Config.
*
* @resource AWS::Lambda::CodeSigningConfig
*/
export class CodeSigningConfig extends Resource implements ICodeSigningConfig {
/**
* Creates a Signing Profile construct that represents an external Signing Profile.
*
* @param scope The parent creating construct (usually `this`).
* @param id The construct's name.
* @param codeSigningConfigArn The ARN of code signing config.
*/
public static fromCodeSigningConfigArn( scope: Construct, id: string, codeSigningConfigArn: string): ICodeSigningConfig {
const codeSigningProfileId = Stack.of(scope).parseArn(codeSigningConfigArn).resourceName;
if (!codeSigningProfileId) {
throw new Error(`Code signing config ARN must be in the format 'arn:aws:lambda:<region>:<account>:code-signing-config:<codeSigningConfigArn>', got: '${codeSigningConfigArn}'`);
}
const assertedCodeSigningProfileId = codeSigningProfileId;
class Import extends Resource implements ICodeSigningConfig {
public readonly codeSigningConfigArn = codeSigningConfigArn;
public readonly codeSigningConfigId = assertedCodeSigningProfileId;

constructor() {
super(scope, id);
}
}
return new Import();
}

public readonly codeSigningConfigArn: string;
public readonly codeSigningConfigId: string;

constructor(scope: Construct, id: string, props: CodeSigningConfigProps) {
super(scope, id);

const signingProfileVersionArns = props.signingProfiles.map(signingProfile => {
return signingProfile.signingProfileVersionArn;
});

const resource: CfnCodeSigningConfig = new CfnCodeSigningConfig(this, 'Resource', {
allowedPublishers: {
signingProfileVersionArns,
},
codeSigningPolicies: {
untrustedArtifactOnDeployment: props.untrustedArtifactOnDeployment ?? UntrustedArtifactOnDeployment.WARN,
},
description: props.description,
});
this.codeSigningConfigArn = resource.attrCodeSigningConfigArn;
this.codeSigningConfigId = resource.attrCodeSigningConfigId;
}
}
9 changes: 9 additions & 0 deletions packages/@aws-cdk/aws-lambda/lib/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as sqs from '@aws-cdk/aws-sqs';
import { Annotations, CfnResource, Duration, Fn, Lazy, Names, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { Code, CodeConfig } from './code';
import { ICodeSigningConfig } from './code-signing-config';
import { EventInvokeConfigOptions } from './event-invoke-config';
import { IEventSource } from './event-source';
import { FileSystem } from './filesystem';
Expand Down Expand Up @@ -290,6 +291,13 @@ export interface FunctionOptions extends EventInvokeConfigOptions {
* @default - AWS Lambda creates and uses an AWS managed customer master key (CMK).
*/
readonly environmentEncryption?: kms.IKey;

/**
* Code signing config associated with this function
*
* @default - Not Sign the Code
*/
readonly codeSigningConfig?: ICodeSigningConfig;
}

export interface FunctionProps extends FunctionOptions {
Expand Down Expand Up @@ -641,6 +649,7 @@ export class Function extends FunctionBase {
}),
kmsKeyArn: props.environmentEncryption?.keyArn,
fileSystemConfigs,
codeSigningConfigArn: props.codeSigningConfig?.codeSigningConfigArn,
});

resource.node.addDependency(this.role);
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-lambda/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from './event-source-mapping';
export * from './destination';
export * from './event-invoke-config';
export * from './scalable-attribute-api';
export * from './code-signing-config';

export * from './log-retention';

Expand Down
5 changes: 4 additions & 1 deletion packages/@aws-cdk/aws-lambda/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/aws-signer": "0.0.0",
"@aws-cdk/aws-sqs": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
Expand All @@ -119,6 +120,7 @@
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/aws-signer": "0.0.0",
"@aws-cdk/aws-sqs": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
Expand Down Expand Up @@ -169,7 +171,8 @@
"props-default-doc:@aws-cdk/aws-lambda.Permission.sourceArn",
"docs-public-apis:@aws-cdk/aws-lambda.ResourceBindOptions",
"docs-public-apis:@aws-cdk/aws-lambda.VersionAttributes",
"props-physical-name:@aws-cdk/aws-lambda.EventInvokeConfigProps"
"props-physical-name:@aws-cdk/aws-lambda.EventInvokeConfigProps",
"props-physical-name:@aws-cdk/aws-lambda.CodeSigningConfigProps"
]
},
"stability": "stable",
Expand Down
102 changes: 102 additions & 0 deletions packages/@aws-cdk/aws-lambda/test/code-signing-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import '@aws-cdk/assert/jest';
import * as signer from '@aws-cdk/aws-signer';
import * as cdk from '@aws-cdk/core';
import * as lambda from '../lib';

let app: cdk.App;
let stack: cdk.Stack;
beforeEach( () => {
app = new cdk.App( {} );
stack = new cdk.Stack( app );
} );

describe('code signing config', () => {
test('default', () => {
const platform = signer.Platform.AWS_LAMBDA_SHA384_ECDSA;
const signingProfile = new signer.SigningProfile(stack, 'SigningProfile', { platform });
new lambda.CodeSigningConfig(stack, 'CodeSigningConfig', {
signingProfiles: [signingProfile],
});

expect(stack).toHaveResource('AWS::Lambda::CodeSigningConfig', {
AllowedPublishers: {
SigningProfileVersionArns: [{
'Fn::GetAtt': [
'SigningProfile2139A0F9',
'ProfileVersionArn',
],
}],
},
CodeSigningPolicies: {
UntrustedArtifactOnDeployment: lambda.UntrustedArtifactOnDeployment.WARN,
},
});
});

test('with multiple signing profiles', () => {
const signingProfile1 = new signer.SigningProfile(stack, 'SigningProfile1', { platform: signer.Platform.AWS_LAMBDA_SHA384_ECDSA });
const signingProfile2 = new signer.SigningProfile(stack, 'SigningProfile2', { platform: signer.Platform.AMAZON_FREE_RTOS_DEFAULT });
const signingProfile3 = new signer.SigningProfile(stack, 'SigningProfile3', { platform: signer.Platform.AWS_IOT_DEVICE_MANAGEMENT_SHA256_ECDSA });
new lambda.CodeSigningConfig(stack, 'CodeSigningConfig', {
signingProfiles: [signingProfile1, signingProfile2, signingProfile3],
});

expect(stack).toHaveResource('AWS::Lambda::CodeSigningConfig', {
AllowedPublishers: {
SigningProfileVersionArns: [
{
'Fn::GetAtt': [
'SigningProfile1D4191686',
'ProfileVersionArn',
],
},
{
'Fn::GetAtt': [
'SigningProfile2E013C934',
'ProfileVersionArn',
],
},
{
'Fn::GetAtt': [
'SigningProfile3A38DE231',
'ProfileVersionArn',
],
},
],
},
});
});

test('with description and with untrustedArtifactOnDeployment of "ENFORCE"', () => {
const platform = signer.Platform.AWS_LAMBDA_SHA384_ECDSA;
const signingProfile = new signer.SigningProfile(stack, 'SigningProfile', { platform });
new lambda.CodeSigningConfig(stack, 'CodeSigningConfig', {
signingProfiles: [signingProfile],
untrustedArtifactOnDeployment: lambda.UntrustedArtifactOnDeployment.ENFORCE,
description: 'test description',
});

expect(stack).toHaveResource('AWS::Lambda::CodeSigningConfig', {
CodeSigningPolicies: {
UntrustedArtifactOnDeployment: lambda.UntrustedArtifactOnDeployment.ENFORCE,
},
Description: 'test description',
});
});

test('import does not create any resources', () => {
const codeSigningConfigId = 'aaa-xxxxxxxxxx';
const codeSigningConfigArn = `arn:aws:lambda:::code-signing-config:${codeSigningConfigId}`;
const codeSigningConfig = lambda.CodeSigningConfig.fromCodeSigningConfigArn(stack, 'Imported', codeSigningConfigArn );

expect(codeSigningConfig.codeSigningConfigArn).toBe(codeSigningConfigArn);
expect(codeSigningConfig.codeSigningConfigId).toBe(codeSigningConfigId);
expect(stack).toCountResources('AWS::Lambda::CodeSigningConfig', 0);
});

test('fail import with malformed code signing config arn', () => {
const codeSigningConfigArn = 'arn:aws:lambda:::code-signing-config';

expect(() => lambda.CodeSigningConfig.fromCodeSigningConfigArn(stack, 'Imported', codeSigningConfigArn ) ).toThrow(/ARN must be in the format/);
});
});
31 changes: 31 additions & 0 deletions packages/@aws-cdk/aws-lambda/test/function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as kms from '@aws-cdk/aws-kms';
import * as logs from '@aws-cdk/aws-logs';
import * as s3 from '@aws-cdk/aws-s3';
import * as sqs from '@aws-cdk/aws-sqs';
import * as signer from '@aws-cdk/aws-signer';
import * as cdk from '@aws-cdk/core';
import * as constructs from 'constructs';
import * as _ from 'lodash';
Expand Down Expand Up @@ -2003,6 +2004,36 @@ describe('function', () => {
});
});
});

describe('code signing config', () => {
test('default', () => {
const stack = new cdk.Stack();

const signingProfile = new signer.SigningProfile(stack, 'SigningProfile', {
platform: signer.Platform.AWS_LAMBDA_SHA384_ECDSA,
});

const codeSigningConfig = new lambda.CodeSigningConfig(stack, 'CodeSigningConfig', {
signingProfiles: [signingProfile],
});

new lambda.Function(stack, 'MyLambda', {
code: new lambda.InlineCode('foo'),
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_10_X,
codeSigningConfig,
});

expect(stack).toHaveResource('AWS::Lambda::Function', {
CodeSigningConfigArn: {
'Fn::GetAtt': [
'CodeSigningConfigD8D41C10',
'CodeSigningConfigArn',
],
},
});
});
});
});

function newTestLambda(scope: constructs.Construct) {
Expand Down
Loading

0 comments on commit 778ea27

Please sign in to comment.