Solo Enterprise Agent Gateway v2.3.0 introduces native AWS Bedrock AgentCore backend support — no sidecar proxy, no custom container, no extra hop. The gateway signs requests with SigV4 using the correct bedrock-agentcore service name and routes A2A traffic directly to your AgentCore runtime agents.
In this workshop you will:
- Install Enterprise Agent Gateway v2.3.0 on EKS
- Configure IRSA so the gateway can call AgentCore natively
- Create an
AgentgatewayBackendwith the newspec.aws.agentCoretype - Route A2A traffic directly to AgentCore — zero proxies
- Apply enterprise policies (JWT auth, rate limiting) to the route
graph LR
Client([Client]) -->|A2A| GW[Agent Gateway]
GW -->|A2A| Proxy[A2A Proxy Pod<br/>Flask + boto3]
Proxy -->|SigV4 bedrock-agentcore| AC[AWS Bedrock<br/>AgentCore]
style Proxy fill:#f66,stroke:#333,color:#fff
style GW fill:#4a9eff,stroke:#333,color:#fff
style AC fill:#ff9900,stroke:#333,color:#fff
The proxy was necessary because the gateway hardcoded SigV4 signing to the bedrock service name. AgentCore requires bedrock-agentcore.
graph LR
Client([Client]) -->|A2A| GW[Agent Gateway]
GW -->|SigV4 bedrock-agentcore| AC[AWS Bedrock<br/>AgentCore]
style GW fill:#4a9eff,stroke:#333,color:#fff
style AC fill:#ff9900,stroke:#333,color:#fff
The gateway now handles SigV4 signing with the correct service name natively.
sequenceDiagram
participant C as Client
participant GW as Agent Gateway<br/>(EKS Pod with IRSA)
participant STS as AWS STS
participant AC as Bedrock AgentCore
C->>GW: POST /tasks/send (A2A JSON-RPC)
Note over GW: Matches HTTPRoute → AgentgatewayBackend<br/>type: aws.agentCore
GW->>STS: AssumeRoleWithWebIdentity<br/>(IRSA token → temporary credentials)
STS-->>GW: Temporary AWS credentials
Note over GW: Signs request with SigV4<br/>service = "bedrock-agentcore"
GW->>AC: InvokeAgentRuntime<br/>(SigV4-signed HTTPS)
AC-->>GW: Agent response (streaming)
GW-->>C: A2A response (JSON-RPC result)
v2.3.0 adds a new spec.aws backend type alongside the existing spec.ai, spec.mcp, and spec.static types:
graph TD
Backend[AgentgatewayBackend] --> AI[spec.ai<br/>LLM Providers]
Backend --> MCP[spec.mcp<br/>MCP Servers]
Backend --> Static[spec.static<br/>Static Host]
Backend --> AWS[spec.aws<br/>AWS Services]
AI --> OpenAI[OpenAI]
AI --> Anthropic[Anthropic]
AI --> Bedrock[Bedrock]
AI --> Azure[Azure OpenAI]
AI --> Gemini[Gemini]
AWS --> AgentCore[agentCore<br/>agentRuntimeArn<br/>qualifier]
style AWS fill:#ff9900,stroke:#333,color:#fff
style AgentCore fill:#ff9900,stroke:#333,color:#fff
- An EKS cluster (v1.28+)
kubectl,aws,helm, andeksctlCLI tools installed- AWS IAM permissions to create roles, policies, and OIDC providers
- A Bedrock AgentCore runtime agent deployed (you need the ARN)
- A Solo Enterprise Agent Gateway license key
export CLUSTER_NAME="<your-eks-cluster-name>"
export AWS_REGION="us-east-1"
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Your AgentCore runtime ARN
export AGENT_RUNTIME_ARN="arn:aws:bedrock-agentcore:${AWS_REGION}:${ACCOUNT_ID}:runtime/<your-runtime-id>"
# Enterprise Agent Gateway license
export AGENTGATEWAY_LICENSE_KEY="<your-license-key>"
echo "Cluster: ${CLUSTER_NAME}"
echo "Account: ${ACCOUNT_ID}"
echo "Region: ${AWS_REGION}"
echo "Runtime: ${AGENT_RUNTIME_ARN}"helm upgrade -i enterprise-agentgateway-crds \
oci://us-docker.pkg.dev/solo-public/enterprise-agentgateway/charts/enterprise-agentgateway-crds \
--create-namespace --namespace agentgateway-system \
--version v2.3.0helm upgrade -i enterprise-agentgateway \
oci://us-docker.pkg.dev/solo-public/enterprise-agentgateway/charts/enterprise-agentgateway \
--namespace agentgateway-system \
--set-string licensing.licenseKey=${AGENTGATEWAY_LICENSE_KEY} \
--version v2.3.0kubectl get pods -n agentgateway-systemExpected output:
NAME READY STATUS RESTARTS AGE
enterprise-agentgateway-xxxxxxxxx-xxxxx 1/1 Running 0 30s
ext-auth-service-enterprise-agentgateway-xxxxxxxxx-xxxxx 1/1 Running 0 30s
ext-cache-enterprise-agentgateway-xxxxxxxxx-xxxxx 1/1 Running 0 30s
rate-limiter-enterprise-agentgateway-xxxxxxxxx-xxxxx 1/1 Running 0 30s
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: agentgateway
namespace: agentgateway-system
spec:
gatewayClassName: enterprise-agentgateway
listeners:
- name: http
port: 8080
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
EOFWait for it to be programmed:
kubectl wait --for=condition=programmed gateway/agentgateway \
-n agentgateway-system --timeout=120sThe Gateway controller creates a proxy pod with its own service account (agentgateway). This proxy pod is the one that actually calls AgentCore, so it needs AWS credentials via IRSA.
graph TD
subgraph EKS Cluster
subgraph agentgateway-system namespace
Controller[Controller Pod<br/>SA: enterprise-agentgateway]
Proxy[Proxy Pod<br/>SA: agentgateway<br/>+ IRSA annotation]
end
end
subgraph AWS IAM
Role[IAM Role<br/>agentgateway-irsa]
Policy[Policy<br/>bedrock-agentcore:*]
end
Proxy -.->|AssumeRoleWithWebIdentity| Role
Role --> Policy
style Proxy fill:#4a9eff,stroke:#333,color:#fff
style Role fill:#ff9900,stroke:#333,color:#fff
style Policy fill:#ff9900,stroke:#333,color:#fff
eksctl utils associate-iam-oidc-provider \
--cluster $CLUSTER_NAME \
--approveexport OIDC_ID=$(aws eks describe-cluster \
--name $CLUSTER_NAME \
--query "cluster.identity.oidc.issuer" \
--output text | sed 's|.*/id/||')
echo "OIDC ID: $OIDC_ID"cat > /tmp/agentcore-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": [
"${AGENT_RUNTIME_ARN}",
"${AGENT_RUNTIME_ARN}/*"
]
}
]
}
EOF
aws iam create-policy \
--policy-name AgentGatewayAgentCoreAccess \
--policy-document file:///tmp/agentcore-policy.jsoncat > /tmp/trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/oidc.eks.${AWS_REGION}.amazonaws.com/id/${OIDC_ID}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.${AWS_REGION}.amazonaws.com/id/${OIDC_ID}:sub": "system:serviceaccount:agentgateway-system:agentgateway",
"oidc.eks.${AWS_REGION}.amazonaws.com/id/${OIDC_ID}:aud": "sts.amazonaws.com"
}
}
}
]
}
EOF
aws iam create-role \
--role-name agentgateway-agentcore-irsa \
--assume-role-policy-document file:///tmp/trust-policy.json
aws iam attach-role-policy \
--role-name agentgateway-agentcore-irsa \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AgentGatewayAgentCoreAccess
export ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/agentgateway-agentcore-irsa"The Gateway controller creates the agentgateway service account automatically. Annotate it with the IRSA role:
kubectl annotate sa agentgateway -n agentgateway-system \
eks.amazonaws.com/role-arn=${ROLE_ARN} --overwrite
# Restart the proxy pod to pick up the IRSA token
kubectl delete pod -n agentgateway-system \
-l gateway.networking.k8s.io/gateway-name=agentgatewayPROXY_POD=$(kubectl get pods -n agentgateway-system \
-l gateway.networking.k8s.io/gateway-name=agentgateway \
-o jsonpath='{.items[0].metadata.name}')
kubectl get pod $PROXY_POD -n agentgateway-system \
-o jsonpath='{range .spec.containers[*].env[*]}{.name}={.value}{"\n"}{end}' \
| grep -E "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION"Expected output:
AWS_STS_REGIONAL_ENDPOINTS=regional
AWS_DEFAULT_REGION=us-east-1
AWS_REGION=us-east-1
AWS_ROLE_ARN=arn:aws:iam::<account>:role/agentgateway-agentcore-irsa
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
This is the key new feature in v2.3.0 — spec.aws.agentCore:
kubectl apply -f - <<EOF
apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayBackend
metadata:
name: agentcore-backend
namespace: agentgateway-system
spec:
aws:
agentCore:
agentRuntimeArn: "${AGENT_RUNTIME_ARN}"
EOFVerify it's accepted:
kubectl get agentgatewaybackend agentcore-backend -n agentgateway-system \
-o jsonpath='{.status.conditions[0].reason}'
# Expected: AcceptedRoute all A2A traffic to the AgentCore backend:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: agentcore
namespace: agentgateway-system
spec:
parentRefs:
- name: agentgateway
namespace: agentgateway-system
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- group: agentgateway.dev
kind: AgentgatewayBackend
name: agentcore-backend
namespace: agentgateway-system
EOFVerify the route is resolved:
kubectl get httproute agentcore -n agentgateway-system \
-o jsonpath='{.status.parents[0].conditions[?(@.type=="ResolvedRefs")].reason}'
# Expected: ResolvedRefskubectl port-forward -n agentgateway-system svc/agentgateway 8080:8080 &
export GW_URL="http://localhost:8080"export GW_URL="http://$(kubectl get svc agentgateway -n agentgateway-system \
-o jsonpath='{.status.loadBalancer.ingress[0].hostname}'):8080"curl -s -X POST ${GW_URL}/tasks/send \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "test-1",
"method": "tasks/send",
"params": {
"id": "task-1",
"message": {
"role": "user",
"parts": [
{
"type": "text",
"text": "Hello from Agent Gateway! What can you help me with?"
}
]
}
}
}' | jqExpected response:
{
"result": {
"role": "assistant",
"content": [
{
"text": "Hello! I'm here to help you with things like answering questions, writing, analysis, brainstorming, problem-solving, and more. What's on your mind?"
}
]
}
}The request flows directly: curl -> Agent Gateway -> AgentCore (SigV4 signed with bedrock-agentcore). No proxy pod, no extra hop.
With traffic flowing through the gateway, you can layer enterprise policies on the route.
# Create RateLimitConfig
kubectl apply -f - <<EOF
apiVersion: ratelimit.solo.io/v1alpha1
kind: RateLimitConfig
metadata:
name: agentcore-rate-limit
namespace: agentgateway-system
spec:
raw:
descriptors:
- key: generic_key
value: counter
rateLimit:
requestsPerUnit: 5
unit: MINUTE
rateLimits:
- actions:
- genericKey:
descriptorValue: counter
type: REQUEST
---
# Apply rate limit policy to the route
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: agentcore-rate-limit
namespace: agentgateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: agentcore
traffic:
entRateLimit:
global:
rateLimitConfigRefs:
- name: agentcore-rate-limit
namespace: agentgateway-system
EOFTest rate limiting:
for i in $(seq 1 8); do
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST ${GW_URL}/tasks/send \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"'$i'","method":"tasks/send","params":{"id":"t'$i'","message":{"role":"user","parts":[{"type":"text","text":"ping"}]}}}')
echo "Request $i: HTTP $CODE"
doneAfter 5 requests, you should see HTTP 429.
kubectl apply -f - <<EOF
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
name: agentcore-jwt-auth
namespace: agentgateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: agentcore
traffic:
jwtAuthentication:
mode: Strict
providers:
- issuer: "https://your-idp.example.com"
audiences: ["agentcore"]
jwks:
remote:
url: "https://your-idp.example.com/.well-known/jwks.json"
EOFTest without token (expect 401):
curl -s -o /dev/null -w "%{http_code}" -X POST ${GW_URL}/tasks/send \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"tasks/send","params":{"id":"t1","message":{"role":"user","parts":[{"type":"text","text":"test"}]}}}'
# Expected: 401graph TB
subgraph "Before v2.3.0"
direction TB
B1[Agent Gateway] --> B2[A2A Proxy Deployment<br/>Flask + gunicorn + boto3]
B2 --> B3[A2A Proxy Service<br/>appProtocol: kgateway.dev/a2a]
B2 --> B4[Proxy IRSA SA<br/>+ ECR image + Dockerfile]
B2 --> B5[AgentCore]
style B2 fill:#f66,stroke:#333,color:#fff
style B3 fill:#f66,stroke:#333,color:#fff
style B4 fill:#f66,stroke:#333,color:#fff
end
subgraph "v2.3.0"
direction TB
A1[Agent Gateway] --> A2[AgentCore]
A1 -.- A3[AgentgatewayBackend<br/>spec.aws.agentCore]
style A1 fill:#4a9eff,stroke:#333,color:#fff
style A3 fill:#4a9eff,stroke:#333,color:#fff
end
| Component | Before v2.3.0 | v2.3.0+ |
|---|---|---|
| Proxy pod | Required (Flask + boto3 + gunicorn) | Eliminated |
| Container image | Build, push to ECR, maintain | Not needed |
| Extra Service + Deployment | Required in separate namespace | Not needed |
| IRSA setup | On proxy service account | On gateway proxy SA |
| SigV4 signing | boto3 in proxy code | Native in gateway |
| Backend CRD | HTTPRoute -> Service (A2A) | AgentgatewayBackend with spec.aws.agentCore |
| Latency | Extra network hop through proxy | Direct to AgentCore |
| Failure modes | Proxy pod crashes, OOM, scaling | One less thing to break |
# Remove Kubernetes resources
kubectl delete httproute agentcore -n agentgateway-system
kubectl delete agentgatewaybackend agentcore-backend -n agentgateway-system
kubectl delete gateway agentgateway -n agentgateway-system
# Uninstall Helm releases
helm uninstall enterprise-agentgateway -n agentgateway-system
helm uninstall enterprise-agentgateway-crds -n agentgateway-system
kubectl delete ns agentgateway-system
# Remove IAM resources
aws iam detach-role-policy \
--role-name agentgateway-agentcore-irsa \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AgentGatewayAgentCoreAccess
aws iam delete-role --role-name agentgateway-agentcore-irsa
aws iam delete-policy \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AgentGatewayAgentCoreAccess| What | How |
|---|---|
| Gateway version | Enterprise Agent Gateway v2.3.0 |
| Backend type | spec.aws.agentCore with agentRuntimeArn |
| AWS credentials | IRSA on the gateway proxy service account |
| SigV4 signing | Native — service name bedrock-agentcore handled automatically |
| Protocol | A2A (JSON-RPC tasks/send) in, SigV4 HTTPS out |
| Enterprise policies | JWT auth, rate limiting, CORS via EnterpriseAgentgatewayPolicy |
| Proxy pod | Not needed |
- solo-io/agentgateway-enterprise#126 — Feature request that drove this change
- Solo Agent Gateway Docs
- AWS Bedrock AgentCore Documentation