From f5b0c808563e74f43d2d0a3225082060847c8271 Mon Sep 17 00:00:00 2001 From: Hai Huang Date: Tue, 14 Apr 2026 21:36:21 -0400 Subject: [PATCH 1/2] ci: Add CI/build for unified authbridge, deprecate old components (Phase 4) CI changes: - Add Go CI jobs for authlib and cmd/authbridge modules - Add authbridge-unified image to build.yaml matrix Documentation: - Update CLAUDE.md with unified binary architecture, Go modules, and modes - Mark authbridge-unified as recommended image - Mark envoy-with-processor and old authbridge as deprecated Deprecation markers: - go-processor/main.go: deprecated in favor of cmd/authbridge - Dockerfile.envoy: deprecated in favor of cmd/authbridge/Dockerfile - entrypoint-envoy.sh: deprecated in favor of cmd/authbridge/entrypoint.sh Follow-up (separate repos): - kagenti-operator: add authbridge-unified to image config - kagenti: add authbridge-unified-config ConfigMap to Helm chart Ref: kagenti/kagenti-extensions#279 Assisted-By: Claude (Anthropic AI) Signed-off-by: Hai Huang --- .github/workflows/build.yaml | 6 +++ .github/workflows/ci.yaml | 57 +++++++++++++++++++++++ CLAUDE.md | 48 ++++++++++++++++--- authbridge/authproxy/Dockerfile.envoy | 2 + authbridge/authproxy/entrypoint-envoy.sh | 1 + authbridge/authproxy/go-processor/main.go | 4 ++ 6 files changed, 112 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 68e982200..6c99f2b61 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -50,6 +50,12 @@ jobs: context: ./authbridge dockerfile: authproxy/Dockerfile.authbridge + # Unified AuthBridge binary (Envoy + authbridge in one container) + # Drop-in replacement for envoy-with-processor + - name: authbridge-unified + context: ./authbridge + dockerfile: cmd/authbridge/Dockerfile + # Demo application for testing - name: demo-app context: ./authbridge/authproxy/quickstart/demo-app diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e40eca35a..cd20d17e6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -66,6 +66,63 @@ jobs: - name: Test run: go test -v -race -cover ./... + go-ci-authlib: + name: Go CI (authlib) + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: authbridge/authlib + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 + with: + go-version-file: authbridge/authlib/go.mod + cache-dependency-path: authbridge/authlib/go.sum + + - name: Lint + run: | + go fmt ./... + go vet ./... + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v -race -cover ./... + + go-ci-authbridge-cmd: + name: Go CI (authbridge cmd) + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: authbridge/cmd/authbridge + env: + # Disable go.work — the workspace at authbridge/ includes ./authproxy + # which would pull in the old go-processor module. The replace directive + # in go.mod handles the authlib dependency for CI builds. + GOWORK: "off" + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 + with: + go-version-file: authbridge/cmd/authbridge/go.mod + cache-dependency-path: authbridge/cmd/authbridge/go.sum + + - name: Lint + run: | + go fmt ./... + go vet ./... + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v -race -cover ./... + python-test: name: Python Tests runs-on: ubuntu-latest diff --git a/CLAUDE.md b/CLAUDE.md index 659c519b2..472e52fcd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,8 +21,21 @@ The sidecar injection webhook lives in a separate repo: [kagenti/kagenti-operato ``` kagenti-extensions/ ├── authbridge/ # Authentication bridge components -│ ├── authproxy/ # Envoy + ext-proc sidecar (Go) — token validation & exchange -│ │ ├── go-processor/ # gRPC ext-proc server (inbound JWT validation, outbound token exchange) +│ ├── authlib/ # Shared auth building blocks (Go module) +│ │ ├── validation/ # JWKS-backed JWT verifier +│ │ ├── exchange/ # RFC 8693 token exchange client +│ │ ├── cache/ # SHA-256 keyed token cache +│ │ ├── bypass/ # Path pattern matcher +│ │ ├── spiffe/ # SPIFFE credential sources +│ │ ├── routing/ # Host-to-audience router +│ │ ├── auth/ # HandleInbound + HandleOutbound composition +│ │ └── config/ # Mode presets, YAML config, validation +│ ├── cmd/authbridge/ # Unified binary — 3 modes, 1 codebase +│ │ ├── listener/ # Protocol adapters (ext_proc, ext_authz, forward/reverse proxy) +│ │ ├── entrypoint.sh # Envoy + authbridge process supervision +│ │ └── Dockerfile # Combined Envoy + authbridge image +│ ├── authproxy/ # [DEPRECATED] Old Envoy + ext-proc sidecar +│ │ ├── go-processor/ # [DEPRECATED] Use cmd/authbridge instead │ │ ├── quickstart/ # Standalone demo (no SPIFFE) │ │ └── k8s/ # Standalone K8s manifests │ ├── client-registration/ # Keycloak auto-registration (Python) @@ -88,12 +101,34 @@ The kagenti-operator (in a separate repo) injects AuthBridge sidecars into workl └────────────────────────────────────┘ ``` +## Unified AuthBridge Binary + +The `cmd/authbridge/` directory contains a unified binary that replaces three separate +codebases (go-processor, waypoint, klaviger) with a single binary supporting three modes: + +| Mode | Interception | Listeners | Use Case | +|------|-------------|-----------|----------| +| `envoy-sidecar` | Envoy iptables + ext_proc | gRPC ext_proc on :9090 | Sidecar per agent pod | +| `waypoint` | Istio ambient + ext_authz | gRPC ext_authz + HTTP forward proxy | Shared service | +| `proxy-sidecar` | Reverse proxy + forward proxy | HTTP reverse proxy + forward proxy | Sidecar without Envoy | + +**Go modules:** +- `authbridge/authlib/` — pure library, no protocol deps (validation, exchange, cache, bypass, spiffe, routing, auth, config) +- `authbridge/cmd/authbridge/` — binary + listeners, imports authlib + gRPC/Envoy deps +- `authbridge/go.work` — workspace linking both modules for local development + +**Config format:** YAML with `${ENV_VAR}` expansion, mode presets, and startup validation. +Supports `keycloak_url` + `keycloak_realm` derivation for operator compatibility. + +**Image:** `ghcr.io/kagenti/kagenti-extensions/authbridge-unified` — Envoy + authbridge +in one container (drop-in replacement for `envoy-with-processor`). + ## CI/CD Workflows | Workflow | Trigger | Purpose | |----------|---------|---------| -| `ci.yaml` | PR to main/release-* | Go fmt, vet, build for AuthProxy; Python tests | -| `build.yaml` | Tag push (`v*`) or manual | Multi-arch Docker builds for: client-registration, auth-proxy, proxy-init, envoy-with-processor, authbridge, demo-app | +| `ci.yaml` | PR to main/release-* | Go fmt, vet, build, test for authproxy, authlib, and cmd/authbridge; Python tests | +| `build.yaml` | Tag push (`v*`) or manual | Multi-arch Docker builds for: client-registration, auth-proxy, proxy-init, envoy-with-processor, authbridge, authbridge-unified, demo-app | | `security-scans.yaml` | PR to main | Dependency review, shellcheck, YAML lint, Hadolint, Bandit, Trivy, CodeQL | | `scorecard.yaml` | Weekly / push to main | OpenSSF Scorecard security health metrics | | `spellcheck_action.yml` | PR | Spellcheck on markdown files | @@ -114,11 +149,12 @@ All images are pushed to `ghcr.io/kagenti/kagenti-extensions/`: | Image | Source | Description | |-------|--------|-------------| -| `envoy-with-processor` | `authbridge/authproxy/Dockerfile.envoy` | Envoy 1.28 + go-processor ext-proc | +| **`authbridge-unified`** | **`authbridge/cmd/authbridge/Dockerfile`** | **Unified Envoy + authbridge binary (recommended)** | +| `envoy-with-processor` | `authbridge/authproxy/Dockerfile.envoy` | [DEPRECATED] Envoy + go-processor ext-proc | | `proxy-init` | `authbridge/authproxy/Dockerfile.init` | Alpine + iptables init container | | `client-registration` | `authbridge/client-registration/Dockerfile` | Python Keycloak client registrar | | `spiffe-helper` | `authbridge/spiffe-helper/Dockerfile` | Fetches SPIFFE credentials from SPIRE | -| `authbridge` | `authbridge/authproxy/Dockerfile.authbridge` | Combined sidecar (Envoy + go-processor + spiffe-helper + client-registration) | +| `authbridge` | `authbridge/authproxy/Dockerfile.authbridge` | [DEPRECATED] Combined sidecar (old architecture) | | `auth-proxy` | `authbridge/authproxy/Dockerfile` | Example pass-through proxy (for demos) | | `demo-app` | `authbridge/authproxy/quickstart/demo-app/Dockerfile` | Demo target service | diff --git a/authbridge/authproxy/Dockerfile.envoy b/authbridge/authproxy/Dockerfile.envoy index 032136edd..ebed36202 100644 --- a/authbridge/authproxy/Dockerfile.envoy +++ b/authbridge/authproxy/Dockerfile.envoy @@ -1,3 +1,5 @@ +# Deprecated: This Dockerfile builds the old envoy-with-processor image. +# Use authbridge/cmd/authbridge/Dockerfile instead (unified binary). FROM golang:1.26-alpine AS go-builder WORKDIR /app COPY go.mod go.sum ./ diff --git a/authbridge/authproxy/entrypoint-envoy.sh b/authbridge/authproxy/entrypoint-envoy.sh index b4fe6295b..ffdc1d382 100644 --- a/authbridge/authproxy/entrypoint-envoy.sh +++ b/authbridge/authproxy/entrypoint-envoy.sh @@ -1,4 +1,5 @@ #!/bin/bash +# Deprecated: Use authbridge/cmd/authbridge/entrypoint.sh instead (unified binary). set -eu # Envoy + go-processor entrypoint with process supervision. diff --git a/authbridge/authproxy/go-processor/main.go b/authbridge/authproxy/go-processor/main.go index bd4772672..57a84c891 100644 --- a/authbridge/authproxy/go-processor/main.go +++ b/authbridge/authproxy/go-processor/main.go @@ -1,3 +1,7 @@ +// Deprecated: This go-processor ext_proc server is replaced by the unified +// authbridge binary at cmd/authbridge/. The unified binary supports envoy-sidecar, +// waypoint, and proxy-sidecar modes with a shared auth library (authlib/). +// This file is kept for backwards compatibility during migration. package main import ( From 76be706a121a7dd2c7973268e5ec13d78622307a Mon Sep 17 00:00:00 2001 From: Hai Huang Date: Tue, 14 Apr 2026 21:54:17 -0400 Subject: [PATCH 2/2] docs: Add unified authbridge documentation and deprecation notices New documentation: - cmd/authbridge/README.md: config reference, build instructions, 3 modes - authlib/README.md: package reference and usage example Updated documentation: - authbridge/README.md: added "Unified AuthBridge Binary" section at top, restructured existing content under "Classic Architecture" - authbridge/CLAUDE.md: added unified binary section pointing to new docs - authbridge/authproxy/README.md: added deprecation banner Assisted-By: Claude (Anthropic AI) Signed-off-by: Hai Huang --- authbridge/CLAUDE.md | 10 ++ authbridge/README.md | 24 ++++- authbridge/authlib/README.md | 42 +++++++++ authbridge/authproxy/README.md | 5 + authbridge/cmd/authbridge/README.md | 136 ++++++++++++++++++++++++++++ authbridge/demos/README.md | 4 + 6 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 authbridge/authlib/README.md create mode 100644 authbridge/cmd/authbridge/README.md diff --git a/authbridge/CLAUDE.md b/authbridge/CLAUDE.md index bba37455d..b0d5138fe 100644 --- a/authbridge/CLAUDE.md +++ b/authbridge/CLAUDE.md @@ -4,6 +4,16 @@ This file provides context for Claude (AI assistant) when working with the `Auth For repo-level context (CI/CD, cross-component relationships), see [`../CLAUDE.md`](../CLAUDE.md). The sidecar injection webhook lives in [kagenti-operator](https://github.com/kagenti/kagenti-operator). +## Unified Binary + +The `cmd/authbridge/` directory contains the unified authbridge binary that replaces the +old `go-processor` ext_proc server. It supports three modes (`envoy-sidecar`, `waypoint`, +`proxy-sidecar`) with shared auth logic in `authlib/`. See [`cmd/authbridge/README.md`](cmd/authbridge/README.md) +for config format and [`authlib/README.md`](authlib/README.md) for the library reference. + +The old `authproxy/go-processor/` is deprecated. New development should target +`authlib/` and `cmd/authbridge/`. + ## What AuthBridge Does AuthBridge provides **zero-trust, transparent token management** for Kubernetes workloads. It combines three capabilities: diff --git a/authbridge/README.md b/authbridge/README.md index 664d9bf9d..872c88350 100644 --- a/authbridge/README.md +++ b/authbridge/README.md @@ -4,7 +4,25 @@ AuthBridge provides **secure, transparent token management** for Kubernetes work > **📘 Looking to run the demo?** See the [Single-Target Demo](./demos/single-target/demo.md) or [Multi-Target Demo](./demos/multi-target/demo.md) for step-by-step instructions. -## What AuthBridge Does +## Unified AuthBridge Binary (New) + +The [`cmd/authbridge/`](./cmd/authbridge/) directory contains a unified binary that supports three deployment modes in a single codebase: + +| Mode | Use Case | Replaces | +|------|----------|----------| +| `envoy-sidecar` | Sidecar per agent pod (Envoy + ext_proc) | `envoy-with-processor` (go-processor) | +| `waypoint` | Shared service in Istio ambient mesh | Waypoint token-exchange-service | +| `proxy-sidecar` | Sidecar without Envoy (reverse + forward proxy) | Klaviger | + +The unified image (`authbridge-unified`) is a **drop-in replacement** for `envoy-with-processor` with the same base image, UID (1337), and ports. See [`cmd/authbridge/README.md`](./cmd/authbridge/README.md) for config format and usage. + +The shared auth library at [`authlib/`](./authlib/) contains the building blocks (JWT validation, token exchange, caching, routing) with no protocol dependencies. See [`authlib/README.md`](./authlib/README.md) for package reference. + +## Classic Architecture (Operator-Injected) + +The following describes the current production deployment using operator-injected split sidecars. The unified binary (`cmd/authbridge/`) replaces the `envoy-proxy` sidecar in this architecture. + +### What AuthBridge Does AuthBridge solves the challenge of **secure service-to-service authentication** in Kubernetes: @@ -357,7 +375,9 @@ This creates target clients, audience scopes, and assigns scopes to the agent. ## Component Documentation -- [AuthProxy](authproxy/README.md) - Token validation and exchange proxy +- [Unified AuthBridge Binary](cmd/authbridge/README.md) - Single binary, three modes (recommended) +- [authlib](authlib/README.md) - Shared auth building blocks (Go library) +- [AuthProxy](authproxy/README.md) - Token validation and exchange proxy (deprecated, use cmd/authbridge) - [Client Registration](client-registration/README.md) - Automatic Keycloak client registration with SPIFFE ## References diff --git a/authbridge/authlib/README.md b/authbridge/authlib/README.md new file mode 100644 index 000000000..6b9a085a5 --- /dev/null +++ b/authbridge/authlib/README.md @@ -0,0 +1,42 @@ +# authlib — Shared Auth Building Blocks + +A pure Go library providing reusable building blocks for JWT validation, OAuth 2.0 token exchange, and SPIFFE-based authentication. No protocol dependencies (no gRPC, no Envoy). + +## Packages + +| Package | Purpose | +|---------|---------| +| `validation/` | JWKS-backed JWT verifier (`lestrrat-go/jwx`) with required audience parameter | +| `exchange/` | RFC 8693 token exchange + client credentials grant with pluggable auth | +| `cache/` | SHA-256 keyed token cache with TTL eviction | +| `bypass/` | Path pattern matcher for public endpoints (health, agent card) | +| `spiffe/` | SPIFFE credential sources (file-based JWT-SVID) | +| `routing/` | Host-to-audience router with glob pattern matching | +| `auth/` | Composition layer: `HandleInbound` + `HandleOutbound` | +| `config/` | YAML config, mode presets, startup validation, URL derivation | + +## Usage + +```go +import ( + "github.com/kagenti/kagenti-extensions/authbridge/authlib/auth" + "github.com/kagenti/kagenti-extensions/authbridge/authlib/config" +) + +// Load and resolve config +cfg, _ := config.Load("config.yaml") +resolved, _ := config.Resolve(ctx, cfg) +handler := auth.New(*resolved) + +// Handle requests +inResult := handler.HandleInbound(ctx, authHeader, path, "") +outResult := handler.HandleOutbound(ctx, authHeader, host) +``` + +## Go Module + +``` +module github.com/kagenti/kagenti-extensions/authbridge/authlib +``` + +Direct dependencies: `lestrrat-go/jwx/v2`, `gobwas/glob`, `gopkg.in/yaml.v3`. No gRPC or Envoy deps. diff --git a/authbridge/authproxy/README.md b/authbridge/authproxy/README.md index f7860e050..c67f1067e 100644 --- a/authbridge/authproxy/README.md +++ b/authbridge/authproxy/README.md @@ -1,5 +1,10 @@ # AuthProxy +> **Deprecated:** The go-processor ext_proc server and `Dockerfile.envoy` are replaced by +> the [unified authbridge binary](../cmd/authbridge/). The unified binary is a drop-in +> replacement for `envoy-with-processor` with the same image structure, UID, and ports. +> This directory is kept for backwards compatibility during migration. + AuthProxy is a **token validation and exchange sidecar** for Kubernetes workloads. It enables secure service-to-service communication by: - **Validating** incoming requests with JWT token verification (inbound) - **Exchanging** tokens for ones with the correct audience for downstream services (outbound) diff --git a/authbridge/cmd/authbridge/README.md b/authbridge/cmd/authbridge/README.md new file mode 100644 index 000000000..661dec79a --- /dev/null +++ b/authbridge/cmd/authbridge/README.md @@ -0,0 +1,136 @@ +# AuthBridge Unified Binary + +A single binary that replaces three separate codebases (go-processor, waypoint, klaviger) with a unified auth proxy supporting three deployment modes. + +## Modes + +| Mode | Interception | Listeners | Deployment | +|------|-------------|-----------|------------| +| `envoy-sidecar` | Envoy iptables + ext_proc | gRPC ext_proc on :9090 | Sidecar per agent pod | +| `waypoint` | Istio ambient + ext_authz | gRPC ext_authz + HTTP forward proxy | Shared service in kagenti-system | +| `proxy-sidecar` | Reverse proxy + forward proxy | HTTP reverse proxy + forward proxy | Sidecar without Envoy | + +## Building + +```bash +# From authbridge/ directory (build context) +podman build -f cmd/authbridge/Dockerfile -t authbridge-unified:local . + +# Load into Kind +kind load docker-image authbridge-unified:local --name kagenti +``` + +The image contains both Envoy and the authbridge binary. The entrypoint starts both processes with `wait -n` supervision (if either dies, the container restarts). + +## Running + +```bash +authbridge --mode envoy-sidecar --config /etc/authbridge/config.yaml +``` + +The `--mode` flag can also be set in the YAML config. The flag overrides the config file value. + +## Configuration + +YAML with `${ENV_VAR}` expansion. Undefined env vars are preserved as-is (not expanded to empty). + +### envoy-sidecar mode + +Drop-in replacement for `envoy-with-processor`. Used as a sidecar alongside Envoy in each agent pod. + +```yaml +mode: envoy-sidecar +inbound: + jwks_url: "${JWKS_URL}" # or derived from token_url + issuer: "${ISSUER}" # or derived from keycloak_url + keycloak_realm +outbound: + token_url: "${TOKEN_URL}" # or derived from keycloak_url + keycloak_realm + keycloak_url: "${KEYCLOAK_URL}" # alternative to explicit token_url + keycloak_realm: "${KEYCLOAK_REALM}" # used with keycloak_url + default_policy: "passthrough" # passthrough (default) or exchange +identity: + type: spiffe # spiffe or client-secret + client_id: "${CLIENT_ID}" # or use client_id_file + client_id_file: "/shared/client-id.txt" # read from file (waits up to 60s) + client_secret_file: "/shared/client-secret.txt" + jwt_svid_path: "/opt/jwt_svid.token" # for SPIFFE JWT-SVID auth +bypass: + inbound_paths: # defaults: /.well-known/*, /healthz, /readyz, /livez + - "/.well-known/*" + - "/healthz" +routes: + file: "/etc/authproxy/routes.yaml" # load routes from file + rules: # or inline + - host: "target-service.**" + target_audience: "target" + token_scopes: "openid target-aud" +``` + +### waypoint mode + +Shared service for Istio ambient mesh. Derives audience from destination hostname automatically. + +```yaml +mode: waypoint +inbound: + issuer: "${ISSUER}" +outbound: + keycloak_url: "${KEYCLOAK_URL}" + keycloak_realm: "${KEYCLOAK_REALM}" + default_policy: "exchange" +identity: + type: client-secret + client_id: "token-exchange-service" + client_secret: "${CLIENT_SECRET}" +``` + +### proxy-sidecar mode + +Sidecar without Envoy. Reverse proxy validates inbound, forward proxy exchanges outbound. + +```yaml +mode: proxy-sidecar +listener: + reverse_proxy_backend: "http://localhost:8081" +inbound: + issuer: "${ISSUER}" +outbound: + keycloak_url: "${KEYCLOAK_URL}" + keycloak_realm: "${KEYCLOAK_REALM}" +identity: + type: spiffe + client_id: "${CLIENT_ID}" + jwt_svid_path: "/opt/jwt_svid.token" +``` + +## URL Derivation + +When explicit URLs are not set, they are derived automatically: + +| Missing field | Derived from | Example | +|---|---|---| +| `token_url` | `keycloak_url` + `keycloak_realm` | `http://keycloak:8080/realms/kagenti/protocol/openid-connect/token` | +| `issuer` | `keycloak_url` + `keycloak_realm` | `http://keycloak:8080/realms/kagenti` | +| `jwks_url` | `token_url` | `.../openid-connect/token` becomes `.../openid-connect/certs` | + +Explicit values always take precedence over derived values. + +## Credential File Waiting + +When `client_id_file`, `client_secret_file`, or `jwt_svid_path` are configured, the binary polls for the file to exist (up to 60 seconds) before starting. This handles the startup race with client-registration and spiffe-helper sidecars. + +## Architecture + +``` +cmd/authbridge/ +├── main.go # --mode + --config, starts listeners, graceful shutdown +├── entrypoint.sh # Envoy + authbridge process supervision (wait -n) +├── Dockerfile # Combined Envoy + authbridge image (ubi-minimal) +└── listener/ + ├── extproc/ # Envoy ext_proc gRPC streaming (envoy-sidecar mode) + ├── extauthz/ # Envoy ext_authz gRPC unary (waypoint mode) + ├── forwardproxy/ # HTTP forward proxy (waypoint + proxy-sidecar) + └── reverseproxy/ # HTTP reverse proxy (proxy-sidecar mode) +``` + +Listeners are thin protocol translators (~50-175 lines each). All auth logic lives in `authlib/`. diff --git a/authbridge/demos/README.md b/authbridge/demos/README.md index bb36011a1..202bf9d82 100644 --- a/authbridge/demos/README.md +++ b/authbridge/demos/README.md @@ -4,6 +4,10 @@ This directory contains demo scenarios showing AuthBridge providing zero-trust authentication for Kubernetes agent workloads. Each demo progressively introduces more AuthBridge capabilities. +> **Note:** These demos use the classic `envoy-with-processor` image with operator-injected +> sidecars. A [unified authbridge binary](../cmd/authbridge/) is available as a drop-in +> replacement. See [`cmd/authbridge/README.md`](../cmd/authbridge/README.md) for details. + ## Available Demos | Demo | Difficulty | What It Shows | Deployment |