Merge pull request #6871 from serverless/support-cfn-role-in-custom-resources

Honor cfnRole in custom resources
This commit is contained in:
Mariusz Nowak 2019-10-23 05:12:52 -05:00 committed by GitHub
commit 8a8f1995d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 193 additions and 73 deletions

View File

@ -86,78 +86,83 @@ function addCustomResourceToService(awsProvider, resourceName, iamRoleStatements
const s3FileName = outputFilePath.split(path.sep).pop();
const S3Key = `${s3Folder}/${s3FileName}`;
let customResourceRole = Resources[customResourcesRoleLogicalId];
if (!customResourceRole) {
customResourceRole = {
Type: 'AWS::IAM::Role',
Properties: {
AssumeRolePolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: {
Service: ['lambda.amazonaws.com'],
},
Action: ['sts:AssumeRole'],
},
],
},
Policies: [
{
PolicyName: {
'Fn::Join': [
'-',
[
awsProvider.getStage(),
awsProvider.serverless.service.service,
'custom-resources-lambda',
],
],
},
PolicyDocument: {
Version: '2012-10-17',
Statement: [],
},
},
],
},
};
const cfnRoleArn = serverless.service.provider.cfnRole;
if (shouldWriteLogs) {
const logGroupsPrefix = awsProvider.naming.getLogGroupName(funcPrefix);
customResourceRole.Properties.Policies[0].PolicyDocument.Statement.push(
{
Effect: 'Allow',
Action: ['logs:CreateLogStream'],
Resource: [
if (!cfnRoleArn) {
let customResourceRole = Resources[customResourcesRoleLogicalId];
if (!customResourceRole) {
customResourceRole = {
Type: 'AWS::IAM::Role',
Properties: {
AssumeRolePolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: {
Service: ['lambda.amazonaws.com'],
},
Action: ['sts:AssumeRole'],
},
],
},
Policies: [
{
'Fn::Sub':
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' +
`:log-group:${logGroupsPrefix}*:*`,
PolicyName: {
'Fn::Join': [
'-',
[
awsProvider.getStage(),
awsProvider.serverless.service.service,
'custom-resources-lambda',
],
],
},
PolicyDocument: {
Version: '2012-10-17',
Statement: [],
},
},
],
},
{
Effect: 'Allow',
Action: ['logs:PutLogEvents'],
Resource: [
{
'Fn::Sub':
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' +
`:log-group:${logGroupsPrefix}*:*:*`,
},
],
}
);
};
Resources[customResourcesRoleLogicalId] = customResourceRole;
if (shouldWriteLogs) {
const logGroupsPrefix = awsProvider.naming.getLogGroupName(funcPrefix);
customResourceRole.Properties.Policies[0].PolicyDocument.Statement.push(
{
Effect: 'Allow',
Action: ['logs:CreateLogStream'],
Resource: [
{
'Fn::Sub':
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' +
`:log-group:${logGroupsPrefix}*:*`,
},
],
},
{
Effect: 'Allow',
Action: ['logs:PutLogEvents'],
Resource: [
{
'Fn::Sub':
'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' +
`:log-group:${logGroupsPrefix}*:*:*`,
},
],
}
);
}
}
const { Statement } = customResourceRole.Properties.Policies[0].PolicyDocument;
iamRoleStatements.forEach(newStmt => {
if (!Statement.find(existingStmt => existingStmt.Resource === newStmt.Resource)) {
Statement.push(newStmt);
}
});
}
const { Statement } = customResourceRole.Properties.Policies[0].PolicyDocument;
iamRoleStatements.forEach(newStmt => {
if (!Statement.find(existingStmt => existingStmt.Resource === newStmt.Resource)) {
Statement.push(newStmt);
}
});
const customResourceFunction = {
Type: 'AWS::Lambda::Function',
@ -169,19 +174,21 @@ function addCustomResourceToService(awsProvider, resourceName, iamRoleStatements
FunctionName: absoluteFunctionName,
Handler,
MemorySize: 1024,
Role: {
'Fn::GetAtt': [customResourcesRoleLogicalId, 'Arn'],
},
Runtime: 'nodejs10.x',
Timeout: 180,
},
DependsOn: [customResourcesRoleLogicalId],
DependsOn: [],
};
Resources[customResourceFunctionLogicalId] = customResourceFunction;
Object.assign(Resources, {
[customResourceFunctionLogicalId]: customResourceFunction,
[customResourcesRoleLogicalId]: customResourceRole,
});
if (cfnRoleArn) {
customResourceFunction.Properties.Role = cfnRoleArn;
} else {
customResourceFunction.Properties.Role = {
'Fn::GetAtt': [customResourcesRoleLogicalId, 'Arn'],
};
customResourceFunction.DependsOn.push(customResourcesRoleLogicalId);
}
if (shouldWriteLogs) {
const customResourceLogGroupLogicalId = awsProvider.naming.getLogGroupLogicalId(

View File

@ -221,6 +221,119 @@ describe('#addCustomResourceToService()', () => {
});
});
it('Should not setup new IAM role, when cfnRole is provided', () => {
const cfnRoleArn = (serverless.service.provider.cfnRole =
'arn:aws:iam::999999999999:role/some-role');
return expect(
BbPromise.all([
// add the custom S3 resource
addCustomResourceToService(provider, 's3', [
...iamRoleStatements,
{
Effect: 'Allow',
Resource: 'arn:aws:s3:::some-bucket',
Action: ['s3:PutBucketNotification', 's3:GetBucketNotification'],
},
]),
// add the custom Cognito User Pool resource
addCustomResourceToService(provider, 'cognitoUserPool', [
...iamRoleStatements,
{
Effect: 'Allow',
Resource: '*',
Action: [
'cognito-idp:ListUserPools',
'cognito-idp:DescribeUserPool',
'cognito-idp:UpdateUserPool',
],
},
]),
// add the custom Event Bridge resource
addCustomResourceToService(provider, 'eventBridge', [
...iamRoleStatements,
{
Effect: 'Allow',
Resource: 'arn:aws:events:*:*:rule/some-rule',
Action: [
'events:PutRule',
'events:RemoveTargets',
'events:PutTargets',
'events:DeleteRule',
],
},
{
Action: ['events:CreateEventBus', 'events:DeleteEventBus'],
Effect: 'Allow',
Resource: 'arn:aws:events:*:*:event-bus/some-event-bus',
},
]),
])
).to.be.fulfilled.then(() => {
const { Resources } = serverless.service.provider.compiledCloudFormationTemplate;
const customResourcesZipFilePath = path.join(
tmpDirPath,
'.serverless',
'custom-resources.zip'
);
expect(execAsyncStub).to.have.callCount(3);
expect(fs.existsSync(customResourcesZipFilePath)).to.equal(true);
// S3 Lambda Function
expect(Resources.CustomDashresourceDashexistingDashs3LambdaFunction).to.deep.equal({
Type: 'AWS::Lambda::Function',
Properties: {
Code: {
S3Bucket: { Ref: 'ServerlessDeploymentBucket' },
S3Key: 'artifact-dir-name/custom-resources.zip',
},
FunctionName: `${serviceName}-dev-custom-resource-existing-s3`,
Handler: 's3/handler.handler',
MemorySize: 1024,
Role: cfnRoleArn,
Runtime: 'nodejs10.x',
Timeout: 180,
},
DependsOn: [],
});
// Cognito User Pool Lambda Function
expect(Resources.CustomDashresourceDashexistingDashcupLambdaFunction).to.deep.equal({
Type: 'AWS::Lambda::Function',
Properties: {
Code: {
S3Bucket: { Ref: 'ServerlessDeploymentBucket' },
S3Key: 'artifact-dir-name/custom-resources.zip',
},
FunctionName: `${serviceName}-dev-custom-resource-existing-cup`,
Handler: 'cognitoUserPool/handler.handler',
MemorySize: 1024,
Role: cfnRoleArn,
Runtime: 'nodejs10.x',
Timeout: 180,
},
DependsOn: [],
});
// Event Bridge Lambda Function
expect(Resources.CustomDashresourceDasheventDashbridgeLambdaFunction).to.deep.equal({
Type: 'AWS::Lambda::Function',
Properties: {
Code: {
S3Bucket: { Ref: 'ServerlessDeploymentBucket' },
S3Key: 'artifact-dir-name/custom-resources.zip',
},
FunctionName: `${serviceName}-dev-custom-resource-event-bridge`,
Handler: 'eventBridge/handler.handler',
MemorySize: 1024,
Role: cfnRoleArn,
Runtime: 'nodejs10.x',
Timeout: 180,
},
DependsOn: [],
});
// Iam Role
expect(Resources.IamRoleCustomResourcesLambdaExecution).to.be.undefined;
});
});
it('should setup CloudWatch Logs when logs.frameworkLambda is true', () => {
serverless.service.provider.logs = { frameworkLambda: true };
return BbPromise.all([