Limit wildcard TLS migration to an observed tailnet bind conflict

This commit is contained in:
archipelago
2026-10-07 00:01:53 -04:00
parent fdee8658c3
commit 40fd91b9e1
2 changed files with 46 additions and 1 deletions
+40 -1
View File
@@ -1070,6 +1070,15 @@ fn is_cgnat(addr: &str) -> bool {
(64..=127).contains(&second)
}
fn has_tailnet_https_listener(sockets: &str) -> bool {
sockets.lines().any(|line| {
line.split_whitespace()
.nth(3)
.and_then(|local| local.rsplit_once(':'))
.is_some_and(|(address, port)| port == "443" && is_cgnat(address))
})
}
/// Rewrite the `listen <ip>:443 ssl;` set for one config's text. Returns the
/// new text when it differs. Lines for absent addresses are dropped and one
/// line per present address is kept, preserving the file's indentation.
@@ -1219,6 +1228,16 @@ async fn run_nginx_listener_repair() -> Result<bool> {
if present.is_empty() {
return Ok(false); // no network yet; a later boot pass will do it
}
// Preserve wildcard/IPv6 service on nodes without a competing tailnet
// listener. Only the observed address-specific bind conflict warrants
// migrating the managed wildcard profile to LAN-only IPv4 listeners.
let repair_wildcards = tokio::process::Command::new("ss")
.args(["-H", "-4", "-ltn"])
.output()
.await
.ok()
.filter(|output| output.status.success())
.is_some_and(|output| has_tailnet_https_listener(&String::from_utf8_lossy(&output.stdout)));
let mut changed = false;
let mut seen = std::collections::HashSet::new();
let repair_id = std::time::SystemTime::now()
@@ -1234,7 +1253,12 @@ async fn run_nginx_listener_repair() -> Result<bool> {
let Ok(text) = tokio::fs::read_to_string(&target).await else {
continue;
};
let Some(healed) = retarget_https_listeners(&text, &present) else {
let healed = if repair_wildcards {
retarget_https_listeners(&text, &present)
} else {
retarget_pinned_https_listeners(&text, &present)
};
let Some(healed) = healed else {
continue;
};
let staged = "/var/lib/archipelago/nginx-listeners.staged";
@@ -2367,6 +2391,21 @@ mod tests {
assert!(retarget_https_listeners(&healed, &present).is_none());
}
#[test]
fn tailnet_https_conflict_requires_an_actual_specific_socket() {
assert!(has_tailnet_https_listener(
"LISTEN 0 4096 100.72.136.7:443 0.0.0.0:*\n"
));
for sockets in [
"LISTEN 0 511 0.0.0.0:443 0.0.0.0:*\n",
"LISTEN 0 4096 100.72.136.7:8443 0.0.0.0:*\n",
"LISTEN 0 511 192.168.1.50:443 0.0.0.0:*\n",
"",
] {
assert!(!has_tailnet_https_listener(sockets));
}
}
#[test]
fn managed_wildcard_tls_migration_preserves_other_vhosts() {
let profile = "server {\n listen 443 ssl default_server;\n listen [::]:443 ssl default_server;\n server_name _;\n ssl_certificate /etc/archipelago/ssl/archipelago.crt;\n ssl_certificate_key /etc/archipelago/ssl/archipelago.key;\n root /opt/archipelago/web-ui;\n}\n";
+6
View File
@@ -150,3 +150,9 @@ These run the exact embedded shell against fake service commands; they do not
reload a real node. Added Rust profile-migration tests and the integrated backend
compile remain pending the shared qualification slot. The live repaired nodes
still run the previously qualified 49703d7e binary.
Review refinement: wildcard conversion additionally requires an actual IPv4
CGNAT-address port-443 listener, observed through read-only socket inspection.
Nodes without that competing tailnet bind retain their existing wildcard and
IPv6 HTTPS service. A configured Tailscale interface alone is not sufficient.
The added socket-profile cases are pending the same backend qualification run.