mirror of
https://github.com/serverless/serverless.git
synced 2026-02-01 16:07:28 +00:00
234 lines
8.1 KiB
JavaScript
234 lines
8.1 KiB
JavaScript
'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);
|
|
},
|
|
};
|