# A2A Gateway by Reilly Manton, Wesley Petry, Scott Wainner, Gabriel Fuentes, Mark Paguay A serverless A2A gateway that provides the complete three-layer architecture required for enterprise agent deployments: management, control, and data layers. ## Why This Matters Amazon Bedrock AgentCore provides a complete gateway solution for MCP (Model Context Protocol) with management, access control, and data proxying. However, AgentCore has no native A2A (Agent-to-Agent) protocol gateway. While AgentCore Runtime can host A2A servers with OAuth 2.0 authentication, it does not provide the management, control, and data layers needed to operate multiple A2A agents behind a single domain. Existing A2A solutions only implement a management layer (agent discovery/registry) but lack the control and data layers, rendering them ineffective for corporate environments where security, access control, and centralized routing are critical. This gateway fills that gap by implementing all three layers, using OAuth 2.0 client credentials flow for backend authentication — the same auth model that A2A and AgentCore Runtime use natively: ### The Three-Layer Architecture **Management Layer** - Agent Discovery & Registry - Centralized agent registry with metadata and capabilities - Dynamic agent card caching and URL rewriting - Agent lifecycle management (register, sync, activate/deactivate) - Multi-backend support for standard A2A servers **Control Layer** - Fine-Grained Access Control (FGAC) - JWT-based authentication via Cognito - Scope-based permissions (who can access which agents) - Lambda authorizer generates agent-specific IAM policies - Unauthorized requests blocked at API Gateway level (never reach Lambdas) - 5-minute policy caching for performance - Audit trail through CloudWatch logs **Data Layer** - Centralized Request Proxying - Single domain endpoint for all agents (`/agents/{agentId}`) - OAuth 2.0 client credentials flow for backend authentication - SSE streaming support for real-time responses - Request/response transformation and validation ## What This Does The gateway hosts multiple A2A agents at a single domain with path-based routing (`/agents/{agentId}`). Each path acts as an independent A2A server from the client's perspective - **standard A2A clients work without modification**. **Key Features**: - ✅ Fully A2A protocol compliant - ✅ Dual protocol binding support (HTTP+JSON/REST and JSON-RPC) - ✅ Fine-grained access control via Cognito JWT scopes - ✅ Per-user rate limiting via DynamoDB - ✅ Semantic search for agent discovery via S3 Vectors - ✅ Async task lifecycle support (get, cancel) via `contextId` session routing - ✅ SSE streaming support for `message:stream` operations - ✅ OAuth 2.0 Client Credentials flow for backend authentication - ✅ Serverless architecture (API Gateway + Lambda + DynamoDB) - ✅ Native support for AWS Bedrock AgentCore Runtime backends ## Architecture ![A2A Gateway Architecture](diagrams/a2a-gateway.png) ### Components **API Gateway (REST API)** - Two static routes that never change when agents are added/removed - Lambda authorizer for JWT validation and FGAC - Response streaming enabled via Lambda Web Adapter **Lambda Functions** - **Authorizer**: JWT validation, FGAC lookup in DynamoDB, generates IAM policies with agent-specific resource ARNs - **Registry**: Agent discovery with permission filtering (returns only agents user can access) - **Search**: Semantic agent discovery using S3 Vectors embeddings - **Proxy** (Container): Routes A2A requests to backends with OAuth authentication, supports real-time SSE streaming via FastAPI + Lambda Web Adapter - **Admin**: Agent registration and management (requires `gateway:admin` scope) **DynamoDB Tables** - **AgentRegistry**: Maps agent IDs to backend URLs, auth configs, cached agent cards - **Permissions**: Maps user scopes to allowed agents and rate limits - **RateLimitCounters**: Tracks per-user request counts per minute (auto-expires via TTL) **Secrets Manager** - Stores OAuth client secrets for backend authentication - Secrets referenced by ARN, never stored in DynamoDB ### A2A Operations Supported The gateway supports both A2A protocol bindings as defined in the [A2A specification](https://a2a-protocol.org/latest/specification/): #### HTTP+JSON/REST Binding (RESTful URLs) - `POST /agents/{agentId}/message:send` - Send message (buffered) - `POST /agents/{agentId}/message:stream` - Send message (SSE streaming) - `GET /agents/{agentId}/.well-known/agent-card.json` - Get Agent Card - `POST /agents/{agentId}/tasks:get` - Get task by ID - `POST /agents/{agentId}/tasks:cancel` - Cancel a task #### JSON-RPC Binding (Single endpoint with method in body) - `POST /agents/{agentId}` with `{"jsonrpc": "2.0", "method": "SendMessage", ...}` - `POST /agents/{agentId}` with `{"jsonrpc": "2.0", "method": "SendStreamingMessage", ...}` - `POST /agents/{agentId}` with `{"jsonrpc": "2.0", "method": "GetTask", ...}` - `POST /agents/{agentId}` with `{"jsonrpc": "2.0", "method": "CancelTask", ...}` The gateway automatically detects the protocol binding based on the request format and translates to JSON-RPC for all backend communication. ### Backend Support The gateway translates all inbound requests to JSON-RPC format for backend communication. This provides a consistent interface regardless of which protocol binding clients use. Backends should implement JSON-RPC handlers for A2A operations. ## Quick Start ### Prerequisites - Terraform >= 1.5.0 - Python 3.12 - AWS CLI configured - Docker (container runtime for building proxy Lambda) - S3 bucket for Terraform state - API Gateway CloudWatch Logging role configured in your account (one-time per account per region — set under API Gateway **Settings → Logging** in the AWS Console) ### 1. Configure Terraform Backend (for remote tfstate) Edit `terraform/backend.tf` - uncomment the S3 backend block and set your bucket name: ```hcl terraform { backend "s3" { bucket = "your-terraform-state-bucket" key = "a2a-gateway/terraform.tfstate" region = "us-east-1" encrypt = true } } ``` **Note:** The S3 backend is commented out by default. If you skip this step, Terraform will use local state storage, which is fine for individual testing but not recommended for team environments. ### 2. Set Variables ```bash cd terraform cp terraform.tfvars.example terraform.tfvars ``` Edit `terraform.tfvars` (set your region and naming preferences): ```hcl aws_region = "us-east-1" # or your preferred region project_name = "a2a-gateway" environment = "poc" ``` ### 3. Build Lambda Package Build the zip package for non-container Lambdas (Authorizer, Registry, Search, Admin): ```bash ./scripts/build_lambda_package.sh ``` ### 4. Deploy ```bash cd terraform terraform init terraform plan # Review what will be created terraform apply ``` This creates everything in one go: - DynamoDB tables (AgentRegistry, Permissions) - Cognito User Pool - ECR repository for the proxy container - Builds and pushes the proxy container image automatically (requires Docker) - 5 Lambda functions (Authorizer, Registry, Search, Proxy container, Admin) - API Gateway with Lambda Authorizer and response streaming - IAM roles and policies - Secrets Manager setup - Automatically updates Lambda env vars with the API Gateway URL **Note**: The proxy Lambda container is built and pushed automatically by Terraform. The other Lambdas use the zip package built in step 3. ### 5. (Optional) Seed Example Permissions ```bash cd .. python3 scripts/seed_permissions.py us-east-1 ``` Get the table name from Terraform outputs: `terraform output permissions_table_name` ### 6. (Optional) Private Deployment To deploy the gateway without internet-facing endpoints, enable private deployment mode. This attaches all Lambdas to a VPC, creates VPC endpoints for all AWS services, and switches API Gateway from `REGIONAL` to `PRIVATE`. **What "private deployment" means**: This mode makes the gateway's own infrastructure private — the API Gateway endpoint is only reachable from within the VPC (or via VPN/Direct Connect/Transit Gateway), and all Lambda functions run inside private subnets. It does **not** provide a fully air-gapped environment. Outbound internet connectivity is still required for OAuth token exchange with backend agents (see below). In enterprise environments, this private gateway would typically sit within an existing VPC architecture that already provides managed egress through NAT Gateways, Transit Gateways, or similar. This sample provisions the VPC endpoints but leaves the outbound connectivity path to the deployer, since it depends on your network topology. You have two options: let the gateway create a new VPC, or bring your own. #### Option A: Let the gateway create a VPC Edit `terraform.tfvars`: ```hcl enable_private_deployment = true vpc_cidr = "10.0.0.0/16" enable_bedrock_endpoint = true # required for semantic search and Bedrock AgentCore backends ``` #### Option B: Bring Your Own VPC (BYOVPC) Deploy into an existing VPC that you manage. The gateway creates only VPC endpoints and attaches Lambdas to your subnets — it does not create or modify your VPC, subnets, route tables, or security groups. Edit `terraform.tfvars`: ```hcl enable_private_deployment = true existing_vpc_id = "vpc-0123456789abcdef0" existing_subnet_ids = ["subnet-aaa", "subnet-bbb"] existing_route_table_ids = ["rtb-aaa"] existing_lambda_security_group_id = "sg-lambda" existing_vpc_endpoint_security_group_id = "sg-vpce" enable_bedrock_endpoint = true ``` **BYOVPC requirements:** - **Subnets**: At least 2 private subnets in different AZs (for Interface VPC endpoint high availability) - **Lambda security group**: Must allow egress on port 443 to the VPC CIDR (for Interface endpoints) and to `0.0.0.0/0` or the DynamoDB/S3 prefix lists (for Gateway endpoints) - **VPC endpoint security group**: Must allow ingress on port 443 from the Lambda security group and from the VPC CIDR - **Route tables**: The route table(s) associated with your private subnets (needed for DynamoDB and S3 Gateway endpoints) - **DNS**: The VPC must have `enableDnsSupport` and `enableDnsHostnames` set to `true` (required for Interface VPC endpoint private DNS) **What gets created in your VPC**: 9 VPC endpoints (DynamoDB, S3, Secrets Manager, execute-api, CloudWatch Logs, ECR API, ECR DKR, S3 Vectors, and optionally Bedrock Runtime). On `terraform destroy`, all gateway resources including these endpoints are removed — your VPC, subnets, route tables, and security groups are never touched. Then re-run `terraform apply`. The API Gateway will only be accessible from within the VPC or via VPN/Direct Connect/Transit Gateway. **Outbound Connectivity Requirement**: The Proxy Lambda performs OAuth 2.0 client credentials flow to authenticate with backend agents. Cognito's OAuth token endpoint (`/oauth2/token`) is hosted on a public domain and [is not accessible via AWS PrivateLink](https://docs.aws.amazon.com/cognito/latest/developerguide/vpc-interface-endpoints.html). Your private VPC must have outbound internet connectivity for this token exchange. In enterprise environments, this is typically provided by: - A **NAT Gateway** in a public subnet - A **Transit Gateway** routing to a shared egress VPC - **AWS Direct Connect** or **VPN** with internet breakout This applies to any external OAuth provider, not just Cognito. The gateway's VPC endpoints handle all AWS service traffic (DynamoDB, S3, Secrets Manager, etc.) privately — only the OAuth token exchange requires outbound connectivity. **Note:** The integration timeout quota defaults to 29 seconds. For long-running agent calls, request an increase for "Maximum integration timeout in milliseconds" in the AWS Service Quotas console. ## Using the Gateway ### 1. Get Authentication Token Export your gateway URL and obtain a JWT token: ```bash # Get gateway configuration GATEWAY_URL=$(cd terraform && terraform output -raw api_gateway_url) TOKEN_ENDPOINT=$(cd terraform && terraform output -raw cognito_token_endpoint) CLIENT_ID=$(cd terraform && terraform output -raw cognito_client_id) CLIENT_SECRET=$(cd terraform && terraform output -raw cognito_client_secret) # Obtain JWT with required scopes TOKEN_RESPONSE=$(curl -s -X POST $TOKEN_ENDPOINT \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=a2a-gateway/gateway:admin a2a-gateway/billing:read") export JWT=$(echo $TOKEN_RESPONSE | jq -r .access_token) echo "JWT obtained: ${JWT:0:50}..." ``` ### 2. Register Backend Agents The gateway supports two types of A2A backends: #### Standard A2A Server ```bash curl -X POST $GATEWAY_URL/admin/agents/register \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "agentId": "my-agent", "name": "My Custom Agent", "backendUrl": "https://your-backend.example.com", "agentCardUrl": "https://your-backend.example.com/.well-known/agent-card.json", "authConfig": { "type": "oauth2_client_credentials", "tokenUrl": "https://your-auth.example.com/oauth/token", "clientId": "your-client-id", "clientSecret": "your-client-secret", "scopes": ["agent:invoke"] } }' ``` **Note**: For Bedrock AgentCore Runtime backends, ensure the agent ARN is URL-encoded in the `backendUrl`. The AgentCore agent must be deployed with OAuth authentication using the `customJWTAuthorizer` — configure it with `allowedClients` (not `allowedAudience`) set to your Cognito client ID. This is required because Cognito client_credentials tokens include a `client_id` claim but not the standard `aud` claim that `allowedAudience` validates against. See the [AgentCore A2A protocol contract](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-a2a-protocol-contract.html) and [Deploy A2A servers in AgentCore Runtime](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-a2a.html) for deployment details. ### 3. Discover Agents List all agents you have access to: ```bash curl $GATEWAY_URL/agents \ -H "Authorization: Bearer $JWT" | jq . ``` Example response: ```json [ { "name": "Calculator Agent", "description": "A calculator agent that can perform basic arithmetic operations.", "url": "https://your-gateway.execute-api.us-east-1.amazonaws.com/v1/agents/bedrock-agent", "protocolVersion": "0.3.0", "skills": [ { "id": "calculator", "name": "calculator", "description": "Calculator powered by SymPy..." } ], "capabilities": { "streaming": true } } ] ``` ### 4. Semantic Search for Agents Search for agents using natural language queries: ```bash curl -X POST $GATEWAY_URL/search \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"query": "calculator math arithmetic", "topK": 5}' | jq . ``` Example response: ```json { "results": [ { "agentCard": { "name": "Calculator Agent", "description": "A calculator agent that can perform basic arithmetic operations.", "url": "https://your-gateway.execute-api.us-east-1.amazonaws.com/v1/agents/bedrock-agent" }, "score": 0.89 } ], "query": "calculator math arithmetic", "totalMatches": 1 } ``` The search uses Amazon Titan Text Embeddings V2 to generate vector embeddings stored in S3 Vectors. Results are filtered by user permissions - you only see agents you have access to. ### 5. Get Agent Card Fetch a specific agent's capabilities: ```bash curl $GATEWAY_URL/agents/bedrock-agent/.well-known/agent-card.json \ -H "Authorization: Bearer $JWT" | jq . ``` ### 6. Send Messages to Agents #### HTTP+JSON/REST Binding Send a message using RESTful URLs (buffered response): ```bash curl -X POST $GATEWAY_URL/agents/bedrock-agent/message:send \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "message": { "messageId": "msg-123", "role": "ROLE_USER", "parts": [{"text": "Calculate 2 + 2"}] } }' | jq . ``` #### JSON-RPC Binding Send a message using JSON-RPC format (method in body): ```bash curl -X POST $GATEWAY_URL/agents/bedrock-agent \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "req-123", "method": "SendMessage", "params": { "message": { "messageId": "msg-123", "role": "ROLE_USER", "parts": [{"text": "Calculate 2 + 2"}] } } }' | jq . ``` Example response (streaming chunks from Bedrock AgentCore): ```json [ { "contextId": "a1a7915d-078b-478c-a7b5-9c23a4883a8c", "kind": "message", "messageId": "b8f185a5-fecd-4736-be3c-4d48806e66c2", "parts": [{"kind": "text", "text": "The result of"}], "role": "agent", "taskId": "5be2cf00-6c20-4e2f-a4d4-7e0f90f47c8f" }, { "contextId": "a1a7915d-078b-478c-a7b5-9c23a4883a8c", "kind": "message", "messageId": "fbb7e32e-bfee-439b-8bca-9b2f50f227ad", "parts": [{"kind": "text", "text": " 2 + 2 is"}], "role": "agent", "taskId": "5be2cf00-6c20-4e2f-a4d4-7e0f90f47c8f" }, { "contextId": "a1a7915d-078b-478c-a7b5-9c23a4883a8c", "kind": "message", "messageId": "ad214099-d7d2-4b3e-931b-e363a4edde84", "parts": [{"kind": "text", "text": " **4**."}], "role": "agent", "taskId": "5be2cf00-6c20-4e2f-a4d4-7e0f90f47c8f" } ] ``` ### 7. Stream Responses #### HTTP+JSON/REST Binding For streaming responses using RESTful URLs (SSE): ```bash curl -N -X POST $GATEWAY_URL/agents/bedrock-agent/message:stream \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "message": { "messageId": "msg-456", "role": "ROLE_USER", "parts": [{"text": "What is 101 * 11?"}] } }' ``` #### JSON-RPC Binding For streaming responses using JSON-RPC format: ```bash curl -N -X POST $GATEWAY_URL/agents/bedrock-agent \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "req-456", "method": "SendStreamingMessage", "params": { "message": { "messageId": "msg-456", "role": "ROLE_USER", "parts": [{"text": "What is 101 * 11?"}] } } }' ``` ### 8. Task Operations (Async Agents) For agents that support async task lifecycle, the gateway passes through task operations using `contextId` for session routing. The `contextId` ensures follow-up requests (get, cancel) hit the same backend container that handled the original `message:send`. #### Get Task ```bash # HTTP+JSON/REST curl -X POST $GATEWAY_URL/agents/my-async-agent/tasks:get \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "id": "task-uuid-from-send-response", "contextId": "context-uuid-from-send-response" }' # JSON-RPC curl -X POST $GATEWAY_URL/agents/my-async-agent \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "req-002", "method": "GetTask", "params": { "id": "task-uuid-from-send-response", "contextId": "context-uuid-from-send-response" } }' ``` #### Cancel Task ```bash curl -X POST $GATEWAY_URL/agents/my-async-agent/tasks:cancel \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "id": "task-uuid", "contextId": "context-uuid" }' ``` ### Complete Workflow Example ```bash # 1. Get JWT TOKEN_RESPONSE=$(curl -s -X POST $TOKEN_ENDPOINT \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=a2a-gateway/gateway:admin a2a-gateway/billing:read") export JWT=$(echo $TOKEN_RESPONSE | jq -r .access_token) # 2. Discover agents curl $GATEWAY_URL/agents -H "Authorization: Bearer $JWT" | jq '.[].name' # 3. Get agent card curl $GATEWAY_URL/agents/bedrock-agent/.well-known/agent-card.json \ -H "Authorization: Bearer $JWT" | jq '.skills[].name' # 4. Send message curl -X POST $GATEWAY_URL/agents/bedrock-agent/message:send \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "message": { "messageId": "msg-001", "role": "ROLE_USER", "parts": [{"text": "Calculate the square root of 144"}] } }' | jq '.[].parts[].text' | tr -d '\n' && echo ``` ## Admin Operations - `POST /admin/agents/register` - Register new backend agent - `POST /admin/agents/{agentId}/sync` - Refresh Agent Card cache - `PATCH /admin/agents/{agentId}/status` - Set agent active/inactive ## A2A Protocol Compliance This gateway implements core A2A messaging operations with support for both protocol bindings. Below is the full compliance status: ### Protocol Bindings | Binding | Status | Notes | |---------|--------|-------| | **HTTP+JSON/REST** | ✅ Supported | RESTful URLs like `/message:send` | | **JSON-RPC** | ✅ Supported | Single endpoint with method in body | ### Operations | Operation | Endpoint | Status | Notes | |-----------|----------|--------|-------| | **Agent Discovery** | `GET /agents` | ✅ Supported | Registry with permission filtering | | **Get Agent Card** | `GET /agents/{id}/.well-known/agent-card.json` | ✅ Supported | Cached with URL rewriting | | **Send Message** | `POST /agents/{id}/message:send` | ✅ Supported | Buffered response | | **Stream Message** | `POST /agents/{id}/message:stream` | ✅ Supported | Real-time SSE streaming | | **Get Task** | `POST /agents/{id}/tasks:get` | ✅ Supported | Pass-through to backend | | **Cancel Task** | `POST /agents/{id}/tasks:cancel` | ✅ Supported | Pass-through to backend | | **Subscribe to Task** | `POST /tasks/{id}:subscribe` | ❌ Not Implemented | | | **Push Notifications** | `POST /tasks/{id}/pushNotificationConfigs` | ❌ Not Implemented | Webhook-based async updates | | **Extended Agent Card** | `GET /extendedAgentCard` | ❌ Not Implemented | User-specific capabilities | ### Known Limitations **Task Management**: Task get and cancel are supported as opaque pass-throughs to the backend agent. The gateway does not store task state itself — the backend agent is responsible for task persistence. **Integration Timeout**: API Gateway REST API has a default 29-second integration timeout. This gateway is configured for 300 seconds (5 minutes), but you must request a quota increase from AWS Support to enable timeouts beyond 29 seconds. Without the quota increase, requests will timeout at 29 seconds regardless of the configured value. Request the "Amazon API Gateway - REST API integration timeout" quota increase in the AWS Service Quotas console. ## Troubleshooting **"Unauthorized" errors**: Check your JWT is valid and not expired. Tokens expire after 60 minutes. **"Permission denied"**: The Lambda Authorizer generates IAM policies based on your JWT scopes. If you get a 403, your scopes don't grant access to that agent. Check your JWT scopes: ```bash echo $JWT | cut -d. -f2 | base64 -d | jq .scope ``` Then verify the Permissions table maps those scopes to the agent you're trying to access. Note: Authorizer results are cached for 5 minutes, so permission changes may take time to take effect. **Agent not found**: Verify the agent is registered and status is "active": ```bash aws dynamodb get-item \ --table-name \ --key '{"agentId": {"S": "test-agent"}}' ``` **Backend connection fails**: Check CloudWatch logs for the Proxy Lambda. Verify OAuth credentials are correct. ## Project Structure ``` /terraform - Infrastructure as Code /modules /dynamodb - Tables /cognito - Auth /ecr - Container registry for proxy Lambda /lambda-functions - All Lambdas /api-gateway - REST API with streaming support /vpc - VPC with private subnets and security groups (private deployment) /vpc-endpoints - VPC endpoints for AWS services (standalone, supports BYOVPC) /s3-vectors - S3 Vectors bucket and index for semantic search /src/lambdas /authorizer - JWT validation /registry - Agent discovery /search - Semantic agent discovery via S3 Vectors /proxy - DEPRECATED: kept for unit tests only (not deployed) /proxy_container - A2A routing with streaming (FastAPI + Lambda Web Adapter) /admin - Agent management /shared - Common utilities /tests /unit - Unit tests /property - Property-based tests /scripts - Helper scripts /diagrams - Architecture diagrams ``` ## Security Considerations This gateway is designed as a reference implementation. For production deployments, review the following security considerations: ### Backend Trust Model The gateway operates on a **trust-after-authentication** model. Once a backend agent is registered and OAuth credentials are validated, the gateway trusts all responses from that backend without content validation. This means: - Responses from backend agents are proxied directly to clients without inspection - A compromised or malicious backend could return harmful content - **Production recommendation**: Implement an approval workflow for agent registration. Admins should review backend agents before registration, ideally integrated with CI/CD pipelines for agent deployment. ### Rate Limiting Per-user, per-agent rate limiting is configured via the Permissions table. Add `requestsPerMinute` for a default limit and `agentLimits` for per-agent overrides: ```bash aws dynamodb put-item --table-name --item '{ "scope": {"S": "billing:read"}, "allowedAgents": {"L": [{"S": "cheap-agent"}, {"S": "expensive-agent"}]}, "requestsPerMinute": {"N": "100"}, "agentLimits": {"M": {"expensive-agent": {"N": "10"}}} }' ``` In this example, `cheap-agent` gets 100/min (default) and `expensive-agent` gets 10/min (override). When a user has multiple scopes, the highest limit for each agent applies. Scopes without any rate limit config have unlimited access. Exceeding the limit returns HTTP 429 with `retryAfterSeconds`. ### CORS Configuration CORS is configured with `Access-Control-Allow-Origin: '*'` for ease of development. This allows any origin to make requests to the API. - **Production recommendation**: Restrict CORS to specific trusted origins. ### Permission Propagation Delay The Lambda Authorizer caches results for 5 minutes for performance. This means: - Permission revocations may take up to 5 minutes to take effect - Newly granted permissions may also have a delay - The cache TTL is configurable in `terraform/modules/api-gateway/main.tf` ### Prompt Injection The gateway proxies A2A messages without modification. - Backend agents are responsible for implementing prompt injection defenses - The gateway provides access control *to* agents, not *within* agents - Consider integrating guardrails at the backend agent level ### API Gateway Timeout API Gateway REST API has a default 29-second integration timeout. This gateway configures 300 seconds, but you must request a quota increase from AWS Support first. Go to AWS Service Quotas console and request an increase for "Amazon API Gateway - REST API integration timeout". ### VPC Security Group Configuration (Private Deployment) When private deployment is enabled, Lambda functions are attached to a VPC with security groups that restrict egress to HTTPS (port 443) only. **When the gateway creates the VPC (Option A):** The gateway automatically creates both security groups and configures the prefix list egress rules for DynamoDB and S3 Gateway endpoints. **When you bring your own VPC (Option B):** You are responsible for configuring security group rules. The Lambda security group must include egress rules for both: - The VPC CIDR (for Interface VPC endpoints like Secrets Manager, CloudWatch Logs, etc.) - DynamoDB and S3 prefix lists (for Gateway VPC endpoints, which route to public IP ranges outside the VPC CIDR) Without the prefix list egress rules, Lambda functions will timeout when attempting to reach DynamoDB or S3 through Gateway endpoints. This is a common VPC networking pitfall — Gateway endpoints use route tables (not ENIs inside the VPC), so their destination IPs fall outside the VPC CIDR block. Alternatively, allowing egress to `0.0.0.0/0` on port 443 covers both cases. ### VPC Block Public Access [VPC Block Public Access (BPA)](https://docs.aws.amazon.com/vpc/latest/userguide/security-vpc-bpa.html) is an account-level setting that blocks traffic through internet gateways across all VPCs in a region. This gateway does not enable BPA because: - BPA is account-scoped, not per-VPC — enabling it could impact other workloads in the account - The private deployment already has no internet gateway, so BPA provides no additional protection for this gateway - **Production recommendation**: Enable VPC BPA at the account or organization level as a defense-in-depth measure. Use the AWS Console (VPC → Settings → Block Public Access) or the `aws ec2 modify-vpc-block-public-access-options` CLI command. If other workloads in the account require internet access, use BPA exclusions for those specific VPCs. ## Test with Example Agents Ready-to-deploy sample agents are included to test the full gateway flow end-to-end. The example deploys two A2A-compliant agents (Weather and Calculator) to Amazon Bedrock AgentCore Runtime with Cognito OAuth authentication, all managed by Terraform. See the full walkthrough: [AgentCore A2A Example Agents](examples/README.md) ## Clean Up ```bash cd terraform terraform destroy ``` Note: You may need to manually delete secrets from Secrets Manager if they're not fully deleted. ## License MIT