UNPKG

serverless

Version:

Serverless Framework - Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more

234 lines (201 loc) • 8.32 kB
'use strict'; const _ = require('lodash'); const path = require('path'); module.exports = { mergeIamTemplates() { // resolve early if no functions are provided if (!this.serverless.service.getAllFunctions().length) { return; } // create log group resources this.serverless.service .getAllFunctions() .filter((functionName) => !this.serverless.service.getFunction(functionName).disableLogs) .forEach((functionName) => { const functionObject = this.serverless.service.getFunction(functionName); const logGroupLogicalId = this.provider.naming.getLogGroupLogicalId(functionName); const newLogGroup = { [logGroupLogicalId]: { Type: 'AWS::Logs::LogGroup', Properties: { LogGroupName: this.provider.naming.getLogGroupName(functionObject.name), }, }, }; const logRetentionInDays = this.provider.getLogRetentionInDays(); if (logRetentionInDays) { newLogGroup[logGroupLogicalId].Properties.RetentionInDays = logRetentionInDays; } _.merge( this.serverless.service.provider.compiledCloudFormationTemplate.Resources, newLogGroup ); }); const iamRole = _.get(this.serverless.service.provider.iam, 'role', {}); // resolve early if provider level role is provided as a reference to existing role if (this.provider.isExistingRoleProvided(iamRole)) { return; } if ('role' in this.serverless.service.provider) { return; } // resolve early if all functions contain a custom role const customRoleProvided = this.serverless.service.getAllFunctions().every((functionName) => { const functionObject = this.serverless.service.getFunction(functionName); return 'role' in functionObject; }); if (customRoleProvided) { return; } // merge in the iamRoleLambdaTemplate const iamRoleLambdaExecutionTemplate = this.serverless.utils.readFileSync( path.join( this.serverless.config.serverlessPath, 'plugins', 'aws', 'package', 'lib', 'iam-role-lambda-execution-template.json' ) ); iamRoleLambdaExecutionTemplate.Properties.Path = this.provider.naming.getRolePath(); iamRoleLambdaExecutionTemplate.Properties.RoleName = this.provider.naming.getRoleName(); // set role tags if (iamRole.tags) { iamRoleLambdaExecutionTemplate.Properties.Tags = Object.keys(iamRole.tags).map((key) => ({ Key: key, Value: iamRole.tags[key], })); } // set permission boundary if (iamRole.permissionsBoundary) { iamRoleLambdaExecutionTemplate.Properties.PermissionsBoundary = iamRole.permissionsBoundary; } else if (iamRole.permissionBoundary) { iamRoleLambdaExecutionTemplate.Properties.PermissionsBoundary = iamRole.permissionBoundary; } else if (this.serverless.service.provider.rolePermissionsBoundary) { iamRoleLambdaExecutionTemplate.Properties.PermissionsBoundary = this.serverless.service.provider.rolePermissionsBoundary; } iamRoleLambdaExecutionTemplate.Properties.Policies[0].PolicyName = this.provider.naming.getPolicyName(); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [this.provider.naming.getRoleLogicalId()]: iamRoleLambdaExecutionTemplate, }); const canonicalFunctionNamePrefix = `${ this.provider.serverless.service.service }-${this.provider.getStage()}`; const logGroupsPrefix = this.provider.naming.getLogGroupName(canonicalFunctionNamePrefix); const policyDocumentStatements = this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ this.provider.naming.getRoleLogicalId() ].Properties.Policies[0].PolicyDocument.Statement; let hasOneOrMoreCanonicallyNamedFunctions = false; const disableLogsCanonicallyNamedFunctions = []; // Ensure policies for functions with custom name resolution this.serverless.service.getAllFunctions().forEach((functionName) => { const { name: resolvedFunctionName, disableLogs: areLogsDisabled } = this.serverless.service.getFunction(functionName); if (!resolvedFunctionName || resolvedFunctionName.startsWith(canonicalFunctionNamePrefix)) { if (areLogsDisabled) { disableLogsCanonicallyNamedFunctions.push(resolvedFunctionName); } else { hasOneOrMoreCanonicallyNamedFunctions = true; } return; } if (!areLogsDisabled) { const customFunctionNamelogGroupsPrefix = this.provider.naming.getLogGroupName(resolvedFunctionName); policyDocumentStatements[0].Resource.push({ 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + `:log-group:${customFunctionNamelogGroupsPrefix}:*`, }); policyDocumentStatements[1].Resource.push({ 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + `:log-group:${customFunctionNamelogGroupsPrefix}:*:*`, }); } }); if (hasOneOrMoreCanonicallyNamedFunctions) { // Ensure general policies for functions with default name resolution policyDocumentStatements[0].Resource.push({ 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + `:log-group:${logGroupsPrefix}*:*`, }); policyDocumentStatements[1].Resource.push({ 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + `:log-group:${logGroupsPrefix}*:*:*`, }); if (disableLogsCanonicallyNamedFunctions.length > 0) { // add deny rule for disabled logs function const arnOfDisableLogGroups = disableLogsCanonicallyNamedFunctions.map((functionName) => { return { 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + `:log-group:${this.provider.naming.getLogGroupName(functionName)}:*`, }; }); this.mergeStatements([ { Effect: 'Deny', Action: 'logs:PutLogEvents', Resource: arnOfDisableLogGroups, }, ]); } } // add custom iam role statements if (iamRole.statements) { this.mergeStatements(iamRole.statements); } else if (this.serverless.service.provider.iamRoleStatements) { this.mergeStatements(this.serverless.service.provider.iamRoleStatements); } // add iam managed policies if (iamRole.managedPolicies) { this.mergeManagedPolicies(iamRole.managedPolicies); } else if (this.serverless.service.provider.iamManagedPolicies) { this.mergeManagedPolicies(this.serverless.service.provider.iamManagedPolicies); } // check if one of the functions contains vpc configuration const vpcConfigProvided = this.serverless.service.getAllFunctions().some((functionName) => { const functionObject = this.serverless.service.getFunction(functionName); return 'vpc' in functionObject; }); if (vpcConfigProvided || this.serverless.service.provider.vpc) { // add managed iam policy to allow ENI management this.mergeManagedPolicies([ { 'Fn::Join': [ '', [ 'arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', ], ], }, ]); } return; }, mergeManagedPolicies(managedPolicies) { if (!managedPolicies.length) return; const resource = this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ this.provider.naming.getRoleLogicalId() ].Properties; if (!Array.isArray(resource.ManagedPolicyArns)) { resource.ManagedPolicyArns = []; } resource.ManagedPolicyArns.push(...managedPolicies); }, mergeStatements(statements) { this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ this.provider.naming.getRoleLogicalId() ].Properties.Policies[0].PolicyDocument.Statement.push(...statements); }, };