CELINE Dashboards
This repository provides the CELINE Dashboards stack, a production‑ready analytics environment built around Apache Superset and Jupyter, secured through Keycloak and oauth2‑proxy, and fronted by Caddy.
The project is designed to deliver: - centralized SSO authentication, - consistent authorization across services, - Docker‑first local development and deployment, - extensible, auditable security logic aligned with the CELINE platform.
Overview
The stack exposes multiple web services (currently Superset and Jupyter) behind a single authentication boundary. Authentication is fully delegated to Keycloak via oauth2‑proxy, while each application consumes identity and authorization data from trusted headers or JWTs.
Key goals: - No local passwords in Superset or Jupyter - Automatic user provisioning - Role and group synchronization from Keycloak - Support for browser users and service / CLI tokens
Architecture
All services are fronted by Caddy, which delegates authentication to oauth2-proxy. oauth2-proxy validates sessions against Keycloak (OIDC). Authenticated requests are forwarded by Caddy to Superset or Jupyter, which each enforce authorization locally.
| Component | Role |
|---|---|
| Caddy | Reverse proxy, TLS termination, forward_auth to oauth2-proxy |
| oauth2-proxy | OIDC authentication gateway, injects identity headers |
| Keycloak | Identity provider, group and role management |
| Superset | Analytics dashboards, custom SecurityManager |
| Jupyter | Notebook execution, JWT-based authorizer |
Authentication and authorization flow:
- User accesses a protected service (Superset or Jupyter).
- Caddy delegates authentication to oauth2‑proxy (
forward_auth). - oauth2‑proxy authenticates the user via Keycloak (OIDC).
- Identity headers and access tokens are forwarded back to Caddy.
- Requests are proxied to the target service.
- Each service validates and enforces authorization locally:
- Superset via a custom
SecurityManager - Jupyter via a JWT‑based authorizer
Repository Structure
.
├── config/
│ ├── caddy/ # Reverse proxy configuration
│ ├── oauth2-proxy/ # oauth2-proxy configuration
│ ├── superset/ # Superset configuration and env files
│ └── jupyter/ # Jupyter server configuration
│
├── packages/
│ ├── celine-superset/ # celine.superset — Superset SSO plugin and management CLI (own src/, tests/)
│ └── celine-jupyter/ # celine.jupyter — Jupyter JWT authorizer (own src/, tests/)
├── pyproject.toml # uv workspace root (members: packages/*), not a package
│
├── Dockerfile # Superset image
├── Dockerfile.jupyter # Jupyter image
├── docker-compose.yaml # Full local stack
├── version.txt # Superset image version
├── version.jupyter.txt # Jupyter image version
├── taskfile.yaml # Common developer tasks
└── README.md
Services
Superset
- Authentication type:
AUTH_REMOTE_USER - Login and logout fully delegated to oauth2‑proxy
- Custom
OAuth2ProxySecurityManager: - validates JWT signatures via Keycloak JWKS
- auto‑creates users on first login
- synchronizes roles on each login
- maps the Keycloak realm role
platform-adminand organisation groups to Superset roles
The mapping is defined in packages/celine-superset/src/celine/superset/auth/groups.py.
There are exactly two levels:
| Token claim | Superset role |
|---|---|
realm role platform-admin (realm_access.roles) |
Admin |
organization.<slug>.groups: admins, managers, editors |
org:<slug>:<level> |
organization.<slug>.groups: viewers, any other group, or membership alone |
org:<slug>:viewers |
Realm groups (the top-level groups claim, e.g. /admins) and every other realm role grant
nothing. An organisation's admins is that organisation's admin only.
Jupyter
- No server token, password or login page: only an access token opens it
- Runs as
jupyter celine-server, Jupyter Server with the CELINE identity provider and authorizer built in - Trusts one issuer,
CELINE_JUPYTER_JWT_ISSUER(the realm URL); tokens of any other issuer are refused before any key is fetched - Fails closed: it does not start when that issuer is unset, when the authentication cannot be loaded, or when configuration replaces it or re-opens unauthenticated access
- Intended for notebook execution under the same SSO boundary
Only holders of the Keycloak realm role platform-admin get access (all or nothing). An
organisation's admins group and a realm group such as /admins grant nothing.
Authentication & Identity
Keycloak
The realm is not defined here: celine-policies reconciles it. This stack consumes:
- Realm:
celine - Clients:
oauth2_proxy(browser SSO); its access token must carryrealm_access.roles(therolesclient scope) andorganization(requestorganization:*)celine-cli(service and CLI tokens)- Platform administrators: the realm role
platform-admin - Organisation roles: each organisation's
admins,managers,editors,viewersgroups
oauth2‑proxy
oauth2‑proxy acts as the single authentication gateway:
- Handles browser login flows
- Injects identity headers and access tokens
- Supports service tokens via
skip_jwt_bearer_tokens - Shares cookies across
*.celine.localhost
It is exposed through a dedicated SSO endpoint:
http://sso.celine.localhost
Documentation
| Document | Description |
|---|---|
| Architecture | Service flow, auth chain, identity propagation |
| Authentication | Keycloak realm, oauth2-proxy setup, JWT validation, cookie sharing |
| Services | Superset SecurityManager, Jupyter JWT authorizer, Caddy config |
| Development | task ensure-env, docker compose, service URLs, extensibility |
Local Development
Prerequisites
- Docker
- Docker Compose
- Task (https://taskfile.dev)
Setup
- Clone the repository
git clone https://github.com/celine-eu/celine-dashboards.git
cd celine-dashboards
- Initialize environment files
task ensure-env
- Start the full stack
docker compose up -d
-
Access services
-
Superset: http://superset.celine.localhost
- Jupyter: http://jupyter.celine.localhost
- SSO / oauth2‑proxy: http://sso.celine.localhost
- Keycloak: http://keycloak.celine.localhost
Docker Images & CI
Docker images are built and published automatically via GitHub Actions.
Superset
ghcr.io/celine-eu/superset:<version>
ghcr.io/celine-eu/superset:latest
The version is defined in version.txt.
Jupyter
ghcr.io/celine-eu/jupyter:<version>
ghcr.io/celine-eu/jupyter:latest
The version is defined in version.jupyter.txt.
Images are rebuilt automatically when relevant source or configuration files change.
Extensibility
This repository is intentionally structured to allow:
- swapping or extending identity providers,
- customizing JWT claim formats,
- adding new services behind the same SSO boundary,
- refining role‑based access control logic.
Authentication logic is isolated in reusable Python packages and kept stateless.
Contributing
Contributions are welcome.
Guidelines: - Keep authentication logic stateless and defensive - Do not introduce local login mechanisms - Ensure JWT validation remains explicit and verifiable - Add tests for any auth‑related changes
License
Copyright © 2025 Spindox Labs
Licensed under the Apache License, Version 2.0.
You may not use this file except in compliance with the License.
See http://www.apache.org/licenses/LICENSE-2.0 for details.