Repository navigation
feat(operator)!: enforce access.groups at the gateway with nebari-operator v0.2.0 - #168
Merged
Merged
Conversation
…rator v0.2.0
nebari-operator v0.2.0 is the first release that enforces a NebariApp's
auth.groups at the gateway. A restricted App's NebariApp now carries
auth.groups with enforceAtGateway: true, so a signed-in non-member gets 403
from the gateway before the request reaches the pod, and the access token
is no longer forwarded to app pods (forwardAccessToken is dropped).
The apps-authz sidecar loses its enforcing mode: it only proxies, recording
activity for scale to zero and fronting image Apps on 8080. Only Apps that
can idle or run an image get it. It refuses to start when given group
settings, so an older operator can never run a restricted App open.
The operator refuses access.groups entries that name no group ("/"), which
nebari-operator would turn into a deny-all policy, and RoutingReady now
carries the NebariApp's reason (GroupsNotResolved and friends). The dev
cluster re-clones nebari-operator when the pin changes.
BREAKING CHANGE: requires nebari-operator v0.2.0; upgrade it before the
pack, or restricted Apps are open to every signed-in user. The operator's
--keycloak-url, --keycloak-internal-url and --keycloak-realm flags are
removed. Restricted Apps that neither idle nor run an image lose the
sidecar, so their Python tasks move from port 8081 to 8080 ($PORT).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ap1Tqx1QLHRGVVSXdxeduM
nebari-operator v0.2.0 makes the groups claim carry full paths and matches auth.groups exactly: "finance" is "/finance", and "/finance" does not admit members of "/finance/analysts" only. apps-api now matches api.adminGroups and access.managers.groups the same way (groups.group_path mirrors nebari-operator's NormalizePath), reports the caller's groups as full paths, and refuses group entries that name no group instead of entries with a comma. The UI validates and explains the rule, and the MCP tool and prompt descriptions say so. BREAKING CHANGE: a subgroup listed by its bare name in api.adminGroups or access.managers.groups no longer matches; list its full path. GET /auth/me returns groups with a leading "/". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ap1Tqx1QLHRGVVSXdxeduM
Architecture, configuration (NebariApp fields the chart emits), launching apps (Access control), troubleshooting, the runbook, getting started, the CRD and API references, upgrading (upgrade nebari-operator first, the one-time re-login from the renamed access-token cookie, full-path groups), README, AGENTS.md, the skill and the examples now describe the split: the gateway enforces sign-in and access.groups, and the sidecar only tracks activity and fronts image Apps. The docs version test also catches non-alpha pins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ap1Tqx1QLHRGVVSXdxeduM
|
📄 Docs preview for |
This was referenced Oct 22, 2026
jbouder
added a commit
that referenced
this pull request
Oct 22, 2026
Since the gateway took over sign-in and access.groups (nebari-operator v0.2.0 SecurityPolicy, #168), the sidecar authorizes nothing: it proxies requests, records activity for scale to zero and gives image Apps the single front port 8080. The name "authz" made readers think the pod checks access. Rename it before 0.2.0, which already renames the images (#169), and before the Quay repositories are created (#85). The directory, Go module, image, chart values, operator flags, container name, port name and sidecar env vars move from authz to proxy. Ports and behaviour are unchanged. The sidecar still refuses to start when AUTHZ_GROUPS (or another setting of the removed enforcing mode) is set, under its old name on purpose, and now also when it gets its own settings under the old AUTHZ_ names, so an operator from before the rename fails closed instead of starting an App open. AUTHZ_PASSTHROUGH, which nothing read any more, is no longer mentioned. BREAKING CHANGE: the sidecar image is now ghcr.io/nebari-dev/apps-pack/proxy (quay.io/nebari/apps-pack-proxy) and the chart's authz.* values are now proxy.*; the schema rejects the old key. The sidecar container is renamed from authz to proxy, so every pod that runs it rolls once. Upgrade the chart and images together. Claude-Session: https://claude.ai/code/session_01Ap1Tqx1QLHRGVVSXdxeduM Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #153
Summary
nebari-operator v0.2.0 (released 2026-10-21) is the first release that enforces a NebariApp's
auth.groupsat the gateway (nebari-operator#198, #209, #214, #215). This PR movesaccess.groupsenforcement there and strips enforcement out of the apps-authz sidecar.Operator
v0.1.0-alpha.20tov0.2.0(.nebari-operator-version,NebariOperatorContract). The dev Makefile reads the pin and now re-clones its cached nebari-operator checkout when the pin changes. The cached one was onalpha.19.auth.groups(as written) withenforceAtGateway: true.forwardAccessTokenis no longer set, so app pods (untrusted code) never receive the user's token.$PORT).access.groupsentries that name no group (/, blank). nebari-operator would turn those into a deny-all policy (GroupsEmpty). Commas are now allowed, since nothing joins the list into an env var any more.RoutingReadynow carries the NebariApp's reason, soGroupsNotResolved(a path missing in Keycloak),GroupsEmptyandGroupsClaimConflictshow up on the App.--keycloak-url,--keycloak-internal-urland--keycloak-realmflags are removed (only the sidecar used them), along with the chart args.apps-authz sidecar
verify.go/jwks.goand the go-oidc/go-jose dependencies are gone. It proxies every request and serves/activity.AUTHZ_GROUPS,AUTHZ_ISSUERS,AUTHZ_JWKS_URLorAUTHZ_CLIENT_IDis set. With a newer image and an older operator, a restricted App stays down instead of running open.AUTHZ_PASSTHROUGHis ignored.Full-path group matching (API, UI, MCP)
finance==/finance, and/financedoes not admit someone who is only in/finance/analysts. apps-api (groups.py), the operator (groupPath) and the UI (groupPath) all mirror nebari-operator'sgroups.NormalizePath.api.adminGroupsandaccess.managers.groupsas full paths, and/auth/mereports groups as full paths. It already stripped the leading/before, so the v0.2.0 claim change was handled. This makes subgroup handling exact. It also rejects group entries that name no group.launch_appandreview_accesstext say so too.Docs: architecture (with a gateway vs sidecar table), configuration ("NebariApp fields the chart emits"), launching apps (Access control), troubleshooting, runbook (new "Group access" section), getting started, CRD/API reference, upgrading, README, AGENTS.md, the skill and the examples are all updated.
upgrading.mdcovers:NebariApp contract changes from v0.1.0-alpha.20 to v0.2.0
I diffed
api/v1/nebariapp_types.goand the CRD between the two tags. Besidesauth.groupsenforcement, the changes are:auth.groupsitems gainedminLength: 1. The pack's validation is stricter than that.landingPage.iconLight/iconDarkandstatus.serviceDiscovery.iconLight/iconDark(v0.1.0). They are additive and the pack doesn't set them.GroupsNotResolved,GroupsRequireKeycloak,GroupsClaimConflict,GroupsEnforcedByApplication,GroupsEmpty. New events:GroupsMappersRemoved,Configured.KEYCLOAK_EXTERNAL_URLmust be set on nebari-operator for Envoy Gateway v1.9.1+ (v0.1.2). This is operator config; the pack doesn't need to change.None of these changes break a field the pack emits.
Security note
The gateway is now the only group check, and the sidecar used to refuse non-members even for in-cluster traffic. The docs recommend
apps.networkPolicy.enabled(ingress only from the gateway namespace). If the pack is upgraded before nebari-operator, restricted Apps are open to every signed-in user, soupgrading.mdsays to upgrade nebari-operator first. The operator can't detect the nebari-operator version without new CRD read RBAC, which this PR doesn't add.How it was tested
These are local quality gates only. Nothing ran on a cluster.
go build,go vet,go test ./... -race(internal coverage 90.1%, floor 89),golangci-lint run(0 issues), envtest suite (-tags envtest, k8s 1.37.0) passes, controller-gen v0.21.0 regenerated the CRD,gen_crd_types.pywas run, andcheck_crd_compat.pypasses.go mod tidy -diff,go vet,go build,golangci-lint run,go test -race(internal coverage 96.8%, floor 95).uv lock --check, pytest with coverage (316 passed, 91.9%), ruff check, ruff format --check, mypy.npm run check:ci, build,npm run size(192.1/245 kB),test:coverage(288 passed), andtest:e2e(150 passed).bun test testandbun run build.helm lint,helm template(default andexamples/nebari-values.yaml), helm-docs README regenerated.check_doc_paths.py,check_versions.py.Follow-ups (not in this PR)
403from the gateway, and a public App and an idling App (including a restricted idling App and its activator page) still work. Also check the one-time re-login.nebari-localto nebari-operator v0.2.0 and an apps-pack build with this change, and bumpgitops/apps/apps-pack.yamlthere.access.managers.groupsandapi.adminGroupson live clusters for subgroups listed by bare name (query inupgrading.md).🤖 Generated with Claude Code