This file provides context for Claude (AI assistant) when working with the cortex monorepo.
- Use
Assisted-Byfor attribution — never addCo-Authored-By,Generated with Claude Code, or similar trailers. See Commit Attribution Policy below.
cortex contains Kubernetes security extensions for the Rossoctl 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: rossoctl/operator.
GitHub: github.com/rossoctl/cortex
Container registry: ghcr.io/rossoctl/cortex/<image-name>
License: Apache 2.0
cortex/
├── 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-proxy/ # proxy-sidecar mode (default): HTTP forward + reverse
│ │ │ # proxies, full plugin set including parsers
│ │ ├── main.go
│ │ ├── Dockerfile # proxy-sidecar combined image
│ │ └── entrypoint.sh
│ ├── cmd/authbridge-envoy/ # envoy-sidecar mode: ext_proc gRPC server, full plugin set
│ │ ├── main.go
│ │ ├── Dockerfile # envoy-sidecar combined image
│ │ └── entrypoint.sh
│ │ # (the authbridge-lite image is this proxy
│ │ # binary built with exclude_plugin_* tags)
│ ├── proxy-init/ # iptables init container (envoy-sidecar + proxy-sidecar enforce-redirect modes)
│ │ ├── init-iptables.sh
│ │ ├── Dockerfile.init
│ │ ├── Makefile
│ │ └── README.md
│ ├── demos/ # Demo scenarios (weather-agent, github-issue, token-exchange-routes, mcp-parser)
│ └── keycloak_sync.py # Declarative Keycloak sync tool
├── tests/ # Python tests (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
Two mode-specific binaries (proxy-sidecar and envoy-sidecar) providing transparent traffic interception for both inbound JWT validation and outbound OAuth 2.0 token exchange (RFC 8693). Each binary is hardcoded to its deployment shape; mode is no longer selected at runtime. The authbridge-lite image is a build variant of the proxy binary (not a third binary) — see below.
Library: authbridge/authlib/ (shared)
Language: Go 1.25
Detailed guide: authbridge/CLAUDE.md
Binaries:
cmd/authbridge-proxy/— proxy-sidecar mode (default): HTTP forward + reverse proxies, full plugin set (jwt-validation, token-exchange, a2a-parser, mcp-parser, inference-parser). No Envoy / no gRPC.cmd/authbridge-envoy/— envoy-sidecar mode: ext_proc gRPC server hooked into Envoy, full plugin set.authbridge-lite(image, not a separate binary) —cmd/authbridge-proxybuilt withexclude_plugin_*tags so only jwt-validation + token-exchange compile in (OPA + parsers dropped), for size-optimized deployments. Individual plugins live incmd/authbridge-proxy/plugins_<name>.gofiles gated by//go:build !exclude_plugin_<name>.
Common:
authlib/— shared auth library (JWT validation, token exchange, caching, routing, all listener implementations, all plugins).proxy-init/init-iptables.sh— traffic interception setup (Istio ambient mesh compatible). Used by envoy-sidecar mode (redirect) and by proxy-sidecar mode'senforce-redirectegress guard.proxy-init/Dockerfile.init— proxy-init container image.
Ports (envoy-sidecar): 15123 (outbound), 15124 (inbound), 9090 (ext-proc), 9901 (admin) Ports (proxy-sidecar / lite): 8080 (reverse proxy), 8081 (forward proxy), 9091 (health), 9093 (stats), 9094 (session API)
Keycloak client registration for workloads is handled by the
operator (separate repo) — see operator/docs/operator-managed-client-registration.md.
The operator creates a Secret with client-id.txt + client-secret.txt
and the webhook mounts it at /shared/ in the workload pod. The
in-pod client-registration sidecar that previously lived in this
repo has been removed.
The operator (in a separate repo) injects AuthBridge sidecars into workload pods. Default deployment shape (proxy-sidecar mode):
┌────────────────────────────────────┐
│ WORKLOAD POD │
│ │
│ spiffe-helper ──► SPIRE Agent │ (in-container,
│ │ writes JWT SVID │ conditional on
│ ▼ │ SPIRE_ENABLED)
│ authbridge-proxy │
│ - Reverse proxy: inbound JWT │
│ - Forward proxy: outbound │
│ token exchange │
│ │ │
│ Your Application │
│ (HTTP_PROXY → forward proxy) │
└────────────────────────────────────┘
The operator also creates a Secret with client-id +
client-secret and mounts it at /shared/.
For envoy-sidecar mode, replace authbridge-proxy with
the authbridge-envoy image (Envoy + ext_proc + spiffe-helper)
and add a proxy-init container for iptables.
Two mode-specific binaries (proxy, envoy), one Dockerfile each; the authbridge-lite image is a build variant of the proxy binary (proxy Dockerfile + exclude_plugin_* tags):
| Binary | Mode | Listeners | Plugins |
|---|---|---|---|
cmd/authbridge-proxy/ |
proxy-sidecar (default) | HTTP forward + reverse proxies | full (incl. parsers) |
cmd/authbridge-envoy/ |
envoy-sidecar | gRPC ext_proc on :9090 | full (incl. parsers) |
authbridge-lite (image: proxy + exclude_plugin_*) |
proxy-sidecar | HTTP forward + reverse proxies | trimmed plugin set (see authbridge/scripts/lite-tags) |
Go modules:
authbridge/authlib/— pure library: validation, exchange, cache, bypass, spiffe, routing, auth, config, all listener implementations, all plugins.authbridge/cmd/authbridge-{proxy,envoy}/— thin main packages that import authlib and start the listeners they need. (Theauthbridge-liteimage isauthbridge-proxybuilt withexclude_plugin_*tags.)authbridge/go.work— workspace linking authlib + the binaries for local development.
Config format: YAML with ${ENV_VAR} expansion, mode presets, and startup validation. Supports keycloak_url + keycloak_realm derivation for operator compatibility. The mode field in YAML must match the binary (each binary rejects mismatched modes at boot).
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yaml |
PR to main/release-* | Pre-commit, Go fmt/vet/build/test for authlib and the cmd/authbridge-* binaries; Python tests |
build.yaml |
Tag push (v*) or manual |
Multi-arch Docker builds for: proxy-init, authbridge (proxy-sidecar combined), authbridge-envoy (envoy-sidecar combined), authbridge-lite (proxy Dockerfile built with exclude_plugin_* tags from authbridge/scripts/lite-tags) |
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 |
PR titles must follow the format:
<Prefix>: <Subject starting with uppercase>
The CI title check (deepakputhraya/action-pr-title) is case-sensitive and
requires a capitalized prefix from this set: Build, Chore, CI, Docs,
Feat, Fix, Perf, Refactor, Revert, Style, Test, Feature,
Bug fix, Proposal, Breaking change, Other, Other/Misc.
Note: commit message examples elsewhere in this doc use lowercase
(feat: / fix:) — that is fine for commits, but the PR title check rejects
lowercase prefixes. Use Fix: / Feat: / Docs: in PR titles.
All images are pushed to ghcr.io/rossoctl/cortex/ from
.github/workflows/build.yaml:
| Image | Source | Description |
|---|---|---|
authbridge |
authbridge/cmd/authbridge-proxy/Dockerfile |
proxy-sidecar combined image (default mode): authbridge-proxy (full plugin set incl. parsers) + spiffe-helper. No Envoy. |
authbridge-envoy |
authbridge/cmd/authbridge-envoy/Dockerfile |
envoy-sidecar combined image: Envoy + authbridge-envoy (ext_proc, full plugin set) + spiffe-helper |
authbridge-lite |
authbridge/cmd/authbridge-proxy/Dockerfile (+ GO_BUILD_TAGS=exclude_plugin_*) |
proxy-sidecar combined image with a trimmed plugin set (see authbridge/scripts/lite-tags), plus spiffe-helper. A build variant of authbridge, not a separate binary; not yet referenced by the operator's default config |
authbridge-cpex |
authbridge/cmd/authbridge-cpex/Dockerfile |
proxy-sidecar build with the CPEX plugin: authbridge-proxy built with -tags cpex, links libcpex_ffi.a from a pinned CPEX release (CGO_ENABLED=1). Routes hooks through the CPEX framework (APL DSL + named CPEX policy plugins). FFI ABI version is read from authbridge/cmd/authbridge-cpex/CPEX_FFI_VERSION |
proxy-init |
authbridge/proxy-init/Dockerfile.init |
Alpine + iptables init container (envoy-sidecar + proxy-sidecar enforce-redirect modes) |
In all three combined images, spiffe-helper is started conditionally
based on the SPIRE_ENABLED env var (set by the operator when SPIRE
identity is enabled for the workload).
The legacy authbridge-unified, authbridge-light, client-registration,
spiffe-helper, auth-proxy, and demo-app standalone images have
been removed from CI (the auth-proxy / demo-app source is still in-tree
for the standalone quickstart). Older release tags continue to publish
the old images.
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/proxy-init/Go files
| Area | Technology |
|---|---|
| AuthBridge unified binary | Go 1.25, 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) |
| 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.
The repo-root local-build-and-test.sh orchestrates every image
the platform needs (spiffe-idp-setup from rossoctl, plus
authbridge, authbridge-envoy, authbridge-lite, proxy-init
from this repo) and loads them into a Kind cluster:
ROSSOCTL_DIR=../rossoctl ./local-build-and-test.shTo build a single image directly:
# proxy-init (iptables init container, envoy-sidecar mode)
cd authbridge/proxy-init && make docker-build-init
# Combined sidecars (proxy-sidecar default / envoy-sidecar)
cd authbridge && podman build -f cmd/authbridge-proxy/Dockerfile -t authbridge:latest .
cd authbridge && podman build -f cmd/authbridge-envoy/Dockerfile -t authbridge-envoy:latest .
# authbridge-lite: same proxy Dockerfile, built with the trimmed
# plugin set derived from plugin source by
# authbridge/scripts/lite-tags.
cd authbridge && LITE_TAGS=$(go -C scripts/lite-tags run .) && podman build -f cmd/authbridge-proxy/Dockerfile \
--build-arg GO_BUILD_TAGS="${LITE_TAGS}" \
-t authbridge-lite:latest .- Set up a Kind cluster with SPIRE + Keycloak (use Rossoctl installer)
- Deploy the webhook via 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) - Routes config reference:
authbridge/demos/token-exchange-routes/README.md(single + multi-target route patterns)
- Getting started:
- Add entry to
.github/workflows/build.yamlmatrix (image_configarray) - Provide
name,context, anddockerfilefields - Image will be pushed to
ghcr.io/rossoctl/cortex/<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.
-
Multiple Go modules: The repo has several Go modules under
authbridge/—authlib/, eachcmd/*/,storage/redis/,scripts/lite-tags/, and thedemos/*/self-contained ones — linked byauthbridge/go.work. Local commands from a specific module directory should typically setGOWORK=off(as CI does) so the module resolves its ownreplacedirectives instead of pulling in workspace siblings. -
Avoid committing venvs: Virtual environment directories (e.g.
authbridge/proxy-init/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