mirror of
https://github.com/serverless/serverless.git
synced 2026-02-01 16:07:28 +00:00
660 lines
21 KiB
JavaScript
660 lines
21 KiB
JavaScript
'use strict';
|
|
|
|
const expect = require('chai').expect;
|
|
|
|
const Serverless = require('../../../../Serverless');
|
|
const AwsProvider = require('../../provider/awsProvider');
|
|
const AwsPackage = require('../index');
|
|
|
|
describe('#mergeIamTemplates()', () => {
|
|
let awsPackage;
|
|
let serverless;
|
|
const serviceName = 'new-service';
|
|
const functionName = 'test';
|
|
const stage = 'dev';
|
|
const resolvedFunctionName = `${serviceName}-${stage}-${functionName}`;
|
|
|
|
beforeEach(() => {
|
|
serverless = new Serverless();
|
|
const options = {
|
|
stage,
|
|
region: 'us-east-1',
|
|
};
|
|
serverless.setProvider('aws', new AwsProvider(serverless, options));
|
|
awsPackage = new AwsPackage(serverless, options);
|
|
awsPackage.serverless.cli = new serverless.classes.CLI();
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate = {
|
|
Resources: {},
|
|
};
|
|
awsPackage.serverless.service.service = serviceName;
|
|
awsPackage.serverless.service.functions = {
|
|
[functionName]: {
|
|
artifact: 'test.zip',
|
|
handler: 'handler.hello',
|
|
},
|
|
};
|
|
serverless.service.setFunctionNames(); // Ensure to resolve function names
|
|
});
|
|
|
|
it('should not merge if there are no functions', () => {
|
|
awsPackage.serverless.service.functions = {};
|
|
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
const resources =
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources;
|
|
|
|
return expect(resources[awsPackage.provider.naming.getRoleLogicalId()]).to.not.exist;
|
|
});
|
|
});
|
|
|
|
it('should merge the IamRoleLambdaExecution template into the CloudFormation template', () =>
|
|
awsPackage.mergeIamTemplates().then(() => {
|
|
const canonicalFunctionsPrefix = `${
|
|
awsPackage.serverless.service.service
|
|
}-${awsPackage.provider.getStage()}`;
|
|
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
]
|
|
).to.deep.equal({
|
|
Type: 'AWS::IAM::Role',
|
|
Properties: {
|
|
AssumeRolePolicyDocument: {
|
|
Version: '2012-10-17',
|
|
Statement: [
|
|
{
|
|
Effect: 'Allow',
|
|
Principal: {
|
|
Service: ['lambda.amazonaws.com'],
|
|
},
|
|
Action: ['sts:AssumeRole'],
|
|
},
|
|
],
|
|
},
|
|
Path: '/',
|
|
Policies: [
|
|
{
|
|
PolicyName: {
|
|
'Fn::Join': [
|
|
'-',
|
|
[awsPackage.provider.getStage(), awsPackage.serverless.service.service, 'lambda'],
|
|
],
|
|
},
|
|
PolicyDocument: {
|
|
Version: '2012-10-17',
|
|
Statement: [
|
|
{
|
|
Effect: 'Allow',
|
|
Action: ['logs:CreateLogStream'],
|
|
Resource: [
|
|
{
|
|
'Fn::Sub':
|
|
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' +
|
|
`log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
Effect: 'Allow',
|
|
Action: ['logs:PutLogEvents'],
|
|
Resource: [
|
|
{
|
|
'Fn::Sub':
|
|
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' +
|
|
`log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*:*`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
RoleName: {
|
|
'Fn::Join': [
|
|
'-',
|
|
[
|
|
awsPackage.serverless.service.service,
|
|
awsPackage.provider.getStage(),
|
|
{
|
|
Ref: 'AWS::Region',
|
|
},
|
|
'lambdaRole',
|
|
],
|
|
],
|
|
},
|
|
},
|
|
});
|
|
}));
|
|
|
|
it('should ensure IAM policies for custom named functions', () => {
|
|
const customFunctionName = 'foo-bar';
|
|
awsPackage.serverless.service.functions = {
|
|
[functionName]: {
|
|
name: customFunctionName,
|
|
artifact: 'test.zip',
|
|
handler: 'handler.hello',
|
|
},
|
|
};
|
|
serverless.service.setFunctionNames(); // Ensure to resolve function names
|
|
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
const canonicalFunctionsPrefix = `${
|
|
awsPackage.serverless.service.service
|
|
}-${awsPackage.provider.getStage()}`;
|
|
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
]
|
|
).to.deep.equal({
|
|
Type: 'AWS::IAM::Role',
|
|
Properties: {
|
|
AssumeRolePolicyDocument: {
|
|
Version: '2012-10-17',
|
|
Statement: [
|
|
{
|
|
Effect: 'Allow',
|
|
Principal: {
|
|
Service: ['lambda.amazonaws.com'],
|
|
},
|
|
Action: ['sts:AssumeRole'],
|
|
},
|
|
],
|
|
},
|
|
Path: '/',
|
|
Policies: [
|
|
{
|
|
PolicyName: {
|
|
'Fn::Join': [
|
|
'-',
|
|
[awsPackage.provider.getStage(), awsPackage.serverless.service.service, 'lambda'],
|
|
],
|
|
},
|
|
PolicyDocument: {
|
|
Version: '2012-10-17',
|
|
Statement: [
|
|
{
|
|
Effect: 'Allow',
|
|
Action: ['logs:CreateLogStream'],
|
|
Resource: [
|
|
{
|
|
'Fn::Sub':
|
|
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' +
|
|
`log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*`,
|
|
},
|
|
{
|
|
'Fn::Sub':
|
|
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' +
|
|
`log-group:/aws/lambda/${customFunctionName}:*`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
Effect: 'Allow',
|
|
Action: ['logs:PutLogEvents'],
|
|
Resource: [
|
|
{
|
|
'Fn::Sub':
|
|
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' +
|
|
`log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*:*`,
|
|
},
|
|
{
|
|
'Fn::Sub':
|
|
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' +
|
|
`log-group:/aws/lambda/${customFunctionName}:*:*`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
RoleName: {
|
|
'Fn::Join': [
|
|
'-',
|
|
[
|
|
awsPackage.serverless.service.service,
|
|
awsPackage.provider.getStage(),
|
|
{
|
|
Ref: 'AWS::Region',
|
|
},
|
|
'lambdaRole',
|
|
],
|
|
],
|
|
},
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should add custom IAM policy statements', () => {
|
|
awsPackage.serverless.service.provider.iamRoleStatements = [
|
|
{
|
|
Effect: 'Allow',
|
|
Action: ['something:SomethingElse'],
|
|
Resource: 'some:aws:arn:xxx:*:*',
|
|
},
|
|
];
|
|
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
].Properties.Policies[0].PolicyDocument.Statement[2]
|
|
).to.deep.equal(awsPackage.serverless.service.provider.iamRoleStatements[0]);
|
|
});
|
|
});
|
|
|
|
it('should add managed policy arns', () => {
|
|
awsPackage.serverless.service.provider.iamManagedPolicies = [
|
|
'some:aws:arn:xxx:*:*',
|
|
'someOther:aws:arn:xxx:*:*',
|
|
{ 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] },
|
|
];
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
].Properties.ManagedPolicyArns
|
|
).to.deep.equal(awsPackage.serverless.service.provider.iamManagedPolicies);
|
|
});
|
|
});
|
|
|
|
it('should merge managed policy arns when vpc config supplied', () => {
|
|
awsPackage.serverless.service.provider.vpc = {
|
|
securityGroupIds: ['xxx'],
|
|
subnetIds: ['xxx'],
|
|
};
|
|
const iamManagedPolicies = [
|
|
'some:aws:arn:xxx:*:*',
|
|
'someOther:aws:arn:xxx:*:*',
|
|
{ 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] },
|
|
];
|
|
awsPackage.serverless.service.provider.iamManagedPolicies = iamManagedPolicies;
|
|
const expectedManagedPolicyArns = [
|
|
'some:aws:arn:xxx:*:*',
|
|
'someOther:aws:arn:xxx:*:*',
|
|
{ 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] },
|
|
{
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
'arn:',
|
|
{ Ref: 'AWS::Partition' },
|
|
':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole',
|
|
],
|
|
],
|
|
},
|
|
];
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
].Properties.ManagedPolicyArns
|
|
).to.deep.equal(expectedManagedPolicyArns);
|
|
});
|
|
});
|
|
|
|
it('should throw error if custom IAM policy statements is not an array', () => {
|
|
awsPackage.serverless.service.provider.iamRoleStatements = {
|
|
policy: 'some_value',
|
|
statments: [
|
|
{
|
|
Effect: 'Allow',
|
|
Action: ['something:SomethingElse'],
|
|
Resource: 'some:aws:arn:xxx:*:*',
|
|
},
|
|
],
|
|
};
|
|
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw('not an array');
|
|
});
|
|
|
|
it('should throw error if a custom IAM policy statement does not have an Effect field', () => {
|
|
awsPackage.serverless.service.provider.iamRoleStatements = [
|
|
{
|
|
Action: ['something:SomethingElse'],
|
|
Resource: '*',
|
|
},
|
|
];
|
|
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw(
|
|
'missing the following properties: Effect'
|
|
);
|
|
});
|
|
|
|
it('should throw error if a custom IAM policy statement does not have an Action field', () => {
|
|
awsPackage.serverless.service.provider.iamRoleStatements = [
|
|
{
|
|
Effect: 'Allow',
|
|
Resource: '*',
|
|
},
|
|
];
|
|
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw(
|
|
'missing the following properties: Action'
|
|
);
|
|
});
|
|
|
|
it('should throw error if a custom IAM policy statement does not have a Resource field', () => {
|
|
awsPackage.serverless.service.provider.iamRoleStatements = [
|
|
{
|
|
Action: ['something:SomethingElse'],
|
|
Effect: 'Allow',
|
|
},
|
|
];
|
|
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw(
|
|
'missing the following properties: Resource'
|
|
);
|
|
});
|
|
|
|
it('should throw an error describing all problematics custom IAM policy statements', () => {
|
|
awsPackage.serverless.service.provider.iamRoleStatements = [
|
|
{
|
|
Action: ['something:SomethingElse'],
|
|
Effect: 'Allow',
|
|
},
|
|
{
|
|
Action: ['something:SomethingElse'],
|
|
Resource: '*',
|
|
Effect: 'Allow',
|
|
},
|
|
{
|
|
Resource: '*',
|
|
},
|
|
];
|
|
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw(
|
|
/statement 0 is missing.*Resource; statement 2 is missing.*Effect, Action/
|
|
);
|
|
});
|
|
|
|
it('should throw error if managed policies is not an array', () => {
|
|
awsPackage.serverless.service.provider.iamManagedPolicies = 'a string';
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw(
|
|
'iamManagedPolicies should be an array of arns'
|
|
);
|
|
});
|
|
|
|
it('should add a CloudWatch LogGroup resource', () => {
|
|
const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName);
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
normalizedName
|
|
]
|
|
).to.deep.equal({
|
|
Type: 'AWS::Logs::LogGroup',
|
|
Properties: {
|
|
LogGroupName: awsPackage.provider.naming.getLogGroupName(resolvedFunctionName),
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should add RetentionInDays to a CloudWatch LogGroup resource if logRetentionInDays is given', () =>
|
|
Promise.all(
|
|
[5, '5'].map(logRetentionInDays => {
|
|
awsPackage.serverless.service.provider.logRetentionInDays = logRetentionInDays;
|
|
const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName);
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
normalizedName
|
|
]
|
|
).to.deep.equal({
|
|
Type: 'AWS::Logs::LogGroup',
|
|
Properties: {
|
|
LogGroupName: awsPackage.provider.naming.getLogGroupName(resolvedFunctionName),
|
|
RetentionInDays: 5,
|
|
},
|
|
});
|
|
});
|
|
})
|
|
));
|
|
|
|
it('should throw error if RetentionInDays is 0 or not an integer', () => {
|
|
awsPackage.serverless.service.provider.logRetentionInDays = 0;
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer');
|
|
awsPackage.serverless.service.provider.logRetentionInDays = 'string';
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer');
|
|
awsPackage.serverless.service.provider.logRetentionInDays = [];
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer');
|
|
awsPackage.serverless.service.provider.logRetentionInDays = {};
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer');
|
|
awsPackage.serverless.service.provider.logRetentionInDays = undefined;
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer');
|
|
awsPackage.serverless.service.provider.logRetentionInDays = null;
|
|
expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer');
|
|
});
|
|
|
|
it('should add a CloudWatch LogGroup resource if all functions use custom roles', () => {
|
|
awsPackage.serverless.service.functions[functionName].role = 'something';
|
|
awsPackage.serverless.service.functions = {
|
|
func0: {
|
|
handler: 'func.function.handler',
|
|
name: 'func0',
|
|
},
|
|
func1: {
|
|
handler: 'func.function.handler',
|
|
name: 'func1',
|
|
},
|
|
};
|
|
const f = awsPackage.serverless.service.functions;
|
|
const normalizedNames = [
|
|
awsPackage.provider.naming.getLogGroupLogicalId(f.func0.name),
|
|
awsPackage.provider.naming.getLogGroupLogicalId(f.func1.name),
|
|
];
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
normalizedNames[0]
|
|
]
|
|
).to.deep.equal({
|
|
Type: 'AWS::Logs::LogGroup',
|
|
Properties: {
|
|
LogGroupName: awsPackage.provider.naming.getLogGroupName(f.func0.name),
|
|
},
|
|
});
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
normalizedNames[1]
|
|
]
|
|
).to.deep.equal({
|
|
Type: 'AWS::Logs::LogGroup',
|
|
Properties: {
|
|
LogGroupName: awsPackage.provider.naming.getLogGroupName(f.func1.name),
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should add default role if one of the functions has an ARN role', () => {
|
|
awsPackage.serverless.service.functions = {
|
|
func0: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func0',
|
|
// obtain role from provider
|
|
},
|
|
func1: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func1',
|
|
role: 'some:aws:arn:xx1:*:*',
|
|
},
|
|
};
|
|
|
|
return awsPackage
|
|
.mergeIamTemplates()
|
|
.then(
|
|
() =>
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
]
|
|
).to.exist
|
|
);
|
|
});
|
|
|
|
it('should not add the default role if role is defined on a provider level', () => {
|
|
awsPackage.serverless.service.provider.role = 'some:aws:arn:xxx:*:*';
|
|
awsPackage.serverless.service.functions = {
|
|
func0: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func0',
|
|
},
|
|
func1: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func1',
|
|
},
|
|
};
|
|
|
|
return awsPackage
|
|
.mergeIamTemplates()
|
|
.then(
|
|
() =>
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
]
|
|
).to.not.exist
|
|
);
|
|
});
|
|
|
|
it('should not add the default role if all functions have an ARN role', () => {
|
|
awsPackage.serverless.service.functions = {
|
|
func0: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func0',
|
|
role: 'some:aws:arn:xx0:*:*',
|
|
},
|
|
func1: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func1',
|
|
role: 'some:aws:arn:xx1:*:*',
|
|
},
|
|
};
|
|
|
|
return awsPackage
|
|
.mergeIamTemplates()
|
|
.then(
|
|
() =>
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
]
|
|
).to.not.exist
|
|
);
|
|
});
|
|
|
|
describe('ManagedPolicyArns property', () => {
|
|
it('should not be added by default', () => {
|
|
awsPackage.serverless.service.functions = {
|
|
func0: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func0',
|
|
},
|
|
};
|
|
|
|
return awsPackage
|
|
.mergeIamTemplates()
|
|
.then(
|
|
() =>
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
].Properties.ManagedPolicyArns
|
|
).to.not.exist
|
|
);
|
|
});
|
|
|
|
it('should be added if vpc config is defined on a provider level', () => {
|
|
awsPackage.serverless.service.provider.vpc = {
|
|
securityGroupIds: ['xxx'],
|
|
subnetIds: ['xxx'],
|
|
};
|
|
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
].Properties.ManagedPolicyArns
|
|
).to.deep.equal([
|
|
{
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
'arn:',
|
|
{ Ref: 'AWS::Partition' },
|
|
':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole',
|
|
],
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
it('should be added if vpc config is defined on function level', () => {
|
|
awsPackage.serverless.service.functions = {
|
|
func0: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func0',
|
|
},
|
|
func1: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func1',
|
|
vpc: {
|
|
securityGroupIds: ['xxx'],
|
|
subnetIds: ['xxx'],
|
|
},
|
|
},
|
|
};
|
|
|
|
return awsPackage.mergeIamTemplates().then(() => {
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
].Properties.ManagedPolicyArns
|
|
).to.deep.equal([
|
|
{
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
'arn:',
|
|
{ Ref: 'AWS::Partition' },
|
|
':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole',
|
|
],
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
it('should not be added if vpc config is defined with role on function level', () => {
|
|
awsPackage.serverless.service.functions = {
|
|
func1: {
|
|
handler: 'func.function.handler',
|
|
name: 'new-service-dev-func1',
|
|
role: 'some:aws:arn:xx1:*:*',
|
|
vpc: {
|
|
securityGroupIds: ['xxx'],
|
|
subnetIds: ['xxx'],
|
|
},
|
|
},
|
|
};
|
|
|
|
return awsPackage
|
|
.mergeIamTemplates()
|
|
.then(
|
|
() =>
|
|
expect(
|
|
awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[
|
|
awsPackage.provider.naming.getRoleLogicalId()
|
|
]
|
|
).to.not.exist
|
|
);
|
|
});
|
|
});
|
|
});
|