mirror of
https://github.com/zellij-org/zellij.git
synced 2026-10-11 22:50:07 +00:00
allow dangerously disabling certificate on non-localhost (#5694)
* allow dangerously disabling certificate on non-localhost * add pr
This commit is contained in:
@@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
* feat: let a plugin give its layout space back while it has nothing to draw (https://github.com/zellij-org/zellij/pull/5590)
|
||||
* fix: occasional resurrection layout serialization corruption (https://github.com/zellij-org/zellij/pull/5690)
|
||||
* fix: support horizontal scroll for programs that request it (https://github.com/zellij-org/zellij/pull/4860)
|
||||
* feat: allow config to dangerously disable https certificate enforcement on non-localhost (https://github.com/zellij-org/zellij/pull/5694)
|
||||
|
||||
## [0.45.1] - 2026-08-28
|
||||
* fix: nested-session detection over SSH (https://github.com/zellij-org/zellij/pull/5522)
|
||||
|
||||
@@ -807,6 +807,14 @@ pub fn describe(key: SettingKey) -> SettingInfo {
|
||||
"false",
|
||||
Everyone,
|
||||
),
|
||||
SettingKey::DangerouslyAllowWebServingWithoutACertificate => info(
|
||||
"Allow HTTP without certificate",
|
||||
Web,
|
||||
"DANGEROUS: serve unencrypted HTTP on non-localhost addresses",
|
||||
Toggle,
|
||||
"false",
|
||||
Everyone,
|
||||
),
|
||||
SettingKey::ClientAsyncWorkerTasks => info(
|
||||
"Web worker tasks",
|
||||
Web,
|
||||
@@ -952,6 +960,7 @@ pub fn section(key: SettingKey) -> &'static str {
|
||||
| WebServerCert
|
||||
| WebServerKey
|
||||
| EnforceHttpsForLocalhost
|
||||
| DangerouslyAllowWebServingWithoutACertificate
|
||||
| ClientAsyncWorkerTasks => "Server",
|
||||
WebClientFont
|
||||
| WebClientFontSize
|
||||
|
||||
+12
-1
@@ -648,12 +648,23 @@ load_plugins {
|
||||
// (127.0.0.0/8)
|
||||
// (Requires restart)
|
||||
//
|
||||
// Note: https is ALWAYS enforced when bound to non-local interfaces
|
||||
// Note: https is enforced when bound to non-local interfaces, unless
|
||||
// dangerously_allow_web_serving_without_a_certificate is set
|
||||
//
|
||||
// Default: false
|
||||
//
|
||||
// enforce_https_for_localhost true
|
||||
|
||||
// DANGEROUS: allow the web server to serve plain, unencrypted HTTP when bound to a
|
||||
// non-local interface without web_server_cert and web_server_key.
|
||||
// Anyone on the network can read and tamper with the traffic, including login tokens.
|
||||
// Only use behind a trusted TLS-terminating proxy or on a fully trusted network.
|
||||
// Does not override enforce_https_for_localhost.
|
||||
// Default: false
|
||||
// (Requires restart)
|
||||
//
|
||||
// dangerously_allow_web_serving_without_a_certificate true
|
||||
|
||||
// The port the web server should listen on when it starts
|
||||
// Default: 8082
|
||||
// (Requires restart)
|
||||
|
||||
@@ -1034,6 +1034,9 @@ pub fn start_client(
|
||||
let has_certificate =
|
||||
config_options.web_server_cert.is_some() && config_options.web_server_key.is_some();
|
||||
let enforce_https_for_localhost = config_options.enforce_https_for_localhost.unwrap_or(false);
|
||||
let dangerously_allow_web_serving_without_a_certificate = config_options
|
||||
.dangerously_allow_web_serving_without_a_certificate
|
||||
.unwrap_or(false);
|
||||
|
||||
let terminal_teardown = TerminalTeardown {
|
||||
include_kitty_exit: !explicitly_disable_kitty_keyboard_protocol,
|
||||
@@ -1504,6 +1507,7 @@ pub fn start_client(
|
||||
web_server_port,
|
||||
has_certificate,
|
||||
enforce_https_for_localhost,
|
||||
dangerously_allow_web_serving_without_a_certificate,
|
||||
);
|
||||
match spawn_web_server(&cli_args) {
|
||||
Ok(_) => {
|
||||
|
||||
@@ -101,13 +101,26 @@ pub fn start_web_client(
|
||||
let web_server_key = custom_server_key.or_else(|| config.options.web_server_key.clone());
|
||||
let has_https_certificate = web_server_cert.is_some() && web_server_key.is_some();
|
||||
|
||||
if let Err(e) = should_use_https(
|
||||
match should_use_https(
|
||||
web_server_ip,
|
||||
has_https_certificate,
|
||||
config.options.enforce_https_for_localhost.unwrap_or(false),
|
||||
config
|
||||
.options
|
||||
.dangerously_allow_web_serving_without_a_certificate
|
||||
.unwrap_or(false),
|
||||
) {
|
||||
eprintln!("{}", e);
|
||||
std::process::exit(2);
|
||||
Ok(false) if !web_server_ip.is_loopback() => {
|
||||
eprintln!(
|
||||
"WARNING: serving the web client on {} over plain HTTP without an SSL certificate (dangerously_allow_web_serving_without_a_certificate is enabled). Traffic, including login tokens, is not encrypted.",
|
||||
web_server_ip
|
||||
);
|
||||
},
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
std::process::exit(2);
|
||||
},
|
||||
};
|
||||
let (runtime, listener, tls_config) = if run_daemonized {
|
||||
daemonize_web_server(
|
||||
|
||||
@@ -24,18 +24,19 @@ pub fn should_use_https(
|
||||
ip: IpAddr,
|
||||
has_certificate: bool,
|
||||
enforce_https_for_localhost: bool,
|
||||
dangerously_allow_web_serving_without_a_certificate: bool,
|
||||
) -> Result<bool, String> {
|
||||
let is_loopback = match ip {
|
||||
IpAddr::V4(ipv4) => ipv4.is_loopback(),
|
||||
IpAddr::V6(ipv6) => ipv6.is_loopback(),
|
||||
};
|
||||
|
||||
if is_loopback && !enforce_https_for_localhost {
|
||||
Ok(has_certificate)
|
||||
} else if is_loopback {
|
||||
Err(format!("Cannot bind without an SSL certificate."))
|
||||
} else if has_certificate {
|
||||
if has_certificate {
|
||||
Ok(true)
|
||||
} else if is_loopback && enforce_https_for_localhost {
|
||||
Err(format!("Cannot bind without an SSL certificate."))
|
||||
} else if is_loopback || dangerously_allow_web_serving_without_a_certificate {
|
||||
Ok(false)
|
||||
} else {
|
||||
Err(format!(
|
||||
"Cannot bind to non-loopback IP: {} without an SSL certificate.",
|
||||
@@ -74,3 +75,87 @@ pub fn terminal_init_messages() -> Vec<&'static str> {
|
||||
enable_mouse_mode,
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod should_use_https_tests {
|
||||
use super::should_use_https;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
fn localhost() -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
|
||||
}
|
||||
|
||||
fn remote_v4() -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))
|
||||
}
|
||||
|
||||
fn remote_v6() -> IpAddr {
|
||||
IpAddr::V6(Ipv6Addr::UNSPECIFIED)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_loopback_ipv4_without_certificate_is_refused_by_default() {
|
||||
assert!(should_use_https(remote_v4(), false, false, false).is_err());
|
||||
assert!(should_use_https(remote_v4(), false, true, false).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_loopback_ipv6_without_certificate_is_refused_by_default() {
|
||||
assert!(should_use_https(remote_v6(), false, false, false).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_loopback_with_certificate_uses_https() {
|
||||
assert_eq!(should_use_https(remote_v4(), true, false, false), Ok(true));
|
||||
assert_eq!(should_use_https(remote_v6(), true, false, false), Ok(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_without_certificate_uses_http() {
|
||||
assert_eq!(
|
||||
should_use_https(localhost(), false, false, false),
|
||||
Ok(false)
|
||||
);
|
||||
assert_eq!(
|
||||
should_use_https(IpAddr::V6(Ipv6Addr::LOCALHOST), false, false, false),
|
||||
Ok(false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_with_certificate_uses_https() {
|
||||
assert_eq!(should_use_https(localhost(), true, false, false), Ok(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_with_enforced_https_and_certificate_uses_https() {
|
||||
assert_eq!(should_use_https(localhost(), true, true, false), Ok(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_with_enforced_https_without_certificate_is_refused() {
|
||||
assert!(should_use_https(localhost(), false, true, false).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_loopback_without_certificate_uses_http_when_dangerously_allowed() {
|
||||
assert_eq!(should_use_https(remote_v4(), false, false, true), Ok(false));
|
||||
assert_eq!(should_use_https(remote_v6(), false, false, true), Ok(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_loopback_with_certificate_still_uses_https_when_dangerously_allowed() {
|
||||
assert_eq!(should_use_https(remote_v4(), true, false, true), Ok(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enforced_https_for_localhost_wins_over_dangerous_allowance() {
|
||||
assert!(should_use_https(localhost(), false, true, true).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dangerous_allowance_does_not_change_loopback_behavior() {
|
||||
assert_eq!(should_use_https(localhost(), false, false, true), Ok(false));
|
||||
assert_eq!(should_use_https(localhost(), true, false, true), Ok(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4416,6 +4416,9 @@ fn init_session(
|
||||
let has_certificate =
|
||||
config_options.web_server_cert.is_some() && config_options.web_server_key.is_some();
|
||||
let enforce_https_for_localhost = config_options.enforce_https_for_localhost.unwrap_or(false);
|
||||
let dangerously_allow_web_serving_without_a_certificate = config_options
|
||||
.dangerously_allow_web_serving_without_a_certificate
|
||||
.unwrap_or(false);
|
||||
|
||||
let default_shell = config_options.default_shell.clone().map(|command| {
|
||||
TerminalAction::RunCommand(RunCommand {
|
||||
@@ -4581,6 +4584,7 @@ fn init_session(
|
||||
web_server_port,
|
||||
has_certificate,
|
||||
enforce_https_for_localhost,
|
||||
dangerously_allow_web_serving_without_a_certificate,
|
||||
);
|
||||
move || {
|
||||
background_jobs_main(
|
||||
|
||||
@@ -418,12 +418,23 @@ context_menu {
|
||||
// (127.0.0.0/8)
|
||||
// (Requires restart)
|
||||
//
|
||||
// Note: https is ALWAYS enforced when bound to non-local interfaces
|
||||
// Note: https is enforced when bound to non-local interfaces, unless
|
||||
// dangerously_allow_web_serving_without_a_certificate is set
|
||||
//
|
||||
// Default: false
|
||||
//
|
||||
// enforce_https_for_localhost true
|
||||
|
||||
// DANGEROUS: allow the web server to serve plain, unencrypted HTTP when bound to a
|
||||
// non-local interface without web_server_cert and web_server_key.
|
||||
// Anyone on the network can read and tamper with the traffic, including login tokens.
|
||||
// Only use behind a trusted TLS-terminating proxy or on a fully trusted network.
|
||||
// Does not override enforce_https_for_localhost.
|
||||
// Default: false
|
||||
// (Requires restart)
|
||||
//
|
||||
// dangerously_allow_web_serving_without_a_certificate true
|
||||
|
||||
// The port the web server should listen on when it starts
|
||||
// Default: 8082
|
||||
// (Requires restart)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2174,6 +2174,8 @@ pub struct Options {
|
||||
pub keybinds_unlock: ::core::option::Option<::prost::alloc::string::String>,
|
||||
#[prost(bool, optional, tag="75")]
|
||||
pub context_menu_enabled: ::core::option::Option<bool>,
|
||||
#[prost(bool, optional, tag="76")]
|
||||
pub dangerously_allow_web_serving_without_a_certificate: ::core::option::Option<bool>,
|
||||
}
|
||||
/// Pane-targeting action messages
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
|
||||
@@ -1286,6 +1286,7 @@ message Options {
|
||||
optional string keybinds_secondary = 73;
|
||||
optional string keybinds_unlock = 74;
|
||||
optional bool context_menu_enabled = 75;
|
||||
optional bool dangerously_allow_web_serving_without_a_certificate = 76;
|
||||
}
|
||||
|
||||
enum OnForceClose {
|
||||
|
||||
@@ -4672,6 +4672,7 @@ setting_keys! {
|
||||
WebServerCert => (TopLevel, "web_server_cert", true, Text),
|
||||
WebServerKey => (TopLevel, "web_server_key", true, Text),
|
||||
EnforceHttpsForLocalhost => (TopLevel, "enforce_https_for_localhost", true, Flag),
|
||||
DangerouslyAllowWebServingWithoutACertificate => (TopLevel, "dangerously_allow_web_serving_without_a_certificate", true, Flag),
|
||||
PostCommandDiscoveryHook => (TopLevel, "post_command_discovery_hook", false, Text),
|
||||
ClientAsyncWorkerTasks => (TopLevel, "client_async_worker_tasks", true, Number),
|
||||
NestedSessionHandling => (TopLevel, "nested_session_handling", false, Text),
|
||||
|
||||
@@ -163,6 +163,7 @@ fn option_values(options: &Options, values: &mut BTreeMap<SettingKey, Option<Str
|
||||
web_server_cert,
|
||||
web_server_key,
|
||||
enforce_https_for_localhost,
|
||||
dangerously_allow_web_serving_without_a_certificate,
|
||||
post_command_discovery_hook,
|
||||
client_async_worker_tasks,
|
||||
nested_session_handling,
|
||||
@@ -303,6 +304,10 @@ fn option_values(options: &Options, values: &mut BTreeMap<SettingKey, Option<Str
|
||||
SettingKey::EnforceHttpsForLocalhost,
|
||||
display_text(enforce_https_for_localhost),
|
||||
),
|
||||
(
|
||||
SettingKey::DangerouslyAllowWebServingWithoutACertificate,
|
||||
display_text(dangerously_allow_web_serving_without_a_certificate),
|
||||
),
|
||||
(
|
||||
SettingKey::PostCommandDiscoveryHook,
|
||||
post_command_discovery_hook.clone(),
|
||||
@@ -622,6 +627,10 @@ pub fn copy_setting(target: &mut Config, source: &Config, key: SettingKey) {
|
||||
SettingKey::EnforceHttpsForLocalhost => {
|
||||
options.enforce_https_for_localhost = from.enforce_https_for_localhost
|
||||
},
|
||||
SettingKey::DangerouslyAllowWebServingWithoutACertificate => {
|
||||
options.dangerously_allow_web_serving_without_a_certificate =
|
||||
from.dangerously_allow_web_serving_without_a_certificate
|
||||
},
|
||||
SettingKey::PostCommandDiscoveryHook => {
|
||||
options.post_command_discovery_hook = from.post_command_discovery_hook.clone()
|
||||
},
|
||||
|
||||
@@ -447,6 +447,7 @@ pub struct Options {
|
||||
pub web_server_cert: Option<PathBuf>,
|
||||
pub web_server_key: Option<PathBuf>,
|
||||
pub enforce_https_for_localhost: Option<bool>,
|
||||
pub dangerously_allow_web_serving_without_a_certificate: Option<bool>,
|
||||
/// A command to run after the discovery of running commands when serializing, for the purpose
|
||||
/// of manipulating the command (eg. with a regex) before it gets serialized
|
||||
#[clap(long, value_parser)]
|
||||
@@ -647,6 +648,9 @@ impl Options {
|
||||
let enforce_https_for_localhost = other
|
||||
.enforce_https_for_localhost
|
||||
.or(self.enforce_https_for_localhost);
|
||||
let dangerously_allow_web_serving_without_a_certificate = other
|
||||
.dangerously_allow_web_serving_without_a_certificate
|
||||
.or(self.dangerously_allow_web_serving_without_a_certificate);
|
||||
let post_command_discovery_hook = other
|
||||
.post_command_discovery_hook
|
||||
.or(self.post_command_discovery_hook.clone());
|
||||
@@ -722,6 +726,7 @@ impl Options {
|
||||
web_server_cert,
|
||||
web_server_key,
|
||||
enforce_https_for_localhost,
|
||||
dangerously_allow_web_serving_without_a_certificate,
|
||||
post_command_discovery_hook,
|
||||
client_async_worker_tasks,
|
||||
nested_session_handling,
|
||||
@@ -838,6 +843,9 @@ impl Options {
|
||||
let enforce_https_for_localhost = other
|
||||
.enforce_https_for_localhost
|
||||
.or(self.enforce_https_for_localhost);
|
||||
let dangerously_allow_web_serving_without_a_certificate = other
|
||||
.dangerously_allow_web_serving_without_a_certificate
|
||||
.or(self.dangerously_allow_web_serving_without_a_certificate);
|
||||
let post_command_discovery_hook = other
|
||||
.post_command_discovery_hook
|
||||
.or_else(|| self.post_command_discovery_hook.clone());
|
||||
@@ -913,6 +921,7 @@ impl Options {
|
||||
web_server_cert,
|
||||
web_server_key,
|
||||
enforce_https_for_localhost,
|
||||
dangerously_allow_web_serving_without_a_certificate,
|
||||
post_command_discovery_hook,
|
||||
client_async_worker_tasks,
|
||||
nested_session_handling,
|
||||
|
||||
@@ -76,6 +76,7 @@ fn every_option_set() -> Options {
|
||||
web_server_cert: Some(PathBuf::from("/tmp/cert.pem")),
|
||||
web_server_key: Some(PathBuf::from("/tmp/key.pem")),
|
||||
enforce_https_for_localhost: Some(true),
|
||||
dangerously_allow_web_serving_without_a_certificate: Some(true),
|
||||
post_command_discovery_hook: Some("echo hook".to_owned()),
|
||||
client_async_worker_tasks: Some(8),
|
||||
nested_session_handling: Some(NestedSessionHandling::Never),
|
||||
|
||||
@@ -996,6 +996,8 @@ impl From<crate::input::options::Options>
|
||||
.web_server_key
|
||||
.map(|p| p.to_string_lossy().to_string()),
|
||||
enforce_https_for_localhost: options.enforce_https_for_localhost,
|
||||
dangerously_allow_web_serving_without_a_certificate: options
|
||||
.dangerously_allow_web_serving_without_a_certificate,
|
||||
post_command_discovery_hook: options.post_command_discovery_hook,
|
||||
client_async_worker_tasks: options.client_async_worker_tasks.map(|v| v as u64),
|
||||
visual_bell: options.visual_bell,
|
||||
@@ -1140,6 +1142,8 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Options>
|
||||
web_server_cert: options.web_server_cert.map(std::path::PathBuf::from),
|
||||
web_server_key: options.web_server_key.map(std::path::PathBuf::from),
|
||||
enforce_https_for_localhost: options.enforce_https_for_localhost,
|
||||
dangerously_allow_web_serving_without_a_certificate: options
|
||||
.dangerously_allow_web_serving_without_a_certificate,
|
||||
post_command_discovery_hook: options.post_command_discovery_hook,
|
||||
client_async_worker_tasks: options.client_async_worker_tasks.map(|v| v as usize),
|
||||
visual_bell: options.visual_bell,
|
||||
|
||||
@@ -515,6 +515,7 @@ fn test_client_messages() {
|
||||
web_server_cert: Some(PathBuf::from("web_server_cert")),
|
||||
web_server_key: Some(PathBuf::from("web_server_key")),
|
||||
enforce_https_for_localhost: Some(true),
|
||||
dangerously_allow_web_serving_without_a_certificate: Some(true),
|
||||
post_command_discovery_hook: Some("post_command_discovery_hook".to_owned()),
|
||||
client_async_worker_tasks: Some(16),
|
||||
mouse_hover_effects: Some(false),
|
||||
|
||||
@@ -3128,6 +3128,12 @@ impl Options {
|
||||
let enforce_https_for_localhost =
|
||||
kdl_property_first_arg_as_bool_or_error!(kdl_options, "enforce_https_for_localhost")
|
||||
.map(|(v, _)| v);
|
||||
let dangerously_allow_web_serving_without_a_certificate =
|
||||
kdl_property_first_arg_as_bool_or_error!(
|
||||
kdl_options,
|
||||
"dangerously_allow_web_serving_without_a_certificate"
|
||||
)
|
||||
.map(|(v, _)| v);
|
||||
let post_command_discovery_hook =
|
||||
kdl_property_first_arg_as_string_or_error!(kdl_options, "post_command_discovery_hook")
|
||||
.map(|(hook, _entry)| hook.to_string());
|
||||
@@ -3257,6 +3263,7 @@ impl Options {
|
||||
web_server_cert,
|
||||
web_server_key,
|
||||
enforce_https_for_localhost,
|
||||
dangerously_allow_web_serving_without_a_certificate,
|
||||
post_command_discovery_hook,
|
||||
client_async_worker_tasks,
|
||||
nested_session_handling,
|
||||
@@ -4400,11 +4407,12 @@ impl Options {
|
||||
}
|
||||
fn enforce_https_for_localhost_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
|
||||
let comment_text = format!(
|
||||
"{}\n{}\n{}\n{}\n{}\n{}\n{}",
|
||||
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
|
||||
"/// Whether to enforce https connections to the web server when it is bound to localhost",
|
||||
"/// (127.0.0.0/8)",
|
||||
"///",
|
||||
"/// Note: https is ALWAYS enforced when bound to non-local interfaces",
|
||||
"/// Note: https is enforced when bound to non-local interfaces, unless",
|
||||
"/// dangerously_allow_web_serving_without_a_certificate is set",
|
||||
"///",
|
||||
"/// Default: false",
|
||||
"// ",
|
||||
@@ -4429,6 +4437,42 @@ impl Options {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn dangerously_allow_web_serving_without_a_certificate_to_kdl(
|
||||
&self,
|
||||
add_comments: bool,
|
||||
) -> Option<KdlNode> {
|
||||
let comment_text = format!(
|
||||
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
|
||||
" ",
|
||||
"// DANGEROUS: allow the web server to serve plain, unencrypted HTTP when bound to a",
|
||||
"// non-local interface without web_server_cert and web_server_key.",
|
||||
"// Anyone on the network can read and tamper with the traffic, including login tokens.",
|
||||
"// Only use behind a trusted TLS-terminating proxy or on a fully trusted network.",
|
||||
"// Does not override enforce_https_for_localhost.",
|
||||
"// Default: false",
|
||||
"// (Requires restart)",
|
||||
"//",
|
||||
);
|
||||
|
||||
let create_node = |node_value: bool| -> KdlNode {
|
||||
let mut node = KdlNode::new("dangerously_allow_web_serving_without_a_certificate");
|
||||
node.push(KdlValue::Bool(node_value));
|
||||
node
|
||||
};
|
||||
if let Some(value) = self.dangerously_allow_web_serving_without_a_certificate {
|
||||
let mut node = create_node(value);
|
||||
if add_comments {
|
||||
node.set_leading(format!("{}\n", comment_text));
|
||||
}
|
||||
Some(node)
|
||||
} else if add_comments {
|
||||
let mut node = create_node(false);
|
||||
node.set_leading(format!("{}\n// ", comment_text));
|
||||
Some(node)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn stacked_resize_to_kdl(&self, add_comments: bool) -> Option<KdlNode> {
|
||||
let comment_text = format!(
|
||||
"{}\n{}\n{}\n{}",
|
||||
@@ -5171,6 +5215,11 @@ impl Options {
|
||||
{
|
||||
nodes.push(enforce_https_for_localhost);
|
||||
}
|
||||
if let Some(dangerously_allow_web_serving_without_a_certificate) =
|
||||
self.dangerously_allow_web_serving_without_a_certificate_to_kdl(add_comments)
|
||||
{
|
||||
nodes.push(dangerously_allow_web_serving_without_a_certificate);
|
||||
}
|
||||
if let Some(stacked_resize) = self.stacked_resize_to_kdl(add_comments) {
|
||||
nodes.push(stacked_resize);
|
||||
}
|
||||
@@ -8888,6 +8937,48 @@ fn explicit_theme_hue_round_trips_through_kdl() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dangerously_allow_web_serving_without_a_certificate_is_parsed_and_round_trips() {
|
||||
for (fake_config, expected) in [
|
||||
(
|
||||
"dangerously_allow_web_serving_without_a_certificate true",
|
||||
Some(true),
|
||||
),
|
||||
(
|
||||
"dangerously_allow_web_serving_without_a_certificate false",
|
||||
Some(false),
|
||||
),
|
||||
("", None),
|
||||
] {
|
||||
let document: KdlDocument = fake_config.parse().unwrap();
|
||||
let deserialized = Options::from_kdl(&document).unwrap();
|
||||
assert_eq!(
|
||||
deserialized.dangerously_allow_web_serving_without_a_certificate, expected,
|
||||
"parsed from {:?}",
|
||||
fake_config
|
||||
);
|
||||
let mut serialized = Options::to_kdl(&deserialized, false);
|
||||
let mut fake_document = KdlDocument::new();
|
||||
fake_document.nodes_mut().append(&mut serialized);
|
||||
let deserialized_from_serialized =
|
||||
Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
deserialized_from_serialized.dangerously_allow_web_serving_without_a_certificate,
|
||||
expected,
|
||||
"round trip of {:?}",
|
||||
fake_config
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dangerously_allow_web_serving_without_a_certificate_rejects_non_bool() {
|
||||
let document: KdlDocument = "dangerously_allow_web_serving_without_a_certificate \"yes\""
|
||||
.parse()
|
||||
.unwrap();
|
||||
assert!(Options::from_kdl(&document).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_options_to_string() {
|
||||
let fake_config = r##"
|
||||
|
||||
+12
-2
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/kdl/mod.rs
|
||||
assertion_line: 9130
|
||||
expression: fake_config_stringified
|
||||
---
|
||||
|
||||
@@ -300,12 +299,23 @@ web_client {
|
||||
/// Whether to enforce https connections to the web server when it is bound to localhost
|
||||
/// (127.0.0.0/8)
|
||||
///
|
||||
/// Note: https is ALWAYS enforced when bound to non-local interfaces
|
||||
/// Note: https is enforced when bound to non-local interfaces, unless
|
||||
/// dangerously_allow_web_serving_without_a_certificate is set
|
||||
///
|
||||
/// Default: false
|
||||
//
|
||||
// enforce_https_for_localhost false
|
||||
|
||||
// DANGEROUS: allow the web server to serve plain, unencrypted HTTP when bound to a
|
||||
// non-local interface without web_server_cert and web_server_key.
|
||||
// Anyone on the network can read and tamper with the traffic, including login tokens.
|
||||
// Only use behind a trusted TLS-terminating proxy or on a fully trusted network.
|
||||
// Does not override enforce_https_for_localhost.
|
||||
// Default: false
|
||||
// (Requires restart)
|
||||
//
|
||||
// dangerously_allow_web_serving_without_a_certificate false
|
||||
|
||||
// Whether to stack panes when resizing beyond a certain size
|
||||
// Default: true
|
||||
//
|
||||
|
||||
+12
-2
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/kdl/mod.rs
|
||||
assertion_line: 8798
|
||||
expression: fake_document.to_string()
|
||||
---
|
||||
|
||||
@@ -261,12 +260,23 @@ web_sharing "disabled"
|
||||
/// Whether to enforce https connections to the web server when it is bound to localhost
|
||||
/// (127.0.0.0/8)
|
||||
///
|
||||
/// Note: https is ALWAYS enforced when bound to non-local interfaces
|
||||
/// Note: https is enforced when bound to non-local interfaces, unless
|
||||
/// dangerously_allow_web_serving_without_a_certificate is set
|
||||
///
|
||||
/// Default: false
|
||||
//
|
||||
// enforce_https_for_localhost false
|
||||
|
||||
// DANGEROUS: allow the web server to serve plain, unencrypted HTTP when bound to a
|
||||
// non-local interface without web_server_cert and web_server_key.
|
||||
// Anyone on the network can read and tamper with the traffic, including login tokens.
|
||||
// Only use behind a trusted TLS-terminating proxy or on a fully trusted network.
|
||||
// Does not override enforce_https_for_localhost.
|
||||
// Default: false
|
||||
// (Requires restart)
|
||||
//
|
||||
// dangerously_allow_web_serving_without_a_certificate false
|
||||
|
||||
// Whether to stack panes when resizing beyond a certain size
|
||||
// Default: true
|
||||
//
|
||||
|
||||
@@ -179,17 +179,20 @@ pub fn web_server_base_url(
|
||||
web_server_port: u16,
|
||||
has_certificate: bool,
|
||||
enforce_https_for_localhost: bool,
|
||||
dangerously_allow_web_serving_without_a_certificate: bool,
|
||||
) -> String {
|
||||
let is_loopback = match web_server_ip {
|
||||
IpAddr::V4(ipv4) => ipv4.is_loopback(),
|
||||
IpAddr::V6(ipv6) => ipv6.is_loopback(),
|
||||
};
|
||||
|
||||
let url_prefix = if is_loopback && !enforce_https_for_localhost && !has_certificate {
|
||||
"http"
|
||||
} else {
|
||||
"https"
|
||||
};
|
||||
let serves_plain_http = !has_certificate
|
||||
&& if is_loopback {
|
||||
!enforce_https_for_localhost
|
||||
} else {
|
||||
dangerously_allow_web_serving_without_a_certificate
|
||||
};
|
||||
let url_prefix = if serves_plain_http { "http" } else { "https" };
|
||||
format!("{}://{}:{}", url_prefix, web_server_ip, web_server_port)
|
||||
}
|
||||
|
||||
@@ -201,11 +204,15 @@ pub fn web_server_base_url_from_config(config_options: Options) -> String {
|
||||
let has_certificate =
|
||||
config_options.web_server_cert.is_some() && config_options.web_server_key.is_some();
|
||||
let enforce_https_for_localhost = config_options.enforce_https_for_localhost.unwrap_or(false);
|
||||
let dangerously_allow_web_serving_without_a_certificate = config_options
|
||||
.dangerously_allow_web_serving_without_a_certificate
|
||||
.unwrap_or(false);
|
||||
web_server_base_url(
|
||||
web_server_ip,
|
||||
web_server_port,
|
||||
has_certificate,
|
||||
enforce_https_for_localhost,
|
||||
dangerously_allow_web_serving_without_a_certificate,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -226,3 +233,77 @@ pub fn parse_base_url(url: &str) -> Result<ServerAddress> {
|
||||
|
||||
Ok(ServerAddress { ip, port })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod web_server_base_url_tests {
|
||||
use super::*;
|
||||
|
||||
fn remote() -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))
|
||||
}
|
||||
|
||||
fn localhost() -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_loopback_without_certificate_uses_https_by_default() {
|
||||
assert_eq!(
|
||||
web_server_base_url(remote(), 8082, false, false, false),
|
||||
"https://0.0.0.0:8082"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_loopback_without_certificate_uses_http_when_dangerously_allowed() {
|
||||
assert_eq!(
|
||||
web_server_base_url(remote(), 8082, false, false, true),
|
||||
"http://0.0.0.0:8082"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_loopback_with_certificate_uses_https_when_dangerously_allowed() {
|
||||
assert_eq!(
|
||||
web_server_base_url(remote(), 8082, true, false, true),
|
||||
"https://0.0.0.0:8082"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_urls_are_unchanged_by_dangerous_allowance() {
|
||||
assert_eq!(
|
||||
web_server_base_url(localhost(), 8082, false, false, true),
|
||||
"http://127.0.0.1:8082"
|
||||
);
|
||||
assert_eq!(
|
||||
web_server_base_url(localhost(), 8082, false, true, true),
|
||||
"https://127.0.0.1:8082"
|
||||
);
|
||||
assert_eq!(
|
||||
web_server_base_url(localhost(), 8082, true, false, true),
|
||||
"https://127.0.0.1:8082"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_url_from_config_reads_dangerous_allowance() {
|
||||
let options = Options {
|
||||
web_server_ip: Some(remote()),
|
||||
dangerously_allow_web_serving_without_a_certificate: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
web_server_base_url_from_config(options),
|
||||
"http://0.0.0.0:8082"
|
||||
);
|
||||
let options = Options {
|
||||
web_server_ip: Some(remote()),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
web_server_base_url_from_config(options),
|
||||
"https://0.0.0.0:8082"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/setup.rs
|
||||
assertion_line: 848
|
||||
expression: "format!(\"{:#?}\", options)"
|
||||
---
|
||||
Options {
|
||||
@@ -67,6 +66,7 @@ Options {
|
||||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
dangerously_allow_web_serving_without_a_certificate: None,
|
||||
post_command_discovery_hook: None,
|
||||
client_async_worker_tasks: None,
|
||||
nested_session_handling: None,
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/setup.rs
|
||||
assertion_line: 882
|
||||
expression: "format!(\"{:#?}\", options)"
|
||||
---
|
||||
Options {
|
||||
@@ -67,6 +66,7 @@ Options {
|
||||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
dangerously_allow_web_serving_without_a_certificate: None,
|
||||
post_command_discovery_hook: None,
|
||||
client_async_worker_tasks: None,
|
||||
nested_session_handling: None,
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/setup.rs
|
||||
assertion_line: 838
|
||||
expression: "format!(\"{:#?}\", options)"
|
||||
---
|
||||
Options {
|
||||
@@ -65,6 +64,7 @@ Options {
|
||||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
dangerously_allow_web_serving_without_a_certificate: None,
|
||||
post_command_discovery_hook: None,
|
||||
client_async_worker_tasks: None,
|
||||
nested_session_handling: None,
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/setup.rs
|
||||
assertion_line: 836
|
||||
expression: "format!(\"{:#?}\", config)"
|
||||
---
|
||||
Config {
|
||||
@@ -6175,6 +6174,7 @@ Config {
|
||||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
dangerously_allow_web_serving_without_a_certificate: None,
|
||||
post_command_discovery_hook: None,
|
||||
client_async_worker_tasks: None,
|
||||
nested_session_handling: None,
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/setup.rs
|
||||
assertion_line: 906
|
||||
expression: "format!(\"{:#?}\", config)"
|
||||
---
|
||||
Config {
|
||||
@@ -6175,6 +6174,7 @@ Config {
|
||||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
dangerously_allow_web_serving_without_a_certificate: None,
|
||||
post_command_discovery_hook: None,
|
||||
client_async_worker_tasks: None,
|
||||
nested_session_handling: None,
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/setup.rs
|
||||
assertion_line: 948
|
||||
expression: "format!(\"{:#?}\", config)"
|
||||
---
|
||||
Config {
|
||||
@@ -152,6 +151,7 @@ Config {
|
||||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
dangerously_allow_web_serving_without_a_certificate: None,
|
||||
post_command_discovery_hook: None,
|
||||
client_async_worker_tasks: None,
|
||||
nested_session_handling: None,
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/setup.rs
|
||||
assertion_line: 858
|
||||
expression: "format!(\"{:#?}\", options)"
|
||||
---
|
||||
Options {
|
||||
@@ -67,6 +66,7 @@ Options {
|
||||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
dangerously_allow_web_serving_without_a_certificate: None,
|
||||
post_command_discovery_hook: None,
|
||||
client_async_worker_tasks: None,
|
||||
nested_session_handling: None,
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/setup.rs
|
||||
assertion_line: 934
|
||||
expression: "format!(\"{:#?}\", config)"
|
||||
---
|
||||
Config {
|
||||
@@ -6175,6 +6174,7 @@ Config {
|
||||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
dangerously_allow_web_serving_without_a_certificate: None,
|
||||
post_command_discovery_hook: None,
|
||||
client_async_worker_tasks: None,
|
||||
nested_session_handling: None,
|
||||
|
||||
+1
-1
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: zellij-utils/src/setup.rs
|
||||
assertion_line: 920
|
||||
expression: "format!(\"{:#?}\", config)"
|
||||
---
|
||||
Config {
|
||||
@@ -6175,6 +6174,7 @@ Config {
|
||||
web_server_cert: None,
|
||||
web_server_key: None,
|
||||
enforce_https_for_localhost: None,
|
||||
dangerously_allow_web_serving_without_a_certificate: None,
|
||||
post_command_discovery_hook: None,
|
||||
client_async_worker_tasks: None,
|
||||
nested_session_handling: None,
|
||||
|
||||
Reference in New Issue
Block a user