314 lines
11 KiB
Markdown
314 lines
11 KiB
Markdown
# 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
|
|
|
|
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_<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
|
|
|
|
1. Login to Casdoor at `https://id.ouranos.helu.ca/` (Casdoor SSO)
|
|
2. Navigate to **Applications** → **Add**
|
|
3. 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`
|
|
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-with-command-below>"
|
|
```
|
|
|
|
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
|
|
# =============================================================================
|
|
# <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:
|
|
|
|
```yaml
|
|
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
|
|
|
|
```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 |