Files

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