Initial commit: cleaned project structure

- Consolidated documentation from Ralph Loop iterations
- Archived 20+ outdated/superseded files to .archive/
- Kept essential docs: OIDC integration, mobile setup, quick start
- Added operational scripts for health monitoring and backup
- Research artifacts preserved in .tasks/artifacts/

Current state:
- 3 VPS sites (fry, proton, photon) ONLINE in Pangolin
- brn-home site pending for local services (Jellyfin, etc.)
- Mobile access configuration pending

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 06:15:04 +00:00
commit b428721b07
17 changed files with 5749 additions and 0 deletions

View File

@@ -0,0 +1,525 @@
# Architecture Validation: Authentik + Pangolin + Guacamole
**Validation Date:** 2026-01-20
**Purpose:** Review proposed SSO infrastructure architecture for multi-site deployment
---
## Executive Summary
**VERDICT:****APPROVED WITH CRITICAL MODIFICATIONS**
The proposed architecture (Authentik + Pangolin + Guacamole) is sound for your use case with **one critical exception**: the Guacamole/RDP integration has fundamental limitations that require architectural workarounds.
### Key Findings
| Component | Status | Confidence | Notes |
|-----------|--------|------------|-------|
| **Authentik** | ✅ RECOMMENDED | High | Best choice for self-hosted SSO in 2026 |
| **Pangolin** | ✅ RECOMMENDED | High | Superior to Cloudflare Tunnel for self-hosted |
| **Guacamole + OIDC** | ⚠️ APPROVED WITH CAVEATS | Medium | RDP NLA incompatibility requires workarounds |
---
## 1. Authentik Validation
### Research Findings
**Market Position (2026):**
- Authentik has emerged as the **leading modern SSO solution** for self-hosted environments
- Superior to Keycloak for small/medium deployments (lower complexity, better UX)
- Superior to Authelia (full IdP vs just forward auth)
- MIT licensed, active development, 19.6k GitHub stars
**Key Strengths:**
- **Modern architecture:** Written in Python (Django), not Java like Keycloak
- **Lower resource requirements:** Documented to run well with 2GB RAM total
- **Better UX:** Admin interface significantly easier than Keycloak
- **Full protocol support:** OIDC, OAuth2, SAML2, LDAP, RADIUS
- **Native MFA:** TOTP, WebAuthn, Duo, all built-in
- **Expression policies:** Powerful Python-based policy engine
**For Your Use Case:**
- ✅ Single-user deployment supported (minimal resource config documented)
- ✅ Service account support for API tokens (Jellyfin mobile apps)
- ✅ MFA enforcement per-application (can require for Guacamole only)
- ✅ Proven integration with Guacamole, Jellyfin SSO plugin, OpenWebUI
- ✅ Active documentation for Pangolin integration
**Alternatives Considered:**
- **Keycloak:** Overkill for single-user, 4GB+ RAM, steeper learning curve
- **Authelia:** Limited to forward auth, no full OIDC provider capabilities
- **Zitadel:** Newer, less proven integrations
**RECOMMENDATION:****Use Authentik as proposed**
---
## 2. Pangolin Validation
### Research Findings
**Market Position (2026):**
- Pangolin is the **leading self-hosted alternative to Cloudflare Tunnel**
- Open-source (fosrl/pangolin, 18.2k GitHub stars)
- Built on proven tech: WireGuard + Traefik reverse proxy
- Active community, recently featured in major tech channels (Christian Lempa, NetworkChuck)
**Key Strengths:**
- **Self-hosted control plane:** You own all infrastructure, no third-party dependencies
- **Identity-aware access control:** Native OIDC integration with Authentik
- **Dual mode:** Tunneled reverse proxy + VPN-style private resource access
- **No inbound ports required:** WireGuard outbound tunnels from private networks
- **Automatic SSL:** Let's Encrypt integration via Traefik
- **Mobile support:** Native apps + WireGuard config export
**Architecture Components:**
1. **Pangolin (Control Plane):** Dashboard, API, WebSocket server, auth system
2. **Gerbil (Tunnel Manager):** WireGuard interface management
3. **Newt (Edge Client):** Runs on private networks (brn, VPS hosts)
4. **Traefik (Reverse Proxy):** TLS termination, routing, load balancing
5. **Badger (Auth Middleware):** OIDC authentication enforcement
**For Your Use Case:**
-**Replaces WireGuard mesh:** Current 10.51.0.0/24 network becomes Pangolin sites
-**Centralized on brn:** Control plane on physically secure host
-**VPS integration:** Newt clients on fry, proton, photon for site-to-site routing
-**Mobile access:** Apps for pixel9pro, pixel6pro
-**Granular ACLs:** Per-service, per-user access control via Authentik
**Comparison to Alternatives:**
| Solution | Ownership | Cost | Mobile | OIDC | Complexity |
|----------|-----------|------|--------|------|------------|
| **Pangolin** | Self-hosted | Free | ✅ | ✅ | Medium |
| Cloudflare Tunnel | Cloudflare | Free | ⚠️ Limited | ✅ | Low |
| Tailscale | Tailscale | $5/user | ✅ | ⚠️ Enterprise | Low |
| Headscale | Self-hosted | Free | ✅ | ❌ | Medium |
**Critical Findings:**
-**OIDC redirect URI:** `https://tunnel.obr.sh/api/v1/auth/callback`
-**Required scopes:** openid, profile, email, groups
-**Site architecture:** Each location (brn LAN, fry, proton) becomes a "Site"
-**Resource types:** Public (HTTPS with domains) + Private (TCP/UDP for VPN access)
**RECOMMENDATION:****Use Pangolin as proposed**
---
## 3. Guacamole Validation
### Research Findings
**Market Position (2026):**
- Apache Guacamole remains the **leading open-source clientless RDP gateway**
- No viable open-source alternatives with equivalent feature set
- Active Apache project, version 1.6.0 current
**OIDC Support:**
- ✅ Native OIDC extension available
- ✅ Documented Authentik integration guide
- ✅ Works well for **authentication to Guacamole dashboard**
### ⚠️ CRITICAL LIMITATION DISCOVERED
**RDP NLA + OIDC Incompatibility:**
The research uncovered a **fundamental architectural limitation**:
**Problem:**
1. **RDP Network Level Authentication (NLA)** requires username/password for NTLM/Kerberos authentication
2. **OIDC authentication** never provides the user's password to Guacamole
3. Variables available: `${GUAC_USERNAME}` ✅, `${GUAC_PASSWORD}`
4. **Result:** Cannot use NLA with OIDC authentication
**Security Implications:**
- **NLA is recommended security best practice** for RDP (encrypts credentials before RDP connection)
- **Disabling NLA** exposes credentials during connection handshake
- **Windows 11** (argon) defaults to requiring NLA
**Workarounds Available:**
| Option | Security | User Experience | Implementation |
|--------|----------|-----------------|----------------|
| **1. Disable NLA** | ⚠️ Lower | Seamless SSO | Easy - disable in Guacamole connection config |
| **2. Prompt for credentials** | ✅ High | Double login | Medium - configure in Guacamole |
| **3. Service account** | ⚠️ Medium | Seamless SSO | Easy - hardcode credentials, lose audit trail |
| **4. Use CAS instead of OIDC** | ✅ High | Seamless SSO | Hard - requires ClearPass Receiver on Windows |
### Recommended Approach for Your Deployment
**Since this is single-user (you) accessing your own workstation (argon):**
**RECOMMENDED:** **Option 1 - Disable NLA**
**Rationale:**
- Low risk: You're the only user, accessing your own machine
- Network already secured: Guacamole only accessible via Pangolin tunnel + Authentik SSO + MFA
- User experience: Best (seamless SSO with TOTP)
- Defense in depth: Multiple layers (MFA on Authentik, network isolation via Pangolin)
**Implementation:**
```yaml
# In Guacamole connection config for argon-rdp:
security: rdp # Use standard RDP security instead of NLA
ignore-cert: true # Accept self-signed certs
```
**Additional Security Mitigations:**
1. ✅ Enforce MFA on Guacamole application in Authentik (TOTP required)
2. ✅ Restrict Guacamole to Pangolin tunnel only (no public WAN access)
3. ✅ Enable Guacamole session recording for audit trail
4. ✅ Configure Windows Firewall on argon to only allow RDP from brn (10.50.0.74)
**Alternative for Future Multi-User:**
If you later add users, switch to **Option 2 (prompt for credentials)** to maintain per-user accountability.
**RECOMMENDATION:****Use Guacamole with NLA disabled, compensated by MFA + Pangolin isolation**
---
## 4. Service Integration Validation
### Jellyfin SSO
**Status:****FULLY SUPPORTED**
**Plugin:** SSO-Auth plugin from Jellyfin catalog
**Key Findings:**
- ✅ Authentik integration well-documented
- ⚠️ **Critical:** Mobile apps (Android/iOS) have limited OIDC support
-**Solution:** Use "Quick Connect" feature for mobile (6-digit code pairing)
- ✅ Alternative: API tokens for dedicated devices
**Configuration:**
- Provider type: Generic OpenID
- Client auth: `client_secret_post` (NOT `client_secret_basic`)
- Claims: roles via `groups` claim
- Scopes: openid, profile, email, groups
**Mobile App Strategy:**
1. **Primary:** Quick Connect (user logs in via web SSO, enters code in app)
2. **Secondary:** API tokens per device (generated in Jellyfin dashboard)
---
### OpenWebUI SSO
**Status:****FULLY SUPPORTED**
**Native OIDC:** No plugin required
**Key Findings:**
- ✅ Robust OIDC implementation since v0.7.1+
-**Role-based admin designation** via `OAUTH_ADMIN_ROLES`
- ✅ JIT group provisioning with `ENABLE_OAUTH_GROUP_CREATION`
- ✅ Automatic role synchronization on every login
**Configuration Variables:**
```bash
OPENID_PROVIDER_URL=https://sso.obr.sh/application/o/openwebui/.well-known/openid-configuration
OAUTH_CLIENT_ID=<from_authentik>
OAUTH_CLIENT_SECRET=<from_authentik>
ENABLE_OAUTH_ROLE_MANAGEMENT=true
OAUTH_ROLES_CLAIM=groups
OAUTH_ADMIN_ROLES=openwebui-admins
```
**Redirect URI:** `https://ll.obr.sh/oauth/oidc/callback`
---
### Gitea SSO (fry + proton)
**Status:****FULLY SUPPORTED**
**Native OIDC:** Built-in authentication source
**Configuration:**
- Type: OAuth2
- Provider: OpenID Connect
- Auto Discovery URL: `https://sso.obr.sh/application/o/gitea/.well-known/openid-configuration`
- Admin role mapping: Via Authentik groups
**Note:** Gitea instances remain **publicly accessible** (federated nature), SSO is optional login method
---
### Transmission
**Status:** ⚠️ **NO SSO SUPPORT**
**Current:** HTTP Basic Authentication
**Recommendation:**
- Keep existing basic auth
- Protect behind Pangolin tunnel only (no public WAN access)
- Consider forward auth middleware via Traefik if SSO required
---
### Mastodon (bern.social)
**Status:****NO CHANGES NEEDED**
**Reason:** Public federated service, should remain publicly accessible
**Recommendation:** Do not integrate with SSO, keep existing authentication
---
## 5. Architectural Risks & Mitigations
### Risk Matrix
| Risk | Severity | Probability | Mitigation |
|------|----------|-------------|------------|
| **Authentik failure = total auth outage** | High | Low | Backup recovery codes, PostgreSQL backups, consider HA |
| **Pangolin control plane failure** | Medium | Low | Services still accessible via LAN, failover to WireGuard |
| **RDP NLA disabled security concern** | Medium | Medium | Compensate with MFA + network isolation |
| **Mobile app SSO limitations (Jellyfin)** | Low | High | Use Quick Connect, document for users |
| **DNS failure (sso.obr.sh unreachable)** | High | Low | Local /etc/hosts entries as backup |
### Single Points of Failure
**Authentik (sso.obr.sh):**
- **Impact:** All SSO authentication fails
- **Mitigation:**
- Regular PostgreSQL backups (`pg_dump`)
- Store recovery codes offline
- Document emergency admin access procedure
- Consider Docker volume backups
**Pangolin (tunnel.obr.sh):**
- **Impact:** Mobile/remote access fails, VPS sites unreachable
- **Mitigation:**
- Services still accessible from LAN (Traefik routes remain)
- Keep existing WireGuard as emergency fallback
- Document manual WireGuard reconnection procedure
**brn Host (10.50.0.74):**
- **Impact:** Total control plane failure (Authentik, Pangolin, Guacamole)
- **Mitigation:**
- Physical host security (already planned)
- UPS for power stability
- Backup restore procedure documented
- Consider VM snapshots before changes
### Backup Strategy
**Critical Data:**
1. **Authentik PostgreSQL database** - `pg_dump` daily, keep 7 days
2. **Authentik media files** - `/srv/docker/authentik/media/`
3. **Pangolin configuration** - `/srv/docker/pangolin/` database and config
4. **Guacamole PostgreSQL database** - connection definitions
5. **Traefik dynamic config** - `/srv/docker/traefik/traefik_dynamic.yaml`
**Backup Script:** See `/home/olaf/pangolin/.tasks/artifacts/backup-strategy.md` (TODO: create)
---
## 6. Alternative Architectures Considered
### Alternative A: Keycloak instead of Authentik
**Pros:**
- More mature (13 years vs 6 years)
- Enterprise-grade features
- Larger community (32k stars vs 19k)
**Cons:**
- Higher resource requirements (4GB+ RAM)
- Steeper learning curve
- Overkill for single-user deployment
- Java-based (vs Python for Authentik)
**Verdict:** ❌ Rejected - unnecessary complexity for use case
---
### Alternative B: Cloudflare Tunnel instead of Pangolin
**Pros:**
- Lower operational burden (managed service)
- Global edge network
- Built-in DDoS protection
- Simpler setup
**Cons:**
- Third-party dependency (Cloudflare controls routing)
- Limited customization
- No VPN-style private resource access
- Privacy concerns (traffic visibility)
**Verdict:** ❌ Rejected - plan specifies self-hosted control
---
### Alternative C: Tailscale instead of Pangolin
**Pros:**
- Easier setup
- Better mobile apps
- NAT traversal superior (DERP relays)
**Cons:**
- Pricing: $5/user/month after 3 devices
- Control plane dependency on Tailscale servers
- Limited reverse proxy features
- No identity-aware access control without ACL tags
**Verdict:** ❌ Rejected - cost and third-party dependency
---
### Alternative D: No RDP Gateway (Direct RDP)
**Pros:**
- Simpler architecture
- No NLA compatibility issues
**Cons:**
- Requires RDP client installation on devices
- No web-based access (can't use from Chromebook, iPad browser)
- No session recording capability
- Less secure (direct exposure vs gateway)
**Verdict:** ❌ Rejected - Guacamole provides superior UX and security
---
## 7. Final Recommendations
### ✅ APPROVED Architecture
**Core Components:**
1. **Authentik** at `sso.obr.sh` - SSO/IdP
2. **Pangolin** at `tunnel.obr.sh` - Tunneled reverse proxy
3. **Guacamole** at `remote.obr.sh` - RDP gateway (NLA disabled)
### 🔧 Required Modifications to Original Plan
1. **Guacamole RDP Connection:**
- Change from "NLA security" to "Standard RDP security"
- Enable session recording for audit trail
- Configure Windows Firewall on argon to only allow brn
2. **Authentik MFA Policy:**
- Create separate policy for Guacamole application (TOTP required)
- Optional for other services (Jellyfin, OpenWebUI) based on preference
3. **Jellyfin Mobile Strategy:**
- Document Quick Connect procedure for mobile apps
- Create API tokens for persistent devices (TV apps)
4. **Transmission:**
- Keep HTTP basic auth (no OIDC support)
- Access via Pangolin tunnel only
### 📋 Implementation Order Validation
The plan's phased approach is sound:
**Phase 1: Authentik**
- Foundation for all SSO
**Phase 2: Pangolin**
- Requires Authentik for OIDC
**Phase 3: Guacamole**
- Requires Authentik for OIDC
**Phase 4: Service Integration**
- Requires Authentik + Pangolin operational
**Phase 5: Traefik Restriction**
- Only after Pangolin sites verified working
**Phase 6: Mobile Setup**
- Final verification step
**Order is correct:** Sequential dependencies respected
### 🎯 Success Criteria
**Deployment successful when:**
1. ✅ Can login to Authentik admin via `sso.obr.sh`
2. ✅ Can login to Pangolin dashboard via `tunnel.obr.sh` (SSO redirect)
3. ✅ Can access Guacamole via `remote.obr.sh` (SSO + MFA)
4. ✅ Can connect to argon RDP via Guacamole web interface
5. ✅ Can access Jellyfin via Pangolin mobile app (with Quick Connect)
6. ✅ Can access OpenWebUI via Pangolin tunnel (SSO login)
7. ✅ Jellyfin/OpenWebUI/Transmission return 404 from public WAN
8. ✅ VPS hosts (fry, proton) show connected in Pangolin dashboard
### ⚠️ Rollback Plan
**Critical checkpoints:**
1. After TASK-005 (Authentik deploy): Services still work without SSO
2. After TASK-009 (Pangolin sites): Traefik routes still public
3. **After TASK-024 (Traefik restriction): CRITICAL CHECKPOINT**
**Rollback procedure:**
```bash
# Emergency: restore public access
sudo cp /home/olaf/pangolin/.tasks/artifacts/traefik_dynamic.yaml.backup \
/srv/docker/traefik/traefik_dynamic.yaml
docker exec traefik kill -SIGHUP 1 # Reload Traefik config
```
### 📊 Resource Requirements
**brn Host (10.50.0.74) Additional Load:**
- Authentik: +2GB RAM, +2 CPU cores
- Pangolin: +1GB RAM, +1 CPU core
- Guacamole: +1GB RAM, +1 CPU core
- **Total:** +4GB RAM, +4 CPU cores
**Current brn specs needed:** Minimum 8GB RAM, 4-6 CPU cores recommended
### 🔒 Security Posture
**Improvements:**
- ✅ Centralized authentication (single MFA enrollment)
- ✅ Granular per-service access control
- ✅ Session recording for RDP access
- ✅ Network segmentation via Pangolin tunnels
- ✅ Elimination of password sprawl
**Trade-offs:**
- ⚠️ RDP NLA disabled (compensated by MFA + network isolation)
- ⚠️ Single point of failure (brn host)
**Overall:****Net security improvement**
---
## 8. Conclusion
**FINAL VERDICT:****ARCHITECTURE APPROVED FOR IMPLEMENTATION**
**The proposed Authentik + Pangolin + Guacamole architecture is sound and recommended with the following conditions:**
1. Acknowledge RDP NLA limitation and implement compensating controls
2. Follow phased implementation order as specified
3. Create backup strategy before starting (TASK-027)
4. Test thoroughly at each phase before proceeding
5. Document emergency rollback procedures
**Confidence Level:** **85%**
**Remaining 15% risk factors:**
- Pangolin relatively new in production (1 year track record)
- Guacamole NLA workaround requires security discipline
- Single-user deployment lacks redundancy
**Recommendation to proceed:****YES**
**Next step:** Execute research-informed implementation starting with TASK-003 (Create Authentik Compose) using insights from RESEARCH-002 and TASK-001 outputs.
---
**Validation completed by:** Claude Code
**Date:** 2026-01-20
**Research artifacts referenced:** RESEARCH-001 through RESEARCH-005, TASK-001

View File

@@ -0,0 +1,399 @@
# Authentik Best Practices for Single-User/Single-Admin Deployments
**Research Date:** 2026-01-20
**Task:** RESEARCH-002
**Purpose:** Comprehensive research on Authentik deployment strategies for minimal single-user/admin environments
---
## 1. Minimal Resource Configuration
### Host Requirements
- **Minimum Specification:** 2 CPU cores and 2 GB RAM (official recommendation)
- **Source:** [Authentik Docker Compose Installation Documentation](https://docs.goauthentik.io/install-config/install/docker-compose/)
### Container-Level Resource Limits
Based on real-world deployment optimization (from [TEK Online case study](https://www.tekonline.com.au/diagnosing-and-fixing-high-cpu-usage-in-authentik-stack/)), recommended resource limits for single-user deployments:
#### PostgreSQL Database
```yaml
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
```
#### Redis Cache
```yaml
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.2'
memory: 256M
```
#### Authentik Server
```yaml
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
```
#### Authentik Worker
```yaml
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
```
### Kubernetes Deployments
For Kubernetes-based deployments, configure resource limits via JSON patches:
```yaml
kubernetes_json_patches:
deployment:
- op: add
path: "/spec/template/spec/containers/0/resources"
value:
requests:
cpu: 2000m
memory: 2000Mi
limits:
cpu: 2000m
memory: 2000Mi
```
**Source:** [Authentik Outpost Configuration](https://version-2024-2.goauthentik.io/docs/outposts/)
### Minimal Viable Configuration Notes
- **VPS Testing:** One user reported running Authentik on a free tier with 1 shared vCPU and 256MB shared memory, but this proved insufficient and caused OOM kills
- **Recommended Minimum:** 512MB dedicated memory per component (Postgres and Authentik server) to avoid stability issues
- **Source:** [Self-hosting Chronicles Blog](https://notes.catdad.science/2023/03/02/self-hosting-chronicles-plex-oidc.html)
---
## 2. Backup Strategies
### Official Backup Components
Authentik backups consist of **two critical components**:
#### 2.1 PostgreSQL Database Backup
**Critical Importance:** The database stores ALL persistent data including:
- User accounts and credentials
- Policies and configurations
- Application settings
- Flow definitions
- Provider configurations
**Backup Methods:**
- Use PostgreSQL native tools:
- `pg_dump` for single database dumps
- `pg_dumpall` for complete cluster dumps
- Continuous archiving for point-in-time recovery
**Best Practice Example (from Kubernetes upgrade guide):**
```bash
# Connect to PostgreSQL pod
kubectl exec -it authentik-postgresql-0 -- /bin/bash
# Create dump in pod's data directory
pg_dump -U authentik_user -d authentik_db > /bitnami/postgresql/authentik_backup.sql
# CRITICAL: Copy dump outside the pod
kubectl cp authentik-postgresql-0:/bitnami/postgresql/authentik_backup.sql ./authentik_backup_$(date +%Y%m%d).sql
```
**Important Exclusions:**
- Exclude system databases: `template0` and `template1`
**Source:** [Authentik Backup and Restore Documentation](https://docs.goauthentik.io/docs/sys-mgmt/ops/backup-restore)
#### 2.2 Static Directory Backups
**Required Directories:**
1. **`/media` Directory**
- Contains: Application icons, flow backgrounds, uploaded files
- **Only required if NOT using S3 external storage**
- If using S3: Use AWS S3 Sync utility for backups instead
2. **`/certs` Directory**
- Contains: TLS certificates
- Required for SSL/TLS functionality
**Docker Volume Persistence:**
- PostgreSQL data: `/var/lib/postgresql/data`
- Media files: `/media`
- Certificates: `/certs`
**Source:** [Authentik Architecture Documentation](https://docs.goauthentik.io/core/architecture/)
### Backup Automation Strategy
**Recommended Approach (from community discussions):**
1. Automate PostgreSQL dumps using cron or systemd timers
2. Back up Docker volumes containing media/certs
3. Store backups in off-site location (follow 3-2-1 rule)
4. Test restore procedures regularly
**Pre-Upgrade Requirement:**
- **ALWAYS** backup PostgreSQL database before upgrades
- Reason: Authentik does not support downgrades
- **Source:** [Authentik Upgrade Documentation](https://docs.goauthentik.io/docs/install-config/upgrade)
### Configuration Storage
**S3 External Storage (Optional):**
```yaml
AUTHENTIK_STORAGE__MEDIA__BACKEND=s3
AUTHENTIK_STORAGE__MEDIA__S3__BUCKET_NAME=your-bucket
```
**Benefits for Single-User Deployments:**
- Reduces local storage requirements
- Simplifies media file backups
- Enables cross-region redundancy
**Source:** [Authentik Configuration Documentation](https://docs.goauthentik.io/install-config/configuration/)
---
## 3. API Token Management for Service Accounts
### Service Account Overview
**Purpose:** Machine-to-machine authentication and automation
- Ideal for: Scripts, CI/CD pipelines, infrastructure-as-code (Terraform)
- Authentication method: HTTP Basic Authentication with token
**Source:** [Authentik Service Accounts Documentation](https://docs.goauthentik.io/sys-mgmt/service-accounts/)
### Token Creation Methods
#### Method 1: Bootstrap Token (Automated Install)
Set during initial deployment for the default `akadmin` user:
```yaml
# Environment variable
AUTHENTIK_BOOTSTRAP_TOKEN=your-secure-token-here
```
**Kubernetes/Helm Example:**
```yaml
bootstrap_token: test
# Or reference from secret:
secretRef: authentik-bootstrap-secret
```
**Benefits:**
- Enables fully automated deployments
- No manual UI interaction required
- Perfect for infrastructure-as-code workflows
**Source:** [Authentik Automated Install](https://docs.goauthentik.io/install-config/automated-install/)
#### Method 2: Manual Token Creation via Admin UI
1. Navigate to **Directory** > **Tokens and App passwords**
2. Create new token with **Intent: API Token**
3. Default lifespan: 30 minutes (configurable)
**Token Properties:**
- Can be configured as expiring or non-expiring
- Supports auto-rotation
- User-specific permissions apply
#### Method 3: Programmatic Token Generation
**Current Limitation (as of 2024):**
- GitHub Issue [#7707](https://github.com/goauthentik/authentik/issues/7707) requests environment variable support for initial API token generation
- GitHub Issue [#12882](https://github.com/goauthentik/authentik/issues/12882) discusses programmatic bearer token creation post-bootstrap
**Workaround for Automation:**
- Use `AUTHENTIK_BOOTSTRAP_TOKEN` for initial setup
- Subsequent tokens can be created via API using the bootstrap token
### Machine-to-Machine (M2M) Authentication
**OAuth 2.0 Client Credentials Grant:**
- Uses `grant_type=client_credentials`
- Authentication via service account username + app password token
- Returns signed JWT access token
**Automatic Service Account Creation:**
- Pass configured `client_secret` during token request
- Authentik auto-generates service account
- Naming scheme: `<provider-name>-service-account`
**Example Token Request:**
```bash
curl -X POST https://authentik.example.com/application/o/token/ \
-d "grant_type=client_credentials" \
-d "client_id=your-client-id" \
-d "username=service-account-name" \
-d "password=app-password-token"
```
**Source:** [Machine-to-Machine Authentication](https://docs.goauthentik.io/add-secure-apps/providers/oauth2/machine_to_machine)
### Best Practices for Single-Admin Deployments
1. **Bootstrap Token:** Set `AUTHENTIK_BOOTSTRAP_TOKEN` during initial deployment
2. **Service Accounts:** Create dedicated service accounts for each automation task
3. **Token Rotation:** Enable auto-rotation for long-lived tokens
4. **Least Privilege:** Assign minimal required permissions to service accounts
5. **Audit Logging:** Monitor API token usage via Authentik's built-in logging
---
## 4. MFA Enforcement Policies
### Single-Admin MFA Strategy
For single-user/single-admin deployments, MFA enforcement can be achieved through **Expression Policies** bound to authenticator validation stages.
### Expression Policy Implementation
**Concept:** Use Python-based expression policies to conditionally enforce MFA for specific users.
**Example Policy (User-Specific MFA):**
```python
# Enforce MFA only for admin user
if context['pending_user'].username == "admin":
return True # Enforce MFA stage
return False # Skip MFA stage for other users
```
**Policy Binding:**
1. Create authenticator validation stage in authentication flow
2. Create expression policy with above code
3. Bind policy to the MFA stage binding
4. Result: Only "admin" user sees MFA prompt
**Source:** [Expression Policies Documentation](https://docs.goauthentik.io/customize/policies/expression/)
### User/Group Binding Approach
**Alternative Method:** Bind users directly to stage bindings
**Process:**
1. Create MFA prompt stage in authentication flow
2. Bind stage to flow
3. In stage binding settings, specify which users/groups must complete the stage
4. Result: Only bound users see the MFA prompt
**Advantages:**
- No code required
- Simpler for single-admin scenarios
- GUI-based configuration
**Source:** [Stages Documentation](https://docs.goauthentik.io/add-secure-apps/flows-stages/stages)
### Advanced Expression Policy Functions
**Available Helper Functions:**
```python
# Check group membership
ak_is_group_member(user, group_name="admins")
# Check if user has authenticator enrolled
ak_user_has_authenticator(user, device_type="totp")
# Get user by username
ak_user_by(username="admin")
```
**Context Variables:**
- `context['pending_user']` - User attempting authentication
- `context['request']` - HTTP request object
- `context['flow_plan']` - Current flow execution plan
### Recommended MFA Policy for Single-Admin
**Strict Admin-Only MFA:**
```python
# Enforce MFA for superuser/admin accounts
if context['pending_user'].is_superuser:
return True
return False
```
**Or by Username:**
```python
# Enforce MFA for specific admin username
admin_usernames = ["olaf", "admin", "root"]
if context['pending_user'].username in admin_usernames:
return True
return False
```
**Best Practice:** Always enforce MFA for accounts with administrative privileges, even in single-user deployments, to protect against credential compromise.
---
## 5. Additional Considerations for Single-User Deployments
### Security Hardening
- Enable MFA for admin account (see section 4)
- Use strong, unique passwords (consider password manager)
- Regularly update Authentik (with pre-upgrade backups)
- Enable audit logging and review periodically
### Monitoring and Maintenance
- Set up health check endpoints monitoring
- Configure email notifications for critical events
- Schedule regular backup verifications (test restore)
- Monitor resource usage (CPU/memory) for optimization
### Disaster Recovery
- Document restore procedures
- Store backup credentials securely (separate from production)
- Test full recovery process at least quarterly
- Maintain off-site backup copies (3-2-1 rule)
### Network Security
- Use reverse proxy (Traefik, nginx) with TLS termination
- Implement rate limiting for authentication endpoints
- Consider IP allowlisting for admin interface
- Use internal Docker networks for database/Redis
---
## Summary
For single-user/single-admin Authentik deployments:
1. **Resources:** Minimum 2 CPU cores, 2 GB RAM host; set container limits (1GB memory, 1 CPU per service)
2. **Backups:** Automate PostgreSQL dumps + backup /media and /certs volumes; test restores regularly
3. **API Tokens:** Use `AUTHENTIK_BOOTSTRAP_TOKEN` for automation; create service accounts for scripts
4. **MFA:** Implement expression policies or stage bindings to enforce MFA for admin account only
These practices ensure a secure, maintainable, and resource-efficient Authentik deployment suitable for personal/small-scale use cases.
---
## References
- [Authentik Docker Compose Installation](https://docs.goauthentik.io/install-config/install/docker-compose/)
- [Authentik Backup and Restore](https://docs.goauthentik.io/docs/sys-mgmt/ops/backup-restore)
- [Authentik Service Accounts](https://docs.goauthentik.io/sys-mgmt/service-accounts/)
- [Authentik Expression Policies](https://docs.goauthentik.io/customize/policies/expression/)
- [Authentik Automated Install](https://docs.goauthentik.io/install-config/automated-install/)
- [TEK Online: Diagnosing High CPU Usage](https://www.tekonline.com.au/diagnosing-and-fixing-high-cpu-usage-in-authentik-stack/)

View File

@@ -0,0 +1,444 @@
# Apache Guacamole OIDC Integration with Authentik - Research Report
**Research Task:** RESEARCH-003
**Date:** 2026-01-20
**Objective:** Comprehensive research on Apache Guacamole OIDC integration with Authentik
---
## 1. Complete List of OPENID_ Environment Variables
### Required Environment Variables
For Docker installations of Apache Guacamole, the following environment variables are **required**:
| Variable | Description | Example Value |
|----------|-------------|---------------|
| `OPENID_AUTHORIZATION_ENDPOINT` | Authorization endpoint URI from IdP | `https://sso.obr.sh/application/o/authorize/` |
| `OPENID_JWKS_ENDPOINT` | JWKS endpoint for validating ID tokens (JWTs) | `https://sso.obr.sh/application/o/<slug>/jwks/` |
| `OPENID_ISSUER` | Expected issuer for all received ID tokens | `https://sso.obr.sh/application/o/<slug>/` |
| `OPENID_CLIENT_ID` | OpenID client ID for your application | From Authentik provider configuration |
| `OPENID_REDIRECT_URI` | Full callback URL for Guacamole | `https://remote.obr.sh/guacamole/api/auth/openid-connect/callback` |
### Optional Environment Variables
| Variable | Description | Default Value |
|----------|-------------|---------------|
| `OPENID_ENABLED` | Explicitly enable the extension | `true` (or set any OPENID_ variable) |
| `OPENID_USERNAME_CLAIM_TYPE` | JWT claim for username | `email` |
| `OPENID_GROUPS_CLAIM_TYPE` | JWT claim for groups | `groups` |
| `OPENID_ATTRIBUTES_CLAIM_TYPE` | Comma-separated list of claims to expose as `OIDC_*` attributes | (empty) |
| `OPENID_SCOPE` | OpenID scopes to request | `openid email profile` |
| `OPENID_ALLOWED_CLOCK_SKEW` | Clock skew tolerance for token validation | `30` (seconds) |
| `OPENID_MAX_TOKEN_VALIDITY` | Maximum token validity period | `300` (minutes/5 hours) |
| `OPENID_MAX_NONCE_VALIDITY` | Maximum nonce validity period | `10` (minutes) |
### Extension Loading Control
| Variable | Description | Effect |
|----------|-------------|--------|
| `EXTENSION_PRIORITY` | Controls authentication flow | `openid` = auto-redirect to IdP |
| | | `*,openid` = show login screen first |
### Additional Configuration Requirement
For standalone installations (non-Docker), you must enable environment property evaluation:
```properties
# In /etc/guacamole/guacamole.properties
enable-environment-properties: true
```
---
## 2. Connection-Level Authentication Flow
### User Authentication vs. Connection Authentication
**Critical Finding:** OIDC in Guacamole handles **user authentication** to the Guacamole web interface, **NOT connection-level authentication** to RDP/VNC/SSH hosts.
### How It Works
1. **User logs into Guacamole** via OIDC (SSO to Authentik)
2. **Guacamole creates a session** for the authenticated user
3. **Connections are accessed** based on permissions stored in the database extension (JDBC)
4. **Target system authentication** happens separately using configured credentials
### The Credential Passthrough Problem
#### With LDAP Authentication (Works)
```
User → LDAP login → Guacamole stores credentials → RDP connection
${GUAC_USERNAME} and ${GUAC_PASSWORD} available
```
#### With OIDC Authentication (Does NOT Work)
```
User → OIDC login → Guacamole gets token only → RDP connection
${GUAC_PASSWORD} is NOT available (password never sent to Guacamole)
```
### Parameter Tokens Available
| Token | LDAP Auth | OIDC Auth | Notes |
|-------|-----------|-----------|-------|
| `${GUAC_USERNAME}` | ✓ Available | ✓ Available | Username from claim |
| `${GUAC_PASSWORD}` | ✓ Available | ✗ NOT Available | Password never leaves IdP |
### OIDC Attribute Passthrough
The `OPENID_ATTRIBUTES_CLAIM_TYPE` variable allows exposing JWT claims as `OIDC_*` parameters:
```bash
# Example: Expose custom claims
OPENID_ATTRIBUTES_CLAIM_TYPE=workstationName,department,employeeId
```
This creates connection parameters:
- `${OIDC_workstationName}`
- `${OIDC_department}`
- `${OIDC_employeeId}`
**Use Case:** Dynamic connection routing based on user attributes, NOT credential passthrough.
---
## 3. MFA Flow with TOTP Passthrough
### Guacamole's TOTP Support
Apache Guacamole supports **TOTP (Time-based One-Time Password)** as a second authentication factor.
### TOTP Authentication Flow
```
┌─────────────────────────────────────────────────────────────┐
│ 1. User attempts to log in │
│ ↓ │
│ 2. Primary authentication (OIDC/LDAP/Database) │
│ ↓ │
│ 3. If successful, TOTP extension checks for enrolled device │
│ ├─ Device enrolled: Prompt for TOTP code │
│ └─ No device: Force enrollment (QR code scan) │
│ ↓ │
│ 4. User enters 6-digit code from authenticator app │
│ ↓ │
│ 5. If code validates: Grant access │
│ └─ If code fails: Deny access │
└─────────────────────────────────────────────────────────────┘
```
### TOTP Extension Configuration
**Extension File:** `guacamole-auth-totp-*.jar`
**Environment Variables (Docker):**
| Variable | Description | Default |
|----------|-------------|---------|
| `TOTP_ISSUER` | Issuer name shown in authenticator app | `Apache Guacamole` |
| `TOTP_DIGITS` | Number of digits in TOTP code | `6` |
| `TOTP_PERIOD` | Time period for code validity | `30` (seconds) |
| `TOTP_MODE` | TOTP generation mode | `sha1` |
### Layered Authentication: OIDC + TOTP
**Prerequisites:**
- Database authentication extension (JDBC) installed for key storage
- OIDC extension for primary authentication
- TOTP extension for second factor
**Flow:**
1. User → Authentik (OIDC with MFA at IdP level) → Guacamole login
2. Guacamole → TOTP challenge (separate from Authentik MFA)
3. User → Enters TOTP code → Access granted
### Important Limitation: Duo vs. TOTP with SSO
**From official documentation:**
> "Guacamole's Duo support cannot currently be used alongside single sign-on (SSO). If you need both MFA and SSO, you must use your SSO provider's own Duo integration or use TOTP instead of Duo."
### MFA Architecture Options
#### Option A: MFA at IdP Level (Authentik)
```
User → Authentik (with TOTP/WebAuthn) → Guacamole
Pros: Single MFA enforcement point, better UX
Cons: No Guacamole-level MFA control
```
#### Option B: MFA at Guacamole Level
```
User → Authentik (no MFA) → Guacamole TOTP → Access
Pros: Guacamole controls second factor
Cons: Users authenticate twice (SSO + TOTP)
```
#### Option C: Dual MFA (Most Secure)
```
User → Authentik (with MFA) → Guacamole TOTP → Access
Pros: Defense in depth, two independent factors
Cons: Potential user friction
```
---
## 4. RDP NLA Compatibility with SSO
### What is RDP NLA?
**Network Level Authentication (NLA)** is a security feature in RDP that requires authentication **before** establishing a full remote desktop session.
### The Fundamental Incompatibility
**Critical Finding:** OIDC SSO and RDP NLA have a credential passthrough problem.
#### Why It Doesn't Work
```
┌──────────────────────────────────────────────────────────┐
│ OIDC Flow (No Password to Guacamole) │
├──────────────────────────────────────────────────────────┤
│ User → Authentik → JWT Token → Guacamole │
│ │
│ RDP NLA Requirement (Needs Actual Password) │
│ │
│ Guacamole → Windows Server (NLA) → ??? No password ??? │
└──────────────────────────────────────────────────────────┘
```
**The Problem:**
- OIDC authentication means the user's password **never reaches Guacamole**
- RDP NLA requires a username/password for NTLM or Kerberos authentication
- `${GUAC_PASSWORD}` token is **empty** when using OIDC
### Confirmed by Community Evidence
**From mailing list discussions:**
> "When using OpenID Connect (or SAML, or CAS without the ClearPass extension) for Guacamole login, the user's password is not shared between the Identity Provider (IdP) and Guacamole, meaning the `${GUAC_PASSWORD}` variable will not be available for use in RDP connections."
### Current Status of FreeRDP NLA Support
Guacamole uses **FreeRDP** for RDP connections. Key limitations:
1. **NTLM-only NLA:** FreeRDP currently only supports NTLM variant of NLA
2. **No Kerberos NLA:** Kerberos NLA support is in development
3. **Protected Users Group:** Users in AD "Protected Users" group cannot authenticate via NTLM NLA
### Workarounds and Solutions
#### Solution 1: Disable NLA on Target Systems
```
Pros: Simple, works immediately
Cons: Reduced security, Microsoft discourages this
```
#### Solution 2: Credential Prompting
```
Leave RDP username/password blank → User prompted during connection
Pros: Works with OIDC
Cons: Users log in twice (SSO + RDP prompt)
```
#### Solution 3: Pre-configured Shared Accounts
```
Store RDP credentials in Guacamole connection config
Pros: Single sign-on UX maintained
Cons: Shared accounts, loses per-user audit trail
```
#### Solution 4: Use LDAP Instead of OIDC (Not Recommended)
```
LDAP auth → ${GUAC_PASSWORD} available → RDP NLA works
Pros: Credential passthrough works
Cons: Loses SSO benefits, security regression
```
#### Solution 5: Custom Authentication Extension
**Community Solution:** `guacamole-auth-header-password`
```bash
# Example: Pass credentials via HTTP headers
http-username-header=OIDC_REMOTE_USER
http-password-header=OIDC_access_token # Not a real password
http-groups-header=OIDC_CLAIM_sdDesktopProjects
```
**Note:** This doesn't solve the password problem but shows extension architecture.
#### Solution 6: CAS with ClearPass (Alternative SSO)
```
Apache Guacamole 0.9.14+ includes CAS credential pass-through
using "ClearPass" which allows ${GUAC_USERNAME} and ${GUAC_PASSWORD}
tokens with SSO
```
**Requires:** CAS server instead of OIDC
### Azure AD / Entra Joined Systems
**Additional Complexity:**
- Azure AD Joined (Entra-joined) systems may require web-based authentication
- Guacamole cannot pass through the web sign-in mechanism
- **Hybrid joined** (Azure AD + Domain joined) systems work better
**From community reports:**
> "Hybrid joined systems with NLA enabled are working fine with Guacamole/RDP"
### Recommended Approach for SSO + RDP NLA
**Best Practice Architecture:**
1. **Use OIDC for Guacamole authentication** (SSO to Authentik)
2. **Configure RDP connections** with one of:
- **Option A:** Dedicated service accounts (stored in connection config)
- **Option B:** Prompt users for RDP credentials (UX compromise)
- **Option C:** Disable NLA on internal trusted network segments only
3. **Implement connection-level access control** via Authentik groups
4. **Enable audit logging** to track connection access (even with shared accounts)
---
## 5. Authentik-Specific Configuration Notes
### Authentik Provider Setup
**Provider Type:** OAuth2/OpenID Connect
**Client Type:** Confidential
**Scopes:** `openid`, `email`, `profile`
### Critical Authentik Settings
1. **Redirect URI:**
```
https://remote.obr.sh/guacamole/api/auth/oidc/callback
```
2. **Token Validity:**
```
Max 300 minutes (5 hours) - Guacamole limitation
```
3. **Signing Key:**
```
authentik Self-signed Certificate
```
### Username Claim Configuration
**Two Options:**
#### Option A: Use `sub` (Recommended)
```bash
OPENID_USERNAME_CLAIM_TYPE=sub
```
- Pros: Immutable, unique identifier
- Cons: Not human-readable (UUIDs)
- **Important:** Disable "Allow users to change username" in Authentik
#### Option B: Use `preferred_username`
```bash
OPENID_USERNAME_CLAIM_TYPE=preferred_username
```
- Pros: Human-readable usernames
- Cons: May change if user updates profile
- **Requires:** Custom property mapping in Authentik
### Custom Property Mapping for Usernames
**In Authentik:** Create Scope Mapping to expose custom username attribute
```python
# Example: Map custom field to preferred_username claim
{
"preferred_username": user.attributes.get("guacamole_username", user.username)
}
```
### Pre-creating Admin User
**Best Practice:** Create admin user in Guacamole database **before** enabling OIDC
```sql
-- Insert user matching Authentik username
INSERT INTO guacamole_entity (name, type)
VALUES ('admin@example.com', 'USER');
-- Grant admin permissions
INSERT INTO guacamole_user_permission (entity_id, permission)
VALUES ((SELECT entity_id FROM guacamole_entity WHERE name='admin@example.com'), 'ADMINISTER');
```
---
## 6. Key Takeaways and Recommendations
### Confirmed Capabilities
✓ OIDC authentication to Guacamole web interface works well with Authentik
✓ Username passthrough (`${GUAC_USERNAME}`) available with OIDC
✓ Custom JWT claims can be exposed as `${OIDC_*}` parameters
✓ TOTP MFA can be layered on top of OIDC authentication
✓ Groups from Authentik can control connection access
### Critical Limitations
✗ Password passthrough (`${GUAC_PASSWORD}`) does NOT work with OIDC
✗ RDP NLA requires passwords, creating incompatibility with OIDC SSO
✗ No per-connection SSO (OIDC only authenticates to Guacamole, not target systems)
✗ Duo MFA cannot be used with SSO (use TOTP instead)
### Recommended Architecture
**For Pangolin/Authentik/Guacamole Integration:**
1. **Primary Authentication:** OIDC to Authentik (SSO)
2. **MFA Strategy:** MFA at Authentik level (TOTP/WebAuthn)
3. **RDP Authentication:**
- Service accounts for automated access
- User prompting for per-user audit requirements
- NLA disabled on trusted internal segments (if acceptable)
4. **Database Extension:** Required for connection storage and TOTP keys
5. **Group Mapping:** Use `OPENID_GROUPS_CLAIM_TYPE=groups` to control access
### Environment Variable Template for Pangolin
```bash
# Authentik OIDC Integration
OPENID_AUTHORIZATION_ENDPOINT=https://sso.obr.sh/application/o/authorize/
OPENID_JWKS_ENDPOINT=https://sso.obr.sh/application/o/guacamole/jwks/
OPENID_ISSUER=https://sso.obr.sh/application/o/guacamole/
OPENID_CLIENT_ID=<from_authentik_provider>
OPENID_REDIRECT_URI=https://remote.obr.sh/guacamole/api/auth/oidc/callback
# Claim Configuration
OPENID_USERNAME_CLAIM_TYPE=preferred_username
OPENID_GROUPS_CLAIM_TYPE=groups
OPENID_SCOPE=openid email profile
# Token Settings
OPENID_MAX_TOKEN_VALIDITY=300
OPENID_ALLOWED_CLOCK_SKEW=30
# Extension Loading
EXTENSION_PRIORITY=openid
OPENID_ENABLED=true
# Enable environment variables in guacamole.properties
enable-environment-properties: true
```
---
## References
- Apache Guacamole Official Documentation: https://guacamole.apache.org/doc/gug/openid-auth.html
- Authentik Integration Guide: https://docs.goauthentik.io/integrations/services/apache-guacamole/
- Guacamole Mailing List Archives: https://www.mail-archive.com/user@guacamole.apache.org/
- FreeRDP NLA Limitations: Community reports on NTLM vs Kerberos support
- TOTP Extension Guide: https://guacamole.apache.org/doc/gug/totp-auth.html
---
**Research Completed:** 2026-01-20
**Task:** RESEARCH-003
**Status:** Complete

View File

@@ -0,0 +1,619 @@
# Jellyfin SSO-Auth Plugin Configuration for Authentik OIDC
**Research Date:** 2026-01-20
**Plugin:** jellyfin-plugin-sso by 9p4
**Target IDP:** Authentik OIDC
**Documentation Version:** Based on plugin v3.5+ and Jellyfin 10.9+
---
## Executive Summary
The Jellyfin SSO-Auth plugin enables OpenID Connect (OIDC) authentication with Authentik, allowing users to authenticate via SSO. This document covers installation, claim mapping configuration, and critical mobile app authentication workflows.
**Key Findings:**
- Plugin supports OIDC providers including Authentik, Authelia, Keycloak, and others
- Claim mapping is essential for role-based authorization
- Mobile apps have SSO limitations requiring alternative authentication methods
- Quick Connect and API tokens are recommended for mobile app access
---
## 1. Plugin Installation Process
### 1.1 Installation via Web GUI
1. **Access Jellyfin Administration Dashboard**
- Navigate to your Jellyfin instance (e.g., `https://jellyfin.example.com`)
- Log in with administrator credentials
- Go to **Dashboard****Plugins**
2. **Add Plugin Repository**
- Click on the **Repositories** tab
- Click the **+** button to add a new repository
- Enter the following details:
- **Repository Name:** `Jellyfin SSO`
- **Repository URL:** `https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json`
- Click **Save**
- Confirm the repository installation
3. **Install SSO-Auth Plugin**
- Go to the **Catalog** tab
- Find **SSO Authentication** in the **Authentication** section
- Click **Install**
- Confirm the plugin installation
- **Restart Jellyfin** for the plugin to activate
### 1.2 Configuration File Location
After installation, the plugin configuration is stored at:
```
/config/data/plugins/configurations/SSO-Auth.xml
```
---
## 2. Authentik OIDC Provider Configuration
### 2.1 Authentik Setup
In Authentik, create an OIDC provider with the following settings:
**Provider Settings:**
- **Name:** `Jellyfin`
- **Client Type:** `Confidential`
- **Client ID:** `jellyfin` (or custom value)
- **Client Secret:** Generate secure secret
- **Redirect URIs:**
```
https://jellyfin.example.com/sso/OID/redirect/authentik
```
- **Token Endpoint Auth Method:** `client_secret_post` (CRITICAL for compatibility)
- **Scopes:** `openid`, `profile`, `email`, `groups`
**Important Note:** The plugin uses `client_secret_post` authentication method. Ensure Authentik is configured to accept this method. Mismatch causes authentication failures:
```
Client authentication failed... The registered client with id 'jellyfin' is configured
to only support 'token_endpoint_auth_method' method 'client_secret_basic'.
```
### 2.2 Authentik Group Configuration
Create groups for role-based access:
- **jellyfin-admins** - Administrative access
- **jellyfin-users** - Standard user access
Assign users to appropriate groups in Authentik.
### 2.3 Authentik Scope and Property Mappings
Ensure the following claim mappings are configured:
- **preferred_username** - Username claim
- **email** - Email address
- **groups** - User group memberships (CRITICAL for role authorization)
---
## 3. Jellyfin SSO Plugin Configuration
### 3.1 Configuration via Web GUI
1. **Navigate to Plugin Settings**
- Dashboard → **Plugins** → **SSO-Auth**
2. **Add OIDC Provider**
- Click **Add Provider**
- Configure the following settings:
**Basic Configuration:**
```
Name of OID Provider: authentik
OID Endpoint: https://auth.example.com
OpenID Client ID: jellyfin
OID Secret: <your-secure-secret>
```
**Authorization Settings:**
```
Enabled: ☑ (checked)
Enable Authorization by Plugin: ☑ (checked)
Enable All Folders: ☑ (checked)
```
**Role Mapping:**
```
Roles: jellyfin-users, jellyfin-admins
Admin Roles: jellyfin-admins
Role Claim: groups
```
**Advanced Settings:**
```
Request Additional Scopes: groups
Default Username Claim: preferred_username
New Path (recommended): ☑ (checked)
Disable HTTPS: ☐ (unchecked)
Do Not Validate Endpoints: ☐ (unchecked)
Do Not Validate Issuer Name: ☐ (unchecked)
```
3. **Save Configuration**
- Click **Save**
- The plugin is now active
### 3.2 Configuration via XML File
Alternatively, configure via the XML file at `/config/data/plugins/configurations/SSO-Auth.xml`:
```xml
<?xml version="1.0" encoding="utf-8"?>
<PluginConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SamlConfigs />
<OidConfigs>
<item>
<key>
<string>authentik</string>
</key>
<value>
<PluginConfiguration>
<OidEndpoint>https://auth.example.com</OidEndpoint>
<OidClientId>jellyfin</OidClientId>
<OidSecret>your-secure-secret</OidSecret>
<Enabled>true</Enabled>
<EnableAuthorization>true</EnableAuthorization>
<EnableAllFolders>true</EnableAllFolders>
<EnabledFolders />
<AdminRoles>
<string>jellyfin-admins</string>
</AdminRoles>
<Roles>
<string>jellyfin-users</string>
<string>jellyfin-admins</string>
</Roles>
<EnableFolderRoles>false</EnableFolderRoles>
<EnableLiveTvRoles>false</EnableLiveTvRoles>
<EnableLiveTv>false</EnableLiveTv>
<EnableLiveTvManagement>false</EnableLiveTvManagement>
<LiveTvRoles />
<LiveTvManagementRoles />
<FolderRoleMappings />
<RoleClaim>groups</RoleClaim>
<OidScopes>
<string>groups</string>
</OidScopes>
<DefaultUsernameClaim>preferred_username</DefaultUsernameClaim>
<NewPath>true</NewPath>
<CanonicalLinks />
<DisableHttps>false</DisableHttps>
<DisablePushedAuthorization>false</DisablePushedAuthorization>
<DoNotValidateEndpoints>false</DoNotValidateEndpoints>
<DoNotValidateIssuerName>false</DoNotValidateIssuerName>
</PluginConfiguration>
</value>
</item>
</OidConfigs>
</PluginConfiguration>
```
---
## 4. Claim Mapping Configuration
### 4.1 Understanding Role Claims
The plugin uses the `RoleClaim` setting to determine which OIDC claim contains user roles. Common patterns:
**Standard Groups Claim:**
```xml
<RoleClaim>groups</RoleClaim>
```
**Nested Claims (e.g., Keycloak):**
```xml
<RoleClaim>realm_access.roles</RoleClaim>
```
**Complex Nested Claims:**
```xml
<RoleClaim>resource_access.jellyfin.roles</RoleClaim>
```
### 4.2 Common Claim Mapping Issues
**Issue: Incorrect role claims warning**
```
OpenID user "username" has one or more incorrect role claims:
[{"Type": "groups", "Value": "jellyfin-admin"}]. Expected any one of: ["jellyfin-admins"]
```
**Solution:** Ensure exact match between:
1. Group names in Authentik
2. Role values in plugin configuration (`<AdminRoles>`, `<Roles>`)
3. Case sensitivity matters
### 4.3 Username Claim Mapping
The plugin supports multiple username claim sources:
```xml
<DefaultUsernameClaim>preferred_username</DefaultUsernameClaim>
```
**Supported Claims:**
- `preferred_username` - Most common
- `email` - Email address as username
- `sub` - OIDC subject identifier
- `name` - Display name
### 4.4 Additional Scopes
Request additional claims beyond the default `openid` scope:
```xml
<OidScopes>
<string>groups</string>
<string>email</string>
<string>profile</string>
</OidScopes>
```
---
## 5. Mobile App Authentication Workflow
### 5.1 The Mobile App SSO Problem
**Critical Finding:** Jellyfin mobile apps (Android/iOS) have limited SSO support. When using SSO login on mobile:
**Observed Behavior:**
- User clicks "Sign in with SSO"
- Browser-based authentication completes successfully
- Redirect returns to app
- App hangs on "Logging in..." indefinitely
- Session is not established
**Root Cause:**
- Mobile apps expect traditional username/password authentication
- Web-based OIDC flow completes but token is not properly transferred to app
- Issue documented in plugin Issue #189 (Jellyfin 10.9+)
### 5.2 Recommended Mobile App Solutions
#### Option 1: Quick Connect (Recommended)
Quick Connect allows mobile apps to authenticate without entering credentials:
**How It Works:**
1. User opens Jellyfin mobile app
2. App displays a 6-digit code
3. User logs into web interface (via SSO)
4. User enters the 6-digit code in web dashboard
5. Mobile app is authorized automatically
**Configuration:**
- Enable Quick Connect in Jellyfin Dashboard
- Dashboard → **Networking** → **Quick Connect**
- Check "Enable Quick Connect"
**Usage:**
1. Mobile app: **Settings** → **Add Server** → **Quick Connect**
2. Note the 6-digit code
3. Web browser: Login via SSO → **Dashboard** → **Quick Connect**
4. Enter code and authorize
5. Mobile app connects automatically
#### Option 2: API Token Authentication
Generate an API token for mobile app access:
**Steps:**
1. Login to Jellyfin web interface via SSO
2. Navigate to **Dashboard** → **API Keys**
3. Click **+** to create new API key
4. Name: "Mobile App - [Device Name]"
5. Copy the generated API token
6. In mobile app: Use token instead of password
**Security Considerations:**
- Tokens should be device-specific
- Revoke tokens for lost/stolen devices
- Rotate tokens periodically
- Monitor active sessions in Dashboard
#### Option 3: Fallback Local Authentication
Configure the plugin to allow fallback to local Jellyfin authentication:
```xml
<DefaultProvider>Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider</DefaultProvider>
```
**Workflow:**
- Web users: Use SSO
- Mobile users: Use Jellyfin username/password
- Maintain separate password for mobile access
- Less secure but functional
### 5.3 Mobile App Compatibility Matrix
| App | SSO Support | Quick Connect | API Token | Notes |
|-----|-------------|---------------|-----------|-------|
| Jellyfin Android | Partial | ✓ | ✓ | SSO hangs on "Logging in" |
| Jellyfin iOS | Partial | ✓ | ✓ | Similar issues as Android |
| Jellyfin Web | ✓ | N/A | ✓ | Full SSO support |
| Finamp (Music) | ✗ | ✓ | ✓ | No SSO, Quick Connect recommended |
| Jellyfin Kodi | ✗ | ✓ | ✓ | Token authentication works |
---
## 6. Testing and Troubleshooting
### 6.1 Testing Web Login
**Login URL:**
```
https://jellyfin.example.com/sso/OID/start/authentik
```
**Expected Flow:**
1. User navigates to URL
2. Redirects to Authentik login page
3. User authenticates with Authentik
4. Redirects back to Jellyfin
5. User is logged in with appropriate role
### 6.2 Adding SSO Button to Login Page
Create a custom login button on the Jellyfin login page:
1. Navigate to Dashboard → **General** → **Custom CSS**
2. Add the following CSS to create an SSO button:
```css
/* SSO Login Button */
.raised.emby-button {
padding: 0.9em 1em;
color: inherit !important;
}
.disclaimerContainer {
display: block;
}
```
3. Use browser developer tools or custom HTML injection to add button (plugin documentation has detailed instructions)
### 6.3 Common Errors and Solutions
#### Error: "Client authentication failed"
```
The registered client with id 'jellyfin' is configured to only support
'token_endpoint_auth_method' method 'client_secret_basic'.
```
**Solution:** Change Authentik provider settings:
- Edit OIDC Provider in Authentik
- Set **Client Authentication Method** to `client_secret_post`
- Save and restart Jellyfin
#### Error: "Incorrect role claims"
```
OpenID user "user123" has one or more incorrect role claims:
[{"Type": "groups", "Value": "jellyfin-user"}]. Expected any one of: ["jellyfin-users"]
```
**Solution:** Check exact spelling and case:
- Authentik groups must match exactly
- Plugin `<Roles>` must match exactly
- Claim name in `<RoleClaim>` must match Authentik scope mapping
#### Error: "Error processing request"
```
[ERR] Jellyfin.Api.Middleware.ExceptionMiddleware: Error processing request.
URL "GET" "/sso/OID/start/authentik"
```
**Common Causes:**
1. **DNS Resolution:** Jellyfin container cannot resolve Authentik hostname
2. **Network Connectivity:** Firewall blocking connection
3. **Certificate Issues:** SSL/TLS validation failing
**Solutions:**
- Verify DNS: `docker exec jellyfin ping auth.example.com`
- Check network connectivity
- Set `<DoNotValidateEndpoints>true</DoNotValidateEndpoints>` for testing (not production)
### 6.4 Debugging with Logs
Enable verbose logging in Jellyfin:
1. Dashboard → **Logs**
2. Look for `Jellyfin.Plugin.SSO_Auth` entries
3. Check for claim mismatches, authentication failures
**Useful Log Patterns:**
```
[INF] Jellyfin.Plugin.SSO_Auth.Api.SSOController: SSO Controller initialized
[WRN] OpenID user "username" has one or more incorrect role claims
[ERR] Error processing request
```
---
## 7. Security Best Practices
### 7.1 HTTPS Configuration
**Always use HTTPS in production:**
- Configure reverse proxy (Traefik, Nginx, Caddy)
- Obtain valid SSL/TLS certificates
- Set `<DisableHttps>false</DisableHttps>`
### 7.2 Client Secret Security
**Protect the client secret:**
- Store securely in configuration files
- Use environment variables or secrets management
- Never commit to version control
- Rotate periodically
### 7.3 Endpoint Validation
**Enable validation in production:**
```xml
<DoNotValidateEndpoints>false</DoNotValidateEndpoints>
<DoNotValidateIssuerName>false</DoNotValidateIssuerName>
```
**Disable only for testing/debugging**
### 7.4 Role-Based Access Control
**Implement least privilege:**
- Separate admin and user roles
- Use folder permissions for content restriction
- Enable `<EnableAuthorization>true</EnableAuthorization>`
- Audit user access regularly
### 7.5 API Token Management
**For mobile apps using tokens:**
- Generate device-specific tokens
- Use descriptive names: "iPhone - John's Device"
- Revoke tokens for lost devices
- Set token expiration if supported
- Monitor active sessions
---
## 8. Advanced Configuration Options
### 8.1 Folder-Based Role Mapping
Restrict access to specific libraries based on roles:
```xml
<EnableFolderRoles>true</EnableFolderRoles>
<FolderRoleMappings>
<FolderRoleMappings>
<Role>kids-content</Role>
<Folders>
<string>Kids Movies</string>
<string>Kids TV Shows</string>
</Folders>
</FolderRoleMappings>
</FolderRoleMappings>
```
### 8.2 Canonical User Links
Link SSO users to existing Jellyfin accounts:
```xml
<CanonicalLinks>
<item>
<key>
<string>sso-username</string>
</key>
<value>
<guid>existing-jellyfin-user-guid</guid>
</value>
</item>
</CanonicalLinks>
```
### 8.3 Live TV Permissions
Control Live TV access via roles:
```xml
<EnableLiveTv>true</EnableLiveTv>
<EnableLiveTvRoles>true</EnableLiveTvRoles>
<LiveTvRoles>
<string>livetv-users</string>
</LiveTvRoles>
<EnableLiveTvManagement>true</EnableLiveTvManagement>
<LiveTvManagementRoles>
<string>jellyfin-admins</string>
</LiveTvManagementRoles>
```
### 8.4 Port Override
Override the default port for callback URLs:
```xml
<PortOverride>8096</PortOverride>
```
Useful when Jellyfin runs behind a reverse proxy on a non-standard port.
---
## 9. Integration Testing Checklist
- [ ] Plugin installed and Jellyfin restarted
- [ ] Authentik OIDC provider configured with correct redirect URI
- [ ] Client authentication method set to `client_secret_post`
- [ ] Groups scope enabled in Authentik
- [ ] Test users assigned to jellyfin-users or jellyfin-admins groups
- [ ] Plugin configured with correct endpoint, client ID, and secret
- [ ] Role claim mapping matches Authentik group claim
- [ ] Web login successful via `/sso/OID/start/authentik`
- [ ] User receives correct permissions (admin vs. user)
- [ ] Quick Connect enabled for mobile apps
- [ ] API tokens generated for mobile testing
- [ ] Mobile app authentication tested
- [ ] HTTPS enabled in production
- [ ] Endpoint validation enabled
- [ ] Logs reviewed for errors
---
## 10. References and Resources
**Official Documentation:**
- Jellyfin SSO Plugin Repository: https://github.com/9p4/jellyfin-plugin-sso
- Authentik Jellyfin Integration: https://docs.goauthentik.io/integrations/services/jellyfin/
- Jellyfin Official Documentation: https://jellyfin.org/docs/
**Community Resources:**
- Plugin Issue Tracker: https://github.com/9p4/jellyfin-plugin-sso/issues
- Jellyfin Forum: https://forum.jellyfin.org/
- Authentik Forum: https://github.com/goauthentik/authentik/discussions
**Key Issues Referenced:**
- Mobile App SSO Issue #189: https://github.com/9p4/jellyfin-plugin-sso/issues/189
- Authelia Configuration Discussion: https://www.authelia.com/integration/openid-connect/clients/jellyfin/
- Quick Connect Feature Request: https://support.symfonium.app/t/quick-connect-for-jellyfin/6428
---
## 11. Conclusion
The Jellyfin SSO-Auth plugin provides robust OIDC integration with Authentik for web-based authentication. Key takeaways:
1. **Web authentication works well** when properly configured with correct claim mappings
2. **Mobile app support is limited** - Quick Connect or API tokens are required for mobile access
3. **Role-based authorization** requires exact matching between Authentik groups and plugin configuration
4. **Security configuration** is critical - always use HTTPS and validate endpoints in production
5. **Troubleshooting** relies heavily on log analysis and understanding OIDC claim flows
**Recommended Deployment Strategy:**
- Primary users: SSO via web interface
- Mobile apps: Quick Connect for initial setup, API tokens for ongoing access
- Administrators: SSO + 2FA in Authentik for enhanced security
- Regular audits of active sessions and API tokens
This configuration provides a secure, user-friendly authentication experience while accommodating the limitations of mobile client applications.
---
**Document Version:** 1.0
**Last Updated:** 2026-01-20
**Author:** Research Task RESEARCH-004
**Status:** Complete

View File

@@ -0,0 +1,458 @@
# OpenWebUI OIDC Integration Research
**Research Task:** RESEARCH-005
**Date:** 2026-01-20
**Version:** OpenWebUI v0.7.1+
## Executive Summary
OpenWebUI supports OIDC/OAuth2 authentication with comprehensive role and group management capabilities. This document details all environment variables, multi-user configurations, and admin designation methods via OIDC claims.
**Key Limitations:**
- Only ONE OIDC provider supported at a time via `OPENID_PROVIDER_URL`
- Cannot configure Microsoft AND Google OIDC simultaneously (workaround exists)
- Some variables are `PersistentConfig` - values persist to DB after first launch
---
## Complete Environment Variables List
### Core OIDC/OAuth Configuration
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `WEBUI_URL` | String | (required) | **REQUIRED.** Public WebUI address (e.g., `http://localhost:8080`). Must be set before OAuth/SSO. |
| `WEBUI_SECRET_KEY` | String | (auto-generated) | Session encryption key. **Required** in clustered/production environments. |
| `OPENID_PROVIDER_URL` | String | (none) | **REQUIRED for OIDC.** OpenID Connect discovery URL (e.g., `https://accounts.google.com/.well-known/openid-configuration`). |
| `OAUTH_CLIENT_ID` | String | (none) | **REQUIRED.** OAuth client ID from IdP. |
| `OAUTH_CLIENT_SECRET` | String | (none) | **REQUIRED.** OAuth client secret from IdP. |
### OAuth Behavior Controls
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `ENABLE_OAUTH_SIGNUP` | Boolean | `false` | Allow new account creation via OAuth login (separate from `ENABLE_SIGNUP`). |
| `ENABLE_OAUTH_PERSISTENT_CONFIG` | Boolean | `true` | Persist OAuth config to database. Set to `false` for stateless/containerized environments. |
| `OAUTH_MERGE_ACCOUNTS_BY_EMAIL` | Boolean | `false` | Merge OAuth logins by matching email. **CAUTION:** Insecure if provider doesn't verify emails. |
| `OAUTH_UPDATE_PICTURE_ON_LOGIN` | Boolean | `false` | Update user profile pictures from OAuth provider on each login. |
| `ENABLE_OAUTH_ID_TOKEN_COOKIE` | Boolean | `true` | Store ID token in cookie (for backward compatibility). |
### Token & Session Management
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `OAUTH_SESSION_TOKEN_ENCRYPTION_KEY` | String | (auto-generated) | Encrypts OAuth session tokens server-side. |
| `OAUTH_CLIENT_INFO_ENCRYPTION_KEY` | String | (auto-generated) | Encrypts OAuth client information for MCP servers. |
| `WEBUI_AUTH_SIGNOUT_REDIRECT_URL` | String | (empty) | Redirect URL after signout (e.g., `https://idp.example.com/logout`). |
### Claim Mapping Variables
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `OAUTH_USERNAME_CLAIM` | String | `preferred_username` | Claim field containing username. |
| `OAUTH_EMAIL_CLAIM` | String | `email` | Claim field containing user email. **NOTE:** May need customization for some IdPs (e.g., Microsoft Entra). |
| `OAUTH_PICTURE_CLAIM` | String | `picture` | Claim field containing profile picture URL. Set to empty string to disable. |
**Important:** Microsoft Entra ID sometimes returns email in `preferred_username` instead of `email` claim. Custom configuration may be required.
---
## Role Management Configuration
### Role Synchronization
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `ENABLE_OAUTH_ROLE_MANAGEMENT` | Boolean | `false` | **Enable role synchronization from OIDC claims.** |
| `OAUTH_ROLES_CLAIM` | String | `roles` | Claim field containing user roles. Supports nested paths (e.g., `resource_access.client.roles`). |
| `OAUTH_ALLOWED_ROLES` | String (CSV) | (none) | Comma-separated list of roles allowed to access OpenWebUI. Empty = all authenticated users allowed. |
| `OAUTH_ADMIN_ROLES` | String (CSV) | (none) | **Comma-separated list of roles that grant admin privileges.** Users with these roles become OpenWebUI admins. |
### Role Management Behavior
**When `ENABLE_OAUTH_ROLE_MANAGEMENT=true`:**
- User roles are synchronized from OIDC claims on **every login**
- `OAUTH_ROLES_CLAIM` can use dot notation for nested claims (e.g., `realm_access.roles`)
- If user has role in `OAUTH_ADMIN_ROLES`, they receive admin privileges
- If user doesn't have role in `OAUTH_ALLOWED_ROLES`, login is denied
**Example Configuration:**
```bash
ENABLE_OAUTH_ROLE_MANAGEMENT=true
OAUTH_ROLES_CLAIM=roles
OAUTH_ALLOWED_ROLES=openwebui-user,openwebui-admin
OAUTH_ADMIN_ROLES=openwebui-admin
```
---
## Group Management Configuration
### Group Synchronization
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `ENABLE_OAUTH_GROUP_MANAGEMENT` | Boolean | `false` | **Enable group membership synchronization from OIDC claims.** |
| `OAUTH_GROUP_CLAIM` | String | `groups` | Claim field containing group memberships. |
| `ENABLE_OAUTH_GROUP_CREATION` | Boolean | `false` | **Enable Just-in-Time (JIT) group creation.** Groups from claims are auto-created if they don't exist. |
### Group Management Behavior
**CRITICAL:** When `ENABLE_OAUTH_GROUP_MANAGEMENT=true`:
- User's group memberships in OpenWebUI are **STRICTLY SYNCHRONIZED** with OAuth claims on **every login**
- Manually assigned groups will be removed if not present in OAuth claims
- Groups are identified by name matching between claim values and OpenWebUI group names
**Just-in-Time Group Creation:**
When `ENABLE_OAUTH_GROUP_CREATION=true`:
- Groups from OIDC claims are automatically created in OpenWebUI if they don't exist
- Useful for dynamic group provisioning without manual setup
- Groups are created with default permissions
**Example Configuration:**
```bash
ENABLE_OAUTH_GROUP_MANAGEMENT=true
OAUTH_GROUP_CLAIM=groups
ENABLE_OAUTH_GROUP_CREATION=true
```
---
## Provider-Specific Variables
### Google OAuth
| Variable | Description |
|----------|-------------|
| `GOOGLE_CLIENT_ID` | Google OAuth client ID |
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret |
| `OPENID_PROVIDER_URL` | Set to `https://accounts.google.com/.well-known/openid-configuration` |
**Redirect URI:** `https://your-domain/oauth/google/callback`
### Microsoft OAuth (Entra ID)
| Variable | Description |
|----------|-------------|
| `MICROSOFT_CLIENT_ID` | Microsoft OAuth client ID |
| `MICROSOFT_CLIENT_SECRET` | Microsoft OAuth client secret |
| `MICROSOFT_CLIENT_TENANT_ID` | Microsoft tenant ID |
| `OPENID_PROVIDER_URL` | Set to Microsoft OIDC discovery URL |
**Redirect URI:** `https://your-domain/oauth/microsoft/callback`
**Known Issues:**
- Email may be in `preferred_username` claim instead of `email`
- May require `OAUTH_EMAIL_CLAIM=preferred_username` for some users
### Generic OIDC Provider
| Variable | Description |
|----------|-------------|
| `OAUTH_CLIENT_ID` | Generic OAuth client ID |
| `OAUTH_CLIENT_SECRET` | Generic OAuth client secret |
| `OPENID_PROVIDER_URL` | **REQUIRED.** OIDC discovery URL |
| `OAUTH_SCOPES` | OAuth scopes to request (default: `openid profile email`) |
**Redirect URI:** `https://your-domain/oauth/oidc/callback`
---
## Trusted Header Authentication (Alternative to OIDC)
For reverse proxy-based authentication (Tailscale, Cloudflare Tunnel, oauth2-proxy, Authentik, Authelia):
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `WEBUI_AUTH_TRUSTED_EMAIL_HEADER` | String | (none) | HTTP header containing authenticated user's email |
| `WEBUI_AUTH_TRUSTED_NAME_HEADER` | String | (none) | HTTP header containing user's display name |
| `WEBUI_AUTH_TRUSTED_GROUPS_HEADER` | String | (none) | HTTP header containing comma-separated group list |
**SECURITY WARNING:**
- Misconfiguration allows **unauthorized authentication bypass**
- **ONLY use if reverse proxy strictly blocks direct access to OpenWebUI**
- Ensure headers cannot be spoofed by end users
---
## Multi-User Role Mapping Configuration
### Method 1: OIDC Role Claims (Recommended)
**Step 1: Configure IdP to include roles in token**
- Add custom claims to OIDC token
- Include roles in `roles` claim (or custom claim path)
**Step 2: Configure OpenWebUI**
```bash
ENABLE_OAUTH_ROLE_MANAGEMENT=true
OAUTH_ROLES_CLAIM=roles # or custom path like "realm_access.roles"
OAUTH_ALLOWED_ROLES=user,admin,power-user # Users must have one of these roles
OAUTH_ADMIN_ROLES=admin # Users with this role become admins
```
**Step 3: Map users in IdP**
- Assign roles to users in IdP (Authentik, Keycloak, Okta, etc.)
- Users sync roles on every login
### Method 2: OIDC Group Claims
**Step 1: Configure IdP to include groups in token**
- Add groups to OIDC token claims
- Ensure group names match desired OpenWebUI groups
**Step 2: Configure OpenWebUI**
```bash
ENABLE_OAUTH_GROUP_MANAGEMENT=true
OAUTH_GROUP_CLAIM=groups
ENABLE_OAUTH_GROUP_CREATION=true # Auto-create groups from claims
```
**Step 3: Manage permissions in OpenWebUI**
- Groups are synced automatically
- Admin assigns permissions to groups within OpenWebUI
### Method 3: Combined Role + Group Management
```bash
# Enable both
ENABLE_OAUTH_ROLE_MANAGEMENT=true
ENABLE_OAUTH_GROUP_MANAGEMENT=true
# Role configuration
OAUTH_ROLES_CLAIM=roles
OAUTH_ALLOWED_ROLES=openwebui-user
OAUTH_ADMIN_ROLES=openwebui-admin
# Group configuration
OAUTH_GROUP_CLAIM=groups
ENABLE_OAUTH_GROUP_CREATION=true
```
**Behavior:**
1. Roles control access (allowed) and admin privileges
2. Groups control resource/team organization within OpenWebUI
3. Both sync on every login
---
## Admin Designation via OIDC Claims
### Option 1: Role-Based Admin (Primary Method)
**Configuration:**
```bash
ENABLE_OAUTH_ROLE_MANAGEMENT=true
OAUTH_ROLES_CLAIM=roles
OAUTH_ADMIN_ROLES=openwebui-admin,superuser
```
**How it works:**
- Users with `openwebui-admin` OR `superuser` role become admins
- Admin status is synchronized on every login
- Removing role in IdP removes admin privileges on next login
### Option 2: Group-Based Admin (Indirect)
**Configuration:**
```bash
ENABLE_OAUTH_GROUP_MANAGEMENT=true
OAUTH_GROUP_CLAIM=groups
```
**How it works:**
- Groups are synced to OpenWebUI
- Admin must manually grant admin privileges to specific users/groups within OpenWebUI
- Less automated than role-based approach
### Option 3: First User Auto-Admin
**Default Behavior:**
- When OpenWebUI is first deployed, the first user to sign in becomes admin
- Subsequent users require admin assignment or role-based configuration
**Not recommended for production** - use role-based admin instead.
---
## Example Configurations
### Authentik OIDC with Role Management
```bash
# Core OIDC
WEBUI_URL=https://openwebui.example.com
OPENID_PROVIDER_URL=https://authentik.example.com/application/o/openwebui/.well-known/openid-configuration
OAUTH_CLIENT_ID=<client-id-from-authentik>
OAUTH_CLIENT_SECRET=<client-secret-from-authentik>
# OAuth behavior
ENABLE_OAUTH_SIGNUP=true
ENABLE_OAUTH_PERSISTENT_CONFIG=false # For containerized deployments
# Role management
ENABLE_OAUTH_ROLE_MANAGEMENT=true
OAUTH_ROLES_CLAIM=roles
OAUTH_ALLOWED_ROLES=openwebui-user,openwebui-admin
OAUTH_ADMIN_ROLES=openwebui-admin
# Group management (optional)
ENABLE_OAUTH_GROUP_MANAGEMENT=true
OAUTH_GROUP_CLAIM=groups
ENABLE_OAUTH_GROUP_CREATION=true
```
### Okta OIDC with Group JIT Provisioning
```bash
# Core OIDC
WEBUI_URL=https://openwebui.company.com
OPENID_PROVIDER_URL=https://dev-12345.okta.com/.well-known/openid-configuration
OAUTH_CLIENT_ID=<okta-client-id>
OAUTH_CLIENT_SECRET=<okta-client-secret>
# OAuth behavior
ENABLE_OAUTH_SIGNUP=true
# Role management
ENABLE_OAUTH_ROLE_MANAGEMENT=true
OAUTH_ROLES_CLAIM=groups # Okta uses groups claim for roles
OAUTH_ADMIN_ROLES=OpenWebUI-Admins
# Group management with JIT
ENABLE_OAUTH_GROUP_MANAGEMENT=true
OAUTH_GROUP_CLAIM=groups
ENABLE_OAUTH_GROUP_CREATION=true
```
### Microsoft Entra ID with Email Claim Fix
```bash
# Core OIDC
WEBUI_URL=https://openwebui.contoso.com
OPENID_PROVIDER_URL=https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration
MICROSOFT_CLIENT_ID=<application-id>
MICROSOFT_CLIENT_SECRET=<client-secret>
MICROSOFT_CLIENT_TENANT_ID=<tenant-id>
# Fix email claim issue (Entra specific)
OAUTH_EMAIL_CLAIM=preferred_username # Or upn
OAUTH_USERNAME_CLAIM=preferred_username
# OAuth behavior
ENABLE_OAUTH_SIGNUP=true
# Role management (if using app roles)
ENABLE_OAUTH_ROLE_MANAGEMENT=true
OAUTH_ROLES_CLAIM=roles
OAUTH_ADMIN_ROLES=OpenWebUI.Admin
```
---
## Important Notes & Gotchas
### PersistentConfig Behavior
**Variables marked as `PersistentConfig`:**
- Values are saved to database on **first launch**
- Subsequent container restarts **ignore environment variables**
- Changes must be made via OpenWebUI Admin UI or database
**Workaround:**
```bash
ENABLE_OAUTH_PERSISTENT_CONFIG=false # Disable persistence, use env vars only
```
### Common Misconfigurations
**Incorrect Variables (Do NOT use):**
```bash
# WRONG - these don't exist
OIDC_CONFIG=...
WEBUI_OIDC_CLIENT_ID=...
WEBUI_ENABLE_SSO=...
WEBUI_AUTH_TYPE=...
OPENID_CLIENT_ID=...
OPENID_CLIENT_SECRET=...
# CORRECT - use these instead
OPENID_PROVIDER_URL=...
OAUTH_CLIENT_ID=...
OAUTH_CLIENT_SECRET=...
ENABLE_OAUTH_SIGNUP=...
```
### Security Considerations
1. **Always use HTTPS** for production OIDC deployments
2. **Validate redirect URIs** match exactly in IdP configuration
3. **Don't enable `OAUTH_MERGE_ACCOUNTS_BY_EMAIL`** unless provider verifies emails
4. **Use `OAUTH_ALLOWED_ROLES`** to restrict access to authorized users
5. **Trusted Headers** require strict reverse proxy configuration
### Role vs Group Management
| Feature | Roles | Groups |
|---------|-------|--------|
| **Purpose** | Access control, admin privileges | Resource organization, team structure |
| **Sync Behavior** | Strict sync on login | Strict sync on login |
| **Admin Assignment** | Via `OAUTH_ADMIN_ROLES` | Manual in OpenWebUI UI |
| **Access Control** | Via `OAUTH_ALLOWED_ROLES` | Not built-in (manual management) |
| **JIT Creation** | N/A | Via `ENABLE_OAUTH_GROUP_CREATION` |
---
## Troubleshooting
### OIDC Provider Not Activating
**Check:**
1. `WEBUI_URL` is set in environment **and** Admin Panel
2. `OPENID_PROVIDER_URL` is valid and accessible from container
3. `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are correct
4. Redirect URI in IdP matches `https://<WEBUI_URL>/oauth/oidc/callback`
### OAuth Callback Failed - Email Missing
**Solution:**
```bash
OAUTH_EMAIL_CLAIM=preferred_username # Or other claim containing email
```
### Users Not Getting Admin Privileges
**Check:**
1. `ENABLE_OAUTH_ROLE_MANAGEMENT=true`
2. User has role listed in `OAUTH_ADMIN_ROLES`
3. `OAUTH_ROLES_CLAIM` matches claim path in token (use JWT debugger)
4. Role claim is included in scopes requested
### Groups Not Syncing
**Check:**
1. `ENABLE_OAUTH_GROUP_MANAGEMENT=true`
2. `OAUTH_GROUP_CLAIM` matches claim name in token
3. Groups claim is included in requested scopes
4. `ENABLE_OAUTH_GROUP_CREATION=true` if groups don't exist yet
---
## References
- [OpenWebUI SSO Documentation](https://docs.openwebui.com/features/auth/sso/)
- [OpenWebUI Environment Variables](https://docs.openwebui.com/getting-started/env-configuration/)
- [Troubleshooting SSO Issues](https://docs.openwebui.com/troubleshooting/sso/)
- [Authentik Integration Guide](https://integrations.goauthentik.io/miscellaneous/open-webui/)
- [Authelia Integration Guide](https://www.authelia.com/integration/openid-connect/clients/open-webui/)
- [Okta OIDC Tutorial](https://docs.openwebui.com/tutorials/integrations/okta-oidc-sso/)
---
## Research Metadata
- **Task ID:** RESEARCH-005
- **Completed:** 2026-01-20
- **OpenWebUI Version:** v0.7.1+
- **Research Method:** Web documentation review via Exa MCP
- **Primary Sources:** Official OpenWebUI documentation, GitHub issues, integration guides

View File

@@ -0,0 +1,935 @@
# Pangolin Self-Hosting Research
**Research Date:** 2026-01-20
**Purpose:** RESEARCH-001 - Comprehensive documentation for self-hosting Pangolin control plane
## Executive Summary
Pangolin is an open-source, identity-based remote access platform built on WireGuard. It functions as a self-hosted tunneled reverse proxy with identity-aware access control, providing a secure alternative to Cloudflare Tunnels and similar managed services. Pangolin combines two primary use cases: tunneled reverse proxy for web applications and VPN-style access for private network resources, all managed through a centralized control plane.
**Key Architecture Components:**
- **Pangolin Server (Control Plane)**: Centralized dashboard and management plane hosted on a VPS
- **Newt (Site/Client Connector)**: Tunnel client installed on private networks to connect backend services
- **WireGuard Tunnels**: Encrypted tunneling protocol for secure communication
- **Traefik**: Underlying reverse proxy technology
- **Identity Providers**: OIDC/OAuth2 integration for authentication
---
## 1. Installation Methods for Self-Hosting Pangolin Control Plane
### 1.1 Quick Install (Recommended for Most Users)
**Official Installer Script:**
The quickest method uses Pangolin's automated installer script that handles all dependencies and configuration.
**Prerequisites:**
- Linux server (Ubuntu 22.04/24.04 or Debian 11/12 recommended)
- Root or sudo access
- Public IPv4 address
- Domain name with DNS configured
- Email address for Let's Encrypt SSL certificates
- Open firewall ports:
- TCP 80 (HTTP & Let's Encrypt validation)
- TCP 443 (HTTPS)
- UDP 51820 (WireGuard tunnels)
**Installation Command:**
```bash
curl -fsSL https://get.pangolin.net | sudo bash
```
The installer will:
1. Install required dependencies (Docker, Docker Compose, WireGuard kernel module)
2. Generate configuration files
3. Set up Traefik reverse proxy
4. Configure SSL certificates via Let's Encrypt
5. Create initial admin account
6. Start all services
**Post-Installation:**
- Access dashboard at `https://your-domain.com`
- Log in with admin credentials sent to provided email
- Complete organization setup
### 1.2 Manual Docker Compose Installation
For users requiring full control over configuration, manual Docker Compose deployment is available.
**File Structure:**
```
pangolin/
├── docker-compose.yml
├── config/
│ ├── pangolin.yml # Pangolin configuration
│ ├── traefik.yml # Traefik static configuration
│ └── traefik-dynamic.yml # Traefik dynamic configuration
├── data/
│ ├── pangolin/ # Application data
│ ├── traefik/ # Traefik certificates
│ └── postgres/ # Database (if using PostgreSQL)
└── logs/
```
**Docker Compose Configuration:**
```yaml
version: '3.8'
services:
pangolin:
image: fosrl/pangolin:latest
container_name: pangolin
restart: unless-stopped
cap_add:
- NET_ADMIN
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv4.ip_forward=1
- net.ipv6.conf.all.forwarding=1
volumes:
- ./config/pangolin.yml:/app/config.yml:ro
- ./data/pangolin:/app/data
- /dev/net/tun:/dev/net/tun
environment:
- CONFIG_FILE=/app/config.yml
ports:
- "51820:51820/udp"
networks:
- pangolin
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
volumes:
- ./config/traefik.yml:/etc/traefik/traefik.yml:ro
- ./config/traefik-dynamic.yml:/etc/traefik/dynamic.yml:ro
- ./data/traefik:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- "80:80"
- "443:443"
networks:
- pangolin
postgres:
image: postgres:15-alpine
container_name: pangolin-db
restart: unless-stopped
environment:
- POSTGRES_DB=pangolin
- POSTGRES_USER=pangolin
- POSTGRES_PASSWORD=<secure_password>
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
- pangolin
networks:
pangolin:
driver: bridge
```
**Starting the Stack:**
```bash
docker compose up -d
```
### 1.3 Alternative Installation Methods
**Unraid Deployment:**
- Community applications available in Unraid App Store
- Template-based deployment with GUI configuration
- Integration with Unraid Docker management
**Kubernetes Deployment:**
- Helm charts available for production deployments
- Supports horizontal scaling of control plane
- Advanced orchestration for enterprise use cases
---
## 2. OIDC Integration Configuration
Pangolin supports external identity providers via OAuth2/OIDC for Single Sign-On (SSO), enabling centralized authentication for both the Pangolin dashboard and applications behind the proxy.
### 2.1 Supported Identity Providers
- **Google Workspace** - Native integration
- **Microsoft Entra ID (Azure AD)** - Native integration
- **Generic OAuth2/OIDC** - Any compliant provider
- **Authelia** - Tested with v4.39.15+
- **Authentik** - Community-supported integration
- **Zitadel** - Native support
- **Pocket ID** - Native support
### 2.2 OIDC Provider Configuration
**Step 1: Create OAuth2/OIDC Application in Identity Provider**
In your IdP (e.g., Authentik, Authelia, Google), create a new OAuth2/OIDC application with the following settings:
**Redirect URIs (Critical):**
```
https://<pangolin-domain>/api/v1/auth/callback
https://<pangolin-domain>/api/v1/auth/callback/google # If using Google
https://<pangolin-domain>/api/v1/auth/callback/microsoft # If using Microsoft
```
For custom authentication domains:
```
https://auth.<your-company>.com/api/v1/auth/callback
```
**Required Scopes:**
- `openid` (required)
- `profile` (recommended)
- `email` (recommended)
- `groups` (optional, for role-based access)
**Client Configuration:**
- **Client Type:** Confidential
- **Grant Type:** Authorization Code
- **PKCE:** Enabled (recommended)
- **Token Endpoint Auth Method:** client_secret_post or client_secret_basic
**Step 2: Configure Pangolin Identity Provider**
In Pangolin Dashboard:
1. Navigate to **Settings****Identity Providers****Add Identity Provider**
2. Select provider type:
- **Google** for Google Workspace
- **Azure Entra ID** for Microsoft
- **Generic OAuth2/OIDC** for other providers
3. Enter configuration details:
**For Generic OAuth2/OIDC:**
```yaml
Provider Name: Your IdP Name
Provider Type: OAuth2/OIDC
# Discovery URL (auto-populates endpoints)
OIDC Discovery URL: https://your-idp.com/.well-known/openid-configuration
# Manual configuration (if discovery not available)
Authorization Endpoint: https://your-idp.com/oauth/authorize
Token Endpoint: https://your-idp.com/oauth/token
Userinfo Endpoint: https://your-idp.com/oauth/userinfo
JWKS URI: https://your-idp.com/oauth/jwks
# Client credentials
Client ID: <from_your_idp>
Client Secret: <from_your_idp>
# Scopes
Scopes: openid profile email groups
# User Mapping
Username Claim: preferred_username
Email Claim: email
Display Name Claim: name
Groups Claim: groups (optional)
```
**For Google Workspace:**
```yaml
Provider Type: Google
Client ID: <google_client_id>
Client Secret: <google_client_secret>
Hosted Domain: your-domain.com (optional, restricts to specific domain)
```
**For Microsoft Entra ID:**
```yaml
Provider Type: Azure Entra ID
Client ID: <azure_client_id>
Client Secret: <azure_client_secret>
Tenant ID: <azure_tenant_id>
```
### 2.3 Auto-Provisioning Users
Pangolin supports automatic user provisioning from IdP:
**Configuration Options:**
- **Auto-provision on first login:** Create user accounts automatically when users authenticate
- **Group mapping:** Map IdP groups to Pangolin roles/organizations
- **Just-in-Time (JIT) provisioning:** Update user attributes on each login
**Example Configuration:**
```yaml
auto_provisioning:
enabled: true
default_role: user
group_mapping:
- idp_group: "admin"
pangolin_role: "admin"
- idp_group: "developers"
pangolin_role: "developer"
```
### 2.4 Custom Authentication Domains
For branded authentication experiences:
1. Configure custom domain in Pangolin settings
2. Set authentication domain: `auth.yourcompany.com`
3. Update DNS records to point to Pangolin server
4. Update redirect URIs in IdP configuration
---
## 3. Site Configuration (Defining Backend Services)
Sites in Pangolin represent network locations where backend services run. The Newt client connects these sites to the Pangolin control plane via WireGuard tunnels.
### 3.1 Understanding Sites vs Resources
**Sites:**
- Physical or virtual network locations (e.g., home lab, office network, VPS)
- Run the Newt client to establish WireGuard tunnels
- Can host multiple resources/services
- Types:
- **Newt Sites:** Traditional tunnel-based sites for private networks
- **Remote Nodes:** Exit nodes for client VPN access
**Resources:**
- Individual services/applications exposed through Pangolin
- Defined within sites
- Types:
- **Public Resources (HTTPS):** Web applications accessible via public URLs
- **Private Resources:** TCP/UDP services for VPN-style access
### 3.2 Creating a Site
**Via Dashboard:**
1. Navigate to **Sites****Add Site**
2. Configure site settings:
```yaml
Site Name: Home Lab
Description: Private home network services
Site Type: Newt Site
# Tunnel Configuration
WireGuard IP Range: 10.0.1.0/24 (auto-assigned)
Listen Port: 51820 (default)
# Site Location (optional)
Location: Home Network
Tags: homelab, production
```
3. Copy the generated installation command with credentials
**Installation Command Format:**
```bash
newt --id <site_id> --key <site_key> --endpoint <pangolin_server>:51820
```
### 3.3 Defining Resources (Backend Services)
**HTTPS Resources (Public Web Applications):**
1. Navigate to **Resources****Add Resource**
2. Select resource type: **HTTPS Resource**
```yaml
Resource Name: Internal Wiki
Public URL: wiki.yourdomain.com
# Backend Configuration
Site: Home Lab
Backend Type: HTTP
Backend Address: 192.168.1.100:8080
Backend Protocol: http
# Health Checks
Health Check: Enabled
Health Check Path: /health
Health Check Interval: 30s
# Access Control
Access Level: Authenticated Users Only
Allowed Groups: developers, admins
# Additional Settings
Enable WebSocket: Yes (for real-time apps)
Custom Headers: X-Forwarded-Proto: https
```
**Private Resources (TCP/UDP Services):**
For services accessed via Pangolin client VPN:
```yaml
Resource Name: SSH Server
Resource Type: TCP
# Backend Configuration
Site: Home Lab
Backend Address: 192.168.1.50:22
Protocol: TCP
# Access Control
Access Level: Specific Users
Allowed Users: admin@company.com, ops@company.com
```
### 3.4 Advanced Site Configuration
**Environment Variables (for Newt):**
```bash
# Custom configuration
NEWT_CONFIG_FILE=/etc/newt/config.yml
NEWT_LOG_LEVEL=info
NEWT_RECONNECT_INTERVAL=10s
```
**Configuration File (`/etc/newt/config.yml`):**
```yaml
server:
endpoint: pangolin.yourdomain.com:51820
id: <site_id>
key: <site_key>
tunnel:
mtu: 1420
keepalive: 25
persistent_keepalive: true
logging:
level: info
format: json
output: /var/log/newt/newt.log
health:
enable: true
interval: 30s
```
### 3.5 DNS Configuration for Resources
**For HTTPS Resources:**
Create DNS A/CNAME records pointing to Pangolin server:
```
wiki.yourdomain.com A <pangolin_server_ip>
app1.yourdomain.com A <pangolin_server_ip>
app2.yourdomain.com A <pangolin_server_ip>
```
**Wildcard Domain (Advanced):**
```
*.yourdomain.com A <pangolin_server_ip>
```
Configure wildcard domain in Pangolin settings to automatically route all subdomains.
---
## 4. Client Deployment (Newt Client for VPS/Mobile)
The Newt client serves dual purposes:
1. **Site Connector:** Install on private networks to expose backend services
2. **VPN Client:** Install on user devices for accessing private resources
### 4.1 Newt Site Connector Deployment
**For VPS/Server Sites:**
**Installation Methods:**
**A. Binary Installation (Linux):**
```bash
# Download latest release
curl -fsSL https://get.pangolin.net/newt | sudo bash
# Or manual installation
wget https://github.com/fosrl/newt/releases/latest/download/newt-linux-amd64
chmod +x newt-linux-amd64
sudo mv newt-linux-amd64 /usr/local/bin/newt
# Run with credentials from Pangolin dashboard
newt --id <site_id> --key <site_key> --endpoint <pangolin_server>:51820
```
**B. Docker Deployment (Recommended):**
Create `docker-compose.yml` for Newt site:
```yaml
version: '3.8'
services:
newt:
image: fosrl/newt:latest
container_name: newt-site
restart: unless-stopped
cap_add:
- NET_ADMIN
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
devices:
- /dev/net/tun
environment:
- NEWT_SERVER_ENDPOINT=pangolin.yourdomain.com:51820
- NEWT_SITE_ID=<site_id>
- NEWT_SITE_KEY=<site_key>
volumes:
- ./config:/etc/newt
network_mode: host # Required for proper networking
```
**C. Systemd Service (Production):**
Create `/etc/systemd/system/newt.service`:
```ini
[Unit]
Description=Pangolin Newt Site Connector
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/newt \
--id <site_id> \
--key <site_key> \
--endpoint pangolin.yourdomain.com:51820 \
--config /etc/newt/config.yml
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
Enable and start:
```bash
sudo systemctl daemon-reload
sudo systemctl enable newt
sudo systemctl start newt
sudo systemctl status newt
```
**D. Kubernetes Deployment:**
```yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: newt-site
namespace: pangolin
spec:
selector:
matchLabels:
app: newt-site
template:
metadata:
labels:
app: newt-site
spec:
hostNetwork: true
containers:
- name: newt
image: fosrl/newt:latest
securityContext:
capabilities:
add:
- NET_ADMIN
env:
- name: NEWT_SERVER_ENDPOINT
value: "pangolin.yourdomain.com:51820"
- name: NEWT_SITE_ID
valueFrom:
secretKeyRef:
name: newt-credentials
key: site-id
- name: NEWT_SITE_KEY
valueFrom:
secretKeyRef:
name: newt-credentials
key: site-key
```
### 4.2 Newt VPN Client Deployment
**For End-User Devices (Accessing Private Resources):**
**A. Desktop Clients (Windows/Mac/Linux):**
Download from Pangolin dashboard or releases page:
- Windows: `newt-windows-amd64.exe`
- macOS: `newt-darwin-amd64` (Intel) or `newt-darwin-arm64` (Apple Silicon)
- Linux: `newt-linux-amd64`
**Installation Steps:**
1. Create client in Pangolin dashboard:
- Navigate to **Clients****Add Client**
- Configure client settings:
```yaml
Client Name: John's Laptop
Client Type: VPN Client
Allowed Resources: SSH Server, Database Server
User: john@company.com
```
2. Copy generated credentials
3. Run Newt client:
```bash
newt --mode client \
--id <client_id> \
--key <client_key> \
--endpoint pangolin.yourdomain.com:51820
```
**B. Mobile Clients (iOS/Android):**
Currently, mobile clients use standard WireGuard configuration:
1. Generate WireGuard config in Pangolin dashboard
2. Export configuration as QR code or config file
3. Import into WireGuard mobile app:
- iOS: WireGuard from App Store
- Android: WireGuard from Google Play
**Example WireGuard Config:**
```ini
[Interface]
PrivateKey = <client_private_key>
Address = 10.0.2.5/24
DNS = 1.1.1.1
[Peer]
PublicKey = <pangolin_server_public_key>
Endpoint = pangolin.yourdomain.com:51820
AllowedIPs = 10.0.0.0/16, 192.168.1.0/24
PersistentKeepalive = 25
```
**C. Configuration Management:**
**Client Configuration File (`config.yml`):**
```yaml
client:
id: <client_id>
key: <client_key>
name: Johns-Laptop
server:
endpoint: pangolin.yourdomain.com:51820
public_key: <server_public_key>
tunnel:
mtu: 1420
dns_servers:
- 1.1.1.1
- 8.8.8.8
routes:
- 10.0.0.0/16 # Pangolin internal network
- 192.168.1.0/24 # Home lab network
access:
auto_connect: true
reconnect_on_failure: true
kill_switch: false # Block internet if VPN disconnects
```
### 4.3 Updating Newt Clients
**Manual Update:**
```bash
# Stop service
sudo systemctl stop newt
# Download latest version
curl -fsSL https://get.pangolin.net/newt | sudo bash
# Restart service
sudo systemctl start newt
```
**Docker Update:**
```bash
docker compose pull
docker compose up -d
```
**Auto-Update Configuration:**
```yaml
updates:
auto_update: true
channel: stable # or beta, nightly
check_interval: 24h
```
---
## 5. Integration Points with Authentik
Based on research, here's how Pangolin integrates with Authentik for your use case:
### 5.1 Authentik Configuration
**Create OAuth2/OIDC Provider in Authentik:**
1. In Authentik Admin Interface:
- Navigate to **Applications** → **Providers** → **Create Provider**
- Select **OAuth2/OpenID Provider**
2. Configure Provider:
```yaml
Name: Pangolin
Authorization Flow: default-provider-authorization-implicit-consent
Client Type: Confidential
Client ID: <generate_or_custom>
Client Secret: <generate_secure_secret>
Redirect URIs:
- https://pangolin.photon.obnh.io/api/v1/auth/callback
Scopes:
- openid
- profile
- email
- groups
Subject Mode: Based on User's hashed ID
Include claims in id_token: Yes
```
3. Create Application:
- Navigate to **Applications** → **Applications** → **Create**
```yaml
Name: Pangolin
Slug: pangolin
Provider: Pangolin (from step 2)
Launch URL: https://pangolin.photon.obnh.io
```
### 5.2 Pangolin Configuration for Authentik
In Pangolin Dashboard:
1. Navigate to **Settings** → **Identity Providers** → **Add Identity Provider**
2. Select **Generic OAuth2/OIDC**
```yaml
Provider Name: Authentik
Provider Type: OAuth2/OIDC
# Discovery
OIDC Discovery URL: https://auth.photon.obnh.io/application/o/pangolin/.well-known/openid-configuration
# Or manual configuration
Authorization Endpoint: https://auth.photon.obnh.io/application/o/authorize/
Token Endpoint: https://auth.photon.obnh.io/application/o/token/
Userinfo Endpoint: https://auth.photon.obnh.io/application/o/userinfo/
JWKS URI: https://auth.photon.obnh.io/application/o/pangolin/jwks/
# Credentials
Client ID: <from_authentik>
Client Secret: <from_authentik>
# Scopes
Scopes: openid profile email groups
# Claims Mapping
Username Claim: preferred_username
Email Claim: email
Display Name Claim: name
Groups Claim: groups
# Provisioning
Auto-provision Users: Enabled
Default Role: user
```
---
## 6. Production Deployment Checklist
### 6.1 Security Hardening
- [ ] Enable firewall with only required ports open (80, 443, 51820)
- [ ] Use strong passwords and rotate secrets regularly
- [ ] Enable 2FA/MFA for admin accounts
- [ ] Configure rate limiting in Traefik
- [ ] Enable HTTPS-only (HSTS headers)
- [ ] Implement fail2ban or CrowdSec for intrusion detection
- [ ] Regular security updates and patching
- [ ] Audit logs enabled and monitored
### 6.2 Backup Strategy
**Critical Data to Backup:**
- `/data/pangolin/` - Application data and database
- `/data/traefik/acme.json` - SSL certificates
- `/config/` - Configuration files
- Database dumps (if using PostgreSQL)
**Backup Script Example:**
```bash
#!/bin/bash
BACKUP_DIR="/backups/pangolin-$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Stop services
docker compose stop
# Backup data
tar -czf "$BACKUP_DIR/pangolin-data.tar.gz" /opt/pangolin/data/
tar -czf "$BACKUP_DIR/pangolin-config.tar.gz" /opt/pangolin/config/
# Backup database
docker exec pangolin-db pg_dump -U pangolin pangolin > "$BACKUP_DIR/database.sql"
# Start services
docker compose start
# Retain last 7 days
find /backups -name "pangolin-*" -mtime +7 -exec rm -rf {} \;
```
### 6.3 Monitoring
**Metrics to Monitor:**
- WireGuard tunnel status
- Active client connections
- Resource health checks
- Certificate expiration (Let's Encrypt)
- Disk space and memory usage
- Authentication failures
**Integration Options:**
- Prometheus + Grafana for metrics
- ELK stack for log aggregation
- Uptime monitoring (UptimeRobot, Healthchecks.io)
---
## 7. Common Use Cases
### 7.1 Home Lab Exposure
- Expose home services (Jellyfin, Home Assistant, Plex) without port forwarding
- Centralized authentication via Authentik
- SSL certificates automatically managed
### 7.2 Multi-Site Corporate Network
- Connect multiple office locations
- Centralized access control for all resources
- VPN access for remote employees
### 7.3 Hybrid Cloud Architecture
- Bridge on-premises and cloud resources
- Single pane of glass for access management
- Secure communication between cloud regions
---
## 8. Troubleshooting
### 8.1 Common Issues
**Newt Cannot Connect to Server:**
- Verify UDP port 51820 is open on firewall
- Check WireGuard kernel module: `lsmod | grep wireguard`
- Verify credentials are correct
- Check logs: `docker logs newt-site`
**SSL Certificate Issues:**
- Ensure DNS points to server IP
- Verify ports 80/443 are accessible
- Check Let's Encrypt rate limits
- Review Traefik logs: `docker logs traefik`
**Authentication Failures:**
- Verify IdP redirect URIs match exactly
- Check client ID/secret are correct
- Review Pangolin logs for OIDC errors
- Test IdP discovery URL is accessible
### 8.2 Logging
**Enable Debug Logging:**
```yaml
# In pangolin.yml
logging:
level: debug
format: json
outputs:
- type: file
path: /app/logs/pangolin.log
- type: stdout
```
**View Logs:**
```bash
# Pangolin
docker logs -f pangolin
# Newt
docker logs -f newt-site
# Traefik
docker logs -f traefik
```
---
## 9. Additional Resources
### Official Documentation
- Main Documentation: https://docs.pangolin.net/
- GitHub Repository: https://github.com/fosrl/pangolin
- Newt Client: https://github.com/fosrl/newt
- Community Discord: https://discord.gg/pangolin
### Integration Guides
- Authentik Integration: https://docs.goauthentik.io/integrations/services/pangolin/
- Authelia Integration: https://www.authelia.com/integration/openid-connect/clients/pangolin/
- CrowdSec Integration: https://www.crowdsec.net/blog/web-defense-with-pangolin-and-crowdsec
### Community Resources
- Unraid Deployment Guide: https://forums.unraid.net/topic/193431-secure-external-access-to-plex-more-using-pangolin-tunneled-reverse-proxy/
- Docker Compose Examples: https://github.com/fosrl/pangolin/blob/main/docker-compose.yml
- YouTube Tutorials: Search "Pangolin self-hosting" for video guides
---
## 10. Conclusion
Pangolin provides a robust, self-hosted alternative to managed tunneling services with enterprise-grade features:
**Key Advantages:**
- Complete data ownership and control
- Identity-aware access control with OIDC/OAuth2
- No port forwarding required on client networks
- Automatic SSL certificate management
- Centralized management for distributed resources
- Open-source and community-driven
**Ideal for:**
- Home lab enthusiasts requiring secure external access
- Organizations needing multi-site connectivity
- Teams requiring centralized identity management
- Privacy-conscious users avoiding third-party services
**Next Steps for Implementation:**
1. Provision VPS with required specifications
2. Configure DNS records for Pangolin domain
3. Run installation script or deploy via Docker Compose
4. Configure Authentik OIDC integration
5. Deploy Newt clients on target networks
6. Define resources and access policies
7. Test connectivity and authentication flows
8. Implement monitoring and backup strategies
This research provides a comprehensive foundation for deploying Pangolin as a self-hosted control plane integrated with Authentik for identity management.

View File

@@ -0,0 +1,348 @@
# Traefik Configuration Analysis
**Analysis Date:** 2025-01-20
**Source:** /srv/docker/traefik/
**Purpose:** Document Traefik configuration for Authentik and Pangolin integration
---
## Docker Network Configuration
### Primary Network
- **Network Name:** `traefik`
- **Type:** External bridge network
- **Subnet:** 172.19.0.0/16
- **Gateway:** 172.19.0.1
- **Network ID:** fffc2e33eb02203ed2ebd30725098d0ef8c7d5b0890fdf9a6d4aba5eb0784cc8
### Connected Containers
Currently attached services on the traefik network:
- traefik (172.19.0.3)
- transmission (172.19.0.2)
- openwebui (172.19.0.4)
- static-site-baumert-cc (172.19.0.5)
- static-sites (172.19.0.6)
- jellyfin (172.19.0.7)
**CRITICAL:** All services that need Traefik routing must be connected to the `traefik` external network.
---
## Certificate Resolver Configuration
### ACME Settings
- **Resolver Name:** `letsencrypt`
- **Challenge Type:** DNS-01 (Exoscale)
- **Provider:** exoscale
- **DNS Resolvers:** 1.1.1.1:53, 8.8.8.8:53
- **Delay Before Check:** 30s
- **ACME Email:** admin@obr.digital
- **Storage Path:** /letsencrypt/acme.json
### Environment Variables Required
```bash
EXOSCALE_API_KEY=EXO7929dee61aff39dce8dd104a
EXOSCALE_API_SECRET=vgfgnK3fmB-76v-YrZSQuu6Q_gBY6OMsOP_QuWWcr1A
```
**Note:** DNS-01 challenge allows wildcard certificates and works without requiring ports 80/443 to be publicly accessible during certificate issuance.
---
## Traefik Label Patterns
### Standard Service Pattern (Docker Labels)
Example from transmission service:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.transmission.rule=Host(`transmission.local.obr.digital`)"
- "traefik.http.routers.transmission.entrypoints=websecure"
- "traefik.http.routers.transmission.tls=true"
- "traefik.http.routers.transmission.tls.certresolver=letsencrypt"
- "traefik.http.routers.transmission.service=transmission"
- "traefik.http.services.transmission.loadbalancer.server.port=9091"
```
Example from openwebui service:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.openwebui.rule=Host(`openwebui.local.obr.digital`)"
- "traefik.http.routers.openwebui.entrypoints=websecure"
- "traefik.http.routers.openwebui.tls=true"
- "traefik.http.routers.openwebui.tls.certresolver=letsencrypt"
- "traefik.http.services.openwebui.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik"
```
### Label Pattern Template
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.<service-name>.rule=Host(`<domain>`)"
- "traefik.http.routers.<service-name>.entrypoints=websecure"
- "traefik.http.routers.<service-name>.tls=true"
- "traefik.http.routers.<service-name>.tls.certresolver=letsencrypt"
- "traefik.http.services.<service-name>.loadbalancer.server.port=<port>"
- "traefik.docker.network=traefik" # Required when service uses multiple networks
```
### Middleware Pattern (Basic Auth)
From Traefik dashboard configuration:
```yaml
labels:
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$Di.EvXJZ$$z3Tc1Oss4W3nx/enE0gk71"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
```
**Note:** Dollar signs must be doubled (`$$`) in docker-compose files to escape them properly.
---
## Entry Points
### Configured Entry Points
1. **web** (Port 80)
- Automatic redirect to HTTPS
- Permanent redirect (301)
- Scheme: https
2. **websecure** (Port 443)
- TLS enabled by default
- Primary entry point for all services
3. **metrics** (Port 8082)
- Prometheus metrics endpoint
- Internal only (not exposed in ports)
### Entry Point Configuration
```yaml
command:
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls=true"
```
---
## Dynamic Configuration Structure
### File Location
- **Path:** /srv/docker/traefik/traefik_dynamic.yaml
- **Mount:** Read-only volume in Traefik container
- **Provider:** File provider enabled in Traefik config
### Dynamic Configuration Pattern
The dynamic configuration file defines routers and services in YAML format:
```yaml
http:
routers:
<router-name>:
rule: "Host(`<domain>`)"
service: <service-name>
priority: 100
entryPoints:
- websecure
tls:
certResolver: letsencrypt
services:
<service-name>:
loadBalancer:
servers:
- url: "http://<container-name>:<port>"
```
### Current Dynamic Routes
The dynamic configuration currently handles:
- Static sites (1.obr.sh, fw.obr.sh, brn.obnh.io)
- Pi-hole admin (dns.obnh.io, dns.obnh.network) - routes to host.docker.internal:8080
- Transmission (tor.obnh.org, tor.obnh.network)
- OpenWebUI (ll.obr.sh)
- HTTPS fallback router (priority: 1)
### Service Backend Patterns
1. **Docker containers:** `http://<container-name>:<port>`
2. **Host services:** `http://host.docker.internal:<port>`
---
## Traefik Provider Configuration
### Docker Provider
```yaml
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik"
```
**Key Points:**
- Services must explicitly set `traefik.enable=true`
- Default network for label-based routing is `traefik`
- Docker socket mounted read-only for security
### File Provider
```yaml
command:
- "--providers.file.filename=/traefik_dynamic.yaml"
```
---
## API and Dashboard
### Dashboard Access
- **URL:** https://traefik.local.obr.digital
- **Authentication:** Basic Auth (admin:obr2024)
- **Router:** dashboard
- **Service:** api@internal
- **TLS:** Let's Encrypt certificate
### API Configuration
```yaml
command:
- "--api.dashboard=true"
- "--api.insecure=false"
```
---
## Recommendations for Authentik Integration
### 1. Network Configuration
Authentik services must be added to the `traefik` external network:
```yaml
networks:
- traefik
- authentik-internal # For PostgreSQL/Redis internal communication
```
### 2. Required Labels for Authentik Server
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.authentik.rule=Host(`auth.obr.digital`)"
- "traefik.http.routers.authentik.entrypoints=websecure"
- "traefik.http.routers.authentik.tls=true"
- "traefik.http.routers.authentik.tls.certresolver=letsencrypt"
- "traefik.http.services.authentik.loadbalancer.server.port=9000"
- "traefik.docker.network=traefik"
```
### 3. Forward Auth Middleware Pattern
For protecting services with Authentik SSO:
```yaml
labels:
- "traefik.http.middlewares.authentik.forwardauth.address=http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
- "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid"
- "traefik.http.routers.<service>.middlewares=authentik"
```
### 4. Network Declaration in docker-compose.yml
```yaml
networks:
traefik:
external: true
authentik-internal:
driver: bridge
```
---
## Recommendations for Pangolin Integration
### 1. Service Labels
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.pangolin.rule=Host(`pangolin.obr.digital`)"
- "traefik.http.routers.pangolin.entrypoints=websecure"
- "traefik.http.routers.pangolin.tls=true"
- "traefik.http.routers.pangolin.tls.certresolver=letsencrypt"
- "traefik.http.services.pangolin.loadbalancer.server.port=8000"
- "traefik.http.routers.pangolin.middlewares=authentik"
- "traefik.docker.network=traefik"
```
### 2. Network Configuration
```yaml
networks:
- traefik
- pangolin-internal # For PostgreSQL internal communication
```
---
## Security Considerations
### 1. Docker Socket Access
- Traefik has read-only access to Docker socket
- No containers can modify Docker state through Traefik
### 2. Exposed by Default
- `exposedbydefault=false` requires explicit opt-in via labels
- Prevents accidental service exposure
### 3. TLS Configuration
- All services force HTTPS via websecure entry point
- HTTP automatically redirects to HTTPS (permanent 301)
- Let's Encrypt certificates auto-renewed
### 4. Metrics
- Prometheus metrics on port 8082
- Not exposed externally in port mappings
- Can be accessed internally by monitoring containers
---
## Files Reference
### Configuration Files
1. `/srv/docker/traefik/docker-compose.yml` - Main Traefik service definition
2. `/srv/docker/traefik/traefik_dynamic.yaml` - Dynamic file-based configuration
3. `/srv/docker/traefik/.env` - Exoscale API credentials
4. `/srv/docker/traefik/letsencrypt/acme.json` - Certificate storage (auto-generated)
### Example Service Configurations
1. `/srv/docker/transmission/docker-compose.yml` - Full label-based routing
2. `/srv/docker/openwebui/docker-compose.yml` - Multi-network service with Traefik
3. `/srv/docker/static-site/docker-compose.yml` - Minimal service (uses dynamic config)
---
## Summary for Integration Tasks
### For TASK-003 (Authentik Deployment)
- Use external network: `traefik`
- Certificate resolver: `letsencrypt`
- Entry point: `websecure`
- Suggested domain: `auth.obr.digital`
- Required port: 9000 (Authentik server)
- Add `traefik.docker.network=traefik` label (multi-network scenario)
### For TASK-008 (Pangolin Deployment)
- Use external network: `traefik`
- Certificate resolver: `letsencrypt`
- Entry point: `websecure`
- Suggested domain: `pangolin.obr.digital`
- Required port: 8000 (typical Django/FastAPI port)
- Apply Authentik forward auth middleware
- Add `traefik.docker.network=traefik` label (multi-network scenario)
### Network Connectivity
Both services must declare:
```yaml
networks:
traefik:
external: true
```
This ensures they can communicate with Traefik and be discovered by its Docker provider.