-
Notifications
You must be signed in to change notification settings - Fork 57
/
Copy pathindex.ts
127 lines (118 loc) · 5.38 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import * as _ from 'lodash';
class ServerlessIamPerFunctionPlugin {
provider: string;
hooks: {[i: string]: () => void};
serverless: any;
awsPackagePlugin: any;
defaultInherit: boolean;
/**
*
* @param serverless - serverless host object
* @param options
*/
constructor(serverless: any) {
this.provider = 'aws';
this.serverless = serverless;
this.hooks = {
'after:package:compileFunctions': this.createRolesPerFunction.bind(this),
};
this.defaultInherit = _.get(this.serverless.service, "custom.serverless-iam-roles-per-function.defaultInherit", false);
}
validateStatements(statements: any): void {
const awsPackagePluginName = "AwsPackage";
if(!this.awsPackagePlugin) {
for (const plugin of this.serverless.pluginManager.plugins) {
if(plugin.constructor && plugin.constructor.name === awsPackagePluginName) {
this.awsPackagePlugin = plugin;
break;
}
}
}
if(!this.awsPackagePlugin) {
this.serverless.cli.log(`WARNING: could not find ${awsPackagePluginName} plugin to verify statements.`);
return;
}
this.awsPackagePlugin.validateStatements(statements);
}
getFunctionRoleName(functionName: string) {
const roleName = this.serverless.providers.aws.naming.getRoleName();
const fnJoin = roleName['Fn::Join'];
if(!_.isArray(fnJoin) || fnJoin.length !== 2 || !_.isArray(fnJoin[1]) || fnJoin[1].length < 2) {
throw new this.serverless.classes.Error("Global Role Name is not in exepcted format. Got name: " + JSON.stringify(roleName));
}
fnJoin[1].splice(2, 0, functionName);
return roleName;
}
updateFunctionResourceRole(functionName: string, roleName: string, globalRoleName: string) {
const functionResourceName = this.serverless.providers.aws.naming.getLambdaLogicalId(functionName);
const functionResource = this.serverless.service.provider.compiledCloudFormationTemplate.Resources[functionResourceName];
if(_.isEmpty(functionResource) || _.isEmpty(functionResource.Properties) || _.isEmpty(functionResource.Properties.Role) ||
!_.isArray(functionResource.Properties.Role["Fn::GetAtt"]) || !_.isArray(functionResource.DependsOn)) {
throw new this.serverless.classes.Error("Function Resource is not in exepcted format. For function name: " + functionName);
}
functionResource.DependsOn = [roleName].concat(functionResource.DependsOn.filter(((val: any) => val !== globalRoleName )));
functionResource.Properties.Role["Fn::GetAtt"][0] = roleName;
}
/**
* 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
*/
createRoleForFunction(functionName: string) {
const functionObject = this.serverless.service.getFunction(functionName);
if(_.isEmpty(functionObject.iamRoleStatements)) {
return;
}
if(functionObject.role) {
throw new this.serverless.classes.Error("Defing function with both 'role' and 'iamRoleStatements' is not supported. Function name: " + functionName);
}
this.validateStatements(functionObject.iamRoleStatements);
//we use the configured role as a template
const globalRoleName = this.serverless.providers.aws.naming.getRoleLogicalId();
const globalIamRole = this.serverless.service.provider.compiledCloudFormationTemplate.Resources[globalRoleName];
const functionIamRole = _.cloneDeep(globalIamRole);
//remove the statements
const policyStatements: any[] = [];
functionIamRole.Properties.Policies[0].PolicyDocument.Statement = policyStatements;
//set log statements
policyStatements[0] = {
Effect: "Allow",
Action: ["logs:CreateLogStream", "logs:PutLogEvents"],
Resource: [
{
'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}' +
`:log-group:${this.serverless.providers.aws.naming.getLogGroupName(functionObject.name)}:*:*`,
},
],
};
//set vpc if needed
if (!_.isEmpty(functionObject.vpc) || !_.isEmpty(this.serverless.service.provider.vpc)) {
functionIamRole.Properties.ManagedPolicyArns = [
'arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole',
];
}
if((functionObject.iamRoleStatementsInherit || (this.defaultInherit && functionObject.iamRoleStatementsInherit !== false))
&& !_.isEmpty(this.serverless.service.provider.iamRoleStatements)) { //add global statements
for (const s of this.serverless.service.provider.iamRoleStatements) {
policyStatements.push(s);
}
}
//add iamRoleStatements
for (const s of functionObject.iamRoleStatements) {
policyStatements.push(s);
}
functionIamRole.Properties.RoleName = this.getFunctionRoleName(functionName);
const roleResourceName = this.serverless.providers.aws.naming.getNormalizedFunctionName(functionName) + globalRoleName;
this.serverless.service.provider.compiledCloudFormationTemplate.Resources[roleResourceName] = functionIamRole;
this.updateFunctionResourceRole(functionName, roleResourceName, globalRoleName);
}
createRolesPerFunction() {
const allFunctions = this.serverless.service.getAllFunctions();
if(_.isEmpty(allFunctions)) {
return;
}
for (const func of allFunctions) {
this.createRoleForFunction(func);
}
}
}
export = ServerlessIamPerFunctionPlugin;