Skip to content

feat: support AWS SigV4 authentication for MCP backends#2181

Draft
Flgado wants to merge 2 commits into
envoyproxy:mainfrom
Flgado:feat/mcp-aws-sigv4
Draft

feat: support AWS SigV4 authentication for MCP backends#2181
Flgado wants to merge 2 commits into
envoyproxy:mainfrom
Flgado:feat/mcp-aws-sigv4

Conversation

@Flgado
Copy link
Copy Markdown
Contributor

@Flgado Flgado commented Jun 2, 2026

Description

Adds awsCredentials to MCPBackendSecurityPolicy, enabling the gateway to sign outbound MCP requests with AWS SigV4. This lets MCP clients connect to AWS IAM-fronted MCP servers (AWS MCP Server, Bedrock AgentCore) through the gateway without any AWS-specific client tooling — the gateway handles all signing server-side, replacing the per-client mcp-proxy-for-aws.

Motivation

MCP servers behind AWS IAM cannot be reached with the existing apiKey or OAuth mechanisms. Today each client must run a local signing proxy. This duplicates AWS credential handling across every client and prevents a single, centrally managed identity from being used. Moving SigV4 signing into the gateway removes both problems: clients speak plain MCP, and one managed AWS identity signs all traffic.

API

New awsCredentials field on the MCP backend security policy, mutually exclusive with apiKey (enforced via CEL):

backendRefs:
  - name: aws-mcp
    kind: Backend
    securityPolicy:
      awsCredentials:
        region: us-east-1
        service: bedrock-agentcore  # optional — inferred from host when omitted
        mode: ServiceIdentify       # default; only supported value today
        credentialsFile:            # optional — otherwise default credential chain
          secretRef:
            name: aws-creds
          profile: default
  • service is optional: the gateway infers it from standard AWS endpoint hosts (<service>.<region>.amazonaws.com / .api.aws). A clear error is returned when it cannot be inferred and is not set.
  • mode defaults to ServiceIdentify (sign with the gateway's own credentials). UserIdentity (per-user CloudTrail attribution via STS AssumeRoleWithWebIdentity) is reserved for a follow-up.
  • Credentials come from the referenced Secret or the default AWS credential chain (env vars, IRSA, EKS Pod Identity, EC2 instance role), and are refreshed on every signing call.

Design decisions

Why the backend must be an Envoy Gateway Backend
SigV4 signs over the Host header and path that AWS ultimately sees. The MCP proxy sends every request to the local Envoy listener, and Envoy rewrites authority/path to the upstream — so the proxy cannot discover the real host on its own. The controller resolves the upstream FQDN by reading the Backend resource and bakes it into the signing config ahead of time.

This is the only MCP code path that reads the Backend object. Non-AWS routes never need it because Envoy owns host resolution for them.

Signing on every send path
igV4 headers are computed as the last step before a request is sent, on every outbound path: session initialization, single-backend calls (tools/call), and the multi-backend fan-out (tools/list). Non-AWS backends are never signed, and signatures never leak across backends on the same route.

The signature is computed against a temporary request built with the resolved upstream host/path, then only the Authorization / X-Amz-* headers are copied onto the real request. This avoids invalidation when Envoy later rewrites the authority.

Standalone aigw run support
aigw run uses a fake client to back the in-process controller. The object collection step now additionally loads Backend resources into that fake client so backend-host resolution works in standalone mode. The Backend YAML was already written to the Envoy output for cluster building — this just registers it with the controller's client too.

Testing

  • Signer: credential sources, SigV4 output, STS session token, path+query signing, concurrency.
  • Controller: service inference, default chain, explicit service + Secret-backed credentials + profile, custom path, and error paths (un-inferrable service, non-Backend kind, missing FQDN).
    Proxy: signer built only for AWS backends; signing on the single-backend and fan-out paths; cross-backend isolation (only the AWS backend is signed); invalid credentials rejected at load.
    CEL/CRD: valid awsCredentials and apiKey/awsCredentials mutual exclusion.
    Manual E2E: aigw run against the AWS MCP Server — tools/list and tools/call (call_aws returning real AWS identity).

Note on SigV4 code duplication with backendauth/aws.go
The existing LLM backend SigV4 signer (internal/backendauth/aws.go) contains nearly identical logic: credential loading, temp file, ContentLength = -1 to avoid signature invalidation by Envoy's chunked transfer-encoding, and copying only Authorization / X-Amz-* headers. This PR intentionally does not refactor that code to avoid a large cross-cutting change. Instead, the new signing logic lives in internal/awsauth/ as a clean, reusable package. A follow-up can migrate backendauth/aws.go to use internal/awsauth/ as well, consolidating both into a single SigV4 implementation.

Out of scope / follow-ups

  • UserIdentity mode — per-user IAM signing via STS AssumeRoleWithWebIdentity.
  • Refactor internal/backendauth/aws.go to use internal/awsauth/ (consolidate SigV4 into one place).

Signed-off-by: Joao Folgado <jfolgado94@gmail.com>
@Flgado Flgado changed the title feat: AWS SigV4 authentication for MCP backends feat: support AWS SigV4 authentication for MCP backends Jun 2, 2026
Signed-off-by: Joao Folgado <jfolgado94@gmail.com>
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.35%. Comparing base (0264ed9) to head (71bedd1).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
internal/controller/gateway.go 76.81% 10 Missing and 6 partials ⚠️
internal/awsauth/awsauth.go 76.00% 6 Missing and 6 partials ⚠️
internal/mcpproxy/awssign.go 85.18% 2 Missing and 2 partials ⚠️
internal/mcpproxy/mcpproxy.go 66.66% 2 Missing and 2 partials ⚠️
internal/mcpproxy/session.go 60.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2181      +/-   ##
==========================================
- Coverage   84.41%   84.35%   -0.06%     
==========================================
  Files         134      136       +2     
  Lines       19156    19327     +171     
==========================================
+ Hits        16171    16304     +133     
- Misses       1998     2019      +21     
- Partials      987     1004      +17     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants