Skip to content

Commit b942b2b

Browse files
committed
Feat: Add token-broker service for OAuth session and token management
The token-broker service provides centralized OAuth token acquisition for AI agents via PKCE-based authorization flows, token caching with JWT expiry parsing, and session management with long-polling coordination. Signed-off-by: David Hadas <david.hadas@gmail.com>
1 parent fb22549 commit b942b2b

38 files changed

Lines changed: 6786 additions & 34 deletions

kagenti-operator/go.mod

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
github.com/prometheus/client_model v0.6.2
1616
github.com/sigstore/sigstore-go v1.1.4
1717
github.com/spiffe/go-spiffe/v2 v2.7.0
18+
golang.org/x/sync v0.21.0
1819
k8s.io/api v0.36.1
1920
k8s.io/apiextensions-apiserver v0.36.1
2021
k8s.io/apimachinery v0.36.1
@@ -139,17 +140,16 @@ require (
139140
go.uber.org/zap v1.28.0 // indirect
140141
go.yaml.in/yaml/v2 v2.4.3 // indirect
141142
go.yaml.in/yaml/v3 v3.0.4 // indirect
142-
golang.org/x/crypto v0.52.0 // indirect
143+
golang.org/x/crypto v0.53.0 // indirect
143144
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
144-
golang.org/x/mod v0.35.0 // indirect
145-
golang.org/x/net v0.55.0 // indirect
145+
golang.org/x/mod v0.36.0 // indirect
146+
golang.org/x/net v0.56.0 // indirect
146147
golang.org/x/oauth2 v0.36.0 // indirect
147-
golang.org/x/sync v0.21.0 // indirect
148-
golang.org/x/sys v0.45.0 // indirect
149-
golang.org/x/term v0.43.0 // indirect
150-
golang.org/x/text v0.37.0 // indirect
148+
golang.org/x/sys v0.46.0 // indirect
149+
golang.org/x/term v0.44.0 // indirect
150+
golang.org/x/text v0.38.0 // indirect
151151
golang.org/x/time v0.15.0 // indirect
152-
golang.org/x/tools v0.44.0 // indirect
152+
golang.org/x/tools v0.45.0 // indirect
153153
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
154154
google.golang.org/api v0.284.0 // indirect
155155
google.golang.org/genproto v0.0.0-20260608224507-4308a22a1bab // indirect

kagenti-operator/go.sum

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -451,28 +451,28 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
451451
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
452452
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
453453
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
454-
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
455-
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
454+
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
455+
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
456456
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
457457
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
458-
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
459-
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
460-
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
461-
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
458+
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
459+
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
460+
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
461+
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
462462
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
463463
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
464464
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
465465
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
466-
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
467-
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
468-
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
469-
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
470-
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
471-
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
466+
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
467+
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
468+
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
469+
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
470+
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
471+
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
472472
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
473473
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
474-
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
475-
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
474+
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
475+
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
476476
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
477477
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
478478
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=

kagenti-operator/hack/kind-reload-all.sh

Lines changed: 117 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# Services:
66
# 1. kagenti-controller-manager (operator)
77
# 2. bundle-service (OPA bundle server)
8+
# 3. token-broker (OAuth token management)
89
#
910
# Usage:
1011
# ./hack/kind-reload-all.sh [kind-cluster-name] [namespace]
@@ -25,6 +26,7 @@ IMAGE_TAG="$(git -C "${ROOT_DIR}" rev-parse --short HEAD)"
2526

2627
OPERATOR_IMG="localhost/kagenti-operator:${IMAGE_TAG}"
2728
BUNDLE_IMG="localhost/bundle-service:${IMAGE_TAG}"
29+
TOKEN_BROKER_IMG="localhost/token-broker:${IMAGE_TAG}"
2830

2931
echo "============================================"
3032
echo " Building and loading to Kind: ${CLUSTER}"
@@ -52,16 +54,31 @@ echo ""
5254
echo "==> Building kagenti-operator image"
5355
${CONTAINER_TOOL} build -t "${OPERATOR_IMG}" -f "${ROOT_DIR}/Dockerfile" "${ROOT_DIR}"
5456

55-
echo ""
56-
echo "==> Building bundle-service image"
57-
${CONTAINER_TOOL} build -t "${BUNDLE_IMG}" -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" "${ROOT_DIR}"
57+
if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then
58+
echo ""
59+
echo "==> Building bundle-service image"
60+
${CONTAINER_TOOL} build -t "${BUNDLE_IMG}" -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" "${ROOT_DIR}"
61+
fi
62+
63+
if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then
64+
echo ""
65+
echo "==> Building token-broker image"
66+
${CONTAINER_TOOL} build -t "${TOKEN_BROKER_IMG}" -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" "${ROOT_DIR}"
67+
fi
5868

5969
# --- Load into Kind ---
6070

6171
echo ""
6272
echo "==> Loading images into Kind cluster '${CLUSTER}'"
6373
kind load docker-image "${OPERATOR_IMG}" --name "${CLUSTER}"
64-
kind load docker-image "${BUNDLE_IMG}" --name "${CLUSTER}"
74+
75+
if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then
76+
kind load docker-image "${BUNDLE_IMG}" --name "${CLUSTER}"
77+
fi
78+
79+
if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then
80+
kind load docker-image "${TOKEN_BROKER_IMG}" --name "${CLUSTER}"
81+
fi
6582

6683
# --- Deploy kagenti-controller-manager ---
6784

@@ -76,8 +93,9 @@ else
7693
echo " (the controller-manager requires webhook certs, RBAC, and CRDs from kustomize)"
7794
fi
7895

79-
# --- Deploy bundle-service ---
96+
# --- Deploy bundle-service (if source exists) ---
8097

98+
if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then
8199
echo ""
82100
echo "==> Deploying bundle-service"
83101

@@ -210,23 +228,87 @@ spec:
210228
targetPort: http
211229
protocol: TCP
212230
EOF
231+
fi
232+
233+
# --- Deploy token-broker (if source exists) ---
234+
235+
if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then
236+
echo ""
237+
echo "==> Deploying token-broker"
238+
239+
# Load OAuth credentials from .env
240+
if [ -f "${ROOT_DIR}/.env" ]; then
241+
export $(grep -v '^#' "${ROOT_DIR}/.env" | grep -v '^\s*$' | xargs)
242+
fi
243+
244+
if [ -z "${GITHUB_OAUTH_CLIENT_ID:-}" ] || [ -z "${GITHUB_OAUTH_CLIENT_SECRET:-}" ]; then
245+
echo " ERROR: OAuth credentials not set."
246+
echo " Create ${ROOT_DIR}/.env with:"
247+
echo " GITHUB_OAUTH_CLIENT_ID=<your-client-id>"
248+
echo " GITHUB_OAUTH_CLIENT_SECRET=<your-client-secret>"
249+
echo " See .env.example for reference."
250+
exit 1
251+
fi
252+
253+
# Create/update OAuth secret
254+
kubectl delete secret github-oauth-credentials -n "${NAMESPACE}" 2>/dev/null || true
255+
kubectl create secret generic github-oauth-credentials \
256+
--from-literal=client-id="${GITHUB_OAUTH_CLIENT_ID}" \
257+
--from-literal=client-secret="${GITHUB_OAUTH_CLIENT_SECRET}" \
258+
--namespace="${NAMESPACE}"
259+
echo " OAuth secret created"
260+
261+
# Apply the deployment manifests (single source of truth).
262+
# These pin namespace kagenti-system and include the OAuth-callback HTTPRoute
263+
# (attached to the shared "http" gateway). The secret is created above from
264+
# .env, so the secret example manifest is intentionally skipped.
265+
DEPLOY_DIR="${ROOT_DIR}/cmd/token-broker/deploy"
266+
kubectl apply \
267+
-f "${DEPLOY_DIR}/00-serviceaccount.yaml" \
268+
-f "${DEPLOY_DIR}/02-deployment.yaml" \
269+
-f "${DEPLOY_DIR}/03-service.yaml" \
270+
-f "${DEPLOY_DIR}/04-httproute.yaml"
271+
272+
# Inject the freshly built, git-tagged image (the manifest carries a placeholder
273+
# tag with imagePullPolicy: IfNotPresent; kind-loaded images need Never).
274+
kubectl set image deployment/token-broker \
275+
token-broker="${TOKEN_BROKER_IMG}" \
276+
-n "${NAMESPACE}"
277+
kubectl patch deployment/token-broker -n "${NAMESPACE}" --type=json \
278+
-p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"Never"}]'
279+
echo " token-broker manifests applied (image ${TOKEN_BROKER_IMG})"
280+
fi
213281

214282
# --- Delete pods to pick up the new images ---
215283

216284
echo ""
217285
echo "==> Deleting pods to pick up new images"
218-
kubectl delete pods -n "${NAMESPACE}" -l app=bundle-service --wait=false
219286
kubectl delete pods -n "${NAMESPACE}" -l control-plane=controller-manager --wait=false 2>/dev/null || true
220287

288+
if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then
289+
kubectl delete pods -n "${NAMESPACE}" -l app=bundle-service --wait=false
290+
fi
291+
292+
if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then
293+
kubectl delete pods -n "${NAMESPACE}" -l app=token-broker --wait=false
294+
fi
295+
221296
# --- Wait for rollouts ---
222297

223298
echo ""
224299
echo "==> Waiting for kagenti-controller-manager rollout"
225300
kubectl rollout status deployment/kagenti-controller-manager -n "${NAMESPACE}" --timeout=120s 2>/dev/null || \
226301
echo " WARNING: kagenti-controller-manager rollout did not complete"
227302

228-
echo "==> Waiting for bundle-service rollout"
229-
kubectl rollout status deployment/bundle-service -n "${NAMESPACE}" --timeout=60s
303+
if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then
304+
echo "==> Waiting for bundle-service rollout"
305+
kubectl rollout status deployment/bundle-service -n "${NAMESPACE}" --timeout=60s
306+
fi
307+
308+
if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then
309+
echo "==> Waiting for token-broker rollout"
310+
kubectl rollout status deployment/token-broker -n "${NAMESPACE}" --timeout=60s
311+
fi
230312

231313
# --- Summary ---
232314

@@ -236,12 +318,35 @@ echo " Done!"
236318
echo ""
237319
echo " Images loaded:"
238320
echo " ${OPERATOR_IMG}"
239-
echo " ${BUNDLE_IMG}"
321+
if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then
322+
echo " ${BUNDLE_IMG}"
323+
fi
324+
if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then
325+
echo " ${TOKEN_BROKER_IMG}"
326+
fi
240327
echo ""
241328
echo " Namespace: ${NAMESPACE}"
242329
echo " - kagenti-controller-manager"
243-
echo " - bundle-service"
330+
if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then
331+
echo " - bundle-service"
332+
fi
333+
if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then
334+
echo " - token-broker"
335+
fi
244336
echo ""
245-
echo " bundle-service URL: http://bundle-service.${NAMESPACE}.svc.cluster.local:8080"
246-
echo " To port-forward: kubectl port-forward -n ${NAMESPACE} svc/bundle-service 8080:8080"
337+
if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then
338+
echo " bundle-service URL: http://bundle-service.${NAMESPACE}.svc.cluster.local:8080"
339+
fi
340+
if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then
341+
echo " token-broker URL: http://token-broker.${NAMESPACE}.svc.cluster.local:8190"
342+
echo " OAuth callback: http://token-broker.localtest.me:8080/oauth/callback (via 'http' gateway)"
343+
fi
344+
echo ""
345+
echo " To port-forward:"
346+
if [ -f "${ROOT_DIR}/cmd/bundle-service/Dockerfile" ]; then
347+
echo " kubectl port-forward -n ${NAMESPACE} svc/bundle-service 8080:8080"
348+
fi
349+
if [ -f "${ROOT_DIR}/cmd/token-broker/Dockerfile" ]; then
350+
echo " kubectl port-forward -n ${NAMESPACE} svc/token-broker 8190:8190"
351+
fi
247352
echo "============================================"

kagenti-operator/token-broker

10.3 MB
Binary file not shown.

token-broker/Dockerfile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS builder
2+
ARG TARGETOS
3+
ARG TARGETARCH
4+
5+
WORKDIR /workspace
6+
COPY go.mod go.mod
7+
COPY go.sum go.sum
8+
RUN go mod download
9+
10+
COPY cmd/ cmd/
11+
COPY internal/ internal/
12+
COPY pkg/ pkg/
13+
14+
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -o token-broker ./cmd/
15+
16+
FROM gcr.io/distroless/static:nonroot
17+
WORKDIR /
18+
COPY --from=builder /workspace/token-broker .
19+
USER 65532:65532
20+
21+
ENTRYPOINT ["/token-broker"]

0 commit comments

Comments
 (0)