Skip to content

feat(mqtt): MQTT subscription management in the instance console#2

Merged
xe-nvdk merged 2 commits into
mainfrom
feat/mqtt-management
Jul 10, 2026
Merged

feat(mqtt): MQTT subscription management in the instance console#2
xe-nvdk merged 2 commits into
mainfrom
feat/mqtt-management

Conversation

@xe-nvdk

@xe-nvdk xe-nvdk commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

Adds an MQTT tab to the instance console for managing an Arc instance's MQTT ingest subscriptions. Ports the MQTT management capability from Arc into Launchpad. Arc owns all state (its own SQLite, lifecycle, AES-encrypted passwords); Launchpad drives Arc's /api/v1/mqtt/* API through the existing instance proxy — no new Launchpad tables or routes.

  • ArcClient (src/lib/arcClient.ts): MQTT types + methods — health, list/get/create/update/delete, start/stop/pause/restart, stats. Passwords are write-only (reads expose only has_password); id path segments are encodeURIComponent'd.
  • MqttSubscriptions.svelte: list with live stats + status badges, create/edit dialog (broker, topics, QoS, database, auth, TLS, topic→db mapping, tuning), lifecycle actions, disabled-MQTT + no-access empty states, edit-while-running guard, numeric-field coercion.
  • Console: new MQTT tab between Alerts and Tokens.

Proxy hardening (default-closed authorization)

The proxy injects the instance admin token, so every proxied request is admin-privileged on Arc. This PR flips the proxy to default-closed:

  • Plain members get a read-only allowlist (query, databases, measurements, metrics, logs, health); everything else — all mutations and admin routes (mqtt, retention, delete, rbac, backup, tokens, …) — requires org owner/admin. New Arc admin routes are locked down by default instead of viewer-reachable.
  • Paths are canonicalized (collapse //, resolve ./ and ../, lowercase) before the role check, matching how Arc (fasthttp) normalizes — so api/v1//mqtt or api/v1/./mqtt can't smuggle an admin path past the gate.
  • Client-side role gating in the component is UX only; the proxy check is authoritative.

Behavior change: members who previously reached retention / continuous-queries / token admin through the proxy now need owner/admin for mutating operations (read/list still works for the read allowlist). This tightens access and matches Arc's own RequireAdmin model.

Test plan

  • npm run check — 0 errors; production build succeeds
  • End-to-end against a mock Arc implementing the /api/v1/mqtt/* contract, with real Launchpad session cookies:
    • Owner: health, list, create (has_password:true), start → stats (live counters) → start-again 409, delete → 404, duplicate-name 409
    • Viewer: 403 on every admin path (mqtt list/create/start, retention, delete, rbac, backup) and on normalization-bypass variants (//mqtt, ./mqtt, mqtt/../mqtt); retains GET reads + POST query; POST to a read path (databases) correctly 403
    • Owner reaches the normalized //mqtt variant (200)
  • MQTT tab present in rendered console; component + ArcClient methods in the client bundle
  • Internal review round (2 agents) — caught & fixed: proxy normalization bypass, default-open polarity, number-input string coercion, lexicographic reconnect validation, toast grammar
  • Reviewer: confirm the members-lose-mutating-access-to-retention/CQ/tokens behavior change is intended

Notes

  • Requires MQTT enabled on the Arc instance ([mqtt] enabled = true) — the tab detects the disabled state and shows guidance.
  • Passwords require ARC_ENCRYPTION_KEY on the Arc side; the UI surfaces Arc's error if unset.

🤖 Generated with Claude Code

Ignacio Van Droogenbroeck and others added 2 commits July 10, 2026 10:10
Adds an "MQTT" tab to the instance console that manages an Arc instance's
MQTT ingest subscriptions. Arc owns all state; Launchpad drives Arc's
/api/v1/mqtt/* API through the existing instance proxy — no new Launchpad
tables or routes.

- ArcClient: MQTT types + methods (health, list/get/create/update/delete,
  start/stop/pause/restart, stats). Passwords are write-only; reads expose
  only has_password. id path segments are encodeURIComponent'd.
- MqttSubscriptions.svelte: list with live stats + status badges, create/edit
  dialog (broker/topics/QoS/database/auth/TLS/topic-mapping/tuning), lifecycle
  actions, disabled-MQTT and no-access empty states, edit-while-running guard.
- Console: new MQTT tab between Alerts and Tokens.

Proxy hardening (default-closed authorization):
- The proxy injects the instance admin token, so every proxied request is
  admin-privileged on Arc. Plain members are now restricted to a read-only
  allowlist (query/databases/measurements/metrics/logs/health); everything
  else — all mutations and admin routes (mqtt, retention, delete, rbac,
  backup, tokens, …) — requires org owner/admin. New Arc admin routes are
  locked down by default rather than viewer-reachable.
- Paths are canonicalized (collapse //, resolve ./ and ../, lowercase) BEFORE
  the role check, matching how Arc normalizes, so slash/dot spellings can't
  smuggle an admin path past the gate. Client-side role gating in the
  component is UX only; the proxy check is authoritative.

Verified end-to-end against a mock Arc: owner full CRUD+lifecycle, viewer
blocked on every admin path and normalization-bypass variant (403) while
retaining read/query access.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Security (HIGH):
- Remove api/v1/logs from the member read allowlist — Arc guards it with
  withAdminAuth (it leaks SQL, internal IPs, tokens), so a viewer must not
  reach it through the proxy's injected admin token. Verified each allowlist
  entry against Arc's actual per-route auth (databases GET/measurements/query/
  metrics are non-admin; logs is admin-only). Owners/admins still reach logs.
- Forward the CANONICAL path (the one we authorize) upstream instead of the
  raw params.path, so the gated string and the routed string are byte-identical
  — closes any residual raw-vs-canonical normalization gap.

Component correctness:
- parseTopicMapping: split on the LAST '=' (a database name can't contain '=',
  but a topic legally can) — fixes silent topic/db corruption for '='-topics.
- parseTopics: newline-only (a comma is a legal topic char) + de-dupe to avoid
  double-subscribe on copy-paste.
- toInt: strict digits-only parse — reject "60abc"/"1e9"/"1.5"/"0x10" (parseInt
  silently truncated these, sending values the user never intended) → fallback.
- arcClient: get/create/update/stats now throw on a malformed (missing-key) 200
  instead of returning undefined typed as the interface.

UX regression from the proxy tightening:
- Retention / Continuous Queries / Tokens tabs now show a clean "Admin access
  required" message to viewers instead of a raw {"error":"Forbidden"} banner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xe-nvdk
xe-nvdk merged commit 24b6d5a into main Jul 10, 2026
xe-nvdk pushed a commit that referenced this pull request Jul 10, 2026
Bump package + Helm chart to 0.2.0 for the release.

Highlights since v0.1.2:
- Security hardening: auth, SSRF (byte-level guard + DNS pinning), secrets,
  multi-tenant proxy authorization (#1)
- MQTT subscription management in the instance console (#2)
- Private/localhost Arc endpoints: actionable error + docs + opt-in flag (#3)
- Fix connecting to a local/private Arc: legacy schema migration + dual-stack
  IP fallback (#4)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant