Cross-Platform Security Service Architecture in Rust
Introduction
Building security services that run reliably across different operating systems is a complex challenge. Each platform has its own service management system, security models, and operational requirements. Rust’s cross-platform capabilities, combined with modern async patterns, provide an excellent foundation for building enterprise-grade security daemons.
In this comprehensive guide, we’ll architect and implement a production-ready security monitoring service that runs natively on Linux (systemd), Windows Services, and macOS launch daemons, while maintaining consistent behavior and security guarantees across all platforms.
Architecture Overview
Design Principles
Our cross-platform security service architecture follows these core principles:
- Platform Abstraction: Hide OS-specific details behind clean interfaces
- Async-First: Use Tokio for efficient resource utilization
- Security by Default: Privilege dropping, input validation, secure defaults
- Observable: Built-in metrics, logging, and health checks
- Configurable: Layered configuration with validation
- Testable: Modular design enabling comprehensive testing
High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ Service Entry Point │
├─────────────────────────────────────────────────────────────┤
│ Platform Abstraction Layer │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ systemd │ │ Windows │ │ macOS │ │
│ │ Integration │ │ Service │ │ Launch Daemon │ │
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Core Service Logic │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ Config │ │ Security │ │ Monitoring │ │
│ │ Manager │ │ Engine │ │ Engine │ │
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Async Runtime (Tokio) │
└─────────────────────────────────────────────────────────────┘
Project Setup
Dependencies
# Cargo.toml
[package]
name = "security-service"
version = "1.0.0"
edition = "2021"
[dependencies]
# Async runtime
tokio = { version = "1.35", features = ["full"] }
tokio-util = { version = "0.7", features = ["codec"] }
# Cross-platform service management
async-trait = "0.1"
# Platform-specific dependencies
[target.'cfg(windows)'.dependencies]
windows-service = "0.6"
windows = { version = "0.52", features = [
"Win32_Foundation",
"Win32_Security",
"Win32_System_Services",
] }
[target.'cfg(target_os = "linux")'.dependencies]
sd-notify = "0.4"
nix = { version = "0.27", features = ["user", "signal"] }
[target.'cfg(target_os = "macos")'.dependencies]
launchd = "0.2"
nix = { version = "0.27", features = ["user", "signal"] }
# Common dependencies
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
config = "0.13"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
tracing-appender = "0.2"
anyhow = "1.0"
thiserror = "1.0"
clap = { version = "4.4", features = ["derive"] }
uuid = { version = "1.6", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
prometheus = { version = "0.13", features = ["process"] }
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite"] }
# Security dependencies
ring = "0.17"
rustls = "0.22"
password-hash = "0.5"
argon2 = "0.5"
[dev-dependencies]
tempfile = "3.8"
mockall = "0.12"
proptest = "1.4"
criterion = "0.5"
[[bin]]
name = "security-service"
path = "src/main.rs"
Project Structure
security-service/
├── Cargo.toml
├── src/
│ ├── main.rs
│ ├── service/
│ │ ├── mod.rs
│ │ ├── core.rs
│ │ ├── linux.rs
│ │ ├── windows.rs
│ │ └── macos.rs
│ ├── config/
│ │ ├── mod.rs
│ │ └── validation.rs
│ ├── security/
│ │ ├── mod.rs
│ │ ├── auth.rs
│ │ ├── crypto.rs
│ │ └── audit.rs
│ ├── monitoring/
│ │ ├── mod.rs
│ │ ├── collector.rs
│ │ └── analyzer.rs
│ └── api/
│ ├── mod.rs
│ └── handlers.rs
├── config/
│ ├── default.toml
│ ├── production.toml
│ └── development.toml
└── scripts/
├── install-linux.sh
├── install-windows.ps1
└── install-macos.sh
Core Service Implementation
Service Trait Definition
// src/service/mod.rs
use async_trait::async_trait;
use std::future::Future;
use std::pin::Pin;
use anyhow::Result;
#[async_trait]
pub trait ServiceController: Send + Sync {
/// Initialize the service
async fn initialize(&mut self) -> Result<()>;
/// Start the service
async fn start(&mut self) -> Result<()>;
/// Stop the service gracefully
async fn stop(&mut self) -> Result<()>;
/// Handle platform-specific control events
async fn handle_control_event(&mut self, event: ControlEvent) -> Result<()>;
/// Get service status
fn status(&self) -> ServiceStatus;
}
#[derive(Debug, Clone)]
pub enum ControlEvent {
Stop,
Pause,
Continue,
Reload,
Custom(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ServiceStatus {
Stopped,
Starting,
Running,
Stopping,
Paused,
Error(String),
}
/// Platform-specific service implementation
#[cfg(target_os = "linux")]
pub type PlatformService = linux::LinuxService;
#[cfg(target_os = "windows")]
pub type PlatformService = windows::WindowsService;
#[cfg(target_os = "macos")]
pub type PlatformService = macos::MacOSService;
Core Service Logic
// src/service/core.rs
use super::*;
use crate::config::Config;
use crate::security::SecurityEngine;
use crate::monitoring::MonitoringEngine;
use tokio::sync::{broadcast, RwLock};
use std::sync::Arc;
use tracing::{info, error, debug};
pub struct SecurityService {
config: Arc<RwLock<Config>>,
security_engine: Arc<SecurityEngine>,
monitoring_engine: Arc<MonitoringEngine>,
status: Arc<RwLock<ServiceStatus>>,
shutdown_tx: broadcast::Sender<()>,
tasks: Vec<tokio::task::JoinHandle<()>>,
}
impl SecurityService {
pub fn new(config: Config) -> Result<Self> {
let (shutdown_tx, _) = broadcast::channel(16);
Ok(Self {
config: Arc::new(RwLock::new(config)),
security_engine: Arc::new(SecurityEngine::new()?),
monitoring_engine: Arc::new(MonitoringEngine::new()?),
status: Arc::new(RwLock::new(ServiceStatus::Stopped)),
shutdown_tx,
tasks: Vec::new(),
})
}
async fn update_status(&self, status: ServiceStatus) {
let mut current = self.status.write().await;
*current = status;
}
async fn spawn_monitoring_task(&mut self) -> Result<()> {
let engine = Arc::clone(&self.monitoring_engine);
let mut shutdown_rx = self.shutdown_tx.subscribe();
let config = Arc::clone(&self.config);
let task = tokio::spawn(async move {
let mut interval = tokio::time::interval(
tokio::time::Duration::from_secs(10)
);
loop {
tokio::select! {
_ = interval.tick() => {
if let Err(e) = engine.collect_metrics(&config).await {
error!("Monitoring error: {}", e);
}
}
_ = shutdown_rx.recv() => {
info!("Monitoring task shutting down");
break;
}
}
}
});
self.tasks.push(task);
Ok(())
}
async fn spawn_security_task(&mut self) -> Result<()> {
let engine = Arc::clone(&self.security_engine);
let mut shutdown_rx = self.shutdown_tx.subscribe();
let task = tokio::spawn(async move {
let mut interval = tokio::time::interval(
tokio::time::Duration::from_secs(5)
);
loop {
tokio::select! {
_ = interval.tick() => {
if let Err(e) = engine.check_threats().await {
error!("Security check error: {}", e);
}
}
_ = shutdown_rx.recv() => {
info!("Security task shutting down");
break;
}
}
}
});
self.tasks.push(task);
Ok(())
}
}
#[async_trait]
impl ServiceController for SecurityService {
async fn initialize(&mut self) -> Result<()> {
info!("Initializing security service");
self.update_status(ServiceStatus::Starting).await;
// Load configuration
let config = self.config.read().await;
// Initialize security engine
self.security_engine.initialize(&config).await?;
// Initialize monitoring engine
self.monitoring_engine.initialize(&config).await?;
// Drop privileges if running as root
#[cfg(unix)]
if nix::unistd::geteuid().is_root() {
drop_privileges(&config)?;
}
info!("Security service initialized");
Ok(())
}
async fn start(&mut self) -> Result<()> {
info!("Starting security service");
// Spawn background tasks
self.spawn_monitoring_task().await?;
self.spawn_security_task().await?;
// Start API server
let api_task = crate::api::start_server(
Arc::clone(&self.config),
self.shutdown_tx.subscribe(),
).await?;
self.tasks.push(api_task);
self.update_status(ServiceStatus::Running).await;
info!("Security service started successfully");
Ok(())
}
async fn stop(&mut self) -> Result<()> {
info!("Stopping security service");
self.update_status(ServiceStatus::Stopping).await;
// Send shutdown signal
let _ = self.shutdown_tx.send(());
// Wait for all tasks to complete
for task in self.tasks.drain(..) {
if let Err(e) = task.await {
error!("Task join error: {}", e);
}
}
// Cleanup
self.security_engine.shutdown().await?;
self.monitoring_engine.shutdown().await?;
self.update_status(ServiceStatus::Stopped).await;
info!("Security service stopped");
Ok(())
}
async fn handle_control_event(&mut self, event: ControlEvent) -> Result<()> {
match event {
ControlEvent::Stop => self.stop().await,
ControlEvent::Reload => {
info!("Reloading configuration");
let new_config = Config::load().await?;
let mut config = self.config.write().await;
*config = new_config;
Ok(())
}
ControlEvent::Custom(cmd) => {
debug!("Received custom command: {}", cmd);
Ok(())
}
_ => Ok(()),
}
}
fn status(&self) -> ServiceStatus {
futures::executor::block_on(async {
self.status.read().await.clone()
})
}
}
#[cfg(unix)]
fn drop_privileges(config: &Config) -> Result<()> {
use nix::unistd::{setuid, setgid, Uid, Gid};
// Get configured user/group
let username = config.service.run_as_user.as_ref()
.ok_or_else(|| anyhow::anyhow!("run_as_user not configured"))?;
// Look up user
let user = nix::unistd::User::from_name(username)?
.ok_or_else(|| anyhow::anyhow!("User {} not found", username))?;
// Set supplementary groups
nix::unistd::setgroups(&[user.gid])?;
// Drop to target group
setgid(user.gid)?;
// Drop to target user
setuid(user.uid)?;
info!("Dropped privileges to user: {}", username);
Ok(())
}
Linux systemd Integration
// src/service/linux.rs
use super::*;
use sd_notify::{notify, NotifyState};
use nix::sys::signal::{self, Signal};
use tokio::signal::unix::{signal, SignalKind};
pub struct LinuxService {
inner: SecurityService,
systemd_enabled: bool,
}
impl LinuxService {
pub fn new(config: Config) -> Result<Self> {
let systemd_enabled = sd_notify::booted().unwrap_or(false);
Ok(Self {
inner: SecurityService::new(config)?,
systemd_enabled,
})
}
pub async fn run(mut self) -> Result<()> {
// Initialize service
self.inner.initialize().await?;
// Setup signal handlers
let mut sigterm = signal(SignalKind::terminate())?;
let mut sigint = signal(SignalKind::interrupt())?;
let mut sighup = signal(SignalKind::hangup())?;
// Start service
self.inner.start().await?;
// Notify systemd we're ready
if self.systemd_enabled {
notify(&[NotifyState::Ready])?;
// Send watchdog keepalive in background
tokio::spawn(async {
let mut interval = tokio::time::interval(
tokio::time::Duration::from_secs(30)
);
loop {
interval.tick().await;
let _ = notify(&[NotifyState::Watchdog]);
}
});
}
// Wait for signals
loop {
tokio::select! {
_ = sigterm.recv() => {
info!("Received SIGTERM");
break;
}
_ = sigint.recv() => {
info!("Received SIGINT");
break;
}
_ = sighup.recv() => {
info!("Received SIGHUP, reloading configuration");
self.inner.handle_control_event(ControlEvent::Reload).await?;
}
}
}
// Notify systemd we're stopping
if self.systemd_enabled {
notify(&[NotifyState::Stopping])?;
}
// Stop service
self.inner.stop().await?;
Ok(())
}
}
// systemd service file
pub const SYSTEMD_SERVICE: &str = r#"
[Unit]
Description=Security Monitoring Service
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/local/bin/security-service
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
KillSignal=SIGTERM
Restart=on-failure
RestartSec=5s
WatchdogSec=60s
# Security hardening
User=security-service
Group=security-service
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/security-service /var/log/security-service
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
RestrictSUIDSGID=true
RemoveIPC=true
PrivateMounts=true
# Resource limits
LimitNOFILE=65535
LimitNPROC=512
MemoryMax=1G
CPUQuota=50%
[Install]
WantedBy=multi-user.target
"#;
Windows Service Integration
// src/service/windows.rs
use super::*;
use windows_service::{
define_windows_service,
service::{
ServiceControl, ServiceControlAccept, ServiceExitCode,
ServiceState, ServiceStatus, ServiceType,
},
service_control_handler::{self, ServiceControlHandlerResult},
service_dispatcher,
};
use std::ffi::OsString;
use std::sync::Mutex;
use std::time::Duration;
use once_cell::sync::Lazy;
static SERVICE_CONTROL: Lazy<Mutex<Option<tokio::sync::mpsc::Sender<ControlEvent>>>> =
Lazy::new(|| Mutex::new(None));
pub struct WindowsService {
inner: SecurityService,
}
impl WindowsService {
pub fn new(config: Config) -> Result<Self> {
Ok(Self {
inner: SecurityService::new(config)?,
})
}
pub fn run() -> Result<()> {
// Register service with Windows
service_dispatcher::start("SecurityService", ffi_service_main)?;
Ok(())
}
}
define_windows_service!(ffi_service_main, service_main);
fn service_main(_arguments: Vec<OsString>) {
if let Err(e) = run_service() {
error!("Service error: {}", e);
}
}
fn run_service() -> Result<()> {
// Create runtime
let runtime = tokio::runtime::Runtime::new()?;
// Load configuration
let config = runtime.block_on(Config::load())?;
// Create service
let mut service = WindowsService::new(config)?;
// Create control channel
let (control_tx, mut control_rx) = tokio::sync::mpsc::channel(16);
// Store sender for control handler
{
let mut control = SERVICE_CONTROL.lock().unwrap();
*control = Some(control_tx);
}
// Define control handler
let event_handler = move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Stop => {
if let Some(tx) = SERVICE_CONTROL.lock().unwrap().as_ref() {
let _ = tx.blocking_send(ControlEvent::Stop);
}
ServiceControlHandlerResult::NoError
}
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
_ => ServiceControlHandlerResult::NotImplemented,
}
};
// Register control handler
let status_handle = service_control_handler::register(
"SecurityService",
event_handler,
)?;
// Report starting
status_handle.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::StartPending,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(10),
process_id: None,
})?;
// Run service
runtime.block_on(async {
// Initialize
service.inner.initialize().await?;
// Start service
service.inner.start().await?;
// Report running
status_handle.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})?;
// Wait for control events
while let Some(event) = control_rx.recv().await {
match event {
ControlEvent::Stop => {
// Report stopping
status_handle.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::StopPending,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(10),
process_id: None,
})?;
// Stop service
service.inner.stop().await?;
// Report stopped
status_handle.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})?;
break;
}
_ => {
service.inner.handle_control_event(event).await?;
}
}
}
Ok::<(), anyhow::Error>(())
})?;
Ok(())
}
Configuration Management
// src/config/mod.rs
use serde::{Deserialize, Serialize};
use config::{Config as ConfigBuilder, ConfigError, Environment, File};
use std::path::PathBuf;
use validator::{Validate, ValidationError};
#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
pub struct Config {
#[validate]
pub service: ServiceConfig,
#[validate]
pub security: SecurityConfig,
#[validate]
pub monitoring: MonitoringConfig,
#[validate]
pub api: ApiConfig,
#[validate]
pub logging: LoggingConfig,
}
#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
pub struct ServiceConfig {
#[validate(length(min = 1))]
pub name: String,
pub description: Option<String>,
#[cfg(unix)]
pub run_as_user: Option<String>,
#[cfg(unix)]
pub run_as_group: Option<String>,
#[validate(range(min = 1024, max = 65535))]
pub port: u16,
pub bind_address: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
pub struct SecurityConfig {
pub tls_cert_path: PathBuf,
pub tls_key_path: PathBuf,
#[validate(range(min = 8, max = 128))]
pub min_password_length: usize,
pub require_mfa: bool,
#[validate(range(min = 1, max = 86400))]
pub session_timeout_seconds: u64,
#[validate(range(min = 1, max = 100))]
pub max_login_attempts: u32,
pub allowed_origins: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
pub struct MonitoringConfig {
#[validate(range(min = 1, max = 3600))]
pub metrics_interval_seconds: u64,
pub enable_prometheus: bool,
#[validate(url)]
pub prometheus_push_gateway: Option<String>,
pub alert_thresholds: AlertThresholds,
}
#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
pub struct AlertThresholds {
#[validate(range(min = 0.0, max = 100.0))]
pub cpu_percent: f64,
#[validate(range(min = 0.0, max = 100.0))]
pub memory_percent: f64,
#[validate(range(min = 0, max = 100000))]
pub error_rate_per_minute: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
pub struct ApiConfig {
#[validate(range(min = 1, max = 10000))]
pub rate_limit_per_minute: u32,
#[validate(range(min = 1, max = 1000))]
pub max_request_size_mb: usize,
#[validate(range(min = 1, max = 300))]
pub request_timeout_seconds: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
pub struct LoggingConfig {
pub level: String,
pub format: LogFormat,
pub output: LogOutput,
pub file_path: Option<PathBuf>,
#[validate(range(min = 1, max = 1000))]
pub max_file_size_mb: usize,
#[validate(range(min = 1, max = 100))]
pub max_files: usize,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum LogFormat {
Json,
Pretty,
Compact,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum LogOutput {
Stdout,
File,
Both,
}
impl Config {
pub async fn load() -> Result<Self> {
let config_dir = Self::get_config_dir()?;
let environment = std::env::var("SERVICE_ENV")
.unwrap_or_else(|_| "development".to_string());
let config = ConfigBuilder::builder()
// Start with default configuration
.add_source(File::from(config_dir.join("default.toml")))
// Layer on environment-specific config
.add_source(
File::from(config_dir.join(format!("{}.toml", environment)))
.required(false)
)
// Layer on environment variables
.add_source(
Environment::with_prefix("SECURITY_SERVICE")
.separator("__")
.try_parsing(true)
)
.build()?;
let config: Config = config.try_deserialize()?;
config.validate()?;
Ok(config)
}
fn get_config_dir() -> Result<PathBuf> {
// Check for explicit config directory
if let Ok(dir) = std::env::var("CONFIG_DIR") {
return Ok(PathBuf::from(dir));
}
// Use platform-specific defaults
#[cfg(unix)]
let config_dir = PathBuf::from("/etc/security-service");
#[cfg(windows)]
let config_dir = std::env::var("ProgramData")
.map(|p| PathBuf::from(p).join("SecurityService"))
.unwrap_or_else(|_| PathBuf::from("C:\\ProgramData\\SecurityService"));
Ok(config_dir)
}
}
Security Engine
// src/security/mod.rs
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::config::Config;
pub struct SecurityEngine {
threat_detector: Arc<ThreatDetector>,
access_controller: Arc<AccessController>,
audit_logger: Arc<AuditLogger>,
}
impl SecurityEngine {
pub fn new() -> Result<Self> {
Ok(Self {
threat_detector: Arc::new(ThreatDetector::new()?),
access_controller: Arc::new(AccessController::new()?),
audit_logger: Arc::new(AuditLogger::new()?),
})
}
pub async fn initialize(&self, config: &Config) -> Result<()> {
self.threat_detector.initialize(config).await?;
self.access_controller.initialize(config).await?;
self.audit_logger.initialize(config).await?;
Ok(())
}
pub async fn check_threats(&self) -> Result<()> {
// Run threat detection logic
let threats = self.threat_detector.scan().await?;
// Log any detected threats
for threat in threats {
self.audit_logger.log_threat(&threat).await?;
// Take action based on threat severity
match threat.severity {
ThreatSeverity::Critical => {
// Immediate response
self.respond_to_critical_threat(&threat).await?;
}
ThreatSeverity::High => {
// Alert administrators
self.alert_administrators(&threat).await?;
}
_ => {
// Log for analysis
}
}
}
Ok(())
}
pub async fn shutdown(&self) -> Result<()> {
self.audit_logger.flush().await?;
Ok(())
}
async fn respond_to_critical_threat(&self, threat: &Threat) -> Result<()> {
// Implement immediate response logic
// e.g., block IP, terminate process, isolate system
Ok(())
}
async fn alert_administrators(&self, threat: &Threat) -> Result<()> {
// Send alerts via configured channels
// e.g., email, SMS, Slack, PagerDuty
Ok(())
}
}
Deployment Patterns
Linux Deployment Script
#!/bin/bash
# scripts/install-linux.sh
set -euo pipefail
SERVICE_NAME="security-service"
SERVICE_USER="security-service"
INSTALL_DIR="/opt/security-service"
CONFIG_DIR="/etc/security-service"
LOG_DIR="/var/log/security-service"
DATA_DIR="/var/lib/security-service"
# Create service user
if ! id "$SERVICE_USER" &>/dev/null; then
useradd --system --shell /bin/false --home-dir "$DATA_DIR" "$SERVICE_USER"
fi
# Create directories
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR"
# Copy binary
cp target/release/security-service "$INSTALL_DIR/"
chmod 755 "$INSTALL_DIR/security-service"
# Copy configuration
cp config/*.toml "$CONFIG_DIR/"
chmod 640 "$CONFIG_DIR"/*.toml
# Set ownership
chown -R "$SERVICE_USER:$SERVICE_USER" "$LOG_DIR" "$DATA_DIR"
chown root:$SERVICE_USER "$CONFIG_DIR"
# Install systemd service
cp scripts/security-service.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable security-service
echo "Security service installed successfully"
echo "Start with: systemctl start security-service"
echo "View logs: journalctl -u security-service -f"
Windows Deployment Script
# scripts/install-windows.ps1
param(
[Parameter(Mandatory=$false)]
[string]$ServiceName = "SecurityService",
[Parameter(Mandatory=$false)]
[string]$InstallPath = "C:\Program Files\SecurityService"
)
# Require administrator privileges
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Error "This script must be run as Administrator."
exit 1
}
# Create installation directory
New-Item -ItemType Directory -Force -Path $InstallPath
New-Item -ItemType Directory -Force -Path "$env:ProgramData\SecurityService"
# Copy files
Copy-Item "target\release\security-service.exe" -Destination $InstallPath
Copy-Item "config\*.toml" -Destination "$env:ProgramData\SecurityService"
# Install service
$binPath = Join-Path $InstallPath "security-service.exe"
New-Service -Name $ServiceName `
-BinaryPathName $binPath `
-DisplayName "Security Monitoring Service" `
-Description "Enterprise security monitoring and threat detection" `
-StartupType Automatic
# Configure service recovery
sc.exe failure $ServiceName reset= 86400 actions= restart/5000/restart/10000/restart/30000
Write-Host "Security service installed successfully"
Write-Host "Start with: Start-Service $ServiceName"
Write-Host "View logs: Get-EventLog -LogName Application -Source $ServiceName"
Performance Optimization
Async Best Practices
// Use bounded channels to prevent memory exhaustion
let (tx, rx) = tokio::sync::mpsc::channel(1000);
// Use select! for efficient event handling
tokio::select! {
biased; // Process in order of priority
result = high_priority_task() => {
handle_high_priority(result);
}
result = normal_task() => {
handle_normal(result);
}
_ = shutdown_signal() => {
break;
}
}
// Use buffer pools for zero-allocation processing
use bytes::{Bytes, BytesMut};
let pool = Arc::new(Mutex::new(Vec::new()));
// Batch operations for efficiency
let batch_size = 100;
let mut batch = Vec::with_capacity(batch_size);
Resource Management
// Implement connection pooling
use deadpool::managed::{Manager, Pool};
// Configure thread pools appropriately
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(num_cpus::get())
.thread_name("security-service")
.enable_all()
.build()?;
// Use resource limits
#[cfg(unix)]
{
use rlimit::{Resource, setrlimit};
setrlimit(Resource::NOFILE, 65535, 65535)?;
}
Security Hardening
Input Validation
use validator::Validate;
#[derive(Validate)]
struct SecurityRequest {
#[validate(length(min = 1, max = 256))]
#[validate(regex = "VALID_ID_REGEX")]
id: String,
#[validate(range(min = 0, max = 1000000))]
count: u64,
#[validate(custom = "validate_timestamp")]
timestamp: i64,
}
fn validate_timestamp(timestamp: i64) -> Result<(), ValidationError> {
let now = chrono::Utc::now().timestamp();
if (now - timestamp).abs() > 300 { // 5 minutes
return Err(ValidationError::new("timestamp_out_of_range"));
}
Ok(())
}
Secure Communication
// TLS configuration
let tls_config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(cert_chain, private_key)?;
// Certificate pinning
let mut root_store = rustls::RootCertStore::empty();
root_store.add(&expected_cert)?;
Monitoring and Observability
Metrics Collection
use prometheus::{Counter, Gauge, Histogram, Registry};
lazy_static! {
static ref REQUEST_COUNTER: Counter = Counter::new(
"security_service_requests_total",
"Total number of requests"
).unwrap();
static ref THREAT_GAUGE: Gauge = Gauge::new(
"security_service_active_threats",
"Number of active threats"
).unwrap();
static ref RESPONSE_TIME: Histogram = Histogram::with_opts(
prometheus::HistogramOpts::new(
"security_service_response_seconds",
"Response time in seconds"
).buckets(vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0])
).unwrap();
}
Health Checks
#[derive(Serialize)]
struct HealthStatus {
status: &'static str,
version: &'static str,
uptime_seconds: u64,
checks: HashMap<String, CheckResult>,
}
async fn health_check() -> Result<HealthStatus> {
let mut checks = HashMap::new();
// Database check
checks.insert("database", check_database().await);
// External service check
checks.insert("threat_intel", check_threat_intel_api().await);
// Resource check
checks.insert("resources", check_system_resources().await);
let overall_status = if checks.values().all(|c| c.healthy) {
"healthy"
} else {
"degraded"
};
Ok(HealthStatus {
status: overall_status,
version: env!("CARGO_PKG_VERSION"),
uptime_seconds: get_uptime(),
checks,
})
}
Testing Strategies
Integration Tests
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[tokio::test]
async fn test_service_lifecycle() {
// Create test configuration
let temp_dir = TempDir::new().unwrap();
let config = create_test_config(&temp_dir);
// Create service
let mut service = SecurityService::new(config).unwrap();
// Test initialization
service.initialize().await.unwrap();
assert_eq!(service.status(), ServiceStatus::Starting);
// Test start
service.start().await.unwrap();
assert_eq!(service.status(), ServiceStatus::Running);
// Test reload
service.handle_control_event(ControlEvent::Reload)
.await
.unwrap();
// Test stop
service.stop().await.unwrap();
assert_eq!(service.status(), ServiceStatus::Stopped);
}
}
Conclusion
Building cross-platform security services in Rust requires careful attention to platform-specific details while maintaining a clean, unified architecture. By leveraging Rust’s type system, async runtime, and security features, we can create robust services that run reliably across different operating systems.
Key takeaways:
- Abstract platform differences behind clean interfaces
- Use async/await for efficient resource utilization
- Implement comprehensive security measures from the start
- Build observability into the service architecture
- Test thoroughly on all target platforms
Ready to implement advanced security monitoring? Check out our next article on Building Production eBPF Security Monitors where we’ll combine eBPF with our service architecture.