# Security Implementation Plan: Google OAuth SSO for Homelab Infrastructure **Version:** 1.1 (Revised Scope) **Date:** 2025-11-15 **Status:** Research & Planning Phase **Revision:** Focused scope on external-facing services, FastAPI native OIDC for core-api ## Executive Summary This document outlines a comprehensive security architecture to implement Single Sign-On (SSO) across all homelab services using Google OAuth (Google Workspace + Gmail accounts). The proposed solution uses **Authentik** as a central Identity Provider (IdP) that federates with Google for authentication, then provides OIDC/SAML/LDAP to downstream services. **Critical Security Gap Identified:** The core-api service currently has **NO authentication**, making it a severe security risk if exposed publicly. This must be addressed immediately as part of the SSO implementation. **Key Benefits:** - Single login for all services using Google accounts - Centralized user management and access control - Support for both Google Workspace and Gmail accounts - Eliminates password fatigue and scattered credentials - Enables secure public exposure of services via api.schweitz.net - Provides audit trail and access logging --- ## 0. Scope Definition & Architecture Decision ### 0.1 Implementation Scope This plan focuses **only** on user-facing services with external domain mappings, plus critical components requiring public access: **✅ IN SCOPE:** - **core-api** (api.schweitz.net) - CRITICAL: Currently no auth, public exposure needed - **Nextcloud** (cloud.schweitz.net) - File storage and collaboration - **Jellyfin** (media.schweitz.net) - Media server - **Gitea** (git.schweitz.net) - Git hosting - **Open WebUI** (ai.schweitz.net) - AI interface - **Organizr** (home.schweitz.net) - Unified dashboard - **code-server** (code.schweitz.net) - VS Code in browser (host service) **❌ OUT OF SCOPE:** - **Infrastructure tools** (Portainer, Uptime Kuma, Netdata, Headscale, Watchtower) - Accessible via core-api endpoints if needed, no direct public exposure required - **Data providers** (Ollama, Qdrant, future stack backends) - Internal-only services - **Local services** (Samba, NPM admin) - LAN-only access **Rationale:** - Infrastructure tools can be proxied through authenticated core-api endpoints when needed - Reduces implementation complexity and attack surface - Focuses SSO on end-user services with clear external access requirements - Maintains security without over-engineering ### 0.2 Core-API Authentication Strategy: FastAPI Native OIDC **Decision:** Implement OIDC token validation **directly in FastAPI** rather than NPM forward auth. **Advantages:** - Native FastAPI dependency injection (`Depends`) - Fine-grained endpoint-level authorization - Better integration with API documentation (OpenAPI/Swagger) - No reliance on HTTP headers from proxy - Standard OAuth2 bearer token authentication - Easier to test and maintain **Implementation:** ```python from fastapi import Depends, Security, HTTPException from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import jwt from jwt import PyJWKClient # OIDC configuration from Authentik OIDC_ISSUER = "https://auth.schweitz.net/application/o/core-api/" JWKS_URL = f"{OIDC_ISSUER}/jwks/" security = HTTPBearer() jwks_client = PyJWKClient(JWKS_URL) async def get_current_user( credentials: HTTPAuthorizationCredentials = Security(security) ) -> dict: """Validate OIDC token and return user claims""" token = credentials.credentials try: signing_key = jwks_client.get_signing_key_from_jwt(token) payload = jwt.decode( token, signing_key.key, algorithms=["RS256"], audience="core-api", issuer=OIDC_ISSUER ) return payload except jwt.InvalidTokenError as e: raise HTTPException(status_code=401, detail="Invalid authentication") # Usage in endpoints @app.get("/infrastructure/services") async def get_services(user: dict = Depends(get_current_user)): # user contains email, name, groups from token return services ``` **Public Endpoints:** - Health check (`/health`) - Unauthenticated - OpenAPI docs (`/docs`, `/openapi.json`) - Unauthenticated (or optionally protected) - Static files (`/static/*`) - Protected via NPM forward auth OR require auth token **Protected Endpoints:** - All `/infrastructure/*` - Require valid OIDC token - All `/v1/*` (AI endpoints) - Require valid OIDC token --- ## 1. Current State: Service Inventory & Authentication Capabilities ### 1.1 In-Scope Services (External-Facing) | Service | Type | Port | Domain | Current Auth | SSO Support | Integration Method | |---------|------|------|--------|--------------|-------------|-------------------| | **core-api** | API | 8083 | api.schweitz.net | **NONE** ❌ | ✅ | FastAPI native OIDC | | **Nextcloud** | App | 8082 | cloud.schweitz.net | MariaDB | ✅ | OIDC (user_oidc app) | | **Jellyfin** | App | 8096 | media.schweitz.net | Built-in | ✅ | OIDC (sso plugin) | | **Gitea** | App | 3002 | git.schweitz.net | PostgreSQL | ✅ | OIDC (native) | | **Open WebUI** | App | 82 | ai.schweitz.net | Built-in | ✅ | OIDC (native) | | **Organizr** | Dashboard | 9999 | home.schweitz.net | Built-in | ⚠️ | Forward auth (NPM) | | **code-server** | IDE | 8084 | code.schweitz.net | Password | ⚠️ | Forward auth (NPM) | **Legend:** - ✅ Native OIDC/OAuth support - ⚠️ Proxy-based authentication (forward auth via NPM) - ❌ No authentication (critical security gap) ### 1.2 Out-of-Scope Services These services remain accessible on local network or via core-api authenticated endpoints: | Service | Type | Port | Reason Out of Scope | |---------|------|------|---------------------| | **Portainer** | Infrastructure | 8080 | Admin tool - accessible via core-api proxy if needed | | **Uptime Kuma** | Monitoring | 3001 | Admin tool - accessible via core-api proxy if needed | | **Netdata** | Monitoring | 19999 | Admin tool - accessible via core-api proxy if needed | | **Headscale** | VPN | 8085 | VPN control plane, no web auth needed | | **NPM** | Infrastructure | 8000 | LAN admin access only | | **Ollama** | AI Backend | 11434 | Internal API, no direct access | | **Qdrant** | Database | 6333 | Internal vector DB, no direct access | | **Samba** | File Share | 445 | LAN-only SMB service | | **Watchtower** | Automation | - | Background service, no UI | ### 1.3 Current Security Posture **Strengths:** - Most services isolated behind NPM reverse proxy - VPN access available via Headscale for infrastructure tools - Core infrastructure tools not publicly exposed **Critical Weaknesses:** - **core-api has ZERO authentication** (blocks api.schweitz.net public exposure) - **code-server uses basic password** (weak auth for IDE access) - Password sprawl across 7 different user-facing services - No centralized authentication or session management - No unified access control or audit logging - User management scattered across multiple databases (MariaDB, PostgreSQL, local files) --- ## 2. Security Requirements & Goals ### 2.1 Primary Objectives 1. **Google OAuth Integration** - Support Google Workspace accounts (e.g., user@schweitz.net) - Support Gmail accounts (e.g., user@gmail.com) - Single source of truth for user identities 2. **Centralized Access Control** - Manage user access from one location - Role-based access control (RBAC) per service - Easy user onboarding/offboarding 3. **Secure Public Access** - Enable safe exposure of core-api at api.schweitz.net - Protect service control widget and infrastructure APIs - Maintain security for publicly accessible services 4. **Minimal User Friction** - One-click login via Google - Session management across services - Mobile-friendly authentication ### 2.2 Security Standards - **Authentication:** OAuth 2.0 / OIDC with Google as IdP - **Authorization:** RBAC with service-level granularity - **Session Management:** Secure cookie handling, configurable timeouts - **Transport Security:** TLS 1.3 via Let's Encrypt (already configured in NPM) - **Audit Logging:** Authentication events, access attempts, authorization decisions --- ## 3. SSO Solution Evaluation ### 3.1 Solution Comparison Matrix | Criteria | Authelia | Authentik | Keycloak | |----------|----------|-----------|----------| | **Architecture** | Forward auth proxy | Full IdP | Enterprise IdP | | **Resource Usage** | Low (~100MB RAM) | Medium (~300MB RAM) | High (1-2GB RAM) | | **Setup Complexity** | Simple | Moderate | Complex | | **Google as Auth Source** | ❌ **NO** | ✅ **YES** | ✅ **YES** | | **Acts as OIDC Provider** | ✅ YES | ✅ YES | ✅ YES | | **LDAP Backend** | LDAP only | Multiple | Multiple | | **User Database** | File/LDAP | PostgreSQL | PostgreSQL/MySQL | | **UI Quality** | Minimal | Modern | Enterprise | | **NPM Integration** | Excellent | Good | Good | | **Community Support** | Large | Growing | Massive | | **Homelab Suitability** | ⚠️ High* | ✅ Excellent | ⚠️ Overkill | *Authelia is excellent for homelabs, but **cannot use Google as the authentication source** - it requires its own user database (file-based or LDAP). ### 3.2 Detailed Analysis #### Authelia **Strengths:** - Lightweight and fast - Perfect NPM integration via forward auth - Simple configuration (YAML files) - Low resource usage - Excellent for homelab scale **Critical Limitation:** - **Cannot federate with Google OAuth** - only acts as an OIDC provider - Requires separate user database (LDAP or file-based) - Would need to manually sync Google users to LDAP - Does not meet requirement for "login with Google" **Verdict:** ❌ Does not meet core requirement (Google as auth source) #### Authentik **Strengths:** - **Can use Google as authentication source** (meets requirement!) - Modern Python-based architecture - Beautiful, intuitive UI - Flow-based configuration (flexible auth/authz journeys) - Acts as OIDC/SAML provider for downstream services - Good documentation and active community - Reasonable resource usage (~300-500MB) - Docker-native, homelab-friendly **Limitations:** - More complex than Authelia - Requires PostgreSQL database - NPM integration via OIDC (not forward auth) **Verdict:** ✅ **RECOMMENDED** - Best fit for requirements #### Keycloak **Strengths:** - Battle-tested enterprise solution - Most comprehensive feature set - Extensive protocol support - Can federate with Google - Massive ecosystem **Limitations:** - Heavy resource usage (1-2GB RAM minimum) - Complex UI and configuration - Overkill for homelab scale - Steeper learning curve **Verdict:** ⚠️ Viable but excessive for homelab ### 3.3 Recommendation: Authentik **Authentik** is the optimal choice because: 1. **Meets Core Requirement:** Supports Google OAuth as authentication source 2. **Right-Sized:** Not too simple (Authelia), not too complex (Keycloak) 3. **Modern Architecture:** Python-based, active development, good docs 4. **Flexible Integration:** Can provide OIDC, SAML, LDAP, and proxy auth 5. **User Experience:** Clean UI for both admins and end-users 6. **Resource Efficient:** ~300-500MB RAM is acceptable for homelab --- ## 4. Recommended Architecture ### 4.1 High-Level Design ``` ┌─────────────────────────────────────────────────────────────┐ │ Internet (Public) │ └─────────────────────┬───────────────────────────────────────┘ │ ┌────────────▼────────────┐ │ Google OAuth 2.0 │ ← User: "Sign in with Google" │ (accounts.google.com) │ (Workspace + Gmail accounts) └────────────┬────────────┘ │ OAuth tokens ┌────────────▼────────────┐ │ Authentik IdP │ ← Central identity provider │ (auth.schweitz.net) │ - Validates Google auth │ │ - Manages users & groups │ PostgreSQL + Redis │ - Issues OIDC tokens & JWTs └────────┬─────────────────┘ │ │ OIDC tokens / Forward auth │ ┌───────────┼───────────────────────────────────┐ │ │ │ ┌─────▼─────┐ ┌──▼────────────┐ ┌────────────▼──────┐ │ NPM │ │ core-api │ │ User Apps (OIDC) │ │ Gateway │ │ (FastAPI) │ │ │ │ │ │ │ │ - Nextcloud │ │ Routes: │ │ Bearer Token │ │ - Jellyfin │ │ *.schweitz │ Validation ────┤ │ - Gitea │ │ .net │ │ (python-jose) │ │ - Open WebUI │ │ │ └───────────────┘ └───────────────────┘ │ │ │ Forward │ ┌───────────────────────────┐ │ Auth ├────────► Proxy Auth Services │ │ (Authent │ │ - Organizr (dashboard) │ │ ik) │ │ - code-server (host) │ │ │ └───────────────────────────┘ └───────────┘ Legend: ━━━ Direct OIDC/OAuth ─── HTTP Proxy ╌╌╌ Forward Auth ``` ### 4.2 Authentication Flow **Initial Login:** 1. User visits `home.schweitz.net` (or any protected service) 2. NPM/Service redirects to Authentik: `auth.schweitz.net` 3. Authentik shows "Sign in with Google" button 4. User authenticates with Google (Workspace or Gmail) 5. Google returns OAuth token to Authentik 6. Authentik creates/updates user profile, issues OIDC token 7. User redirected back to original service with session cookie 8. Service validates OIDC token, grants access **Subsequent Access:** - Session cookie valid for configurable duration (e.g., 8 hours) - No re-authentication required within session - Logout clears all service sessions ### 4.3 Component Architecture #### New Components to Deploy 1. **Authentik Stack** (new) - `authentik-server`: Main application server - `authentik-worker`: Background tasks - `postgresql`: User database - `redis`: Cache and message queue - Resources: ~500MB RAM, 1 CPU, 5GB SSD - Port: 9000 (internal), exposed via NPM 2. **NPM Configuration Updates** - Add proxy host: `auth.schweitz.net` → `authentik-server:9000` - Add forward auth snippet for protected services - Configure SSL certificates (Let's Encrypt) #### Integration Patterns **Pattern A: FastAPI Native OIDC** (core-api only) - Service: core-api - Integration: Direct JWT validation in FastAPI using python-jose - Session: Stateless bearer token authentication - Benefits: Fine-grained endpoint control, API-first security, OpenAPI integration **Pattern B: Native OIDC** (Preferred for user apps) - Services: Nextcloud, Gitea, Jellyfin, Open WebUI - Integration: Configure OIDC client in Authentik, add OIDC provider in service - Session: Service manages its own session after OIDC login - Benefits: Native integration, best UX, full feature support **Pattern C: Forward Auth via NPM** (For non-OIDC services) - Services: Organizr, code-server - Integration: NPM/Authentik proxy checks auth before forwarding request - Session: Handled by Authentik via cookies - Benefits: Works with any service, no code changes needed --- ## 5. Service-by-Service Integration Plan ### 5.1 Tier 1: Critical Infrastructure (Week 1) #### 5.1.1 Authentik Deployment **Tasks:** 1. Create `stacks/authentik.yml` with PostgreSQL + Redis + Authentik 2. Deploy stack via Portainer 3. Access initial setup at `http://localhost:9000` 4. Configure: - Admin account - Email settings (optional) - Brand customization **Success Criteria:** - Authentik accessible via NPM at `auth.schweitz.net` - Admin portal functional - Health checks passing #### 5.1.2 Google OAuth Source Configuration **Tasks:** 1. Create Google Cloud Project (or use existing) 2. Enable OAuth consent screen - App name: "Homelab SSO" - Support email: admin email - Scopes: `openid`, `email`, `profile` - Authorized domains: `schweitz.net` 3. Create OAuth 2.0 credentials - Authorized redirect URIs: `https://auth.schweitz.net/source/oauth/callback/google/` 4. Configure Google source in Authentik - Provider: Google - Client ID: from Google Cloud - Client Secret: from Google Cloud - Scopes: `openid email profile` **Testing:** 1. Test login with Google Workspace account 2. Test login with Gmail account 3. Verify user profile attributes synced 4. Confirm logout works #### 5.1.3 core-api Protection (CRITICAL) **Current Risk:** core-api at port 8083 has **zero authentication**. Cannot expose publicly without SSO. **Approach:** FastAPI Native OIDC (OAuth2 Bearer Token) **Implementation:** 1. **Create Authentik OIDC Provider** - In Authentik: Applications → Create - Name: "Core API" - Slug: `core-api` - Provider type: OAuth2/OIDC - Client type: `Confidential` - Client ID: (auto-generated, save for later) - Client Secret: (auto-generated, save securely) - Redirect URIs: `https://api.schweitz.net/auth/callback` (for web flows if needed) - Signing Key: Auto (Authentik default) - Subject mode: `Based on User's Email` - Include claims in ID token: ✅ - Scopes: `openid`, `email`, `profile` 2. **Install Python Dependencies** (add to `requirements.txt`): ```txt PyJWT[crypto]==2.8.0 python-jose[cryptography]==3.3.0 ``` 3. **Create Authentication Module** (`src/auth/oidc.py`): ```python from fastapi import Depends, HTTPException, Security from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from jose import jwt, jwk from jose.utils import base64url_decode import httpx from functools import lru_cache from typing import Dict, Optional from src.config import get_settings from src.logging_config import get_logger logger = get_logger(__name__) settings = get_settings() security = HTTPBearer(auto_error=False) # OIDC Configuration OIDC_ISSUER = settings.oidc_issuer # "https://auth.schweitz.net/application/o/core-api/" JWKS_URI = f"{OIDC_ISSUER}jwks/" @lru_cache() def get_jwks() -> Dict: """Fetch JWKS from Authentik (cached)""" try: response = httpx.get(JWKS_URI, timeout=10) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Failed to fetch JWKS: {e}") raise HTTPException(status_code=500, detail="Auth configuration error") async def get_current_user( credentials: Optional[HTTPAuthorizationCredentials] = Security(security) ) -> Dict: """ Validate OIDC token from Authorization: Bearer header Returns: User claims (email, name, groups, etc.) Raises: HTTPException 401 if token invalid/missing """ if not credentials: raise HTTPException( status_code=401, detail="Missing authentication token", headers={"WWW-Authenticate": "Bearer"}, ) token = credentials.credentials try: # Decode header to get key ID unverified_header = jwt.get_unverified_header(token) kid = unverified_header.get("kid") # Find matching key in JWKS jwks = get_jwks() rsa_key = None for key in jwks.get("keys", []): if key.get("kid") == kid: rsa_key = key break if not rsa_key: raise HTTPException(status_code=401, detail="Invalid token key") # Verify and decode token payload = jwt.decode( token, rsa_key, algorithms=["RS256"], audience=settings.oidc_audience, # "core-api" issuer=OIDC_ISSUER, ) logger.info(f"Authenticated user: {payload.get('email')}") return payload except jwt.ExpiredSignatureError: raise HTTPException(status_code=401, detail="Token expired") except jwt.JWTClaimsError: raise HTTPException(status_code=401, detail="Invalid token claims") except Exception as e: logger.error(f"Token validation error: {e}") raise HTTPException(status_code=401, detail="Invalid authentication token") async def get_admin_user(user: Dict = Depends(get_current_user)) -> Dict: """Require admin group membership""" groups = user.get("groups", []) if "admin" not in groups: raise HTTPException(status_code=403, detail="Admin access required") return user ``` 4. **Update `src/config.py`** (add OIDC settings): ```python class Settings(BaseSettings): # ... existing settings ... # OIDC Authentication oidc_issuer: str = "https://auth.schweitz.net/application/o/core-api/" oidc_audience: str = "core-api" oidc_enabled: bool = True # Set False to disable auth (dev mode) ``` 5. **Protect Endpoints** (update controllers): ```python from src.auth.oidc import get_current_user, get_admin_user # Public endpoints (no change needed) @router.get("/health") async def health_check(): return {"status": "healthy"} # Protected endpoints (add dependency) @router.get("/infrastructure/services") async def get_services(user: Dict = Depends(get_current_user)): logger.info(f"User {user['email']} fetching services") return services # Admin-only endpoints @router.post("/infrastructure/services/{name}/stop") async def stop_service(name: str, user: Dict = Depends(get_admin_user)): logger.info(f"Admin {user['email']} stopping {name}") # ... implementation ``` 6. **Update OpenAPI Documentation** (`src/main.py`): ```python from fastapi.security import OAuth2AuthorizationCodeBearer oauth2_scheme = OAuth2AuthorizationCodeBearer( authorizationUrl=f"{settings.oidc_issuer}authorize/", tokenUrl=f"{settings.oidc_issuer}token/", scopes={"openid": "OpenID Connect", "email": "Email", "profile": "Profile"} ) app = FastAPI( # ... existing config ... swagger_ui_init_oauth={ "clientId": settings.oidc_client_id, "appName": "Core API", "usePkceWithAuthorizationCodeGrant": True, } ) ``` 7. **Configure NPM Proxy** (simple passthrough): - Domain: `api.schweitz.net` - Forward to: `core-api:8083` - SSL: Let's Encrypt - No forward auth needed (handled by FastAPI) 8. **Update Widget** (`static/widgets/service-control.html`): ```javascript // Redirect to Authentik login, then get token async function getAuthToken() { // Check if token in localStorage let token = localStorage.getItem('oidc_token'); if (token && !isTokenExpired(token)) { return token; } // Redirect to Authentik login page const authUrl = 'https://auth.schweitz.net/application/o/core-api/'; window.location.href = authUrl; } // Include token in API calls async function fetchServices() { const token = await getAuthToken(); const response = await fetch(`${API_BASE}/infrastructure/services`, { headers: { 'Authorization': `Bearer ${token}` } }); // ... } ``` **Success Criteria:** - ✅ Unauthenticated requests return 401 with proper error - ✅ Valid OIDC token grants access to protected endpoints - ✅ Token validation uses Authentik JWKS - ✅ User email/groups available in endpoints - ✅ OpenAPI docs show authentication requirement - ✅ Widget works with OAuth flow ### 5.2 Tier 2: User-Facing Services (Week 2) #### 5.2.1 Nextcloud (OIDC) **Plugin:** `user_oidc` (official Nextcloud app) **Configuration:** 1. Install app: Apps → Search "OpenID Connect" → Install 2. Create Authentik OIDC Provider - Client type: Confidential - Redirect URIs: `https://cloud.schweitz.net/apps/user_oidc/code` - Scopes: `openid`, `email`, `profile` 3. Configure in Nextcloud - Settings → OpenID Connect - Add provider: Authentik - Discovery URL: `https://auth.schweitz.net/application/o/nextcloud/.well-known/openid-configuration` - Client ID: from Authentik - Client Secret: from Authentik **Testing:** - Verify "Login with Authentik" button appears - Test new user login creates Nextcloud account - Test existing Nextcloud users can link Google accounts - Verify file access preserved after OIDC login #### 5.2.2 Gitea (OIDC) **Native Support:** Gitea has built-in OIDC **Configuration:** 1. Create Authentik OIDC Provider for Gitea - Redirect URI: `https://git.schweitz.net/user/oauth2/authentik/callback` 2. In Gitea Admin → Authentication Sources - Add authentication source - Type: OAuth2 - Provider: OpenID Connect - Client ID: from Authentik - Client Secret: from Authentik - Auto Discovery URL: `https://auth.schweitz.net/application/o/gitea/.well-known/openid-configuration` **Testing:** - Test new user registration via Google - Test existing user account linking - Test SSH key management post-OIDC - Test repository access #### 5.2.3 Jellyfin (SSO Plugin) **Plugin:** `jellyfin-plugin-sso` (community plugin) **Configuration:** 1. Add plugin repository: - URL: `https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json` 2. Install SSO plugin from catalog 3. Create Authentik OIDC Provider for Jellyfin - Redirect URI: `https://media.schweitz.net/sso/OID/redirect/authentik` 4. Configure plugin: - Settings → Plugins → SSO Authentication - Provider: OIDC - Client ID/Secret from Authentik - Discovery URL: `https://auth.schweitz.net/application/o/jellyfin/.well-known/openid-configuration` **Testing:** - Test Google login creates Jellyfin user - Test viewing permissions inherited - Test continue watching preserved - Test mobile app compatibility #### 5.2.4 Open WebUI (Native Google OAuth) **Note:** Open WebUI supports Google OAuth directly, but switching to Authentik provides unified management. **Configuration:** 1. Create Authentik OIDC Provider 2. Update `open-webui.yml` environment: ```yaml - ENABLE_OAUTH=true - OAUTH_PROVIDER_NAME=Google (via Authentik) - OPENID_PROVIDER_URL=https://auth.schweitz.net/application/o/open-webui/.well-known/openid-configuration - OAUTH_CLIENT_ID= - OAUTH_CLIENT_SECRET= - OAUTH_SCOPES=openid email profile ``` 3. Restart container **Testing:** - Test login with Google - Test conversation history preserved - Test model access unchanged - Test RAG functionality ### 5.3 Tier 3: Dashboard & Development Tools (Week 3) #### 5.3.1 Organizr (Forward Auth) **Note:** Organizr doesn't support OIDC natively. Use trusted header authentication. **Configuration:** 1. Create Authentik Proxy Provider - External host: `https://home.schweitz.net` - Internal host: `http://organizr:80` - Forward auth mode: Single application 2. Configure NPM with Authentik forward auth snippet 3. Organizr settings: - Settings → System Settings → Authentication - Enable "Auth Proxy" - Header name: `X-Authentik-Username` - Auto-create users: Yes **Testing:** - Test redirect to Authentik on access - Test user creation from header - Test tab access after login - Test iframe embedding still works #### 5.3.2 code-server (Forward Auth + Host Service) **Current State:** Running as systemd service on host at `127.0.0.1:8084` **Note:** code-server is a host service (not containerized). Currently uses password authentication. **Approach:** Disable built-in auth, use Authentik forward auth via NPM **Configuration:** 1. **Disable code-server Password Auth** - Edit config: `~/.config/code-server/config.yaml` ```yaml bind-addr: 127.0.0.1:8084 auth: none # Changed from 'password' cert: false user-data-dir: /home/jpmschweitzer/docker-data/code-server/user-data extensions-dir: /home/jpmschweitzer/docker-data/code-server/extensions ``` - Restart service: `sudo systemctl restart code-server` 2. **Create Authentik Proxy Provider** - In Authentik: Applications → Create - Name: "Code Server" - Slug: `code-server` - Provider type: Proxy Provider - External host: `https://code.schweitz.net` - Internal host: `http://127.0.0.1:8084` - Forward auth mode: Single application - Authorization flow: (default) 3. **Configure NPM Proxy Host** - Domain: `code.schweitz.net` - Scheme: `http` - Forward Hostname/IP: `192.168.86.149` (tower-of-joy IP) - Forward Port: `8084` - SSL: Let's Encrypt - Websockets: ✅ Enabled (required for VS Code) - Advanced config (add Authentik forward auth snippet): ```nginx # Authentik Forward Auth auth_request /outpost.goauthentik.io/auth/nginx; error_page 401 = @goauthentik_proxy_signin; auth_request_set $auth_cookie $upstream_http_set_cookie; add_header Set-Cookie $auth_cookie; # Pass authentication headers auth_request_set $authentik_username $upstream_http_x_authentik_username; auth_request_set $authentik_groups $upstream_http_x_authentik_groups; auth_request_set $authentik_email $upstream_http_x_authentik_email; auth_request_set $authentik_name $upstream_http_x_authentik_name; auth_request_set $authentik_uid $upstream_http_x_authentik_uid; proxy_set_header X-authentik-username $authentik_username; proxy_set_header X-authentik-groups $authentik_groups; proxy_set_header X-authentik-email $authentik_email; proxy_set_header X-authentik-name $authentik_name; proxy_set_header X-authentik-uid $authentik_uid; location @goauthentik_proxy_signin { internal; add_header Set-Cookie $auth_cookie; return 302 /outpost.goauthentik.io/start?rd=$request_uri; } location /outpost.goauthentik.io { proxy_pass https://auth.schweitz.net/outpost.goauthentik.io; proxy_set_header Host $host; proxy_set_header X-Original-URL $scheme://$http_host$request_uri; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Uri $request_uri; proxy_set_header X-Forwarded-Ssl on; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_pass_request_body off; proxy_set_header Content-Length ""; } ``` 4. **Deploy Authentik Outpost** (if not already deployed) - In Authentik: Outposts → Create - Name: "NPM Proxy Outpost" - Type: Proxy - Integration: Docker (or manual) - Applications: Select "Code Server" and "Organizr" **Testing:** - ✅ Access `https://code.schweitz.net` redirects to Authentik login - ✅ After Google authentication, VS Code loads - ✅ Workspace and extensions preserved - ✅ Terminal access works - ✅ File editing and Git integration functional - ✅ Websocket connection stable (check browser console) - ✅ Session persists across page reloads **Security Notes:** - code-server has NO auth when accessed via `http://localhost:8084` on the host - Ensure host firewall blocks external access to port 8084 - Only accessible via `code.schweitz.net` through authenticated NPM proxy - UFW rule: `sudo ufw deny 8084/tcp` (block external access) --- ## 6. Security Considerations ### 6.1 Authentication Security **Google OAuth Security:** - OAuth 2.0 with PKCE (Proof Key for Code Exchange) - Tokens expire per Google's policy (typically 1 hour) - Refresh tokens handled by Authentik - No passwords stored in homelab **Session Management:** - Configurable session timeout (recommended: 8 hours) - Secure, HttpOnly, SameSite cookies - Session invalidation on logout - Concurrent session limits (optional) **Multi-Factor Authentication:** - Enforced at Google level (Google Workspace admin can require 2FA) - Optional: Enable Authentik 2FA as second layer - Supports TOTP, WebAuthn, Duo ### 6.2 Authorization & Access Control **Role-Based Access Control (RBAC):** - Define groups in Authentik (e.g., `admin`, `family`, `guest`) - Map Google Workspace groups to Authentik groups - Per-service authorization policies - Attribute-based access control (ABAC) for advanced scenarios **Service-Level Permissions:** - Portainer: Admin vs User roles - Nextcloud: File permissions via groups - Jellyfin: Library access per user - Gitea: Repository permissions ### 6.3 Network Security **Public Exposure:** - Only NPM and Authentik accessible from internet - All other services behind reverse proxy - Optional: Firewall rules to restrict by geo/IP - Rate limiting on authentication endpoints **Internal Security:** - Services communicate via Docker networks - core-api only accessible via authenticated NPM proxy - Ollama/Qdrant remain internal-only (no public access) **SSL/TLS:** - Let's Encrypt certificates via NPM - TLS 1.3 minimum - HSTS headers enabled - Certificate auto-renewal ### 6.4 Data Privacy **User Data:** - Email and profile from Google (read-only) - No passwords stored - User attributes cached in Authentik database - GDPR compliance: Users can request data deletion **Logging & Auditing:** - Authentication attempts logged - Failed login alerts - Access logs per service - Retention policy (recommend 90 days) **Secrets Management:** - Client secrets stored in Authentik database (encrypted at rest) - Database backups encrypted - Secrets not committed to git - Consider: HashiCorp Vault for advanced scenarios ### 6.5 Backup & Recovery **Critical Data to Backup:** 1. Authentik PostgreSQL database (user profiles, OIDC clients) 2. Authentik configuration (flows, policies, providers) 3. NPM configuration (proxy hosts, SSL certs) 4. Service-specific user databases (if not using OIDC exclusively) **Disaster Recovery:** - Document OIDC client configurations for each service - Export Authentik flows and policies - Keep Google OAuth credentials in secure vault - Test restore procedure quarterly --- ## 7. Implementation Phases ### Phase 1: Foundation (Week 1) **Goal:** Deploy Authentik, configure Google OAuth, protect core-api **Tasks:** - [ ] Deploy Authentik stack (server, worker, PostgreSQL, Redis) - [ ] Configure NPM proxy: `auth.schweitz.net` - [ ] Set up Google Cloud OAuth credentials - [ ] Configure Google as Authentik source - [ ] Test login with Google Workspace account - [ ] Test login with Gmail account - [ ] Create core-api proxy provider - [ ] Configure NPM forward auth for `api.schweitz.net` - [ ] Test widget access with authentication - [ ] Document OIDC client creation process **Success Criteria:** - ✅ Authentik accessible and functional - ✅ Google login works for both account types - ✅ core-api protected and publicly accessible - ✅ Service control widget requires auth **Rollback Plan:** - Remove `api.schweitz.net` NPM proxy - Keep core-api on internal port only - Destroy Authentik stack - No impact on existing services ### Phase 2: User Services (Week 2) **Goal:** Integrate user-facing applications (Nextcloud, Gitea, Jellyfin, Open WebUI) **Tasks:** - [ ] Install Nextcloud OIDC app - [ ] Create Authentik OIDC provider for Nextcloud - [ ] Configure and test Nextcloud SSO - [ ] Configure Gitea OIDC authentication - [ ] Test Gitea login and repository access - [ ] Install Jellyfin SSO plugin - [ ] Configure Jellyfin OIDC - [ ] Test Jellyfin media playback with SSO - [ ] Update Open WebUI environment for Authentik - [ ] Test Open WebUI AI conversations with SSO **Success Criteria:** - ✅ All 4 services support Google login - ✅ New users auto-created on first login - ✅ Service functionality unchanged post-SSO - ✅ Sessions persist appropriately **Rollback Plan:** - Disable OIDC providers in Authentik - Re-enable native auth in each service - Users revert to local passwords - No data loss ### Phase 3: Admin Tools (Week 3) **Goal:** Secure admin interfaces (Portainer, Organizr, Uptime Kuma, Netdata) **Tasks:** - [ ] Configure Portainer OAuth - [ ] Test Portainer admin access via Google - [ ] Set up Organizr trusted header auth - [ ] Configure NPM forward auth for Organizr - [ ] Test Organizr tab functionality - [ ] Disable Uptime Kuma internal auth - [ ] Configure forward auth for status.schweitz.net - [ ] Test monitor creation and access - [ ] Configure Netdata forward auth - [ ] Test metrics access and real-time graphs **Success Criteria:** - ✅ All admin tools require Google authentication - ✅ Functionality preserved - ✅ Unauthorized access blocked **Rollback Plan:** - Re-enable native auth in services - Remove forward auth from NPM - Services remain accessible on local network ### Phase 4: Hardening & Documentation (Week 4) **Goal:** Finalize security, monitoring, and documentation **Tasks:** - [ ] Configure Authentik flows (MFA, password policies) - [ ] Set up Authentik groups (admin, family, guest) - [ ] Define per-service authorization policies - [ ] Enable audit logging across all services - [ ] Set up alerts for failed auth attempts - [ ] Configure session timeouts - [ ] Test logout across all services - [ ] Document user onboarding process - [ ] Create troubleshooting guide - [ ] Backup Authentik configuration - [ ] Test disaster recovery procedure **Success Criteria:** - ✅ Security policies enforced - ✅ Monitoring and alerting operational - ✅ Documentation complete - ✅ Backup/restore tested --- ## 8. Alternative Approaches ### 8.1 Authelia + LDAP (Google Workspace Directory Sync) **Architecture:** - Use Google Workspace Directory Sync or third-party tool - Sync Google users to OpenLDAP or lldap - Authelia authenticates against LDAP - Services use Authelia for forward auth or LDAP directly **Pros:** - Authelia is lighter weight than Authentik - Simple reverse proxy integration - Lower resource usage **Cons:** - Cannot directly "log in with Google" (not true SSO) - Requires separate sync mechanism (complexity) - Password management still needed (defeats purpose) - No single-click login experience - User changes not real-time **Verdict:** ❌ Rejected - Doesn't meet "login with Google" requirement ### 8.2 Keycloak **Pros:** - Enterprise-grade, battle-tested - Comprehensive features - Excellent Google federation **Cons:** - Heavy resource usage (1-2GB RAM) - Overkill for homelab scale - Complex configuration **Verdict:** ⚠️ Viable but excessive ### 8.3 Service-by-Service Google OAuth **Architecture:** - Configure Google OAuth directly in each service - No central IdP - Each service manages its own sessions **Pros:** - No additional infrastructure - Direct Google integration **Cons:** - No centralized access control - Each service needs separate OAuth client - Inconsistent user experience - Cannot protect services without native OAuth (core-api, Organizr, etc.) - No unified session management - User management scattered **Verdict:** ❌ Rejected - No solution for core-api or non-OIDC services --- ## 9. Cost Analysis ### 9.1 Resource Requirements **New Infrastructure:** | Component | CPU | RAM | Storage | Notes | |-----------|-----|-----|---------|-------| | Authentik Server | 0.5 | 256 MB | 1 GB | Main application | | Authentik Worker | 0.3 | 128 MB | - | Background tasks | | PostgreSQL | 0.5 | 256 MB | 2 GB | User database | | Redis | 0.2 | 64 MB | 100 MB | Cache | | **Total** | **1.5 CPU** | **~700 MB** | **~3 GB** | | **Current System:** - CPU: Intel i7-6700 (4 cores / 8 threads) - 1.5 core usage is ~19% - RAM: 16 GB total - 700 MB is ~4% - Storage: SSD space available **Verdict:** ✅ Easily within capacity ### 9.2 Time Investment | Phase | Estimated Time | Skill Level | |-------|----------------|-------------| | Research & Planning | 8 hours | All levels | | Authentik Deployment | 2 hours | Intermediate | | Google OAuth Setup | 1 hour | Beginner | | core-api Protection | 2 hours | Intermediate | | Service Integration (4 services) | 8 hours | Intermediate | | Admin Tool Integration (4 services) | 6 hours | Intermediate | | Testing & Validation | 4 hours | All levels | | Documentation | 4 hours | All levels | | **Total** | **35 hours** | | **Breakdown per week:** - Week 1: 10 hours (Foundation) - Week 2: 10 hours (User services) - Week 3: 10 hours (Admin tools) - Week 4: 5 hours (Hardening) ### 9.3 Monetary Costs **Required:** - Google Cloud Project: Free (OAuth is free tier) - Domain: schweitz.net (assuming already owned) - SSL Certificates: Free (Let's Encrypt via NPM) **Optional:** - Google Workspace subscription: If using Workspace features (not required for OAuth) - Cloud monitoring tools: Free tier available **Total additional cost:** $0 (assuming domain already owned) --- ## 10. Risk Assessment ### 10.1 Technical Risks | Risk | Likelihood | Impact | Mitigation | |------|------------|--------|------------| | Authentik failure locks out all services | Low | High | Keep local admin account; VPN access | | Google OAuth outage | Low | Medium | Authentik has local account fallback | | Session handling bugs | Medium | Low | Extensive testing; gradual rollout | | Plugin incompatibilities (Jellyfin, Nextcloud) | Medium | Medium | Test thoroughly; keep native auth during transition | | Database corruption (PostgreSQL) | Low | High | Automated backups; replication (optional) | ### 10.2 Operational Risks | Risk | Likelihood | Impact | Mitigation | |------|------------|--------|------------| | User confusion during transition | High | Low | Clear documentation; email notifications | | Lost access due to forgotten Google account | Low | Medium | Admin can manually link accounts | | Mobile app compatibility issues | Medium | Medium | Test all mobile apps; document workarounds | | Breaking change in Authentik update | Low | Medium | Pin versions; test updates in staging | ### 10.3 Security Risks | Risk | Likelihood | Impact | Mitigation | |------|------------|--------|------------| | Compromise of Google account | Low | High | Enforce 2FA at Google level | | Authentik vulnerability | Low | High | Keep updated; subscribe to security advisories | | Session hijacking | Low | Medium | Secure cookies; short timeouts | | Misconfigured OIDC client | Medium | Medium | Follow official docs; peer review configs | --- ## 11. Success Metrics ### 11.1 Technical Metrics - **Authentication Success Rate:** >99% of login attempts succeed - **Session Uptime:** Authentik availability >99.9% - **Response Time:** Login flow completes in <3 seconds - **Integration Coverage:** 100% of user-facing services support SSO ### 11.2 Security Metrics - **Zero Unauthorized Access:** No successful unauthorized access attempts - **Audit Log Completeness:** 100% of auth events logged - **MFA Adoption:** >80% of users enable 2FA at Google level (if enforced) - **Failed Login Rate:** <5% of attempts fail (indicates good UX) ### 11.3 User Experience Metrics - **Login Friction:** 1 click to authenticate (vs 8+ passwords previously) - **User Onboarding:** New user can access all services in <5 minutes - **User Satisfaction:** Positive feedback on unified login - **Support Tickets:** <10% increase in auth-related support (temporary during transition) --- ## 12. Next Steps & Discussion Points ### 12.1 Questions for User Before proceeding with implementation, please confirm: 1. **Google Account Type:** - Do you have Google Workspace, or Gmail only? - Should we support both types of accounts? 2. **User Base:** - How many users will access the system? - Do you need group-based access control (e.g., family, friends, admin)? 3. **Service Priority:** - Which services are most critical to secure first? - Any services you want to exclude from SSO? 4. **MFA Requirements:** - Should we enforce 2FA for all users? - At Google level, Authentik level, or both? 5. **Session Duration:** - How long should sessions last before re-authentication? - Different timeouts for admin vs regular users? 6. **Rollout Strategy:** - Gradual rollout (one service at a time) or big-bang? - Pilot with single user before full deployment? ### 12.2 Immediate Next Actions If approved, the first concrete steps: 1. **Create `stacks/authentik.yml`** - Docker Compose for Authentik stack 2. **Deploy Authentik** - `make deploy-authentik` (or via Portainer UI) 3. **Access Setup** - `http://localhost:9000` → complete initial wizard 4. **Google Cloud Console** - Create OAuth 2.0 client credentials 5. **Configure Google Source** - In Authentik admin panel 6. **Test Authentication** - Verify Google login works for test account 7. **Document OIDC Config** - Template for service integration ### 12.3 Documentation Deliverables Post-implementation, we'll create: 1. **User Guide:** "How to Log In to Homelab Services" 2. **Admin Guide:** "Managing Users and Access in Authentik" 3. **Troubleshooting Guide:** Common issues and solutions 4. **Architecture Diagram:** Visual representation of auth flow 5. **Runbook:** Disaster recovery and maintenance procedures --- ## 13. Conclusion ### 13.1 Summary This plan proposes using **Authentik** as a central Identity Provider (IdP) that federates with **Google OAuth** to provide secure, unified authentication across all homelab services. This architecture: - **Meets Requirements:** Direct Google login for Workspace and Gmail accounts - **Addresses Security Gap:** Protects core-api for public API exposure - **Provides Flexibility:** Supports OIDC, SAML, LDAP, and proxy auth - **Scales Appropriately:** Right-sized for homelab (~700MB RAM) - **Maintains Simplicity:** Clear integration patterns per service ### 13.2 Key Benefits **For Users:** - Single-click login with Google across all services - No password management or sprawl - Consistent authentication experience - Mobile-friendly (Google OAuth optimized) **For Admin:** - Centralized user and access management - Unified audit logging and security monitoring - Granular access control per service - Easy onboarding/offboarding **For Security:** - No passwords stored in homelab - MFA enforced at Google level - Reduced attack surface (one auth point) - Professional-grade OAuth 2.0 implementation ### 13.3 Recommendation **Proceed with Authentik implementation** following the phased approach: - Week 1: Foundation (Authentik + core-api protection) - Week 2: User services integration - Week 3: Admin tools integration - Week 4: Hardening and documentation This gradual rollout minimizes risk, allows for testing at each stage, and provides clear rollback points. --- ## Appendix A: Reference Links ### Official Documentation - **Authentik:** https://docs.goauthentik.io/ - **Google OAuth 2.0:** https://developers.google.com/identity/protocols/oauth2 - **Nginx Proxy Manager:** https://nginxproxymanager.com/ - **Portainer OAuth:** https://docs.portainer.io/admin/settings/authentication/oauth - **Nextcloud OIDC:** https://github.com/nextcloud/user_oidc - **Gitea OAuth:** https://docs.gitea.com/usage/authentication - **Jellyfin SSO Plugin:** https://github.com/9p4/jellyfin-plugin-sso - **Open WebUI SSO:** https://docs.openwebui.com/features/auth/sso/ ### Integration Guides - **Authentik + NPM:** https://docs.goauthentik.io/integrations/services/nginx-proxy-manager/ - **Authentik + Google:** https://docs.goauthentik.io/docs/users-sources/sources/social-logins/google/ - **Authentik + Nextcloud:** https://docs.goauthentik.io/integrations/services/nextcloud/ - **Authentik + Portainer:** https://docs.goauthentik.io/integrations/services/portainer/ - **Authentik + Gitea:** https://www.authelia.com/integration/openid-connect/clients/gitea/ (similar for Authentik) ### Community Resources - **r/selfhosted:** https://reddit.com/r/selfhosted - **Authentik Discord:** https://discord.gg/jg33eMhnj6 - **Homelab Forum:** https://homelabos.com/ --- ## Appendix B: Glossary - **OAuth 2.0:** Authorization framework for delegated access - **OIDC (OpenID Connect):** Authentication layer on top of OAuth 2.0 - **IdP (Identity Provider):** Service that authenticates users (Authentik in our case) - **SSO (Single Sign-On):** One login for multiple applications - **SAML:** XML-based standard for exchanging auth data - **LDAP:** Protocol for accessing directory services - **Forward Auth:** Reverse proxy checks auth before forwarding request - **PKCE:** Security extension for OAuth to prevent interception attacks - **RBAC:** Role-Based Access Control - **2FA/MFA:** Two-Factor / Multi-Factor Authentication --- **End of Document** *This plan is ready for review and discussion. Once approved, we can proceed with Phase 1 implementation.*