From bd3c1e7b5b216cdcc875f0369971e233fd1bf72c Mon Sep 17 00:00:00 2001 From: Hai Huang Date: Wed, 24 Jun 2026 17:57:49 -0400 Subject: [PATCH 1/4] refactor(authbridge): collapse authbridge-lite into a build-tag variant of authbridge-proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit authbridge-lite differed from authbridge-proxy only in its compiled-in plugin set (same HTTP listeners, same pure-Go CGO_ENABLED=0 build), and it had drifted: its doc comments claimed an auth-only/size-optimized build while it imported the opa plugin, pulling in the full OPA/Rego Go SDK. Measured, the lite image (43.6 MB) was the same size as proxy (43.7 MB) — the size goal was negated. Generalize the existing exclude_plugin_* build-tag pattern to every plugin in cmd/authbridge-proxy (one plugins_.go per plugin gated by //go:build !exclude_plugin_); jwt-validation and token-exchange stay always-compiled. Delete cmd/authbridge-lite, and build the authbridge-lite image from the proxy Dockerfile with the exclude tags so only jwt-validation + token-exchange compile in. The image name is preserved for consumers (build.yaml repoints it at the proxy Dockerfile with a GO_BUILD_TAGS build-arg). Result: lite binary 14.7 MB vs full 27.8 MB (0 open-policy-agent symbols vs 7,475); lite image 29.7 MB vs 43.7 MB. go build/vet/test pass with and without the lite tags; a PR-time CI step compiles the lite tag set. The lite image now runs the authbridge-proxy binary (logs as authbridge-proxy); abctl is unaffected (keys on the container name). Refs #620, kagenti/kagenti#1458 Assisted-By: Claude (Anthropic AI) Signed-off-by: Hai Huang --- .github/dependabot.yml | 5 - .github/workflows/build.yaml | 16 +- .github/workflows/ci.yaml | 9 +- CLAUDE.md | 26 +- Makefile | 1 - README.md | 4 +- authbridge/CLAUDE.md | 29 +- authbridge/README.md | 6 +- authbridge/cmd/README.md | 13 +- authbridge/cmd/authbridge-envoy/main.go | 6 +- authbridge/cmd/authbridge-lite/Dockerfile | 44 -- authbridge/cmd/authbridge-lite/entrypoint.sh | 40 -- authbridge/cmd/authbridge-lite/go.mod | 73 --- authbridge/cmd/authbridge-lite/go.sum | 217 --------- authbridge/cmd/authbridge-lite/main.go | 420 ------------------ authbridge/cmd/authbridge-proxy/main.go | 27 +- .../cmd/authbridge-proxy/plugins_a2aparser.go | 5 + .../plugins_inferenceparser.go | 5 + .../cmd/authbridge-proxy/plugins_mcpparser.go | 5 + .../cmd/authbridge-proxy/plugins_opa.go | 5 + .../cmd/authbridge-proxy/plugins_sparc.go | 5 + .../authbridge-proxy/plugins_tokenbroker.go | 5 + authbridge/demos/README.md | 6 +- authbridge/docs/framework-architecture.md | 5 +- authbridge/docs/plugin-reference.md | 4 +- authbridge/go.work | 1 - local-build-and-test.sh | 10 +- 27 files changed, 120 insertions(+), 872 deletions(-) delete mode 100644 authbridge/cmd/authbridge-lite/Dockerfile delete mode 100755 authbridge/cmd/authbridge-lite/entrypoint.sh delete mode 100644 authbridge/cmd/authbridge-lite/go.mod delete mode 100644 authbridge/cmd/authbridge-lite/go.sum delete mode 100644 authbridge/cmd/authbridge-lite/main.go create mode 100644 authbridge/cmd/authbridge-proxy/plugins_a2aparser.go create mode 100644 authbridge/cmd/authbridge-proxy/plugins_inferenceparser.go create mode 100644 authbridge/cmd/authbridge-proxy/plugins_mcpparser.go create mode 100644 authbridge/cmd/authbridge-proxy/plugins_opa.go create mode 100644 authbridge/cmd/authbridge-proxy/plugins_sparc.go create mode 100644 authbridge/cmd/authbridge-proxy/plugins_tokenbroker.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dcd5bebf9..2007153b4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -22,11 +22,6 @@ updates: directory: /authbridge/cmd/authbridge-envoy schedule: interval: weekly - - package-ecosystem: gomod - directory: /authbridge/cmd/authbridge-lite - schedule: - interval: weekly - # Go - abctl TUI - package-ecosystem: gomod directory: /authbridge/cmd/abctl diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6503fc7bf..0ff8fd97f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -44,14 +44,17 @@ jobs: context: ./authbridge dockerfile: cmd/authbridge-proxy/Dockerfile - # AuthBridge proxy-sidecar lite combined image — - # authbridge-lite (auth-only plugins, parsers dropped for - # binary size) + spiffe-helper. Same listener layout as the - # full proxy image; not yet referenced by the operator's - # default config. + # AuthBridge proxy-sidecar LITE image — the SAME authbridge-proxy + # binary + Dockerfile, built with exclude_plugin_* tags so only + # jwt-validation + token-exchange compile in (drops the OPA SDK + # and the parsers, roughly halving the binary). A build variant, + # not a separate binary. Same listener layout as the full proxy + # image; not yet referenced by the operator's default config. - name: authbridge-lite context: ./authbridge - dockerfile: cmd/authbridge-lite/Dockerfile + dockerfile: cmd/authbridge-proxy/Dockerfile + build_args: | + GO_BUILD_TAGS=exclude_plugin_a2aparser,exclude_plugin_ibac,exclude_plugin_inferenceparser,exclude_plugin_mcpparser,exclude_plugin_opa,exclude_plugin_sparc,exclude_plugin_tokenbroker # SPARC reflection service — the backend the `sparc` plugin calls. # Deployed once per cluster via authbridge/sparc-service/deploy. @@ -112,6 +115,7 @@ jobs: with: context: ${{ matrix.image_config.context }} file: ${{ matrix.image_config.context }}/${{ matrix.image_config.dockerfile }} + build-args: ${{ matrix.image_config.build_args }} push: true platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a642f7325..31fd6aeed 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -76,7 +76,6 @@ jobs: binary: - authbridge-proxy - authbridge-envoy - - authbridge-lite defaults: run: working-directory: authbridge/cmd/${{ matrix.binary }} @@ -101,6 +100,14 @@ jobs: - name: Build run: go build -v ./... + # The authbridge-lite image is this same authbridge-proxy binary built + # with exclude_plugin_* tags (only jwt-validation + token-exchange). + # Verify that tag combination compiles on every PR — build.yaml only + # exercises it on tag/main pushes. + - name: Build lite variant (exclude_plugin_* tags) + if: matrix.binary == 'authbridge-proxy' + run: go build -v -tags "exclude_plugin_a2aparser,exclude_plugin_ibac,exclude_plugin_inferenceparser,exclude_plugin_mcpparser,exclude_plugin_opa,exclude_plugin_sparc,exclude_plugin_tokenbroker" ./... + - name: Test run: go test -v -race -cover ./... diff --git a/CLAUDE.md b/CLAUDE.md index 75e5e16dd..c2b60433b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,11 +39,8 @@ kagenti-extensions/ │ │ ├── main.go │ │ ├── Dockerfile # envoy-sidecar combined image │ │ └── entrypoint.sh -│ ├── cmd/authbridge-lite/ # proxy-sidecar mode, lite plugin set (no parsers) -│ │ │ # for size-optimized deployments -│ │ ├── main.go -│ │ ├── Dockerfile # proxy-sidecar lite 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 @@ -63,7 +60,7 @@ kagenti-extensions/ ### 1. AuthBridge Binaries (Go) -**Three mode-specific binaries** 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. +**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 @@ -72,7 +69,7 @@ kagenti-extensions/ **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. -- `cmd/authbridge-lite/` — proxy-sidecar mode, lite plugin set (auth gates only, parsers dropped) for size-optimized deployments. +- `authbridge-lite` (image, **not** a separate binary) — `cmd/authbridge-proxy` built with `exclude_plugin_*` tags so only jwt-validation + token-exchange compile in (OPA + parsers dropped), for size-optimized deployments. Individual plugins live in `cmd/authbridge-proxy/plugins_.go` files gated by `//go:build !exclude_plugin_`. **Common:** - `authlib/` — shared auth library (JWT validation, token exchange, caching, routing, all listener implementations, all plugins). @@ -128,11 +125,11 @@ Three mode-specific binaries, one Dockerfile per binary: |--------|------|-----------|---------| | `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) | -| `cmd/authbridge-lite/` | proxy-sidecar | HTTP forward + reverse proxies | auth-only (jwt-validation + token-exchange, no parsers) | +| `authbridge-lite` _(image: proxy + `exclude_plugin_*`)_ | proxy-sidecar | HTTP forward + reverse proxies | auth-only (jwt-validation + token-exchange; OPA + parsers dropped) | **Go modules:** - `authbridge/authlib/` — pure library: validation, exchange, cache, bypass, spiffe, routing, auth, config, all listener implementations, all plugins. -- `authbridge/cmd/authbridge-{proxy,envoy,lite}/` — thin main packages that import authlib and start the listeners they need. +- `authbridge/cmd/authbridge-{proxy,envoy}/` — thin main packages that import authlib and start the listeners they need. (The `authbridge-lite` image is `authbridge-proxy` built with `exclude_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). @@ -142,7 +139,7 @@ Three mode-specific binaries, one Dockerfile per binary: | 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-sidecar lite combined) | +| `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 — auth-only) | | `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 | @@ -173,7 +170,7 @@ All images are pushed to `ghcr.io/kagenti/kagenti-extensions/` from |-------|--------|-------------| | **`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-lite/Dockerfile` | proxy-sidecar lite combined image: authbridge-lite (auth gates only, parsers dropped) + spiffe-helper. Same listener layout as `authbridge`; not yet referenced by the operator's default config | +| `authbridge-lite` | `authbridge/cmd/authbridge-proxy/Dockerfile` (+ `GO_BUILD_TAGS=exclude_plugin_*`) | proxy-sidecar combined image built auth-only (jwt-validation + token-exchange; OPA + parsers dropped) + spiffe-helper. A build variant of `authbridge`, not a separate binary; not yet referenced by the operator's default config | | `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 @@ -251,10 +248,13 @@ To 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 / lite) +# 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 . -cd authbridge && podman build -f cmd/authbridge-lite/Dockerfile -t authbridge-lite:latest . +# authbridge-lite: same proxy Dockerfile, built with exclude_plugin_* tags (auth-only) +cd authbridge && podman build -f cmd/authbridge-proxy/Dockerfile \ + --build-arg GO_BUILD_TAGS="exclude_plugin_a2aparser,exclude_plugin_ibac,exclude_plugin_inferenceparser,exclude_plugin_mcpparser,exclude_plugin_opa,exclude_plugin_sparc,exclude_plugin_tokenbroker" \ + -t authbridge-lite:latest . ``` ### Running the Full Demo diff --git a/Makefile b/Makefile index 17110ed9e..c33a06cbf 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,6 @@ fmt: ## Run formatters across all sub-projects cd authbridge/cmd/abctl && go fmt ./... cd authbridge/cmd/authbridge-proxy && go fmt ./... cd authbridge/cmd/authbridge-envoy && go fmt ./... - cd authbridge/cmd/authbridge-lite && go fmt ./... ruff format authbridge/ pre-commit: ## Install pre-commit hooks (including commit-msg) diff --git a/README.md b/README.md index 8bc559960..82aeb3f9e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Kubernetes security extensions for the [Kagenti](https://github.com/kagenti/kage - **Mode-specific binaries** under [`authbridge/cmd/`](./authbridge/cmd/): - [`authbridge-proxy`](./authbridge/cmd/authbridge-proxy/) — proxy-sidecar (default): HTTP forward + reverse proxies, full plugin set. - [`authbridge-envoy`](./authbridge/cmd/authbridge-envoy/) — envoy-sidecar: ext_proc gRPC server hooked into Envoy, full plugin set. - - [`authbridge-lite`](./authbridge/cmd/authbridge-lite/) — proxy-sidecar with auth-only plugins (no parsers); for size-constrained deployments. + - `authbridge-lite` — **not a separate binary**: the `authbridge-lite` image is `authbridge-proxy` built with `exclude_plugin_*` tags (auth-only: jwt-validation + token-exchange), for size-constrained deployments. See [`authbridge-proxy`](./authbridge/cmd/authbridge-proxy/). - **[proxy-init](./authbridge/proxy-init/)** — iptables init container used by envoy-sidecar mode for transparent traffic interception. - **[Keycloak Sync](./authbridge/keycloak_sync.py)** — Declarative tool for synchronizing Keycloak configuration. @@ -29,7 +29,7 @@ mode-specific combined images, and the per-component sidecars |-------|-------------| | `authbridge` | proxy-sidecar combined (default): authbridge-proxy + bundled spiffe-helper, full plugin set | | `authbridge-envoy` | envoy-sidecar combined: Envoy + ext_proc + bundled spiffe-helper, full plugin set | -| `authbridge-lite` | proxy-sidecar shape, auth-only plugins (no parsers) for size-constrained deployments | +| `authbridge-lite` | `authbridge-proxy` built with `exclude_plugin_*` tags — auth-only (jwt-validation + token-exchange, OPA + parsers dropped), for size-constrained deployments. A build variant, not a separate binary | | `proxy-init` | Alpine + iptables init container (envoy-sidecar mode only) | `spiffe-helper` is bundled inside each combined image and gated per diff --git a/authbridge/CLAUDE.md b/authbridge/CLAUDE.md index 3670286b7..7a7a0b95e 100644 --- a/authbridge/CLAUDE.md +++ b/authbridge/CLAUDE.md @@ -10,14 +10,17 @@ The unified `cmd/authbridge/` binary has been split into three mode-specific binaries with shared auth logic in `authlib/`: - `cmd/authbridge-proxy/` — proxy-sidecar mode (default). HTTP forward + reverse - proxies. Full plugin set (jwt-validation, token-exchange, a2a-parser, - mcp-parser, inference-parser, ibac, sparc). Ibac is excludable via - `-tags exclude_plugin_ibac`. + proxies. Compiles in every plugin by default (jwt-validation, token-exchange, + a2a-parser, mcp-parser, inference-parser, opa, sparc, ibac, token-broker). + Every plugin except jwt-validation + token-exchange is excludable via + `-tags exclude_plugin_` — one `plugins_.go` file per plugin, + gated by `//go:build !exclude_plugin_`. - `cmd/authbridge-envoy/` — envoy-sidecar mode. ext_proc gRPC server hooked - into Envoy. Full plugin set. Same build-tag exclusions apply. -- `cmd/authbridge-lite/` — proxy-sidecar mode, lite plugin set (auth gates - only, parsers dropped). For size-optimized deployments that don't need - protocol-aware session events. + into Envoy. Full plugin set. The `exclude_plugin_ibac` tag applies here too. +- `authbridge-lite` (**image, not a separate binary**) — `cmd/authbridge-proxy` + built with `exclude_plugin_*` tags so only jwt-validation + token-exchange + compile in (OPA + parsers dropped). For size-optimized deployments that + don't need protocol-aware session events. Each binary is hardcoded to its deployment shape; mode is no longer selected at runtime. The YAML `mode:` field must match the binary or boot fails. @@ -58,11 +61,6 @@ authbridge/ │ ├── Dockerfile # envoy-sidecar combined image (Envoy + authbridge-envoy) │ └── entrypoint.sh │ -├── cmd/authbridge-lite/ # proxy-sidecar mode. Lite plugin set (no parsers). -│ ├── main.go -│ ├── Dockerfile # proxy-sidecar lite combined image -│ └── entrypoint.sh -│ ├── proxy-init/ # iptables init container (envoy-sidecar + proxy-sidecar enforce-redirect modes) │ ├── init-iptables.sh # iptables setup script │ ├── Dockerfile.init # proxy-init container image @@ -362,7 +360,10 @@ make load-image # Uses KIND_CLUSTER_NAME env var (default: k cd .. podman build -f cmd/authbridge-proxy/Dockerfile -t authbridge:latest . # proxy-sidecar (default) podman build -f cmd/authbridge-envoy/Dockerfile -t authbridge-envoy:latest . # envoy-sidecar -podman build -f cmd/authbridge-lite/Dockerfile -t authbridge-lite:latest . # proxy-sidecar lite +# authbridge-lite: the proxy Dockerfile built with exclude_plugin_* tags (auth-only) +podman build -f cmd/authbridge-proxy/Dockerfile \ + --build-arg GO_BUILD_TAGS="exclude_plugin_a2aparser,exclude_plugin_ibac,exclude_plugin_inferenceparser,exclude_plugin_mcpparser,exclude_plugin_opa,exclude_plugin_sparc,exclude_plugin_tokenbroker" \ + -t authbridge-lite:latest . kind load docker-image authbridge:latest --name kagenti kind load docker-image authbridge-envoy:latest --name kagenti kind load docker-image authbridge-lite:latest --name kagenti @@ -499,7 +500,7 @@ See [`docs/framework-architecture.md`](docs/framework-architecture.md#9-config-h - Go 1.25 - Modules: `authbridge/authlib/` (pure library — all listeners, all plugins) and `authbridge/cmd/authbridge-{proxy,envoy,lite}/` (mode-specific binaries that wire listeners + plugins together) - `authbridge/go.work` workspace links the modules for local development -- Logging with `log/slog`; the binaries log under their own name (`authbridge-proxy`, `authbridge-envoy`, `authbridge-lite`) +- Logging with `log/slog`; the binaries log under their own name (`authbridge-proxy`, `authbridge-envoy`). Note the `authbridge-lite` image runs the `authbridge-proxy` binary, so it logs as `authbridge-proxy`. - gRPC ext-proc using `envoyproxy/go-control-plane` types (in `authlib/listener/extproc`) - JWT validation with `lestrrat-go/jwx/v2` (in `authlib/plugins/jwtvalidation/validation`) diff --git a/authbridge/README.md b/authbridge/README.md index 903920db6..22b8262aa 100644 --- a/authbridge/README.md +++ b/authbridge/README.md @@ -12,13 +12,13 @@ The [`cmd/authbridge/`](./cmd/authbridge/) directory contains a unified binary t |-------|----------| | `authbridge` | proxy-sidecar combined: authbridge-proxy binary + bundled spiffe-helper | | `authbridge-envoy` | envoy-sidecar combined: Envoy + ext_proc + bundled spiffe-helper | -| `authbridge-lite` | proxy-sidecar shape, auth-only plugins (no parsers) | +| `authbridge-lite` | `authbridge-proxy` built with `exclude_plugin_*` tags — auth-only (jwt-validation + token-exchange; OPA + parsers dropped). A build variant, not a separate binary | | Mode | Image | Use Case | How It Works | |------|-------|----------|-------------| | `proxy-sidecar` (default) | `authbridge` | HTTP_PROXY-based forward + reverse proxies | Agent routes outbound traffic through forward proxy; reverse proxy validates inbound JWTs | | `envoy-sidecar` | `authbridge-envoy` | Transparent interception via iptables | Envoy intercepts all traffic, delegates auth to authbridge via ext_proc gRPC | -| `lite` | `authbridge-lite` | Same shape as proxy-sidecar with auth-only plugins (no parsers) | For size-constrained deployments that don't need protocol-aware session events | +| `lite` | `authbridge-lite` | The `authbridge-proxy` binary built with `exclude_plugin_*` tags (auth-only: jwt-validation + token-exchange) | For size-constrained deployments that don't need protocol-aware session events | The kagenti-operator resolves the mode per workload from `AgentRuntime.Spec.AuthBridgeMode` → namespace ConfigMap → deprecated `kagenti.io/authbridge-mode` annotation → cluster default (`proxy-sidecar`). See kagenti-operator#361. @@ -448,7 +448,7 @@ plugin package from being imported and compiled into the binary. - [authlib](authlib/README.md) — Shared auth building blocks (Go library) - [cmd/authbridge-proxy](cmd/authbridge-proxy/) — proxy-sidecar binary (default mode, full plugin set) - [cmd/authbridge-envoy](cmd/authbridge-envoy/) — envoy-sidecar binary (Envoy + ext_proc, full plugin set) -- [cmd/authbridge-lite](cmd/authbridge-lite/) — auth-only proxy-sidecar binary (no parsers) +- `authbridge-lite` image — `cmd/authbridge-proxy` built with `exclude_plugin_*` tags (auth-only); a build variant, not a separate binary - [proxy-init](proxy-init/README.md) — iptables init container (envoy-sidecar mode only) - [docs/](docs/) — framework architecture and plugin author references diff --git a/authbridge/cmd/README.md b/authbridge/cmd/README.md index aace8b438..db089b489 100644 --- a/authbridge/cmd/README.md +++ b/authbridge/cmd/README.md @@ -11,7 +11,7 @@ binary you run, not at runtime via a flag. |---|---|---|---|---| | [`authbridge-proxy/`](authbridge-proxy/) | `proxy-sidecar` (default) | HTTP forward + reverse proxies | full (jwt-validation, token-exchange, a2a-parser, mcp-parser, inference-parser) | `ghcr.io/kagenti/kagenti-extensions/authbridge` | | [`authbridge-envoy/`](authbridge-envoy/) | `envoy-sidecar` | gRPC ext_proc on `:9090` (hooked into Envoy) | full | `ghcr.io/kagenti/kagenti-extensions/authbridge-envoy` | -| [`authbridge-lite/`](authbridge-lite/) | `proxy-sidecar` | HTTP forward + reverse proxies | lite (jwt-validation + token-exchange only — parsers dropped) | `ghcr.io/kagenti/kagenti-extensions/authbridge-lite` | +| `authbridge-lite` _(build variant of `authbridge-proxy`)_ | `proxy-sidecar` | HTTP forward + reverse proxies | lite — `authbridge-proxy` built with `exclude_plugin_*` tags (jwt-validation + token-exchange only; OPA + parsers dropped) | `ghcr.io/kagenti/kagenti-extensions/authbridge-lite` | | [`abctl/`](abctl/) | n/a | n/a | n/a | not published — local TUI for the Session Events API | Each binary directory contains `main.go`, `go.mod`/`go.sum`, @@ -34,7 +34,7 @@ ConfigMap contracts are documented in ## Ports -**Proxy-sidecar (`authbridge-proxy`, `authbridge-lite`):** +**Proxy-sidecar (`authbridge-proxy`, and its `authbridge-lite` image variant):** | Port | Purpose | |---|---| @@ -60,7 +60,8 @@ ConfigMap contracts are documented in - **Need ambient/transparent interception via Envoy**: use `authbridge-envoy`. Requires the [`proxy-init`](../authproxy/) iptables init container. -- **Size-constrained, no protocol-aware events needed**: use - `authbridge-lite`. Same listener layout as `authbridge-proxy` but - without parsers — abctl will only see denial events and basic - auth-level invocations, not full A2A/MCP/Inference protocol context. +- **Size-constrained, no protocol-aware events needed**: use the + `authbridge-lite` image — the `authbridge-proxy` binary built with + `exclude_plugin_*` tags (auth-only). Same listener layout, but without + parsers/OPA — abctl will only see denial events and basic auth-level + invocations, not full A2A/MCP/Inference protocol context. diff --git a/authbridge/cmd/authbridge-envoy/main.go b/authbridge/cmd/authbridge-envoy/main.go index 439b04fb0..41d99b284 100644 --- a/authbridge/cmd/authbridge-envoy/main.go +++ b/authbridge/cmd/authbridge-envoy/main.go @@ -6,9 +6,9 @@ // // Mode is hardcoded to envoy-sidecar; YAML configs that specify a // different mode are rejected at boot. For proxy-sidecar mode (HTTP -// forward/reverse proxies, no Envoy), use cmd/authbridge-proxy. For a -// size-optimized proxy-sidecar build with parsers dropped, use -// cmd/authbridge-lite. +// forward/reverse proxies, no Envoy), use cmd/authbridge-proxy — which +// also produces the size-optimized authbridge-lite image when built +// with exclude_plugin_* tags. package main import ( diff --git a/authbridge/cmd/authbridge-lite/Dockerfile b/authbridge/cmd/authbridge-lite/Dockerfile deleted file mode 100644 index bfbe7c0de..000000000 --- a/authbridge/cmd/authbridge-lite/Dockerfile +++ /dev/null @@ -1,44 +0,0 @@ -# AuthBridge proxy-sidecar lite combined image — authbridge-lite -# (jwt-validation + token-exchange only, no parsers) in a single -# container. -# -# Same listener layout as the full authbridge-proxy image, but with -# the a2a / mcp / inference parser plugins dropped to optimize binary -# size for deployments that don't need protocol-aware session events -# in abctl. Functionally identical for inbound JWT validation + -# outbound token exchange. -# -# SPIFFE credentials are obtained in-process via go-spiffe's -# WorkloadAPI client (no spiffe-helper sidecar binary). -# -# Build context: ./authbridge (needs access to authlib/ and cmd/authbridge-lite/) - -# Stage 1: Build authbridge-lite binary. -# `-ldflags="-s -w"` drops the symbol table and DWARF debug info to -# shave ~30% off the binary. Go's runtime keeps `pclntab` separately, -# so panic stack traces still show function names. -FROM golang:1.25-alpine@sha256:a2ea69f8f07fe886989c61e9da297f932ddf50b42dad2f44d2664710c94dca37 AS builder - -RUN apk add --no-cache git - -WORKDIR /app - -COPY authlib/ authlib/ -COPY cmd/authbridge-lite/ cmd/authbridge-lite/ - -ENV GOWORK=off -RUN cd cmd/authbridge-lite && CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /authbridge-lite . - -# Stage 2: Runtime — alpine (has bash, ~5 MB base) -FROM alpine:3.20@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d - -RUN apk add --no-cache bash ca-certificates - -COPY --from=builder --chmod=755 /authbridge-lite /usr/local/bin/authbridge-lite -COPY --chmod=755 cmd/authbridge-lite/entrypoint.sh /usr/local/bin/entrypoint.sh - -USER 1001 - -EXPOSE 8080 8081 9091 9093 9094 - -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/authbridge/cmd/authbridge-lite/entrypoint.sh b/authbridge/cmd/authbridge-lite/entrypoint.sh deleted file mode 100755 index 155587113..000000000 --- a/authbridge/cmd/authbridge-lite/entrypoint.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -set -eu - -# AuthBridge proxy-sidecar lite combined entrypoint with process supervision. -# Manages: authbridge-lite. -# -# Startup order: -# 1. authbridge-lite (background) — HTTP forward + reverse proxies -# -# Process management: PID 1 (this shell) supervises every long-running -# critical process. If any critical process exits, the others are killed -# and the container exits non-zero so Kubernetes restarts it. SIGTERM / -# SIGINT are forwarded for graceful shutdown. - -CRITICAL_PIDS="" - -cleanup() { - echo "[entrypoint] Received signal, shutting down..." - # shellcheck disable=SC2086 - kill $CRITICAL_PIDS 2>/dev/null || true - wait - exit 0 -} -trap cleanup TERM INT - -# --- Phase 1: authbridge-lite (HTTP forward + reverse proxies) --- -echo "[entrypoint] Starting authbridge-lite..." -/usr/local/bin/authbridge-lite "$@" & -CRITICAL_PIDS="$CRITICAL_PIDS $!" - -# Block until any critical process exits, then terminate the container -# so Kubernetes restarts the pod. -# shellcheck disable=SC2086 -wait -n $CRITICAL_PIDS -EXIT_CODE=$? -echo "[entrypoint] A critical process exited unexpectedly (exit code $EXIT_CODE), terminating container" -# shellcheck disable=SC2086 -kill $CRITICAL_PIDS 2>/dev/null || true -wait -exit 1 diff --git a/authbridge/cmd/authbridge-lite/go.mod b/authbridge/cmd/authbridge-lite/go.mod deleted file mode 100644 index eb3842e3c..000000000 --- a/authbridge/cmd/authbridge-lite/go.mod +++ /dev/null @@ -1,73 +0,0 @@ -module github.com/kagenti/kagenti-extensions/authbridge/cmd/authbridge-lite - -go 1.25.0 - -replace github.com/kagenti/kagenti-extensions/authbridge/authlib => ../../authlib - -require github.com/kagenti/kagenti-extensions/authbridge/authlib v0.0.0-00010101000000-000000000000 - -require ( - github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/agnivade/levenshtein v1.2.1 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/containerd v1.7.32 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.10.1 // indirect - github.com/go-ini/ini v1.67.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.4 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/goccy/go-json v0.10.3 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/mux v1.8.1 // indirect - github.com/klauspost/compress v1.18.0 // indirect - github.com/lestrrat-go/blackmagic v1.0.3 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc v1.0.6 // indirect - github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx/v2 v2.1.6 // indirect - github.com/lestrrat-go/option v1.0.1 // indirect - github.com/moby/locker v1.0.1 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/open-policy-agent/opa v1.4.2 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/prometheus/client_golang v1.21.1 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect - github.com/segmentio/asm v1.2.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect - github.com/tchap/go-patricia/v2 v2.3.2 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/yashtewari/glob-intersection v0.2.0 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/net v0.56.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/text v0.38.0 // indirect - golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect - google.golang.org/grpc v1.81.1 // indirect - google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - oras.land/oras-go/v2 v2.5.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect -) diff --git a/authbridge/cmd/authbridge-lite/go.sum b/authbridge/cmd/authbridge-lite/go.sum deleted file mode 100644 index 561abbed9..000000000 --- a/authbridge/cmd/authbridge-lite/go.sum +++ /dev/null @@ -1,217 +0,0 @@ -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= -github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= -github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= -github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= -github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= -github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= -github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/containerd v1.7.32 h1:S54xuVcPxeLaYgaRABtpJ2VyVUVsy0IGf7qHBs+sbY8= -github.com/containerd/containerd v1.7.32/go.mod h1:jdwD6s/BhV4XVJGrvtziNPVA+83n66TwptVaPKprq4E= -github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= -github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= -github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= -github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= -github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= -github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= -github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= -github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= -github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= -github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= -github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= -github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs= -github.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= -github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= -github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= -github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA= -github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= -github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= -github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= -github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= -github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/open-policy-agent/opa v1.4.2 h1:ag4upP7zMsa4WE2p1pwAFeG4Pn3mNwfAx9DLhhJfbjU= -github.com/open-policy-agent/opa v1.4.2/go.mod h1:DNzZPKqKh4U0n0ANxcCVlw8lCSv2c+h5G/3QvSYdWZ8= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= -github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= -github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM= -github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= -github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= -gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= -gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= -google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= -google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= -oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/authbridge/cmd/authbridge-lite/main.go b/authbridge/cmd/authbridge-lite/main.go deleted file mode 100644 index 0f1199d0d..000000000 --- a/authbridge/cmd/authbridge-lite/main.go +++ /dev/null @@ -1,420 +0,0 @@ -// Package main is the "lite" authbridge binary: proxy-sidecar mode -// only (no Envoy), with jwt-validation + token-exchange as the only -// plugins compiled in. Optimizes for binary size by dropping the -// gRPC / envoy go-control-plane dependency tree and the -// a2a/mcp/inference parser plugins. -// -// For full-featured proxy-sidecar deployments (with the parsers, so -// abctl can render protocol-aware session events), use -// cmd/authbridge-proxy. For envoy-sidecar mode, use cmd/authbridge-envoy. -package main - -import ( - "context" - "flag" - "fmt" - "log" - "log/slog" - "net" - "net/http" - "os" - "os/signal" - "strings" - "syscall" - "time" - - "github.com/kagenti/kagenti-extensions/authbridge/authlib/auth" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/config" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/observe" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/pipeline" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/reloader" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/session" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/sessionapi" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/shared" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/spiffe" - authtls "github.com/kagenti/kagenti-extensions/authbridge/authlib/tls" - - // Only HTTP listeners are compiled in: no extproc/extauthz - // (no gRPC, no envoy types). - "github.com/kagenti/kagenti-extensions/authbridge/authlib/listener/forwardproxy" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/listener/reverseproxy" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/listener/skiphost" - "github.com/kagenti/kagenti-extensions/authbridge/authlib/listener/transparentproxy" - - // Auth gates only: drop the parsers and token-broker. - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/jwtvalidation" - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/opa" - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/tokenbroker" - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/tokenexchange" -) - -var logLevel = new(slog.LevelVar) - -func initLogging() { - switch strings.ToLower(os.Getenv("LOG_LEVEL")) { - case "debug": - logLevel.Set(slog.LevelDebug) - case "warn": - logLevel.Set(slog.LevelWarn) - case "error": - logLevel.Set(slog.LevelError) - default: - logLevel.Set(slog.LevelInfo) - } - slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel}))) -} - -func startSignalToggle() { - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGUSR1) - go func() { - for range sigCh { - if logLevel.Level() == slog.LevelDebug { - logLevel.Set(slog.LevelInfo) - slog.Info("log level toggled to INFO (send SIGUSR1 to switch back to DEBUG)") - } else { - logLevel.Set(slog.LevelDebug) - slog.Info("log level toggled to DEBUG (send SIGUSR1 to switch back to INFO)") - } - } - }() -} - -func main() { - configPath := flag.String("config", "", "path to config YAML file") - flag.Parse() - - initLogging() - startSignalToggle() - - if *configPath == "" { - log.Fatal("--config is required") - } - - // Build the SPIFFE Provider when the spiffe block is configured. The - // Provider drives both mTLS (via X509Source) and token-exchange's - // spiffe identity (via JWTSource). Construction blocks until the first - // X.509-SVID arrives (cold-start gate); kubelet restarts on failure. - // - // We need cfg first to read the spiffe block, so do a one-shot Load - // before buildPipelines runs (buildPipelines re-Loads internally for - // hot-reload). The Provider is captured by buildPipelines via closure - // so reload-time pipeline rebuilds inject the same Provider into - // freshly constructed plugin instances. - bootCfg, err := config.Load(*configPath) - if err != nil { - log.Fatalf("initial config load: %v", err) - } - var provider *spiffe.Provider - if bootCfg.SPIFFE != nil { - mirrorFiles := true - if bootCfg.SPIFFE.MirrorFiles != nil { - mirrorFiles = *bootCfg.SPIFFE.MirrorFiles - } - provider, err = spiffe.NewProvider(context.Background(), spiffe.ProviderConfig{ - SocketPath: bootCfg.SPIFFE.Socket, - MirrorFiles: mirrorFiles, - MirrorDir: bootCfg.SPIFFE.MirrorDir, - }) - if err != nil { - log.Fatalf("spiffe provider: %v", err) - } - defer provider.Close() - } - - // This binary is hardcoded to proxy-sidecar. Rejecting other modes - // early gives operators a clear boot-time error instead of silently - // misbehaving (e.g., YAML says envoy-sidecar but binary can't - // serve ext_proc). - buildPipelines := func() (*pipeline.Pipeline, *pipeline.Pipeline, *config.Config, error) { - c, err := config.Load(*configPath) - if err != nil { - return nil, nil, nil, err - } - if c.Mode != "" && c.Mode != config.ModeProxySidecar { - return nil, nil, nil, fmt.Errorf( - "authbridge-lite supports only mode=%q (got %q); use cmd/authbridge-envoy for envoy-sidecar mode", - config.ModeProxySidecar, c.Mode) - } - c.Mode = config.ModeProxySidecar - config.ApplyPreset(c) - if err := config.Validate(c); err != nil { - return nil, nil, nil, err - } - config.WarnEmptyPipelines(c, slog.Default()) - in, err := plugins.BuildWithSPIFFE(c.Pipeline.Inbound.Plugins, provider) - if err != nil { - return nil, nil, nil, fmt.Errorf("inbound: %w", err) - } - out, err := plugins.BuildWithSPIFFE(c.Pipeline.Outbound.Plugins, provider) - if err != nil { - return nil, nil, nil, fmt.Errorf("outbound: %w", err) - } - return in, out, c, nil - } - - inboundPipeline, outboundPipeline, cfg, err := buildPipelines() - if err != nil { - log.Fatalf("initial pipeline build: %v", err) - } - - initCtx, initCancel := context.WithTimeout(context.Background(), 60*time.Second) - defer initCancel() - if err := inboundPipeline.Start(initCtx); err != nil { - log.Fatalf("inbound pipeline Start: %v", err) - } - if err := outboundPipeline.Start(initCtx); err != nil { - log.Fatalf("outbound pipeline Start: %v", err) - } - - inboundH := pipeline.NewHolder(inboundPipeline) - outboundH := pipeline.NewHolder(outboundPipeline) - - ctx, cancelCtx := context.WithCancel(context.Background()) - defer cancelCtx() - rld := reloader.New(*configPath, inboundH, outboundH, buildPipelines, cfg) - if err := rld.Start(ctx); err != nil { - log.Fatalf("reloader: %v", err) - } - - var sessions *session.Store - if cfg.Session.SessionEnabled() { - ttl := 30 * time.Minute - if cfg.Session.TTL != "" { - if d, err := time.ParseDuration(cfg.Session.TTL); err == nil { - ttl = d - } else { - slog.Warn("invalid session.ttl, using default", "value", cfg.Session.TTL, "error", err) - } - } - maxEvents := 500 // raised from 100: recording every message (incl. no-plugin-activity) ~doubles volume - if cfg.Session.MaxEvents > 0 { - maxEvents = cfg.Session.MaxEvents - } - maxSessions := 100 - if cfg.Session.MaxSessions > 0 { - maxSessions = cfg.Session.MaxSessions - } - sessions = session.New(ttl, maxEvents, maxSessions) - slog.Info("session tracking enabled", "ttl", ttl, "maxEvents", maxEvents, "maxSessions", maxSessions) - } else { - slog.Info("session tracking disabled") - } - - var httpServers []*http.Server - - // mTLS: see authbridge-proxy/main.go for the architectural notes — - // this is the size-optimized variant of the same wiring. - var ( - rpMTLS *reverseproxy.MTLSOptions - fpMTLS *forwardproxy.MTLSOptions - mtlsMetrics *authtls.Metrics - ) - if cfg.MTLS != nil { - if provider == nil { - log.Fatal("mtls requires the spiffe block to be configured") - } - strict := cfg.MTLS.ResolvedMode() == config.MTLSModeStrict - src := provider.X509Source() - mtlsMetrics = authtls.NewMetrics() - // Inbound permissive peeks-and-routes; strict rejects non-TLS. - rpMTLS = &reverseproxy.MTLSOptions{Source: src, Strict: strict, Metrics: mtlsMetrics} - // Outbound: TLS-or-fail in strict only. Permissive is plaintext - // outbound — see authbridge-proxy/main.go for the architectural - // note on why proxy-sidecar's outbound semantics now match - // envoy-sidecar's (no per-connection try-TLS-with-fallback). - if strict { - fpMTLS = &forwardproxy.MTLSOptions{Source: src, Metrics: mtlsMetrics} - } - slog.Info("mTLS enabled", "mode", cfg.MTLS.ResolvedMode()) - } else { - slog.Info("mTLS disabled (no mtls block in config)") - } - - // Proxy-sidecar: reverse proxy on the inbound path + forward proxy - // on the outbound path. - rpSrv, err := reverseproxy.NewServer(inboundH, sessions, cfg.Listener.ReverseProxyBackend, rpMTLS) - if err != nil { - log.Fatalf("creating reverse proxy: %v", err) - } - fpSrv, err := forwardproxy.NewServer(outboundH, sessions, fpMTLS) - if err != nil { - log.Fatalf("creating forward proxy: %v", err) - } - // SkipHosts: outbound destinations that bypass the pipeline AND - // session recording entirely. See ListenerConfig.SkipHosts. - skipHosts, err := skiphost.New(cfg.Listener.SkipHosts) - if err != nil { - log.Fatalf("listener.skip_hosts: %v", err) - } - fpSrv.SkipHosts = skipHosts - sharedStore := shared.New() - defer sharedStore.Close() // stop the TTL janitor on normal main return - rpSrv.Shared = sharedStore - fpSrv.Shared = sharedStore - httpServers = append(httpServers, startReverseProxyServer("reverse-proxy", rpSrv, cfg.Listener.ReverseProxyAddr)) - httpServers = append(httpServers, startHTTPServer("forward-proxy", fpSrv.Handler(), cfg.Listener.ForwardProxyAddr)) - - // Outbound transparent listener (enforce-redirect mode); shares the forward - // proxy's outbound pipeline. Closed explicitly on shutdown. - transparentLn := startTransparentProxy(fpSrv, cfg.Listener.TransparentProxyAddr) - - _ = mtlsMetrics // TODO Phase 2: surface metrics through /stats - - statsProvider := func() *auth.Stats { - sources := plugins.CollectStats(inboundH.Load()) - sources = append(sources, plugins.CollectStats(outboundH.Load())...) - return auth.MergeStats(sources...) - } - statSrv := startStatServer(cfg, rld.ConfigProvider(), statsProvider, rld.Handler()) - - // Warm the plugin catalog at boot so any factory that violates the - // constructor contract surfaces here rather than on the first - // /v1/plugins request. - plugins.WarmCatalog() - - var sessionAPISrv *sessionapi.Server - if cfg.Listener.SessionAPIAddr != "" && sessions != nil { - sessionAPISrv = sessionapi.New( - cfg.Listener.SessionAPIAddr, - sessions, - sessionapi.WithPipelines(inboundH, outboundH), - sessionapi.WithCatalog(sessionapi.PluginsCatalog), - ) - go func() { - slog.Warn("session API listening — UNAUTHENTICATED; contains raw user content; never expose via ingress", - "addr", cfg.Listener.SessionAPIAddr) - if err := sessionAPISrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("session API: %v", err) - } - }() - } - - slog.Info("authbridge-lite starting", "mode", cfg.Mode, "logLevel", logLevel.Level().String()) - - go func() { - mux := http.NewServeMux() - mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) { - if name := inboundH.NotReadyPlugin(); name != "" { - http.Error(w, "inbound plugin not ready: "+name, http.StatusServiceUnavailable) - return - } - if name := outboundH.NotReadyPlugin(); name != "" { - http.Error(w, "outbound plugin not ready: "+name, http.StatusServiceUnavailable) - return - } - w.WriteHeader(http.StatusOK) - }) - slog.Info("health server listening", "addr", ":9091") - if err := http.ListenAndServe(":9091", mux); err != nil { - slog.Warn("health server failed", "error", err) - } - }() - - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) - sig := <-sigCh - slog.Info("shutting down", "signal", sig) - - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second) - defer shutdownCancel() - - for _, srv := range httpServers { - srv.Shutdown(shutdownCtx) - } - if transparentLn != nil { - _ = transparentLn.Close() - } - statSrv.Shutdown(shutdownCtx) - if sessionAPISrv != nil { - sessionAPISrv.Shutdown(shutdownCtx) - } - - outboundPipeline.Stop(shutdownCtx) - inboundPipeline.Stop(shutdownCtx) - - if sessions != nil { - sessions.Close() - } -} - -func startHTTPServer(name string, handler http.Handler, addr string) *http.Server { - srv := &http.Server{ - Addr: addr, - Handler: handler, - ReadHeaderTimeout: 10 * time.Second, - } - go func() { - slog.Info("HTTP server listening", "name", name, "addr", addr) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("%s serve: %v", name, err) - } - }() - return srv -} - -// startTransparentProxy binds the outbound transparent listener (enforce-redirect -// mode) and serves it in a goroutine, dispatching each REDIRECTed connection -// through the forward proxy's outbound pipeline. Returns the listener for -// shutdown, or nil when addr is empty. Bind failures are fatal — enforce-redirect -// iptables would otherwise REDIRECT to a dead port and break all egress silently. -func startTransparentProxy(fp *forwardproxy.Server, addr string) *net.TCPListener { - if addr == "" { - return nil - } - la, err := net.ResolveTCPAddr("tcp", addr) - if err != nil { - log.Fatalf("resolve transparent-proxy addr %q: %v", addr, err) - } - ln, err := net.ListenTCP("tcp", la) - if err != nil { - log.Fatalf("transparent-proxy listen on %q: %v", addr, err) - } - srv := transparentproxy.NewServer(fp.HandleTransparentConn) - go func() { - slog.Info("transparent proxy listening", "addr", addr) - if err := srv.Serve(ln); err != nil { - log.Fatalf("transparent-proxy serve: %v", err) - } - }() - return ln -} - -// startReverseProxyServer mirrors startHTTPServer but routes through -// reverseproxy.Server.Listen() so the byte-peek TLS-sniffing listener -// is wired in when mTLS is enabled. Same shape as the equivalent -// helper in cmd/authbridge-proxy. -func startReverseProxyServer(name string, rp *reverseproxy.Server, addr string) *http.Server { - srv := &http.Server{ - Addr: addr, - Handler: rp.Handler(), - ReadHeaderTimeout: 10 * time.Second, - } - listener, err := rp.Listen(addr) - if err != nil { - log.Fatalf("%s listen: %v", name, err) - } - go func() { - slog.Info("HTTP server listening", "name", name, "addr", addr, "mtls", rp.MTLSEnabled()) - if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { - log.Fatalf("%s serve: %v", name, err) - } - }() - return srv -} - -func startStatServer(cfg *config.Config, cfgProvider observe.ConfigProvider, statsProvider observe.StatsProvider, reloadStatus http.Handler) *observe.StatServer { - srv := observe.NewStatServer(cfg.Stats.StatsAddress, cfgProvider, statsProvider, - observe.WithReloadStatus(reloadStatus)) - go func() { - slog.Info("stat server listening", "addr", cfg.Stats.StatsAddress) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("stat server: %v", err) - } - }() - return srv -} diff --git a/authbridge/cmd/authbridge-proxy/main.go b/authbridge/cmd/authbridge-proxy/main.go index 4f6306bab..c70d5fe04 100644 --- a/authbridge/cmd/authbridge-proxy/main.go +++ b/authbridge/cmd/authbridge-proxy/main.go @@ -1,12 +1,17 @@ // Package main is the proxy-sidecar authbridge binary: HTTP forward -// proxy + reverse proxy, no Envoy / gRPC dependencies, full plugin set -// (jwt-validation, token-exchange, a2a-parser, mcp-parser, -// inference-parser). +// proxy + reverse proxy, no Envoy / gRPC dependencies. By default it +// compiles in every registered plugin; individual plugins can be +// dropped at build time via `-tags exclude_plugin_` (each lives +// in its own plugins_.go file). jwt-validation and token-exchange +// are always compiled in. +// +// The `authbridge-lite` image is this same binary built with the +// parser/opa/token-broker plugins excluded, leaving only jwt-validation +// + token-exchange — it is a build variant, not a separate binary. // // Mode is hardcoded to proxy-sidecar; YAML configs that specify a // different mode are rejected at boot. For envoy-sidecar mode, use -// cmd/authbridge-envoy. For a size-optimized build with parsers -// dropped, use cmd/authbridge-lite. +// cmd/authbridge-envoy. package main import ( @@ -44,15 +49,11 @@ import ( "github.com/kagenti/kagenti-extensions/authbridge/authlib/listener/skiphost" "github.com/kagenti/kagenti-extensions/authbridge/authlib/listener/transparentproxy" - // Plugins. Auth gates first, then the protocol parsers that - // supply session-event context for abctl. - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/a2aparser" - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/inferenceparser" + // Always-compiled plugins (the "lite" footprint). Every other plugin + // is wired via its own plugins_.go file gated by + // `//go:build !exclude_plugin_`, so it can be dropped at build + // time (e.g. the authbridge-lite image excludes all but these two). _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/jwtvalidation" - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/mcpparser" - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/opa" - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/sparc" - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/tokenbroker" // Named (not blank) so pluginUsesSPIFFEIdentity can reference the shared // SpiffeIdentity constant instead of duplicating the "spiffe" literal. "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/tokenexchange" diff --git a/authbridge/cmd/authbridge-proxy/plugins_a2aparser.go b/authbridge/cmd/authbridge-proxy/plugins_a2aparser.go new file mode 100644 index 000000000..99dbeb6ef --- /dev/null +++ b/authbridge/cmd/authbridge-proxy/plugins_a2aparser.go @@ -0,0 +1,5 @@ +//go:build !exclude_plugin_a2aparser + +package main + +import _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/a2aparser" diff --git a/authbridge/cmd/authbridge-proxy/plugins_inferenceparser.go b/authbridge/cmd/authbridge-proxy/plugins_inferenceparser.go new file mode 100644 index 000000000..cfb1bde6a --- /dev/null +++ b/authbridge/cmd/authbridge-proxy/plugins_inferenceparser.go @@ -0,0 +1,5 @@ +//go:build !exclude_plugin_inferenceparser + +package main + +import _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/inferenceparser" diff --git a/authbridge/cmd/authbridge-proxy/plugins_mcpparser.go b/authbridge/cmd/authbridge-proxy/plugins_mcpparser.go new file mode 100644 index 000000000..5e8db1c86 --- /dev/null +++ b/authbridge/cmd/authbridge-proxy/plugins_mcpparser.go @@ -0,0 +1,5 @@ +//go:build !exclude_plugin_mcpparser + +package main + +import _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/mcpparser" diff --git a/authbridge/cmd/authbridge-proxy/plugins_opa.go b/authbridge/cmd/authbridge-proxy/plugins_opa.go new file mode 100644 index 000000000..040c2d2c7 --- /dev/null +++ b/authbridge/cmd/authbridge-proxy/plugins_opa.go @@ -0,0 +1,5 @@ +//go:build !exclude_plugin_opa + +package main + +import _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/opa" diff --git a/authbridge/cmd/authbridge-proxy/plugins_sparc.go b/authbridge/cmd/authbridge-proxy/plugins_sparc.go new file mode 100644 index 000000000..4f6108bc1 --- /dev/null +++ b/authbridge/cmd/authbridge-proxy/plugins_sparc.go @@ -0,0 +1,5 @@ +//go:build !exclude_plugin_sparc + +package main + +import _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/sparc" diff --git a/authbridge/cmd/authbridge-proxy/plugins_tokenbroker.go b/authbridge/cmd/authbridge-proxy/plugins_tokenbroker.go new file mode 100644 index 000000000..acccd402f --- /dev/null +++ b/authbridge/cmd/authbridge-proxy/plugins_tokenbroker.go @@ -0,0 +1,5 @@ +//go:build !exclude_plugin_tokenbroker + +package main + +import _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/tokenbroker" diff --git a/authbridge/demos/README.md b/authbridge/demos/README.md index 389873436..4e3f108e9 100644 --- a/authbridge/demos/README.md +++ b/authbridge/demos/README.md @@ -5,9 +5,9 @@ authentication for Kubernetes agent workloads. Each demo progressively introduce more AuthBridge capabilities. > **Note:** These demos use the operator-injected combined sidecar (after -> kagenti-extensions#411 — one image per mode: `authbridge` for proxy-sidecar, -> `authbridge-envoy` for envoy-sidecar, `authbridge-lite` for the auth-only -> shape). The previous `authbridge-unified` image and the per-component +> kagenti-extensions#411 — `authbridge` for proxy-sidecar, `authbridge-envoy` +> for envoy-sidecar, and `authbridge-lite`, the proxy image built auth-only +> via `exclude_plugin_*` tags). The previous `authbridge-unified` image and the per-component > sidecars (`client-registration`, standalone `spiffe-helper`) have been > removed. diff --git a/authbridge/docs/framework-architecture.md b/authbridge/docs/framework-architecture.md index 5f85aed9f..2517b2bf8 100644 --- a/authbridge/docs/framework-architecture.md +++ b/authbridge/docs/framework-architecture.md @@ -662,8 +662,9 @@ identity: The mTLS code lives in `authlib/tls` + `authlib/spiffe` (framework- shared) and `authlib/listener/internal/tlssniff` (listener-internal -byte-peek dispatcher). Only `cmd/authbridge-proxy` and -`cmd/authbridge-lite` wire it up; `cmd/authbridge-envoy` stays on +byte-peek dispatcher). Only `cmd/authbridge-proxy` wires it up (this +also covers the `authbridge-lite` image — the same binary built with +`exclude_plugin_*` tags); `cmd/authbridge-envoy` stays on plaintext-localhost because Envoy handles wire encryption via SDS independently. diff --git a/authbridge/docs/plugin-reference.md b/authbridge/docs/plugin-reference.md index 14d535f2b..b5f52b227 100644 --- a/authbridge/docs/plugin-reference.md +++ b/authbridge/docs/plugin-reference.md @@ -1140,8 +1140,8 @@ The handle→token store lives in memory and is shared only within a single process. The mode therefore works only where inbound mint and outbound resolve run in the **same** process: -- the reverse+forward proxy sidecar (`authbridge-proxy` / - `authbridge-lite`); +- the reverse+forward proxy sidecar (`authbridge-proxy`, and its + `authbridge-lite` image variant); - a **single-replica** extproc/extauthz (`authbridge-envoy`). Multi-replica (HA) or shared/scaled Istio ambient waypoint deployments diff --git a/authbridge/go.work b/authbridge/go.work index b571df7ab..2dff3b222 100644 --- a/authbridge/go.work +++ b/authbridge/go.work @@ -4,6 +4,5 @@ use ( ./authlib ./cmd/abctl ./cmd/authbridge-envoy - ./cmd/authbridge-lite ./cmd/authbridge-proxy ) diff --git a/local-build-and-test.sh b/local-build-and-test.sh index 40e638b23..e00a41983 100755 --- a/local-build-and-test.sh +++ b/local-build-and-test.sh @@ -89,12 +89,16 @@ load_image_to_kind ghcr.io/kagenti/kagenti-extensions/authbridge-envoy:local echo "✅ Built and loaded: authbridge-envoy:local" echo "" -# Build authbridge-lite (proxy-sidecar shape, auth-only plugins, no parsers) +# Build authbridge-lite: the same authbridge-proxy binary/Dockerfile built +# with exclude_plugin_* tags so only jwt-validation + token-exchange compile +# in (drops the OPA SDK + parsers). A build variant, not a separate binary. echo "==========================================" -echo "Building authbridge-lite (proxy-sidecar lite)" +echo "Building authbridge-lite (proxy build variant: auth-only plugins)" echo "==========================================" cd "${SCRIPT_DIR}/authbridge" -${CONTAINER_RUNTIME} build -f cmd/authbridge-lite/Dockerfile -t ghcr.io/kagenti/kagenti-extensions/authbridge-lite:local . +${CONTAINER_RUNTIME} build -f cmd/authbridge-proxy/Dockerfile \ + --build-arg GO_BUILD_TAGS="exclude_plugin_a2aparser,exclude_plugin_ibac,exclude_plugin_inferenceparser,exclude_plugin_mcpparser,exclude_plugin_opa,exclude_plugin_sparc,exclude_plugin_tokenbroker" \ + -t ghcr.io/kagenti/kagenti-extensions/authbridge-lite:local . load_image_to_kind ghcr.io/kagenti/kagenti-extensions/authbridge-lite:local echo "✅ Built and loaded: authbridge-lite:local" echo "" From 809aa2d2c73528e6b6dfdf6084c489752e8c403e Mon Sep 17 00:00:00 2001 From: Hai Huang Date: Wed, 24 Jun 2026 18:12:14 -0400 Subject: [PATCH 2/4] refactor(authbridge): make jwt-validation and token-exchange excludable too Extend the build-tag treatment to the last two always-compiled plugins so *every* plugin in cmd/authbridge-proxy can be dropped via -tags exclude_plugin_. main.go now imports no plugin package at all: its lone coupling (tokenexchange.SpiffeIdentity) is replaced by a local constant for the shared identity.type=spiffe config convention, which is what let token-exchange be excluded without breaking the SPIFFE-provider gate. The authbridge-lite tag set is unchanged (it still excludes all but jwt-validation + token-exchange); this only adds composability. Verified: go build/vet/test pass with no tags, with the lite tag set, and with all nine plugins excluded. Refs #620, kagenti/kagenti#1458 Assisted-By: Claude (Anthropic AI) Signed-off-by: Hai Huang --- authbridge/CLAUDE.md | 6 +-- authbridge/cmd/authbridge-proxy/main.go | 37 ++++++++++--------- .../authbridge-proxy/plugins_jwtvalidation.go | 5 +++ .../authbridge-proxy/plugins_tokenexchange.go | 5 +++ 4 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 authbridge/cmd/authbridge-proxy/plugins_jwtvalidation.go create mode 100644 authbridge/cmd/authbridge-proxy/plugins_tokenexchange.go diff --git a/authbridge/CLAUDE.md b/authbridge/CLAUDE.md index 7a7a0b95e..0ba81ef74 100644 --- a/authbridge/CLAUDE.md +++ b/authbridge/CLAUDE.md @@ -12,9 +12,9 @@ binaries with shared auth logic in `authlib/`: - `cmd/authbridge-proxy/` — proxy-sidecar mode (default). HTTP forward + reverse proxies. Compiles in every plugin by default (jwt-validation, token-exchange, a2a-parser, mcp-parser, inference-parser, opa, sparc, ibac, token-broker). - Every plugin except jwt-validation + token-exchange is excludable via - `-tags exclude_plugin_` — one `plugins_.go` file per plugin, - gated by `//go:build !exclude_plugin_`. + **Every** plugin is excludable via `-tags exclude_plugin_` — one + `plugins_.go` file per plugin, gated by `//go:build !exclude_plugin_`; + `main.go` imports no plugin package directly. - `cmd/authbridge-envoy/` — envoy-sidecar mode. ext_proc gRPC server hooked into Envoy. Full plugin set. The `exclude_plugin_ibac` tag applies here too. - `authbridge-lite` (**image, not a separate binary**) — `cmd/authbridge-proxy` diff --git a/authbridge/cmd/authbridge-proxy/main.go b/authbridge/cmd/authbridge-proxy/main.go index c70d5fe04..0ad6f76ca 100644 --- a/authbridge/cmd/authbridge-proxy/main.go +++ b/authbridge/cmd/authbridge-proxy/main.go @@ -1,13 +1,14 @@ // Package main is the proxy-sidecar authbridge binary: HTTP forward // proxy + reverse proxy, no Envoy / gRPC dependencies. By default it -// compiles in every registered plugin; individual plugins can be -// dropped at build time via `-tags exclude_plugin_` (each lives -// in its own plugins_.go file). jwt-validation and token-exchange -// are always compiled in. +// compiles in every registered plugin. Every plugin — including +// jwt-validation and token-exchange — has its own plugins_.go +// file gated by `//go:build !exclude_plugin_`, so any subset can +// be dropped at build time via `-tags exclude_plugin_`. main.go +// imports no plugin package directly. // -// The `authbridge-lite` image is this same binary built with the -// parser/opa/token-broker plugins excluded, leaving only jwt-validation -// + token-exchange — it is a build variant, not a separate binary. +// The `authbridge-lite` image is this same binary built with everything +// except jwt-validation + token-exchange excluded — it is a build +// variant, not a separate binary. // // Mode is hardcoded to proxy-sidecar; YAML configs that specify a // different mode are rejected at boot. For envoy-sidecar mode, use @@ -48,15 +49,10 @@ import ( "github.com/kagenti/kagenti-extensions/authbridge/authlib/listener/reverseproxy" "github.com/kagenti/kagenti-extensions/authbridge/authlib/listener/skiphost" "github.com/kagenti/kagenti-extensions/authbridge/authlib/listener/transparentproxy" - - // Always-compiled plugins (the "lite" footprint). Every other plugin - // is wired via its own plugins_.go file gated by - // `//go:build !exclude_plugin_`, so it can be dropped at build - // time (e.g. the authbridge-lite image excludes all but these two). - _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/jwtvalidation" - // Named (not blank) so pluginUsesSPIFFEIdentity can reference the shared - // SpiffeIdentity constant instead of duplicating the "spiffe" literal. - "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/tokenexchange" + // Plugins are wired via per-plugin plugins_.go files, each gated + // by `//go:build !exclude_plugin_`. main.go imports no plugin + // package directly, so every plugin can be dropped at build time. The + // authbridge-lite image excludes all but jwt-validation + token-exchange. ) var logLevel = new(slog.LevelVar) @@ -114,6 +110,13 @@ func spiffeProviderNeeded(c *config.Config) bool { return false } +// spiffeIdentityType is the `identity.type` config value that selects the +// SPIFFE identity scheme. It is a shared config convention (token-exchange is +// the only consumer today); kept as a local constant so main.go stays +// decoupled from any specific plugin package — every plugin is build-tag +// excludable via plugins_.go. +const spiffeIdentityType = "spiffe" + // pluginUsesSPIFFEIdentity reports whether a plugin's config selects the spiffe // identity scheme (identity.type=spiffe) — the only plugin-level consumer of // the Provider today (token-exchange). The `identity` block is a shared @@ -133,7 +136,7 @@ func pluginUsesSPIFFEIdentity(p config.PluginEntry) bool { // later with a precise error; don't force the provider on for it. return false } - return probe.Identity.Type == tokenexchange.SpiffeIdentity + return probe.Identity.Type == spiffeIdentityType } func main() { diff --git a/authbridge/cmd/authbridge-proxy/plugins_jwtvalidation.go b/authbridge/cmd/authbridge-proxy/plugins_jwtvalidation.go new file mode 100644 index 000000000..a67edd23f --- /dev/null +++ b/authbridge/cmd/authbridge-proxy/plugins_jwtvalidation.go @@ -0,0 +1,5 @@ +//go:build !exclude_plugin_jwtvalidation + +package main + +import _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/jwtvalidation" diff --git a/authbridge/cmd/authbridge-proxy/plugins_tokenexchange.go b/authbridge/cmd/authbridge-proxy/plugins_tokenexchange.go new file mode 100644 index 000000000..653aa6ab6 --- /dev/null +++ b/authbridge/cmd/authbridge-proxy/plugins_tokenexchange.go @@ -0,0 +1,5 @@ +//go:build !exclude_plugin_tokenexchange + +package main + +import _ "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/tokenexchange" From 2d4a677ba4e96794bab9881ff1bc60e754f7fb74 Mon Sep 17 00:00:00 2001 From: Hai Huang Date: Wed, 24 Jun 2026 18:44:17 -0400 Subject: [PATCH 3/4] =?UTF-8?q?refactor(authbridge):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20test=20the=20lite=20tag=20set=20in=20CI;=20fix=20st?= =?UTF-8?q?ale=20lite-as-binary=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci.yaml: the lite-variant PR step now runs `go test -race -cover` with the exclude tags too (not just `go build`), guarding lite-only regressions; the tag list is split across lines to stay under the 150-char yamllint cap. - Docs/comments: reframe the remaining "three binaries / lite binary" references (CLAUDE.md, authbridge/CLAUDE.md, authbridge/cmd/README.md, authlib/sessionapi/catalog_adapter.go) as two binaries (proxy, envoy) plus the lite build variant of the proxy binary. Addresses the CodeRabbit review on #621. Two of its findings were checked and intentionally not changed: excluding token-broker is correct (token-broker != token-exchange — distinct plugins; lite keeps token-exchange), and the "only authbridge-proxy wires TLS" wording is accurate on main (authbridge-cpex is from an unmerged PR, not present here). Refs #620, kagenti/kagenti#1458 Assisted-By: Claude (Anthropic AI) Signed-off-by: Hai Huang --- .github/workflows/ci.yaml | 12 ++++++++---- CLAUDE.md | 2 +- authbridge/CLAUDE.md | 6 +++--- authbridge/authlib/sessionapi/catalog_adapter.go | 6 +++--- authbridge/cmd/README.md | 12 +++++++----- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 31fd6aeed..87bad7b2c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -102,11 +102,15 @@ jobs: # The authbridge-lite image is this same authbridge-proxy binary built # with exclude_plugin_* tags (only jwt-validation + token-exchange). - # Verify that tag combination compiles on every PR — build.yaml only - # exercises it on tag/main pushes. - - name: Build lite variant (exclude_plugin_* tags) + # Build AND test that tag set on every PR — build.yaml only exercises + # it on tag/main pushes, and this guards against lite-only regressions. + - name: Build + test lite variant (exclude_plugin_* tags) if: matrix.binary == 'authbridge-proxy' - run: go build -v -tags "exclude_plugin_a2aparser,exclude_plugin_ibac,exclude_plugin_inferenceparser,exclude_plugin_mcpparser,exclude_plugin_opa,exclude_plugin_sparc,exclude_plugin_tokenbroker" ./... + run: | + TAGS="exclude_plugin_a2aparser,exclude_plugin_ibac,exclude_plugin_inferenceparser" + TAGS="$TAGS,exclude_plugin_mcpparser,exclude_plugin_opa,exclude_plugin_sparc,exclude_plugin_tokenbroker" + go build -v -tags "$TAGS" ./... + go test -v -race -cover -tags "$TAGS" ./... - name: Test run: go test -v -race -cover ./... diff --git a/CLAUDE.md b/CLAUDE.md index c2b60433b..a65208f3f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -119,7 +119,7 @@ into workload pods. Default deployment shape (proxy-sidecar mode): ## AuthBridge Binaries -Three mode-specific binaries, one Dockerfile per binary: +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 | |--------|------|-----------|---------| diff --git a/authbridge/CLAUDE.md b/authbridge/CLAUDE.md index 0ba81ef74..5574ebba1 100644 --- a/authbridge/CLAUDE.md +++ b/authbridge/CLAUDE.md @@ -90,7 +90,7 @@ authbridge/ ## Component Details -### AuthBridge Binaries (cmd/authbridge-{proxy,envoy,lite}/) +### AuthBridge Binaries (cmd/authbridge-{proxy,envoy}/) The mode-specific authbridge binaries handle both traffic directions. Auth logic and all listener implementations live in `authlib/` (under `authlib/listener/`); @@ -496,9 +496,9 @@ See [`docs/framework-architecture.md`](docs/framework-architecture.md#9-config-h ## Code Conventions -### Go (authlib, cmd/authbridge-{proxy,envoy,lite}, demo-app) +### Go (authlib, cmd/authbridge-{proxy,envoy}, demo-app) - Go 1.25 -- Modules: `authbridge/authlib/` (pure library — all listeners, all plugins) and `authbridge/cmd/authbridge-{proxy,envoy,lite}/` (mode-specific binaries that wire listeners + plugins together) +- Modules: `authbridge/authlib/` (pure library — all listeners, all plugins) and `authbridge/cmd/authbridge-{proxy,envoy}/` (mode-specific binaries that wire listeners + plugins together). The `authbridge-lite` image is the proxy binary built with `exclude_plugin_*` tags, not a separate module. - `authbridge/go.work` workspace links the modules for local development - Logging with `log/slog`; the binaries log under their own name (`authbridge-proxy`, `authbridge-envoy`). Note the `authbridge-lite` image runs the `authbridge-proxy` binary, so it logs as `authbridge-proxy`. - gRPC ext-proc using `envoyproxy/go-control-plane` types (in `authlib/listener/extproc`) diff --git a/authbridge/authlib/sessionapi/catalog_adapter.go b/authbridge/authlib/sessionapi/catalog_adapter.go index 4f45177ad..90d0edf21 100644 --- a/authbridge/authlib/sessionapi/catalog_adapter.go +++ b/authbridge/authlib/sessionapi/catalog_adapter.go @@ -6,9 +6,9 @@ import ( ) // PluginsCatalog adapts plugins.Catalog() into the wire-shaped -// CatalogEntry slice WithCatalog expects. Three binaries (authbridge- -// proxy, -envoy, -lite) plug it in identically; centralizing the -// conversion here keeps the field list one-place. +// CatalogEntry slice WithCatalog expects. Both binaries (authbridge- +// proxy, -envoy) plug it in identically; centralizing the conversion +// here keeps the field list one-place. // // Direction is left empty: the catalog describes plugin TYPES, and // most plugins can be configured into either chain (parsers especially). diff --git a/authbridge/cmd/README.md b/authbridge/cmd/README.md index db089b489..16957aa57 100644 --- a/authbridge/cmd/README.md +++ b/authbridge/cmd/README.md @@ -1,9 +1,11 @@ # AuthBridge Binaries -Three mode-specific authbridge binaries plus the `abctl` TUI. Each binary -is hardcoded to a single deployment shape; the YAML `mode:` field must -match the binary or boot fails. Mode is selected at build time by which -binary you run, not at runtime via a flag. +Two mode-specific authbridge binaries (proxy, envoy) plus the `abctl` TUI. +Each binary is hardcoded to a single deployment shape; the YAML `mode:` +field must match the binary or boot fails. Mode is selected at build time +by which binary you run, not at runtime via a flag. The `authbridge-lite` +image is a build variant of the proxy binary (proxy Dockerfile + +`exclude_plugin_*` tags), not a separate binary. ## Binaries @@ -23,7 +25,7 @@ variant — the Envoy proxy itself. ## Configuration -All three binaries accept a single flag, `--config `, pointing +Both binaries accept a single flag, `--config `, pointing at the YAML config file the operator mounts at `/etc/authbridge/config.yaml`. The config schema and per-plugin options are documented in From c0d9114c05189979d97dfc7b939362d1ae85d62a Mon Sep 17 00:00:00 2001 From: Hai Huang Date: Wed, 24 Jun 2026 18:50:51 -0400 Subject: [PATCH 4/4] test(authbridge): guard spiffeIdentityType against drift from token-exchange MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main.go deliberately does not import the token-exchange plugin package (so token-exchange stays build-tag excludable), which traded compiler-linkage of the identity.type=spiffe value for a duplicated "spiffe" literal. Add a test-time assertion that spiffeIdentityType == tokenexchange.SpiffeIdentity — a test file may import the package unconditionally without pulling it into the tag-gated production binary — so the two can't silently drift. Refs #620, kagenti/kagenti#1458 Assisted-By: Claude (Anthropic AI) Signed-off-by: Hai Huang --- authbridge/cmd/authbridge-proxy/main_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/authbridge/cmd/authbridge-proxy/main_test.go b/authbridge/cmd/authbridge-proxy/main_test.go index 4f51b0dc9..54ec737d0 100644 --- a/authbridge/cmd/authbridge-proxy/main_test.go +++ b/authbridge/cmd/authbridge-proxy/main_test.go @@ -6,12 +6,28 @@ import ( "github.com/kagenti/kagenti-extensions/authbridge/authlib/config" "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins" + "github.com/kagenti/kagenti-extensions/authbridge/authlib/plugins/tokenexchange" ) func identityConfig(idType string) json.RawMessage { return json.RawMessage(`{"identity":{"type":"` + idType + `"}}`) } +// TestSpiffeIdentityTypeMatchesTokenExchange guards against drift between +// main.go's local spiffeIdentityType constant and the token-exchange plugin's +// canonical SpiffeIdentity value. main.go intentionally does NOT import the +// plugin package (so token-exchange stays build-tag excludable via +// plugins_tokenexchange.go); this test re-couples the two at test time — a +// test file may import the package unconditionally without pulling it into the +// tag-gated production binary. +func TestSpiffeIdentityTypeMatchesTokenExchange(t *testing.T) { + if spiffeIdentityType != tokenexchange.SpiffeIdentity { + t.Errorf("spiffeIdentityType = %q but tokenexchange.SpiffeIdentity = %q; "+ + "the identity.type=spiffe convention has drifted — update spiffeIdentityType in main.go", + spiffeIdentityType, tokenexchange.SpiffeIdentity) + } +} + // TestSpiffeProviderNeeded pins the need-driven gate: the SPIFFE provider is // built only when mTLS is on or a plugin selects the spiffe identity scheme. // A bare `spiffe: {}` block with neither must NOT trigger it — that is what