# SearXNG Authentication Design Document # Red Panda Approved ## Overview This document describes the design for adding Casdoor-based authentication to SearXNG, which doesn't natively support SSO/OIDC authentication. ## Architecture ``` ┌──────────────┐ ┌───────────────┐ ┌─────────────────────────────────────┐ │ Browser │────▶│ HAProxy │────▶│ Oberon │ │ │ │ (titania) │ │ ┌────────────────┐ ┌───────────┐ │ └──────────────┘ └───────┬───────┘ │ │ OAuth2-Proxy │─▶│ SearXNG │ │ │ │ │ (port 22073) │ │ (22083) │ │ │ │ └───────┬────────┘ └───────────┘ │ │ └──────────┼─────────────────────────┘ │ │ OIDC │ ┌──────────────────▼────────────────┐ └────▶│ Casdoor │ │ (OIDC Provider - titania) │ └───────────────────────────────────┘ ``` The OAuth2-Proxy runs as a **native binary sidecar** on Oberon alongside SearXNG, following the same pattern used for JupyterLab on Puck. The upstream connection is `localhost` — eliminating the cross-host hop from the previous Docker-based deployment on Titania. > ℹ️ Each host supports at most one OAuth2-Proxy sidecar instance. The binary is > shared at `/usr/local/bin/oauth2-proxy`; each service gets a unique config directory > and systemd unit name. ## Components ### 1. OAuth2-Proxy (Sidecar on Oberon) - **Purpose**: Acts as authentication gateway for SearXNG - **Port**: 22073 (exposed to HAProxy) - **Binary**: Native `oauth2-proxy` v7.6.0 (systemd service `oauth2-proxy-searxng`) - **Config**: `/etc/oauth2-proxy-searxng/oauth2-proxy.cfg` - **Upstream**: `http://127.0.0.1:22083` (localhost sidecar to SearXNG) - **Logging**: systemd journal (`SyslogIdentifier=oauth2-proxy-searxng`) ### 2. Casdoor (Existing on Titania) - **Purpose**: OIDC Identity Provider - **Port**: 22081 - **URL**: https://id.ouranos.helu.ca/ (via HAProxy) - **Required Setup**: - Create Application for SearXNG - Configure redirect URI - Generate client credentials ### 3. HAProxy Updates (Titania) - Route `searxng.ouranos.helu.ca` to OAuth2-Proxy on Oberon (`oberon.incus:22073`) - OAuth2-Proxy handles authentication before proxying to SearXNG on localhost ### 4. SearXNG (Existing on Oberon) - **No changes required** - remains unaware of authentication - Receives pre-authenticated requests from OAuth2-Proxy ## Authentication Flow 1. User navigates to `https://searxng.ouranos.helu.ca/` 2. HAProxy routes to OAuth2-Proxy on oberon:22073 3. OAuth2-Proxy checks for valid session cookie (`_oauth2_proxy_searxng`) 4. **If no valid session**: - Redirect to Casdoor login: `https://id.ouranos.helu.ca/login/oauth/authorize` - User authenticates with Casdoor (username/password, social login, etc.) - Casdoor redirects back with authorization code - OAuth2-Proxy exchanges code for tokens - OAuth2-Proxy sets session cookie 5. **If valid session**: - OAuth2-Proxy adds `X-Forwarded-User` header - Request proxied to SearXNG at `127.0.0.1:22083` (localhost sidecar) ## Casdoor Configuration ### Application Setup (Manual via Casdoor UI) 1. Login to Casdoor at https://id.ouranos.helu.ca/ 2. Navigate to Applications → Add 3. Configure: - **Name**: `searxng` - **Display Name**: `SearXNG Search` - **Organization**: `built-in` (or your organization) - **Redirect URLs**: - `https://searxng.ouranos.helu.ca/oauth2/callback` - **Grant Types**: `authorization_code`, `refresh_token` - **Response Types**: `code` 4. Save and note the `Client ID` and `Client Secret` ### Cookie Secret Generation Generate a 32-byte random secret for OAuth2-Proxy cookies: ```bash openssl rand -base64 32 ``` ## Environment Variables ### Development (Sandbox) ```yaml # In inventory/host_vars/oberon.incus.yml searxng_oauth2_proxy_dir: /etc/oauth2-proxy-searxng searxng_oauth2_proxy_version: "7.6.0" searxng_proxy_port: 22073 searxng_domain: "ouranos.helu.ca" searxng_oauth2_oidc_issuer_url: "https://id.ouranos.helu.ca" searxng_oauth2_redirect_url: "https://searxng.ouranos.helu.ca/oauth2/callback" # OAuth2 Credentials (from vault) searxng_oauth2_client_id: "{{ vault_searxng_oauth2_client_id }}" searxng_oauth2_client_secret: "{{ vault_searxng_oauth2_client_secret }}" searxng_oauth2_cookie_secret: "{{ vault_searxng_oauth2_cookie_secret }}" ``` > ℹ️ Variables use the `searxng_` prefix, following the same naming pattern as > `jupyterlab_oauth2_*` variables on Puck. The upstream URL (`http://127.0.0.1:22083`) > is derived from `searxng_port` in the config template — no cross-host URL needed. ## Deployment Steps ### 1. Add Vault Secrets ```bash ansible-vault edit inventory/group_vars/all/vault.yml ``` Add: ```yaml vault_searxng_oauth2_client_id: "" vault_searxng_oauth2_client_secret: "" vault_searxng_oauth2_cookie_secret: "" ``` Note: The `searxng_` prefix allows service-specific credentials. The Oberon host_vars maps these directly to `searxng_oauth2_*` variables used by the sidecar config template. ### 2. Update Host Variables OAuth2-Proxy variables are defined in `inventory/host_vars/oberon.incus.yml` alongside the existing SearXNG configuration. No separate service entry is needed — the OAuth2-Proxy sidecar is deployed as part of the `searxng` service. ```yaml # SearXNG OAuth2-Proxy Sidecar (in oberon.incus.yml) searxng_oauth2_proxy_dir: /etc/oauth2-proxy-searxng searxng_oauth2_proxy_version: "7.6.0" searxng_proxy_port: 22073 searxng_domain: "ouranos.helu.ca" searxng_oauth2_oidc_issuer_url: "https://id.ouranos.helu.ca" searxng_oauth2_redirect_url: "https://searxng.ouranos.helu.ca/oauth2/callback" ``` ### 3. Update HAProxy Backend Route SearXNG traffic through OAuth2-Proxy on Oberon: ```yaml # In inventory/host_vars/titania.incus.yml haproxy_backends: - subdomain: "searxng" backend_host: "oberon.incus" # Same host as SearXNG backend_port: 22073 # OAuth2-Proxy port health_path: "/ping" # OAuth2-Proxy health endpoint ``` ### 4. Deploy ```bash cd ansible # Deploy SearXNG + OAuth2-Proxy sidecar ansible-playbook searxng/deploy.yml # Update HAProxy configuration ansible-playbook haproxy/deploy.yml ``` ## Monitoring ### Logs OAuth2-Proxy logs to systemd journal on Oberon. Alloy's default `systemd_logs` source captures these logs automatically, filterable by `SyslogIdentifier=oauth2-proxy-searxng`. ```bash # View logs on Oberon ssh oberon.incus journalctl -u oauth2-proxy-searxng -f ``` ### Metrics OAuth2-Proxy exposes Prometheus metrics at `/metrics` on port 22073: - `oauth2_proxy_requests_total` - Total requests - `oauth2_proxy_errors_total` - Error count - `oauth2_proxy_upstream_latency_seconds` - Upstream latency ## Security Considerations 1. **Cookie Security**: - `cookie_secure = true` enforces HTTPS-only cookies - `cookie_httponly = true` prevents JavaScript access - `cookie_samesite = "lax"` provides CSRF protection 2. **Email Domain Restriction**: - Configure `oauth2_proxy_email_domains` to limit who can access - Example: `["yourdomain.com"]` or `["*"]` for any 3. **Group-Based Access**: - Optional: Configure `oauth2_proxy_allowed_groups` in Casdoor - Only users in specified groups can access SearXNG ## Troubleshooting ### Check OAuth2-Proxy Status ```bash ssh oberon.incus systemctl status oauth2-proxy-searxng journalctl -u oauth2-proxy-searxng --no-pager -n 50 ``` ### Test OIDC Discovery ```bash curl https://id.ouranos.helu.ca/.well-known/openid-configuration ``` ### Test Health Endpoint ```bash curl http://oberon.incus:22073/ping ``` ### Verify Cookie Domain Ensure the cookie domain (`.ouranos.helu.ca`) matches your HAProxy domain. Cookies won't work across different domains. ## Files | File | Purpose | |------|---------| | `ansible/searxng/deploy.yml` | SearXNG + OAuth2-Proxy sidecar deployment | | `ansible/searxng/oauth2-proxy-searxng.cfg.j2` | OAuth2-Proxy OIDC configuration | | `ansible/searxng/oauth2-proxy-searxng.service.j2` | Systemd unit for OAuth2-Proxy | | `ansible/inventory/host_vars/oberon.incus.yml` | Host variables (`searxng_oauth2_*`) | | `docs/searxng-auth.md` | This design document | ### Generic OAuth2-Proxy Module (Retained) The standalone `ansible/oauth2_proxy/` directory is retained as a generic, reusable Docker-based OAuth2-Proxy module for future services: | File | Purpose | |------|---------| | `ansible/oauth2_proxy/deploy.yml` | Generic Docker Compose deployment | | `ansible/oauth2_proxy/docker-compose.yml.j2` | Docker Compose template | | `ansible/oauth2_proxy/oauth2-proxy.cfg.j2` | Generic OIDC configuration template | | `ansible/oauth2_proxy/stage.yml` | Validation / dry-run playbook |