15 KiB
15 KiB
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