Skip to content

Commit ed93135

Browse files
feat(examples): cross-namespace agent-to-agent communication with production security governance
Adds a complete worked example proving A2A routing between agents in different Kubernetes namespaces via the kagent Agent Gateway, with a layered security governance stack for production use. Scenario: team-alpha/orchestrator delegates to team-beta/specialist. Files: - 00-namespaces.yaml — consumer/provider labels driving AllowedNamespaces - 01-rbac.yaml — least-privilege ServiceAccounts per namespace - 02-network-policy.yaml — default-deny + kagent-only ingress/egress - 03-secrets.yaml — API keys + delegation tokens (replace before apply) - 04-model-configs.yaml — ModelConfig CRDs per namespace - 05-specialist-agent.yaml — target agent with allowedNamespaces Selector - 06-orchestrator-agent.yaml — source agent with cross-ns tool + headersFrom - 07-verify.sh — smoke test: routing, delegation, rejection proofs - 08-istio-authz.yaml — PeerAuthentication (mTLS STRICT) + AuthorizationPolicy - 09-pod-security.yaml — Pod Security Admission restricted profile - 10-gatekeeper-policy.yaml — OPA ConstraintTemplate + Constraint - 11-cert-manager.yaml — Certificate resources for agent mTLS - 12-external-secrets.yaml — Vault-backed ExternalSecret for key rotation - 13-audit-policy.yaml — K8s audit policy targeting kagent CRDs + Secrets Security layers enforced: 1. OPA Gatekeeper — admission webhook blocks non-consumer namespace refs 2. kagent reconciler — AllowedNamespaces selector (Gateway API pattern) 3. NetworkPolicy + Istio mTLS — default-deny, SPIFFE identity enforcement 4. RBAC — agents read only own-namespace Secrets 5. headersFrom + ESO — delegation token injected, rotated from Vault daily 6. K8s audit policy — RequestResponse on CRD mutations, SIEM-ready Known gaps documented: UnsecureAuthenticator + NoopAuthorizer are dev defaults; roadmap to EP-476 OIDC/JWT and Vault PKI issuer included in README. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e462260 commit ed93135

15 files changed

Lines changed: 1136 additions & 0 deletions
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Two namespaces on the same cluster. Labels drive the AllowedNamespaces
2+
# selector on the specialist — only namespaces with team=alpha can reference it.
3+
apiVersion: v1
4+
kind: Namespace
5+
metadata:
6+
name: team-alpha
7+
labels:
8+
team: alpha
9+
kagent.dev/agent-consumer: "true"
10+
---
11+
apiVersion: v1
12+
kind: Namespace
13+
metadata:
14+
name: team-beta
15+
labels:
16+
team: beta
17+
kagent.dev/agent-provider: "true"
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# ServiceAccounts for each agent. Agents run with least-privilege:
2+
# they can only read Secrets in their own namespace.
3+
# The kagent controller itself has cluster-scoped Agent/ModelConfig read
4+
# (granted by the helm chart) — these bindings are agent-runtime only.
5+
6+
# --- team-beta: specialist ---
7+
apiVersion: v1
8+
kind: ServiceAccount
9+
metadata:
10+
name: specialist-agent
11+
namespace: team-beta
12+
---
13+
# Specialist can read its own secrets (for model API key resolution)
14+
apiVersion: rbac.authorization.k8s.io/v1
15+
kind: Role
16+
metadata:
17+
name: specialist-agent
18+
namespace: team-beta
19+
rules:
20+
- apiGroups: [""]
21+
resources: ["secrets", "configmaps"]
22+
verbs: ["get", "list"]
23+
- apiGroups: ["kagent.dev"]
24+
resources: ["agents", "modelconfigs"]
25+
verbs: ["get", "list", "watch"]
26+
---
27+
apiVersion: rbac.authorization.k8s.io/v1
28+
kind: RoleBinding
29+
metadata:
30+
name: specialist-agent
31+
namespace: team-beta
32+
subjects:
33+
- kind: ServiceAccount
34+
name: specialist-agent
35+
namespace: team-beta
36+
roleRef:
37+
kind: Role
38+
name: specialist-agent
39+
apiGroup: rbac.authorization.k8s.io
40+
41+
# --- team-alpha: orchestrator ---
42+
apiVersion: v1
43+
kind: ServiceAccount
44+
metadata:
45+
name: orchestrator-agent
46+
namespace: team-alpha
47+
---
48+
# Orchestrator can read its own secrets. Critically, it CANNOT read
49+
# team-beta secrets — cross-namespace Secret access is not granted.
50+
apiVersion: rbac.authorization.k8s.io/v1
51+
kind: Role
52+
metadata:
53+
name: orchestrator-agent
54+
namespace: team-alpha
55+
rules:
56+
- apiGroups: [""]
57+
resources: ["secrets", "configmaps"]
58+
verbs: ["get", "list"]
59+
- apiGroups: ["kagent.dev"]
60+
resources: ["agents", "modelconfigs"]
61+
verbs: ["get", "list", "watch"]
62+
---
63+
apiVersion: rbac.authorization.k8s.io/v1
64+
kind: RoleBinding
65+
metadata:
66+
name: orchestrator-agent
67+
namespace: team-alpha
68+
subjects:
69+
- kind: ServiceAccount
70+
name: orchestrator-agent
71+
namespace: team-alpha
72+
roleRef:
73+
kind: Role
74+
name: orchestrator-agent
75+
apiGroup: rbac.authorization.k8s.io
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# NetworkPolicies: default-deny then allow only the kagent controller
2+
# to reach agent pods. Agents cannot call each other directly — all A2A
3+
# traffic routes through the gateway (controller service on port 8083).
4+
5+
# --- team-beta: deny all ingress, allow only from kagent controller ---
6+
apiVersion: networking.k8s.io/v1
7+
kind: NetworkPolicy
8+
metadata:
9+
name: default-deny-ingress
10+
namespace: team-beta
11+
spec:
12+
podSelector: {}
13+
policyTypes:
14+
- Ingress
15+
---
16+
apiVersion: networking.k8s.io/v1
17+
kind: NetworkPolicy
18+
metadata:
19+
name: allow-kagent-controller
20+
namespace: team-beta
21+
spec:
22+
podSelector:
23+
matchLabels:
24+
app.kubernetes.io/name: specialist-agent
25+
policyTypes:
26+
- Ingress
27+
ingress:
28+
# Only the kagent controller pod may initiate connections to the specialist.
29+
# The controller lives in the kagent namespace and carries this label.
30+
- from:
31+
- namespaceSelector:
32+
matchLabels:
33+
kubernetes.io/metadata.name: kagent
34+
podSelector:
35+
matchLabels:
36+
app.kubernetes.io/name: kagent
37+
ports:
38+
- protocol: TCP
39+
port: 8080 # agent HTTP port (A2A endpoint)
40+
41+
# --- team-alpha: deny all ingress, allow only from kagent controller ---
42+
apiVersion: networking.k8s.io/v1
43+
kind: NetworkPolicy
44+
metadata:
45+
name: default-deny-ingress
46+
namespace: team-alpha
47+
spec:
48+
podSelector: {}
49+
policyTypes:
50+
- Ingress
51+
---
52+
apiVersion: networking.k8s.io/v1
53+
kind: NetworkPolicy
54+
metadata:
55+
name: allow-kagent-controller
56+
namespace: team-alpha
57+
spec:
58+
podSelector:
59+
matchLabels:
60+
app.kubernetes.io/name: orchestrator-agent
61+
policyTypes:
62+
- Ingress
63+
ingress:
64+
- from:
65+
- namespaceSelector:
66+
matchLabels:
67+
kubernetes.io/metadata.name: kagent
68+
podSelector:
69+
matchLabels:
70+
app.kubernetes.io/name: kagent
71+
ports:
72+
- protocol: TCP
73+
port: 8080
74+
75+
# --- Both namespaces: allow egress only to kagent controller + kube-dns ---
76+
apiVersion: networking.k8s.io/v1
77+
kind: NetworkPolicy
78+
metadata:
79+
name: allow-egress-to-kagent
80+
namespace: team-alpha
81+
spec:
82+
podSelector: {}
83+
policyTypes:
84+
- Egress
85+
egress:
86+
- to:
87+
- namespaceSelector:
88+
matchLabels:
89+
kubernetes.io/metadata.name: kagent
90+
ports:
91+
- protocol: TCP
92+
port: 8083 # kagent A2A gateway port
93+
- to:
94+
- namespaceSelector:
95+
matchLabels:
96+
kubernetes.io/metadata.name: kube-system
97+
ports:
98+
- protocol: UDP
99+
port: 53 # kube-dns
100+
- protocol: TCP
101+
port: 53
102+
---
103+
apiVersion: networking.k8s.io/v1
104+
kind: NetworkPolicy
105+
metadata:
106+
name: allow-egress-to-kagent
107+
namespace: team-beta
108+
spec:
109+
podSelector: {}
110+
policyTypes:
111+
- Egress
112+
egress:
113+
- to:
114+
- namespaceSelector:
115+
matchLabels:
116+
kubernetes.io/metadata.name: kagent
117+
ports:
118+
- protocol: TCP
119+
port: 8083
120+
- to:
121+
- namespaceSelector:
122+
matchLabels:
123+
kubernetes.io/metadata.name: kube-system
124+
ports:
125+
- protocol: UDP
126+
port: 53
127+
- protocol: TCP
128+
port: 53
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Each namespace holds only its own secrets. The orchestrator in team-alpha
2+
# holds a delegation token it passes as X-Agent-Token when calling the
3+
# specialist. The specialist validates this header (or will once OIDC lands).
4+
#
5+
# DO NOT commit real keys — replace these base64 values before applying.
6+
# Generate: echo -n 'your-key' | base64
7+
8+
# --- team-beta: specialist model API key ---
9+
apiVersion: v1
10+
kind: Secret
11+
metadata:
12+
name: openai-secret
13+
namespace: team-beta
14+
type: Opaque
15+
data:
16+
# echo -n 'sk-YOUR-KEY-HERE' | base64
17+
api-key: c2stWU9VUi1LRVktSEVSRQ==
18+
---
19+
# Token the specialist uses to validate callers. The orchestrator must
20+
# present this in X-Agent-Token. Rotate independently of model keys.
21+
apiVersion: v1
22+
kind: Secret
23+
metadata:
24+
name: specialist-delegation-token
25+
namespace: team-beta
26+
type: Opaque
27+
data:
28+
# echo -n 'change-me-strong-random-token' | base64
29+
token: Y2hhbmdlLW1lLXN0cm9uZy1yYW5kb20tdG9rZW4=
30+
31+
# --- team-alpha: orchestrator model API key ---
32+
apiVersion: v1
33+
kind: Secret
34+
metadata:
35+
name: openai-secret
36+
namespace: team-alpha
37+
type: Opaque
38+
data:
39+
api-key: c2stWU9VUi1LRVktSEVSRQ==
40+
---
41+
# Delegation token the orchestrator presents to the specialist.
42+
# Must match specialist-delegation-token.token in team-beta.
43+
apiVersion: v1
44+
kind: Secret
45+
metadata:
46+
name: specialist-delegation-token
47+
namespace: team-alpha
48+
type: Opaque
49+
data:
50+
token: Y2hhbmdlLW1lLXN0cm9uZy1yYW5kb20tdG9rZW4=
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# ModelConfig per namespace — each team owns its own model config and API key.
2+
# Neither can read the other's Secret.
3+
4+
apiVersion: kagent.dev/v1alpha2
5+
kind: ModelConfig
6+
metadata:
7+
name: default-model
8+
namespace: team-beta
9+
spec:
10+
provider: OpenAI
11+
model: gpt-4o-mini
12+
apiKeySecret: openai-secret
13+
apiKeySecretKey: api-key
14+
---
15+
apiVersion: kagent.dev/v1alpha2
16+
kind: ModelConfig
17+
metadata:
18+
name: default-model
19+
namespace: team-alpha
20+
spec:
21+
provider: OpenAI
22+
model: gpt-4o-mini
23+
apiKeySecret: openai-secret
24+
apiKeySecretKey: api-key
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Specialist agent in team-beta.
2+
#
3+
# Key security field: spec.allowedNamespaces
4+
# The reconciler calls AllowsNamespace() before wiring this agent as a tool.
5+
# Any Agent in a namespace NOT matching the selector will be rejected at
6+
# reconcile time — the orchestrator's tool reference will fail with an error
7+
# and the orchestrator agent will not be admitted.
8+
#
9+
# Pattern follows Gateway API cross-namespace route attachment:
10+
# https://gateway-api.sigs.k8s.io/guides/multiple-ns/#cross-namespace-routing
11+
12+
apiVersion: kagent.dev/v1alpha2
13+
kind: Agent
14+
metadata:
15+
name: specialist
16+
namespace: team-beta
17+
spec:
18+
type: Declarative
19+
declarative:
20+
description: >
21+
Math specialist. Solves arithmetic, algebra, and calculus problems
22+
step by step. Only accepts delegations from team-alpha.
23+
systemMessage: |
24+
You are a precise math specialist. When given a problem:
25+
1. State the problem clearly.
26+
2. Show every step.
27+
3. State the final answer on its own line prefixed with "Answer:".
28+
Never guess. If unsure, say so.
29+
modelConfig: default-model
30+
tools: []
31+
32+
# Cross-namespace access control — Gateway API pattern.
33+
# Only namespaces labelled team=alpha may reference this agent as a tool.
34+
# Change to `from: All` to allow any namespace (not recommended for prod).
35+
# Change to `from: Same` (or omit) to lock to team-beta only.
36+
allowedNamespaces:
37+
from: Selector
38+
selector:
39+
matchLabels:
40+
team: alpha
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Orchestrator agent in team-alpha.
2+
#
3+
# Calls the specialist in team-beta as a tool via the kagent A2A gateway.
4+
# The gateway URL pattern is:
5+
# <controller-svc>:8083/api/a2a/team-beta/specialist/
6+
#
7+
# headersFrom injects the delegation token from team-alpha's own Secret as
8+
# X-Agent-Token on every outbound A2A call to the specialist. The specialist
9+
# can validate this header. Crucially, the orchestrator never touches
10+
# team-beta's Secrets — it only presents a matching token from its own namespace.
11+
#
12+
# If the specialist's allowedNamespaces selector does NOT match team-alpha,
13+
# the controller reconciler rejects this Agent at admission and logs:
14+
# "cross-namespace reference denied: team-alpha -> team-beta/specialist"
15+
16+
apiVersion: kagent.dev/v1alpha2
17+
kind: Agent
18+
metadata:
19+
name: orchestrator
20+
namespace: team-alpha
21+
spec:
22+
type: Declarative
23+
declarative:
24+
description: >
25+
Orchestrator agent. Receives user requests and delegates math problems
26+
to the team-beta specialist via the kagent Agent Gateway.
27+
systemMessage: |
28+
You are an orchestrating agent. For any math question, delegate it to
29+
the specialist tool and return the specialist's answer verbatim.
30+
Do not attempt to solve math yourself.
31+
modelConfig: default-model
32+
tools:
33+
# Cross-namespace agent tool reference.
34+
# `name` is the Agent name; `namespace` resolves the cross-namespace ref.
35+
# The reconciler validates team-alpha is allowed by team-beta/specialist
36+
# before this agent is admitted.
37+
- agent:
38+
name: specialist
39+
namespace: team-beta
40+
# headersFrom: inject delegation token from THIS namespace's Secret.
41+
# The controller translator resolves the Secret at reconcile time and
42+
# adds X-Agent-Token to every A2A request to the specialist.
43+
# The specialist's auth layer (UnsecureAuthenticator today, JWT once
44+
# EP-476 lands) can validate this header.
45+
headersFrom:
46+
- name: X-Agent-Token
47+
valueFrom:
48+
type: Secret
49+
name: specialist-delegation-token
50+
key: token

0 commit comments

Comments
 (0)