Replaces the minimal project description with a comprehensive README including a component overview table, quick start instructions, common Ansible operations, and links to detailed documentation. Aligns with Red Panda Approval™ standards.
9.5 KiB
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-proxyv7.6.0 (systemd serviceoauth2-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.cato 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
- User navigates to
https://searxng.ouranos.helu.ca/ - HAProxy routes to OAuth2-Proxy on oberon:22073
- OAuth2-Proxy checks for valid session cookie (
_oauth2_proxy_searxng) - 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
- Redirect to Casdoor login:
- If valid session:
- OAuth2-Proxy adds
X-Forwarded-Userheader - Request proxied to SearXNG at
127.0.0.1:22083(localhost sidecar)
- OAuth2-Proxy adds
Casdoor Configuration
Application Setup (Manual via Casdoor UI)
- Login to Casdoor at https://id.ouranos.helu.ca/
- Navigate to Applications → Add
- 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
- Name:
- Save and note the
Client IDandClient Secret
Cookie Secret Generation
Generate a 32-byte random secret for OAuth2-Proxy cookies:
openssl rand -base64 32
Environment Variables
Development (Sandbox)
# 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 asjupyterlab_oauth2_*variables on Puck. The upstream URL (http://127.0.0.1:22083) is derived fromsearxng_portin the config template — no cross-host URL needed.
Deployment Steps
1. Add Vault Secrets
ansible-vault edit inventory/group_vars/all/vault.yml
Add:
vault_searxng_oauth2_client_id: "<from-casdoor>"
vault_searxng_oauth2_client_secret: "<from-casdoor>"
vault_searxng_oauth2_cookie_secret: "<generated-32-byte-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.
# 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:
# 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
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.
# 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 requestsoauth2_proxy_errors_total- Error countoauth2_proxy_upstream_latency_seconds- Upstream latency
Security Considerations
-
Cookie Security:
cookie_secure = trueenforces HTTPS-only cookiescookie_httponly = trueprevents JavaScript accesscookie_samesite = "lax"provides CSRF protection
-
Email Domain Restriction:
- Configure
oauth2_proxy_email_domainsto limit who can access - Example:
["yourdomain.com"]or["*"]for any
- Configure
-
Group-Based Access:
- Optional: Configure
oauth2_proxy_allowed_groupsin Casdoor - Only users in specified groups can access SearXNG
- Optional: Configure
Troubleshooting
Check OAuth2-Proxy Status
ssh oberon.incus
systemctl status oauth2-proxy-searxng
journalctl -u oauth2-proxy-searxng --no-pager -n 50
Test OIDC Discovery
curl https://id.ouranos.helu.ca/.well-known/openid-configuration
Test Health Endpoint
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 |