zernel/commands/
secure.rs

1// Copyright (C) 2026 Dyber, Inc. — Proprietary
2
3//! zernel secure — System hardening for production ML
4//!
5//! Lock down the OS for production GPU clusters: disable unnecessary services,
6//! configure firewall, enable audit logging, set up security updates.
7
8use anyhow::Result;
9use clap::Subcommand;
10use std::process::Command;
11
12#[derive(Subcommand)]
13pub enum SecureCommands {
14    /// Scan system and show security recommendations
15    Scan,
16    /// Apply production hardening (requires root)
17    Harden {
18        /// Dry run
19        #[arg(long)]
20        dry_run: bool,
21    },
22    /// Configure firewall for ML workloads (NCCL ports only)
23    Firewall {
24        /// Dry run
25        #[arg(long)]
26        dry_run: bool,
27    },
28    /// Enable audit logging
29    Audit,
30    /// Check for security updates
31    Updates,
32}
33
34fn run_check(_name: &str, cmd_name: &str, args: &[&str]) -> (bool, String) {
35    match Command::new(cmd_name).args(args).output() {
36        Ok(o) if o.status.success() => {
37            let out = String::from_utf8_lossy(&o.stdout).trim().to_string();
38            (true, out)
39        }
40        _ => (false, "N/A".into()),
41    }
42}
43
44pub async fn run(cmd: SecureCommands) -> Result<()> {
45    match cmd {
46        SecureCommands::Scan => {
47            println!("Zernel Security Scan");
48            println!("{}", "=".repeat(60));
49            println!();
50
51            let mut issues = 0;
52
53            // Check SSH
54            println!("[1/8] SSH Configuration");
55            #[cfg(target_os = "linux")]
56            {
57                if let Ok(config) = std::fs::read_to_string("/etc/ssh/sshd_config") {
58                    let root_login = config
59                        .lines()
60                        .any(|l| l.trim().starts_with("PermitRootLogin") && l.contains("yes"));
61                    let password_auth = config.lines().any(|l| {
62                        l.trim().starts_with("PasswordAuthentication") && l.contains("yes")
63                    });
64
65                    if root_login {
66                        println!("  WARN: Root login enabled — disable with PermitRootLogin no");
67                        issues += 1;
68                    } else {
69                        println!("  OK: Root login disabled");
70                    }
71
72                    if password_auth {
73                        println!("  WARN: Password auth enabled — use key-based auth only");
74                        issues += 1;
75                    } else {
76                        println!("  OK: Key-based auth only");
77                    }
78                } else {
79                    println!("  SKIP: sshd_config not found");
80                }
81            }
82            #[cfg(not(target_os = "linux"))]
83            println!("  SKIP: Linux only");
84
85            // Check firewall
86            println!();
87            println!("[2/8] Firewall");
88            let (fw_ok, _) = run_check("iptables", "iptables", &["-L", "-n"]);
89            if fw_ok {
90                println!("  OK: Firewall active");
91            } else {
92                println!("  WARN: No firewall rules — configure with: zernel secure firewall");
93                issues += 1;
94            }
95
96            // Check unattended upgrades
97            println!();
98            println!("[3/8] Security Updates");
99            #[cfg(target_os = "linux")]
100            {
101                let auto_updates =
102                    std::path::Path::new("/etc/apt/apt.conf.d/20auto-upgrades").exists();
103                if auto_updates {
104                    println!("  OK: Unattended security updates enabled");
105                } else {
106                    println!("  WARN: Auto security updates not configured");
107                    println!("    Fix: apt install unattended-upgrades && dpkg-reconfigure unattended-upgrades");
108                    issues += 1;
109                }
110            }
111            #[cfg(not(target_os = "linux"))]
112            println!("  SKIP: Linux only");
113
114            // Check swap
115            println!();
116            println!("[4/8] Swap (should be disabled for ML)");
117            #[cfg(target_os = "linux")]
118            {
119                if let Ok(swaps) = std::fs::read_to_string("/proc/swaps") {
120                    let swap_active = swaps.lines().count() > 1;
121                    if swap_active {
122                        println!("  WARN: Swap is active — disable with: swapoff -a");
123                        issues += 1;
124                    } else {
125                        println!("  OK: Swap disabled");
126                    }
127                }
128            }
129            #[cfg(not(target_os = "linux"))]
130            println!("  SKIP: Linux only");
131
132            // Check unnecessary services
133            println!();
134            println!("[5/8] Unnecessary Services");
135            let unnecessary = ["cups", "avahi-daemon", "bluetooth", "ModemManager"];
136            for svc in &unnecessary {
137                let (running, _) = run_check(svc, "systemctl", &["is-active", svc]);
138                if running {
139                    println!("  WARN: {svc} is running — not needed for ML servers");
140                    issues += 1;
141                }
142            }
143            if issues == 0 {
144                println!("  OK: No unnecessary services detected");
145            }
146
147            // Check GPU persistence mode
148            println!();
149            println!("[6/8] GPU Persistence Mode");
150            let (_, gpu_pm) = run_check(
151                "nvidia-smi",
152                "nvidia-smi",
153                &["--query-gpu=persistence_mode", "--format=csv,noheader"],
154            );
155            if gpu_pm.contains("Enabled") {
156                println!("  OK: GPU persistence mode enabled");
157            } else {
158                println!("  WARN: GPU persistence mode disabled — enable with: nvidia-smi -pm 1");
159                issues += 1;
160            }
161
162            // Check PQC keys
163            println!();
164            println!("[7/8] Post-Quantum Cryptography");
165            let pqc_dir = crate::experiments::tracker::zernel_dir().join("pqc");
166            let has_keys = pqc_dir.exists()
167                && std::fs::read_dir(&pqc_dir)
168                    .map(|entries| entries.flatten().count() > 0)
169                    .unwrap_or(false);
170            if has_keys {
171                println!("  OK: PQC keys configured");
172            } else {
173                println!("  INFO: No PQC keys — generate with: zernel pqc keygen");
174            }
175
176            // Check kernel tuning
177            println!();
178            println!("[8/8] Kernel Tuning");
179            #[cfg(target_os = "linux")]
180            {
181                let zernel_conf = std::path::Path::new("/etc/sysctl.d/99-zernel.conf");
182                if zernel_conf.exists() {
183                    println!("  OK: Zernel sysctl tuning applied");
184                } else {
185                    println!("  WARN: Zernel sysctl tuning not applied");
186                    println!("    Fix: zernel tune apply");
187                    issues += 1;
188                }
189            }
190            #[cfg(not(target_os = "linux"))]
191            println!("  SKIP: Linux only");
192
193            // Summary
194            println!();
195            println!("{}", "=".repeat(60));
196            if issues == 0 {
197                println!("Security scan: PASS (0 issues)");
198            } else {
199                println!("Security scan: {issues} issue(s) found");
200                println!("Harden: zernel secure harden");
201            }
202        }
203
204        SecureCommands::Harden { dry_run } => {
205            println!(
206                "Zernel System Hardening{}",
207                if dry_run { " (dry run)" } else { "" }
208            );
209            println!("{}", "=".repeat(50));
210
211            let actions: Vec<(&str, Vec<&str>)> = vec![
212                ("Disable swap", vec!["swapoff", "-a"]),
213                ("Enable GPU persistence", vec!["nvidia-smi", "-pm", "1"]),
214                (
215                    "Disable unnecessary services",
216                    vec![
217                        "systemctl",
218                        "disable",
219                        "--now",
220                        "cups",
221                        "avahi-daemon",
222                        "bluetooth",
223                    ],
224                ),
225                (
226                    "Install unattended-upgrades",
227                    vec!["apt-get", "install", "-y", "unattended-upgrades"],
228                ),
229            ];
230
231            for (name, args) in &actions {
232                if dry_run {
233                    println!("  WOULD: {name} ({} {})", args[0], args[1..].join(" "));
234                } else {
235                    print!("  {name}... ");
236                    let status = Command::new(args[0]).args(&args[1..]).output();
237                    match status {
238                        Ok(o) if o.status.success() => println!("OK"),
239                        _ => println!("SKIP (may need root)"),
240                    }
241                }
242            }
243
244            // Apply sysctl tuning
245            if dry_run {
246                println!("  WOULD: Apply kernel tuning (zernel tune apply)");
247            } else {
248                println!("  Applying kernel tuning...");
249                let _ = Command::new("zernel").args(["tune", "apply"]).status();
250            }
251
252            println!();
253            if !dry_run {
254                println!("Hardening complete. Run: zernel secure scan  — to verify");
255            }
256        }
257
258        SecureCommands::Firewall { dry_run } => {
259            println!(
260                "Zernel ML Firewall Configuration{}",
261                if dry_run { " (dry run)" } else { "" }
262            );
263            println!("{}", "=".repeat(50));
264            println!();
265            println!("Allows:");
266            println!("  - SSH (port 22)");
267            println!("  - NCCL (ports 29500-30000)");
268            println!("  - Zernel services (9091, 9092, 3000)");
269            println!("  - Prometheus (9090)");
270            println!("  - Ollama (11434)");
271            println!("Blocks: everything else inbound");
272            println!();
273
274            let rules = [
275                vec![
276                    "-A",
277                    "INPUT",
278                    "-m",
279                    "state",
280                    "--state",
281                    "ESTABLISHED,RELATED",
282                    "-j",
283                    "ACCEPT",
284                ],
285                vec!["-A", "INPUT", "-i", "lo", "-j", "ACCEPT"],
286                vec!["-A", "INPUT", "-p", "tcp", "--dport", "22", "-j", "ACCEPT"],
287                vec![
288                    "-A",
289                    "INPUT",
290                    "-p",
291                    "tcp",
292                    "--dport",
293                    "29500:30000",
294                    "-j",
295                    "ACCEPT",
296                ],
297                vec![
298                    "-A", "INPUT", "-p", "tcp", "--dport", "9091", "-j", "ACCEPT",
299                ],
300                vec![
301                    "-A", "INPUT", "-p", "tcp", "--dport", "9092", "-j", "ACCEPT",
302                ],
303                vec![
304                    "-A", "INPUT", "-p", "tcp", "--dport", "3000", "-j", "ACCEPT",
305                ],
306                vec![
307                    "-A", "INPUT", "-p", "tcp", "--dport", "9090", "-j", "ACCEPT",
308                ],
309                vec![
310                    "-A", "INPUT", "-p", "tcp", "--dport", "11434", "-j", "ACCEPT",
311                ],
312                vec!["-A", "INPUT", "-p", "icmp", "-j", "ACCEPT"],
313                vec!["-P", "INPUT", "DROP"],
314            ];
315
316            for rule in &rules {
317                if dry_run {
318                    println!("  iptables {}", rule.join(" "));
319                } else {
320                    let _ = Command::new("iptables").args(rule).output();
321                    println!("  Applied: iptables {}", rule.join(" "));
322                }
323            }
324
325            if !dry_run {
326                println!();
327                println!("Firewall configured. Save with: iptables-save > /etc/iptables.rules");
328            }
329        }
330
331        SecureCommands::Audit => {
332            println!("Enabling Audit Logging");
333            println!("{}", "=".repeat(50));
334
335            let status = Command::new("apt-get")
336                .args(["install", "-y", "auditd"])
337                .output();
338
339            match status {
340                Ok(o) if o.status.success() => {
341                    println!("  auditd installed");
342
343                    // Add ML-relevant audit rules
344                    let rules = [
345                        "-w /etc/zernel/ -p wa -k zernel_config",
346                        "-w /usr/local/bin/zernel -p x -k zernel_exec",
347                        "-w /usr/local/bin/zerneld -p x -k zerneld_exec",
348                    ];
349
350                    for rule in &rules {
351                        let _ = Command::new("auditctl")
352                            .args(rule.split_whitespace())
353                            .output();
354                        println!("  Rule: {rule}");
355                    }
356
357                    println!();
358                    println!("Audit logging enabled. View: ausearch -k zernel_config");
359                }
360                _ => {
361                    println!("  Failed to install auditd (requires root + apt)");
362                }
363            }
364        }
365
366        SecureCommands::Updates => {
367            println!("Security Updates Check");
368            println!("{}", "=".repeat(50));
369
370            let output = Command::new("apt-get")
371                .args(["--just-print", "upgrade"])
372                .output();
373
374            match output {
375                Ok(o) if o.status.success() => {
376                    let stdout = String::from_utf8_lossy(&o.stdout);
377                    let upgradable: Vec<&str> =
378                        stdout.lines().filter(|l| l.starts_with("Inst")).collect();
379
380                    if upgradable.is_empty() {
381                        println!("  All packages up to date.");
382                    } else {
383                        println!("  {} packages need updating:", upgradable.len());
384                        for pkg in upgradable.iter().take(20) {
385                            println!("    {pkg}");
386                        }
387                        if upgradable.len() > 20 {
388                            println!("    ... and {} more", upgradable.len() - 20);
389                        }
390                        println!();
391                        println!("  Apply: sudo apt-get upgrade -y");
392                    }
393                }
394                _ => {
395                    println!("  Cannot check updates (requires apt)");
396                }
397            }
398        }
399    }
400    Ok(())
401}