# Architecture Overview ## System Components ``` ┌─────────────────────────────────────────────────────────────────┐ │ Network Clients │ │ (IoT Devices, Laptops, Phones with WPA-Enterprise credentials) │ └──────────────────────┬──────────────────────────────────────────┘ │ RADIUS Access-Request │ (MAC, Username, EAP) ▼ ┌─────────────────────────────────────────────────────────────────┐ │ FreeRADIUS Server │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ EAP Module (Password/Certificate Verification) │ │ │ └──────────────────────────┬─────────────────────────────────┘ │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ device_manager_radius.py (rlm_python) │ │ │ │ │ │ │ │ • Parse RADIUS attributes (MAC, Username, NAS, SSID) │ │ │ │ • Call Frappe API for authorization decision │ │ │ │ • Cache credentials in SQLite for offline operation │ │ │ │ • Return VLAN assignment and reply attributes │ │ │ └──────────────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ SQLite Credential Cache │ │ │ │ /var/lib/freeradius/device_manager_verifier_cache.sqlite3 │ │ │ │ │ │ │ │ • Stores SSHA password hashes (no plaintext) │ │ │ │ • Device-specific VLAN assignments │ │ │ │ • Expiration timestamps │ │ │ │ • Used when Frappe unreachable │ │ │ └────────────────────────────────────────────────────────────┘ │ └──────────────────────────┬──────────────────────────────────────┘ │ HTTP POST with API token │ /api/method/device_manager.api.radius_authorize ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Frappe Server │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ device_manager.api.radius_authorize() │ │ │ │ │ │ │ │ • Authenticate API token │ │ │ │ • Find device by MAC address │ │ │ │ • Evaluate access policy │ │ │ │ • Create audit records │ │ │ │ • Return decision with VLAN and credentials │ │ │ └──────────────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ MariaDB/PostgreSQL Database │ │ │ │ │ │ │ │ • DM Device (registered devices) │ │ │ │ • DM Access Policy (authorization rules) │ │ │ │ • DM Network Segment (VLAN mappings) │ │ │ │ • DM Radius Auth Event (audit log) │ │ │ │ • DM Access Decision (decision log) │ │ │ │ • DM Device Audit Event (compliance log) │ │ │ │ • Stored Credential Verifier (password hashes) │ │ │ └────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ## Data Flow ### 1. Normal Operation (Frappe Reachable) ``` Client → FreeRADIUS → device_manager_radius.py │ ├─→ HTTP API call to Frappe │ POST /api/method/device_manager.api.radius_authorize │ Authorization: token API_KEY:API_SECRET │ │ Request payload: │ - calling_station_id (MAC) │ - username │ - nas_identifier │ - nas_ip_address │ - ssid │ - raw_request (full RADIUS attributes) │ ├─← Response from Frappe │ { │ "event": "AUTH-001", │ "decision": "DEC-001", │ "result": "Allow", │ "vlan_id": 100, │ "radius_reply_attributes": {...}, │ "cacheable_credentials": { │ "username": "device001", │ "control_attributes": { │ "SSHA-Password": "base64hash" │ } │ } │ } │ ├─→ Cache decision in SQLite │ └─→ Return to FreeRADIUS - RADIUS reply attributes (VLAN) - Control attributes (password hash) - Accept/Reject decision Client ← FreeRADIUS ← Access-Accept + VLAN assignment ``` ### 2. Offline Operation (Frappe Unreachable) ``` Client → FreeRADIUS → device_manager_radius.py │ ├─→ HTTP API call to Frappe (FAILS) │ Network error / Timeout │ ├─→ Query SQLite cache │ SELECT * FROM radius_verifier_cache │ WHERE username = ? │ ├─← Cached decision found │ { │ "result": "Allow", │ "vlan_id": 100, │ "control_attributes": { │ "SSHA-Password": "base64hash" │ }, │ "from_cache": true │ } │ └─→ Return to FreeRADIUS - RADIUS reply from cache - Control attributes from cache - Accept with cached VLAN Client ← FreeRADIUS ← Access-Accept (from cache) ``` ## Deployment Modes Comparison ### Mode 1: Standalone Client (NEW) **Use Case:** FreeRADIUS on dedicated appliance, Frappe on app server ``` ┌─────────────────┐ API over HTTPS ┌─────────────────┐ │ RADIUS Server │ ←──────────────────────→ │ Frappe Server │ │ │ │ │ │ • FreeRADIUS │ │ • Frappe │ │ • Python 3.10+ │ │ • device_mgr │ │ • device_mgr_ │ │ • MariaDB │ │ radius.py │ │ │ │ • SQLite cache │ │ │ └─────────────────┘ └─────────────────┘ Dependencies: Python stdlib only Module: device_manager_radius Config: DEVICE_MANAGER_FRAPPE_URL + API credentials ``` ### Mode 2: Local (Integrated) **Use Case:** Everything on one server (lab/testing) ``` ┌──────────────────────────────────┐ │ Single Server │ │ │ │ • FreeRADIUS │ │ • Frappe bench │ │ • device_manager app │ │ • MariaDB │ │ │ │ In-process import: │ │ device_manager.freeradius │ │ ↓ calls ↓ │ │ device_manager.radius │ │ ↓ queries ↓ │ │ Database │ └──────────────────────────────────┘ Dependencies: Full Frappe + device_manager Module: device_manager.freeradius Config: DEVICE_MANAGER_BENCH_PATH + SITE ``` ### Mode 3: Remote (Full App Installed) **Use Case:** RADIUS server with device_manager installed, Frappe remote ``` ┌─────────────────┐ API over HTTPS ┌─────────────────┐ │ RADIUS Server │ ←──────────────────────→ │ Frappe Server │ │ │ │ │ │ • FreeRADIUS │ │ • Frappe │ │ • Python 3.10+ │ │ • device_mgr │ │ • device_mgr │ │ • MariaDB │ │ app (full) │ │ │ │ • SQLite cache │ │ │ └─────────────────┘ └─────────────────┘ Dependencies: device_manager package Module: device_manager.freeradius Config: DEVICE_MANAGER_FRAPPE_URL + API credentials ``` ## Security Architecture ### Authentication Flow ``` 1. FreeRADIUS validates device credentials (EAP-PEAP/TLS) └─→ If valid, call device_manager_radius 2. device_manager_radius calls Frappe API ├─→ Authorization: token API_KEY:API_SECRET ├─→ HTTPS encrypted transport └─→ Token validated by Frappe 3. Frappe evaluates device policy ├─→ Lookup device by MAC address ├─→ Check approval status ├─→ Check lifecycle state ├─→ Evaluate access policy rules └─→ Determine VLAN assignment 4. Response cached locally (if cacheable) ├─→ Only SSHA hashes stored (no plaintext) ├─→ Cache file owned by freerad user ├─→ Optional expiration date └─→ Used only when Frappe unreachable ``` ### Secret Management | Secret Type | Storage Location | Access Control | |-------------|------------------|----------------| | Device passwords | Never stored plaintext | N/A | | SSHA verifiers | SQLite cache + Frappe DB | freerad user, DB permissions | | API credentials | systemd override | root:root 600 | | Frappe session tokens | Frappe DB | System Manager only | ## Performance Characteristics | Operation | Typical Latency | Notes | |-----------|----------------|-------| | Cache hit | < 5ms | SQLite query | | API call (LAN) | 10-50ms | Network + DB query | | API call (WAN) | 50-500ms | Depends on network | | API timeout | 2.5s (default) | Configurable | | Cache write | < 10ms | SQLite insert | ## Scalability ### Single RADIUS Server - Handles 1000+ auth/sec with cache hits - ~100 auth/sec with API calls (network bound) - SQLite cache suitable for <100k devices ### High Availability - Deploy multiple RADIUS servers (round-robin DNS or load balancer) - Each server maintains independent SQLite cache - Shared Frappe backend ensures consistent policy - Consider Redis cache for distributed deployment (future enhancement) ## Monitoring Points ### RADIUS Server - FreeRADIUS logs (`radiusd.L_INFO`, `radiusd.L_ERR`) - Cache hit rate (grep logs for "using cached credentials") - API timeout rate (grep logs for "authorization failed") - Cache size (SQLite table row count) ### Frappe Server - API endpoint latency (Frappe request logs) - Authentication success/failure rate - Policy evaluation time - Audit log growth rate ### Network - HTTPS latency between RADIUS and Frappe - Packet loss between clients and RADIUS - Certificate expiration monitoring