# OAuth2-Proxy Authentication Gateway # Red Panda Approved ## Overview OAuth2-Proxy provides authentication for services that don't natively support SSO/OIDC. It acts as a reverse proxy that requires users to authenticate via Casdoor before accessing the upstream service. This document describes the generic approach for adding OAuth2-Proxy authentication to any service in the Agathos infrastructure. ## Architecture ``` ┌──────────────┐ ┌───────────────┐ ┌────────────────┐ ┌───────────────┐ │ Browser │────▶│ HAProxy │────▶│ OAuth2-Proxy │────▶│ Your Service │ │ │ │ (titania) │ │ (titania) │ │ (any host) │ └──────────────┘ └───────┬───────┘ └───────┬────────┘ └───────────────┘ │ │ │ ┌───────────────▼───────────────┐ └────▶│ Casdoor │ │ (OIDC Provider - titania) │ └───────────────────────────────┘ ``` ## How It Works 1. User requests `https://service.ouranos.helu.ca/` 2. HAProxy routes to OAuth2-Proxy (titania:22082) 3. OAuth2-Proxy checks for valid session cookie 4. **No session?** → Redirect to Casdoor login → After login, redirect back with cookie 5. **Valid session?** → Forward request to upstream service ## File Structure ``` ansible/oauth2_proxy/ ├── deploy.yml # Main deployment playbook ├── docker-compose.yml.j2 # Docker Compose template ├── oauth2-proxy.cfg.j2 # OAuth2-Proxy configuration └── stage.yml # Validation/staging playbook ``` Monitoring configuration is integrated into the host-specific Alloy config: - `ansible/alloy/titania/config.alloy.j2` - Contains OAuth2-Proxy log collection and metrics scraping ## Variable Architecture The OAuth2-Proxy template uses **generic variables** (`oauth2_proxy_*`) that are mapped from **service-specific variables** in host_vars: ``` Vault (service-specific) Host Vars (mapping) Template (generic) ──────────────────────── ─────────────────── ────────────────── vault__oauth2_* ──► _oauth2_* ──► oauth2_proxy_* ``` This allows: - Multiple services to use the same OAuth2-Proxy template - Service-specific credentials in vault - Clear naming conventions ## Configuration Steps ### Step 1: Create Casdoor Application 1. Login to Casdoor at `https://id.ouranos.helu.ca/` (Casdoor SSO) 2. Navigate to **Applications** → **Add** 3. Configure: - **Name**: `` (e.g., `searxng`, `jupyter`) - **Organization**: `heluca` (or your organization) - **Redirect URLs**: `https://.ouranos.helu.ca/oauth2/callback` - **Grant Types**: `authorization_code`, `refresh_token` 4. Save and note the **Client ID** and **Client Secret** ### Step 2: Add Vault Secrets ```bash ansible-vault edit ansible/inventory/group_vars/all/vault.yml ``` Add service-specific credentials: ```yaml # SearXNG OAuth2 credentials vault_searxng_oauth2_client_id: "abc123..." vault_searxng_oauth2_client_secret: "secret..." vault_searxng_oauth2_cookie_secret: "" ``` Generate cookie secret: ```bash openssl rand -base64 32 ``` ### Step 3: Configure Host Variables Add to the host that will run OAuth2-Proxy (typically `titania.incus.yml`): ```yaml # ============================================================================= # OAuth2 Configuration (Service-Specific) # ============================================================================= _oauth2_client_id: "{{ vault__oauth2_client_id }}" _oauth2_client_secret: "{{ vault__oauth2_client_secret }}" _oauth2_cookie_secret: "{{ vault__oauth2_cookie_secret }}" # ============================================================================= # OAuth2-Proxy Configuration (Generic Template Variables) # ============================================================================= oauth2_proxy_user: oauth2proxy oauth2_proxy_group: oauth2proxy oauth2_proxy_uid: 802 oauth2_proxy_gid: 802 oauth2_proxy_directory: /srv/oauth2-proxy oauth2_proxy_port: 22082 # OIDC Configuration oauth2_proxy_oidc_issuer_url: "http://titania.incus:{{ casdoor_port }}" # Map service-specific credentials to generic template variables oauth2_proxy_client_id: "{{ _oauth2_client_id }}" oauth2_proxy_client_secret: "{{ _oauth2_client_secret }}" oauth2_proxy_cookie_secret: "{{ _oauth2_cookie_secret }}" # Service-specific URLs oauth2_proxy_redirect_url: "https://.{{ haproxy_domain }}/oauth2/callback" oauth2_proxy_upstream_url: "http://:" oauth2_proxy_cookie_domain: "{{ haproxy_domain }}" # Access Control oauth2_proxy_email_domains: - "*" # Or restrict to specific domains # Session Configuration oauth2_proxy_cookie_expire: "168h" oauth2_proxy_cookie_refresh: "1h" # SSL Verification oauth2_proxy_skip_ssl_verify: true # Set false for production ``` ### Step 4: Update HAProxy Backend Change the service backend to route through OAuth2-Proxy: ```yaml haproxy_backends: - subdomain: "" backend_host: "titania.incus" # OAuth2-Proxy host backend_port: 22082 # OAuth2-Proxy port health_path: "/ping" # OAuth2-Proxy health endpoint ``` ### Step 5: Deploy ```bash cd ansible # Validate configuration ansible-playbook oauth2_proxy/stage.yml # Deploy OAuth2-Proxy ansible-playbook oauth2_proxy/deploy.yml # Update HAProxy routing ansible-playbook haproxy/deploy.yml ``` ## Complete Example: SearXNG ### Vault Variables ```yaml vault_searxng_oauth2_client_id: "searxng-client-id-from-casdoor" vault_searxng_oauth2_client_secret: "searxng-client-secret-from-casdoor" vault_searxng_oauth2_cookie_secret: "ABCdef123..." ``` ### Host Variables (titania.incus.yml) ```yaml # SearXNG OAuth2 (service-specific) 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 }}" # OAuth2-Proxy (generic mapping) oauth2_proxy_client_id: "{{ searxng_oauth2_client_id }}" oauth2_proxy_client_secret: "{{ searxng_oauth2_client_secret }}" oauth2_proxy_cookie_secret: "{{ searxng_oauth2_cookie_secret }}" oauth2_proxy_redirect_url: "https://searxng.{{ haproxy_domain }}/oauth2/callback" oauth2_proxy_upstream_url: "http://oberon.incus:25599" ``` ### HAProxy Backend ```yaml - subdomain: "searxng" backend_host: "titania.incus" backend_port: 22082 health_path: "/ping" ``` ## Adding a Second Service (e.g., Jupyter) When adding authentication to another service, you would: 1. Create a new Casdoor application for Jupyter 2. Add vault variables: ```yaml vault_jupyter_oauth2_client_id: "..." vault_jupyter_oauth2_client_secret: "..." vault_jupyter_oauth2_cookie_secret: "..." ``` 3. Either: - **Option A**: Deploy a second OAuth2-Proxy instance on a different port - **Option B**: Configure the same OAuth2-Proxy with multiple upstreams (more complex) For multiple services, **Option A** is recommended for isolation and simplicity. ## Monitoring OAuth2-Proxy monitoring is handled by Grafana Alloy, which runs on each host. ### Architecture ``` OAuth2-Proxy ─────► Grafana Alloy ─────► Prometheus (prospero) (titania) (local agent) (remote_write) │ └─────────────► Loki (prospero) (log forwarding) ``` ### Metrics (via Prometheus) Alloy scrapes OAuth2-Proxy metrics at `/metrics` and forwards them to Prometheus: - `oauth2_proxy_requests_total` - Total requests processed - `oauth2_proxy_errors_total` - Total errors - `oauth2_proxy_upstream_latency_seconds` - Latency to upstream service Configuration in `ansible/alloy/titania/config.alloy.j2`: ```alloy prometheus.scrape "oauth2_proxy" { targets = [{"__address__" = "127.0.0.1:{{oauth2_proxy_port}}"}] scrape_interval = "30s" forward_to = [prometheus.remote_write.default.receiver] job_name = "oauth2-proxy" } ``` ### Logs (via Loki) OAuth2-Proxy logs are collected via syslog and forwarded to Loki: ```alloy loki.source.syslog "oauth2_proxy_logs" { listener { address = "127.0.0.1:{{oauth2_proxy_syslog_port}}" protocol = "tcp" labels = { job = "oauth2-proxy", hostname = "{{inventory_hostname}}" } } forward_to = [loki.write.default.receiver] } ``` ### Deploy Alloy After Changes If you update the Alloy configuration: ```bash ansible-playbook alloy/deploy.yml --limit titania.incus ``` ## Security Considerations 1. **Cookie Security**: - `cookie_secure = true` - HTTPS only - `cookie_httponly = true` - No JavaScript access - `cookie_samesite = "lax"` - CSRF protection 2. **Access Control**: - Use `oauth2_proxy_email_domains` to restrict by email domain - Use `oauth2_proxy_allowed_groups` to restrict by Casdoor groups 3. **SSL Verification**: - Set `oauth2_proxy_skip_ssl_verify: false` in production - Ensure Casdoor has valid SSL certificates ## Troubleshooting ### Check OAuth2-Proxy Logs ```bash ssh titania.incus docker logs oauth2-proxy ``` ### Test OIDC Discovery ```bash curl http://titania.incus:22081/.well-known/openid-configuration ``` ### Verify Cookie Domain Ensure `oauth2_proxy_cookie_domain` matches your HAProxy domain. ### Common Issues | Issue | Cause | Solution | |-------|-------|----------| | Redirect loop | Cookie domain mismatch | Check `oauth2_proxy_cookie_domain` | | 403 Forbidden | Email domain not allowed | Update `oauth2_proxy_email_domains` | | OIDC discovery failed | Casdoor not accessible | Check network/firewall | | Invalid redirect URI | Mismatch in Casdoor app | Verify redirect URL in Casdoor | ## Related Documentation - [SearXNG Authentication](services/searxng-auth.md) - Specific implementation details - [Casdoor Documentation](casdoor.md) - Identity provider configuration