12 KiB
Harbor device ecosystem contract
Use this reference for Gateway advertisement, discovery, pairing, connected-device lists, or integration with future Harbor-capable subsystems.
Layer ownership
| Layer | Source of truth | Identity | Lifetime | Meaning |
|---|---|---|---|---|
| Discovery | DNS-SD browser cache | advertised Gateway instance | ephemeral | a compatible service may be reachable |
| Gateway trust | client pairing store | gatewayId plus pinned public identity |
persisted | this is a Gateway the owner paired |
| Peer registry | Gateway pairing store | stable client installation ID plus public identity | persisted | this client belongs to the Harbor ecosystem |
| Presence | Gateway session owner | authenticated client ID plus lease | ephemeral | this paired client is currently connected to the control plane |
| Route | Connect route owner | selected gatewayId plus applied route state |
runtime/canonical | this client currently intends to use this Gateway |
| Network inventory | existing device inventory | Gateway-scoped MAC-derived dev_* ID |
observed/persisted | this network endpoint was seen and may own traffic or policy |
| Binding | Gateway backend projection | ecosystem client ID -> network inventory ID | derived | current correlation, never trust identity |
Do not reuse the current MAC-derived dev_* ID as an ecosystem ID. For a future fleet view, scope network inventory IDs by gatewayId.
Minimal discovery profile
Advertise one TCP DNS-SD service such as _harbor-gw._tcp.local.. Use the service instance name as a short, user-friendly Gateway name; do not encode a MAC address or serial number in it.
Use SRV/A/AAAA for endpoint resolution. Keep TXT metadata small and additive:
txtvers=1for the TXT schema;protovers=1for the application protocol;- public
id=<gatewayId>for candidate deduplication; - compact capability flags and whether a pairing window is open.
Never advertise credentials, pairing invitations, subscription data, client names, client counts, or private network policy. Unknown TXT keys must be ignored. Resolve addresses immediately before connection and verify the paired identity after connecting.
mDNS is link-local. For another VLAN or routed segment, use an explicitly configured address, QR/manual endpoint, or unicast DNS-SD. Do not invent a custom UDP broadcast or assume multicast crosses routers.
In the current Gateway deployment, first inspect the Compose network boundary: the control service is bridge-networked while the dataplane owns host networking. Keep identity and trust in control, but publish the public DNS-SD descriptor through host Avahi or a narrow host-network publisher. Do not move the whole control plane to host networking merely to gain multicast. On macOS, prefer the native Bonjour/dns-sd surface through the existing host-side network monitor over a second custom discovery stack.
Discovery answers only “where might a Gateway be?” It does not answer “do I trust it?”, “am I connected?”, or “is my traffic routed through it?”.
Identity and pairing
Maintain a stable Gateway identity and a stable key pair in persisted, backup-aware storage. Maintain a stable installation identity and key pair per client; store the client private key in the platform credential store, including macOS Keychain.
Do not reuse the current subscription hwid as public ecosystem identity: it is also sent to the subscription provider as x-hwid. Create a separate local identity so LAN and provider correlation remain independent.
Model the minimum persisted relationship records:
PairedGateway = gatewayId, displayName, pinnedPublicIdentity,
capabilities, pairedAt
PairedDevice = deviceId, displayName, deviceType, publicIdentity,
capabilities, pairedAt, revokedAt?
Treat display names, platform, version, and capabilities as metadata, not authentication or authorization.
Use this pairing sequence:
- Let the Gateway open a bounded pairing window and create a short-lived, single-use invitation.
- Bind the invitation to the Gateway ID and public-identity fingerprint.
- Let the client create or load its installation key and submit its public identity and minimal metadata.
- Confirm the peer on a trusted surface before committing both records.
- Issue a per-device credential or register proof-of-possession for later authenticated requests.
- Consume the invitation atomically; reject expiry, replay, mismatch, or reuse.
- Support revoke and credential rotation without changing unrelated LAN inventory.
Persist each local trust record atomically and make finalization idempotent so an interrupted client can resume or safely retry. Pairing spans two devices and is not a distributed transaction; never report it as cross-device atomicity.
For the smallest safe first slice, encode the same grouped high-entropy invitation in QR and manual copy/paste form. If the owner requires a short numeric code, use a vetted password-authenticated key exchange such as SPAKE2 and mutual key confirmation, or require an independent confirmation that provides equivalent protection. Use platform or Node standard cryptography for identity and signatures; do not design custom cryptography or add a crypto dependency without need.
Do not use the subscription URL as a pairing credential. Keep the existing subscription-HMAC presence only as an explicitly labelled legacy trust source during the transition window.
Authenticated presence and route state
After pairing, let the client renew a bounded authenticated lease. Start with periodic requests using the existing control-plane API shape, but protect invitations and credentials with pinned end-to-end identity and confidentiality; the current plaintext HTTP transport alone is not sufficient. Do not add WebSocket, broker, or event-stream infrastructure until a concrete server-push feature requires it.
The Gateway derives:
paired = persisted relationship exists and is not revoked
connected = paired and authenticated lease has not expired
routed = connected and current Connect route state selects this gatewayId
inventory = optional current network binding exists
Store or project lastAuthenticatedAt and leaseExpiresAt; derive status rather than persisting a mutable online boolean. Use Gateway receipt time for freshness. Let the client report deviceType, app/protocol version, capabilities, and selected route as authenticated metadata, but distinguish a reported route from dataplane-confirmed traffic.
Discovery failure marks the candidate stale. It does not revoke pairing or immediately terminate a still-valid lease. Lease expiry marks the client disconnected without deleting its relationship. Revocation invalidates the credential immediately.
Joining a Mac to the Gateway device view
When a paired Mac renews its lease:
- Authenticate its stable ecosystem identity.
- Derive the remote source address from the accepted connection; do not trust a claimed IP.
- Correlate that address with the current neighbor snapshot to obtain an optional MAC-derived inventory ID.
- Publish the relationship, presence, route, and binding together from the Gateway backend.
Represent the binding with networkDeviceId, confidence, and observedAt. A randomized MAC, DHCP change, or interface switch updates or removes only this binding. The stable Mac relationship remains.
If the source address is NATed, missing, duplicated, or maps to an ambiguous neighbor, keep the Mac paired/connected but leave traffic and policy attribution unavailable. Never attach another device's counters by hostname or client-provided MAC.
Expose one revisioned Gateway projection to the UI. It may extend the current device snapshot or use a dedicated ecosystem snapshot, but the backend must perform the join. React must not fetch authoritative pairing, presence, and inventory lists independently and guess the relationship.
Multi-Gateway behavior
- Let Connect persist several
PairedGatewayrecords keyed by stable Gateway ID. - Let discovery resolve zero or more current endpoints for each identity.
- Keep one active route target and make selection or automatic policy explicit.
- Require a paired identity match before automatic handoff. A new advertisement is never enough.
- Preserve a verified active Gateway through transient discovery failure, but surface stale freshness.
- Let one Gateway register many clients without assuming macOS; branch on negotiated capabilities, not hard-coded platform paths.
- Do not add fleet federation. If it becomes real, aggregate with compound
{gatewayId, deviceId}identities.
Canonical data and failure rules
- Give the pairing registry its own
schemaVersionand monotonicrevision, or include it in an existing canonical aggregate with equivalent guarantees. - Reuse the existing atomic JSON-store pattern before considering a database or another persistence dependency.
- Make pair, rename, revoke, and credential rotation atomic. Return the complete new snapshot after a mutation.
- Keep active leases process-local unless restart continuity has a demonstrated need. Persist last-seen metadata at a bounded cadence if required; never write every heartbeat by default.
- Apply incoming frontend snapshots only when their revision is not older than the current one. Keep transport stale/error state outside the domain snapshot.
- Preserve last-good discovery and registry data on source errors; expose freshness and the error separately.
- Treat public-key mismatch as an identity error requiring explicit repair or re-pairing, not an address update.
- Treat an unsupported protocol version as an incompatible state, not a generic offline state.
Compatibility sequence
- Add the new identity, discovery, pairing, and presence contract without changing current route behavior.
- Prefer evolving the existing Gateway presence protocol to a version that signs challenges with ecosystem identity instead of creating a second overlapping presence subsystem.
- Keep the subscription-HMAC default-gateway proof as a labelled
legacy-default-routesource for one documented transition release. - Publish the stable
gatewayIdin canonical route state; keep address, UI origin, and freshness as changeable observations. - Prefer a paired Gateway when its verified identity matches the current route candidate. Preserve the approved sticky verified-Gateway behavior through transient discovery failure.
- Never auto-mint a pairing record from a matching subscription URL.
- When forgetting the active Gateway, demote route state atomically before deleting trust.
- Preserve existing
dev_*inventory IDs, aliases, traffic totals, and policies; add only a derived ecosystem binding. - Remove the legacy proof only after migration evidence and an explicit cutover task.
Automated evidence
Cover at least:
- zero, one, and multiple advertisements; deduplication and endpoint changes;
- mDNS loss and recovery without unpairing;
- no secrets in TXT metadata or logs;
- invitation expiry, replay, mismatch, and atomic single-use consumption;
- valid/invalid proof-of-possession, credential rotation, and revocation;
- lease renewal and expiry using Gateway receipt time;
- paired/connected/routed state separation;
- DHCP, interface, and randomized-MAC changes;
- missing and ambiguous inventory binding without false traffic attribution;
- multiple Gateways with one active route;
- revision ordering, migration, rollback, and legacy transition.
Standards basis
These standards define discovery and an available PAKE building block. They do not make an mDNS advertisement trustworthy; preserve end-to-end identity verification.