New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

serverless-iam-roles-per-function

Package Overview
Dependencies
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

serverless-iam-roles-per-function - npm Package Compare versions

Comparing version 0.1.3 to 0.1.4

10

CHANGELOG.md

@@ -5,2 +5,12 @@ # Change Log

<a name="0.1.4"></a>
## [0.1.4](https://github.com/functionalone/serverless-iam-roles-per-function/compare/v0.1.3...v0.1.4) (2018-02-23)
### Bug Fixes
* support for stream based event sources (issue [#3](https://github.com/functionalone/serverless-iam-roles-per-function/issues/3)) ([3b63d49](https://github.com/functionalone/serverless-iam-roles-per-function/commit/3b63d49))
<a name="0.1.3"></a>

@@ -7,0 +17,0 @@ ## [0.1.3](https://github.com/functionalone/serverless-iam-roles-per-function/compare/v0.1.2...v0.1.3) (2018-02-20)

23

dist/lib/index.d.ts

@@ -17,10 +17,29 @@ declare class ServerlessIamPerFunctionPlugin {

getFunctionRoleName(functionName: string): any;
updateFunctionResourceRole(functionName: string, roleName: string, globalRoleName: string): void;
/**
*
* @param functionName
* @param roleName
* @param globalRoleName
* @return the function resource name
*/
updateFunctionResourceRole(functionName: string, roleName: string, globalRoleName: string): string;
/**
* Get the necessary statement permissions if there are stream event sources of dynamo or kinesis.
* @param functionObject
* @return array of statements (possibly empty)
*/
getStreamStatements(functionObject: any): any[];
/**
* Will check if function has a definition of iamRoleStatements. If so will create a new Role for the function based on these statements.
* @param functionName
* @param functionToRoleMap - populate the map with a mapping from function resource name to role resource name
*/
createRoleForFunction(functionName: string): void;
createRoleForFunction(functionName: string, functionToRoleMap: Map<string, string>): void;
/**
* Go over each EventSourceMapping and if it is for a function with a function level iam role then adjust the DependsOn
* @param functionToRoleMap
*/
setEventSourceMappings(functionToRoleMap: Map<string, string>): void;
createRolesPerFunction(): void;
}
export = ServerlessIamPerFunctionPlugin;

@@ -54,2 +54,9 @@ "use strict";

}
/**
*
* @param functionName
* @param roleName
* @param globalRoleName
* @return the function resource name
*/
updateFunctionResourceRole(functionName, roleName, globalRoleName) {

@@ -64,8 +71,64 @@ const functionResourceName = this.serverless.providers.aws.naming.getLambdaLogicalId(functionName);

functionResource.Properties.Role["Fn::GetAtt"][0] = roleName;
return functionResourceName;
}
/**
* Get the necessary statement permissions if there are stream event sources of dynamo or kinesis.
* @param functionObject
* @return array of statements (possibly empty)
*/
getStreamStatements(functionObject) {
const res = [];
if (!functionObject.events) {
return res;
}
const dynamodbStreamStatement = {
Effect: 'Allow',
Action: [
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
'dynamodb:DescribeStream',
'dynamodb:ListStreams',
],
Resource: [],
};
const kinesisStreamStatement = {
Effect: 'Allow',
Action: [
'kinesis:GetRecords',
'kinesis:GetShardIterator',
'kinesis:DescribeStream',
'kinesis:ListStreams',
],
Resource: [],
};
for (const event of functionObject.events) {
if (event.stream) {
const streamArn = event.stream.arn || event.stream;
const streamType = event.stream.type || streamArn.split(':')[2];
switch (streamType) {
case 'dynamodb':
dynamodbStreamStatement.Resource.push(streamArn);
break;
case 'kinesis':
kinesisStreamStatement.Resource.push(streamArn);
break;
default:
throw new this.serverless.classes.Error(`Unsupported stream type: ${streamType} for function: `, functionObject);
}
}
}
if (dynamodbStreamStatement.Resource.length) {
res.push(dynamodbStreamStatement);
}
if (kinesisStreamStatement.Resource.length) {
res.push(kinesisStreamStatement);
}
return res;
}
/**
* Will check if function has a definition of iamRoleStatements. If so will create a new Role for the function based on these statements.
* @param functionName
* @param functionToRoleMap - populate the map with a mapping from function resource name to role resource name
*/
createRoleForFunction(functionName) {
createRoleForFunction(functionName, functionToRoleMap) {
const functionObject = this.serverless.service.getFunction(functionName);

@@ -103,2 +166,5 @@ if (lodash_1.default.isEmpty(functionObject.iamRoleStatements)) {

}
for (const s of this.getStreamStatements(functionObject)) {
policyStatements.push(s);
}
if ((functionObject.iamRoleStatementsInherit || (this.defaultInherit && functionObject.iamRoleStatementsInherit !== false))

@@ -117,4 +183,24 @@ && !lodash_1.default.isEmpty(this.serverless.service.provider.iamRoleStatements)) {

this.serverless.service.provider.compiledCloudFormationTemplate.Resources[roleResourceName] = functionIamRole;
this.updateFunctionResourceRole(functionName, roleResourceName, globalRoleName);
const functionResourceName = this.updateFunctionResourceRole(functionName, roleResourceName, globalRoleName);
functionToRoleMap.set(functionResourceName, roleResourceName);
}
/**
* Go over each EventSourceMapping and if it is for a function with a function level iam role then adjust the DependsOn
* @param functionToRoleMap
*/
setEventSourceMappings(functionToRoleMap) {
for (const mapping of lodash_1.default.values(this.serverless.service.provider.compiledCloudFormationTemplate.Resources)) {
if (mapping.Type && mapping.Type === 'AWS::Lambda::EventSourceMapping') {
const functionNameFn = lodash_1.default.get(mapping, "Properties.FunctionName.Fn::GetAtt");
if (!lodash_1.default.isArray(functionNameFn)) {
continue;
}
const functionName = functionNameFn[0];
const roleName = functionToRoleMap.get(functionName);
if (roleName) {
mapping.DependsOn = roleName;
}
}
}
}
createRolesPerFunction() {

@@ -125,5 +211,7 @@ const allFunctions = this.serverless.service.getAllFunctions();

}
const functionToRoleMap = new Map();
for (const func of allFunctions) {
this.createRoleForFunction(func);
this.createRoleForFunction(func, functionToRoleMap);
}
this.setEventSourceMappings(functionToRoleMap);
}

@@ -130,0 +218,0 @@ }

2

package.json
{
"name": "serverless-iam-roles-per-function",
"private": false,
"version": "0.1.3",
"version": "0.1.4",
"engines": {

@@ -6,0 +6,0 @@ "node": ">=6.10.0"

@@ -47,3 +47,3 @@ # Serverless IAM Roles Per Function Plugin

By deafault, function level `iamRoleStatements` override the provider level definition. It is also possible to inherit the provider level definition by specifying the option `iamRoleStatementsInherit: true`:
By default, function level `iamRoleStatements` override the provider level definition. It is also possible to inherit the provider level definition by specifying the option `iamRoleStatementsInherit: true`:

@@ -72,3 +72,3 @@ ```yaml

If you wish to change the default behaviour to `inherit` instead of `override` it is possible to specify the following custom configuration:
If you wish to change the default behavior to `inherit` instead of `override` it is possible to specify the following custom configuration:

@@ -87,3 +87,3 @@ ```yaml

**Note**: Servless Framework provides support for defining custom IAM roles on a per function level through the use of the `role` property and creating CloudFormation resources, as documented [here](https://serverless.com/framework/docs/providers/aws/guide/iam#custom-iam-roles). This plugin doesn't support defining both the `role` property and `iamRoleStatements` at the function level.
**Note**: Serverless Framework provides support for defining custom IAM roles on a per function level through the use of the `role` property and creating CloudFormation resources, as documented [here](https://serverless.com/framework/docs/providers/aws/guide/iam#custom-iam-roles). This plugin doesn't support defining both the `role` property and `iamRoleStatements` at the function level.

@@ -93,2 +93,2 @@ [npm-image]:https://badge.fury.io/js/serverless-iam-roles-per-function.svg

[sls-image]:http://public.serverless.com/badges/v3.svg
[sls-url]:http://www.serverless.com
[sls-url]:http://www.serverless.com
import _ from 'lodash';
interface Statement {
Effect: "Allow" | "Deny";
Action: string | string[];
Resource: string | any[];
}
class ServerlessIamPerFunctionPlugin {

@@ -61,3 +67,10 @@

updateFunctionResourceRole(functionName: string, roleName: string, globalRoleName: string) {
/**
*
* @param functionName
* @param roleName
* @param globalRoleName
* @return the function resource name
*/
updateFunctionResourceRole(functionName: string, roleName: string, globalRoleName: string): string {
const functionResourceName = this.serverless.providers.aws.naming.getLambdaLogicalId(functionName);

@@ -71,9 +84,66 @@ const functionResource = this.serverless.service.provider.compiledCloudFormationTemplate.Resources[functionResourceName];

functionResource.Properties.Role["Fn::GetAtt"][0] = roleName;
}
return functionResourceName;
}
/**
* Get the necessary statement permissions if there are stream event sources of dynamo or kinesis.
* @param functionObject
* @return array of statements (possibly empty)
*/
getStreamStatements(functionObject: any) {
const res: any[] = [];
if(!functionObject.events) { //no events
return res;
}
const dynamodbStreamStatement: Statement = {
Effect: 'Allow',
Action: [
'dynamodb:GetRecords',
'dynamodb:GetShardIterator',
'dynamodb:DescribeStream',
'dynamodb:ListStreams',
],
Resource: [],
};
const kinesisStreamStatement: Statement = {
Effect: 'Allow',
Action: [
'kinesis:GetRecords',
'kinesis:GetShardIterator',
'kinesis:DescribeStream',
'kinesis:ListStreams',
],
Resource: [],
};
for (const event of functionObject.events) {
if(event.stream) {
const streamArn = event.stream.arn || event.stream;
const streamType = event.stream.type || streamArn.split(':')[2];
switch (streamType) {
case 'dynamodb':
(dynamodbStreamStatement.Resource as any[]).push(streamArn);
break;
case 'kinesis':
(kinesisStreamStatement.Resource as any[]).push(streamArn);
break;
default:
throw new this.serverless.classes.Error(`Unsupported stream type: ${streamType} for function: `, functionObject);
}
}
}
if (dynamodbStreamStatement.Resource.length) {
res.push(dynamodbStreamStatement);
}
if (kinesisStreamStatement.Resource.length) {
res.push(kinesisStreamStatement);
}
return res;
}
/**
* Will check if function has a definition of iamRoleStatements. If so will create a new Role for the function based on these statements.
* @param functionName
* @param functionToRoleMap - populate the map with a mapping from function resource name to role resource name
*/
createRoleForFunction(functionName: string) {
createRoleForFunction(functionName: string, functionToRoleMap: Map<string, string>) {
const functionObject = this.serverless.service.getFunction(functionName);

@@ -92,3 +162,3 @@ if(_.isEmpty(functionObject.iamRoleStatements)) {

//remove the statements
const policyStatements: any[] = [];
const policyStatements: Statement[] = [];
functionIamRole.Properties.Policies[0].PolicyDocument.Statement = policyStatements;

@@ -111,3 +181,6 @@ //set log statements

];
}
}
for (const s of this.getStreamStatements(functionObject)) { //set stream statements (if needed)
policyStatements.push(s);
}
if((functionObject.iamRoleStatementsInherit || (this.defaultInherit && functionObject.iamRoleStatementsInherit !== false))

@@ -126,5 +199,26 @@ && !_.isEmpty(this.serverless.service.provider.iamRoleStatements)) { //add global statements

this.serverless.service.provider.compiledCloudFormationTemplate.Resources[roleResourceName] = functionIamRole;
this.updateFunctionResourceRole(functionName, roleResourceName, globalRoleName);
const functionResourceName = this.updateFunctionResourceRole(functionName, roleResourceName, globalRoleName);
functionToRoleMap.set(functionResourceName, roleResourceName);
}
/**
* Go over each EventSourceMapping and if it is for a function with a function level iam role then adjust the DependsOn
* @param functionToRoleMap
*/
setEventSourceMappings(functionToRoleMap: Map<string, string>) {
for (const mapping of _.values(this.serverless.service.provider.compiledCloudFormationTemplate.Resources)) {
if(mapping.Type && mapping.Type === 'AWS::Lambda::EventSourceMapping') {
const functionNameFn = _.get(mapping, "Properties.FunctionName.Fn::GetAtt");
if(!_.isArray(functionNameFn)) {
continue;
}
const functionName = functionNameFn[0];
const roleName = functionToRoleMap.get(functionName);
if(roleName) {
mapping.DependsOn = roleName;
}
}
}
}
createRolesPerFunction() {

@@ -135,5 +229,7 @@ const allFunctions = this.serverless.service.getAllFunctions();

}
const functionToRoleMap: Map<string, string> = new Map();
for (const func of allFunctions) {
this.createRoleForFunction(func);
}
this.createRoleForFunction(func, functionToRoleMap);
}
this.setEventSourceMappings(functionToRoleMap);
}

@@ -140,0 +236,0 @@ }

@@ -97,2 +97,14 @@ {

"Action": [
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:DescribeStream",
"dynamodb:ListStreams"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:123456789012:table/test/stream/2017-10-09T19:39:15.151"
]
},
{
"Effect": "Allow",
"Action": [
"xray:PutTelemetryRecords",

@@ -173,2 +185,28 @@ "xray:PutTraceSegments"

},
"StreamHandlerLambdaFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "ServerlessDeploymentBucket"
},
"S3Key": "serverless/test-python/dev/1517233344526-2018-01-29T13:42:24.526Z/test-python.zip"
},
"FunctionName": "test-python-dev-stream-handler",
"Handler": "handler.stream",
"MemorySize": 128,
"Role": {
"Fn::GetAtt": [
"IamRoleLambdaExecution",
"Arn"
]
},
"Runtime": "python2.7",
"Timeout": 15
},
"DependsOn": [
"StreamHandlerLogGroup",
"IamRoleLambdaExecution"
]
},
"HelloLambdaVersionnJ9Yfm1HjU1A2HFWWfHbwg0iMvO1Ymsf3fPkNKFCIo": {

@@ -183,4 +221,20 @@ "Type": "AWS::Lambda::Version",

}
},
"StreamHandlerEventSourceMappingDynamodbTest": {
"Type": "AWS::Lambda::EventSourceMapping",
"DependsOn": "IamRoleLambdaExecution",
"Properties": {
"BatchSize": 10,
"EventSourceArn": "arn:aws:dynamodb:us-east-1:1234567890:table/test/stream/2017-10-09T19:39:15.151",
"FunctionName": {
"Fn::GetAtt": [
"StreamHandlerLambdaFunction",
"Arn"
]
},
"StartingPosition": "TRIM_HORIZON",
"Enabled": "True"
}
}
},
},
"Outputs": {

@@ -259,2 +313,20 @@ "ServerlessDeploymentBucketName": {

"vpc": {}
},
"streamHandler": {
"handler": "handler.stream",
"iamRoleStatements": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:*:table/test"
}
],
"events": [
{"stream": "arn:aws:dynamodb:us-east-1:1234567890:table/test/stream/2017-10-09T19:39:15.151"}
],
"name": "test-python-dev-stream-handler",
"package": {},
"vpc": {}
}

@@ -261,0 +333,0 @@ },

// tslint:disable:no-var-requires
import {assert} from 'chai';
const Plugin = require('../lib/index');
import Plugin from '../lib/index';
const Serverless = require('serverless/lib/Serverless');

@@ -85,5 +85,11 @@ const funcWithIamTemplate = require('../../src/test/funcs-with-iam.json');

assertFunctionRoleName('helloInherit', helloInheritRole.Properties.RoleName);
const statements: any[] = helloInheritRole.Properties.Policies[0].PolicyDocument.Statement;
let statements: any[] = helloInheritRole.Properties.Policies[0].PolicyDocument.Statement;
assert.isObject(statements.find((s) => s.Action[0] === "xray:PutTelemetryRecords"), 'global statements imported upon inherit');
assert.isObject(statements.find((s) => s.Action[0] === "dynamodb:GetItem"), 'per function statements imported upon inherit');
const streamHandlerRole = serverless.service.provider.compiledCloudFormationTemplate.Resources.StreamHandlerIamRoleLambdaExecution;
assertFunctionRoleName('streamHandler', streamHandlerRole.Properties.RoleName);
statements = streamHandlerRole.Properties.Policies[0].PolicyDocument.Statement;
assert.isObject(statements.find((s) => s.Action[0] === "dynamodb:GetRecords"), 'stream statements included');
const streamMapping = serverless.service.provider.compiledCloudFormationTemplate.Resources.StreamHandlerEventSourceMappingDynamodbTest;
assert.equal(streamMapping.DependsOn, "StreamHandlerIamRoleLambdaExecution");
});

@@ -90,0 +96,0 @@ });

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc