Skip to content

Stack Topology

This page describes the two-stack architecture of the Complete Device Management platform, the network boundaries between stacks, the trust relationships, and all communication paths.


Overview Diagram

graph TB
    subgraph provider["Provider-Stack  (CDM operator)"]
        CADDY_P["Caddy :8888 (entry point)"]
        KC_P["Keycloak<br>(realm: cdm)"]
        RMQ["RabbitMQ<br>(vHost per tenant)"]
        TSDB_P["TimescaleDB<br>(provider metrics)"]
        TLG["Telegraf<br>(service health)"]
        GRF_P["Grafana<br>(platform dashboards)"]
        SCA_P["step-ca<br>(Root CA + Intermediate CA)"]
        IBA["IoT Bridge API<br>(management API)"]
    end

    subgraph tenant["Tenant-Stack  (customer)  ×N"]
        CADDY_T["Caddy :8888 (entry point)"]
        KC_T["Keycloak<br>(tenant realm)"]
        TB["ThingsBoard<br>(device mgmt + MQTT)"]
        HB["hawkBit<br>(OTA campaigns)"]
        SCA_T["step-ca<br>(Issuing Sub-CA)"]
        WGS["WireGuard Server"]
        TXP["Terminal Proxy"]
        TSDB_T["TimescaleDB<br>(device telemetry)"]
        GRF_T["Grafana<br>(tenant dashboards)"]
    end

    subgraph device["Device-Stack  (edge)"]
        BST["bootstrap<br>(enroll.sh)"]
        MQC["mqtt-client"]
        WGC["wireguard-client"]
        UPD["rauc-updater"]
        TTD["ttyd"]
    end

    %% PKI trust chain
    SCA_P -->|"signs Sub-CA CSR"| SCA_T
    SCA_T -->|"issues device cert"| BST

    %% Keycloak federation
    KC_T -->|"Identity Provider federation"| KC_P

    %% Tenant JOIN
    IBA -->|"creates vHost + user,<br>signs Sub-CA CSR,<br>registers IdP"| tenant

    %% Device → Tenant-Stack
    MQC -->|"MQTTS mTLS"| TB
    WGC -->|"WireGuard VPN"| WGS
    UPD -->|"DDI poll"| HB

    %% Provider Telegraf
    TLG -->|"SQL"| TSDB_P

    %% Tenant → Provider
    TB -->|"metrics (AMQP)"| RMQ
    TSDB_T -.->|"aggregated metrics"| TSDB_P

    %% Terminal
    TXP -->|"WS → WireGuard IP → ttyd"| TTD

Network Boundaries

Boundary Protocol Authentication
Provider-Stack ingress HTTPS (Caddy ACME) Keycloak OIDC
Tenant-Stack ingress HTTPS (Caddy ACME) Keycloak OIDC (tenant realm)
Device → Tenant MQTT MQTTS (port 8883) mTLS (device cert signed by Tenant Sub-CA)
Tenant-Stack → Provider RabbitMQ AMQPS (port 5671) mTLS (service cert signed by Provider CA)
Tenant Keycloak → Provider Keycloak HTTPS OIDC Identity Provider federation
Device → WireGuard WireGuard UDP (51820) Pre-shared key provisioned at enrollment
Browser → Terminal Proxy WSS Keycloak JWT (cdm-operator / cdm-admin)

Trust Hierarchy

graph TD
    RCA["Root CA<br>(Provider-Stack step-ca)<br>10-year · offline-safe"]
    ICA["Intermediate CA<br>(Provider-Stack step-ca)<br>5-year · online"]
    TSCA["Tenant Issuing Sub-CA<br>(Tenant-Stack step-ca)<br>2-year · per tenant"]
    SVC["Provider Service Certs<br>(serverAuth + clientAuth · 1-year)"]
    DEV["Device Certs<br>(clientAuth only · 24h–90d)"]
    TSVC["Tenant Service Certs<br>(serverAuth + clientAuth · 1-year)"]

    RCA --> ICA
    ICA --> TSCA
    ICA --> SVC
    TSCA --> DEV
    TSCA --> TSVC

The Root CA private key is stored in the step-ca-data Docker volume, encrypted with STEP_CA_PASSWORD. In production, export it and store it offline after generating the Intermediate CA.

Each Tenant-Stack generates its own Sub-CA key pair and sends a CSR to the Provider IoT Bridge API via the JOIN workflow. The Provider Intermediate CA signs the CSR, establishing a chain of trust from device → Tenant Sub-CA → Provider Intermediate CA → Provider Root CA.


Keycloak Identity Federation

sequenceDiagram
    participant U as User (browser)
    participant TKC as Tenant Keycloak
    participant PKC as Provider Keycloak (cdm realm)

    U->>TKC: login via "CDM Platform" Identity Provider
    TKC->>PKC: OIDC Authorization Request
    PKC-->>U: login page (provider SSO)
    U->>PKC: credentials
    PKC-->>TKC: ID token (platform roles)
    TKC->>TKC: map platform roles → tenant roles
    TKC-->>U: session established

Platform administrators and operators from the Provider-Stack automatically receive scoped access to every Tenant-Stack through this federation — without separate credentials.


RabbitMQ vHost Routing

The Provider-Stack RabbitMQ instance is the central message broker. Each tenant gets a dedicated vHost (e.g. /tenant-acme) to ensure complete message isolation.

vHost Producer Consumer Content
cdm-metrics Provider Telegraf, IoT Bridge API Provider TimescaleDB Platform health metrics
/tenant-acme Tenant-Stack MQTT bridge Provider TimescaleDB (aggregated) Device telemetry
/tenant-beta

The Provider IoT Bridge API creates the vHost, AMQP user, and permissions automatically when a tenant JOIN request is approved.


JOIN Workflow (Phase 3 preview)

sequenceDiagram
    participant T as Tenant-Stack
    participant A as Provider IoT Bridge API
    participant KC as Provider Keycloak
    participant RMQ as Provider RabbitMQ
    participant SCA as Provider step-ca

    T->>A: POST /admin/tenants/{id}/join-request (Sub-CA CSR, WG public key)
    Note over A: Admin reviews + approves manually
    A->>SCA: sign Sub-CA CSR
    SCA-->>A: Tenant Sub-CA certificate
    A->>RMQ: create vHost /tenant-{id}, user, permissions
    A->>KC: register Tenant Keycloak as Identity Provider in cdm realm
    A-->>T: { sub_ca_cert, root_ca_cert, rabbitmq_url+creds, wg_peer_config }
    T->>T: install Sub-CA, configure MQTT bridge, apply WG config

Full details: Use Cases → Tenant Onboarding