fix(enrollment): filter Docker bridge IPs and add report_interface/report_ip config
- identity.rs: filter 172.16.0.0/12 (Docker bridge) and 169.254.0.0/16 (link-local) from get_ip_addresses() auto-detection - identity.rs: add is_container_bridge(), is_link_local(), get_ip_for_interface(), get_primary_ip() functions - client.rs: add report_interface/report_ip fields to EnrollmentClient, new with_ip_overrides() constructor, register() uses get_primary_ip() - loader.rs: add report_interface/report_ip to EnrollmentConfig - mod.rs: wire config overrides through to EnrollmentClient - config.yaml.example: document new report_interface/report_ip options - Tests: add 18 new bridge filtering/IP override tests, fix Docker container compatibility in existing tests
This commit is contained in:
@ -82,8 +82,14 @@ fn build_tls_config(cert_dir: &std::path::Path) -> TlsConfig {
|
||||
}
|
||||
|
||||
/// Build an EnrollmentClient pointing at the mock server.
|
||||
/// Uses a test report_ip so enrollment works inside Docker containers
|
||||
/// where the only IPs are in the 172.16.0.0/12 bridge range (filtered).
|
||||
fn build_client(base_url: &str) -> EnrollmentClient {
|
||||
EnrollmentClient::new(base_url)
|
||||
EnrollmentClient::with_ip_overrides(
|
||||
base_url,
|
||||
None,
|
||||
Some("192.168.1.10".to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@ -33,8 +33,14 @@ async fn create_mock_manager() -> (MockServer, String) {
|
||||
}
|
||||
|
||||
/// Build an EnrollmentClient pointing at the mock server.
|
||||
/// Uses a test report_ip so enrollment works inside Docker containers
|
||||
/// where the only IPs are in the 172.16.0.0/12 bridge range (filtered).
|
||||
fn build_client(base_url: &str) -> EnrollmentClient {
|
||||
EnrollmentClient::new(base_url)
|
||||
EnrollmentClient::with_ip_overrides(
|
||||
base_url,
|
||||
None,
|
||||
Some("192.168.1.10".to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
use linux_patch_api::enroll::identity::{
|
||||
get_fqdn, get_ip_addresses, get_machine_id, get_os_details,
|
||||
get_primary_ip, is_container_bridge, is_link_local,
|
||||
};
|
||||
use linux_patch_api::enroll::EnrollmentRequest;
|
||||
use serde_json::Value;
|
||||
@ -144,10 +145,11 @@ fn test_fqdn_reasonable_length() {
|
||||
#[test]
|
||||
fn test_ip_addresses_returns_at_least_one() {
|
||||
let addrs = get_ip_addresses().expect("Failed to get IP addresses");
|
||||
assert!(
|
||||
!addrs.is_empty(),
|
||||
"Should return at least one IP address on this system"
|
||||
);
|
||||
// In Docker containers, all IPs may be in 172.16.0.0/12 (filtered), so empty is valid
|
||||
if addrs.is_empty() {
|
||||
eprintln!("NOTE: No routable IPs found — likely running inside a Docker container with only bridge IPs");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -364,11 +366,8 @@ fn test_enrollment_payload_construction() {
|
||||
let ip_addrs = get_ip_addresses().expect("Failed to get IP addresses");
|
||||
let os_details = get_os_details().expect("Failed to get OS details");
|
||||
|
||||
// Use first non-loopback IP as the primary address
|
||||
let primary_ip = ip_addrs
|
||||
.first()
|
||||
.expect("Should have at least one IP")
|
||||
.clone();
|
||||
// In Docker containers, all IPs may be in 172.16.0.0/12 (filtered), so use fallback
|
||||
let primary_ip = ip_addrs.first().cloned().unwrap_or_else(|| "127.0.0.1".to_string());
|
||||
|
||||
let request = EnrollmentRequest {
|
||||
machine_id,
|
||||
@ -514,3 +513,119 @@ fn test_identity_functions_do_not_panic() {
|
||||
let _ = get_os_details();
|
||||
});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Container Bridge & Link-Local Filtering Tests
|
||||
// =============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_is_container_bridge_docker_default_range() {
|
||||
// Docker default bridge: 172.17.0.0/16
|
||||
assert!(is_container_bridge(&"172.17.0.1".parse().unwrap()));
|
||||
assert!(is_container_bridge(&"172.17.255.255".parse().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_container_bridge_full_172_16_range() {
|
||||
// 172.16.0.0/12 = 172.16.0.0 – 172.31.255.255
|
||||
assert!(is_container_bridge(&"172.16.0.1".parse().unwrap()));
|
||||
assert!(is_container_bridge(&"172.20.0.1".parse().unwrap()));
|
||||
assert!(is_container_bridge(&"172.31.255.255".parse().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_not_container_bridge_outside_range() {
|
||||
assert!(!is_container_bridge(&"192.168.3.36".parse().unwrap()));
|
||||
assert!(!is_container_bridge(&"10.0.0.1".parse().unwrap()));
|
||||
assert!(!is_container_bridge(&"172.32.0.1".parse().unwrap()));
|
||||
assert!(!is_container_bridge(&"172.15.255.255".parse().unwrap()));
|
||||
assert!(!is_container_bridge(&"8.8.8.8".parse().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_link_local_range() {
|
||||
assert!(is_link_local(&"169.254.0.1".parse().unwrap()));
|
||||
assert!(is_link_local(&"169.254.255.255".parse().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_not_link_local() {
|
||||
assert!(!is_link_local(&"192.168.1.1".parse().unwrap()));
|
||||
assert!(!is_link_local(&"169.253.0.1".parse().unwrap()));
|
||||
assert!(!is_link_local(&"169.255.0.1".parse().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_ip_addresses_excludes_docker_bridge() {
|
||||
let addrs = get_ip_addresses().expect("Failed to get IP addresses");
|
||||
for addr in &addrs {
|
||||
let parsed: std::net::Ipv4Addr =
|
||||
addr.parse().expect("Should parse as IPv4");
|
||||
assert!(
|
||||
!is_container_bridge(&parsed),
|
||||
"IP '{}' is in Docker bridge range 172.16.0.0/12 — should be excluded",
|
||||
addr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_ip_addresses_excludes_link_local() {
|
||||
let addrs = get_ip_addresses().expect("Failed to get IP addresses");
|
||||
for addr in &addrs {
|
||||
let parsed: std::net::Ipv4Addr =
|
||||
addr.parse().expect("Should parse as IPv4");
|
||||
assert!(
|
||||
!is_link_local(&parsed),
|
||||
"IP '{}' is link-local 169.254.0.0/16 — should be excluded",
|
||||
addr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_primary_ip_auto_detect_no_bridge() {
|
||||
// In Docker containers, auto-detect may find no routable IPs — that's valid
|
||||
match get_primary_ip(None, None) {
|
||||
Ok(ip) => {
|
||||
assert!(!ip.is_empty(), "Primary IP should not be empty");
|
||||
let parsed: std::net::Ipv4Addr = ip.parse().expect("Primary IP should be valid IPv4");
|
||||
assert!(
|
||||
!is_container_bridge(&parsed),
|
||||
"Auto-detected IP should not be Docker bridge"
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("NOTE: No routable IPs found — likely running inside a Docker container");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_primary_ip_explicit_override() {
|
||||
let ip = get_primary_ip(None, Some("10.99.99.1"))
|
||||
.expect("Failed with explicit IP");
|
||||
assert_eq!(ip, "10.99.99.1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_primary_ip_rejects_loopback_override() {
|
||||
// Loopback override should fall back to auto-detect; if auto-detect also fails, that's valid
|
||||
match get_primary_ip(None, Some("127.0.0.1")) {
|
||||
Ok(ip) => assert_ne!(ip, "127.0.0.1"),
|
||||
Err(_) => {
|
||||
eprintln!("NOTE: Loopback rejected but no routable IPs for fallback — Docker container");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_primary_ip_invalid_override_falls_back() {
|
||||
// Invalid IP override should fall back to auto-detect; if auto-detect also fails, that's valid
|
||||
match get_primary_ip(None, Some("not-an-ip")) {
|
||||
Ok(ip) => assert!(!ip.is_empty()),
|
||||
Err(_) => {
|
||||
eprintln!("NOTE: Invalid IP rejected but no routable IPs for fallback — Docker container");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user