c0re: bind dashboard to 127.0.0.1 only (#652)
This commit is contained in:
parent
85790b0cca
commit
c5d466c5c5
2 changed files with 26 additions and 12 deletions
|
|
@ -92,9 +92,16 @@ pub async fn serve(port: u16, coord: Arc<Coordinator>) -> Result<()> {
|
||||||
// /static/dashboard.css → dist/static/dashboard.css, etc.).
|
// /static/dashboard.css → dist/static/dashboard.css, etc.).
|
||||||
.fallback_service(ServeDir::new(&static_dir))
|
.fallback_service(ServeDir::new(&static_dir))
|
||||||
.with_state(AppState { coord });
|
.with_state(AppState { coord });
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
// Bind loopback-only (#652). External access funnels through
|
||||||
|
// hive-gateway (in-host-netns nginx container), which proxies
|
||||||
|
// `/` → `127.0.0.1:<dashboardPort>` upstream. Operators who opt
|
||||||
|
// out of the gateway lose remote dashboard access — that's by
|
||||||
|
// design; the c0re HTTP surface is privileged (approve / deny /
|
||||||
|
// destroy, etc.) and any external exposure needs to pass through
|
||||||
|
// a real reverse proxy with auth.
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||||
let listener = bind_with_retry(addr).await?;
|
let listener = bind_with_retry(addr).await?;
|
||||||
tracing::info!(%port, "dashboard listening");
|
tracing::info!(%addr, "dashboard listening");
|
||||||
axum::serve(listener, app).await?;
|
axum::serve(listener, app).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,21 +194,28 @@ in
|
||||||
managerToplevel
|
managerToplevel
|
||||||
];
|
];
|
||||||
|
|
||||||
# Dashboard + per-container web UIs share the host's network namespace and
|
# Per-container web UIs share the host's network namespace and need
|
||||||
# need their ports reachable when there's no gateway in front. Dashboard:
|
# their ports reachable when there's no gateway in front. Manager:
|
||||||
# `cfg.dashboardPort` (default 7000). Manager: 8000. Sub-agents: 8100..8999
|
# 8000. Sub-agents: 8100..8999 (deterministic hash; see
|
||||||
# (deterministic hash; see `lifecycle::agent_web_port`).
|
# `lifecycle::agent_web_port`).
|
||||||
|
#
|
||||||
|
# The dashboard port (`cfg.dashboardPort`, default 7000) is *not*
|
||||||
|
# listed here — since #652 the dashboard binds `127.0.0.1` only,
|
||||||
|
# so opening the firewall hole would be a no-op. Remote dashboard
|
||||||
|
# access flows through hive-gateway (default-on); operators who
|
||||||
|
# opt out of the gateway lose external dashboard reach by design —
|
||||||
|
# the surface is privileged (approve / deny / destroy) and must
|
||||||
|
# not be exposed without a real reverse proxy in front.
|
||||||
#
|
#
|
||||||
# When `services.hyperhive.gateway.enable = true` (the default), the
|
# When `services.hyperhive.gateway.enable = true` (the default), the
|
||||||
# gateway nginx is the sole external entry point and proxies to
|
# gateway nginx is the sole external entry point and proxies to
|
||||||
# `127.0.0.1:7000` etc. internally — leaving the direct ports open
|
# `127.0.0.1:7000` etc. internally — leaving the per-agent ports
|
||||||
# in the host firewall would defeat the gateway's "single front
|
# open in the host firewall would defeat the gateway's "single
|
||||||
# door" story (closes #621). Operators who opt out of the gateway
|
# front door" story (closes #621). Operators who opt out of the
|
||||||
# still get the direct ports opened so the legacy
|
# gateway still get those direct ports opened so the legacy
|
||||||
# `http://<host>:7000/` flow works.
|
# `http://<host>:8100/` flow works.
|
||||||
networking.firewall = lib.mkIf (!config.services.hyperhive.gateway.enable) {
|
networking.firewall = lib.mkIf (!config.services.hyperhive.gateway.enable) {
|
||||||
allowedTCPPorts = [
|
allowedTCPPorts = [
|
||||||
cfg.dashboardPort
|
|
||||||
8000
|
8000
|
||||||
];
|
];
|
||||||
allowedTCPPortRanges = [
|
allowedTCPPortRanges = [
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue