Skip to content

Conversation

@dineshSajwan
Copy link
Contributor

@dineshSajwan dineshSajwan commented Oct 17, 2025

Issue # (if applicable)

Related to aws/aws-cdk-rfcs#785

Reason for this change

Adding bedrock agent core gateway and gateway target

Description of changes

  • Added a new L2 construct for agentcore gateway
  • Added a new L2 construct for agentcore gateway target
  • Added validation helper methods, convenience method like addLambdaTarget, addOpenApiTarget, and other static methods.
  • Added integ and unit test cases

Describe any new or updated permissions being added

The gateway creates a role with permission to bedrock agentcore , s3

Description of how you validated changes

Unit tests, integration tests, manual tests

Checklist


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

dinsajwa and others added 30 commits September 29, 2025 11:57
@aws-cdk-automation aws-cdk-automation added the pr/needs-further-review PR requires additional review from our team specialists due to the scope or complexity of changes. label Oct 27, 2025
Copy link
Contributor

@mazyu36 mazyu36 left a comment

Choose a reason for hiding this comment

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

I received a review request in another place, so I've reviewed part of it.
I left one nit comment. I'll review the rest when I have time.

/**
* The Cognito User Pool to use for authentication
*/
readonly userPool: UserPool;
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 should be IUserPool (Interface).

@kumvprat kumvprat self-assigned this Nov 10, 2025
Copy link
Contributor

@kumvprat kumvprat left a comment

Choose a reason for hiding this comment

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

Added inline comments, overall adding more test cases in integration test can increase visibility on how the gateway and target combinations can be used.

public readonly gatewayId = attrs.gatewayId;
public readonly name = attrs.gatewayName;
public readonly description = undefined;
public readonly protocolConfiguration: IGatewayProtocolConfig;
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we are assigning undefined values and for protocolConfiguration and authorizerConfig we are just type casting properties but not assigning any values, do we need to do this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These properties are already typecast in base class hence in child we are initializing all the optional fields. This is as per a previous code review comment for runtime construct ( avoid duplicate type cast wherever possible). The protocolConfiguration and authorizerConfig are required fields and cannot be assigned to undefined hence they are cast in child class too.

const gatewayId = resourceParts[1] || 'unknown';

// Create a minimal role for the imported gateway
const role = iam.Role.fromRoleArn(scope, `${id}Role`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the role in the original resource can be a customer provided role, hardcoding it to ManagedPolicy is not correct in a referenced version of Gateway resource.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed , wrong commit. we actually use fromGatewayAttributes to import an existing resource . we don't need fromGatewayArn because it ultimately use fromGatewayAttributes. Removing fromGatewayArn.

actions: GatewayPerms.ASSUME_ROLE,
conditions: {
StringEquals: {
'aws:SourceAccount': account,
Copy link
Contributor

Choose a reason for hiding this comment

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

I am confused here
Why not restrict the permissions here to take in a resource field ? That way we can scope it down to a specific resource inside the source account ?
Or why do we need to scope it down like this we already have limited via a service principal.

Just curious around the ideology behind scoping down the permissions via the Conditions field

On the same lines : Is it possible for gateway in one account to invoke a target in another account. If so will this role create any issues, since we are tying it to a SourceAccount?

Copy link
Contributor Author

@dineshSajwan dineshSajwan Nov 12, 2025

Choose a reason for hiding this comment

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

The trust policy conditions follow the official AWS Bedrock AgentCore Gateway documentation.

The documentation explicitly requires these conditions in the trust policy:
StringEquals: 'aws:SourceAccount' - Ensures only gateways from this account can assume the role
ArnLike: 'aws:SourceArn' - Scopes to specific gateway resources.
I think this is for to prevent the 'confused deputy' security issue where a malicious actor could potentially trick the bedrock-agentcore service into using our role via their gateway in a different account.

Cross account access - No, this will not create any issue.
This condition only check trust policy ( who can assume the role) meaning only gateways from Account A can assume this role.
The cross account permission check (WHAT the role can do once assumed) happen at invoke time , example - //

// Gateway role (in account A) needs permissions to cross-account target
gateway.role.addToPolicy(new iam.PolicyStatement({
  actions: ['lambda:InvokeFunction'],
  resources: ['arn:aws:lambda:us-east-1:222222222222:function/TargetFunc']
}));
// Lambda (in account B) must allow Account A's gateway role
targetFunction.grantInvoke(
  iam.Role.fromRoleArn(this, 'GatewayRole', 
    'arn:aws:iam::111111111111:role/GatewayRole'
  )
);

* Provisions a Cognito User Pool and configures JWT authentication
* @internal
*/
private createDefaultCognitoAuthorizer(): IGatewayAuthorizerConfig {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this creating an authorizer config can we change the name to createDefaultCognitoAuthorizerConfig or something similar ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack.

/**
* Static method for creating a Cognito authorizer
*/
public static usingCognito(props: CognitoAuthorizerProps): IGatewayAuthorizerConfig {
Copy link
Contributor

Choose a reason for hiding this comment

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

I dont think we need to redirect call from one public static function to another. We can directly use the this one and shift the logic here to ensure consistency in code design

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack. Didn't updated the old code. removed fromCognito and directly using usingCognito

* OAuth credential provider configuration implementation
* Can be used with OpenAPI targets
*/
export class OAuthCredentialProviderConfiguration implements ICredentialProvider {
Copy link
Contributor

Choose a reason for hiding this comment

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

Again if it is a provider itself we should remove configuration from the class name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated ICredentialProvider to ICredentialProviderConfig

/**
* Properties for creating a Gateway Target resource
*/
export interface GatewayTargetProps extends McpGatewayTargetCommonProps {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we not using GatewayTargetProps as the base class for each of the target types ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We follow this structure

McpGatewayTargetCommonProps       // ← Common base for both generic and specific targets
└──GatewayTargetProps                  // ← For generic use
└── GatewayTargetLambdaProps   // ← For Lambda Target-specific use
            GatewayTargetOpenApiProps // ← For open API target-specific use
            GatewayTargetSmithyProps    //← For Smithy target-specific use

we have convenience methods to create specific target like lambda, openapi and smithy. These specific target have fixed targetconfiguration while GatewayTargetProps can be used to create any target with generic constructor with targetconfiguration.
The McpGatewayTargetCommonProps help us avoiding duplication for common properties.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just keep : GatewayTargetProps

And provide 3 helper functions inside the above class that allows anyone to create 3 different types of GatewayProps

Basically moving the stuff here : Code link to config classes and that way we can keep GatewayTarget simple and all the abstraction resides on the configuration class.

Copy link
Contributor

Choose a reason for hiding this comment

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

Example :

export interface LambdaGatewayTargetProps extends GatewayTargetProps {
...
constructor(
    gatewayTargetName: string,
    credentialProviderConfigurations: ICredentialProviderConfig,
    lambdaFunction: IFunction,
    toolSchema: IToolSchema,
    description: string
  ) {
    super(
      gatewayTargetName,
      description,
      credentialProviderConfigurations,
      targetConfiguration: LambdaTargetConfiguration.create(
        lambdaFunction,
        toolSchema,
      ),
    });
  }
}

Now in GatewayTarget :

export class GatewayTarget extends GatewayTargetBase implements IMcpGatewayTarget {
...
public static forLambda(
    scope: Construct,
    id: string,
    props: LambdaGatewayTargetProps,
  ): GatewayTarget {
    return new GatewayTarget(scope, id,  props);
  }

}

* @returns Array of validation error messages (empty if valid)
* @internal
*/
export function validateOpenApiSchema(params: OpenApiSchemaValidationParams): string[] {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function seems too bloated.

I assume all the validations here are necessary but clubbing them together doesnot really allow for maintainable code.

Can we break into smaller function chunks ? We can encapsulate the overall logic into smaller functions that handle a specific part of validation. (I have not read the whole function details and assumption behind this comment is that all the validation are mutually-exclusive )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack.

Copy link
Contributor Author

@dineshSajwan dineshSajwan Nov 12, 2025

Choose a reason for hiding this comment

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

yes they are mutually exclusive and don't interfere with each other

});

// Create the integration test
new integ.IntegTest(app, 'GatewayIntegTest', {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is a cross-service integration we should have assertion logic that check that the gateway is properly tied to the Lambda function and is able to invoke the lambda properly when a user request comes in.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack

import * as integ from '@aws-cdk/integ-tests-alpha';
import * as agentcore from '../../../agentcore';

const app = new cdk.App();
Copy link
Contributor

Choose a reason for hiding this comment

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

This and the integ test above seem to be duplicate : packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/gateway/integ.gateway.ts

@aws-cdk-automation aws-cdk-automation added pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member. labels Nov 12, 2025
Copy link
Contributor

@kumvprat kumvprat left a comment

Choose a reason for hiding this comment

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

Added a comment for GatewayTargetProps structure

/**
* Properties for creating a Gateway Target resource
*/
export interface GatewayTargetProps extends McpGatewayTargetCommonProps {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just keep : GatewayTargetProps

And provide 3 helper functions inside the above class that allows anyone to create 3 different types of GatewayProps

Basically moving the stuff here : Code link to config classes and that way we can keep GatewayTarget simple and all the abstraction resides on the configuration class.

/**
* Properties for creating a Gateway Target resource
*/
export interface GatewayTargetProps extends McpGatewayTargetCommonProps {
Copy link
Contributor

Choose a reason for hiding this comment

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

Example :

export interface LambdaGatewayTargetProps extends GatewayTargetProps {
...
constructor(
    gatewayTargetName: string,
    credentialProviderConfigurations: ICredentialProviderConfig,
    lambdaFunction: IFunction,
    toolSchema: IToolSchema,
    description: string
  ) {
    super(
      gatewayTargetName,
      description,
      credentialProviderConfigurations,
      targetConfiguration: LambdaTargetConfiguration.create(
        lambdaFunction,
        toolSchema,
      ),
    });
  }
}

Now in GatewayTarget :

export class GatewayTarget extends GatewayTargetBase implements IMcpGatewayTarget {
...
public static forLambda(
    scope: Construct,
    id: string,
    props: LambdaGatewayTargetProps,
  ): GatewayTarget {
    return new GatewayTarget(scope, id,  props);
  }

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

p2 pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member. pr/needs-further-review PR requires additional review from our team specialists due to the scope or complexity of changes. repeat-contributor [Pilot] contributed between 3-5 PRs to the CDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants