Private
Public Access
1
0

fix(enrollment): filter Docker bridge IPs and add report_interface/report_ip config
Some checks failed
CI/CD Pipeline / Code Format (push) Failing after 3s
CI/CD Pipeline / Clippy Lints (push) Failing after 44s
CI/CD Pipeline / Enrollment Tests (push) Has been skipped
CI/CD Pipeline / Verify Enrollment CLI Flag (push) Has been skipped
CI/CD Pipeline / All Unit Tests (push) Successful in 1m12s
CI/CD Pipeline / Build Debian Package (push) Has been skipped
CI/CD Pipeline / Build Debian Package (Ubuntu 22.04) (push) Has been skipped
CI/CD Pipeline / Build RPM Package (push) Has been skipped
CI/CD Pipeline / Build Alpine Package (push) Has been skipped
CI/CD Pipeline / Build Arch Package (push) Has been skipped
CI/CD Pipeline / Security Audit (push) Successful in 5s

- 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:
2026-05-18 02:02:54 +00:00
parent 5b5791f52f
commit 7c55c99e48
9 changed files with 428 additions and 224 deletions

View File

@ -77,6 +77,10 @@ pub struct EnrollmentClient {
pub manager_url: String,
/// Pre-configured reqwest client with insecure TLS and timeout.
http_client: reqwest::Client,
/// Network interface whose IP is reported to the manager (overrides auto-detect).
report_interface: Option<String>,
/// Explicit IPv4 address reported to the manager (highest priority override).
report_ip: Option<String>,
}
impl EnrollmentClient {
@ -91,6 +95,20 @@ impl EnrollmentClient {
/// contains a valid host component. Rejects dangerous schemes like `file://`,
/// `gopher://`, or URLs without a host.
pub fn new(manager_url: &str) -> Self {
Self::with_ip_overrides(manager_url, None, None)
}
/// Create a new enrollment client with optional IP reporting overrides.
///
/// See [`identity::get_primary_ip`] for resolution priority:
/// 1. `report_ip` — explicit IP (highest priority)
/// 2. `report_interface` — IP from named interface
/// 3. Auto-detect — first routable IP (container bridge subnets filtered)
pub fn with_ip_overrides(
manager_url: &str,
report_interface: Option<String>,
report_ip: Option<String>,
) -> Self {
// SECURITY: Validate URL scheme before building HTTP client.
// Only http and https are permitted to prevent path traversal, SSRF,
// or local file access via dangerous schemes (file://, gopher://, etc.).
@ -124,6 +142,8 @@ impl EnrollmentClient {
Self {
manager_url: manager_url.to_string(),
http_client,
report_interface,
report_ip,
}
}
@ -187,17 +207,14 @@ impl EnrollmentClient {
.context("Failed to read machine-id — host cannot enroll without identity")?;
let fqdn = identity::get_fqdn()
.context("Failed to determine FQDN — check hostname configuration")?;
let ip_addresses = identity::get_ip_addresses()
.context("Failed to enumerate network interfaces — check network configuration")?;
let ip_address = identity::get_primary_ip(
self.report_interface.as_deref(),
self.report_ip.as_deref(),
)
.context("Failed to determine reportable IP address — check network configuration or set report_interface/report_ip in config")?;
let os_details = identity::get_os_details()
.context("Failed to collect OS details — /etc/os-release may be missing")?;
// Use first non-loopback IP (manager expects single string)
let ip_address = ip_addresses
.first()
.cloned()
.unwrap_or_else(|| "127.0.0.1".to_string());
// 2. Build EnrollmentRequest struct
let request = EnrollmentRequest {
machine_id,