Brocker configuration
Authoritative configuration reference

Broker management and delivery configuration

The running PSSP Broker independently authenticates clients, authorizes topic and delivery access, and owns SQLite. WebService and other provisioning applications communicate only with the encrypted management listener.

Single-Broker design

This implementation deliberately has no Broker clustering, replication, quorum, partition ownership, or cross-node coordination. Run one authoritative low-latency Broker and connect multiple remote worker/client instances to it. Multiple WebService workers are supported; multiple Broker replicas are not.

Security and storage boundary

ComponentWhat it knowsWhat it must not do
PSSP BrokerSQLite path, Argon2id hashes, ACLs, subscription grants, management allow-list, audit actor identity.Call WebService or interpret application payloads.
WebServiceManagement host/port, its service ID and Ed25519 private key, pinned Broker management public key.Open SQLite, receive its path, launch the Broker executable, or validate PSSP client passwords.
PSSP client/workerIts one-time client ID, username, password, normal PSSP endpoint, and pinned data-plane Broker public key.Select unauthorized delivery mode/group or create another principal.

The Broker takes an exclusive operating-system lock on <sqlite_path>.lock for its lifetime. Offline administration fails while the Broker is active. The setup command is the bootstrap exception and must run before serve.

Complete AES-only TOML example

Use different X25519 private keys for normal PSSP traffic and management traffic. Replace every placeholder and keep the file readable only by the Broker service account.

[listener]
tcp_bind = "0.0.0.0:6688"
udp_bind = "0.0.0.0:6688"

[tls]
enabled = false

[encryption]
enabled = true
static_private_key = "REPLACE_WITH_DATA_PLANE_X25519_PRIVATE_KEY"

[transport]
allow_insecure_tcp_udp = false

[limits]
max_connections = 2000
max_header_bytes = 1024
max_payload_bytes = 65536
max_outbound_queue_bytes = 4194304
idle_timeout_seconds = 120
subscriber_resume_ttl_seconds = 300

[storage]
sqlite_path = "/var/lib/pssp/pssp-broker.sqlite3"
unix_owner_user = "pssp"
unix_owner_group = "pssp"
auth_cache_seconds = 30
auth_cache_entries = 4096

[topic_defaults]
max_bytes = 67108864
max_messages = 10000
max_age_seconds = 600

[management]
enabled = true
bind = "127.0.0.1:6690"
encryption_enabled = true
static_private_key = "REPLACE_WITH_MANAGEMENT_X25519_PRIVATE_KEY"
max_request_bytes = 65536
authentication_timeout_seconds = 10
request_timeout_seconds = 15

[[management.allowed_services]]
service_id = "oxrecorder-webservice"
enabled = true
ed25519_public_key = "REPLACE_WITH_RAW_32_BYTE_ED25519_PUBLIC_KEY_BASE64URL"
permissions = ["principal.provision", "principal.delete"]
principal_kinds = ["device"]
client_id_patterns = ["ox-device-*"]
username_patterns = ["device-*"]
allowed_delivery_modes = ["broadcast"]
allowed_share_groups = []
publish_topic_patterns = [
  "/api/devices/*/heartbeat",
  "/api/devices/*/audio/chunks",
]
subscribe_topic_patterns = ["/api/devices/*/commands"]

[[management.allowed_services]]
service_id = "worker-deployment-controller"
enabled = true
ed25519_public_key = "REPLACE_WITH_SECOND_RAW_ED25519_PUBLIC_KEY_BASE64URL"
permissions = ["principal.provision", "principal.delete"]
principal_kinds = ["worker"]
client_id_patterns = ["ox-*-worker-*"]
username_patterns = ["worker-*"]
allowed_delivery_modes = ["shared"]
allowed_share_groups = ["audio-workers", "device-sync-workers"]
publish_topic_patterns = ["/api/devices/+/commands"]
subscribe_topic_patterns = [
  "/api/devices/+/heartbeat",
  "/api/devices/+/audio/chunks",
]

Management field reference

FieldMeaning
enabledStarts the independent management TCP listener. When false, no remote provisioning endpoint exists.
bindManagement address. Prefer loopback, a private management interface, or a firewall-restricted address; do not expose it publicly.
encryption_enabledMust be true. Management traffic always uses X25519/HKDF and AES-256-GCM without TLS.
static_private_keyBase64url-without-padding 32-byte X25519 private key for management. It is separate from [encryption].static_private_key.
max_request_bytesMaximum decrypted JSON management request, 1 to 1,048,576 bytes.
authentication_timeout_secondsTime allowed to answer the Broker challenge.
request_timeout_secondsMaximum silent interval while an authenticated management connection waits for its next request.

Allowed-service fields

FieldMeaning
service_idStable application identity included in the signed challenge and credential audit actor.
enabledEmergency allow/deny switch. Unknown or disabled services are rejected before any database work.
ed25519_public_keyRaw 32-byte Ed25519 public key encoded as base64url without padding. The matching private key remains with the service. Follow the generation steps below; never paste the PEM private key here.
permissionsprincipal.provision, principal.delete, principal.list, and/or principal.update. The desktop Access tab needs principal.list to review accounts, principal.provision to add them, principal.update to change them, and principal.delete to remove them. No permission implies denial.
principal_kindsPrincipal kinds the service may create. Management never permits creation of administrator.
client_id_patterns, username_patternsSimple glob allow-lists where * matches zero or more characters. Every requested identity must match.
allowed_delivery_modesbroadcast, shared, or both. The service cannot assign a mode outside this list.
allowed_share_groupsExact group names a service may assign. Required when shared is allowed.
publish_topic_patterns, subscribe_topic_patternsTopic-scope allow-lists. A segment containing only * matches one concrete segment. Use a literal + when authorizing a worker wildcard filter. A literal # grants an explicitly trusted service all valid PSSP filters.

Create the Ed25519 key for an allowed service

Each [[management.allowed_services]] entry needs a distinct Ed25519 key pair. The Broker stores the public key in TOML; the management application keeps the private PEM and signs its requests with it.

  1. Choose a protected location for the service private key. For a local desktop operator, run this from the repository root:
    cd broker
    cargo run -- generate-management-service-key \
      --private-key-file config/desktop-operator-ed25519.pem
    The command creates config/desktop-operator-ed25519.pem with owner-only permissions and prints one base64url public key.
  2. Copy only the printed value into the matching service entry:
    [[management.allowed_services]]
    service_id = "desktop-operator"
    ed25519_public_key = "PASTE_THE_PRINTED_PUBLIC_KEY_HERE"
  3. Keep the PEM with the service that connects to the management listener. Do not commit it, put it in pssp.toml, or send it to the Broker. To print the public key again later, without displaying the private key, run:
    cd broker
    cargo run -- management-service-public-key \
      --private-key-file config/desktop-operator-ed25519.pem
  4. If the PEM is replaced, generate a new public key, update ed25519_public_key, and restart the Broker. The old private key will no longer authenticate.

Generate and protect keys

  1. Generate the Broker data-plane and management X25519 private keys separately.
    pssp-broker generate-aes-only-private-key
    pssp-broker generate-aes-only-private-key
  2. Derive each public key for pinning. The command accepts a protected file or hidden terminal prompt.
    pssp-broker aes-only-public-key --private-key-file /run/secrets/pssp-management-x25519
  3. Generate one Ed25519 identity for each management application. The command writes a mode-0600 PKCS#8 PEM private key and prints the raw public key for TOML.
    pssp-broker generate-management-service-key \
      --private-key-file /run/secrets/pssp-webservice-ed25519.pem
  4. Print the public key again later without exposing the private key.
    pssp-broker management-service-public-key \
      --private-key-file /run/secrets/pssp-webservice-ed25519.pem

Never copy a service private key into Broker TOML. Never put the Broker private keys in WebService environment variables. Distribute only public pins through a trusted deployment channel.

Management authentication sequence

  1. The Broker sends a fresh random challenge and its management X25519 public key.
  2. The service verifies the public key equals its pinned value, creates an ephemeral X25519 key and salt, and signs the canonical service ID, challenge, Unix timestamp, ephemeral public key, and salt with Ed25519.
  3. The Broker finds the exact enabled service_id, verifies the signature with configured public key, checks ±30 seconds clock skew, and derives a session key with X25519/HKDF-SHA256.
  4. Both sides use sequenced AES-256-GCM frames. Replayed, reordered, modified, unsigned, unknown, or disabled-service requests are rejected.
  5. For every request the Broker rechecks permission, principal kind, identity pattern, topics, delivery mode, and share group before its internal credential store opens a transaction.

Successful provisioning creates or rotates the principal atomically, replaces ACLs and delivery grants, stores only an Argon2id password hash, records the authenticated service_id in audit, and returns the generated plaintext password once.

Provisioning request and scope examples

{
  "operation": "provisionPrincipal",
  "clientId": "ox-device-OXRC-001",
  "username": "device-OXRC-001",
  "principalKind": "device",
  "passwordBytes": 32,
  "publish": [
    "/api/devices/OXRC-001/heartbeat",
    "/api/devices/OXRC-001/audio/chunks"
  ],
  "subscribe": ["/api/devices/OXRC-001/commands"],
  "deliveryGrants": [{
    "topicFilter": "/api/devices/OXRC-001/commands",
    "deliveryMode": "broadcast",
    "shareGroup": ""
  }]
}

The WebService service above can create only device identities with those exact device topic shapes and broadcast command delivery. It cannot create an administrator, grant #, create a worker, join audio-workers, or delete an identity outside its configured patterns.

If an older management client omits deliveryGrants, the Broker creates broadcast grants for its subscribe ACLs. Shared-worker provisioning must always send explicit delivery grants. If a normal PSSP client omits shareGroup and the principal has exactly one grant for that filter, the Broker applies that grant automatically; the client cannot override it.

{
  "operation": "provisionPrincipal",
  "clientId": "ox-audio-worker-a",
  "username": "worker-a",
  "principalKind": "worker",
  "publish": [],
  "subscribe": ["/api/devices/+/audio/chunks"],
  "deliveryGrants": [{
    "topicFilter": "/api/devices/+/audio/chunks",
    "deliveryMode": "shared",
    "shareGroup": "audio-workers"
  }]
}

workerID, shared workers, and broadcast clients

clientId authenticates and authorizes a principal. workerID is the durable ACK/resume identity supplied in SUBSCRIBE. The connection ID identifies only the current TCP session. shareGroup identifies competing workers, but the Broker accepts it only when an exact persisted delivery grant authorizes it.

ConnectionworkerIDGrantResult for one message
Aaudio-worker-ashared / audio-workersEligible shared worker.
Baudio-worker-bshared / audio-workersEligible shared worker.
Caudio-worker-cshared / audio-workersEligible shared worker.
Dviewer-dbroadcastAlways receives its independent copy.

For A/B/C/D, the Broker sends each message to exactly one of A, B, or C by local round-robin and also sends a copy to D. A/B/C share one group cursor. If the selected worker disconnects before MSGACK, the pending QoS 1 message is reassigned to another connected group member. D owns a cursor keyed by its workerID and can reconnect with from: "resume".

Multiple broadcast worker IDs each receive a copy; different shared groups each receive one copy. A worker cannot omit its configured group to gain broadcast access, and a viewer cannot name a worker group. Delivery is at least once, so workers must make processing idempotent. A slow or full outbound client queue does not block publisher acknowledgement or other subscribers.

Compatibility

The Broker temporarily accepts the old subscriberId header as an input alias. Updated clients send workerID; new integrations must use workerID.

WebService configuration

PSSP_DEVICE_PROVISIONING_ENABLED=true
PSSP_MANAGEMENT_HOST=127.0.0.1
PSSP_MANAGEMENT_PORT=6690
PSSP_MANAGEMENT_SERVICE_ID=oxrecorder-webservice
PSSP_MANAGEMENT_PRIVATE_KEY_FILE=/run/secrets/pssp-webservice-ed25519.pem
PSSP_MANAGEMENT_BROKER_PUBLIC_KEY=PINNED_MANAGEMENT_X25519_PUBLIC_KEY
PSSP_MANAGEMENT_TIMEOUT_MS=15000
PSSP_DEVICE_CLIENT_ID_PREFIX=ox-device
PSSP_DEVICE_USERNAME_PREFIX=device

After POST /api/me/devices completes registration, WebService requests one device principal from the Broker. The response contains pssp_client_id, pssp_username, and the one-time pssp_password. WebService returns them to the device but does not store the plaintext password. Device deletion requests deletePrincipal through the same authenticated listener.

The old PSSP_BROKER_BINARY, PSSP_BROKER_CONFIG, and PSSP_BROKER_WORKING_DIRECTORY settings are removed. They must not be restored.

Initialization, migration, and startup

  1. Back up the existing SQLite file and stop the old Broker.
  2. Add [management] and explicit allowed services to the protected TOML. Generate all keys and install service private keys separately.
  3. Run setup once if this is a new database.
    pssp-broker setup --config /etc/pssp/pssp.toml \
      --bootstrap-client-id pssp-admin --bootstrap-username admin
  4. Start pssp-broker serve. Schema version 1 databases migrate to version 2 by creating structured delivery grants for existing subscriptions in broadcast mode.
  5. Configure WebService with management endpoint and keys, then restart it. Confirm it has no SQLite/Broker binary/config path.
  6. Provision worker principals through a separately authorized deployment controller. Give every worker instance a unique workerID and the same approved shareGroup.

The supplied offline credential CLI remains useful before service startup. Once the Broker holds its SQLite lock, direct commands fail and applications must use the authenticated management listener. Treat operating-system access capable of stopping the Broker or reading its protected files as host-administrator access.

Security failures and troubleshooting

Error/observationCheck
Management connection rejected immediatelyService ID exists and is enabled; Ed25519 public/private keys match; clocks differ by no more than 30 seconds.
pssp_management_broker_public_key_pin_mismatchThe WebService public pin is not derived from management.static_private_key. Do not bypass the check.
management_not_authorizedPermission, principal kind, identity glob, topic pattern, delivery mode, or share group is outside the allowed-service scope.
subscription_delivery_not_authorizedThe authenticated principal lacks an exact structured grant for requested filter plus broadcast/shared group.
Offline CLI says Broker owns SQLiteThe service is running as designed. Use the management endpoint, or stop it only for approved maintenance/setup.
Only one of A/B/C receives dataExpected for the same shared group. D needs an independent broadcast grant to receive a separate copy.
Duplicate processing after failureExpected at-least-once behavior. Deduplicate by topic sequence/application message ID before committing side effects.
High audio latencyDo not add Broker replicas. Inspect worker queue depth, payload processing, network path, and per-session outbound capacity on the single Broker.