Skip to content

fix(events): now EventBus.grantPutEventsTo correctly handles service principals (under feature flag) #33729

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Mar 21, 2025

Conversation

scorbiere
Copy link
Contributor

Issue #22080

Closes #22080.

Reason for this change

When trying to grant PutEvents permissions to an AWS Service Principal using grantPutEventsTo, the method performed a no-op without any warnings or errors. This prevented users from properly granting permissions to service principals, even though this is a valid use case that can be done through the AWS Console. The change implements the correct behavior by creating an EventBusPolicy when dealing with service principals.

Description of changes

  • Added special handling for service principals in EventBus.grantPutEventsTo method
  • When granting permissions to a service principal, creates an EventBusPolicy instead of attempting to modify IAM policies
  • Returns iam.Grant.drop() for service principals to indicate permissions are handled via EventBusPolicy
  • Added test cases to verify both service principal and IAM principal scenarios

Describe any new or updated permissions being added

The change introduces the creation of EventBusPolicy resources with events:PutEvents permission when granting access to service principals. This is not a new permission, but rather a different way of granting the same permission through resource-based policies instead of identity-based policies.

Description of how you validated changes

Added new test cases that verify:

  • EventBusPolicy is correctly created when granting permissions to service principals
  • IAM policies are correctly created when granting permissions to IAM roles/users

Checklist


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@aws-cdk-automation aws-cdk-automation requested a review from a team March 10, 2025 23:55
@github-actions github-actions bot added bug This issue is a bug. effort/small Small work item – less than a day of effort p1 labels Mar 10, 2025
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label Mar 10, 2025
Copy link
Collaborator

@aws-cdk-automation aws-cdk-automation left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This review is outdated)

@scorbiere scorbiere changed the title fix(events): EventBus.grantPutEventsTo now correctly handles service principals fix(events): now EventBus.grantPutEventsTo correctly handles service principals Mar 10, 2025
Copy link

codecov bot commented Mar 11, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 82.38%. Comparing base (e307404) to head (96717bb).

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #33729   +/-   ##
=======================================
  Coverage   82.38%   82.38%           
=======================================
  Files         120      120           
  Lines        6937     6937           
  Branches     1170     1170           
=======================================
  Hits         5715     5715           
  Misses       1119     1119           
  Partials      103      103           
Flag Coverage Δ
suite.unit 82.38% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
packages/aws-cdk ∅ <ø> (∅)
packages/aws-cdk-lib/core 82.38% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@moelasmar moelasmar self-assigned this Mar 11, 2025
@@ -177,6 +177,21 @@ abstract class EventBusBase extends Resource implements IEventBus {
}

public grantPutEventsTo(grantee: iam.IGrantable): iam.Grant {
// Special handling for service principals
if (grantee.grantPrincipal instanceof iam.ServicePrincipal) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a generic solution. We still have issues with other Principle types that does not implement addToPrincipalPolicy (or it can not linked to a Policy resource), like AccountPrincipal, AnyPrincipal.

EventBus resource supports the ResourcePolicy (AWS::Events::EventBusPolicy) .. so we should make the EventBus L2 to implement the interface IResourceWithPolicy, and to implements addToResourcePolicy function which looks it is already there.

Then you can change the implementation of this function from iam.Grant.addToPrincipal to be iam.Grant.addToPrincipalOrResource

Copy link
Contributor

@moelasmar moelasmar Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one more thing, I am not sure if it is Ok to create multiple EventBusPolicy resources for the same EventBus or not .. we need to have an integ test case for that.

Copy link
Contributor

@moelasmar moelasmar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks Seb for raising this PR, I left some comment

…ntPutEventsTo

* Use Grant.addToPrincipalOrResource for consistent cross-account and service principal handling
* Add default SID generation for EventBus resource policies
* Add comprehensive test coverage including:
  - Same account IAM principals
  - Cross-account service principals
  - Cross-account IAM principals
  - Cross-account organization principals
  - Token account scenarios
  - Imported event bus cases
@scorbiere scorbiere added the pr/do-not-merge This PR should not be merged at this time. label Mar 14, 2025
@scorbiere scorbiere changed the title fix(events): now EventBus.grantPutEventsTo correctly handles service principals fix(aws-events): now EventBus.grantPutEventsTo correctly handles service principals (under feature flag) Mar 14, 2025
…us resource policies

When granting permissions to service principals using grantPutEventsTo(),
the operation currently fails silently because service principals require
resource policies with Statement IDs.

This change introduces a new feature flag '@aws-cdk/aws-events:requireEventBusPolicySid'
that controls this behavior:

* When enabled (recommended):
  - Resource policies will be created with Statement IDs for service principals
  - The operation will succeed as expected

* When disabled (legacy):
  - A warning will be emitted
  - The grant operation will be dropped
  - No permissions will be added

The feature flag is set to 'true' in recommended-feature-flags.json to ensure
proper behavior by default in future versions.
@scorbiere scorbiere removed the pr/do-not-merge This PR should not be merged at this time. label Mar 14, 2025
Comment on lines 135 to 144
Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBusPolicy', {
Statement: Match.objectLike({
Effect: 'Allow',
Action: 'events:PutEvents',
Principal: { Service: 'states.amazonaws.com' },
Resource: Match.anyValue(),
Condition: Match.absent(),
}),
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to understand, what we are checking here different than the check in line 118

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this is confusing and this 2nd assertion doesn't add much value. I will re-write the assertions with helper functions in order to improve the readability.

import { STANDARD_NODEJS_RUNTIME } from '../../config';

// Create a single app for both stacks
const app = new App();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this integ test case, there is no difference between with feature flag, and without. In both cases the permissions will be added to the Role .. may be this test case is designed to verify that the current customer code will stay working. so, can we have a new integ test case to verify the new behaviour of grant method, and also, to verify that we can call grant method more than one in the new approach.

Comment on lines 644 to 656
test('Event Bus policy statements must have a sid', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'Stack');
const bus = new EventBus(stack, 'Bus');

// THEN
expect(() => bus.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [new iam.ArnPrincipal('arn')],
actions: ['events:PutEvents'],
}))).toThrow('Event Bus policy statements must have a sid');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test case is still valid, right ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good catch.

});
});

test('grantPutEventsTo creates both IAM and resource policies for cross-account service principal', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the test title is wrong.

expect(grant.success).toBeTruthy();
});

test('grantPutEventsTo creates both IAM and resource policies for cross-account role principal', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one as well, has a wrong title

expect(grant.success).toBeTruthy();
});

test('grantPutEventsTo creates both IAM and resource policies for cross-account organization principal', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here as well

Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0);

// Verify all required parts of the organization policy
Template.fromStack(stack).hasResourceProperties('AWS::Events::EventBusPolicy', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the difference between this check and check in line 217

Copy link
Contributor

@moelasmar moelasmar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Seb, I still have some concern about the new integration test case

Comment on lines +11 to +23
/**
* Verifies that no resource policy (EventBusPolicy resource) was created.
*/
function assertNoResourcePolicy(stack: Stack) {
Template.fromStack(stack).resourceCountIs('AWS::Events::EventBusPolicy', 0);
}

/**
* Verifies that no identity-based policy was created.
*/
function assertNoIdentityPolicy(stack: Stack) {
Template.fromStack(stack).resourceCountIs('AWS::IAM::Policy', 0);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit comment, and you can ignore it .. I believe the assert statement itself is clear, and does not need to be wrapped in a function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, but I prefer the readability of the method name in the test.

expect(grant.success).toBeTruthy();
});
});

describe('cross-account scenarios', () => {
test('grantPutEventsTo creates EventBusPolicy for service principal without conditions', () => {
test('creates resource policy for service principal without conditions', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this test case is not a cross account test case, and there is no big difference vs test case creates only resource policy for service principal with source account condition from this change POV.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I will remove it.

});

test('grantPutEventsTo creates both IAM and resource policies for cross-account service principal', () => {
test('creates resource policy for service principal with cross-account condition', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I also believe this test case is redundant, I think from this change POV, the Service principal with account condition is not different from a normal service principal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I will remove it as well.

':',
{ Ref: 'AWS::AccountId' },
':event-bus/',
{ Ref: 'EventBus7B8748AA' },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to the previous comment, I expect to see some normal string value, instead of ref intrinsic function.

// GIVEN
// Create a role with a token principal
const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.AccountRootPrincipal(), // This will use the stack's account token
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to understand, why do we care about this token .. how may this value change the process of adding the permissions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case was making sense when I was trying to use the Grant.addToPrincipalOrResource(...) method in EventBusBase.grantPutEventsTo(...). Which not the strategy used anymore. I will remove this test.

});
});

describe('cross-stack scenarios', () => {
test('grantPutEventsTo works across stacks', () => {
test('creates identity-based policy in different stack', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test case to grant an imported role that is not managed by the CDK application. I expect in this scenario, we should see an Event Resource Policy instead of identity policy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add that test case, but the template generated is:

ImportedRoleIdPolicy878A765C :: {
      "Properties": {
        "PolicyDocument": {
          "Statement": [
            {
              "Action": "events:PutEvents",
              "Effect": "Allow",
              "Resource": {
                "Fn::GetAtt": [ "EventBus7B8748AA", "Arn" ],
              }
            }
          ],
          "Version": "2012-10-17"
        },
        "PolicyName": "ImportedRoleIdPolicy878A765C",
        "Roles": [ "importedRoleName" ]
      },
      "Type": "AWS::IAM::Policy"
    }

Even when I use iam.Role.fromRoleArn(...). The code in Grant.addToPrincipal(...) call directly ImportedRole.addToPrincipalPolicy(...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when you call fromRolexxx function, set mutable flag to false .. https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam.FromRoleArnOptions.html#mutable

This means that CDK could not attach policies to this Role.

@@ -0,0 +1,142 @@
import * as cdk from 'aws-cdk-lib';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test case is also miss leading, if you checked the generated template, you will find the following:

  • TargetEventBuscdkEventsServicePrincipalGrant11A8AC78 Event resource policy that grant events.amazonaws.com service to put event on eventBus TargetEventBus43DE6DE2 (this is the point we need to verify)
  • But also, you will find ForwardingRuleEventsRoleDefaultPolicy5A4528E2 identity policy that grant role ForwardingRuleEventsRole9F12172A to put events on EventBus TargetEventBus43DE6DE2, and this role is attached to EventRule ForwardingRule6039FF0D which is linked to EventBus SourceEventBusA326BD45

So, I am not sure which policy is applied. .. can you try to change this logic, to add a lambda function that try to put some event to an event bus, and do not grant the function itself, but grant the Lambda Service, and see if this will work or not

Copy link
Contributor Author

@scorbiere scorbiere Mar 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, it seems that the EventBus class implementation (link) of the method bind automatically tries to add the put event policy on its associated role (type iam.IRole), which is not compatible with the service principal we are using in this integ test.

Since I can't think of a realistic use case where we would only use a service principal role, I will remove this integ test.

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildv2Project1C6BFA3F-wQm2hXv2jqQv
  • Commit ID: 96717bb
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@scorbiere scorbiere changed the title fix(aws-events): now EventBus.grantPutEventsTo correctly handles service principals (under feature flag) fix(events): now EventBus.grantPutEventsTo correctly handles service principals (under feature flag) Mar 21, 2025
@aws-cdk-automation aws-cdk-automation dismissed their stale review March 21, 2025 05:03

✅ Updated pull request passes all PRLinter validations. Dismissing previous PRLinter review.

Copy link
Contributor

mergify bot commented Mar 21, 2025

Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify mergify bot merged commit 38d82c4 into aws:main Mar 21, 2025
27 checks passed
Copy link
Contributor

Comments on closed issues and PRs are hard for our team to see.
If you need help, please open a new issue that references this one.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug This issue is a bug. contribution/core This is a PR that came from AWS. effort/small Small work item – less than a day of effort p1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

aws-events: Cannot grant putEvents to Service Principals
3 participants