Log the observed peer IP on an allow_ips denial (silent-inert allowlist) #29

Open
opened 2026-08-26 15:51:03 +00:00 by james.oates · 0 comments
Owner

Problem

An allow_ips access rule can fail silently in both directions, with the config still reading as a correct allowlist:

  • Too narrow (e.g. 127.0.0.1/32) → denies everything; the service looks broken though the rule reads as intended.
  • Widened until traffic flows → can land on a gateway address or a broad CIDR (/16) that allows everyone, while still reading as an allowlist.

Neither state emits any signal. Compounding it, a denied request falls through to the default site — which now returns 404 (7865329), previously 200, so a status-code-only check used to report success on a denial.

Root cause is environmental, not edge-router

Reported by DevInfra from wiring the OpenBao UI through ER on azula. Under Docker bridge networking / OrbStack, the VM boundary re-originates every connection, so ER sees the devnet bridge gateway (192.168.97.1) as the peer for every client — host loopback, LAN device, and a real phone on the wifi all measured identically (3,700 samples via /proc/net/tcp). ER's zone logic is correct; it's being handed source addresses that no longer exist. This is the source-address analogue of the match_local_ip caveat already documented in edge-router.example.toml. On Linux userland-proxy: false avoids it; on macOS it can't. DevInfra's deployment fix was a dedicated 127.0.0.1:8443 binding so Docker enforces the boundary where addresses are still real.

Proposed change (small)

On an allow_ips denial, log a warning naming the actual observed peer IP, e.g.:

access denied: peer 192.168.97.1 not in allow_ips for service "vault-ui" (via "cloudflare")

This states an observed fact (no heuristic), fires exactly when someone is confused, and would have collapsed the whole /proc/net/tcp investigation into one line. Anchor: the denial path in access.rs (access_allowed / effective_client_ip), consumed by proxy.rs and l4.rs.

Known limitation to document, not solve

A denial log is structurally blind to the over-wide case — when the rule has swallowed the gateway, nothing is denied, so nothing logs. Catching that reliably needs exactly the "every observed peer looks like a gateway outside every configured CIDR" startup heuristic that was considered and rejected (can't evaluate pre-traffic, has to guess what a gateway looks like, false-positives on any narrow-but-unexercised allowlist). Ship the denial log as the reliable win and document the over-wide gap as a known limitation near the match_local_ip caveat, rather than fake-solve it.

Scope

  • Add the per-denial warning (HTTP path and L4 path).
  • Doc note near the match_local_ip caveat covering both the bridge/OrbStack re-origination and the over-wide-allowlist blind spot.

Not started — logged for prioritisation.

## Problem An `allow_ips` access rule can fail **silently in both directions**, with the config still reading as a correct allowlist: - Too narrow (e.g. `127.0.0.1/32`) → denies everything; the service looks broken though the rule reads as intended. - Widened until traffic flows → can land on a gateway address or a broad CIDR (`/16`) that **allows everyone**, while still reading as an allowlist. Neither state emits any signal. Compounding it, a denied request falls through to the default site — which now returns **404** (7865329), previously 200, so a status-code-only check used to report success on a denial. ## Root cause is environmental, not edge-router Reported by DevInfra from wiring the OpenBao UI through ER on azula. Under Docker **bridge** networking / OrbStack, the VM boundary re-originates every connection, so ER sees the **devnet bridge gateway** (`192.168.97.1`) as the peer for *every* client — host loopback, LAN device, and a real phone on the wifi all measured identically (3,700 samples via `/proc/net/tcp`). ER's zone logic is correct; it's being handed source addresses that no longer exist. This is the source-address analogue of the `match_local_ip` caveat already documented in `edge-router.example.toml`. On Linux `userland-proxy: false` avoids it; on macOS it can't. DevInfra's deployment fix was a dedicated `127.0.0.1:8443` binding so Docker enforces the boundary where addresses are still real. ## Proposed change (small) On an `allow_ips` denial, log a warning naming the **actual observed peer IP**, e.g.: ``` access denied: peer 192.168.97.1 not in allow_ips for service "vault-ui" (via "cloudflare") ``` This states an observed fact (no heuristic), fires exactly when someone is confused, and would have collapsed the whole `/proc/net/tcp` investigation into one line. Anchor: the denial path in `access.rs` (`access_allowed` / `effective_client_ip`), consumed by `proxy.rs` and `l4.rs`. ## Known limitation to document, not solve A denial log is structurally blind to the **over-wide** case — when the rule has swallowed the gateway, nothing is denied, so nothing logs. Catching that reliably needs exactly the "every observed peer looks like a gateway outside every configured CIDR" startup heuristic that was considered and rejected (can't evaluate pre-traffic, has to guess what a gateway looks like, false-positives on any narrow-but-unexercised allowlist). Ship the denial log as the reliable win and document the over-wide gap as a known limitation near the `match_local_ip` caveat, rather than fake-solve it. ## Scope - Add the per-denial warning (HTTP path and L4 path). - Doc note near the `match_local_ip` caveat covering both the bridge/OrbStack re-origination and the over-wide-allowlist blind spot. Not started — logged for prioritisation.
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
IsoHex/edge-router#29
No description provided.