Repository navigation
feat(sensitive): bring the Ring Alarm into Home Assistant through ring-mqtt (ADR-0104) - #1256
Merged
Merged
Conversation
…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>
There was a problem hiding this comment.
🟡 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
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>
5 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What changed
Two new services in
stacks/sensitivebring the Ring Alarm into Home Assistant: its base station, sensors, keypads and arming.mosquitto: the MQTT broker.mqtt. Only Home Assistant and ring-mqtt join it (DIFFERENCE 14).ring/#andhomeassistant/#. Anonymous access is refused.ring-mqtt: the bridge.RENDER_UID, read-only, with no capabilities, and nevergit pulls code./data.enable_camerasis false, so the cameras stay with the core integration and its go2rtc never starts.sensitive. It joins onlymqttandring-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:
smoke.sh.ring-mqtt-datais listed as disposable.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
trinityexactly 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.yamlVerification
Mosquitto under the real hardening:
check_hardened_boot.sh --service mosquittoreported healthy, read-only,CapDrop=ALL, and no-new-privileges. Itssmoke.shpassed: both users sign in, a wrong password and an anonymous client are refused, and ring-mqtt's message toelsewhere/never arrives.ring-mqtt: its hardened boot couldn't run on
trinity, because the livesensitivenetwork 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:/data/config.jsonthrough the tmpfs link (mode 600) and reached the point of waiting for a Ring token;smoke.shpassed;Network isolation: a container on
sensitivecould not reachmosquitto:1883. With ring-mqtt's token page up, a container onsensitivecould not reach port 55123 at either of ring-mqtt's addresses; onlymqttcould. ring-mqtt still reached api.ring.com throughring-egress.Secrets: both are generated straight into SOPS and never printed; each is 24 alphanumeric characters, and they differ.
make validatepassed. Skipped there:ci.ymland the example secrets, clean;Other checks:
check_docs.pyOK,lint.shpassed, 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 validatepasses (with the skips above)Deployed to the lab and confirmed working
Docs updated (
docs/, service README, ordocs/roadmap.md)🤖 Generated with Claude Code