firewall.rs
1 //! Firewall configuration. 2 3 use acdc_core::{config::NodeInstance, Error, Result}; 4 use acdc_tui::output; 5 use std::process::Command; 6 use tracing::debug; 7 8 /// Configure firewall rules for a node instance. 9 pub fn configure(instance: &NodeInstance) -> Result<()> { 10 if !has_ufw() { 11 output::info( 12 "Firewall", 13 "UFW not available, skipping firewall configuration", 14 ); 15 return Ok(()); 16 } 17 18 output::status("Configuring firewall rules..."); 19 20 let ports = instance.ports.all(); 21 for port in ports { 22 allow_port(port)?; 23 } 24 25 // Always allow SSH 26 allow_port(22)?; 27 28 // Radicle P2P port 29 allow_port(8776)?; 30 31 output::success("Firewall rules configured"); 32 Ok(()) 33 } 34 35 /// Check if ufw is available. 36 pub fn has_ufw() -> bool { 37 Command::new("which") 38 .arg("ufw") 39 .output() 40 .map(|o| o.status.success()) 41 .unwrap_or(false) 42 } 43 44 /// Check if ufw is enabled. 45 pub fn is_ufw_enabled() -> bool { 46 Command::new("sudo") 47 .args(["ufw", "status"]) 48 .output() 49 .map(|o| { 50 let stdout = String::from_utf8_lossy(&o.stdout); 51 stdout.contains("Status: active") 52 }) 53 .unwrap_or(false) 54 } 55 56 /// Allow a port through the firewall. 57 pub fn allow_port(port: u16) -> Result<()> { 58 debug!("Allowing port {} through firewall", port); 59 60 let output = Command::new("sudo") 61 .args(["ufw", "allow", &port.to_string()]) 62 .output() 63 .map_err(Error::Io)?; 64 65 if !output.status.success() { 66 let stderr = String::from_utf8_lossy(&output.stderr); 67 return Err(Error::Config(format!( 68 "Failed to allow port {}: {}", 69 port, stderr 70 ))); 71 } 72 73 Ok(()) 74 } 75 76 /// Deny a port through the firewall. 77 pub fn deny_port(port: u16) -> Result<()> { 78 debug!("Denying port {} through firewall", port); 79 80 let output = Command::new("sudo") 81 .args(["ufw", "deny", &port.to_string()]) 82 .output() 83 .map_err(Error::Io)?; 84 85 if !output.status.success() { 86 let stderr = String::from_utf8_lossy(&output.stderr); 87 return Err(Error::Config(format!( 88 "Failed to deny port {}: {}", 89 port, stderr 90 ))); 91 } 92 93 Ok(()) 94 } 95 96 /// Delete a firewall rule for a port. 97 pub fn delete_rule(port: u16) -> Result<()> { 98 debug!("Deleting firewall rule for port {}", port); 99 100 let output = Command::new("sudo") 101 .args(["ufw", "delete", "allow", &port.to_string()]) 102 .output() 103 .map_err(Error::Io)?; 104 105 if !output.status.success() { 106 // Non-fatal, rule might not exist 107 debug!("Could not delete rule for port {}", port); 108 } 109 110 Ok(()) 111 } 112 113 /// Remove firewall rules for a node instance. 114 pub fn remove(instance: &NodeInstance) -> Result<()> { 115 if !has_ufw() { 116 return Ok(()); 117 } 118 119 output::status("Removing firewall rules..."); 120 121 let ports = instance.ports.all(); 122 for port in ports { 123 delete_rule(port)?; 124 } 125 126 output::success("Firewall rules removed"); 127 Ok(()) 128 } 129 130 /// Print current firewall status. 131 pub fn print_status() -> Result<()> { 132 if !has_ufw() { 133 output::info("Firewall", "UFW not installed"); 134 return Ok(()); 135 } 136 137 let output = Command::new("sudo") 138 .args(["ufw", "status", "verbose"]) 139 .output() 140 .map_err(Error::Io)?; 141 142 let stdout = String::from_utf8_lossy(&output.stdout); 143 println!("{}", stdout); 144 145 Ok(()) 146 } 147 148 /// Enable the firewall. 149 pub fn enable() -> Result<()> { 150 output::status("Enabling firewall..."); 151 152 let output = Command::new("sudo") 153 .args(["ufw", "--force", "enable"]) 154 .output() 155 .map_err(Error::Io)?; 156 157 if !output.status.success() { 158 let stderr = String::from_utf8_lossy(&output.stderr); 159 return Err(Error::Config(format!( 160 "Failed to enable firewall: {}", 161 stderr 162 ))); 163 } 164 165 output::success("Firewall enabled"); 166 Ok(()) 167 } 168 169 /// Disable the firewall. 170 pub fn disable() -> Result<()> { 171 output::status("Disabling firewall..."); 172 173 let output = Command::new("sudo") 174 .args(["ufw", "disable"]) 175 .output() 176 .map_err(Error::Io)?; 177 178 if !output.status.success() { 179 let stderr = String::from_utf8_lossy(&output.stderr); 180 return Err(Error::Config(format!( 181 "Failed to disable firewall: {}", 182 stderr 183 ))); 184 } 185 186 output::success("Firewall disabled"); 187 Ok(()) 188 }