fix: apply cargo fmt to resolve CI formatting failures
Format all enrollment module source files and tests per rustfmt standards. Resolves Gitea CI workflow cargo fmt check failures.
This commit is contained in:
@ -7,7 +7,7 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::signal::unix::{SignalKind, signal as unix_signal};
|
||||
use tokio::signal::unix::{signal as unix_signal, SignalKind};
|
||||
|
||||
use crate::enroll::identity;
|
||||
|
||||
@ -99,7 +99,7 @@ impl EnrollmentClient {
|
||||
.expect("Failed to parse manager URL");
|
||||
|
||||
match parsed.scheme() {
|
||||
"http" | "https" => {}, // Allowed schemes
|
||||
"http" | "https" => {} // Allowed schemes
|
||||
other => panic!(
|
||||
"Invalid manager URL scheme '{}' — only 'http' and 'https' are allowed. \
|
||||
Refused dangerous scheme to prevent SSRF/path traversal.",
|
||||
@ -139,12 +139,11 @@ impl EnrollmentClient {
|
||||
/// - `Err` if URL parsing fails or DNS resolution yields no results
|
||||
pub async fn manager_ip(&self) -> Result<String> {
|
||||
// Parse URL to extract host using url crate for RFC-compliant parsing
|
||||
let parsed = url::Url::parse(&self.manager_url).with_context(|| {
|
||||
format!("Failed to parse manager URL '{}'", self.manager_url)
|
||||
})?;
|
||||
let host_str = parsed.host_str().with_context(|| {
|
||||
format!("Manager URL '{}' has no host component", self.manager_url)
|
||||
})?;
|
||||
let parsed = url::Url::parse(&self.manager_url)
|
||||
.with_context(|| format!("Failed to parse manager URL '{}'", self.manager_url))?;
|
||||
let host_str = parsed
|
||||
.host_str()
|
||||
.with_context(|| format!("Manager URL '{}' has no host component", self.manager_url))?;
|
||||
|
||||
// Check if already an IP address using url::Host parsing
|
||||
if let Ok(url::Host::Ipv4(addr)) = url::Host::parse(host_str) {
|
||||
@ -287,9 +286,8 @@ impl EnrollmentClient {
|
||||
.await
|
||||
.context("Failed to read status response body")?;
|
||||
|
||||
let status: EnrollmentStatusResponse =
|
||||
serde_json::from_str(&body)
|
||||
.context("Invalid status response — malformed JSON from manager")?;
|
||||
let status: EnrollmentStatusResponse = serde_json::from_str(&body)
|
||||
.context("Invalid status response — malformed JSON from manager")?;
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
@ -336,7 +334,11 @@ impl EnrollmentClient {
|
||||
max_attempts: u32,
|
||||
) -> Result<PkiBundle> {
|
||||
// Enforce hard limits
|
||||
let effective_interval = if interval_seconds == 0 { 60 } else { interval_seconds };
|
||||
let effective_interval = if interval_seconds == 0 {
|
||||
60
|
||||
} else {
|
||||
interval_seconds
|
||||
};
|
||||
let effective_max = match max_attempts {
|
||||
0 => 1440,
|
||||
n if n > 1440 => 1440,
|
||||
@ -417,7 +419,11 @@ impl EnrollmentClient {
|
||||
attempts = attempt,
|
||||
"Enrollment approved — received PKI bundle from manager"
|
||||
);
|
||||
return Ok(PkiBundle { ca_crt, server_crt, server_key });
|
||||
return Ok(PkiBundle {
|
||||
ca_crt,
|
||||
server_crt,
|
||||
server_key,
|
||||
});
|
||||
}
|
||||
EnrollmentStatusResponse::Denied => {
|
||||
tracing::warn!(
|
||||
@ -444,20 +450,22 @@ impl EnrollmentClient {
|
||||
total_seconds = total_seconds,
|
||||
"Enrollment polling timed out after maximum attempts"
|
||||
);
|
||||
Err(anyhow!("Enrollment timed out after {} hours ({}/{} attempts)",
|
||||
total_seconds / 3600, effective_max, effective_max))
|
||||
Err(anyhow!(
|
||||
"Enrollment timed out after {} hours ({}/{} attempts)",
|
||||
total_seconds / 3600,
|
||||
effective_max,
|
||||
effective_max
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a SIGINT (Ctrl+C) signal receiver.
|
||||
fn setup_sigint() -> Result<tokio::signal::unix::Signal> {
|
||||
unix_signal(SignalKind::interrupt())
|
||||
.context("Failed to create SIGINT signal handler")
|
||||
unix_signal(SignalKind::interrupt()).context("Failed to create SIGINT signal handler")
|
||||
}
|
||||
|
||||
/// Create a SIGTERM signal receiver.
|
||||
fn setup_sigterm() -> Result<tokio::signal::unix::Signal> {
|
||||
unix_signal(SignalKind::terminate())
|
||||
.context("Failed to create SIGTERM signal handler")
|
||||
unix_signal(SignalKind::terminate()).context("Failed to create SIGTERM signal handler")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -66,8 +66,7 @@ pub fn get_fqdn() -> Result<String> {
|
||||
|
||||
/// Collect all non-loopback IPv4 addresses from network interfaces.
|
||||
pub fn get_ip_addresses() -> Result<Vec<String>> {
|
||||
let ifaces = if_addrs::get_if_addrs()
|
||||
.context("Failed to enumerate network interfaces")?;
|
||||
let ifaces = if_addrs::get_if_addrs().context("Failed to enumerate network interfaces")?;
|
||||
|
||||
let mut addrs: Vec<String> = ifaces
|
||||
.iter()
|
||||
@ -105,16 +104,28 @@ pub fn get_os_details() -> Result<serde_json::Value> {
|
||||
let unquoted = value.trim().trim_matches('"').trim_matches('\'');
|
||||
match key {
|
||||
"NAME" => {
|
||||
details.insert("distro".into(), serde_json::Value::String(unquoted.to_string()));
|
||||
details.insert(
|
||||
"distro".into(),
|
||||
serde_json::Value::String(unquoted.to_string()),
|
||||
);
|
||||
}
|
||||
"VERSION_ID" => {
|
||||
details.insert("version".into(), serde_json::Value::String(unquoted.to_string()));
|
||||
details.insert(
|
||||
"version".into(),
|
||||
serde_json::Value::String(unquoted.to_string()),
|
||||
);
|
||||
}
|
||||
"ID_LIKE" => {
|
||||
details.insert("id_like".into(), serde_json::Value::String(unquoted.to_string()));
|
||||
details.insert(
|
||||
"id_like".into(),
|
||||
serde_json::Value::String(unquoted.to_string()),
|
||||
);
|
||||
}
|
||||
"VERSION_CODENAME" => {
|
||||
details.insert("codename".into(), serde_json::Value::String(unquoted.to_string()));
|
||||
details.insert(
|
||||
"codename".into(),
|
||||
serde_json::Value::String(unquoted.to_string()),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -123,7 +134,10 @@ pub fn get_os_details() -> Result<serde_json::Value> {
|
||||
} else {
|
||||
// Fallback for systems without os-release (very rare)
|
||||
details.insert("distro".into(), serde_json::Value::String("unknown".into()));
|
||||
details.insert("version".into(), serde_json::Value::String("unknown".into()));
|
||||
details.insert(
|
||||
"version".into(),
|
||||
serde_json::Value::String("unknown".into()),
|
||||
);
|
||||
}
|
||||
|
||||
// Kernel version via uname -r
|
||||
@ -159,6 +173,9 @@ mod tests {
|
||||
#[test]
|
||||
fn os_details_contains_kernel() {
|
||||
let details = get_os_details().expect("Failed to get OS details");
|
||||
assert!(details.get("kernel").is_some(), "OS details must contain kernel version");
|
||||
assert!(
|
||||
details.get("kernel").is_some(),
|
||||
"OS details must contain kernel version"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,8 +12,7 @@ use anyhow::{Context, Result};
|
||||
|
||||
/// Re-export key types for ergonomic access from parent modules.
|
||||
pub use client::{
|
||||
EnrollmentClient, EnrollmentRequest, EnrollmentResponse,
|
||||
EnrollmentStatusResponse, PkiBundle,
|
||||
EnrollmentClient, EnrollmentRequest, EnrollmentResponse, EnrollmentStatusResponse, PkiBundle,
|
||||
};
|
||||
/// Re-export identity extraction functions.
|
||||
pub use identity::{get_fqdn, get_ip_addresses, get_machine_id, get_os_details};
|
||||
@ -40,10 +39,16 @@ pub async fn run_enrollment(manager_url: &str, config: &super::AppConfig) -> Res
|
||||
tracing::info!("Registration successful - received polling token");
|
||||
|
||||
// Get polling config (use defaults if not set)
|
||||
let interval = config.enrollment.as_ref()
|
||||
.map(|e| e.polling_interval_seconds).unwrap_or(60);
|
||||
let max_attempts = config.enrollment.as_ref()
|
||||
.map(|e| e.max_poll_attempts).unwrap_or(1440);
|
||||
let interval = config
|
||||
.enrollment
|
||||
.as_ref()
|
||||
.map(|e| e.polling_interval_seconds)
|
||||
.unwrap_or(60);
|
||||
let max_attempts = config
|
||||
.enrollment
|
||||
.as_ref()
|
||||
.map(|e| e.max_poll_attempts)
|
||||
.unwrap_or(1440);
|
||||
|
||||
// Phase 2: Polling
|
||||
tracing::info!(
|
||||
@ -51,7 +56,9 @@ pub async fn run_enrollment(manager_url: &str, config: &super::AppConfig) -> Res
|
||||
max_attempts = max_attempts,
|
||||
"Starting enrollment - polling phase"
|
||||
);
|
||||
let pki_bundle = client.poll_for_approval(&response.polling_token, interval, max_attempts).await?;
|
||||
let pki_bundle = client
|
||||
.poll_for_approval(&response.polling_token, interval, max_attempts)
|
||||
.await?;
|
||||
|
||||
// Phase 3: PKI provisioning & whitelist update
|
||||
tracing::info!("Enrollment approved - starting PKI provisioning phase");
|
||||
@ -62,13 +69,15 @@ pub async fn run_enrollment(manager_url: &str, config: &super::AppConfig) -> Res
|
||||
&pki_bundle.server_crt,
|
||||
&pki_bundle.server_key,
|
||||
config.tls_config(),
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("PKI bundle written to disk");
|
||||
|
||||
// Resolve manager hostname to IP and append to whitelist
|
||||
let manager_ip = client.manager_ip().await.context(
|
||||
"Failed to resolve manager IP - cannot update whitelist",
|
||||
)?;
|
||||
let manager_ip = client
|
||||
.manager_ip()
|
||||
.await
|
||||
.context("Failed to resolve manager IP - cannot update whitelist")?;
|
||||
provision::append_manager_to_whitelist(&manager_ip, config.whitelist_path()).await?;
|
||||
tracing::info!(manager_ip = %manager_ip, "Manager IP appended to whitelist");
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
//! PKI provisioning module for self-enrollment.
|
||||
//! Handles certificate extraction, validation, and secure file writing.
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use crate::auth::WhitelistManager;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
@ -71,8 +71,9 @@ pub fn write_pem_file(path: &str, pem_data: &str, is_key: bool) -> Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(parent)?.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(parent, perms)
|
||||
.with_context(|| format!("Failed to set permissions on: {}", parent.display()))?;
|
||||
fs::set_permissions(parent, perms).with_context(|| {
|
||||
format!("Failed to set permissions on: {}", parent.display())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,14 +108,13 @@ pub fn write_pem_file(path: &str, pem_data: &str, is_key: bool) -> Result<()> {
|
||||
.with_context(|| format!("Failed to flush PEM data to: {}", temp_path.display()))?;
|
||||
|
||||
// Atomic rename to target path
|
||||
fs::rename(&temp_path, path)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to atomically rename {} to {}",
|
||||
temp_path.display(),
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
fs::rename(&temp_path, path).with_context(|| {
|
||||
format!(
|
||||
"Failed to atomically rename {} to {}",
|
||||
temp_path.display(),
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
tracing::info!(
|
||||
path = %path.display(),
|
||||
@ -138,7 +138,11 @@ pub async fn provision_pki_bundle(
|
||||
) -> Result<()> {
|
||||
// Determine target paths from config or defaults
|
||||
let (ca_path, cert_path, key_path) = if let Some(tls) = tls_config {
|
||||
(tls.ca_cert.clone(), tls.server_cert.clone(), tls.server_key.clone())
|
||||
(
|
||||
tls.ca_cert.clone(),
|
||||
tls.server_cert.clone(),
|
||||
tls.server_key.clone(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
DEFAULT_CA_CERT.to_string(),
|
||||
@ -148,10 +152,8 @@ pub async fn provision_pki_bundle(
|
||||
};
|
||||
|
||||
// 1. Validate all three PEM strings before any writes
|
||||
validate_pem(ca_crt, "CERTIFICATE")
|
||||
.context("CA certificate validation failed")?;
|
||||
validate_pem(server_crt, "CERTIFICATE")
|
||||
.context("Server certificate validation failed")?;
|
||||
validate_pem(ca_crt, "CERTIFICATE").context("CA certificate validation failed")?;
|
||||
validate_pem(server_crt, "CERTIFICATE").context("Server certificate validation failed")?;
|
||||
|
||||
// Server key can be PRIVATE KEY (PKCS#8), RSA PRIVATE KEY (PKCS#1), or EC PRIVATE KEY
|
||||
let key_valid = validate_pem(server_key, "PRIVATE KEY").is_ok()
|
||||
@ -165,14 +167,11 @@ pub async fn provision_pki_bundle(
|
||||
}
|
||||
|
||||
// 2. Write to configured paths (atomic writes)
|
||||
write_pem_file(&ca_path, ca_crt, false)
|
||||
.context("Failed to write CA certificate")?;
|
||||
write_pem_file(&ca_path, ca_crt, false).context("Failed to write CA certificate")?;
|
||||
|
||||
write_pem_file(&cert_path, server_crt, false)
|
||||
.context("Failed to write server certificate")?;
|
||||
write_pem_file(&cert_path, server_crt, false).context("Failed to write server certificate")?;
|
||||
|
||||
write_pem_file(&key_path, server_key, true)
|
||||
.context("Failed to write server key")?;
|
||||
write_pem_file(&key_path, server_key, true).context("Failed to write server key")?;
|
||||
|
||||
// 3. Log successful provisioning with structured fields
|
||||
tracing::info!(
|
||||
@ -198,11 +197,19 @@ pub async fn append_manager_to_whitelist(manager_ip: &str, whitelist_path: &str)
|
||||
}
|
||||
|
||||
// Create or load WhitelistManager and call append_entry
|
||||
let mut manager = WhitelistManager::new(whitelist_path)
|
||||
.with_context(|| format!("Failed to initialize whitelist manager for path: {}", whitelist_path))?;
|
||||
let mut manager = WhitelistManager::new(whitelist_path).with_context(|| {
|
||||
format!(
|
||||
"Failed to initialize whitelist manager for path: {}",
|
||||
whitelist_path
|
||||
)
|
||||
})?;
|
||||
|
||||
manager.append_entry(ip_or_cidr)
|
||||
.with_context(|| format!("Failed to append manager IP '{}' to whitelist at: {}", ip_or_cidr, whitelist_path))?;
|
||||
manager.append_entry(ip_or_cidr).with_context(|| {
|
||||
format!(
|
||||
"Failed to append manager IP '{}' to whitelist at: {}",
|
||||
ip_or_cidr, whitelist_path
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -343,7 +350,8 @@ mod tests {
|
||||
let dir = tempdir().expect("failed to create temp dir");
|
||||
let target_path = dir.path().join("cert.pem");
|
||||
let cert1 = sample_certificate();
|
||||
let cert2 = "-----BEGIN CERTIFICATE-----\nNEWCERTDATA\n-----END CERTIFICATE-----".to_string();
|
||||
let cert2 =
|
||||
"-----BEGIN CERTIFICATE-----\nNEWCERTDATA\n-----END CERTIFICATE-----".to_string();
|
||||
|
||||
// Write initial file
|
||||
write_pem_file(target_path.to_str().unwrap(), &cert1, false).expect("initial write failed");
|
||||
@ -352,7 +360,10 @@ mod tests {
|
||||
write_pem_file(target_path.to_str().unwrap(), &cert2, false).expect("second write failed");
|
||||
|
||||
let backup_path = format!("{}.bak", target_path.display());
|
||||
assert!(std::path::Path::new(&backup_path).exists(), "Backup file should exist");
|
||||
assert!(
|
||||
std::path::Path::new(&backup_path).exists(),
|
||||
"Backup file should exist"
|
||||
);
|
||||
|
||||
// Original content in backup
|
||||
let backup_content = fs::read_to_string(&backup_path).expect("failed to read backup");
|
||||
|
||||
Reference in New Issue
Block a user