1375 lines
49 KiB
Markdown
1375 lines
49 KiB
Markdown
# 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=<from_authentik>
|
|
- OAUTH_CLIENT_SECRET=<from_authentik>
|
|
- 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.*
|