Skip to content

Feature request: allow binding to non-loopback address when TLS is terminated externally #586

@jk89

Description

@jk89

--no-tls-very-insecure silently overrides --ip, making reverse proxy deployments impossible

Summary

When --no-tls-very-insecure is set, the server unconditionally binds to 127.0.0.1 regardless of the value passed to --ip. This makes it impossible to use frostd behind a reverse proxy unless the proxy shares the same network namespace as the server process.

Description

The documentation for --tls-cert explicitly recommends using a reverse proxy for production deployments:

For production deployments, it's recommended to provide HTTPS using a reverse proxy. In that case, set no_tls_very_insecure instead.

However, the ip() method in args.rs ignores the user-supplied --ip argument when no_tls_very_insecure is set:

frostd/src/args.rs

pub fn ip(&self) -> String {
    if self.no_tls_very_insecure {
        "127.0.0.1".to_string() // --ip flag is silently ignored
    } else {
        self.ip.clone()
    }
}

And in frostd/src/lib.rs, the bind address is derived from args.ip():

let addr: SocketAddr = format!("{}:{}", args.ip(), args.port).parse()?;

if args.no_tls_very_insecure {
    tracing::warn!(
        "starting an INSECURE HTTP server at {}. This should be done only \
        for testing or if you are providing TLS/HTTPS with a separate \
        mechanism (e.g. reverse proxy)",
        addr,
    );
    let listener = tokio::net::TcpListener::bind(addr).await?;

The warning message at runtime even mentions the reverse proxy use case, but addr will always be 127.0.0.1:<port> at this point, making the flag effectively unusable for that deployment pattern.

Expected behaviour

When --no-tls-very-insecure is set alongside --ip 0.0.0.0, the server should bind to 0.0.0.0:<port>, allowing a reverse proxy to reach it from outside the local network namespace.

Suggested fix

Keep --no-tls-very-insecure as-is for its current safe default (localhost only). Add a companion flag --allow-public-no-tls that must be explicitly passed alongside it to permit binding to a non-loopback address. This keeps the opt-in nature of the current flag while making the public bind an additional, deliberate choice:

frostd/src/args.rs

/// Disable TLS/HTTPS. Binds to 127.0.0.1 unless --allow-public-no-tls is also set.
#[arg(short, long, default_value_t = false)]
pub no_tls_very_insecure: bool,

/// Allow binding to the address specified by --ip when --no-tls-very-insecure is set.
/// Only use this if TLS is being handled externally (e.g. a reverse proxy).
#[arg(long, default_value_t = false)]
pub allow_public_no_tls: bool,

pub fn ip(&self) -> String {
    if self.no_tls_very_insecure && !self.allow_public_no_tls {
        "127.0.0.1".to_string()
    } else {
        self.ip.clone()
    }
}

No changes required in lib.rs. The bind address returned by ip() already flows through to the listener correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions