Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# unreleased

- Sets the policy Version in all generated Roles to **2012-10-17**
- Adds a `RoleArn` setting to Lambda shortcuts. If set, the created Lambda function will use this Role and will not create a new one.

# v4.3.0

- Adds Tags option to Role shortcuts
Expand Down
4 changes: 2 additions & 2 deletions lib/shortcuts/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ Log Group, a Role, an Alarm on function errors, and the Lambda Function itself.
the name of the condition here. See [AWS documentation][78]. (optional, default `undefined`)
- `options.DependsOn` **[String][60]** Specify a stack resource dependency
to this Lambda function. See [AWS documentation][79]. (optional, default `undefined`)
- `options.Statement` **[Array][68]<[Object][59]>** an array of policy statements
defining the permissions that your Lambda function needs to execute. (optional, default `[]`)
- `options.Statement` **[Array][68]<[Object][59]>** Policy statements that will be added to a generated IAM role defining the permissions your Lambda function needs to run. _Do not use this option when specifying your own role via RoleArn._ (optional, default `[]`)
- `options.RoleArn` **[String][60]** If specified, the Lambda function will use this role instead of creating a new role. _If this option is specified, do not use the Statement option; add the permissions you need to your Role directly._ (optional, default `undefined`)
- `options.AlarmName` **[String][60]** See [AWS documentation][80]. (optional, default `'${stack name}-${logical name}-Errors-${region}'`)
- `options.AlarmDescription` **[String][60]** See [AWS documentation][81]. (optional, default `'Error alarm for ${stack name}-${logical name} lambda function in ${stack name} stack'`)
- `options.AlarmActions` **[Array][68]<[String][60]>** See [AWS documentation][82]. (optional, default `[]`)
Expand Down
90 changes: 52 additions & 38 deletions lib/shortcuts/lambda.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use strict';

const merge = require('../merge');
const ServiceRole = require('./service-role');

/**
* Baseline CloudFormation resources involved in a Lambda Function. Creates a
* Log Group, a Role, an Alarm on function errors, and the Lambda Function itself.
Expand Down Expand Up @@ -28,8 +31,8 @@
* the name of the condition here. See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html).
* @param {String} [options.DependsOn=undefined] - Specify a stack resource dependency
* to this Lambda function. See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html).
* @param {Array<Object>} [options.Statement=[]] an array of policy statements
* defining the permissions that your Lambda function needs to execute.
* @param {Array<Object>} [options.Statement=[]] Policy statements that will be added to a generated IAM role defining the permissions your Lambda function needs to run. _Do not use this option when specifying your own role via RoleArn._
* @param {String} [options.RoleArn=undefined] If specified, the Lambda function will use this role instead of creating a new role. _If this option is specified, do not use the Statement option; add the permissions you need to your Role directly._
* @param {String} [options.AlarmName='${stack name}-${logical name}-Errors-${region}'] - See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cw-alarm.html#cfn-cloudwatch-alarms-alarmname).
* @param {String} [options.AlarmDescription='Error alarm for ${stack name}-${logical name} lambda function in ${stack name} stack'] - See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cw-alarm.html#cfn-cloudwatch-alarms-alarmdescription).
* @param {Array<String>} [options.AlarmActions=[]] - See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cw-alarm.html#cfn-cloudwatch-alarms-alarmactions).
Expand Down Expand Up @@ -82,6 +85,7 @@ class Lambda {
Condition = undefined,
DependsOn = undefined,
Statement = [],
RoleArn,
AlarmName = { 'Fn::Sub': `\${AWS::StackName}-${LogicalName}-Errors-\${AWS::Region}` },
AlarmDescription = {
'Fn::Sub': [
Expand Down Expand Up @@ -113,6 +117,18 @@ class Lambda {
if (!supportedRuntimes.includes(Runtime))
throw new Error(`Runtime ${Runtime} is not one of the supported runtimes: ${supportedRuntimes}`);

if (Statement.length > 0 && RoleArn) {
throw new Error('You cannot specify both Statements and a RoleArn');
}

// if the RoleArn was specified, we need to split just the name for use with the log policy
let roleName;
if (RoleArn) {
roleName = { 'Fn::Select': [1, { 'Fn::Split': ['/', RoleArn] }] };
} else {
roleName = { Ref: `${LogicalName}Role` };
}

this.LogicalName = LogicalName;
this.FunctionName = FunctionName;
this.Condition = Condition;
Expand All @@ -129,41 +145,6 @@ class Lambda {
}
},

[`${LogicalName}Role`]: {
Type: 'AWS::IAM::Role',
Condition,
Properties: {
AssumeRolePolicyDocument: {
Statement: [
{
Effect: 'Allow',
Action: 'sts:AssumeRole',
Principal: {
Service: 'lambda.amazonaws.com'
}
}
]
},
Policies: [
{
PolicyName: 'main',
PolicyDocument: {
Statement: [
{
Effect: 'Allow',
Action: 'logs:*',
Resource: {
'Fn::GetAtt': [`${LogicalName}Logs`, 'Arn']
}
},
...Statement
]
}
}
]
}
},

[`${LogicalName}`]: {
Type: 'AWS::Lambda::Function',
Condition,
Expand All @@ -179,7 +160,6 @@ class Lambda {
Layers,
MemorySize,
ReservedConcurrentExecutions,
Role: { 'Fn::GetAtt': [`${LogicalName}Role`, 'Arn'] },
Runtime,
Timeout,
TracingConfig,
Expand Down Expand Up @@ -214,8 +194,42 @@ class Lambda {
],
MetricName: 'Errors'
}
},

[`${LogicalName}LogPolicy`]: {
Type: 'AWS::IAM::Policy',
Condition,
DependsOn: (RoleArn) ? undefined : `${LogicalName}Role`,
Properties: {
PolicyName: 'lambda-log-access',
Roles: [roleName],
PolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: 'logs:*',
Resource: {
'Fn::GetAtt': [`${LogicalName}Logs`, 'Arn']
}
}
]
}
}
}
};

if (RoleArn) {
this.Resources[`${LogicalName}`].Properties.Role = RoleArn;
} else {
const serviceRole = new ServiceRole({
LogicalName: `${LogicalName}Role`,
Service: 'lambda',
Statement
});
this.Resources[`${LogicalName}`].Properties.Role = { 'Fn::GetAtt': [`${LogicalName}Role`, 'Arn'] };
this.Resources = merge(this, serviceRole).Resources;
}
}
}

Expand Down
40 changes: 27 additions & 13 deletions lib/shortcuts/queue-lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,33 @@ class QueueLambda extends Lambda {
}
};

this.Resources[`${this.LogicalName}Role`]
.Properties.Policies[0].PolicyDocument.Statement.push({
Effect: 'Allow',
Action: [
'sqs:DeleteMessage',
'sqs:ReceiveMessage',
'sqs:GetQueueAttributes'
],
Resource: [
EventSourceArn,
{ 'Fn::Sub': ['${arn}/*', { arn: EventSourceArn }] }
]
});
const generatedRoleRef = this.Resources[`${this.LogicalName}Role`];
const sqsStatement = {
Effect: 'Allow',
Action: [
'sqs:DeleteMessage',
'sqs:ReceiveMessage',
'sqs:GetQueueAttributes'
],
Resource: [
EventSourceArn,
{ 'Fn::Sub': ['${arn}/*', { arn: EventSourceArn }] }
]
};

if (generatedRoleRef && generatedRoleRef.Properties.Policies) {
generatedRoleRef.Properties.Policies[0].PolicyDocument.Statement.push(sqsStatement);
} else if (generatedRoleRef) {
generatedRoleRef.Properties.Policies = [
{
PolicyName: 'SQSAccess',
PolicyDocument: {
Version: '2012-10-17',
Statement: [sqsStatement]
}
}
];
}
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/shortcuts/role.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class Role {
{
PolicyName: 'main',
PolicyDocument: {
Version: '2012-10-17',
Statement
}
}
Expand Down
50 changes: 32 additions & 18 deletions lib/shortcuts/stream-lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,38 @@ class StreamLambda extends Lambda {
}
};

this.Resources[`${this.LogicalName}Role`]
.Properties.Policies[0].PolicyDocument.Statement.push({
Effect: 'Allow',
Action: [
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
'dynamodb:DescribeStream',
'dynamodb:ListStreams',
'kinesis:GetRecords',
'kinesis:GetShardIterator',
'kinesis:DescribeStream',
'kinesis:ListStreams'
],
Resource: [
EventSourceArn,
{ 'Fn::Sub': ['${arn}/*', { arn: EventSourceArn }] }
]
});
const generatedRoleRef = this.Resources[`${this.LogicalName}Role`];
const streamStatement = {
Effect: 'Allow',
Action: [
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
'dynamodb:DescribeStream',
'dynamodb:ListStreams',
'kinesis:GetRecords',
'kinesis:GetShardIterator',
'kinesis:DescribeStream',
'kinesis:ListStreams'
],
Resource: [
EventSourceArn,
{ 'Fn::Sub': ['${arn}/*', { arn: EventSourceArn }] }
]
};

if (generatedRoleRef && generatedRoleRef.Properties.Policies) {
generatedRoleRef.Properties.Policies[0].PolicyDocument.Statement.push(streamStatement);
} else if (generatedRoleRef) {
generatedRoleRef.Properties.Policies = [
{
PolicyName: 'StreamAccess',
PolicyDocument: {
Version: '2012-10-17',
Statement: [streamStatement]
}
}
];
}
}
}

Expand Down
1 change: 1 addition & 0 deletions local.dic
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ KinesisFirehoseBase
LogGroup
LogSubscriptionLambda
QueueLambda
RoleArn
S3KinesisFirehose
ScheduledLambda
ServiceRole
Expand Down
Loading