The agent gateway for Kubernetes.
Every agent team rebuilds the same auth, rate limiting, and credential management from scratch. This operator eliminates that — turning Gateway API + MCP Gateway + Kuadrant into a unified agent gateway with a clean separation of concerns:
- AI Engineers build their agent, push the image, and add a label to their Deployment manifest. That's it — no CRDs, no gateway config, no auth code. GitOps handles the rest.
- Platform Engineers own the GitOps pipeline, define security policies per tier, and manage the cluster. One
AgentPolicyCR covers auth, credentials, rate limits, and MCP tool access.
The operator auto-discovers agents from their live endpoints, then generates HTTPRoutes, AuthPolicies, RateLimitPolicies, MCPServerRegistrations, and sidecar configs automatically. Every call — inbound, agent-to-agent, MCP, and outbound — goes through an enforcement point. Agents ship with business logic only.
Status: proof-of-concept. APIs will change.
CRD installation, AgentCard/AgentPolicy creation, controller reconciliation, generated resource inspection, and cascade deletion — all on a live OpenShift cluster with Gateway API and Kuadrant.
The three layers of enforcement: inbound JWT auth via Authorino, per-tier rate limiting via Limitador, and outbound credential management (Vault, token exchange, default deny) via the sidecar proxy.
The controller watches two CRDs and generates all downstream infrastructure:
- An AgentCard represents an agent's identity: description, protocols, skills, and service port. AgentCards are discovered automatically from running agents — not written by developers. A2A agents expose
/.well-known/agent.json, MCP agents exposetools/list, and the discovery controller creates the AgentCard CR from this metadata. - An AgentPolicy selects one or more AgentCards by label, then declares ingress rules, agent-to-agent permissions, MCP tool federation, external credential policies, and rate limits.
- The controller watches both resources and generates the downstream Kubernetes objects that enforce the policy.
| Persona | Responsibility | Touches |
|---|---|---|
| AI Engineer | Builds the agent, pushes the image, adds kagenti.com/agent: "true" label to the Deployment manifest, and commits to git. Writes business logic only. |
Agent code, Dockerfile, Deployment YAML |
| Platform Engineer | Owns the GitOps pipeline (ArgoCD/Flux), creates AgentPolicy CRs per tier, and manages the cluster infrastructure. |
AgentPolicy CR, GitOps config, cluster setup |
| GitOps | Syncs manifests from git to the cluster. Deploys the agent when the AI Engineer's commit merges. | Deployment, Service (from git) |
| Discovery Controller | Detects labeled agents, fetches their agent card / capabilities, and creates the AgentCard CR automatically. |
AgentCard CR (auto-generated) |
| Policy Controller | Generates all downstream enforcement resources from the AgentPolicy + AgentCard pairing. | HTTPRoute, AuthPolicy, RateLimitPolicy, ConfigMap, MCPServerRegistration |
Gateway API + MCP Gateway + this operator form a unified agent gateway handling every call pattern:
| Call pattern | Path | Enforced by |
|---|---|---|
| User → Agent | Client → Gateway → Agent | Authorino (JWT) + Limitador |
| Agent → Agent (A2A) | Agent A → Gateway → Agent B | AuthPolicy (allowedAgents) |
| Agent → MCP Tools | Agent → Gateway → MCP Gateway → MCP Server | MCPVirtualServer (tool filtering) |
| Agent → External API | Agent → Sidecar (vault/exchange) → External | Forward proxy + credential injection |
| Agent → Blocked Host | NetworkPolicy (network) + Sidecar (app) | NetworkPolicy primary, sidecar defense-in-depth |
The MCP Gateway aggregates tools from multiple MCP servers into a single /mcp endpoint. The operator auto-creates MCPServerRegistration resources when agents declare the mcp protocol, and AgentPolicy.mcpTools.virtualServerRef controls which tools each tier can access.
Outbound access uses two layers of enforcement:
- NetworkPolicy (primary): When
external.defaultModeisdeny, the operator generates a Kubernetes NetworkPolicy that blocks all egress except DNS and the cluster gateway. This is kernel-level enforcement -- the pod cannot bypass it. - Sidecar proxy (defense-in-depth): The sidecar handles per-host credential injection (vault, exchange, passthrough) and hostname-level routing. NetworkPolicy only supports CIDR, not hostnames, so the sidecar provides the fine-grained per-host logic.
Both are needed. NetworkPolicy alone can't inject credentials. The sidecar alone can be bypassed by a process making direct outbound connections.
allowedAgents in the IngressPolicy references Kubernetes ServiceAccounts, not free-form names. The operator resolves short names (e.g., orchestrator) to system:serviceaccount:{namespace}:orchestrator for JWT-based identity matching via Authorino. Cross-namespace references use namespace/name format. This maps directly to SPIFFE IDs if you adopt SPIRE/Istio later -- zero migration needed.
Without this operator, securing 20 agents means manually creating and maintaining 100+ resources (HTTPRoutes, AuthPolicies, RateLimitPolicies, ConfigMaps, NetworkPolicies). With it, you write one AgentPolicy per tier. New agents inherit the matching policy automatically via labels. Every agent in a tier gets the same security posture -- no drift between manually maintained resources. The tradeoff: if you need per-agent customization beyond what the CRD exposes, you drop down to the underlying resources directly.
| Requirement | Version | Notes |
|---|---|---|
| Go | 1.24+ | Build dependency |
| kubectl | any recent | Cluster access |
| Kubernetes cluster | 1.28+ | Gateway API v1 support |
| Gateway API CRDs | v1.2+ | kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml |
| Kuadrant | (optional) | AuthPolicy / RateLimitPolicy enforcement |
| MCP Gateway | (optional) | MCP tool federation via MCPServerRegistration |
# Clone the repo
git clone https://github.com/agentoperations/agent-access-control.git
cd agent-access-control
# Build the controller binary
make build
# Generate and install CRDs into the cluster
make install
# Run the controller locally (connects to current kubeconfig context)
make run -- --gateway-name=my-gateway --gateway-namespace=default
# In another terminal, apply the sample resources
kubectl apply -f config/samples/
# Verify generated resources
kubectl get agentcards
kubectl get agentpolicies
kubectl get httproutes -l kagenti.com/managed-by=agent-access-control
kubectl get configmaps -l kagenti.com/managed-by=agent-access-control# Build and vet
make build
go vet ./...
# Run tests
make test# Create a kind cluster
kind create cluster --name agent-test
# Install Gateway API CRDs
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml
# Install agent-access-control CRDs
make install
# Verify CRDs are registered
kubectl get crd agentcards.kagenti.com
kubectl get crd agentpolicies.kagenti.com
# Run controller locally
make run -- --gateway-name=test-gateway --gateway-namespace=default &
# Create a dummy service (simulates an agent)
kubectl create deployment weather-agent --image=nginx --port=8080
kubectl expose deployment weather-agent --name=weather-agent-svc --port=8080
# Apply sample AgentCard
kubectl apply -f config/samples/agentcard-weather.yaml
# Check: HTTPRoute should be generated
kubectl get httproutes -l kagenti.com/agent-card=weather-agent
kubectl get agentcard weather-agent -o yaml # check status.conditions
# Apply sample AgentPolicy
kubectl apply -f config/samples/agentpolicy-standard.yaml
# Check: ConfigMap should be generated (AuthPolicy/RateLimitPolicy skipped without Kuadrant)
kubectl get configmaps -l kagenti.com/managed-by=agent-access-control
kubectl get agentpolicy standard-tier -o yaml # check status
# Inspect the sidecar config
kubectl get configmap sidecar-config-weather-agent -o yaml
# Cleanup
kubectl delete -f config/samples/
kind delete cluster --name agent-testIf Kuadrant is installed on the cluster, the controller also generates AuthPolicy and RateLimitPolicy resources:
# Verify Kuadrant CRDs exist
kubectl get crd authpolicies.kuadrant.io
kubectl get crd ratelimitpolicies.kuadrant.io
# After applying samples, check generated policies
kubectl get authpolicies -l kagenti.com/managed-by=agent-access-control
kubectl get ratelimitpolicies -l kagenti.com/managed-by=agent-access-controlWithout Kuadrant, the controller logs a warning and sets a status condition -- it does not crash.
# Build the image (default: quay.io/azaalouk/agent-access-control:latest)
make docker-build
# Or with a custom image name
make docker-build IMG=quay.io/myorg/agent-access-control:v0.1.0
# Push to registry
make docker-push# Install CRDs + RBAC + controller deployment
make deployThis creates:
- Namespace
agent-access-control-system - ServiceAccount, ClusterRole, and ClusterRoleBinding
- Controller Deployment with health probes, resource limits, and security context
Edit the gateway name and namespace in the deployment before applying:
# Set your gateway name and namespace
kubectl set env deployment/agent-access-controller \
GATEWAY_NAME=my-gateway \
GATEWAY_NAMESPACE=my-namespace \
-n agent-access-control-systemOr edit deploy/manager.yaml directly before deploying.
kubectl get pods -n agent-access-control-system
kubectl logs -n agent-access-control-system deployment/agent-access-controllermake undeploy # Remove controller, RBAC, namespace
make uninstall # Remove CRDs (deletes all AgentCards and AgentPolicies)Created by: Discovery Controller (auto-generated from agent endpoints). AI Engineers do not write this resource.
# Auto-generated by the discovery controller from /.well-known/agent.json or /tools/list
apiVersion: kagenti.com/v1alpha1
kind: AgentCard
metadata:
name: my-agent
labels:
tier: standard # inherited from pod labels, used by AgentPolicy selector
spec:
description: "What this agent does"
protocols: [a2a, rest] # a2a, mcp, rest
skills:
- name: skill-name
description: "What this skill does"
servicePort: 8080 # default 8080, convention: Service = {name}-svc| Field | Type | Required | Description |
|---|---|---|---|
spec.description |
string |
No | Human-readable description |
spec.protocols |
[]string |
Yes (min 1) | a2a, mcp, rest |
spec.skills |
[]AgentSkill |
No | Agent capabilities |
spec.servicePort |
int32 |
No | Service port (default 8080) |
Convention: The agent's Kubernetes Service must be named {agentcard-name}-svc.
Created by: Platform Engineer. This is the only CRD a human writes.
apiVersion: kagenti.com/v1alpha1
kind: AgentPolicy
metadata:
name: my-policy
spec:
agentSelector:
matchLabels:
tier: standard # selects AgentCards with this label
ingress: # who can call these agents (gateway enforces)
allowedAgents: [orchestrator]
allowedUsers: ["*"]
agents: [summarizer] # which agents these can call (gateway enforces)
mcpTools: # MCP tool filtering (MCP Gateway enforces)
virtualServerRef: my-vs
external: # outbound to external APIs (sidecar enforces)
defaultMode: deny
rules:
- host: api.example.com
mode: vault
vaultPath: secret/data/api-key
header: Authorization
headerPrefix: "Bearer "
rateLimit: # rate limiting (gateway enforces)
requestsPerMinute: 100| Field | Type | Required | Description |
|---|---|---|---|
spec.agentSelector.matchLabels |
map[string]string |
Yes | Selects AgentCards by label |
spec.ingress.allowedAgents |
[]string |
No | ServiceAccount names permitted to call (short or namespace/name) |
spec.ingress.allowedUsers |
[]string |
No | Users permitted to call (* = any) |
spec.agents |
[]string |
No | Outbound agent-to-agent permissions |
spec.mcpTools.virtualServerRef |
string |
No | MCPVirtualServer name |
spec.external.defaultMode |
string |
No | Default: deny |
spec.external.rules[].host |
string |
Yes | Target hostname |
spec.external.rules[].mode |
string |
Yes | vault, exchange, passthrough, deny |
spec.external.rules[].vaultPath |
string |
No | Vault secret path |
spec.external.rules[].audience |
string |
No | Token exchange audience |
spec.external.rules[].scopes |
[]string |
No | Token exchange scopes |
spec.external.rules[].header |
string |
No | Default: Authorization |
spec.external.rules[].headerPrefix |
string |
No | Default: Bearer |
spec.rateLimit.requestsPerMinute |
int |
No | Max requests/min |
| Input | Generated Resource | Purpose |
|---|---|---|
| AgentCard | HTTPRoute |
Routes traffic to the agent through the Gateway |
| AgentCard (protocol=mcp) | MCPServerRegistration |
Registers agent as MCP server with MCP Gateway |
AgentPolicy .ingress |
AuthPolicy |
Inbound auth enforcement (requires Kuadrant) |
AgentPolicy .rateLimit |
RateLimitPolicy |
Rate limit enforcement (requires Kuadrant) |
AgentPolicy .external |
ConfigMap |
Sidecar forward proxy credential config |
AgentPolicy .external (defaultMode=deny) |
NetworkPolicy |
Deny-all egress + allow DNS + allow gateway |
All generated resources are labeled kagenti.com/managed-by: agent-access-control and have owner references for automatic cleanup.
agent-access-control/
├── cmd/main.go # Controller entry point
├── api/v1alpha1/
│ ├── agentcard_types.go # AgentCard CRD
│ ├── agentpolicy_types.go # AgentPolicy CRD
│ ├── groupversion_info.go # Scheme registration
│ └── zz_generated.deepcopy.go # Generated
├── internal/controller/
│ ├── agentcard_controller.go # AgentCard reconciler
│ ├── agentpolicy_controller.go # AgentPolicy reconciler
│ ├── builders.go # Resource builder functions
│ └── builders_test.go # Unit tests for builders
├── config/
│ ├── crd/bases/ # Generated CRD YAML
│ └── samples/ # Example CRs
├── deploy/ # Kubernetes deployment manifests
│ ├── manager.yaml # Controller Deployment
│ ├── namespace.yaml # Controller namespace
│ ├── role.yaml # ClusterRole
│ ├── role_binding.yaml # ClusterRoleBinding
│ └── service_account.yaml # ServiceAccount
├── hack/
│ ├── demo.mp4 # Demo: setup & lifecycle
│ ├── demo-access-control.mp4 # Demo: access control enforcement
│ └── demo-setup.sh # Demo environment setup script
├── docs/
│ ├── auth-concepts.md # Auth flows with sequence diagrams
│ ├── demo-walkthrough.md # Step-by-step demo with ADR comparison
│ └── images/ # Architecture and sequence diagrams
├── Makefile
├── Dockerfile
├── LICENSE # Apache License 2.0
└── go.mod
This project and Agent Registry solve different problems at different stages:
| Agent Registry | Agent Access Control | |
|---|---|---|
| When | Build-time | Runtime |
| Discovery | Static — LLM reads source code | Runtime — probes live agent endpoints |
| Produces | BOM, eval records, promotion lifecycle | Auth, routing, rate limits, credentials |
| Answers | What does this agent depend on? | What can this agent do right now? |
The registry knows the agent's dependency graph (models, tools, skills) at build time. Access Control enforces what it can actually reach at runtime. Together: full lifecycle governance.
| Document | Description |
|---|---|
| Presentation | Slide deck: architecture, personas, agent gateway, MCP integration |
| Auth Concepts | Auth components, token types, and all four auth flows with sequence diagrams |
| Demo Walkthrough | Step-by-step demo with ADR comparison |
| Setup & Lifecycle Demo | Video: CRD install, AgentCards, AgentPolicies, generated resources, cascade deletion |
| Access Control Demo | Video: Authorino auth, Limitador rate limiting, sidecar credential management |
This project is licensed under the Apache License 2.0.


