Skip to content

Close go2rtc's unauthenticated API to clients outside the container - #111

Merged
SteveGilvarry merged 2 commits into
ZoneMinder:masterfrom
SteveGilvarry:go2rtc-proxy-lockdown
Oct 25, 2026
Merged

SteveGilvarry merged 2 commits into
ZoneMinder:masterfrom
SteveGilvarry:go2rtc-proxy-lockdown

Conversation

@SteveGilvarry

Copy link
Copy Markdown
Member

Security fix. go2rtc's API has no login, and ZoneMinder's login doesn't cover /zm/go2rtc/. So since #105, anyone who can reach ZoneMinder's web port can read and rewrite go2rtc's config and add streams. Before #105, the same API was open on the published port 1984, so this isn't new exposure, but it needs closing. go2rtc itself refuses exec/echo/expr and raw ffmpeg sources added through the API. Its config endpoint is the risky part. go2rtc's docs, and go2rtc #2155, describe what happens when the API is exposed.

What changes

From Gets
Inside the container (loopback), and GO2RTC_API_ALLOW the whole API, as before
Anyone else the live view WebSocket for ZoneMinder's stream names only (^src=<id>(_CameraDirectPrimary|_CameraDirectSecondary|_ZoneMinderPrimary)?$), and the HLS fallback paths

Config, adding streams, URLs as sources, extra query parameters and go2rtc's own web page now get 403 from outside.

zmc registers its streams through GO2RTC_PATH. With a hostname in ZM_PUBLIC_URL, #105's /etc/hosts entry sends that over loopback, so nothing changes for the documented setup.

Who needs to act

  • ZM_PUBLIC_URL is an IP address: zmc's calls come in through the host, so go2rtc refuses them and live view goes blank. The README already said to use a hostname. init-config now logs a warning that says so.
  • ZM_PUBLIC_URL is https behind a reverse proxy: set the new GO2RTC_API_ALLOW to the proxy's address. It takes IPs or CIDR ranges, space separated. init-config warns if it's missing and refuses malformed values.

Still open: live view streams need no login. Anyone who can reach the web port can watch a monitor if they know its ID. ZoneMinder's browser code connects with no credentials, so closing that needs a change in ZoneMinder upstream, such as a token that go2rtc checks. The README says to keep the port on a trusted network.

Tested

  • Access checks on warlock against a fresh container, from the host (outside) and from inside over loopback. Every case above behaves as listed, GO2RTC_API_ALLOW=172.16.0.0/12 opens the API to that range, and a malformed value stops startup.
  • The full tests/smoke.sh passes on warlock with the new outside checks: config, stream list, adding a stream, the web page and a URL-as-source WebSocket all get 403, and a monitor's WebSocket gets 101.
  • Real cameras: I moved the zmlive stack, with five holodeck cameras and go2rtc on, to this image. After the restart, zmc registered all 10 streams again through the locked-down proxy. A WebSocket client outside the container then pulled live MSE video for every monitor, about 220–250 KB each in 8 seconds, the same as before the change.
  • apache2ctl -t passes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CKoPm5qySMSsCYJmCo8r2J

SteveGilvarry and others added 2 commits October 25, 2026 17:46
ZoneMinder#105 put go2rtc behind Apache at /zm/go2rtc/. go2rtc's API has no login,
and ZoneMinder's login doesn't cover that path. So anyone who could reach
the web port could read and rewrite go2rtc's config and add streams. Before
ZoneMinder#105, the same API was published openly on port 1984.

Apache now gives the whole API only to loopback. zmc registers its streams
through ZM_PUBLIC_URL, which init-config points at the container. Outside
clients get just what ZoneMinder's live view uses:
- the WebSocket, for stream names of the form <monitor id>[_CameraDirectPrimary|
  _CameraDirectSecondary|_ZoneMinderPrimary], with no other query parameters
- the HLS fallback paths

GO2RTC_API_ALLOW lists more addresses that get the whole API, for an https
reverse proxy that zmc's calls come through. init-config refuses malformed
values. It warns when ZM_PUBLIC_URL is an IP address, or https without
GO2RTC_API_ALLOW, because live view stays blank in both cases.

The smoke test now checks from outside the container: config, the stream
list, adding a stream, the web page, and a WebSocket with a URL as source
all get 403, and a monitor's WebSocket gets 101.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKoPm5qySMSsCYJmCo8r2J
The note goes in .env.example (ZM_PUBLIC_URL), the default go2rtc.yaml, the
Docker Hub settings table and the README's go2rtc section. ZoneMinder's
browser code connects to go2rtc without credentials, so anyone who can
reach the web port can watch a monitor that has go2rtc enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKoPm5qySMSsCYJmCo8r2J
@SteveGilvarry
SteveGilvarry merged commit ef5ebad into ZoneMinder:master Oct 25, 2026
8 checks passed
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