A missing routes config starts silently with zero routes — and an open admin API #66

Closed
opened 2026-10-26 17:13:15 +00:00 by james.oates · 2 comments
Owner

Found by DevInfra during the v2.0.0 cold-start rehearsal. They omitted ROUTES_CONFIG, ER started cheerfully with nothing configured, and the symptom looked like a parser regression rather than a missing file.

load_routes_config (src/routes_config.rs:373) returns a fully empty RouteConfig when the path does not exist — no services, no static sites, no [[acme]], no [[admin]], no bindings — and logs nothing about it. ROUTES_CONFIG defaults to edge-router.toml relative to the working directory, so a typo in the variable, a missing bind mount, or a different working directory all land here.

Why it is worse than a quiet no-op

With no [[admin]] entries, middleware.rs skips the admin host/IP check entirely and the admin API is reachable from any host with only API-key auth (#54). So an operator who mistypes ROUTES_CONFIG gets a process that:

  • serves no routes, and
  • exposes an unrestricted admin API,

while logging a normal successful startup. The two behaviours are individually defensible and jointly bad: the configuration that silently produces the open-admin case is the same one an operator reaches by accident.

It also means a bind-mount that fails to attach degrades to "running but empty" rather than failing fast, which in a restart loop is the difference between an alert and silence.

Suggested fix

Distinguish explicit from default:

  • ROUTES_CONFIG set and the file missing → hard error. The operator declared where their configuration lives; not finding it is not a state to boot in. This matches how ER already treats a removed [[tls]] block and the inert admin env vars.
  • Default path absent → a prominent warning naming the path it looked for and stating that no routes were seeded. A pure-env deployment is a legitimate shape, so this should not be fatal, but it should be impossible to miss.

Logging the resolved path and the seeded counts at startup either way would have turned DevInfra's twenty-minute false alarm into one line.

Worth considering for v2.0.0 rather than later: it is small, and the open-admin interaction makes it more than cosmetic.

Found by DevInfra during the v2.0.0 cold-start rehearsal. They omitted `ROUTES_CONFIG`, ER started cheerfully with nothing configured, and the symptom looked like a parser regression rather than a missing file. `load_routes_config` (`src/routes_config.rs:373`) returns a fully empty `RouteConfig` when the path does not exist — no services, no static sites, no `[[acme]]`, no `[[admin]]`, no bindings — and logs nothing about it. `ROUTES_CONFIG` defaults to `edge-router.toml` relative to the working directory, so a typo in the variable, a missing bind mount, or a different working directory all land here. ## Why it is worse than a quiet no-op With no `[[admin]]` entries, `middleware.rs` skips the admin host/IP check entirely and the admin API is reachable from any host with only API-key auth (#54). So an operator who mistypes `ROUTES_CONFIG` gets a process that: - serves no routes, and - exposes an **unrestricted** admin API, while logging a normal successful startup. The two behaviours are individually defensible and jointly bad: the configuration that silently produces the open-admin case is the same one an operator reaches by accident. It also means a bind-mount that fails to attach degrades to "running but empty" rather than failing fast, which in a restart loop is the difference between an alert and silence. ## Suggested fix Distinguish explicit from default: - **`ROUTES_CONFIG` set and the file missing → hard error.** The operator declared where their configuration lives; not finding it is not a state to boot in. This matches how ER already treats a removed `[[tls]]` block and the inert admin env vars. - **Default path absent → a prominent warning** naming the path it looked for and stating that no routes were seeded. A pure-env deployment is a legitimate shape, so this should not be fatal, but it should be impossible to miss. Logging the resolved path and the seeded counts at startup either way would have turned DevInfra's twenty-minute false alarm into one line. Worth considering for v2.0.0 rather than later: it is small, and the open-admin interaction makes it more than cosmetic.
Author
Owner

Correction to this issue's rationale. The "Why it is worse than a quiet no-op" section claimed a missing routes config leaves an unrestricted admin API via #54. That is wrong — #54 is closed as incorrect. With no [[admin]] entries, admin_host_gate hands every API request to proxy_handler, so the API is not served at all.

So the actual consequence of a missing routes config is the opposite of exposure: ER starts serving nothing — no routes, no admin API, no dashboard, no /health, no /ready — while logging a normal successful startup. A bind mount that failed to attach, a typo in ROUTES_CONFIG, or a different working directory all produce a process that looks healthy and does nothing.

That is still worth fixing, and the suggested fix is unchanged: explicit-but-missing should be a hard error naming the path, a missing default path should be a prominent warning, and either way the resolved path and the seeded counts belong in the startup log. DevInfra lost time to exactly this and read it as a parser regression.

What it is not: a security issue. Downgrading accordingly — it is an operability defect, and the "more than cosmetic because of the open-admin interaction" argument at the end of the body does not hold.

**Correction to this issue's rationale.** The "Why it is worse than a quiet no-op" section claimed a missing routes config leaves an unrestricted admin API via #54. That is wrong — #54 is closed as incorrect. With no `[[admin]]` entries, `admin_host_gate` hands every API request to `proxy_handler`, so the API is not served at all. So the actual consequence of a missing routes config is the opposite of exposure: ER starts serving **nothing** — no routes, no admin API, no dashboard, no `/health`, no `/ready` — while logging a normal successful startup. A bind mount that failed to attach, a typo in `ROUTES_CONFIG`, or a different working directory all produce a process that looks healthy and does nothing. That is still worth fixing, and the suggested fix is unchanged: explicit-but-missing should be a hard error naming the path, a missing default path should be a prominent warning, and either way the resolved path and the seeded counts belong in the startup log. DevInfra lost time to exactly this and read it as a parser regression. What it is not: a security issue. Downgrading accordingly — it is an operability defect, and the "more than cosmetic because of the open-admin interaction" argument at the end of the body does not hold.
Author
Owner

Fixed in 9ab9f83, as suggested — resolution is now separate from loading and distinguishes who chose the path.

Explicit and missing is fatal. Verified against the real binary, which exits before any database work:

$ ROUTES_CONFIG=/nope/edge-router.toml edge-router
INFO edge-router starting — version=… commit=df35d28
Error: ROUTES_CONFIG points at /nope/edge-router.toml but no such file exists.
Refusing to start: without it EdgeRouter would serve no routes at all, and with no
[[admin]] entries no API, dashboard, /health or /ready either — while reporting a
successful startup. Check the path, and that any bind mount or volume carrying it
is attached.

Default and missing warns and continues, since a pure-env deployment is legitimate — run from an empty directory:

WARN edge_router::routes_config: No routes config found at edge-router.toml (the
default, relative to the working directory) and ROUTES_CONFIG is not set. Starting
with NO services, static sites, L4 routes or [[admin]] entries — nothing will be
proxied and the API will not be served. Set ROUTES_CONFIG if your config is elsewhere.

The resolved path is also logged when the file is found, so the startup log now says which file is in effect either way — which is the line that would have saved DevInfra the diagnosis.

The decision is a pure function (missing_config_message) so both branches are tested without touching process environment, and config.rs shares the default-path constant rather than repeating the literal.

356 tests green, clippy clean, fmt clean in rust:1.98.0-bookworm.

Fixed in `9ab9f83`, as suggested — resolution is now separate from loading and distinguishes who chose the path. **Explicit and missing is fatal.** Verified against the real binary, which exits before any database work: ``` $ ROUTES_CONFIG=/nope/edge-router.toml edge-router INFO edge-router starting — version=… commit=df35d28 Error: ROUTES_CONFIG points at /nope/edge-router.toml but no such file exists. Refusing to start: without it EdgeRouter would serve no routes at all, and with no [[admin]] entries no API, dashboard, /health or /ready either — while reporting a successful startup. Check the path, and that any bind mount or volume carrying it is attached. ``` **Default and missing warns and continues**, since a pure-env deployment is legitimate — run from an empty directory: ``` WARN edge_router::routes_config: No routes config found at edge-router.toml (the default, relative to the working directory) and ROUTES_CONFIG is not set. Starting with NO services, static sites, L4 routes or [[admin]] entries — nothing will be proxied and the API will not be served. Set ROUTES_CONFIG if your config is elsewhere. ``` The resolved path is also logged when the file *is* found, so the startup log now says which file is in effect either way — which is the line that would have saved DevInfra the diagnosis. The decision is a pure function (`missing_config_message`) so both branches are tested without touching process environment, and `config.rs` shares the default-path constant rather than repeating the literal. 356 tests green, clippy clean, fmt clean in `rust:1.98.0-bookworm`.
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#66
No description provided.