feat(superset): install Apache Superset
This commit is contained in:
89
CLAUDE.md
89
CLAUDE.md
@@ -22,6 +22,27 @@ just # Show all available commands
|
|||||||
- **List All Recipes**: Run `just` to display all available recipes across modules
|
- **List All Recipes**: Run `just` to display all available recipes across modules
|
||||||
- **Module-Specific Help**: Run `just <module>` (e.g., `just keycloak`) to show recipes for that module
|
- **Module-Specific Help**: Run `just <module>` (e.g., `just keycloak`) to show recipes for that module
|
||||||
- **Execution Location**: ALWAYS run all recipes from the top directory (buun-stack root)
|
- **Execution Location**: ALWAYS run all recipes from the top directory (buun-stack root)
|
||||||
|
- **Recipe Parameters**: Recipe parameters are passed as **positional arguments**, not named arguments
|
||||||
|
|
||||||
|
**Parameter Passing Examples:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CORRECT: Positional arguments
|
||||||
|
just postgres::create-user-and-db superset superset "password123"
|
||||||
|
|
||||||
|
# INCORRECT: Named arguments (will not work)
|
||||||
|
just postgres::create-user-and-db username=superset db_name=superset password="password123"
|
||||||
|
|
||||||
|
# Recipe definition (for reference)
|
||||||
|
create-user-and-db username='' db_name='' password='':
|
||||||
|
just create-db "{{ db_name }}"
|
||||||
|
just create-user "{{ username }}" "{{ password }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Notes:**
|
||||||
|
- Parameters must be passed in the exact order they appear in the recipe definition
|
||||||
|
- Named parameter syntax in the recipe definition is only for documentation
|
||||||
|
- Always quote parameters that contain special characters or spaces
|
||||||
|
|
||||||
### Core Installation Sequence
|
### Core Installation Sequence
|
||||||
|
|
||||||
@@ -92,53 +113,69 @@ All scripts in `/keycloak/scripts/` follow this pattern:
|
|||||||
|
|
||||||
### Credential Storage Pattern
|
### Credential Storage Pattern
|
||||||
|
|
||||||
The credential storage approach depends on whether External Secrets Operator is available:
|
The credential storage approach depends on the type of secret and whether External Secrets Operator is available:
|
||||||
|
|
||||||
**When External Secrets is available** (determined by `helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE}`):
|
|
||||||
|
|
||||||
- Credentials are generated and stored in Vault using `just vault::put` commands
|
|
||||||
- Vault commands are used for secret management
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Example: PostgreSQL superuser password (only when External Secrets is available)
|
|
||||||
just vault::get secret/postgres/superuser password
|
|
||||||
```
|
|
||||||
|
|
||||||
**When External Secrets is NOT available**:
|
|
||||||
|
|
||||||
- Credentials are stored directly as Kubernetes Secrets
|
|
||||||
- Vault commands are NOT used
|
|
||||||
|
|
||||||
#### Secret Management Rules
|
#### Secret Management Rules
|
||||||
|
|
||||||
1. **Environment File**: Do NOT write to `.env.local` directly for secrets. Use it only for configuration values.
|
1. **Environment File**: Do NOT write to `.env.local` directly for secrets. Use it only for configuration values.
|
||||||
|
|
||||||
2. **Vault and External Secrets Integration**:
|
2. **Two Types of Secrets**:
|
||||||
- When Vault and External Secrets are available, ALWAYS:
|
|
||||||
- Store secrets in Vault
|
**Application Secrets** (Metabase, Querybook, Superset, etc.):
|
||||||
- Create ExternalSecret resources to sync secrets from Vault to Kubernetes
|
- When External Secrets Operator is available:
|
||||||
|
- Store in Vault using `just vault::put`
|
||||||
|
- Create ExternalSecret resources to sync from Vault to Kubernetes
|
||||||
- Let External Secrets Operator create the actual Secret resources
|
- Let External Secrets Operator create the actual Secret resources
|
||||||
- Check availability with:
|
- When External Secrets Operator is NOT available:
|
||||||
|
- Create Kubernetes Secrets directly
|
||||||
|
- Do NOT store in Vault (even if Vault is available)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then
|
if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then
|
||||||
# Use Vault + External Secrets pattern
|
# Store in Vault + create ExternalSecret
|
||||||
|
just vault::put app/config key="${value}"
|
||||||
|
gomplate -f app-external-secret.gomplate.yaml | kubectl apply -f -
|
||||||
|
else
|
||||||
|
# Create Kubernetes Secret directly (no Vault)
|
||||||
|
kubectl create secret generic app-secret --from-literal=key="${value}"
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Fallback Pattern**: Only create Kubernetes Secrets directly when Vault/External Secrets are not available.
|
**Core/Admin Credentials** (PostgreSQL superuser, Keycloak admin, MinIO root, etc.):
|
||||||
|
- When External Secrets Operator is available:
|
||||||
|
- Store in Vault using `just vault::put` or `just vault::put-root`
|
||||||
|
- Create ExternalSecret resources
|
||||||
|
- When External Secrets Operator is NOT available:
|
||||||
|
- Create Kubernetes Secrets directly
|
||||||
|
- ALSO store in Vault if Vault is available (as backup)
|
||||||
|
|
||||||
4. **Helm Values Secret References**:
|
```bash
|
||||||
|
if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then
|
||||||
|
# Store in Vault + create ExternalSecret
|
||||||
|
just vault::put-root postgres/admin username=postgres password="${password}"
|
||||||
|
gomplate -f postgres-superuser-external-secret.gomplate.yaml | kubectl apply -f -
|
||||||
|
else
|
||||||
|
# Create Kubernetes Secret directly
|
||||||
|
kubectl create secret generic postgres-cluster-superuser \
|
||||||
|
--from-literal=username=postgres --from-literal=password="${password}"
|
||||||
|
# ALSO store in Vault if available (backup for admin credentials)
|
||||||
|
if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then
|
||||||
|
just vault::put-root postgres/admin username=postgres password="${password}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Helm Values Secret References**:
|
||||||
- When Helm charts support referencing external Secrets (via `existingSecret`, `secretName`, etc.), ALWAYS use this pattern
|
- When Helm charts support referencing external Secrets (via `existingSecret`, `secretName`, etc.), ALWAYS use this pattern
|
||||||
- Create the Secret using External Secrets (preferred) or directly as Kubernetes Secret
|
- Create the Secret using External Secrets (preferred) or directly as Kubernetes Secret
|
||||||
- Reference the Secret in Helm values instead of embedding credentials
|
- Reference the Secret in Helm values instead of embedding credentials
|
||||||
|
|
||||||
5. **Keycloak Client Configuration**:
|
4. **Keycloak Client Configuration**:
|
||||||
- Prefer creating Public clients (without client secret) when possible
|
- Prefer creating Public clients (without client secret) when possible
|
||||||
- Public clients are suitable for browser-based applications and native apps
|
- Public clients are suitable for browser-based applications and native apps
|
||||||
- Only use confidential clients (with secret) when required by the service
|
- Only use confidential clients (with secret) when required by the service
|
||||||
|
|
||||||
6. **Password Generation**:
|
5. **Password Generation**:
|
||||||
- Use `just utils::random-password` whenever possible to generate random passwords
|
- Use `just utils::random-password` whenever possible to generate random passwords
|
||||||
- Avoid using `openssl rand -base64 32` or other direct methods
|
- Avoid using `openssl rand -base64 32` or other direct methods
|
||||||
- This ensures consistent password generation across all modules
|
- This ensures consistent password generation across all modules
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -40,7 +40,8 @@ A remotely accessible Kubernetes home lab with OIDC authentication. Build a mode
|
|||||||
- **[ClickHouse](https://clickhouse.com/)**: High-performance columnar analytics database
|
- **[ClickHouse](https://clickhouse.com/)**: High-performance columnar analytics database
|
||||||
- **[Qdrant](https://qdrant.tech/)**: Vector database for AI/ML applications
|
- **[Qdrant](https://qdrant.tech/)**: Vector database for AI/ML applications
|
||||||
- **[Lakekeeper](https://lakekeeper.io/)**: Apache Iceberg REST Catalog for data lake management
|
- **[Lakekeeper](https://lakekeeper.io/)**: Apache Iceberg REST Catalog for data lake management
|
||||||
- **[Metabase](https://www.metabase.com/)**: Business intelligence and data visualization
|
- **[Apache Superset](https://superset.apache.org/)**: BI platform with rich chart types and high customizability
|
||||||
|
- **[Metabase](https://www.metabase.com/)**: Lightweight BI with simple configuration and clean, modern interface
|
||||||
- **[DataHub](https://datahubproject.io/)**: Data catalog and metadata management
|
- **[DataHub](https://datahubproject.io/)**: Data catalog and metadata management
|
||||||
|
|
||||||
### Orchestration (Optional)
|
### Orchestration (Optional)
|
||||||
@@ -147,6 +148,18 @@ Multi-user platform for interactive computing with Keycloak authentication and p
|
|||||||
|
|
||||||
[📖 See JupyterHub Documentation](./jupyterhub/README.md)
|
[📖 See JupyterHub Documentation](./jupyterhub/README.md)
|
||||||
|
|
||||||
|
### Apache Superset
|
||||||
|
|
||||||
|
Modern business intelligence platform with rich visualization capabilities:
|
||||||
|
|
||||||
|
- **40+ Chart Types**: Mixed charts, treemaps, sunburst, heatmaps, and more
|
||||||
|
- **SQL Lab**: Powerful SQL editor for complex queries and dataset creation
|
||||||
|
- **Keycloak Authentication**: OAuth2 integration with group-based admin access
|
||||||
|
- **Trino Integration**: Connect to Iceberg data lake and multiple data sources
|
||||||
|
- **High Customizability**: Extensive chart configuration and dashboard design options
|
||||||
|
|
||||||
|
[📖 See Superset Documentation](./superset/README.md)
|
||||||
|
|
||||||
### Metabase
|
### Metabase
|
||||||
|
|
||||||
Business intelligence and data visualization platform with PostgreSQL integration.
|
Business intelligence and data visualization platform with PostgreSQL integration.
|
||||||
@@ -312,6 +325,7 @@ kubectl --context yourpc-oidc get nodes
|
|||||||
# Keycloak: https://auth.yourdomain.com
|
# Keycloak: https://auth.yourdomain.com
|
||||||
# Trino: https://trino.yourdomain.com
|
# Trino: https://trino.yourdomain.com
|
||||||
# Querybook: https://querybook.yourdomain.com
|
# Querybook: https://querybook.yourdomain.com
|
||||||
|
# Superset: https://superset.yourdomain.com
|
||||||
# Metabase: https://metabase.yourdomain.com
|
# Metabase: https://metabase.yourdomain.com
|
||||||
# Airflow: https://airflow.yourdomain.com
|
# Airflow: https://airflow.yourdomain.com
|
||||||
# JupyterHub: https://jupyter.yourdomain.com
|
# JupyterHub: https://jupyter.yourdomain.com
|
||||||
|
|||||||
1
justfile
1
justfile
@@ -24,6 +24,7 @@ mod oauth2-proxy
|
|||||||
mod postgres
|
mod postgres
|
||||||
mod qdrant
|
mod qdrant
|
||||||
mod querybook
|
mod querybook
|
||||||
|
mod superset
|
||||||
mod trino
|
mod trino
|
||||||
mod utils
|
mod utils
|
||||||
mod vault
|
mod vault
|
||||||
|
|||||||
3
superset/.gitignore
vendored
Normal file
3
superset/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Generated files from gomplate templates
|
||||||
|
superset-values.yaml
|
||||||
|
superset-config-external-secret.yaml
|
||||||
341
superset/README.md
Normal file
341
superset/README.md
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
# Apache Superset
|
||||||
|
|
||||||
|
Modern, enterprise-ready business intelligence web application with Keycloak OAuth authentication and Trino integration.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This module deploys Apache Superset using the official Helm chart with:
|
||||||
|
|
||||||
|
- **Keycloak OAuth authentication** for user login
|
||||||
|
- **Trino integration** for data lake analytics
|
||||||
|
- **PostgreSQL backend** for metadata storage (dedicated user)
|
||||||
|
- **Redis** for caching and Celery task queue
|
||||||
|
- **HTTPS reverse proxy support** via Traefik
|
||||||
|
- **Group-based access control** via Keycloak groups
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Kubernetes cluster (k3s)
|
||||||
|
- Keycloak installed and configured
|
||||||
|
- PostgreSQL cluster (CloudNativePG)
|
||||||
|
- Trino with password authentication
|
||||||
|
- External Secrets Operator (optional, for Vault integration)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Basic Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just superset::install
|
||||||
|
```
|
||||||
|
|
||||||
|
You will be prompted for:
|
||||||
|
|
||||||
|
1. **Superset host (FQDN)**: e.g., `superset.example.com`
|
||||||
|
2. **Keycloak host (FQDN)**: e.g., `auth.example.com`
|
||||||
|
|
||||||
|
### What Gets Installed
|
||||||
|
|
||||||
|
- Superset web application
|
||||||
|
- Superset worker (Celery for async tasks)
|
||||||
|
- PostgreSQL database and user for Superset metadata
|
||||||
|
- Redis for caching and Celery broker
|
||||||
|
- Keycloak OAuth client (confidential client)
|
||||||
|
- `superset-admin` group in Keycloak for admin access
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Environment variables (set in `.env.local` or override):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SUPERSET_NAMESPACE=superset # Kubernetes namespace
|
||||||
|
SUPERSET_CHART_VERSION=0.15.0 # Helm chart version
|
||||||
|
SUPERSET_HOST=superset.example.com # External hostname
|
||||||
|
KEYCLOAK_HOST=auth.example.com # Keycloak hostname
|
||||||
|
KEYCLOAK_REALM=buunstack # Keycloak realm name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Architecture Notes
|
||||||
|
|
||||||
|
**Superset 5.0+ Changes**:
|
||||||
|
|
||||||
|
- Uses `uv` instead of `pip` for package management
|
||||||
|
- Lean base image without database drivers (installed via bootstrapScript)
|
||||||
|
- Required packages: `psycopg2-binary`, `sqlalchemy-trino`, `authlib`
|
||||||
|
|
||||||
|
**Redis Image**:
|
||||||
|
|
||||||
|
- Uses `bitnami/redis:latest` due to Bitnami's August 2025 strategy change
|
||||||
|
- Community users can only use `latest` tag (no version pinning)
|
||||||
|
- For production version pinning, consider using official Redis image separately
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Access Superset
|
||||||
|
|
||||||
|
1. Navigate to `https://your-superset-host/`
|
||||||
|
2. Click "Sign in with Keycloak" to authenticate
|
||||||
|
3. Create charts and dashboards
|
||||||
|
|
||||||
|
### Grant Admin Access
|
||||||
|
|
||||||
|
Add users to the `superset-admin` group:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just keycloak::add-user-to-group <username> superset-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Admin users have full privileges including:
|
||||||
|
|
||||||
|
- Database connection management
|
||||||
|
- User and role management
|
||||||
|
- All chart and dashboard operations
|
||||||
|
|
||||||
|
### Configure Database Connections
|
||||||
|
|
||||||
|
**Prerequisites**: User must be in `superset-admin` group
|
||||||
|
|
||||||
|
#### Trino Connection
|
||||||
|
|
||||||
|
1. Log in as an admin user
|
||||||
|
2. Navigate to **Settings** → **Database Connections** → **+ Database**
|
||||||
|
3. Select **Trino** from supported databases
|
||||||
|
4. Configure connection:
|
||||||
|
|
||||||
|
```plain
|
||||||
|
DISPLAY NAME: Trino Iceberg (or any name)
|
||||||
|
SQLALCHEMY URI: trino://admin:<password>@trino.example.com/iceberg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Notes**:
|
||||||
|
- **Must use HTTPS hostname** (e.g., `trino.example.com`)
|
||||||
|
- **Cannot use internal service** (e.g., `trino.trino:8080`)
|
||||||
|
- Trino password authentication requires HTTPS connection
|
||||||
|
- Get admin password: `just trino::admin-password`
|
||||||
|
|
||||||
|
5. Click **TEST CONNECTION** to verify
|
||||||
|
6. Click **CONNECT** to save
|
||||||
|
|
||||||
|
**Available Trino Catalogs**:
|
||||||
|
|
||||||
|
- `iceberg` - Iceberg data lakehouse (Lakekeeper)
|
||||||
|
- `postgresql` - PostgreSQL connector
|
||||||
|
- `tpch` - TPC-H benchmark data
|
||||||
|
|
||||||
|
Example URIs:
|
||||||
|
|
||||||
|
```plain
|
||||||
|
trino://admin:<password>@trino.example.com/iceberg
|
||||||
|
trino://admin:<password>@trino.example.com/postgresql
|
||||||
|
trino://admin:<password>@trino.example.com/tpch
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Other Database Connections
|
||||||
|
|
||||||
|
Superset supports many databases. Examples:
|
||||||
|
|
||||||
|
**PostgreSQL**:
|
||||||
|
|
||||||
|
```plain
|
||||||
|
postgresql://user:password@postgres-cluster-rw.postgres:5432/database
|
||||||
|
```
|
||||||
|
|
||||||
|
**MySQL**:
|
||||||
|
|
||||||
|
```plain
|
||||||
|
mysql://user:password@mysql-host:3306/database
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create Charts and Dashboards
|
||||||
|
|
||||||
|
1. Navigate to **Charts** → **+ Chart**
|
||||||
|
2. Select dataset (from configured database)
|
||||||
|
3. Choose visualization type
|
||||||
|
4. Configure chart settings
|
||||||
|
5. Save chart
|
||||||
|
6. Add to dashboard
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Rich Visualizations**: 40+ chart types including tables, line charts, bar charts, maps, etc.
|
||||||
|
- **SQL Lab**: Interactive SQL editor with query history
|
||||||
|
- **No-code Chart Builder**: Drag-and-drop interface for creating charts
|
||||||
|
- **Dashboard Composer**: Create interactive dashboards with filters
|
||||||
|
- **Row-level Security**: Control data access per user/role
|
||||||
|
- **Alerting & Reports**: Schedule email reports and alerts
|
||||||
|
- **Semantic Layer**: Define metrics and dimensions for consistent analysis
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```plain
|
||||||
|
External Users
|
||||||
|
↓
|
||||||
|
Cloudflare Tunnel (HTTPS)
|
||||||
|
↓
|
||||||
|
Traefik Ingress (HTTPS)
|
||||||
|
↓
|
||||||
|
Superset Web (HTTP inside cluster)
|
||||||
|
├─ OAuth → Keycloak (authentication)
|
||||||
|
├─ PostgreSQL (metadata: charts, dashboards, users)
|
||||||
|
├─ Redis (cache, Celery broker)
|
||||||
|
└─ Celery Worker (async tasks)
|
||||||
|
↓
|
||||||
|
Data Sources (via HTTPS)
|
||||||
|
├─ Trino (analytics)
|
||||||
|
├─ PostgreSQL (operational data)
|
||||||
|
└─ Others
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Components**:
|
||||||
|
|
||||||
|
- **Proxy Fix**: `ENABLE_PROXY_FIX = True` for correct HTTPS redirect URLs behind Traefik
|
||||||
|
- **OAuth Integration**: Uses Keycloak OIDC discovery (`.well-known/openid-configuration`)
|
||||||
|
- **Database Connections**: Must use external HTTPS hostnames for authenticated connections
|
||||||
|
- **Role Mapping**: Keycloak groups map to Superset roles (Admin, Alpha, Gamma)
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
### User Login (OAuth)
|
||||||
|
|
||||||
|
- Users authenticate via Keycloak
|
||||||
|
- Standard OIDC flow with Authorization Code grant
|
||||||
|
- Group membership included in UserInfo endpoint response
|
||||||
|
- Roles synced at each login (`AUTH_ROLES_SYNC_AT_LOGIN = True`)
|
||||||
|
|
||||||
|
### Role Mapping
|
||||||
|
|
||||||
|
Keycloak groups automatically map to Superset roles:
|
||||||
|
|
||||||
|
```python
|
||||||
|
AUTH_ROLES_MAPPING = {
|
||||||
|
"superset-admin": ["Admin"], # Full privileges
|
||||||
|
"Alpha": ["Alpha"], # Create charts/dashboards
|
||||||
|
"Gamma": ["Gamma"], # View only
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Default Role**: New users are assigned `Gamma` role by default
|
||||||
|
|
||||||
|
### Access Levels
|
||||||
|
|
||||||
|
- **Admin**: Full access to all features (requires `superset-admin` group)
|
||||||
|
- **Alpha**: Create and edit charts/dashboards
|
||||||
|
- **Gamma**: View charts and dashboards only
|
||||||
|
|
||||||
|
## Management
|
||||||
|
|
||||||
|
### Upgrade Superset
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just superset::upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
Updates the Helm deployment with current configuration.
|
||||||
|
|
||||||
|
### Uninstall
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Keep PostgreSQL database
|
||||||
|
just superset::uninstall false
|
||||||
|
|
||||||
|
# Delete PostgreSQL database and user
|
||||||
|
just superset::uninstall true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Check Pod Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get pods -n superset
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected pods:
|
||||||
|
|
||||||
|
- `superset-*` - Main application (1 replica)
|
||||||
|
- `superset-worker-*` - Celery worker (1 replica)
|
||||||
|
- `superset-redis-master-*` - Redis cache
|
||||||
|
- `superset-init-db-*` - Database initialization (Completed)
|
||||||
|
|
||||||
|
### OAuth Login Fails with "Invalid parameter: redirect_uri"
|
||||||
|
|
||||||
|
**Error**: Redirect URI uses `http://` instead of `https://`
|
||||||
|
|
||||||
|
**Solution**: Ensure proxy configuration is enabled in `configOverrides`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
ENABLE_PROXY_FIX = True
|
||||||
|
PREFERRED_URL_SCHEME = "https"
|
||||||
|
```
|
||||||
|
|
||||||
|
### OAuth Login Fails with "The request to sign in was denied"
|
||||||
|
|
||||||
|
**Error**: `Missing "jwks_uri" in metadata`
|
||||||
|
|
||||||
|
**Solution**: Ensure `server_metadata_url` is configured in OAuth provider:
|
||||||
|
|
||||||
|
```python
|
||||||
|
"server_metadata_url": f"https://{KEYCLOAK_HOST}/realms/{REALM}/.well-known/openid-configuration"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Connection Test Fails
|
||||||
|
|
||||||
|
#### Trino: "Password not allowed for insecure authentication"
|
||||||
|
|
||||||
|
- Must use external HTTPS hostname (e.g., `trino.example.com`)
|
||||||
|
- Cannot use internal service name (e.g., `trino.trino:8080`)
|
||||||
|
- Trino enforces HTTPS for password authentication
|
||||||
|
|
||||||
|
#### Trino: "error 401: Basic authentication required"
|
||||||
|
|
||||||
|
- Missing username in SQLAlchemy URI
|
||||||
|
- Format: `trino://username:password@host:port/catalog`
|
||||||
|
|
||||||
|
### Database Connection Not Available
|
||||||
|
|
||||||
|
- Only users in `superset-admin` Keycloak group can add databases
|
||||||
|
- Add user to group: `just keycloak::add-user-to-group <user> superset-admin`
|
||||||
|
- Logout and login again to sync roles
|
||||||
|
|
||||||
|
### Worker Pod Crashes
|
||||||
|
|
||||||
|
Check worker logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl logs -n superset deployment/superset-worker
|
||||||
|
```
|
||||||
|
|
||||||
|
Common issues:
|
||||||
|
|
||||||
|
- Redis connection failed (check Redis pod status)
|
||||||
|
- PostgreSQL connection failed (check database credentials)
|
||||||
|
- Missing Python packages (check bootstrapScript execution)
|
||||||
|
|
||||||
|
### Package Installation Issues
|
||||||
|
|
||||||
|
Superset 5.0+ uses `uv` for package management. Check bootstrap logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl logs -n superset deployment/superset -c superset | grep "uv pip install"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected packages:
|
||||||
|
|
||||||
|
- `psycopg2-binary` - PostgreSQL driver
|
||||||
|
- `sqlalchemy-trino` - Trino driver
|
||||||
|
- `authlib` - OAuth library
|
||||||
|
|
||||||
|
### Chart/Dashboard Not Loading
|
||||||
|
|
||||||
|
- Check browser console for errors
|
||||||
|
- Verify database connection is active: Settings → Database Connections
|
||||||
|
- Test query in SQL Lab first
|
||||||
|
- Check Superset logs for errors
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Apache Superset Documentation](https://superset.apache.org/docs/)
|
||||||
|
- [Superset GitHub](https://github.com/apache/superset)
|
||||||
|
- [Superset Helm Chart](https://github.com/apache/superset/tree/master/helm/superset)
|
||||||
|
- [Trino Integration](../trino/README.md)
|
||||||
|
- [Keycloak OAuth](https://www.keycloak.org/docs/latest/securing_apps/#_oidc)
|
||||||
259
superset/justfile
Normal file
259
superset/justfile
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
set fallback := true
|
||||||
|
|
||||||
|
export SUPERSET_NAMESPACE := env("SUPERSET_NAMESPACE", "superset")
|
||||||
|
export SUPERSET_CHART_VERSION := env("SUPERSET_CHART_VERSION", "0.15.0")
|
||||||
|
export SUPERSET_HOST := env("SUPERSET_HOST", "")
|
||||||
|
export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets")
|
||||||
|
export K8S_VAULT_NAMESPACE := env("K8S_VAULT_NAMESPACE", "vault")
|
||||||
|
export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "buunstack")
|
||||||
|
export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "")
|
||||||
|
|
||||||
|
[private]
|
||||||
|
default:
|
||||||
|
@just --list --unsorted --list-submodules
|
||||||
|
|
||||||
|
# Add Helm repository
|
||||||
|
add-helm-repo:
|
||||||
|
helm repo add superset https://apache.github.io/superset
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
# Remove Helm repository
|
||||||
|
remove-helm-repo:
|
||||||
|
helm repo remove superset
|
||||||
|
|
||||||
|
# Create Superset namespace
|
||||||
|
create-namespace:
|
||||||
|
@kubectl get namespace ${SUPERSET_NAMESPACE} &>/dev/null || \
|
||||||
|
kubectl create namespace ${SUPERSET_NAMESPACE}
|
||||||
|
|
||||||
|
# Delete Superset namespace
|
||||||
|
delete-namespace:
|
||||||
|
@kubectl delete namespace ${SUPERSET_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
|
# Create Keycloak client and OAuth secret for Superset
|
||||||
|
create-keycloak-client:
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
while [ -z "${SUPERSET_HOST}" ]; do
|
||||||
|
SUPERSET_HOST=$(
|
||||||
|
gum input --prompt="Superset host (FQDN): " --width=100 \
|
||||||
|
--placeholder="e.g., superset.example.com"
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Creating Keycloak client for Superset..."
|
||||||
|
|
||||||
|
just keycloak::delete-client ${KEYCLOAK_REALM} superset || true
|
||||||
|
|
||||||
|
CLIENT_SECRET=$(just utils::random-password)
|
||||||
|
|
||||||
|
just keycloak::create-group superset-admin '' 'Superset administrators' || echo "Group may already exist"
|
||||||
|
|
||||||
|
just keycloak::create-client \
|
||||||
|
realm=${KEYCLOAK_REALM} \
|
||||||
|
client_id=superset \
|
||||||
|
redirect_url="https://${SUPERSET_HOST}/oauth-authorized/keycloak" \
|
||||||
|
client_secret="${CLIENT_SECRET}"
|
||||||
|
|
||||||
|
just keycloak::add-groups-mapper superset
|
||||||
|
|
||||||
|
kubectl delete secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} --ignore-not-found
|
||||||
|
kubectl create secret generic superset-oauth-temp -n ${SUPERSET_NAMESPACE} \
|
||||||
|
--from-literal=client_secret="${CLIENT_SECRET}"
|
||||||
|
|
||||||
|
echo "Keycloak client created successfully"
|
||||||
|
echo "Client ID: superset"
|
||||||
|
echo "Redirect URI: https://${SUPERSET_HOST}/oauth-authorized/keycloak"
|
||||||
|
echo ""
|
||||||
|
echo "Admin Group: superset-admin"
|
||||||
|
echo "To grant admin access, add users to 'superset-admin' group:"
|
||||||
|
echo " just keycloak::add-user-to-group <username> superset-admin"
|
||||||
|
|
||||||
|
# Delete Keycloak client
|
||||||
|
delete-keycloak-client:
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Deleting Keycloak client for Superset..."
|
||||||
|
just keycloak::delete-client ${KEYCLOAK_REALM} superset || true
|
||||||
|
echo "Deleting superset-admin group..."
|
||||||
|
just keycloak::delete-group superset-admin || true
|
||||||
|
kubectl delete secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
|
# Create Superset secrets
|
||||||
|
create-secrets postgres_password='':
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
secret_key=$(just utils::random-password)
|
||||||
|
|
||||||
|
pg_host="postgres-cluster-rw.postgres"
|
||||||
|
pg_port="5432"
|
||||||
|
pg_user="superset"
|
||||||
|
pg_password="{{ postgres_password }}"
|
||||||
|
pg_database="superset"
|
||||||
|
|
||||||
|
database_url="postgresql://${pg_user}:${pg_password}@${pg_host}:${pg_port}/${pg_database}"
|
||||||
|
|
||||||
|
if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null && \
|
||||||
|
just vault::get superset/oauth client_secret &>/dev/null; then
|
||||||
|
oauth_client_secret=$(just vault::get superset/oauth client_secret)
|
||||||
|
elif kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} &>/dev/null; then
|
||||||
|
oauth_client_secret=$(kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} \
|
||||||
|
-o jsonpath='{.data.client_secret}' | base64 -d)
|
||||||
|
else
|
||||||
|
echo "Error: Cannot retrieve OAuth client secret. Please run 'just superset::create-keycloak-client' first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then
|
||||||
|
echo "External Secrets Operator detected. Storing secrets in Vault..."
|
||||||
|
|
||||||
|
just vault::put superset/config \
|
||||||
|
SECRET_KEY="${secret_key}" \
|
||||||
|
SQLALCHEMY_DATABASE_URI="${database_url}" \
|
||||||
|
OAUTH_CLIENT_SECRET="${oauth_client_secret}"
|
||||||
|
|
||||||
|
kubectl delete secret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found
|
||||||
|
kubectl delete externalsecret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
|
gomplate -f superset-config-external-secret.gomplate.yaml \
|
||||||
|
-o superset-config-external-secret.yaml
|
||||||
|
kubectl apply -f superset-config-external-secret.yaml
|
||||||
|
|
||||||
|
echo "Waiting for ExternalSecret to sync..."
|
||||||
|
kubectl wait --for=condition=Ready externalsecret/superset-secret \
|
||||||
|
-n ${SUPERSET_NAMESPACE} --timeout=60s
|
||||||
|
else
|
||||||
|
echo "External Secrets Operator not found. Creating secret directly..."
|
||||||
|
kubectl delete secret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found
|
||||||
|
kubectl create secret generic superset-secret -n ${SUPERSET_NAMESPACE} \
|
||||||
|
--from-literal=SECRET_KEY="${secret_key}" \
|
||||||
|
--from-literal=SQLALCHEMY_DATABASE_URI="${database_url}" \
|
||||||
|
--from-literal=OAUTH_CLIENT_SECRET="${oauth_client_secret}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete Superset secrets
|
||||||
|
delete-secrets:
|
||||||
|
@kubectl delete secret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found
|
||||||
|
@kubectl delete externalsecret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
|
# Install Superset
|
||||||
|
install:
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
while [ -z "${SUPERSET_HOST}" ]; do
|
||||||
|
SUPERSET_HOST=$(
|
||||||
|
gum input --prompt="Superset host (FQDN): " --width=100 \
|
||||||
|
--placeholder="e.g., superset.example.com"
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
while [ -z "${KEYCLOAK_HOST}" ]; do
|
||||||
|
KEYCLOAK_HOST=$(
|
||||||
|
gum input --prompt="Keycloak host (FQDN): " --width=100 \
|
||||||
|
--placeholder="e.g., auth.example.com"
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
just create-namespace
|
||||||
|
|
||||||
|
# Create Superset database and user
|
||||||
|
POSTGRES_PASSWORD=$(just utils::random-password)
|
||||||
|
just postgres::create-user-and-db superset superset "${POSTGRES_PASSWORD}"
|
||||||
|
|
||||||
|
just create-keycloak-client
|
||||||
|
just create-secrets "${POSTGRES_PASSWORD}"
|
||||||
|
|
||||||
|
if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null && \
|
||||||
|
just vault::get superset/oauth client_secret &>/dev/null; then
|
||||||
|
export OAUTH_CLIENT_SECRET=$(just vault::get superset/oauth client_secret)
|
||||||
|
elif kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} &>/dev/null; then
|
||||||
|
export OAUTH_CLIENT_SECRET=$(kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} \
|
||||||
|
-o jsonpath='{.data.client_secret}' | base64 -d)
|
||||||
|
else
|
||||||
|
echo "Error: Cannot retrieve OAuth client secret. Please run 'just superset::create-keycloak-client' first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export SUPERSET_DB_PASSWORD="${POSTGRES_PASSWORD}"
|
||||||
|
|
||||||
|
just add-helm-repo
|
||||||
|
gomplate -f superset-values.gomplate.yaml -o superset-values.yaml
|
||||||
|
|
||||||
|
helm upgrade --cleanup-on-fail --install superset superset/superset \
|
||||||
|
--version ${SUPERSET_CHART_VERSION} -n ${SUPERSET_NAMESPACE} --wait \
|
||||||
|
-f superset-values.yaml
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Superset installed successfully!"
|
||||||
|
echo "Access URL: https://${SUPERSET_HOST}"
|
||||||
|
echo ""
|
||||||
|
echo "OAuth Configuration:"
|
||||||
|
echo " Provider: Keycloak"
|
||||||
|
echo " Realm: ${KEYCLOAK_REALM}"
|
||||||
|
echo " Authorization URL: https://${KEYCLOAK_HOST}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/auth"
|
||||||
|
echo ""
|
||||||
|
echo "Admin Access:"
|
||||||
|
echo " To grant admin access, add users to 'superset-admin' group:"
|
||||||
|
echo " just keycloak::add-user-to-group <username> superset-admin"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Upgrade Superset
|
||||||
|
upgrade:
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
while [ -z "${SUPERSET_HOST}" ]; do
|
||||||
|
SUPERSET_HOST=$(
|
||||||
|
gum input --prompt="Superset host (FQDN): " --width=100 \
|
||||||
|
--placeholder="e.g., superset.example.com"
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
while [ -z "${KEYCLOAK_HOST}" ]; do
|
||||||
|
KEYCLOAK_HOST=$(
|
||||||
|
gum input --prompt="Keycloak host (FQDN): " --width=100 \
|
||||||
|
--placeholder="e.g., auth.example.com"
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null && \
|
||||||
|
just vault::get superset/oauth client_secret &>/dev/null; then
|
||||||
|
export OAUTH_CLIENT_SECRET=$(just vault::get superset/oauth client_secret)
|
||||||
|
elif kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} &>/dev/null; then
|
||||||
|
export OAUTH_CLIENT_SECRET=$(kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} \
|
||||||
|
-o jsonpath='{.data.client_secret}' | base64 -d)
|
||||||
|
else
|
||||||
|
echo "Error: Cannot retrieve OAuth client secret. Please run 'just superset::create-keycloak-client' first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract database password from SQLALCHEMY_DATABASE_URI in existing secret
|
||||||
|
database_uri=$(kubectl get secret superset-secret -n ${SUPERSET_NAMESPACE} \
|
||||||
|
-o jsonpath='{.data.SQLALCHEMY_DATABASE_URI}' | base64 -d)
|
||||||
|
export SUPERSET_DB_PASSWORD=$(echo "$database_uri" | sed -n 's|.*://[^:]*:\([^@]*\)@.*|\1|p')
|
||||||
|
|
||||||
|
echo "Upgrading Superset..."
|
||||||
|
|
||||||
|
gomplate -f superset-values.gomplate.yaml -o superset-values.yaml
|
||||||
|
helm upgrade superset superset/superset \
|
||||||
|
--version ${SUPERSET_CHART_VERSION} -n ${SUPERSET_NAMESPACE} --wait \
|
||||||
|
-f superset-values.yaml
|
||||||
|
|
||||||
|
echo "Superset upgraded successfully"
|
||||||
|
|
||||||
|
# Uninstall Superset
|
||||||
|
uninstall delete-db='true':
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
helm uninstall superset -n ${SUPERSET_NAMESPACE} --ignore-not-found --wait
|
||||||
|
just delete-secrets
|
||||||
|
just delete-keycloak-client
|
||||||
|
just delete-namespace
|
||||||
|
if [ "{{ delete-db }}" = "true" ]; then
|
||||||
|
just postgres::delete-user-and-db superset superset
|
||||||
|
fi
|
||||||
|
|
||||||
|
if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then
|
||||||
|
just vault::delete superset/config || true
|
||||||
|
just vault::delete superset/oauth || true
|
||||||
|
fi
|
||||||
26
superset/superset-config-external-secret.gomplate.yaml
Normal file
26
superset/superset-config-external-secret.gomplate.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: superset-secret
|
||||||
|
namespace: {{ .Env.SUPERSET_NAMESPACE }}
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault-secret-store
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: superset-secret
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: SECRET_KEY
|
||||||
|
remoteRef:
|
||||||
|
key: superset/config
|
||||||
|
property: SECRET_KEY
|
||||||
|
- secretKey: SQLALCHEMY_DATABASE_URI
|
||||||
|
remoteRef:
|
||||||
|
key: superset/config
|
||||||
|
property: SQLALCHEMY_DATABASE_URI
|
||||||
|
- secretKey: OAUTH_CLIENT_SECRET
|
||||||
|
remoteRef:
|
||||||
|
key: superset/config
|
||||||
|
property: OAUTH_CLIENT_SECRET
|
||||||
168
superset/superset-values.gomplate.yaml
Normal file
168
superset/superset-values.gomplate.yaml
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# Apache Superset Helm values
|
||||||
|
# Generated by gomplate
|
||||||
|
|
||||||
|
# Service configuration
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8088
|
||||||
|
|
||||||
|
# Ingress configuration
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
ingressClassName: traefik
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: traefik
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
hosts:
|
||||||
|
- {{ env.Getenv "SUPERSET_HOST" }}
|
||||||
|
tls:
|
||||||
|
- secretName: superset-tls
|
||||||
|
hosts:
|
||||||
|
- {{ env.Getenv "SUPERSET_HOST" }}
|
||||||
|
|
||||||
|
# Init job settings (disable to use external database initialization)
|
||||||
|
init:
|
||||||
|
enabled: true
|
||||||
|
loadExamples: false
|
||||||
|
|
||||||
|
# Superset node configuration
|
||||||
|
supersetNode:
|
||||||
|
replicaCount: 1
|
||||||
|
connections:
|
||||||
|
# Redis configuration
|
||||||
|
redis_host: superset-redis-headless
|
||||||
|
redis_port: "6379"
|
||||||
|
redis_cache_db: "1"
|
||||||
|
redis_celery_db: "0"
|
||||||
|
# PostgreSQL configuration for initContainer (wait-for-postgres)
|
||||||
|
# The actual database connection uses SQLALCHEMY_DATABASE_URI from extraEnvRaw
|
||||||
|
db_host: postgres-cluster-rw.postgres
|
||||||
|
db_port: "5432"
|
||||||
|
db_user: superset
|
||||||
|
db_pass: {{ env.Getenv "SUPERSET_DB_PASSWORD" }}
|
||||||
|
db_name: superset
|
||||||
|
|
||||||
|
# Superset worker (Celery) configuration
|
||||||
|
supersetWorker:
|
||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
# Database configuration (use existing PostgreSQL)
|
||||||
|
postgresql:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Redis configuration (embedded)
|
||||||
|
redis:
|
||||||
|
enabled: true
|
||||||
|
image:
|
||||||
|
registry: docker.io
|
||||||
|
repository: bitnami/redis
|
||||||
|
# Since August 2025, Bitnami changed its strategy:
|
||||||
|
# - Community users can only use 'latest' tag (no version pinning)
|
||||||
|
# - Versioned tags moved to 'bitnamilegacy' repository (deprecated, no updates)
|
||||||
|
# - For production with version pinning, consider using official redis image separately
|
||||||
|
tag: latest
|
||||||
|
master:
|
||||||
|
persistence:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Extra environment variables
|
||||||
|
extraEnv:
|
||||||
|
KEYCLOAK_HOST: {{ env.Getenv "KEYCLOAK_HOST" }}
|
||||||
|
KEYCLOAK_REALM: {{ env.Getenv "KEYCLOAK_REALM" }}
|
||||||
|
|
||||||
|
# Extra environment variables from existing secrets
|
||||||
|
extraEnvRaw:
|
||||||
|
- name: SUPERSET_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: superset-secret
|
||||||
|
key: SECRET_KEY
|
||||||
|
- name: SQLALCHEMY_DATABASE_URI
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: superset-secret
|
||||||
|
key: SQLALCHEMY_DATABASE_URI
|
||||||
|
- name: OAUTH_CLIENT_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: superset-secret
|
||||||
|
key: OAUTH_CLIENT_SECRET
|
||||||
|
|
||||||
|
# Configuration overrides for superset_config.py
|
||||||
|
configOverrides:
|
||||||
|
keycloak_oauth: |
|
||||||
|
import os
|
||||||
|
from flask_appbuilder.security.manager import AUTH_OAUTH
|
||||||
|
from superset.security import SupersetSecurityManager
|
||||||
|
|
||||||
|
|
||||||
|
class CustomSsoSecurityManager(SupersetSecurityManager):
|
||||||
|
def oauth_user_info(self, provider, response=None):
|
||||||
|
"""Get user information from OAuth provider."""
|
||||||
|
if provider == "keycloak":
|
||||||
|
me = self.appbuilder.sm.oauth_remotes[provider].get(
|
||||||
|
"protocol/openid-connect/userinfo"
|
||||||
|
)
|
||||||
|
data = me.json()
|
||||||
|
return {
|
||||||
|
"username": data.get("preferred_username"),
|
||||||
|
"name": data.get("name"),
|
||||||
|
"email": data.get("email"),
|
||||||
|
"first_name": data.get("given_name", ""),
|
||||||
|
"last_name": data.get("family_name", ""),
|
||||||
|
"role_keys": data.get("groups", []),
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
# Authentication type
|
||||||
|
AUTH_TYPE = AUTH_OAUTH
|
||||||
|
|
||||||
|
# Auto-registration for new users
|
||||||
|
AUTH_USER_REGISTRATION = True
|
||||||
|
AUTH_USER_REGISTRATION_ROLE = "Gamma"
|
||||||
|
|
||||||
|
# Custom security manager
|
||||||
|
CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
|
||||||
|
|
||||||
|
# OAuth configuration
|
||||||
|
OAUTH_PROVIDERS = [
|
||||||
|
{
|
||||||
|
"name": "keycloak",
|
||||||
|
"icon": "fa-key",
|
||||||
|
"token_key": "access_token",
|
||||||
|
"remote_app": {
|
||||||
|
"client_id": "superset",
|
||||||
|
"client_secret": os.environ.get("OAUTH_CLIENT_SECRET"),
|
||||||
|
"server_metadata_url": f"https://{os.environ.get('KEYCLOAK_HOST')}/realms/{os.environ.get('KEYCLOAK_REALM')}/.well-known/openid-configuration",
|
||||||
|
"api_base_url": f"https://{os.environ.get('KEYCLOAK_HOST')}/realms/{os.environ.get('KEYCLOAK_REALM')}/",
|
||||||
|
"client_kwargs": {
|
||||||
|
"scope": "openid email profile"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Role mapping
|
||||||
|
AUTH_ROLES_MAPPING = {
|
||||||
|
"superset-admin": ["Admin"],
|
||||||
|
"Alpha": ["Alpha"],
|
||||||
|
"Gamma": ["Gamma"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sync roles at each login
|
||||||
|
AUTH_ROLES_SYNC_AT_LOGIN = True
|
||||||
|
|
||||||
|
# Enable Trino database support
|
||||||
|
PREVENT_UNSAFE_DB_CONNECTIONS = False
|
||||||
|
|
||||||
|
# Proxy configuration (for HTTPS behind Traefik)
|
||||||
|
ENABLE_PROXY_FIX = True
|
||||||
|
PREFERRED_URL_SCHEME = "https"
|
||||||
|
|
||||||
|
# Bootstrap script for initial setup
|
||||||
|
# Note: Superset 5.0+ uses 'uv' instead of 'pip' for package management
|
||||||
|
bootstrapScript: |
|
||||||
|
#!/bin/bash
|
||||||
|
uv pip install psycopg2-binary sqlalchemy-trino authlib
|
||||||
|
if [ ! -f ~/bootstrap ]; then echo "Bootstrap complete" > ~/bootstrap; fi
|
||||||
66
superset/superset_config.py.template
Normal file
66
superset/superset_config.py.template
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import os
|
||||||
|
from flask_appbuilder.security.manager import AUTH_OAUTH
|
||||||
|
from superset.security import SupersetSecurityManager
|
||||||
|
|
||||||
|
|
||||||
|
class CustomSsoSecurityManager(SupersetSecurityManager):
|
||||||
|
def oauth_user_info(self, provider, response=None):
|
||||||
|
"""Get user information from OAuth provider."""
|
||||||
|
if provider == "keycloak":
|
||||||
|
me = self.appbuilder.sm.oauth_remotes[provider].get(
|
||||||
|
"protocol/openid-connect/userinfo"
|
||||||
|
)
|
||||||
|
data = me.json()
|
||||||
|
return {
|
||||||
|
"username": data.get("preferred_username"),
|
||||||
|
"name": data.get("name"),
|
||||||
|
"email": data.get("email"),
|
||||||
|
"first_name": data.get("given_name", ""),
|
||||||
|
"last_name": data.get("family_name", ""),
|
||||||
|
"role_keys": data.get("groups", []),
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
# Authentication type
|
||||||
|
AUTH_TYPE = AUTH_OAUTH
|
||||||
|
|
||||||
|
# Auto-registration for new users
|
||||||
|
AUTH_USER_REGISTRATION = True
|
||||||
|
AUTH_USER_REGISTRATION_ROLE = "Gamma"
|
||||||
|
|
||||||
|
# Custom security manager
|
||||||
|
CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
|
||||||
|
|
||||||
|
# OAuth configuration
|
||||||
|
OAUTH_PROVIDERS = [
|
||||||
|
{
|
||||||
|
"name": "keycloak",
|
||||||
|
"icon": "fa-key",
|
||||||
|
"token_key": "access_token",
|
||||||
|
"remote_app": {
|
||||||
|
"client_id": "superset",
|
||||||
|
"client_secret": os.environ.get("OAUTH_CLIENT_SECRET"),
|
||||||
|
"api_base_url": "https://{{ env.Getenv "KEYCLOAK_HOST" }}/realms/{{ env.Getenv "KEYCLOAK_REALM" }}/",
|
||||||
|
"client_kwargs": {
|
||||||
|
"scope": "openid email profile"
|
||||||
|
},
|
||||||
|
"access_token_url": "https://{{ env.Getenv "KEYCLOAK_HOST" }}/realms/{{ env.Getenv "KEYCLOAK_REALM" }}/protocol/openid-connect/token",
|
||||||
|
"authorize_url": "https://{{ env.Getenv "KEYCLOAK_HOST" }}/realms/{{ env.Getenv "KEYCLOAK_REALM" }}/protocol/openid-connect/auth",
|
||||||
|
"request_token_url": None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Role mapping
|
||||||
|
AUTH_ROLES_MAPPING = {
|
||||||
|
"superset-admin": ["Admin"],
|
||||||
|
"Alpha": ["Alpha"],
|
||||||
|
"Gamma": ["Gamma"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sync roles at each login
|
||||||
|
AUTH_ROLES_SYNC_AT_LOGIN = True
|
||||||
|
|
||||||
|
# Enable Trino database support
|
||||||
|
PREVENT_UNSAFE_DB_CONNECTIONS = False
|
||||||
Reference in New Issue
Block a user