Skip to content

feat(sensitive): bring the Ring Alarm into Home Assistant through ring-mqtt (ADR-0104) - #1256

Merged
Gerrrt merged 2 commits into
mainfrom
gerrrt/ring-mqtt
Oct 22, 2026
Merged

Gerrrt merged 2 commits into
mainfrom
gerrrt/ring-mqtt

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

What changed

Two new services in stacks/sensitive bring the Ring Alarm into Home Assistant: its base station, sensors, keypads and arming.

  • mosquitto: the MQTT broker.
    • It's on a new internal network, mqtt. Only Home Assistant and ring-mqtt join it (DIFFERENCE 14).
    • There are two users, and an ACL keeps ring-mqtt to ring/# and homeassistant/#. Anonymous access is refused.
    • Nothing is stored on disk. The password file is built at start, in a tmpfs, from two new SOPS values.
  • ring-mqtt: the bridge.
    • It runs as plain Node instead of the image's s6 init, so it runs as RENDER_UID, read-only, with no capabilities, and never git pulls code.
    • Its config, which holds the broker password, is written to a tmpfs and linked into /data.
    • The volume holds only Ring's refresh token, made once by the new runbook.
    • enable_cameras is false, so the cameras stay with the core integration and its go2rtc never starts.
    • It is not on sensitive. It joins only mqtt and ring-egress, a network for its way out to Ring with no other member. That keeps its unauthenticated token page on 55123, which runs while it has no working token, away from the tier's other services (review fix).

Also in this PR:

  • CI: both services are booted under their hardening, each with a smoke.sh.
  • Backups: ring-mqtt-data is listed as disposable.
  • Docs: ADR-0104, docs/runbooks/connect-the-ring-alarm.md, the stack README, architecture, security, the host runbook's secrets table, the README counts, and the changelog. The changelog also records today's move of Home Assistant's Ring entry to the location owner's account.

Why

The household wants the Ring Alarm in Home Assistant. The core Ring integration can't see it: its library doesn't speak Ring's alarm websocket. The stack README already said a broker would become "a pinned service here on the day a device needs it". ADR-0104 records the decision, the alternatives (a Z-Wave stick, or ring-mqtt as published) and the trade.

Blast radius

  • Host: trinity, and only the sensitive stack.

  • Home Assistant joins a second network, which has no gateway. Its routing, Caddy route and existing integrations are unchanged.

  • Ring's cloud: ring-mqtt connects from trinity exactly as Home Assistant's Ring integration already does. There are no new firewall rules and no new published ports.

  • Ring credentials: ring-mqtt will hold the Ring location owner's refresh token, which can arm and disarm the alarm. It can be revoked in Ring's Control Center.

  • No change to network segmentation or firewall rules

  • No new port published to a VLAN that could not already reach the service

  • No credential added outside secrets/*.sops.yaml

Verification

  • Mosquitto under the real hardening: check_hardened_boot.sh --service mosquitto reported healthy, read-only, CapDrop=ALL, and no-new-privileges. Its smoke.sh passed: both users sign in, a wrong password and an anonymous client are refused, and ring-mqtt's message to elsewhere/ never arrives.

  • ring-mqtt: its hardened boot couldn't run on trinity, because the live sensitive network owns the subnet and the throwaway copy clashes with it. CI runners don't have the live stack, so CI will run it. I booted it instead in a throwaway project on another subnet, with internal networks:

    • it ran as non-root on a read-only root;
    • it read /data/config.json through the tmpfs link (mode 600) and reached the point of waiting for a Ring token;
    • its smoke.sh passed;
    • idle memory was 48 MiB for ring-mqtt and 3 MiB for Mosquitto.
  • Network isolation: a container on sensitive could not reach mosquitto:1883. With ring-mqtt's token page up, a container on sensitive could not reach port 55123 at either of ring-mqtt's addresses; only mqtt could. ring-mqtt still reached api.ring.com through ring-egress.

  • Secrets: both are generated straight into SOPS and never printed; each is 24 alphanumeric characters, and they differ.

  • make validate passed. Skipped there:

    • yamllint, run separately on the compose file, ci.yml and the example secrets, clean;
    • ruff and ansible-lint, with no Python or Ansible changed;
    • the deployment-checkout and timer checks, which don't apply to a worktree;
    • one check CI covers.
  • Other checks: check_docs.py OK, lint.sh passed, shellcheck clean on both smoke scripts.

  • Not yet done: deployment, the Ring sign-in and the MQTT integration in Home Assistant. Those come after merge, by the runbook.

  • make validate passes (with the skips above)

  • Deployed to the lab and confirmed working

  • Docs updated (docs/, service README, or docs/roadmap.md)

🤖 Generated with Claude Code

…g-mqtt (ADR-0104)

Home Assistant's core Ring integration carries the cameras and doorbell but
not the Ring Alarm: its library does not speak Ring's alarm websocket, so
the base station's sensors, keypads and arming were invisible. ring-mqtt
does, publishing them to an MQTT broker with Home Assistant discovery.

- mosquitto: on a new internal network, `mqtt`, that only Home Assistant
  and ring-mqtt join (DIFFERENCE 14). Two users, an ACL keeping ring-mqtt to
  ring/# and homeassistant/#, no anonymous access, nothing on disk. Its
  argon2id password file is built in a tmpfs from SOPS on every start.
- ring-mqtt: plain Node instead of the image's s6 init, so it runs as
  RENDER_UID on a read-only root with no capabilities, and never git-pulls
  code. Its config is written to a tmpfs and linked into /data; the volume
  holds only the Ring refresh token, made once by the new runbook.
  enable_cameras is false, so the cameras stay the core integration's.
- CI boots both under their hardening, each with a smoke.sh: the broker's
  logins, refusals and ACL, and ring-mqtt reading its config.
- MQTT_HA_PASSWORD and MQTT_RING_PASSWORD in SOPS, the render list and the
  validation seed; ring-mqtt-data listed disposable for backups.
- ADR-0104, docs/runbooks/connect-the-ring-alarm.md, and the stack README,
  architecture, security, host runbook, README counts and changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 20:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The bridge's unauthenticated token-management UI remains reachable by peer containers during provisioning or authentication failure.

1 open issue
What changed in this PR

This PR adds a hardened Mosquitto broker and ring-mqtt bridge to the sensitive stack, bringing Ring Alarm devices into Home Assistant while leaving cameras with the existing core integration.

Changes:

  • Adds an internal MQTT network, authenticated topic access, and SOPS-backed broker credentials.
  • Adds hardened-boot CI checks and smoke tests for both services.
  • Documents the architecture, sign-in procedure, security tradeoffs, and disposable token storage.
File Description
stacks/​sensitive/​ring-mqtt/​smoke.sh Checks bridge startup and configuration loading.
stacks/​sensitive/​README.md Documents services, isolation, and operation.
stacks/​sensitive/​mosquitto/​smoke.sh Checks authentication and topic permissions.
stacks/​sensitive/​mosquitto/​mosquitto.conf Configures an authenticated, nonpersistent broker.
stacks/​sensitive/​mosquitto/​acl Defines each client's topic access.
stacks/​sensitive/​compose.yaml Adds hardened services, MQTT networking, and token storage.
secrets/​sensitive.sops.yaml Adds encrypted broker credentials.
secrets/​sensitive.example.yaml Documents the new credential keys.
scripts/​seed-validation-env.sh Supplies validation-only MQTT credentials.
scripts/​render-config.sh Includes MQTT credentials in rendering.
scripts/​backup-volumes.sh Excludes disposable bridge state from backups.
README.md Updates ADR and runbook counts.
docs/​security.md Describes broker isolation and token security.
docs/​runbooks/​connect-the-ring-alarm.md Documents sign-in, MQTT setup, rotation, and rebuilding.
docs/​runbooks/​build-the-sensitive-tier-host.md Adds broker credentials to provisioning guidance.
docs/​changelog.md Records account migration and the new integration work.
docs/​architecture.md Adds the Ring Alarm integration to the tier description.
docs/​adr/​0104-bring-the-ring-alarm-into-home-assistant-through-ring-mqtt.md Records the decision, alternatives, and tradeoffs.
.github/​workflows/​ci.yml Adds hardened boots and smoke-test cache inputs.

Review effort: Balanced

Comment thread stacks/sensitive/compose.yaml Outdated
Review on the ring-mqtt PR: while ring-mqtt has no working Ring token it
serves an unauthenticated sign-in page on 55123 on every interface, and on
`sensitive` any service beside it could start a Ring sign-in there.

ring-mqtt now joins only `mqtt` and `ring-egress`, a new network for its way
out to Ring with no other member. Tested in a throwaway boot with the page
up: unreachable from a container on `sensitive` at both of ring-mqtt's
addresses; reachable only on `mqtt`, whose other members are the broker and
Home Assistant, which already holds the same owner's Ring sign-in; and
api.ring.com still reachable from ring-mqtt.

DIFFERENCE 14, ADR-0104, the stack README and security.md say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Gerrrt
Gerrrt merged commit 929625d into main Oct 22, 2026
8 of 9 checks passed
@Gerrrt
Gerrrt deleted the gerrrt/ring-mqtt branch October 22, 2026 21:53
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.

2 participants