Edge Collector Provisioning Architecture¶
Overview¶
This document describes the provisioning architecture for Chaverim ALPR edge collectors (Raspberry Pi devices). The design prioritizes field-friendly deployment by non-technical volunteers while maintaining strong security controls.
Key Design Principles: - Pre-provisioned SD cards with unique claim codes (no CLI required in field) - Admin-provisioned API keys with claim-based activation (no self-registration) - Encrypted credential storage using device-bound keys - Central-managed configuration with local override capability
1. Provisioning Workflow¶
Recommended Approach: Pre-Built Image + Claim Code¶
ADMIN PHASE (Office/HQ)
───────────────────────────────────────────────────────────────────────────
1. Admin creates "Pending Collector" in Central UI
└─ Enters: Site name, address, expected cameras
2. Central generates:
├─ Unique Claim Code (8 chars, expires 48h): "ALPR-K7X9-M2P4"
├─ API Key (stored encrypted, not revealed yet)
└─ QR Code containing claim URL
3. Admin burns base image to SD card
4. Admin prints QR code label, attaches to Pi
FIELD PHASE (Installation Site)
───────────────────────────────────────────────────────────────────────────
5. Volunteer connects Pi to network + power
6. Pi boots, starts local setup UI (Flask on port 8080)
7. Volunteer scans QR code with phone → opens setup wizard
OR enters claim code manually in local UI
8. Pi contacts Central with claim code
9. Central validates claim, returns:
├─ API Key (encrypted for this device)
├─ Collector ID
└─ Initial configuration
10. Pi stores credentials, starts normal operation
11. Central marks collector as "Active"
Why This Approach?¶
| Alternative | Pros | Cons | Verdict |
|---|---|---|---|
| Pre-built image + claim code | Field-friendly, secure, auditable | Requires admin prep | Recommended |
| Pre-baked credentials per device | Zero field config | Credential exposure if SD card lost, no flexibility | Rejected |
| Self-registration | Simple deployment | Security risk, unauthorized devices | Rejected |
| SSH-based setup | Flexible | Requires technical skill, not field-friendly | Rejected |
2. First Boot Sequence¶
┌─────────┐
│ BOOT │
└────┬────┘
│
▼
┌───────────┐ No credentials found
│ CHECK │─────────────────────────────┐
│ CLAIMED? │ │
└─────┬─────┘ │
│ Yes ▼
│ ┌────────────────┐
▼ │ START SETUP │
┌───────────┐ │ WIZARD SERVICE │
│ START │ │ (port 8080) │
│ COLLECTOR │ └───────┬────────┘
│ SERVICE │ │
└───────────┘ ┌────────┴────────┐
│ │ AWAIT CLAIM │
│ │ - Display URL │
│ │ - Blink LED │
│ └────────┬────────┘
│ │
│ Claim code entered
│ ▼
│ ┌─────────────────────────┐
│ │ VALIDATE WITH CENTRAL │
│ │ POST /api/v1/claim │
│ └─────────────┬───────────┘
│ │
│ Success? │
│ ┌─────────────┴─────────────┐
│ Yes No
│ │ │
│ ▼ ▼
│ ┌──────────────────┐ ┌──────────────────┐
│ │ STORE CREDS │ │ SHOW ERROR │
│ │ STOP SETUP SVC │ │ RETRY/CONTACT │
│ │ START COLLECTOR │ │ ADMIN │
│ └──────────────────┘ └──────────────────┘
│ │
└────────────────────┘
│
▼
┌──────────────────┐
│ NORMAL │
│ OPERATION │
└──────────────────┘
Base Image Contents¶
/opt/chaverim-edge/
├── bin/
│ ├── edge-collector # Main service binary
│ ├── setup-wizard # First-boot setup service
│ └── health-check # Systemd health check
├── config/
│ ├── base-config.yaml # Non-sensitive defaults
│ └── .credentials/ # Created after claim (encrypted)
├── data/
│ └── events.db # SQLite buffer (created on first run)
├── logs/
└── systemd/
├── chaverim-edge.service
├── chaverim-setup.service # Runs until claimed
└── chaverim-watchdog.service
3. Edge-to-Central Registration¶
Database Schema for Collectors¶
CREATE TABLE edge.collectors (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Identity
site_name VARCHAR(100) NOT NULL,
site_address VARCHAR(255),
gps_latitude DECIMAL(10, 8),
gps_longitude DECIMAL(11, 8),
-- Claim lifecycle
claim_code VARCHAR(20) UNIQUE, -- "ALPR-K7X9-M2P4"
claim_code_expires_at TIMESTAMPTZ,
claimed_at TIMESTAMPTZ,
claimed_from_ip INET,
-- Authentication
api_key_hash VARCHAR(255), -- SHA-256 hash
api_key_prefix VARCHAR(8), -- First 8 chars for identification
-- Device info (populated after claim)
hardware_id VARCHAR(100), -- Pi serial number
os_version VARCHAR(50),
edge_version VARCHAR(20),
-- Status
status VARCHAR(20) DEFAULT 'pending', -- pending, active, disabled, decommissioned
last_heartbeat_at TIMESTAMPTZ,
config_version INTEGER DEFAULT 1,
-- Audit
created_at TIMESTAMPTZ DEFAULT NOW(),
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT valid_status CHECK (
status IN ('pending', 'active', 'disabled', 'decommissioned')
)
);
CREATE INDEX idx_collectors_api_key_prefix ON edge.collectors(api_key_prefix);
CREATE INDEX idx_collectors_claim_code ON edge.collectors(claim_code)
WHERE claim_code IS NOT NULL;
CREATE INDEX idx_collectors_status ON edge.collectors(status);
Claim Code Design¶
Format: ALPR-XXXX-XXXX (e.g., "ALPR-K7X9-M2P4")
- Excludes ambiguous characters (0, O, I, L, 1)
- ~30 bits of entropy
- 48-hour expiration
- Single-use (cleared after successful claim)
Claim Flow:
1. Edge presents claim code to POST /api/v1/collectors/claim
2. Central validates: code exists, not expired, hardware not already registered
3. Central returns: collector ID, API key (only time shown), initial config
4. Edge stores credentials using device-bound encryption
Implementation Details: See Edge Provisioning PRP for claim code generation, validation endpoint, and hardware binding patterns.
4. Camera Authentication¶
Credential Storage Model¶
CENTRAL SERVER EDGE COLLECTOR
┌──────────────────────┐ ┌──────────────────────┐
│ Camera Credentials │ │ Encrypted Store │
│ (Admin UI entry) │ │ /opt/.../creds/ │
│ │ │ │
│ camera_id: UUID │ Encrypted │ cameras.enc │
│ vendor: "hikvision" │──via config──│ (Fernet/AES-256) │
│ host: "192.168.1.x" │ push │ │
│ username: "admin" │ │ Key derivation: │
│ password: "******" │ │ - Pi serial number │
│ port: 80 │ │ - Collector ID │
│ │ │ - Install timestamp │
└──────────────────────┘ └──────────────────────┘
Device-Bound Encryption¶
Camera credentials are encrypted at rest using a key derived from device-specific values:
Key Derivation Inputs: - Collector ID (from claim response) - Hardware ID (Pi serial number) - Install timestamp (first boot time)
Security Properties: - Key is never stored on disk - regenerated at runtime - Stolen SD card cannot be decrypted without the hardware - Uses PBKDF2 with 100k iterations - Fernet (AES-128-CBC + HMAC) for encryption
Implementation Details: See Edge Provisioning PRP for key derivation, CredentialStore class, and secure deletion patterns.
Vendor-Specific Adapters¶
Each camera vendor requires a specialized adapter implementing the common CameraAdapter interface:
| Vendor | Auth Method | Protocol |
|---|---|---|
| Hikvision | Digest Auth | ISAPI (HTTP) |
| Unifi Protect | Bearer Token | REST API (HTTPS) |
Implementation Details: See Camera Adapters PRP for adapter interface, vendor implementations, and detection normalization patterns.
5. Security Model¶
Threat Model and Mitigations¶
| Threat | Mitigation |
|---|---|
| Stolen SD card | Credentials encrypted with device-bound key (requires Pi serial + collector ID). Admin can revoke API key immediately. |
| Rogue device | Claim codes are admin-provisioned only. Expire in 48 hours. Hardware ID binding prevents reuse. |
| Network sniffing | All edge-to-central over TLS 1.3. Camera traffic on isolated VLAN. |
| Compromised edge | API keys are per-collector (revocable). Rate limiting on central API. Anomaly detection. |
| Credential extraction | File permissions (600). Encryption at rest. No plaintext passwords in config. |
| Denial of service | Heartbeat timeout triggers alerts. Local buffer survives 7 days offline. |
First-Boot Security Measures¶
- Network Isolation - Setup wizard only accessible from LAN
- Claim Code Protection - 48-hour expiration, single-use, rate-limited attempts
- Hardware Binding - Pi serial recorded, same hardware cannot claim twice
- Post-Claim Lockdown - Setup wizard disabled, port 8080 closed
Remote Wipe / Decommission¶
The wipe command securely removes all sensitive data from an edge device:
Wipe Sequence: 1. Stop collector service 2. Overwrite + delete credential files (secure deletion) 3. Overwrite + delete SQLite Detection Buffer 4. Clear application logs 5. Delete central configuration 6. Restart in setup mode (awaiting new claim)
What Gets Wiped: - Camera passwords (encrypted credential store) - Detection Buffer (SQLite database) - Application logs - Central-pushed configuration
Implementation Details: See Edge Provisioning PRP for secure deletion implementation and wipe command handler.
6. Operational Procedures¶
Device Replacement (Hardware Failure)¶
Option A: Keep Same Site Identity (Recommended) 1. Admin marks collector as "pending-replacement" in Central UI 2. Central generates new claim code for same collector record 3. Burn fresh SD card, deploy to site 4. Claim with new code → Same collector ID, new hardware binding 5. Cameras must be re-added (credentials re-entered) 6. Historical data remains linked to same collector ID
Option B: Create New Collector 1. Admin decommissions old collector 2. Admin creates new collector for same site 3. Standard provisioning flow 4. Historical data shows under old collector (for audit trail)
Site Relocation¶
Update collector metadata in Central UI. Does not require re-provisioning - just metadata update. Audit log records location change.
Multi-Camera Site Setup¶
SITE: Shopping Mall (Example)
┌─────────────────────────────────────────────────────────────────────┐
│ Raspberry Pi 5 (8GB) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Camera 1 │ │ Camera 2 │ │ Camera 3 │ │ Camera 4 │ │
│ │ Entrance A │ │ Entrance B │ │ Parking N │ │ Parking S │ │
│ │ (Hikvision)│ │ (Hikvision)│ │ (Unifi) │ │ (Unifi) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └─────┬─────┘ │
│ └────────────────┴────────────────┴───────────────┘ │
│ │ │
│ Detection Aggregator │
│ (dedupe, tag, batch) │
│ │ │
│ SQLite Buffer │
└─────────────────────────────────────────────────────────────────────┘
RESOURCE GUIDELINES:
• Pi 4 (2GB): Up to 4 cameras, ~50 events/min aggregate
• Pi 4 (4GB): Up to 8 cameras, ~100 events/min aggregate
• Pi 5 (8GB): Up to 16 cameras, ~200 events/min aggregate
7. Configuration Management¶
Configuration Hierarchy¶
LAYER 1: Base Image Defaults (Immutable)
/opt/chaverim-edge/config/defaults.yaml
─────────────────────────────────────────
heartbeat_interval_seconds: 60
event_batch_size: 50
log_level: INFO
buffer_retention_days: 7
│
▼ (override)
LAYER 2: Central-Pushed Config (Managed)
/opt/chaverim-edge/config/central.yaml
─────────────────────────────────────────
config_version: 5
heartbeat_interval_seconds: 30 # Override
cameras: [...]
│
▼ (override)
LAYER 3: Local Overrides (Field Troubleshooting)
/opt/chaverim-edge/config/local.yaml
─────────────────────────────────────────
log_level: DEBUG # Temporary
upload_enabled: false # Debug pause
MERGE ORDER: defaults → central → local
Config Push via Heartbeat¶
Config updates delivered through heartbeat response when config_version changes. Edge acknowledges application (success/failed/rolled_back). Automatic rollback if new config causes failures.
8. Network Requirements¶
Minimum Requirements¶
- Internet: 1 Mbps upload (supports ~100 events/min with images)
- LAN: Access to camera IPs (same subnet or routed)
- DNS: Resolve central server hostname
- Ports: Outbound HTTPS (443) to central server
Recommended Network Setup¶
Internet
│
▼
┌─────────┐
│ Router │
└────┬────┘
│
┌────┴────────────────────────────────────┐
│ VLAN 1: General │
│ (Pi management) │
│ │ │
│ ┌────┴────┐ │
│ │ Pi │ │
│ └────┬────┘ │
└─────────────────────│───────────────────┘
│
┌─────────────────────┴───────────────────┐
│ VLAN 2: Cameras │
│ (Isolated, no internet) │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Cam 1 │ │ Cam 2 │ │ Cam 3 │ │
│ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────┘
Firewall Rules (Pi)¶
OUTBOUND (allow): - TCP 443 to central server (HTTPS) - TCP 80/443 to camera IPs (ISAPI/Unifi API) - UDP 53 to DNS servers - UDP 123 to NTP servers (time sync critical)
INBOUND (allow): - TCP 8080 from LAN only (local diagnostics UI) - TCP 22 from LAN only (SSH, if enabled)
9. Implementation Phases¶
Phase 1: MVP Provisioning¶
Central Server: - [ ] Collector database table with claim code support - [ ] Admin UI: Create pending collector - [ ] Admin UI: View collector list with status - [ ] POST /api/v1/collectors/claim endpoint - [ ] API key validation middleware
Edge Collector: - [ ] Base Raspberry Pi OS image with edge software - [ ] First-boot detection (check for credentials file) - [ ] Simple claim UI (Flask form - enter claim code) - [ ] Credential storage (encrypted file) - [ ] Hardware ID collection (Pi serial) - [ ] Systemd services (setup + collector)
Phase 2: Production Hardening¶
Security: - [ ] Encrypted SQLite buffer (SQLCipher) - [ ] Read-only root filesystem option - [ ] Automatic security updates - [ ] File integrity monitoring
Operations: - [ ] Config push via heartbeat - [ ] Config versioning and rollback - [ ] Remote wipe command - [ ] Device replacement workflow - [ ] Collector health dashboard
Field Usability: - [ ] QR code claim flow - [ ] Camera discovery (mDNS/SSDP scan) - [ ] Connection test wizard - [ ] LED status indicators
10. Field Deployment Checklist¶
Before Leaving for Site¶
- [ ] Raspberry Pi with pre-burned SD card
- [ ] Power supply (official Pi power adapter)
- [ ] Ethernet cable (or WiFi credentials)
- [ ] QR code label attached to Pi (or claim code)
- [ ] Camera IP addresses and credentials from site owner
- [ ] Your phone (for scanning QR / accessing setup UI)
At the Site¶
- [ ] Connect Pi to network (Ethernet to router/switch)
- [ ] Connect Pi to power
- [ ] Wait 2 minutes for boot (green LED stops flashing)
- [ ] Connect your phone to same network
- [ ] Scan QR code OR open
http://chaverim-setup.local:8080 - [ ] Enter claim code when prompted
- [ ] Add cameras using the wizard
- [ ] Verify detections appear in "Test" section
- [ ] Done! Pi will now upload to central server
Troubleshooting¶
- No setup page? Check network cable, try different port
- Claim code rejected? Check expiration with admin
- Camera not connecting? Verify IP, username, password
- No detections? Drive a car past camera, check camera ALPR enabled
Trade-off Summary¶
| Decision | Chosen Approach | Alternative | Rationale |
|---|---|---|---|
| Provisioning | Pre-provisioned claim codes | Self-registration | Security: Admin controls what devices can join |
| API Key delivery | Via claim response | Pre-burned in image | Flexibility: Same image for all devices |
| Credential storage | Device-bound encryption | Plaintext config | Security: Stolen SD card unusable |
| Camera setup | Local UI wizard | Central push only | UX: Volunteers can add cameras without admin |
| Config management | 3-layer hierarchy | Central-only | Flexibility: Local override for troubleshooting |
| Hardware binding | Pi serial number | MAC address | Reliability: Serial survives NIC replacement |