Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion dstack/gateway/dstack-app/builder/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ tls_versions = ["1.2"]
listen_addr = "0.0.0.0"
listen_port = "${PROXY_LISTEN_PORT:-443}"
connect_top_n = 3
localhost_enabled = false
app_address_ns_compat = true
workers = ${PROXY_WORKERS:-32}
max_connections_per_app = ${MAX_CONNECTIONS_PER_APP:-0}
Expand Down
6 changes: 5 additions & 1 deletion dstack/gateway/gateway.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ insecure_no_auth = false

[core.debug]
insecure_enable_debug_rpc = false
# Route the app address "localhost" to 127.0.0.1 on the gateway host. Off by
# default: the app address also comes from the _dstack-app-address TXT record of
# arbitrary custom domains, so enabling this lets any DNS zone owner reach the
# gateway's own loopback on a port of their choosing, bypassing port_policy.
insecure_localhost_backend = false
insecure_skip_attestation = false
key_file = "debug_key.json"
address = "127.0.0.1:8012"
Expand All @@ -80,7 +85,6 @@ agent_port = 8090
buffer_size = 65536
# number of hosts to try to connect to
connect_top_n = 3
localhost_enabled = false
app_address_ns_prefix = "_dstack-app-address"
app_address_ns_compat = true
workers = 32
Expand Down
14 changes: 13 additions & 1 deletion dstack/gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ pub struct ProxyConfig {
/// 1 MiB pipe fed without the syscall rate 8 KiB imposed.
pub buffer_size: usize,
pub connect_top_n: usize,
pub localhost_enabled: bool,
pub workers: usize,
/// Run one single-threaded runtime per worker, each with its own
/// `SO_REUSEPORT` listener, instead of one accept thread feeding a shared
Expand Down Expand Up @@ -522,6 +521,19 @@ pub struct DebugConfig {
pub insecure_enable_debug_rpc: bool,
#[serde(default)]
pub insecure_skip_attestation: bool,
/// Let the app-address `localhost` resolve to 127.0.0.1, so a hostname can
/// be routed to a service on the gateway host itself.
///
/// This lives under `debug` and carries the `insecure_` prefix because the
/// app address is not only read from the platform's own `<id>.<base_domain>`
/// grammar: it also comes from the `_dstack-app-address` TXT record of an
/// arbitrary custom domain. With this on, anyone who controls any DNS zone
/// can point the gateway at its own loopback -- where the admin and debug
/// listeners bind precisely because being unreachable is their access
/// control -- and pick the port, since the `localhost` shortcut is not a
/// registered instance and so bypasses `port_policy` entirely.
#[serde(default)]
pub insecure_localhost_backend: bool,
/// Path to pre-generated debug key data file (JSON format containing key, quote, event_log, and vm_config)
#[serde(default)]
pub key_file: String,
Expand Down
11 changes: 10 additions & 1 deletion dstack/gateway/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use rocket::{
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tracing::info;
use tracing::{info, warn};

use admin_service::AdminRpcHandler;
use main_service::{Proxy, ProxyOptions, RpcHandler};
Expand Down Expand Up @@ -245,6 +245,15 @@ async fn main() -> Result<()> {
if config.sync.enabled && config.sync.node_id == 0 {
anyhow::bail!("node_id must be greater than 0");
}
if config.debug.insecure_localhost_backend {
warn!(
"core.debug.insecure_localhost_backend = true; the app address \"localhost\" now \
resolves to 127.0.0.1 on this host. App addresses also come from the \
_dstack-app-address TXT record of arbitrary custom domains, so any DNS zone owner \
can reach this host's loopback on a port of their choosing, bypassing port_policy. \
Never use this outside local development"
);
}
// Before anything reads `proxy.ktls`: the acceptor built later decides
// whether to extract session secrets from it.
proxy::disable_ktls_if_unsupported(&mut config.proxy);
Expand Down
2 changes: 1 addition & 1 deletion dstack/gateway/src/main_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,7 @@ impl ProxyState {
}

pub(crate) fn select_top_n_hosts(&mut self, id: &str) -> Result<AddressGroup> {
if self.config.proxy.localhost_enabled && id == "localhost" {
if self.config.debug.insecure_localhost_backend && id == "localhost" {
return Ok(smallvec![AddressInfo {
ip: Ipv4Addr::new(127, 0, 0, 1),
counter: Default::default(),
Expand Down
2 changes: 1 addition & 1 deletion dstack/gateway/test-run/proxy/gwconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def main():
rpc_domain = ""
set_ulimit = false
[core.debug]
insecure_localhost_backend = true
insecure_skip_attestation = true
insecure_enable_debug_rpc = false
[core.admin]
Expand All @@ -81,7 +82,6 @@ def main():
[core.proxy]
listen_addr = "127.0.0.1"
listen_port = {o["proxy_port"]}
localhost_enabled = true
base_domain = "{o["base_domain"]}"
cert_chain = "{cert}"
cert_key = "{key}"
Expand Down
4 changes: 2 additions & 2 deletions dstack/gateway/test-run/test_proxy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ ORIGIN_PLAIN=$((BASE_PORT + 4))
ORIGIN_TLS=$((BASE_PORT + 5))

BASE_DOMAIN="gwtest.local"
# `localhost_enabled` routes `localhost-<port>[s]` to 127.0.0.1:<port>, which is
# what lets these tests run without registering a CVM.
# `insecure_localhost_backend` routes `localhost-<port>[s]` to 127.0.0.1:<port>,
# which is what lets these tests run without registering a CVM.
SNI_TERMINATE="localhost-$ORIGIN_PLAIN.$BASE_DOMAIN"
SNI_PASSTHROUGH="localhost-${ORIGIN_TLS}s.$BASE_DOMAIN"
ADMIN_TOKEN="proxy-integration-test"
Expand Down
Loading