docs: rewrite README with structured overview and quick start guide

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.
This commit is contained in:
2026-03-03 12:49:06 +00:00
parent c7be03a743
commit b4d60f2f38
219 changed files with 34586 additions and 2 deletions

314
docs/oauth2_proxy.md Normal file
View File

@@ -0,0 +1,314 @@
# 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_<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