This file provides context for Claude (AI assistant) when working with the kagenti-extensions monorepo.
- Use
Assisted-Byfor attribution — never addCo-Authored-By,Generated with Claude Code, or similar trailers. See Commit Attribution Policy below.
kagenti-extensions contains Kubernetes security extensions for the Kagenti ecosystem. It provides zero-trust authentication for Kubernetes workloads through transparent token exchange and dynamic Keycloak client registration using SPIFFE/SPIRE identities.
The sidecar injection webhook lives in a separate repo: kagenti/kagenti-operator.
GitHub: github.com/kagenti/kagenti-extensions
Container registry: ghcr.io/kagenti/kagenti-extensions/<image-name>
License: Apache 2.0
kagenti-extensions/
├── authbridge/ # Authentication bridge components
│ ├── authlib/ # Shared auth building blocks (Go module)
│ │ ├── validation/ # JWKS-backed JWT verifier
│ │ ├── exchange/ # RFC 8693 token exchange client
│ │ ├── cache/ # SHA-256 keyed token cache
│ │ ├── bypass/ # Path pattern matcher
│ │ ├── spiffe/ # SPIFFE credential sources
│ │ ├── routing/ # Host-to-audience router
│ │ ├── auth/ # HandleInbound + HandleOutbound composition
│ │ └── config/ # Mode presets, YAML config, validation
│ ├── cmd/authbridge/ # Unified binary — 3 modes, 1 codebase
│ │ ├── listener/ # Protocol adapters (ext_proc, ext_authz, forward/reverse proxy)
│ │ ├── entrypoint.sh # Envoy + authbridge process supervision
│ │ └── Dockerfile # Combined Envoy + authbridge image
│ ├── authproxy/ # Auth proxy support files and demos
│ │ ├── quickstart/ # Standalone demo (no SPIFFE)
│ │ └── k8s/ # Standalone K8s manifests
│ ├── client-registration/ # Keycloak auto-registration (Python)
│ ├── spiffe-helper/ # SPIFFE helper Dockerfile (fetches JWT-SVIDs from SPIRE)
│ ├── demos/ # Demo scenarios (weather-agent, github-issue, webhook, single-target, multi-target)
│ └── keycloak_sync.py # Declarative Keycloak sync tool
├── tests/ # Python tests (client-registration, keycloak_sync)
├── .github/
│ ├── workflows/ # CI/CD (ci.yaml, build.yaml, security-scans, scorecard, spellcheck)
│ └── ISSUE_TEMPLATE/ # Bug report, feature request, epic templates
├── .pre-commit-config.yaml # Pre-commit hooks (trailing whitespace, go fmt/vet, ruff)
└── CLAUDE.md # This file
A single binary providing transparent traffic interception for both inbound JWT validation and outbound OAuth 2.0 token exchange (RFC 8693), supporting three deployment modes.
Location: authbridge/cmd/authbridge/
Library: authbridge/authlib/
Language: Go 1.24
Detailed guide: authbridge/CLAUDE.md
Core components:
cmd/authbridge/main.go— Unified binary (ext_proc, ext_authz, forward/reverse proxy modes)authlib/— Shared auth library (JWT validation, token exchange, caching, routing)authproxy/init-iptables.sh— Traffic interception setup (Istio ambient mesh compatible)authproxy/Dockerfile.init— Init container image
Ports: 15123 (outbound), 15124 (inbound), 9090 (ext-proc/ext-authz), 9901 (admin), 9093 (stats and config)
A Python script that automatically registers Kubernetes workloads as Keycloak OAuth2 clients using their SPIFFE identity.
Location: authbridge/client-registration/
Language: Python 3.12
Detailed guide: authbridge/CLAUDE.md
Flow: Reads SPIFFE ID from JWT, registers client in Keycloak, writes secret to /shared/client-secret.txt
The kagenti-operator (in a separate repo) injects AuthBridge sidecars into workload pods. Once injected, the sidecars work together:
┌────────────────────────────────────┐
│ WORKLOAD POD │
│ │
│ proxy-init (init) ─► iptables │
│ │
│ spiffe-helper ──► SPIRE Agent │
│ │ writes JWT SVID │
│ ▼ │
│ client-registration ──► Keycloak │
│ │ writes client secret │
│ ▼ │
│ envoy-proxy (+ authbridge) │
│ - Inbound: JWT validation │
│ - Outbound: token exchange │
│ │ │
│ Your Application │
└────────────────────────────────────┘
The cmd/authbridge/ directory contains a unified binary that replaces three separate
codebases (go-processor, waypoint, klaviger) with a single binary supporting three modes:
| Mode | Interception | Listeners | Use Case |
|---|---|---|---|
envoy-sidecar |
Envoy iptables + ext_proc | gRPC ext_proc on :9090 | Sidecar per agent pod |
waypoint |
Istio ambient + ext_authz | gRPC ext_authz + HTTP forward proxy | Shared service |
proxy-sidecar |
Reverse proxy + forward proxy | HTTP reverse proxy + forward proxy | Sidecar without Envoy |
Go modules:
authbridge/authlib/— pure library, no protocol deps (validation, exchange, cache, bypass, spiffe, routing, auth, config)authbridge/cmd/authbridge/— binary + listeners, imports authlib + gRPC/Envoy depsauthbridge/go.work— workspace linking both modules for local development
Config format: YAML with ${ENV_VAR} expansion, mode presets, and startup validation.
Supports keycloak_url + keycloak_realm derivation for operator compatibility.
Image: ghcr.io/kagenti/kagenti-extensions/authbridge-unified — Envoy + authbridge
in one container (drop-in replacement for envoy-with-processor).
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yaml |
PR to main/release-* | Go fmt, vet, build, test for authproxy, authlib, and cmd/authbridge; Python tests |
build.yaml |
Tag push (v*) or manual |
Multi-arch Docker builds for: client-registration, auth-proxy, proxy-init, authbridge, authbridge-unified, spiffe-helper, demo-app |
security-scans.yaml |
PR to main | Dependency review, shellcheck, YAML lint, Hadolint, Bandit, Trivy, CodeQL |
scorecard.yaml |
Weekly / push to main | OpenSSF Scorecard security health metrics |
spellcheck_action.yml |
PR | Spellcheck on markdown files |
PRs must follow conventional commits format:
<type>: <Subject starting with uppercase>
Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
All images are pushed to ghcr.io/kagenti/kagenti-extensions/:
| Image | Source | Description |
|---|---|---|
authbridge-unified |
authbridge/cmd/authbridge/Dockerfile |
Unified Envoy + authbridge binary (recommended) |
authbridge |
authbridge/authproxy/Dockerfile.authbridge |
Combined sidecar (Envoy + authbridge + spiffe-helper + client-registration) |
proxy-init |
authbridge/authproxy/Dockerfile.init |
Alpine + iptables init container |
client-registration |
authbridge/client-registration/Dockerfile |
Python Keycloak client registrar |
spiffe-helper |
authbridge/spiffe-helper/Dockerfile |
Fetches SPIFFE credentials from SPIRE |
auth-proxy |
authbridge/authproxy/Dockerfile |
Example pass-through proxy (for demos) |
demo-app |
authbridge/authproxy/quickstart/demo-app/Dockerfile |
Demo target service |
Install: pre-commit install
Hooks:
trailing-whitespace,end-of-file-fixer,check-added-large-files(max 1024KB),check-yaml,check-json,check-merge-conflict,mixed-line-endingai-assisted-by-trailer— RewritesCo-Authored-BytoAssisted-By(commit-msg stage)ruff,ruff-format— Python linting/formatting onauthbridge/filesgo-fmt,go-vet— Runs onauthbridge/authproxy/Go files
| Area | Technology |
|---|---|
| AuthBridge unified binary | Go 1.24, envoy-control-plane, lestrrat-go/jwx |
| Client Registration | Python 3.12, python-keycloak, PyJWT |
| Proxy | Envoy 1.28 |
| Traffic interception | iptables (via init container) |
| Identity | SPIFFE/SPIRE (JWT-SVIDs) |
| Auth provider | Keycloak (OAuth2/OIDC, token exchange RFC 8693) |
| Packaging | Docker |
| CI | GitHub Actions |
| Service | Required | Purpose |
|---|---|---|
| Kubernetes | Yes | Target platform (v1.25+ recommended) |
| kagenti-operator | Yes | Injects AuthBridge sidecars into workload pods |
| Keycloak | Yes | OAuth2/OIDC provider, token exchange |
| SPIRE | Optional | SPIFFE identity (JWT-SVIDs) for workloads |
When the operator injects sidecars, the target namespace needs these resources:
| Resource | Kind | Used by | Keys |
|---|---|---|---|
authbridge-config |
ConfigMap | client-registration, authbridge | KEYCLOAK_URL, KEYCLOAK_REALM, PLATFORM_CLIENT_IDS (optional), TOKEN_URL (optional, derived from KEYCLOAK_URL+KEYCLOAK_REALM), ISSUER (optional, derived or explicit for split-horizon DNS), DEFAULT_OUTBOUND_POLICY (optional, defaults to passthrough). Inbound audience validation uses CLIENT_ID from /shared/client-id.txt. Target audience and scopes are configured per-route in authproxy-routes. |
keycloak-admin-secret |
Secret | client-registration | KEYCLOAK_ADMIN_USERNAME, KEYCLOAK_ADMIN_PASSWORD |
authproxy-routes |
ConfigMap (optional) | authbridge | routes.yaml -- per-host token exchange rules (see authbridge/CLAUDE.md for format) |
spiffe-helper-config |
ConfigMap | spiffe-helper | SPIFFE helper configuration file |
envoy-config |
ConfigMap | envoy-proxy | Envoy YAML configuration |
Note: authproxy-routes is optional. Without it, all outbound traffic passes through unchanged (the default policy is passthrough). Only create it when the agent needs to call services that require token exchange. Set DEFAULT_OUTBOUND_POLICY: "exchange" in authbridge-config to restore the legacy behavior.
# AuthProxy images
cd authbridge/authproxy && make build-images
# Client registration (no separate build needed, uses Dockerfile directly)- Set up a Kind cluster with SPIRE + Keycloak (use Kagenti Ansible installer)
- Deploy the webhook via kagenti-operator
- See the AuthBridge demos index for a recommended learning path:
- Getting started:
authbridge/demos/weather-agent/demo-ui.md(inbound validation, UI deployment) - Full flow:
authbridge/demos/github-issue/demo-ui.md(token exchange + scope-based access) - Webhook internals:
authbridge/demos/webhook/README.md - Manual deployment:
authbridge/demos/single-target/demo.md
- Getting started:
- Add entry to
.github/workflows/build.yamlmatrix (image_configarray) - Provide
name,context, anddockerfilefields - Image will be pushed to
ghcr.io/kagenti/kagenti-extensions/<name>
- Use
go fmt(enforced by pre-commit and CI) - Use
go vet(enforced by pre-commit and CI)
- Python 3.12+ syntax (type hints with
str | None) - Dependencies in
requirements.txt(version-pinned, e.g.python-keycloak==5.3.1)
- Example deployment YAMLs in
authbridge/demos/*/k8s/
set -euo pipefail(strict mode)- Extensive inline documentation (especially
init-iptables.sh)
-
UID/GID Sync: The
client-registrationDockerfile creates a user with UID/GID 1000. The operator's webhook setsrunAsUser: 1000/runAsGroup: 1000when injecting the client-registration container. These MUST match. In combined mode (authbridgecontainer), everything runs as UID 1337 instead. -
Envoy Proxy UID: Envoy runs as UID 1337. The
proxy-initiptables rules exclude this UID from redirection to prevent loops. The combinedauthbridgecontainer also runs as UID 1337. -
Shared Volume Contract: The sidecars communicate through shared volumes:
/opt/jwt_svid.token— spiffe-helper writes, client-registration reads/shared/client-id.txt— client-registration writes, envoy-proxy reads/shared/client-secret.txt— client-registration writes, envoy-proxy reads
-
Port Coordination: Envoy listens on 15123 (outbound) and 15124 (inbound). The ext-proc listens on 9090. The
proxy-initiptables rules redirect to these ports.
-
One Go module: The repo has a single Go module at
authbridge/authproxy/go.mod(Go 1.24). -
Avoid committing venvs: Virtual environment directories (e.g.
authbridge/authproxy/quickstart/venv/) should be gitignored (the repo's.gitignorehas avenvpattern). Do not create and commit new virtual environments under version control. -
Envoy config not embedded: The envoy-proxy sidecar mounts
envoy-configConfigMap at/etc/envoy. This ConfigMap must exist in the target namespace before workloads are created. -
Outbound policy is passthrough by default: AuthBridge defaults to passing outbound traffic through unchanged. Token exchange only happens for hosts explicitly listed in the
authproxy-routesConfigMap. Target audience and scopes are configured per-route inauthproxy-routes. -
Route host patterns use short service names: The
hostfield inauthproxy-routesmatches against the HTTPHostheader, which is typically just the short Kubernetes service name (e.g.,github-tool-mcp), not the FQDN. Glob patterns (*) are supported but the most common case is a plain service name.
All commits must include a Signed-off-by trailer (Developer Certificate of Origin).
Always use the -s flag when committing:
git commit -s -m "feat: Add new feature"This adds a line like Signed-off-by: Your Name <your@email.com> to the commit message.
PRs without DCO sign-off will fail CI checks. To retroactively sign-off existing commits:
git rebase --signoff mainThis repo includes orchestrate skills for enhancing related repositories.
Run /orchestrate <repo-url> to start.
| Skill | Description |
|---|---|
orchestrate |
Entry point — scan, plan, execute phases |
orchestrate:scan |
Assess repo structure, CI, security gaps |
orchestrate:plan |
Create phased enhancement plan |
orchestrate:precommit |
Add pre-commit hooks and linting |
orchestrate:tests |
Add test infrastructure |
orchestrate:ci |
Add CI/CD workflows |
orchestrate:security |
Add security governance files |
orchestrate:replicate |
Bootstrap skills into target repo |
orchestrate:review |
Review all orchestration PRs before merge |
Skills management:
| Skill | Description |
|---|---|
skills |
Skills router — create, validate, scan |
skills:write |
Create or edit skills with proper structure |
skills:validate |
Validate skill format and naming |
skills:scan |
Audit repo for skill gaps |
When creating git commits, do NOT use Co-Authored-By trailers for AI attribution.
Instead, use Assisted-By to acknowledge AI assistance without inflating contributor stats:
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Never add Co-authored-by, Made-with, or similar trailers that GitHub parses as co-authorship.
PR descriptions should end with the same Assisted-By trailer:
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Do not use 🤖 Generated with [Claude Code](https://claude.com/claude-code) or similar.
A commit-msg hook in scripts/hooks/commit-msg enforces this automatically for commits.
Install it via pre-commit:
pre-commit install --hook-type pre-commit --hook-type commit-msg