From b509345d1da126630f3e30f53bd67bc454a191bc Mon Sep 17 00:00:00 2001 From: gitikavj Date: Tue, 27 Oct 2026 21:39:18 +0000 Subject: [PATCH] feat(gateway-target): allow IAM auth for MCP and OpenAPI --- .../project/add/gateway-target/index.test.ts | 39 +++++++++++ src/projectSchemas/gateway.test.ts | 64 +++++++++++++++++++ src/projectSchemas/gateway.ts | 22 +++++-- 3 files changed, 121 insertions(+), 4 deletions(-) diff --git a/src/handlers/project/add/gateway-target/index.test.ts b/src/handlers/project/add/gateway-target/index.test.ts index 93a1159e2d..99aebb488c 100644 --- a/src/handlers/project/add/gateway-target/index.test.ts +++ b/src/handlers/project/add/gateway-target/index.test.ts @@ -152,6 +152,45 @@ describe("project add gateway-target", () => { expect((await projectSpec(projectRoot)).agentCoreGateways[0].targets[0]).toEqual(target); }); + test("accepts configured IAM auth through --target-configuration", async () => { + const projectRoot = await inProject(); + await addGateway(); + const targets = [ + { + name: "mcp-iam", + targetType: "mcpServer", + endpoint: ENDPOINT, + outboundAuth: { + type: "GATEWAY_IAM_ROLE", + service: "bedrock-agentcore", + region: "us-east-1", + }, + }, + { + name: "openapi-iam", + targetType: "openApiSchema", + schemaSource: { inline: { path: "openapi.json" } }, + outboundAuth: { + type: "GATEWAY_IAM_ROLE", + service: "execute-api", + }, + }, + ]; + + for (const target of targets) { + await run([ + "add", + "gateway-target", + "--gateway", + "tools", + "--target-configuration", + JSON.stringify(target), + ]); + } + + expect((await projectSpec(projectRoot)).agentCoreGateways[0].targets).toEqual(targets); + }); + test("rejects shortcut flags with complete Target JSON", async () => { await inProject(); await addGateway(); diff --git a/src/projectSchemas/gateway.test.ts b/src/projectSchemas/gateway.test.ts index 7c715378e4..001d0a157b 100644 --- a/src/projectSchemas/gateway.test.ts +++ b/src/projectSchemas/gateway.test.ts @@ -146,6 +146,70 @@ describe("gateway custom validation", () => { }).success, ).toBe(false); }); + it.each([ + [ + "mcpServer", + { + endpoint: "https://example.com/mcp", + }, + ], + [ + "openApiSchema", + { + schemaSource: { inline: { path: "openapi.json" } }, + }, + ], + ])("allows configured IAM role authentication for %s targets", (targetType, configuration) => { + const target = { + name: "target", + targetType, + ...configuration, + }; + expect( + AgentCoreGatewayTargetSchema.safeParse({ + ...target, + outboundAuth: { type: "GATEWAY_IAM_ROLE" }, + }).success, + ).toBe(false); + expect( + AgentCoreGatewayTargetSchema.safeParse({ + ...target, + outboundAuth: { type: "GATEWAY_IAM_ROLE", service: "bedrock-agentcore" }, + }).success, + ).toBe(true); + expect( + AgentCoreGatewayTargetSchema.safeParse({ + ...target, + outboundAuth: { + type: "GATEWAY_IAM_ROLE", + service: "bedrock-agentcore", + region: "us-east-1", + }, + }).success, + ).toBe(true); + }); + it("rejects IAM signing fields for other outbound authentication types", () => { + expect( + AgentCoreGatewayTargetSchema.safeParse({ + name: "mcp", + targetType: "mcpServer", + endpoint: "https://example.com/mcp", + outboundAuth: { type: "NONE", service: "bedrock-agentcore" }, + }).success, + ).toBe(false); + expect( + AgentCoreGatewayTargetSchema.safeParse({ + name: "openapi", + targetType: "openApiSchema", + schemaSource: { inline: { path: "openapi.json" } }, + outboundAuth: { + type: "OAUTH", + credentialName: "oauth", + region: "us-east-1", + }, + }).success, + ).toBe(false); + }); it("limits passthrough-only authentication modes", () => { expect( AgentCoreGatewayTargetSchema.safeParse({ diff --git a/src/projectSchemas/gateway.ts b/src/projectSchemas/gateway.ts index b0a2f65aca..c4bfc054cf 100644 --- a/src/projectSchemas/gateway.ts +++ b/src/projectSchemas/gateway.ts @@ -68,12 +68,16 @@ export const TARGET_TYPE_AUTH_CONFIG: Record< > = { openApiSchema: { authRequired: true, - validAuthTypes: ["OAUTH", "API_KEY"], + validAuthTypes: ["OAUTH", "API_KEY", "GATEWAY_IAM_ROLE"], iamRoleFallback: false, }, smithyModel: { authRequired: false, validAuthTypes: [], iamRoleFallback: true }, apiGateway: { authRequired: false, validAuthTypes: ["API_KEY", "NONE"], iamRoleFallback: true }, - mcpServer: { authRequired: false, validAuthTypes: ["OAUTH", "NONE"], iamRoleFallback: false }, + mcpServer: { + authRequired: false, + validAuthTypes: ["OAUTH", "NONE", "GATEWAY_IAM_ROLE"], + iamRoleFallback: false, + }, lambda: { authRequired: false, validAuthTypes: ["OAUTH", "NONE"], iamRoleFallback: true }, lambdaFunctionArn: { authRequired: false, @@ -406,15 +410,25 @@ export const AgentCoreGatewayTargetSchema = z } if ( data.outboundAuth?.type === "GATEWAY_IAM_ROLE" && - data.targetType === "passthrough" && + authConfig.validAuthTypes.includes("GATEWAY_IAM_ROLE") && !data.outboundAuth.service ) { ctx.addIssue({ code: "custom", - message: "GATEWAY_IAM_ROLE outbound auth on passthrough targets requires a service name.", + message: `GATEWAY_IAM_ROLE outbound auth on ${data.targetType} targets requires a service name.`, path: ["outboundAuth", "service"], }); } + if ( + data.outboundAuth?.type !== "GATEWAY_IAM_ROLE" && + (data.outboundAuth?.service !== undefined || data.outboundAuth?.region !== undefined) + ) { + ctx.addIssue({ + code: "custom", + message: "service and region are only valid with GATEWAY_IAM_ROLE outbound auth.", + path: ["outboundAuth"], + }); + } } }); export type AgentCoreGatewayTarget = z.infer;