Skip to content

Instantly share code, notes, and snippets.

@rvennam
Created February 19, 2026 20:54
Show Gist options
  • Select an option

  • Save rvennam/9c4291ad9deede222f8350db3c5f3312 to your computer and use it in GitHub Desktop.

Select an option

Save rvennam/9c4291ad9deede222f8350db3c5f3312 to your computer and use it in GitHub Desktop.
Workshop: Routing to AWS AgentCore agents through Solo Agent Gateway (IRSA/SigV4)

Workshop: Invoking AWS AgentCore Agents Through Solo Agent Gateway (A2A) — EKS with IRSA

Overview

In this workshop you will:

  1. Configure IRSA (IAM Roles for Service Accounts) so the A2A proxy can call AgentCore without static AWS credentials
  2. Deploy a thin A2A proxy that wraps an AWS Bedrock AgentCore runtime agent
  3. Route traffic to it through Solo Agent Gateway using the A2A protocol
  4. Apply enterprise policies (JWT auth, rate limiting) via EnterpriseAgentgatewayPolicy

Architecture

┌──────────────────────────────────────────────────────────────┐
│                        EKS Cluster                           │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │         Solo Agent Gateway (enterprise-agentgateway)   │  │
│  │                                                        │  │
│  │  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐   │  │
│  │  │   JWT Auth   │ │ Rate Limiter │ │     CORS     │   │  │
│  │  │   (policy)   │ │   (policy)   │ │   (policy)   │   │  │
│  │  └──────┬───────┘ └──────┬───────┘ └──────┬───────┘   │  │
│  │         └────────────────┼────────────────┘            │  │
│  │                          │                             │  │
│  │                   ┌──────┴───────┐                     │  │
│  │                   │  HTTPRoute   │                     │  │
│  │                   │ (agentcore)  │                     │  │
│  │                   └──────┬───────┘                     │  │
│  │                          │  OTel metrics + traces      │  │
│  └──────────────────────────┼─────────────────────────────┘  │
│                             │                                │
│  ┌──────────────────────────┼──────── ns: agentcore ──────┐  │
│  │                          ▼                             │  │
│  │  ┌────────────────────────────────────────────────┐    │  │
│  │  │       A2A Proxy (pre-built image / gunicorn)   │    │  │
│  │  │                                                │    │  │
│  │  │  /.well-known/agent.json → Agent Card          │    │  │
│  │  │  POST / (tasks/send)     → InvokeAgentRuntime  │    │  │
│  │  │                                                │    │  │
│  │  │  serviceAccountName: agentcore-proxy           │    │  │
│  │  │  (IRSA — no static AWS creds)                  │    │  │
│  │  │                                                │    │  │
│  │  │  Service: appProtocol: kgateway.dev/a2a        │    │  │
│  │  └────────────────────┬───────────────────────────┘    │  │
│  └───────────────────────┼────────────────────────────────┘  │
│                          │                                   │
└──────────────────────────┼───────────────────────────────────┘
                           │  SigV4-signed HTTPS (via IRSA)
                           ▼
            ┌──────────────────────────────┐
            │    AWS Bedrock AgentCore     │
            │    InvokeAgentRuntime API    │
            │                              │
            │  ┌────────────────────────┐  │
            │  │   Your Agent Runtime   │  │
            │  │   (Claude-powered)     │  │
            │  └────────────────────────┘  │
            └──────────────────────────────┘

Why IRSA instead of static credentials?

Concern Static credentials IRSA
Credential type Long-lived access keys (or short-lived SSO tokens) Temporary STS credentials, auto-refreshed
Rotation Manual (SSO tokens expire every 1-12 hours) Automatic — EKS injects fresh tokens
Blast radius Any pod with Secret access Single service account only
Secret management K8s Secret with AWS_ACCESS_KEY_ID, etc. No secrets to manage
Code changes None None — boto3 picks up IRSA automatically

Why an A2A proxy?

AgentCore's InvokeAgentRuntime API requires AWS SigV4 signing, which Agent Gateway doesn't support natively for the bedrock-agentcore service. The proxy handles SigV4 via boto3 (using IRSA credentials), while exposing a standard A2A interface (tasks/send) that Agent Gateway can route, observe, and apply policies to.


Prerequisites

  • An EKS cluster with Solo Enterprise Agent Gateway installed
  • kubectl, aws, and eksctl CLI tools installed and configured
  • AWS IAM permissions to create roles and OIDC providers
  • An AgentCore runtime agent deployed (you'll need the ARN)
  • A container registry (ECR) to push the proxy image

Step 1: Set variables

# Your AgentCore agent ARN (without the /runtime-endpoint/DEFAULT suffix)
export AGENT_RUNTIME_ARN="arn:aws:bedrock-agentcore:us-east-1:986112284769:runtime/rvennam_agent-tEJ8OxEBo1"
export AWS_REGION="us-east-1"
export CLUSTER_NAME="<your-eks-cluster-name>"

# Auto-detect account ID
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# Agent Gateway details (adjust to your cluster)
export GATEWAY_NAME="agentgateway"
export GATEWAY_NS="enterprise-agentgateway"

# Get the gateway external address
export GW_ADDR=$(kubectl get svc -n $GATEWAY_NS $GATEWAY_NAME \
  -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
export GW_PORT=$(kubectl get svc -n $GATEWAY_NS $GATEWAY_NAME \
  -o jsonpath="{.spec.ports[0].port}")
echo "Gateway: http://${GW_ADDR}:${GW_PORT}"
echo "Account: ${ACCOUNT_ID}"

Step 2: Configure IRSA for the A2A proxy

IRSA lets the proxy pod assume an IAM role directly. boto3 picks up the credentials automatically — no static keys, no secret rotation.

2a: Associate the EKS OIDC provider with IAM

eksctl utils associate-iam-oidc-provider \
  --cluster $CLUSTER_NAME \
  --approve

2b: Get the OIDC provider ID

export OIDC_ISSUER=$(aws eks describe-cluster \
  --name $CLUSTER_NAME \
  --query "cluster.identity.oidc.issuer" \
  --output text)

export OIDC_ID=$(echo $OIDC_ISSUER | sed 's|.*/id/||')

echo "OIDC Issuer: $OIDC_ISSUER"
echo "OIDC ID: $OIDC_ID"

2c: Create the IAM policy for AgentCore access

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 AgentCoreProxyAccess \
  --policy-document file:///tmp/agentcore-policy.json \
  --query 'Policy.Arn' \
  --output text

2d: Create the IRSA service account

This creates a Kubernetes service account annotated with an IAM role that only the agentcore-proxy SA in the agentcore namespace can assume.

kubectl create ns agentcore

eksctl create iamserviceaccount \
  --name agentcore-proxy \
  --namespace agentcore \
  --cluster $CLUSTER_NAME \
  --attach-policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AgentCoreProxyAccess \
  --approve
Manual alternative (if not using eksctl)

Create the trust policy:

cat > /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:agentcore:agentcore-proxy",
          "oidc.eks.${AWS_REGION}.amazonaws.com/id/${OIDC_ID}:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}
EOF

aws iam create-role \
  --role-name AgentCoreProxyIRSA \
  --assume-role-policy-document file:///tmp/trust-policy.json \
  --description "IRSA role for AgentCore A2A proxy"

aws iam attach-role-policy \
  --role-name AgentCoreProxyIRSA \
  --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AgentCoreProxyAccess

export ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/AgentCoreProxyIRSA"

kubectl create ns agentcore
kubectl create sa agentcore-proxy -n agentcore
kubectl annotate sa agentcore-proxy -n agentcore \
  eks.amazonaws.com/role-arn=$ROLE_ARN

2e: Verify the service account annotation

kubectl get sa agentcore-proxy -n agentcore \
  -o jsonpath='{.metadata.annotations}' | python3 -m json.tool

You should see eks.amazonaws.com/role-arn pointing to your IAM role.

Step 3: Build and push the A2A proxy image

The proxy code is a Flask app that:

  • Serves an A2A Agent Card at /.well-known/agent.json
  • Handles tasks/send JSON-RPC requests
  • Translates them to AgentCore InvokeAgentRuntime API calls with SigV4
  • Returns A2A-formatted responses

Build and push to ECR

# Create the ECR repository
aws ecr create-repository \
  --repository-name agentcore-a2a-proxy \
  --region $AWS_REGION 2>/dev/null || true

# Build and push (from the agentcore-mcp-bridge/ directory)
cd agentcore-mcp-bridge/
docker build --platform linux/amd64 -t ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/agentcore-a2a-proxy:latest .

aws ecr get-login-password --region $AWS_REGION | \
  docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com

docker push ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/agentcore-a2a-proxy:latest
cd -

Step 4: Deploy the A2A proxy

Note that:

  • serviceAccountName: agentcore-proxy — the IRSA-annotated SA from Step 2
  • No AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, or AWS_SESSION_TOKEN env vars
  • No ConfigMap volume — the code is baked into the container image
  • gunicorn on port 8000 (production-ready)
kubectl apply -f - <<EOF
# --- Deployment ---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: agentcore-a2a-proxy
  namespace: agentcore
  labels:
    app: agentcore-a2a-proxy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: agentcore-a2a-proxy
  template:
    metadata:
      labels:
        app: agentcore-a2a-proxy
    spec:
      serviceAccountName: agentcore-proxy
      containers:
        - name: proxy
          image: ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/agentcore-a2a-proxy:latest
          ports:
            - containerPort: 8000
          env:
            - name: AGENT_RUNTIME_ARN
              value: "${AGENT_RUNTIME_ARN}"
            - name: AWS_REGION
              value: "${AWS_REGION}"
            - name: AGENT_NAME
              value: "AgentCore Agent"
            - name: AGENT_DESCRIPTION
              value: "AI assistant powered by AWS Bedrock AgentCore"
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 3
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            periodSeconds: 30
---
# --- Service with A2A appProtocol ---
apiVersion: v1
kind: Service
metadata:
  name: agentcore-a2a-proxy
  namespace: agentcore
  labels:
    app: agentcore-a2a-proxy
spec:
  selector:
    app: agentcore-a2a-proxy
  ports:
    - protocol: TCP
      port: 8000
      targetPort: 8000
      appProtocol: kgateway.dev/a2a
  type: ClusterIP
---
# --- HTTPRoute ---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: agentcore
  namespace: agentcore
spec:
  parentRefs:
    - name: ${GATEWAY_NAME}
      namespace: ${GATEWAY_NS}
  rules:
    - backendRefs:
        - name: agentcore-a2a-proxy
          port: 8000
EOF

Wait for the pods to be ready

kubectl wait --for=condition=ready pod -l app=agentcore-a2a-proxy \
  -n agentcore --timeout=120s

Verify IRSA injection

Confirm the EKS webhook injected credentials into the pod:

POD=$(kubectl get pods -n agentcore -l app=agentcore-a2a-proxy \
  -o jsonpath='{.items[0].metadata.name}')

echo "=== IRSA Environment Variables ==="
kubectl get pod $POD -n agentcore \
  -o jsonpath='{range .spec.containers[0].env[*]}{.name}={.value}{"\n"}{end}' \
  | grep -E "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE"

echo ""
echo "=== Projected Token Volume ==="
kubectl get pod $POD -n agentcore \
  -o jsonpath='{.spec.volumes[?(@.name=="aws-iam-token")]}' | python3 -m json.tool

You should see:

  • AWS_ROLE_ARN set to your IAM role ARN
  • AWS_WEB_IDENTITY_TOKEN_FILE set to /var/run/secrets/eks.amazonaws.com/serviceaccount/token
  • A projected volume named aws-iam-token

Verify the HTTPRoute is accepted

kubectl describe httproute agentcore -n agentcore

You should see Reason: Accepted and Status: True.

Step 5: Test through Agent Gateway

Fetch the Agent Card

curl -s http://${GW_ADDR}:${GW_PORT}/.well-known/agent.json | jq

Expected output:

{
  "name": "AgentCore Agent",
  "description": "AI assistant powered by AWS Bedrock AgentCore",
  "skills": [
    {
      "id": "chat",
      "name": "Chat",
      "description": "Have a conversation with the AgentCore agent."
    }
  ],
  "version": "1.0.0"
}

Send an A2A task (invokes the real AgentCore agent)

curl -s -X POST http://${GW_ADDR}:${GW_PORT}/ \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "tasks/send",
    "params": {
      "id": "task-1",
      "message": {
        "role": "user",
        "parts": [{"type": "text", "text": "Hello from Agent Gateway!"}]
      }
    }
  }' | jq

The request flows: curl -> Agent Gateway -> A2A proxy (IRSA) -> AgentCore -> Claude

Step 6: Apply enterprise policies

Now that traffic is flowing through Agent Gateway, you can apply EnterpriseAgentgatewayPolicy resources to the HTTPRoute.

6a: JWT Authentication

Require a valid JWT on every request. Replace the issuer/JWKS with your IdP.

kubectl apply -f - <<EOF
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
  name: agentcore-jwt-auth
  namespace: agentcore
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: agentcore
  traffic:
    jwtAuthentication:
      mode: Strict
      providers:
      - issuer: "\${KEYCLOAK_ISSUER}"
        audiences: ["agentcore"]
        jwks:
          remote:
            jwksPath: "\${KEYCLOAK_JWKS_PATH}"
            backendRef:
              name: keycloak
              namespace: keycloak
              kind: Service
              port: 8080
EOF

Test without a token (should get 401):

curl -v -X POST http://${GW_ADDR}:${GW_PORT}/ \
  -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: HTTP 401 Unauthorized

Test with a valid token (should get 200):

TOKEN=$(curl -s -X POST "${KEYCLOAK_ISSUER}/protocol/openid-connect/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=agentcore-client" \
  -d "client_secret=${CLIENT_SECRET}" | jq -r .access_token)

curl -s -X POST http://${GW_ADDR}:${GW_PORT}/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"jsonrpc":"2.0","id":"1","method":"tasks/send","params":{"id":"t1","message":{"role":"user","parts":[{"type":"text","text":"Hello with auth!"}]}}}' | jq

6b: Rate Limiting

Step 1: Create the RateLimitConfig in the gateway's namespace:

kubectl apply -f - <<EOF
apiVersion: ratelimit.solo.io/v1alpha1
kind: RateLimitConfig
metadata:
  name: agentcore-rate-limit
  namespace: ${GATEWAY_NS}
spec:
  raw:
    descriptors:
    - key: generic_key
      value: counter
      rateLimit:
        requestsPerUnit: 3
        unit: MINUTE
    rateLimits:
    - actions:
      - genericKey:
          descriptorValue: counter
      type: REQUEST
EOF

Step 2: Create the EnterpriseAgentgatewayPolicy targeting the HTTPRoute:

kubectl apply -f - <<EOF
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
  name: agentcore-rate-limit
  namespace: agentcore
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: agentcore
  traffic:
    entRateLimit:
      global:
        rateLimitConfigRefs:
        - name: agentcore-rate-limit
          namespace: ${GATEWAY_NS}
EOF

Test rate limiting (send 6 requests — the first ~3 succeed, then 429):

for i in $(seq 1 6); do
  CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST http://${GW_ADDR}:${GW_PORT}/ \
    -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"
done

6c: CORS (for browser-based clients)

kubectl apply -f - <<EOF
apiVersion: enterpriseagentgateway.solo.io/v1alpha1
kind: EnterpriseAgentgatewayPolicy
metadata:
  name: agentcore-cors
  namespace: agentcore
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: agentcore
  traffic:
    cors:
      allowOrigins:
      - exact: "https://your-app.example.com"
      allowMethods:
      - POST
      - GET
      - OPTIONS
      allowHeaders:
      - Content-Type
      - Authorization
      maxAge: 86400s
EOF

Step 7: Observability

If your cluster has the OTel stack deployed (Prometheus + Grafana + Tempo), Agent Gateway automatically exports metrics and traces for all traffic flowing through it, including the A2A requests to your AgentCore agent.

Check proxy metrics

# Port-forward to Grafana
kubectl port-forward svc/grafana-prometheus-grafana -n monitoring 3000:80 &

# Open http://localhost:3000 and look for agentgateway dashboards

Check proxy logs

kubectl logs -n agentcore -l app=agentcore-a2a-proxy -f

Cleanup

# Remove Kubernetes resources
kubectl delete ns agentcore

# Remove rate limit config from gateway namespace
kubectl delete ratelimitconfig agentcore-rate-limit -n ${GATEWAY_NS} 2>/dev/null

# Remove IRSA service account and IAM role
eksctl delete iamserviceaccount \
  --name agentcore-proxy \
  --namespace agentcore \
  --cluster $CLUSTER_NAME

# Remove the IAM policy
aws iam delete-policy \
  --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AgentCoreProxyAccess

# Remove the ECR repository (optional)
aws ecr delete-repository \
  --repository-name agentcore-a2a-proxy \
  --region $AWS_REGION \
  --force

Summary

What How
AWS credentials IRSA — automatic, short-lived, no secrets
AgentCore agent invocation boto3 invoke_agent_runtime() with SigV4 (credentials from IRSA)
Container image Pre-built from ECR with gunicorn
Protocol to Agent Gateway A2A (JSON-RPC tasks/send) via appProtocol: kgateway.dev/a2a
Gateway routing HTTPRoute -> Service (A2A)
JWT authentication EnterpriseAgentgatewayPolicy traffic.jwtAuthentication
Rate limiting RateLimitConfig + EnterpriseAgentgatewayPolicy traffic.entRateLimit
CORS EnterpriseAgentgatewayPolicy traffic.cors
Observability Built-in OTel metrics/traces through Agent Gateway
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment