Private
Public Access
1
0
Files
linux_patch_api/src/main.rs
Echo fc0b42040e
All checks were successful
CI/CD Pipeline / Code Format (push) Successful in 1s
CI/CD Pipeline / Clippy Lints (push) Successful in 44s
CI/CD Pipeline / All Unit Tests (push) Successful in 1m14s
CI/CD Pipeline / Security Audit (push) Successful in 4s
CI/CD Pipeline / Enrollment Tests (push) Successful in 1m52s
CI/CD Pipeline / Verify Enrollment CLI Flag (push) Successful in 1m23s
CI/CD Pipeline / Build Debian Package (Ubuntu 22.04) (push) Successful in 2m21s
CI/CD Pipeline / Build Arch Package (push) Successful in 2m47s
CI/CD Pipeline / Build Alpine Package (push) Successful in 3m57s
CI/CD Pipeline / Build Debian Package (push) Successful in 2m15s
CI/CD Pipeline / Build RPM Package (push) Successful in 4m19s
fix(server): add explicit rustls CryptoProvider initialization for v1.1.6
- Add rustls::crypto::aws_lc_rs::default_provider().install_default()
  in main() before any TLS operations to prevent startup panic
- Bump version from 1.1.5 to 1.1.6
- Update debian/changelog with 1.1.6-1 entry
2026-05-18 13:43:34 +00:00

243 lines
8.3 KiB
Rust

//! Linux Patch API - Main Entry Point
//!
//! Secure remote package management API for Linux systems.
//!
//! # Configuration
//!
//! Configuration is loaded from `/etc/linux_patch_api/config.yaml` by default.
//! Use `--config` flag to specify a custom configuration path.
//!
//! # Security
//!
//! - mTLS authentication required on port 12443
//! - IP whitelist enforced (deny by default)
//! - Detailed audit logging
use actix_web::middleware::Logger;
use actix_web::{web, App, HttpServer};
use anyhow::Result;
use clap::Parser;
use std::net::TcpListener;
use std::sync::Arc;
use tracing::{error, info, warn};
use linux_patch_api::api::{configure_api_routes, configure_health_route};
use linux_patch_api::auth::{mtls, MtlsMiddleware, WhitelistManager};
use linux_patch_api::enroll;
use linux_patch_api::packages::create_backend;
use linux_patch_api::{init_logging, AppConfig, JobManager};
/// Linux Patch API CLI arguments
#[derive(Parser, Debug)]
#[command(name = "linux-patch-api")]
#[command(version = env!("CARGO_PKG_VERSION"))]
#[command(about = "Secure remote package management API for Linux systems")]
struct Args {
/// Path to configuration file
#[arg(short, long, default_value = "/etc/linux_patch_api/config.yaml")]
config: String,
/// Enable verbose logging
#[arg(short, long)]
verbose: bool,
/// Enroll with manager at URL (skips mTLS startup, runs enrollment flow only)
#[arg(
long,
help = "Enroll with manager at URL (skips mTLS startup, runs enrollment flow only)"
)]
enroll: Option<String>,
}
#[actix_web::main]
async fn main() -> Result<()> {
// Parse command line arguments
let args = Args::parse();
// Initialize logging
let _guard = init_logging(args.verbose)?;
// Install rustls crypto provider (required for mTLS and HTTPS clients)
rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.expect("Failed to install rustls crypto provider (aws-lc-rs)");
info!(
version = env!("CARGO_PKG_VERSION"),
config_path = args.config,
"Linux Patch API starting"
);
// Load configuration
let config = match AppConfig::load(&args.config, args.enroll.is_some()) {
Ok(cfg) => {
info!(
port = cfg.server.port,
bind = &cfg.server.bind,
"Configuration loaded"
);
cfg
}
Err(e) => {
error!(error = %e, path = args.config, "Failed to load configuration");
return Err(anyhow::anyhow!("Configuration error: {}", e));
}
};
// Handle enrollment mode - runs before server startup
if let Some(ref manager_url) = args.enroll {
info!(
manager_url = manager_url,
"Enrollment mode activated - running enrollment flow before server startup"
);
match enroll::run_enrollment(manager_url, &config).await {
Ok(()) => {
info!("Enrollment complete - proceeding to server startup");
}
Err(e) => {
error!(error = %e, "Enrollment failed - shutting down");
return Err(anyhow::anyhow!("Enrollment failed: {}", e));
}
}
}
// Initialize job manager
let job_manager = JobManager::new(config.jobs.max_concurrent, config.jobs.timeout_minutes)?;
info!(
max_jobs = config.jobs.max_concurrent,
timeout_minutes = config.jobs.timeout_minutes,
"Job manager initialized"
);
// Initialize package manager backend
let package_backend = match create_backend() {
Ok(backend) => {
info!("Package manager backend initialized");
backend
}
Err(e) => {
error!(error = %e, "Failed to initialize package manager backend");
return Err(anyhow::anyhow!("Package backend error: {}", e));
}
};
// Initialize IP whitelist manager
let whitelist_path = config.whitelist_path();
info!(
path = whitelist_path,
"Initializing IP whitelist enforcement"
);
let whitelist_manager = match WhitelistManager::new(whitelist_path) {
Ok(manager) => {
info!(
entries = manager.entry_count(),
"Whitelist manager initialized"
);
Some(Arc::new(manager))
}
Err(e) => {
warn!(error = %e, "Failed to load whitelist - continuing with empty whitelist (all denied)");
None
}
};
// Store job manager and backend in Arc for sharing
let job_manager_data = web::Data::new(job_manager);
let backend_data = web::Data::new(package_backend);
// Configure bind address
let bind_address = format!("{}:{}", config.server.bind, config.server.port);
info!(bind = %bind_address, "Starting HTTP server");
// Create server
// Create server builder
let server_builder = HttpServer::new(move || {
let mut app = App::new()
.wrap(Logger::default())
.app_data(job_manager_data.clone())
.app_data(backend_data.clone());
// Configure API routes
app = app.configure(|cfg| {
configure_api_routes(cfg, job_manager_data.clone(), backend_data.clone());
});
// Configure health route (outside API scope)
app = app.configure(configure_health_route);
app
})
.workers(4)
// VULN-004: Configure header size limit to 8KB to prevent DoS via oversized headers
.client_request_timeout(std::time::Duration::from_secs(5))
// FIX: Set explicit client disconnect timeout to prevent connection resets on larger responses
.client_disconnect_timeout(std::time::Duration::from_secs(5))
.keep_alive(std::time::Duration::from_secs(15))
.max_connection_rate(1000);
info!(
mtls_enabled = config.tls_config().is_some(),
whitelist_enabled = whitelist_manager.is_some(),
"Security layer status"
);
info!("Linux Patch API initialized successfully");
info!("Listening on {}", bind_address);
// Apply TLS/mTLS configuration if enabled
if let Some(tls_config) = config.tls_config() {
info!(
ca_cert = %tls_config.ca_cert,
server_cert = %tls_config.server_cert,
server_key = %tls_config.server_key,
min_tls_version = %tls_config.min_tls_version,
"Initializing mTLS authentication with TLS binding"
);
let mtls_config = mtls::MtlsConfig {
ca_cert_path: tls_config.ca_cert.clone(),
server_cert_path: tls_config.server_cert.clone(),
server_key_path: tls_config.server_key.clone(),
min_tls_version: tls_config.min_tls_version.clone(),
};
match MtlsMiddleware::new(mtls_config.clone()) {
Ok(middleware) => {
// Build rustls server configuration
let rustls_config = middleware
.build_rustls_config()
.map_err(|e| anyhow::anyhow!("Failed to build rustls config: {}", e))?;
info!("mTLS middleware and rustls config initialized successfully");
// Create TCP listener (std::net for listen_rustls_0_23)
let tcp_listener = TcpListener::bind(&bind_address)
.map_err(|e| anyhow::anyhow!("Failed to bind to {}: {}", bind_address, e))?;
info!("TCP listener bound to {}", bind_address);
// Clone the ServerConfig from Arc for listen_rustls_0_23
let server_config = (*rustls_config).clone();
info!("Binding server with TLS 1.3 - non-TLS connections will be rejected");
// Bind with TLS using rustls 0.23 - non-TLS connections fail at handshake
server_builder
.listen_rustls_0_23(tcp_listener, server_config)?
.run()
.await?;
}
Err(e) => {
error!(error = %e, "Failed to initialize mTLS middleware");
return Err(anyhow::anyhow!("mTLS initialization failed: {}", e));
}
}
} else {
warn!("TLS is disabled - running without mTLS authentication (INSECURE)");
server_builder.bind(&bind_address)?.run().await?;
}
info!("Linux Patch API shutting down");
Ok(())
}