Skip to content

feat(cmd): Add authbridge-proxy and authbridge-envoy lite binaries#407

Merged
huang195 merged 1 commit into
rossoctl:mainfrom
huang195:feat/lite-binaries
May 12, 2026
Merged

feat(cmd): Add authbridge-proxy and authbridge-envoy lite binaries#407
huang195 merged 1 commit into
rossoctl:mainfrom
huang195:feat/lite-binaries

Conversation

@huang195

@huang195 huang195 commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

Adds two single-mode, single-purpose authbridge binaries alongside
the existing "batteries-included" cmd/authbridge:

Binary Mode Plugins Size
cmd/authbridge (existing) all 3 modes all 6 in-tree 25 MB / 17 MB stripped
cmd/authbridge-envoy (new) envoy-sidecar only jwt-validation + token-exchange 25 MB / 17 MB stripped
cmd/authbridge-proxy (new) proxy-sidecar only (no Envoy) jwt-validation + token-exchange 12 MB / 8.0 MB stripped

Motivation: deployments that need only one mode and only the two
auth plugins don't need to pay for gRPC + envoy go-control-plane +
the a2a/mcp/inference parsers + the ext_authz listener. The
majority of the current binary's footprint comes from those deps.

Binary sizes (darwin amd64, Go 1.26.2)

Built with go build and go build -trimpath -ldflags="-s -w":

authbridge (full)       25 MB  →  17 MB  (baseline)
authbridge-envoy        25 MB  →  17 MB  (ext_proc keeps gRPC in)
authbridge-proxy        12 MB  →   8 MB  (-52% unstripped, -53% stripped)

The big win is authbridge-proxy. Dropped from the link graph:

  • google.golang.org/grpc
  • envoyproxy/go-control-plane
  • cncf/xds/go
  • envoyproxy/protoc-gen-validate
  • google.golang.org/genproto/googleapis/rpc
  • google.golang.org/protobuf
  • 4 uncompiled plugins (a2a, mcp, inference, token-broker)

authbridge-envoy is marginal on size (ext_proc forces gRPC +
envoy types back in), but adds value as a deployment-story
simplification: one binary that only does envoy-sidecar, can't
misconfigure into other modes.

Container image sizes (linux/amd64)

Real measurements from podman build with
CGO_ENABLED=0 -trimpath -ldflags="-s -w":

Image Base Envoy bundled Size
authbridge:combined (existing — full, with Envoy) UBI9-micro yes 141 MB
authbridge:light (existing — full, no Envoy) distroless/static no 29.8 MB
authbridge-envoy:lite (new) UBI9-micro yes 132 MB
authbridge-proxy:lite (new) distroless/static no 11.5 MB

Deltas (apples-to-apples, same base image)

Comparison Saving
authbridge-proxy:lite vs authbridge:light (distroless, no Envoy) 29.8 → 11.5 MB (−61%)
authbridge-envoy:lite vs authbridge:combined (UBI9 + Envoy) 141 → 132 MB (−6%)

Deltas (cross-profile — if you can switch envoy-sidecar → proxy-sidecar)

Comparison Saving
authbridge-proxy:lite vs authbridge:combined 141 → 11.5 MB (−92%)

Practical impact

  • Proxy-sidecar deployments: 11.5 MB is essentially the floor for a
    Go sidecar image — distroless/static base (~2 MB) plus the binary
    (~9.5 MB Linux-static-stripped). Pull-time and startup on rolling
    updates drop proportionally.
  • Envoy-sidecar deployments: the 9 MB image saving is marginal
    because Envoy (87 MB) + UBI9-micro (24 MB) dominate. The value is
    mode lock-in, not size.
  • Attack surface: authbridge-proxy:lite contains zero gRPC code,
    zero envoy types, zero parser plugins. Whatever CVE surface those
    carry is gone from that image.

Sample Dockerfiles used for the measurements are added as
follow-up work (see "Out of scope" below).

Prerequisite refactors

1. Parser plugins → subpackages

Three flat parser plugins lived in authlib/plugins/ as files
alongside registry.go:

  • a2aparser.go
  • mcpparser.go
  • inferenceparser.go

Their init() registration was forced into every binary that
imported authlib/plugins (because importing a package runs its
init() regardless of what the binary actually needs). Moved each
to its own subpackage (matching the pattern jwtvalidation/,
tokenexchange/, tokenbroker/ already use). All plugins are now
uniformly opt-in via side-effect import.

Shared parser helpers (JSONRPCRequest, Truncate, DebugBodyMax)
extracted to authlib/plugins/internal/parsercommon/. internal/
keeps them scoped to plugins under this tree.

2. HTTP listeners → authlib

forwardproxy and reverseproxy were under
cmd/authbridge/listener/. They're net/http-only with no gRPC or
envoy dependencies, so they belong in the shared library where any
binary can reuse them. Moved to authlib/listener/.

extproc and extauthz stay under cmd/authbridge/listener/
their gRPC/envoy surface is specific to envoy-integration binaries,
and pulling them into authlib/ would drag grpc+envoy into the
shared lib's go.mod.

Updated cmd/authbridge/main.go

All 5 plugins now pulled in via side-effect imports. Preserves
existing binary behavior.

New binary layouts

authbridge/
  authlib/
    listener/
      forwardproxy/          (moved)
      reverseproxy/          (moved)
    plugins/
      a2aparser/             (moved)
      mcpparser/             (moved)
      inferenceparser/       (moved)
      internal/
        parsercommon/        (new)
      jwtvalidation/         (unchanged)
      tokenexchange/         (unchanged)
      tokenbroker/           (unchanged)
  cmd/
    authbridge/              (existing — batteries-included)
      listener/
        extproc/             (stays)
        extauthz/            (stays)
    authbridge-proxy/        (NEW — own module, authlib-only, no gRPC)
      go.mod
      main.go
    authbridge-envoy/        (NEW — own module, depends on authlib +
                              cmd/authbridge via replace for extproc)
      go.mod
      main.go

Both new modules are added to authbridge/go.work so workspace
builds cover them.

Test plan

  • go test -race ./... in authbridge/authlib — passes
  • go test -race ./... in authbridge/cmd/authbridge — passes
  • go build ./... in authbridge/cmd/authbridge-proxy — passes
  • go build ./... in authbridge/cmd/authbridge-envoy — passes
  • gofmt -l on touched files — clean (pre-existing drift in
    pipeline/session_test.go, auth.go, etc. not touched)
  • Binary sizes measured locally (darwin amd64) and documented
    above.

No new tests specific to the lite binaries — they are main.go
packages that duplicate the relevant branches of the full binary's
wiring. If drift becomes a concern we can extract a shared setup
helper into authlib/.

Out of scope (follow-ups)

  • Dockerfiles for the lite binaries. The existing
    cmd/authbridge/Dockerfile still builds the full binary. Adding
    Dockerfile.proxy and Dockerfile.envoy is a small follow-up
    PR once the release/CI workflows are decided.
  • CI builds for the new binaries. Current CI matrix lives in
    .github/workflows/ci.yaml — adding them should be straightforward
    but left as a separate change so this PR is code-only.
  • UPX compression recipe for operators who want to push sizes
    further (~5 MB for authbridge-proxy with UPX, at startup-time
    cost).
  • Size-optimization README under cmd/authbridge-proxy/ noting
    the build-time tradeoffs.

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

Introduces two smaller, single-mode binaries alongside the existing
"batteries-included" cmd/authbridge:

  cmd/authbridge-proxy   proxy-sidecar only (no Envoy, no gRPC)
                         jwt-validation + token-exchange only
  cmd/authbridge-envoy   envoy-sidecar only (ext_proc)
                         jwt-validation + token-exchange only

Motivation: deployments that only need a single mode and two auth
plugins don't need to pay for gRPC + envoy go-control-plane types +
a2a/mcp/inference parsers + the ext_authz listener. Most of the
current binary's size comes from those deps.

Prerequisite refactors:

1. Three flat parser plugins (a2a-parser, mcp-parser,
   inference-parser) moved from authlib/plugins/ into their own
   subpackages under authlib/plugins/{a2aparser,mcpparser,
   inferenceparser}/. Their init()-based registration was previously
   forced into every binary that imported the plugins package
   (because that's how Go module init works); subpackage layout
   makes them opt-in via side-effect import like jwt-validation,
   token-exchange, and token-broker already were. Shared parser
   helpers moved to authlib/plugins/internal/parsercommon/ (tiny
   package: JSONRPCRequest, Truncate, DebugBodyMax).

2. HTTP listeners (forwardproxy, reverseproxy) moved from
   cmd/authbridge/listener/ to authlib/listener/. They're
   net/http-only with no gRPC or envoy dependencies, so they belong
   in the shared library where any binary can reuse them without
   cross-module deps. extproc and extauthz stay under
   cmd/authbridge/listener/ because their gRPC/envoy surface is
   specific to the envoy-integration binaries.

Binary size measurements (darwin amd64, Go 1.26.2):

  Binary                  Unstripped    Stripped (-s -w)
  authbridge (full)       25 MB         17 MB
  authbridge-envoy        25 MB         17 MB
  authbridge-proxy        12 MB         8.0 MB

authbridge-proxy comes in at roughly half the size of the full
binary. The drops: google.golang.org/grpc, envoyproxy/go-control-plane,
cncf/xds, protoc-gen-validate, genproto/googleapis/rpc, protobuf
runtime, and the 4 uncompiled plugins (a2a, mcp, inference,
token-broker). That dep subtree is where the size actually lives.

authbridge-envoy doesn't move the needle much on size because
ext_proc forces gRPC + envoy types in regardless. Its real value is
deployment-story simplification: one binary that only knows how to
do envoy-sidecar mode, cannot misconfigure into other modes.

Layout:

  authbridge/
    authlib/
      listener/
        forwardproxy/       (moved from cmd/authbridge/listener/)
        reverseproxy/       (moved from cmd/authbridge/listener/)
      plugins/
        a2aparser/          (moved from a2aparser.go)
        mcpparser/          (moved from mcpparser.go)
        inferenceparser/    (moved from inferenceparser.go)
        internal/
          parsercommon/     (extracted shared helpers)
        jwtvalidation/      (unchanged — already subpackaged)
        tokenexchange/      (unchanged)
        tokenbroker/        (unchanged)
    cmd/
      authbridge/           (existing full binary — unchanged behavior,
                             now uses side-effect imports for all plugins)
        listener/
          extproc/          (stays here — envoy-bound)
          extauthz/         (stays here — envoy-bound)
      authbridge-proxy/     (NEW — own module, authlib-only)
      authbridge-envoy/     (NEW — own module, authlib + cmd/authbridge
                             via replace directive for extproc)

Tests:

  All existing authlib tests pass under -race. The
  TestBuiltinsRegistered test moved from the package-internal
  registry_test.go to the external plugins_test.go so it can import
  the plugin subpackages via side effect (internal tests would
  cycle: plugins → plugin-subpackage → plugins).

  No new tests specific to the lite binaries — they're main.go
  packages and duplicate the full binary's wiring. If drift becomes
  a concern, consider extracting a shared setup helper into authlib.

Follow-ups (not in this PR):

  - Dockerfiles for the lite binaries (the existing
    cmd/authbridge/Dockerfile still builds the full one).
  - CI build for the lite binaries — will surface immediately as
    go build ./... coverage expands via the go.work.
  - UPX compression recipe if operators need to push sizes down
    further (~5 MB for authbridge-proxy with UPX).

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>

@evaline-ju evaline-ju left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well-structured refactoring that cleanly separates parser plugins into opt-in subpackages and introduces two purpose-built lite binaries. The proxy binary achieves a meaningful -61% image size reduction by excluding gRPC/Envoy deps. Import paths are consistently updated, tests migrated correctly, and the parsercommon API is minimal and safe. CI is all green including DCO, CodeQL, Go CI, and Bandit.

Areas reviewed: Go (new binaries, plugin refactoring, module layout), Security (deps, hardcoded secrets)
Commits: 1 commit, signed-off: yes
CI status: all passing (17/17, 1 skipped: Spellcheck)


replace github.com/kagenti/kagenti-extensions/authbridge/authlib => ../../authlib

replace github.com/kagenti/kagenti-extensions/authbridge/cmd/authbridge => ../authbridge

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The replace on cmd/authbridge creates a build-time coupling to the full binary's module (needed for extproc/extauthz listeners). The PR body explains this well, but a one-line comment in go.mod above the replace would help future maintainers understand why it exists and what breaks if cmd/authbridge is refactored.

@@ -0,0 +1,279 @@
// Package main is the envoy-sidecar lite binary: envoy-sidecar mode

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: ~270 lines duplicated between authbridge-proxy/main.go and authbridge-envoy/main.go (initLogging, startSignalToggle, startStatServer, health check, shutdown). PR body acknowledges this — fine for now, but worth tracking so drift doesn't sneak in.

@huang195
huang195 merged commit 70f97c2 into rossoctl:main May 12, 2026
18 checks passed
@huang195
huang195 deleted the feat/lite-binaries branch May 12, 2026 23:43
huang195 added a commit to huang195/kagenti-extensions that referenced this pull request May 14, 2026
Consolidates the authbridge container images down to two combined
variants, each bundling spiffe-helper that starts conditionally on
SPIRE_ENABLED. Removes the in-pod client-registration sidecar from
this repo (operator now owns Keycloak client registration).

## Image layout (after)

| Name | Built from | Contents |
|---|---|---|
| `authbridge` (proxy-sidecar combined, default mode) | cmd/authbridge/Dockerfile.proxy | authbridge-proxy + spiffe-helper |
| `authbridge-envoy` (envoy-sidecar combined) | cmd/authbridge/Dockerfile.envoy | Envoy + authbridge (ext_proc) + spiffe-helper |
| `proxy-init` | authproxy/Dockerfile.init | iptables init for envoy-sidecar mode |

Both combined images use the same supervisor pattern:

  * If SPIRE_ENABLED=true: start spiffe-helper in background, append
    its PID to CRITICAL_PIDS.
  * Start authbridge-proxy / Envoy + authbridge in background, append.
  * `wait -n $CRITICAL_PIDS` blocks until any critical process exits.
  * On SIGTERM/SIGINT: graceful shutdown, exit 0.
  * On any critical-process death: kill the others, exit 1, kubelet
    restarts the pod. No silent process death.

## Removals

  * `authproxy/Dockerfile.authbridge` (old envoy+everything combined)
  * `authproxy/entrypoint-authbridge.sh`
  * `authproxy/authbridge-combined.yaml` (was the bake-in YAML for
    the old combined image; operator-supplied per-agent ConfigMap
    superseded it)
  * `client-registration/` (entire directory: Dockerfile,
    Python script, requirements, example manifests, images).
    Operator now creates Keycloak clients via its
    ClientRegistration controller and mounts the resulting Secret
    at /shared/client-id.txt + /shared/client-secret.txt — same
    paths the in-pod sidecar wrote to. Workloads opting back into
    the legacy in-pod path use `kagenti.io/client-registration-inject:
    "true"`; that path now sources the image from kagenti-operator.
  * `spiffe-helper/Dockerfile` (standalone spiffe-helper image —
    bundled into both combined images now).
  * `tests/test_client_registration.py` (covered the removed Python).
  * `TestAuthbridgeCombinedYAML_Loads` (asserted the deleted YAML).

## Renames

  * `cmd/authbridge/Dockerfile` → `cmd/authbridge/Dockerfile.envoy`
  * `cmd/authbridge/Dockerfile.light` → `cmd/authbridge/Dockerfile.proxy`
  * `cmd/authbridge/entrypoint.sh` → `cmd/authbridge/entrypoint-envoy.sh`

The proxy variant uses `authbridge-proxy` (lite binary from PR rossoctl#407,
no gRPC, no envoy types) and switches base from distroless/static to
alpine. Distroless/static has no shell, which the bash supervisor
needs; alpine is +3 MB but has bash readily and matches the envoy
variant's pattern. Image size ends up around ~48 MB (vs ~12 MB for
the no-spiffe-helper variant) — spiffe-helper bundle is the bulk;
size optimization is deferred to a follow-up.

The envoy variant adds spiffe-helper to the existing UBI9-micro base
(already has bash + glibc, both required). Image size ~165 MB (vs
~141 MB without spiffe-helper).

## CI matrix

Drops 6 entries (client-registration, auth-proxy, spiffe-helper,
authbridge-unified, authbridge-light, demo-app), retains 3:

  * `proxy-init` (still needed for envoy-sidecar mode iptables)
  * `authbridge-envoy`
  * `authbridge`

The `auth-proxy` and `demo-app` source files stay in-tree for the
standalone quickstart; just not built/published by CI.

## Out of scope (Phases B + C)

Operator + chart coordination:

  * kagenti-operator must flip default mode from envoy-sidecar to
    proxy-sidecar.
  * Operator's image-name slot for proxy-sidecar mode (`Images.AuthBridgeLight`)
    should be renamed and pointed at the new `authbridge` image.
    The new image bundles spiffe-helper, so the operator should stop
    injecting spiffe-helper as a separate sidecar in proxy-sidecar
    mode (envoy-sidecar mode unchanged).
  * `combinedSidecar` feature gate cleanup.
  * kagenti chart values pin update.

These changes don't break anything in this PR because operator/chart
pin to existing image tags published by older releases of this repo.
A new release should not be cut from this branch until B + C are
ready.

## Verification

  * `go build ./...` clean across authlib, cmd/authbridge,
    cmd/authbridge-proxy, cmd/authbridge-envoy.
  * `go test -race ./...` passes in authlib + cmd/authbridge.
  * `podman build` succeeds for both Dockerfiles.
  * Built image sizes (linux/amd64):
      authbridge:test        47.8 MB
      authbridge-envoy:test  165 MB

## Follow-ups (not blocking)

  * Demo manifests in `authbridge/demos/**/k8s/` reference deleted
    image names — update after Phase B + C land. Most likely require
    rewriting the deployment shape (proxy-sidecar default), so
    delaying until operator semantics settle is correct.
  * Doc references in `authbridge/README.md`, `authbridge/docs/*`,
    `authbridge/cmd/authbridge/README.md`, `LOCAL_TESTING_GUIDE.md`,
    `local-build-and-test.sh` — find/replace pass.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants