11 KiB
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 Ouranos infrastructure.
Architecture
┌──────────────┐ ┌───────────────┐ ┌────────────────┐ ┌───────────────┐
│ Browser │────▶│ HAProxy │────▶│ OAuth2-Proxy │────▶│ Your Service │
│ │ │ (titania) │ │ (titania) │ │ (any host) │
└──────────────┘ └───────┬───────┘ └───────┬────────┘ └───────────────┘
│ │
│ ┌───────────────▼───────────────┐
└────▶│ Casdoor │
│ (OIDC Provider - titania) │
└───────────────────────────────┘
How It Works
- User requests
https://service.ouranos.helu.ca/ - HAProxy routes to OAuth2-Proxy (titania:22082)
- OAuth2-Proxy checks for valid session cookie
- No session? → Redirect to Casdoor login → After login, redirect back with cookie
- 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_<service>_oauth2_* ──► <service>_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
- Login to Casdoor at
https://id.ouranos.helu.ca/(Casdoor SSO) - Navigate to Applications → Add
- Configure:
- Name:
<your-service>(e.g.,searxng,jupyter) - Organization:
heluca(or your organization) - Redirect URLs:
https://<service>.ouranos.helu.ca/oauth2/callback - Grant Types:
authorization_code,refresh_token
- Name:
- Save and note the Client ID and Client Secret
Step 2: Add Vault Secrets
ansible-vault edit ansible/inventory/group_vars/all/vault.yml
Add service-specific credentials:
# SearXNG OAuth2 credentials
vault_searxng_oauth2_client_id: "abc123..."
vault_searxng_oauth2_client_secret: "secret..."
vault_searxng_oauth2_cookie_secret: "<generate-with-command-below>"
Generate cookie secret:
openssl rand -base64 32
Step 3: Configure Host Variables
Add to the host that will run OAuth2-Proxy (typically titania.incus.yml):
# =============================================================================
# <Service> OAuth2 Configuration (Service-Specific)
# =============================================================================
<service>_oauth2_client_id: "{{ vault_<service>_oauth2_client_id }}"
<service>_oauth2_client_secret: "{{ vault_<service>_oauth2_client_secret }}"
<service>_oauth2_cookie_secret: "{{ vault_<service>_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: "{{ <service>_oauth2_client_id }}"
oauth2_proxy_client_secret: "{{ <service>_oauth2_client_secret }}"
oauth2_proxy_cookie_secret: "{{ <service>_oauth2_cookie_secret }}"
# Service-specific URLs
oauth2_proxy_redirect_url: "https://<service>.{{ haproxy_domain }}/oauth2/callback"
oauth2_proxy_upstream_url: "http://<service-host>:<service-port>"
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:
haproxy_backends:
- subdomain: "<service>"
backend_host: "titania.incus" # OAuth2-Proxy host
backend_port: 22082 # OAuth2-Proxy port
health_path: "/ping" # OAuth2-Proxy health endpoint
Step 5: Deploy
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
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)
# 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
- 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:
- Create a new Casdoor application for Jupyter
- Add vault variables:
vault_jupyter_oauth2_client_id: "..." vault_jupyter_oauth2_client_secret: "..." vault_jupyter_oauth2_cookie_secret: "..." - 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 processedoauth2_proxy_errors_total- Total errorsoauth2_proxy_upstream_latency_seconds- Latency to upstream service
Configuration in ansible/alloy/titania/config.alloy.j2:
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:
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:
ansible-playbook alloy/deploy.yml --limit titania.incus
Security Considerations
-
Cookie Security:
cookie_secure = true- HTTPS onlycookie_httponly = true- No JavaScript accesscookie_samesite = "lax"- CSRF protection
-
Access Control:
- Use
oauth2_proxy_email_domainsto restrict by email domain - Use
oauth2_proxy_allowed_groupsto restrict by Casdoor groups
- Use
-
SSL Verification:
- Set
oauth2_proxy_skip_ssl_verify: falsein production - Ensure Casdoor has valid SSL certificates
- Set
Troubleshooting
Check OAuth2-Proxy Logs
ssh titania.incus
docker logs oauth2-proxy
Test OIDC Discovery
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 - Specific implementation details
- Casdoor Documentation - Identity provider configuration