Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 26 additions & 6 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,36 @@ jobs:

- name: Update Helm repo index
if: steps.check.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p gh-pages
set -euo pipefail
GH_PAGES_URL="https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
REPO_URL="https://github.com/${{ github.repository }}/releases/download/${{ steps.chart.outputs.name }}-${{ steps.chart.outputs.version }}"

# Preserve any existing gh-pages history (and index) so prior chart
# releases are not lost. Bootstrap the branch on first release.
if git ls-remote --exit-code --heads "${GH_PAGES_URL}" gh-pages >/dev/null 2>&1; then
git clone --depth 1 --branch gh-pages "${GH_PAGES_URL}" gh-pages
else
mkdir -p gh-pages
git -C gh-pages init -b gh-pages
git -C gh-pages remote add origin "${GH_PAGES_URL}"
fi

cp *.tgz gh-pages/
cd gh-pages
git init
git checkout -b gh-pages

# Merge with the existing index when present so old chart versions
# remain installable from the Helm repo.
if [ -f index.yaml ]; then
helm repo index . --url "${REPO_URL}" --merge index.yaml
else
helm repo index . --url "${REPO_URL}"
fi

git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
helm repo index . --url https://github.com/${{ github.repository }}/releases/download/${{ steps.chart.outputs.name }}-${{ steps.chart.outputs.version }}
git add .
git commit -m "Release ${{ steps.chart.outputs.name }}-${{ steps.chart.outputs.version }}"
git push origin gh-pages --force
git push origin gh-pages
14 changes: 9 additions & 5 deletions .github/workflows/test-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
timeout-minutes: 45
env:
CLUSTER_NAME: pack-integration
OPERATOR_REF: v0.1.0-alpha.19
steps:
- name: Checkout software-pack-template
uses: actions/checkout@v4
Expand All @@ -24,6 +25,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: nebari-dev/nebari-operator
ref: ${{ env.OPERATOR_REF }}
path: nebari-operator

- name: Create kind cluster
Expand Down Expand Up @@ -86,7 +88,7 @@ jobs:
# ============================================================
- name: Install nebari-operator
run: |
kubectl apply -f https://github.com/nebari-dev/nebari-operator/releases/download/v0.1.0-alpha.5/install.yaml
kubectl apply -f https://github.com/nebari-dev/nebari-operator/releases/download/${OPERATOR_REF}/install.yaml

- name: Wait for nebari-operator
run: |
Expand Down Expand Up @@ -206,21 +208,23 @@ jobs:
- name: "Deploy wrap-existing-chart (podinfo)"
run: |
helm dependency update examples/wrap-existing-chart/chart/
helm install test-podinfo examples/wrap-existing-chart/chart/ \
helm install test-wrap examples/wrap-existing-chart/chart/ \
--set nebariapp.enabled=true \
--set nebariapp.hostname=podinfo.nebari.local \
--wait --timeout 3m

- name: "Verify wrap-existing-chart NebariApp"
run: |
kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=test-podinfo --timeout=120s
kubectl wait --for=condition=Ready nebariapp/test-podinfo-my-pack --timeout=120s
# Podinfo's chart labels its pods with app.kubernetes.io/name=<release>-podinfo
# and does not set app.kubernetes.io/instance, so wait on the Deployment instead.
kubectl wait --for=condition=available deployment/test-wrap-podinfo --timeout=120s
kubectl wait --for=condition=Ready nebariapp/test-wrap-my-pack --timeout=120s
kubectl get httproute -l app.kubernetes.io/managed-by=nebari-operator
echo "wrap-existing-chart: NebariApp Ready"

- name: "Cleanup wrap-existing-chart"
if: always()
run: helm uninstall test-podinfo --wait || true
run: helm uninstall test-wrap --wait || true

# ============================================================
# Debug on failure
Expand Down
40 changes: 35 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ spec:
enabled: true # Require login (default: false)
provider: keycloak # keycloak or generic-oidc
provisionClient: true # Auto-create Keycloak client (default: true)
redirectURI: / # OAuth callback path
# redirectURI: /oauth2/callback # OAuth callback path (default shown; rarely needs overriding)
scopes: # OIDC scopes to request
- openid
- profile
Expand Down Expand Up @@ -247,6 +247,36 @@ kind: NebariApp
With plain YAML or Kustomize, the NebariApp manifest is always present. When
deploying standalone, simply skip that file or exclude it from your apply command.

### Beyond the basics

The fields shown above cover the common cases. The operator also supports several
more specialized features. Each is documented in
[docs/nebariapp-crd-reference.md](docs/nebariapp-crd-reference.md):

- **`routing.publicRoutes`** - paths that bypass OIDC auth (e.g., `/healthz`,
webhooks, public APIs).
- **`routing.annotations`** - extra annotations on the generated HTTPRoute, useful
for ArgoCD tracking or other tooling.
- **`auth.forwardAccessToken`** - send the user's access token to your app as
`Authorization: Bearer <token>` so it can decode the JWT itself.
- **`auth.denyRedirect`** - return 401 instead of redirecting to Keycloak when
matching headers are present. Most useful alongside `auth.spaClient` to avoid
PKCE races when an SPA fires several requests on page load.
- **`auth.spaClient`** - provision a separate public Keycloak client for browser-
based PKCE flows (React + `keycloak-js`, etc.).
- **`auth.deviceFlowClient`** - provision a public client for the OAuth2 Device
Authorization Grant (CLIs and native apps).
- **`auth.keycloakConfig`** - declaratively manage Keycloak realm groups and
client-level protocol mappers from the NebariApp.
- **`auth.tokenExchange`** - opt this app into RFC 8693 token exchange so other
NebariApp clients can mint tokens for its audience.
- **`landingPage`** - register the app on the Nebari landing page with an icon,
category, priority, and optional health check.
- **`serviceAccountName`** - which ServiceAccount the operator should grant access
to the OIDC client Secret.
- **`service.namespace`** - point the NebariApp at a Service in a different
namespace.

For the complete field reference, see [docs/nebariapp-crd-reference.md](docs/nebariapp-crd-reference.md).

## Example 1: Vanilla YAML (Plain Manifests)
Expand Down Expand Up @@ -400,8 +430,8 @@ spec:
helm dependency update examples/wrap-existing-chart/chart/

# Deploy standalone
helm install test-podinfo examples/wrap-existing-chart/chart/
kubectl port-forward svc/test-podinfo-podinfo 9898:9898
helm install test-wrap examples/wrap-existing-chart/chart/
kubectl port-forward svc/test-wrap-podinfo 9898:9898

# Deploy on Nebari
helm install my-pack examples/wrap-existing-chart/chart/ \
Expand All @@ -427,7 +457,7 @@ Gateway SecurityPolicy that handles the full OIDC flow:
- IdToken-<suffix> (JWT with user claims)
- AccessToken-<suffix>
- RefreshToken-<suffix>
(<suffix> is an 7-char, lower-case hex derived from the SecurityPolicy UID)
(<suffix> is an 8-char, lower-case hex derived from the SecurityPolicy UID via FNV-32a)
7. Request (now with cookies) is forwarded to your app
```

Expand Down Expand Up @@ -531,7 +561,7 @@ file. Tests each example with `nebariapp.enabled=true` on a full Nebari
infrastructure stack:

- Creates a kind cluster with MetalLB, Envoy Gateway, cert-manager, and Keycloak
- Installs the nebari-operator from the latest published release
- Installs the nebari-operator from a pinned release (currently `v0.1.0-alpha.19`)
- Deploys each example with NebariApp enabled and a `*.nebari.local` hostname
- Verifies NebariApp reaches `Ready` condition (HTTPRoute created, TLS configured)
- For auth-enabled examples (kustomize production, auth-fastapi), verifies
Expand Down
4 changes: 2 additions & 2 deletions dev/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ _cluster-create: _clone-operator _kind-create _metallb _services _keycloak-setup
@echo ""
@echo "Run 'make up-basic' (or any up-* target) to deploy an example."

OPERATOR_REF := main
OPERATOR_REF := v0.1.0-alpha.19

_clone-operator:
@if [ ! -d "$(OPERATOR_REPO)" ]; then \
Expand Down Expand Up @@ -110,7 +110,7 @@ _keycloak-setup:

_operator:
@echo "Installing nebari-operator..."
kubectl apply -f https://github.com/nebari-dev/nebari-operator/releases/download/v0.1.0-alpha.5/install.yaml
kubectl apply -f https://github.com/nebari-dev/nebari-operator/releases/download/$(OPERATOR_REF)/install.yaml
kubectl wait --for=condition=available deployment/nebari-operator-controller-manager \
-n nebari-operator-system --timeout=120s
@echo "nebari-operator is running"
Expand Down
45 changes: 42 additions & 3 deletions docs/auth-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,44 @@ userinfo endpoint and maps them to Superset roles (Admin, Gamma, Public). See it
[nebari-values.yaml](https://github.com/nebari-dev/nebari-superset-pack/blob/main/examples/nebari-values.yaml)
for the complete configuration.

## NebariApp CRD vs Envoy Gateway SecurityPolicy

The fields documented in [nebariapp-crd-reference.md](nebariapp-crd-reference.md) are the
fields the **operator** understands - they go on `spec.auth` of the NebariApp resource.
At runtime, the operator generates an Envoy Gateway `SecurityPolicy` from the NebariApp,
and that SecurityPolicy has its own (much larger) set of OIDC tuning knobs.

This section is specifically about the **Envoy Gateway SecurityPolicy OIDC
filter** fields. Other Keycloak-side or client-provisioning features (`spaClient`,
`deviceFlowClient`, `keycloakConfig`, `tokenExchange`) are first-class NebariApp
fields and are documented in
[nebariapp-crd-reference.md](nebariapp-crd-reference.md).

For the OIDC filter fields specifically, mentally place each one in one of these
buckets:

1. **Surfaced on NebariApp.** The operator exposes the field as a NebariApp
`spec.auth.*` field and copies it into the SecurityPolicy at reconcile time.
Currently this includes `forwardAccessToken` (`auth.forwardAccessToken`) and
redirect-deny rules (`auth.denyRedirect`). Set these on the NebariApp.

2. **Not surfaced on NebariApp.** Most fine-grained OIDC filter fields - including
`cookieNames`, `disableIdToken`, `disableAccessToken`, `passThroughAuthHeader`,
custom logout URLs, and similar - are not exposed on NebariApp today. To use
them, either:
- File an issue / PR on
[nebari-operator](https://github.com/nebari-dev/nebari-operator) asking for the
field to be plumbed through.
- Set `auth.enforceAtGateway: false` and manage your own `SecurityPolicy`
resource alongside the NebariApp. The operator will still provision the
Keycloak client and Secret, but won't generate a SecurityPolicy to conflict
with yours.

The shape of the SecurityPolicy the operator generates is documented in
[What the Operator Creates → 3. Envoy Gateway SecurityPolicy](#3-envoy-gateway-securitypolicy-when-enforceatgateway-true).
Refer to the [Envoy Gateway SecurityPolicy reference](https://gateway.envoyproxy.io/docs/api/extension_types/#securitypolicy)
for the full set of OIDC fields.

## Limitations

- **Local development:** The OIDC flow requires Keycloak and Envoy Gateway. When
Expand All @@ -365,6 +403,7 @@ for the complete configuration.

- **Cookie size:** Very large JWTs (many groups/roles) may exceed browser cookie
size limits (typically 4KB). If this is an issue, reduce token size by limiting
scopes/claims at the Keycloak level, or use `disableIdToken`/`disableAccessToken`
in the SecurityPolicy OIDC config to skip setting cookies for tokens your app
doesn't need.
scopes/claims at the Keycloak level. Token-cookie suppression
(`disableIdToken`/`disableAccessToken`) is a SecurityPolicy field the operator
does not currently expose - see the boundary section above for how to use it
if you need to.
Loading
Loading