This commit is contained in:
Christoffer Martinsson 2025-10-12 17:21:05 +02:00
parent cd593e32d2
commit 4bb36b7735

View File

@ -660,7 +660,7 @@ impl ServiceCollector {
return None; return None;
} }
let config = String::from_utf8_lossy(&output.stdout); let config = String::from_utf8_lossy(&output.stdout);
return self.parse_nginx_config(&config); return self.parse_nginx_config(&config).await;
} }
Err(e) => { Err(e) => {
tracing::warn!("Failed to execute sudo nginx -T: {}", e); tracing::warn!("Failed to execute sudo nginx -T: {}", e);
@ -694,7 +694,7 @@ impl ServiceCollector {
let config = String::from_utf8_lossy(&output.stdout); let config = String::from_utf8_lossy(&output.stdout);
tracing::debug!("Got nginx config, {} bytes", config.len()); tracing::debug!("Got nginx config, {} bytes", config.len());
self.parse_nginx_config(&config) self.parse_nginx_config(&config).await
} }
async fn get_nginx_config_from_systemd(&self) -> Option<String> { async fn get_nginx_config_from_systemd(&self) -> Option<String> {
@ -730,7 +730,7 @@ impl ServiceCollector {
None None
} }
fn parse_nginx_config(&self, config: &str) -> Option<Vec<String>> { async fn parse_nginx_config(&self, config: &str) -> Option<Vec<String>> {
let mut sites = Vec::new(); let mut sites = Vec::new();
let lines: Vec<&str> = config.lines().collect(); let lines: Vec<&str> = config.lines().collect();
let mut i = 0; let mut i = 0;
@ -740,31 +740,39 @@ impl ServiceCollector {
// Look for server blocks // Look for server blocks
if trimmed == "server {" { if trimmed == "server {" {
if let Some(site_info) = self.parse_server_block(&lines, &mut i) { if let Some(hostname) = self.parse_server_block(&lines, &mut i) {
sites.push(site_info); sites.push(hostname);
} }
} }
i += 1; i += 1;
} }
tracing::info!("Extracted {} sites with routing info", sites.len()); tracing::info!("Found {} potential sites, checking accessibility", sites.len());
// Check which sites are actually accessible
let mut accessible_sites = Vec::new();
for site in sites {
if self.check_site_accessibility(&site).await {
accessible_sites.push(format!("{}", site));
}
}
// Limit to reasonable number // Limit to reasonable number
sites.truncate(15); accessible_sites.truncate(15);
tracing::info!("Final nginx sites list: {:?}", sites); tracing::info!("Final accessible nginx sites: {:?}", accessible_sites);
if sites.is_empty() { if accessible_sites.is_empty() {
tracing::warn!("No nginx sites found"); tracing::warn!("No accessible nginx sites found");
None None
} else { } else {
Some(sites) Some(accessible_sites)
} }
} }
fn parse_server_block(&self, lines: &[&str], start_index: &mut usize) -> Option<String> { fn parse_server_block(&self, lines: &[&str], start_index: &mut usize) -> Option<String> {
let mut server_names = Vec::new(); let mut server_names = Vec::new();
let mut destinations = Vec::new(); let mut has_redirect = false;
let mut i = *start_index + 1; let mut i = *start_index + 1;
let mut brace_count = 1; let mut brace_count = 1;
@ -788,37 +796,9 @@ impl ServiceCollector {
} }
} }
// Extract routing information // Check if this server block is just a redirect
if trimmed.starts_with("return") && trimmed.contains("301") { if trimmed.starts_with("return") && trimmed.contains("301") {
// Skip simple HTTPS redirects (return 301 https://$host$request_uri) has_redirect = true;
if !trimmed.contains("$host") {
// Meaningful redirect: return 301 https://pages.cmtec.se$request_uri;
if let Some(url_start) = trimmed.find("https://") {
let url_part = &trimmed[url_start + 8..]; // Skip "https://"
if let Some(url_end) = url_part.find('$') {
let domain = &url_part[..url_end];
destinations.push(format!("{}", domain));
}
}
}
} else if trimmed.starts_with("proxy_pass") {
// Proxy: proxy_pass http://localhost:8080;
if let Some(url_start) = trimmed.find("http") {
let url_part = &trimmed[url_start..];
if let Some(url_end) = url_part.find(';') {
let proxy_url = &url_part[..url_end];
destinations.push(format!("{}", proxy_url));
}
}
} else if trimmed.starts_with("root") {
// Static files: root /var/www/example;
if let Some(path_start) = trimmed.find('/') {
let path_part = &trimmed[path_start..];
if let Some(path_end) = path_part.find(';') {
let root_path = &path_part[..path_end];
destinations.push(format!("static {}", root_path));
}
}
} }
i += 1; i += 1;
@ -826,14 +806,47 @@ impl ServiceCollector {
*start_index = i - 1; *start_index = i - 1;
// Build site info string - only show sites with meaningful routing // Only return hostnames that are not redirects and have actual content
if !server_names.is_empty() && !destinations.is_empty() { if !server_names.is_empty() && !has_redirect {
let primary_name = &server_names[0]; Some(server_names[0].clone())
Some(format!("{} {}", primary_name, destinations[0]))
} else { } else {
None None
} }
} }
async fn check_site_accessibility(&self, hostname: &str) -> bool {
// Try HTTPS first, then HTTP
for scheme in ["https", "http"] {
let url = format!("{}://{}", scheme, hostname);
match tokio::time::timeout(
Duration::from_secs(5),
Command::new("curl")
.args(["-s", "-I", "--max-time", "3", &url])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
).await {
Ok(Ok(output)) if output.status.success() => {
let response = String::from_utf8_lossy(&output.stdout);
// Check for successful HTTP status codes
if response.contains("HTTP/") && (
response.contains(" 200 ") ||
response.contains(" 301 ") ||
response.contains(" 302 ") ||
response.contains(" 403 ") // Some sites return 403 but are still "accessible"
) {
tracing::debug!("Site {} accessible via {}", hostname, scheme);
return true;
}
}
_ => continue,
}
}
tracing::debug!("Site {} not accessible", hostname);
false
}
} }
#[async_trait] #[async_trait]