Give the agent a door a human can reach
Nothing outside the cluster could talk to it: every conversation went through kubectl run curl, which makes an agent that works and nobody can use. The same authentication subrequest that guards the n8n endpoints guards this one. The agent forwards and exchanges the caller's token, so a forged one fails at the realm — but failing downstream is not refusing, and an unauthenticated caller could still make it do inference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Reaching this agent from outside the cluster.
|
||||
#
|
||||
# Until now nothing did: an agent answered only what could call its Service, so
|
||||
# every conversation with it went through `kubectl run curl`. An orchestrator
|
||||
# nobody can talk to is not finished.
|
||||
#
|
||||
# Peer traffic does **not** come through here — catalogue calls, and is called by,
|
||||
# other agents on their in-cluster Service names. This is the human door.
|
||||
#
|
||||
# ## It is not an open door
|
||||
#
|
||||
# A2A carries the end user's token in `Authorization`, and this agent forwards it
|
||||
# and exchanges it; a forged one fails at the realm. But "fails downstream" is
|
||||
# not the same as "refused", and an unauthenticated caller could still make the
|
||||
# agent do work — inference costs money and a request that reaches an LLM is a
|
||||
# prompt-injection surface. So the same authentication subrequest that guards the
|
||||
# n8n endpoints guards this one: Keycloak's userinfo answers 200 for a valid
|
||||
# bearer in the realm and 401 otherwise, which is the auth-request contract
|
||||
# exactly.
|
||||
#
|
||||
# It authenticates, it does not authorise. What the caller may actually reach is
|
||||
# decided further down, by the exchange and by the roles in their token.
|
||||
#
|
||||
# **The agent card is behind it too.** A2A discovery is normally public; here it
|
||||
# is not, because a client that cannot send an `Authorization` header cannot use
|
||||
# the agent anyway, and the card names the skills.
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: catalogue-agent-tls
|
||||
namespace: agents
|
||||
spec:
|
||||
secretName: catalogue-agent-tls
|
||||
dnsNames:
|
||||
- catalogue-agent.sttlab.local
|
||||
issuerRef:
|
||||
name: sttlab-local-ca
|
||||
kind: ClusterIssuer
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: catalogue-agent
|
||||
namespace: agents
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/auth-url: http://keycloak.keycloak.svc.cluster.local:8080/realms/classic-models/protocol/openid-connect/userinfo
|
||||
# A2A streams a task's progress; buffering it defeats that.
|
||||
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
|
||||
# An agent turn is a model call and several hops. The nginx default of 60s
|
||||
# cuts a working answer off mid-flight.
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- catalogue-agent.sttlab.local
|
||||
secretName: catalogue-agent-tls
|
||||
rules:
|
||||
- host: catalogue-agent.sttlab.local
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: catalogue-agent
|
||||
port:
|
||||
number: 8080
|
||||
@@ -7,3 +7,4 @@ resources:
|
||||
- configmap.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
|
||||
Reference in New Issue
Block a user