Repository: serverless/serverless (JavaScript) — Bug Fix

Issue:
Maximum policy size of 10240 bytes exceeded - event bridge triggered lambdas
# Bug Report

## Description

1. What did you do?

I deployed a stack using 20+ lambdas triggered by event bridge events, on the same event bus.

1. What happened?

The deployment failed with error "Maximum policy size of 10240 bytes exceeded for role xxx...IamRoleCustomResourcesLa-ZKI29XIB3PPS"

1. What should've happened?

The deployment should have succeeded.

1. What's the content of your `serverless.yml` file?

Of interest for this issue: An event bridge resource and 20+ lambdas using it.

1. What's the output you get when you use the `SLS_DEBUG=*` environment variable (e.g. `SLS_DEBUG=* serverless deploy`)

ServerlessError: An error occurred: IamRoleCustomResourcesLambdaExecution - Maximum policy size of 10240 bytes exceeded for role ...-IamRoleCustomResourcesLa-ZKI29XIB3PPS (Service: AmazonIdentityManagement; Status Code: 409; Error Code: LimitExceeded; Request ID: ...).

Similar or dependent issues:

- #6212 
- #2508



Discussion:

Resolving patch:
diff --git a/lib/plugins/aws/package/compile/events/eventBridge/index.js b/lib/plugins/aws/package/compile/events/eventBridge/index.js
index 8366de831c5..ce59eb00d13 100644
--- a/lib/plugins/aws/package/compile/events/eventBridge/index.js
+++ b/lib/plugins/aws/package/compile/events/eventBridge/index.js
@@ -20,6 +20,7 @@ class AwsCompileEventBridgeEvents {
     const { provider } = service;
     const { compiledCloudFormationTemplate } = provider;
     const iamRoleStatements = [];
+    const eventBusIamRoleStatements = new Map();
 
     service.getAllFunctions().forEach(functionName => {
       let funcUsesEventBridge = false;
@@ -159,11 +160,14 @@ class AwsCompileEventBridgeEvents {
                     ],
                   ],
                 };
-                iamRoleStatements.push({
-                  Effect: 'Allow',
-                  Resource: eventBusResource,
-                  Action: ['events:CreateEventBus', 'events:DeleteEventBus'],
-                });
+
+                if (!eventBusIamRoleStatements.has(eventBusName)) {
+                  eventBusIamRoleStatements.set(eventBusName, {
+                    Effect: 'Allow',
+                    Resource: eventBusResource,
+                    Action: ['events:CreateEventBus', 'events:DeleteEventBus'],
+                  });
+                }
               }
 
               iamRoleStatements.push({
@@ -209,6 +213,8 @@ class AwsCompileEventBridgeEvents {
       }
     });
 
+    iamRoleStatements.unshift(...eventBusIamRoleStatements.values());
+
     if (iamRoleStatements.length) {
       return addCustomResourceToService(this.provider, 'eventBridge', iamRoleStatements);
     }


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/eventBridge/index.test.js b/lib/plugins/aws/package/compile/events/eventBridge/index.test.js
index 902c4cd1074..e768f60c44c 100644
--- a/lib/plugins/aws/package/compile/events/eventBridge/index.test.js
+++ b/lib/plugins/aws/package/compile/events/eventBridge/index.test.js
@@ -395,6 +395,216 @@ describe('AwsCompileEventBridgeEvents', () => {
       );
     });
 
+    it('should create the necessary resources with only one create/delete event bus policy', () => {
+      awsCompileEventBridgeEvents.serverless.service.functions = {
+        first: {
+          name: 'first',
+          events: [
+            {
+              eventBridge: {
+                eventBus: 'some-event-bus',
+                schedule: 'rate(10 minutes)',
+                pattern: {
+                  'source': ['aws.cloudformation'],
+                  'detail-type': ['AWS API Call via CloudTrail'],
+                  'detail': {
+                    eventSource: ['cloudformation.amazonaws.com'],
+                  },
+                },
+              },
+            },
+          ],
+        },
+        second: {
+          name: 'second',
+          events: [
+            {
+              eventBridge: {
+                eventBus: 'some-event-bus',
+                schedule: 'rate(10 minutes)',
+                pattern: {
+                  'source': ['aws.cloudformation'],
+                  'detail-type': ['AWS API Call via CloudTrail'],
+                  'detail': {
+                    eventSource: ['cloudformation.amazonaws.com'],
+                  },
+                },
+              },
+            },
+          ],
+        },
+      };
+
+      return expect(awsCompileEventBridgeEvents.compileEventBridgeEvents()).to.be.fulfilled.then(
+        () => {
+          const {
+            Resources,
+          } = awsCompileEventBridgeEvents.serverless.service.provider.compiledCloudFormationTemplate;
+
+          expect(addCustomResourceToServiceStub).to.have.been.calledOnce;
+          expect(addCustomResourceToServiceStub.args[0][1]).to.equal('eventBridge');
+          expect(addCustomResourceToServiceStub.args[0][2]).to.deep.equal([
+            {
+              Action: ['events:CreateEventBus', 'events:DeleteEventBus'],
+              Effect: 'Allow',
+              Resource: {
+                'Fn::Join': [
+                  ':',
+                  [
+                    'arn',
+                    {
+                      Ref: 'AWS::Partition',
+                    },
+                    'events',
+                    {
+                      Ref: 'AWS::Region',
+                    },
+                    {
+                      Ref: 'AWS::AccountId',
+                    },
+                    'event-bus/some-event-bus',
+                  ],
+                ],
+              },
+            },
+            {
+              Action: [
+                'events:PutRule',
+                'events:RemoveTargets',
+                'events:PutTargets',
+                'events:DeleteRule',
+              ],
+              Effect: 'Allow',
+              Resource: {
+                'Fn::Join': [
+                  ':',
+                  [
+                    'arn',
+                    {
+                      Ref: 'AWS::Partition',
+                    },
+                    'events',
+                    {
+                      Ref: 'AWS::Region',
+                    },
+                    {
+                      Ref: 'AWS::AccountId',
+                    },
+                    'rule/some-event-bus/first-rule-1',
+                  ],
+                ],
+              },
+            },
+            {
+              Action: ['lambda:AddPermission', 'lambda:RemovePermission'],
+              Effect: 'Allow',
+              Resource: {
+                'Fn::Join': [
+                  ':',
+                  [
+                    'arn',
+                    {
+                      Ref: 'AWS::Partition',
+                    },
+                    'lambda',
+                    {
+                      Ref: 'AWS::Region',
+                    },
+                    {
+                      Ref: 'AWS::AccountId',
+                    },
+                    'function',
+                    'first',
+                  ],
+                ],
+              },
+            },
+            {
+              Action: [
+                'events:PutRule',
+                'events:RemoveTargets',
+                'events:PutTargets',
+                'events:DeleteRule',
+              ],
+              Effect: 'Allow',
+              Resource: {
+                'Fn::Join': [
+                  ':',
+                  [
+                    'arn',
+                    {
+                      Ref: 'AWS::Partition',
+                    },
+                    'events',
+                    {
+                      Ref: 'AWS::Region',
+                    },
+                    {
+                      Ref: 'AWS::AccountId',
+                    },
+                    'rule/some-event-bus/second-rule-1',
+                  ],
+                ],
+              },
+            },
+            {
+              Action: ['lambda:AddPermission', 'lambda:RemovePermission'],
+              Effect: 'Allow',
+              Resource: {
+                'Fn::Join': [
+                  ':',
+                  [
+                    'arn',
+                    {
+                      Ref: 'AWS::Partition',
+                    },
+                    'lambda',
+                    {
+                      Ref: 'AWS::Region',
+                    },
+                    {
+                      Ref: 'AWS::AccountId',
+                    },
+                    'function',
+                    'second',
+                  ],
+                ],
+              },
+            },
+          ]);
+          expect(Resources.FirstCustomEventBridge1).to.deep.equal({
+            Type: 'Custom::EventBridge',
+            Version: 1,
+            DependsOn: [
+              'FirstLambdaFunction',
+              'CustomDashresourceDasheventDashbridgeLambdaFunction',
+            ],
+            Properties: {
+              ServiceToken: {
+                'Fn::GetAtt': ['CustomDashresourceDasheventDashbridgeLambdaFunction', 'Arn'],
+              },
+              FunctionName: 'first',
+              EventBridgeConfig: {
+                EventBus: 'some-event-bus',
+                Input: undefined,
+                InputPath: undefined,
+                InputTransformer: undefined,
+                Pattern: {
+                  'detail': {
+                    eventSource: ['cloudformation.amazonaws.com'],
+                  },
+                  'detail-type': ['AWS API Call via CloudTrail'],
+                  'source': ['aws.cloudformation'],
+                },
+                RuleName: 'first-rule-1',
+                Schedule: 'rate(10 minutes)',
+              },
+            },
+          });
+        }
+      );
+    });
+
     it('should create the necessary resources when using an own event bus arn', () => {
       awsCompileEventBridgeEvents.serverless.service.functions = {
         first: {