Skip to content

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

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

  1. Network Isolation - Setup wizard only accessible from LAN
  2. Claim Code Protection - 48-hour expiration, single-use, rate-limited attempts
  3. Hardware Binding - Pi serial recorded, same hardware cannot claim twice
  4. 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
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

  1. [ ] Connect Pi to network (Ethernet to router/switch)
  2. [ ] Connect Pi to power
  3. [ ] Wait 2 minutes for boot (green LED stops flashing)
  4. [ ] Connect your phone to same network
  5. [ ] Scan QR code OR open http://chaverim-setup.local:8080
  6. [ ] Enter claim code when prompted
  7. [ ] Add cameras using the wizard
  8. [ ] Verify detections appear in "Test" section
  9. [ ] 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