Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a150c84
feat: add CPEX plugin to AuthBridge
araujof Jun 8, 2026
89288d9
fix(cpex): harden session-taint isolation and inbound host-bypass
araujof Jun 10, 2026
a490a82
refactor: update hr-cpex demo to deploy authbridge as a sidecar to th…
araujof Jun 10, 2026
cd2e38b
fix(cpex): address CI failures and automated review findings
araujof Jun 10, 2026
76286e8
build(cpex): bump CPEX dependency to v0.2.0-alpha.1
araujof Jun 10, 2026
8898128
fix(cpex): address PR #493 review feedback on the hr-cpex demo
araujof Jun 11, 2026
014c85b
fix(cpex): roll rebuilt images on `make deploy` (same-tag :dev footgun)
araujof Jun 11, 2026
e0676fd
feat(cpex): harden hr-cpex chat demo — faithful rendering, --show-too…
araujof Jun 11, 2026
5cbaa41
fix(cpex): address PR #493 review — multi-part redaction leak + harde…
araujof Jun 12, 2026
7da57be
fix(cpex): address PR #493 re-review — symmetric MCP response guard +…
araujof Jun 13, 2026
634f6d6
docs(cpex): close remaining PR #493 review nits
araujof Jun 13, 2026
86724b1
chore: merge main and resolve merge conflicts
araujof Jun 13, 2026
0295322
build(cpex): slim authbridge-cpex to musl/alpine on CPEX v0.2.0-alpha.2
araujof Jun 15, 2026
ecec8d5
Merge remote-tracking branch 'origin/main' into feat/cpex_plugin
araujof Jun 15, 2026
a634b03
fix(cpex): address PR #493 review nits on the hr-cpex demo
araujof Jun 16, 2026
47d6828
fix(cpex): address PR #493 review on awaitBackground + JSON logging
araujof Jun 16, 2026
2bf264a
docs(cpex): align demo + docs with MCP-frame authorization-error rend…
araujof Jun 16, 2026
57044fa
fix: small regression introduced when add demo warning to keycloak re…
araujof Jun 22, 2026
d68cdc7
Merge remote-tracking branch 'origin/main' into feat/cpex_plugin
araujof Jul 17, 2026
26740cf
fix(cpex): bump litellm to 1.84.0 to clear dependency-review vulns
araujof Jul 17, 2026
821160f
build(cpex): bump CPEX to v0.2.2 and migrate hr demo policy to canoni…
araujof Jul 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@
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

# AuthBridge proxy-sidecar CPEX image — authbridge-proxy built
# with -tags cpex (links libcpex_ffi.a from a pinned CPEX
# release) so the `cpex` plugin routes hooks through the CPEX
# framework (APL DSL + named CPEX policy plugins). Needs the
# CPEX_FFI_VERSION build-arg, read from the CPEX_FFI_VERSION
# file in the build step below.
- name: authbridge-cpex

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-blocking / procedural note (the one standing item on this PR).

This adds authbridge-cpex to the build matrix, so the image is unconditionally built and pushed to ghcr.io/kagenti/kagenti-extensions/ and tagged latest on main. The -tags cpex isolation correctly keeps the CGO/libcpex_ffi.a backend out of the three default pure-Go binaries — but it does not gate this separately-built image, which (with its FFI blob) flows into the midstream → RHOAI sync by default.

If/when the org defines the research-vs-production boundary, this line is the lever: gate the image behind the experimental-tier / sync-exclude mechanism (or label it not-for-production). Purely a downstream-exposure governance call — the code itself is good to go.

context: ./authbridge
dockerfile: cmd/authbridge-cpex/Dockerfile

# SPARC reflection service — the backend the `sparc` plugin calls.
# Deployed once per cluster via authbridge/sparc-service/deploy.
- name: sparc-service
Expand Down Expand Up @@ -107,8 +117,29 @@
# Always use the computed tag
type=raw,value=${{ steps.tag.outputs.tag }}
# Add 'latest' tag for version tags, workflow_dispatch, and pushes to main
type=raw,value=latest,enable=${{ (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) || github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }}

Check warning on line 120 in .github/workflows/build.yaml

View workflow job for this annotation

GitHub Actions / YAML Lint

120:151 [line-length] line too long (189 > 150 characters)

# 6b. Resolve build-args. authbridge-cpex needs CPEX_FFI_VERSION
# (the release tag) and CPEX_FFI_ABI (the FFI ABI integer the
# linked lib must report) — both read from the files next to its
# Dockerfile and asserted against the tarball at build time. Other
# images leave this empty (an undeclared build-arg is ignored).
- name: Resolve build args
id: buildargs
run: |
if [[ "${{ matrix.image_config.name }}" == "authbridge-cpex" ]]; then
VERSION="$(tr -d '[:space:]' < authbridge/cmd/authbridge-cpex/CPEX_FFI_VERSION)"
ABI="$(tr -d '[:space:]' < authbridge/cmd/authbridge-cpex/CPEX_FFI_ABI)"
{
echo "args<<EOF"
echo "CPEX_FFI_VERSION=${VERSION}"
echo "CPEX_FFI_ABI=${ABI}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
else
echo "args=" >> "$GITHUB_OUTPUT"
fi

# 7. Build and push image
- name: Build and push ${{ matrix.image_config.name }}
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
Expand All @@ -120,3 +151,4 @@
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: ${{ steps.buildargs.outputs.args }}

Check failure on line 154 in .github/workflows/build.yaml

View workflow job for this annotation

GitHub Actions / YAML Lint

154:11 [key-duplicates] duplication of key "build-args" in mapping
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ Thumbs.db
*.bak
*.orig

# Local scratch / working notes (never committed)
.sketchpad/

# venv
venv
.venv
Expand Down Expand Up @@ -67,3 +70,11 @@ __pycache__/
.mypy_cache/
test-jwt-rotation.sh
kagenti-webhook/bin/

# Go build artifacts — local `go build` outputs in cmd/* dirs
# (binary name matches the package dir; trivially `git add`-ed by mistake).
authbridge/cmd/abctl/abctl
authbridge/cmd/authbridge-proxy/authbridge-proxy
authbridge/cmd/authbridge-envoy/authbridge-envoy
authbridge/cmd/authbridge-lite/authbridge-lite
authbridge/cmd/authbridge-cpex/authbridge-cpex
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,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-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 |
| `authbridge-cpex` | `authbridge/cmd/authbridge-cpex/Dockerfile` | proxy-sidecar build with the CPEX plugin: authbridge-proxy built with `-tags cpex`, links `libcpex_ffi.a` from a pinned CPEX release (CGO_ENABLED=1). Routes hooks through the CPEX framework (APL DSL + named CPEX policy plugins). FFI ABI version is read from `authbridge/cmd/authbridge-cpex/CPEX_FFI_VERSION` |
| `proxy-init` | `authbridge/proxy-init/Dockerfile.init` | Alpine + iptables init container (envoy-sidecar + proxy-sidecar enforce-redirect modes) |

In all three combined images, `spiffe-helper` is started conditionally
Expand Down
12 changes: 12 additions & 0 deletions authbridge/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ binaries with shared auth logic in `authlib/`:
`-tags include_plugin_contextguru` to link it in.
- `cmd/authbridge-envoy/` — envoy-sidecar mode. ext_proc gRPC server hooked
into Envoy. Full plugin set. The `exclude_plugin_ibac` tag applies here too.
- `cmd/authbridge-cpex/` — proxy-sidecar mode, full plugin set plus the
`cpex` plugin. Built with `-tags cpex` and requires cgo (CGO_ENABLED=1):
it links `libcpex_ffi.a` from a pinned CPEX release to route hooks through
the CPEX framework (APL DSL + named CPEX policy plugins). The FFI ABI
version lives in `cmd/authbridge-cpex/CPEX_FFI_VERSION`. The other
binaries are pure-Go (CGO_ENABLED=0) and do not import the cpex package.
- `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
Expand Down Expand Up @@ -64,6 +70,12 @@ authbridge/
│ ├── Dockerfile # envoy-sidecar combined image (Envoy + authbridge-envoy)
│ └── entrypoint.sh
├── cmd/authbridge-cpex/ # proxy-sidecar mode + cpex plugin. -tags cpex, cgo required.
│ ├── main.go
│ ├── Dockerfile # proxy-sidecar build linking libcpex_ffi.a
│ ├── CPEX_FFI_VERSION # pinned CPEX FFI ABI version (build-arg source of truth)
│ └── 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
Expand Down
43 changes: 43 additions & 0 deletions authbridge/authlib/contracts/identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package contracts

// ClaimsCarrier is an optional capability interface a pipeline.Identity
// may implement to expose richer claim data than the minimal
// pipeline.Identity surface (Subject / ClientID / Scopes).
//
// It exists so consumers that want issuer / audience / auth-method /
// curated claims — chiefly the cpex plugin building a policy-input
// document — can read them without growing pipeline.Identity (which
// stays deliberately minimal so any auth shape can satisfy it). Mirrors
// the ContentSource pattern: consumers type-assert to this interface and
// simply skip the enrichment when the concrete identity doesn't
// implement it.
//
// if cc, ok := pctx.Identity.(contracts.ClaimsCarrier); ok {
// authMethod = cc.AuthMethod()
// claims = cc.Claims()
// }
//
// Producers (e.g. jwt-validation's claims adapter) curate what Claims
// returns — a small, safe-to-forward set (issuer, audience, expiry), NOT
// the full raw claim map. The session API and CPEX traces both surface
// this data, so producers must keep it free of secrets.
type ClaimsCarrier interface {
// Issuer is the token issuer (`iss`), or "" when not applicable.
Issuer() string

// Audience is the token audience list (`aud`), or nil.
Audience() []string

// AuthMethod names how the caller authenticated — "jwt", "mtls",
// "spiffe", etc. Drives policies that branch on authentication
// strength. "" when the producer can't classify it.
AuthMethod() string

// Claims returns a curated, string-valued claim set safe to forward
// into policy context and observability surfaces. Producers pick the
// keys (conventionally "issuer", "audience", "exp"); they MUST NOT
// dump the full raw claim map, which may contain secrets or PII.
// Values are strings so the set maps cleanly onto CPEX's
// SubjectExtension.Claims (map[string]string) without lossy coercion.
Claims() map[string]string
}
3 changes: 3 additions & 0 deletions authbridge/authlib/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/kagenti/kagenti-extensions/authbridge/authlib
go 1.26.4

require (
github.com/contextforge-org/cpex/go/cpex v0.2.2
github.com/envoyproxy/go-control-plane/envoy v1.37.0
github.com/fsnotify/fsnotify v1.10.1
github.com/gobwas/glob v0.2.3
Expand Down Expand Up @@ -99,6 +100,8 @@ require (
github.com/valyala/fasthttp v1.71.0 // indirect
github.com/valyala/fastjson v1.6.10 // indirect
github.com/vektah/gqlparser/v2 v2.5.34 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
Expand Down
6 changes: 6 additions & 0 deletions authbridge/authlib/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/contextforge-org/cpex/go/cpex v0.2.2 h1:rC0HciJa19nEz9LcjmgwyjoRF3EUFidNpAT6eYu1fhA=
github.com/contextforge-org/cpex/go/cpex v0.2.2/go.mod h1:SuDkdY76asy9MP/72YKqhc2FBcFZ9t5PKe3CeXSO4/g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand Down Expand Up @@ -261,6 +263,10 @@ github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADT
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
github.com/vektah/gqlparser/v2 v2.5.34 h1:MEea5P0qhdcqfBL45ghKE+qr9laidVHTMHjav5h7ckk=
github.com/vektah/gqlparser/v2 v2.5.34/go.mod h1:mFdHLGCio7OGX1fby9ZjTW6FN+qxgmbnBcRIeeScE5s=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
Expand Down
10 changes: 10 additions & 0 deletions authbridge/authlib/pipeline/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,16 @@ func (c *Context) clearCurrent() {
// them.
func (c *Context) RejectingPlugin() string { return c.rejectingPlugin }

// CurrentPhase reports the phase of the plugin dispatch currently in
// flight — InvocationPhaseRequest while Pipeline.Run is iterating
// OnRequest, InvocationPhaseResponse while Pipeline.RunResponse is
// iterating OnResponse, and "" outside a dispatch. Plugins read it to
// distinguish request from response without inferring the phase from
// body presence (an empty-bodied 204 response would otherwise look
// like a request). Set by the framework via setCurrent before each
// dispatch; see SetCurrentPlugin.
func (c *Context) CurrentPhase() InvocationPhase { return c.currentPhase }

// setRejectingPlugin records the name of the plugin that returned
// Reject. Framework-internal; callers in Pipeline.Run / RunResponse
// set this once per request, never overwrite (first rejection wins,
Expand Down
18 changes: 18 additions & 0 deletions authbridge/authlib/pipeline/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,10 +391,28 @@ func (d *DelegationExtension) Depth() int {
}

// DelegationHop represents one hop in the delegation chain.
//
// Audience, Strategy, and FromCache enrich a hop produced by an
// AuthBridge auth plugin (today: token-exchange) so downstream policy
// can reason about WHAT a token was minted for and HOW. They map onto
// the corresponding CPEX DelegationHop fields. All three are optional —
// a producer that only knows the subject leaves them zero.
type DelegationHop struct {
SubjectID string
Scopes []string
Timestamp time.Time

// Audience is the target audience the hop's token was minted for
// (RFC 8693 `audience`). Empty when the hop isn't an exchange.
Audience string

// Strategy names how the hop was produced — e.g. "token-exchange"
// for an RFC 8693 exchange. Empty when unclassified.
Strategy string

// FromCache reports the hop's token was served from the exchange
// cache rather than freshly minted at the IdP.
FromCache bool
}

// AppendHop adds a hop to the delegation chain. This is the only way to extend
Expand Down
Loading
Loading