Skip to content

Prometheus, Loki and Alertmanager bind 0.0.0.0 with no authentication #70

Closed

Description

@Gerrrt

.env.example:11 ships BIND_ADDR=0.0.0.0, and the deployed .env on the
monitoring host has the same. Confirmed with ss -ltn on prometheus
(10.0.99.20):

LISTEN  0.0.0.0:9090     prometheus
LISTEN  0.0.0.0:9093     alertmanager
LISTEN  0.0.0.0:3100     loki
LISTEN  0.0.0.0:3000     grafana
UNCONN  0.0.0.0:1514     alloy (syslog)

Only Grafana authenticates. For the other three, anyone who can reach the host
on VLAN 99 can:

  • Prometheus — inject arbitrary metrics (--web.enable-remote-write-receiver)
    and force a config reload (--web.enable-lifecycle);
  • Loki — push arbitrary log lines and issue delete requests
    (auth_enabled: false);
  • Alertmanager — create silences, i.e. switch off any alert.

The last one is the interesting one: silencing an alert is a quiet way to
disable monitoring, and the stack's own record of that action is the thing being
disabled.

This is not currently exploitable from an untrusted segment

Default-deny between VLANs means only VLAN 50 can reach VLAN 99 at all
(docs/adr/0002). So the firewall is the entire control, and there is no
defence in depth behind it — a compromised workstation on the trusted segment
has unauthenticated write access to the metrics and log stores.

.env.example:9-11 already documents that BIND_ADDR can be set to the
management IP. The question is whether the shipped default should be the wide
one.

Options

  • Ship BIND_ADDR=127.0.0.1 and publish only Grafana, which is the only service
    with a login. Alloy's syslog listener has to stay reachable off-host and is
    separately bound.
  • Or keep it and record the residual in SECURITY.md, which already carries an
    exposure table.

Found while verifying #12.

Activity

  1. added
    securityA credential, an exposure, or a control that is weaker than recorded
    on Aug 21, 2026
  2. added a commit that references this issue on Aug 30, 2026
  3. Gerrrt commented on Aug 30, 2026

    @Gerrrt
    OwnerAuthor

    Closed by #181, deployed to the lab and verified 2026-08-30. Partly fixed, partly accepted — worth spelling out, since the title names three services and only one of them moved.

    Fixed: Alertmanager

    9093 now binds to 127.0.0.1. This was the sharpest of the three for the reason the issue gives — a silence switches off monitoring, and the record of it lives in the system being switched off.

    It cost nothing to close. Nothing off-host ever used the port: Grafana proxies Alertmanager over the compose network through an authenticated datasource, so every operator path to a silence already avoided 9093. Verified after deploy:

    127.0.0.1:9093   -> 200
    10.0.99.20:9093  -> refused
    alertmanager:9093 reachable from the grafana container
    

    Not fixed: Prometheus and Loki

    Both stay on ${BIND_ADDR}, i.e. 0.0.0.0. The option-1 plan in this issue would have broken the lab, and only checking the running host caught it.

    docs/roadmap.md claimed only the monitoring host ran an agent. It was about a day out of date. oracle had been remote-writing to 10.0.99.20:9090 and pushing to :3100 for ~18 hours — 378 distinct metric names — and it is not a Prometheus scrape target, so those published ports are its only path. Loopback-binding them would have cut its metrics and logs silently, because Alloy retries with backoff and buffers rather than failing loudly.

    So the write surface this issue describes for those two is real and still open: anything that can route to 10.0.99.20 can read every metric and log line, inject metrics through the remote-write receiver, and delete log ranges through Loki's delete API. Firewall default-deny is the only control, exactly as the issue says. That is now recorded as an accepted residual in SECURITY.md alongside the SNMP and unencrypted-disk entries, rather than carried implicitly.

    Closing it properly means authentication in front of the ingest ports plus a credential on every agent — separate work, and a new secret to rotate. Not tracked as an issue yet.

    Answering the question the issue actually asked

    .env.example:9-11 already documents that BIND_ADDR can be set to the management IP. The question is whether the shipped default should be the wide one.

    Two findings settle it, and neither was what I expected going in:

    1. It is not a default, it is the setting. scripts/render-config.sh regenerates .env from .env.example on every make up, so a hand-edit of .env never survived a deploy. docs/architecture.md saying BIND_ADDR "is set in .env" was misleading; fixed.
    2. Narrowing to the management IP would buy nothing here. The monitoring host has one interface — a USB ethernet adapter already on 10.0.99.20/24. 0.0.0.0 and 10.0.99.20 are the same thing on this machine, and pinning the literal address would only add a way for the stack to fail if it ever moved.

    The rule that replaced the shared default is in ADR-0012: publish a port when something off-host uses it, and name the client. That also means #88's Saruman deploy needs no bind change — oracle already made Prometheus and Loki published services. Roadmap #88 is corrected accordingly.

  4. Gerrrt commented on Aug 30, 2026

    @Gerrrt
    OwnerAuthor

    Follow-up opened as #182 — authenticating the ingest ports, which is what would retire the Prometheus/Loki residual above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    securityA credential, an exposure, or a control that is weaker than recorded

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions