diff --git a/docker-compose.yml b/docker-compose.yml index 5ee9f66..51fbeb9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,7 @@ services: restart: always build: context: . + pull_policy: build cap_add: - NET_ADMIN @@ -13,3 +14,4 @@ services: volumes: - /var/log/auth.log:/host_ssh/auth.log + - /etc/iptables/rules.v4:/host_iptables/rules.v4 diff --git a/src/iptables_save.rs b/src/iptables_save.rs new file mode 100644 index 0000000..231d84b --- /dev/null +++ b/src/iptables_save.rs @@ -0,0 +1,8 @@ +use std::process::Command; + +pub fn save_iptables() { + let _ = Command::new("iptables-save") + .arg(">") + .arg("/host_iptables/rules.v4") + .output(); +} diff --git a/src/login_attempt.rs b/src/login_attempt.rs index 44eae93..6c7672b 100644 --- a/src/login_attempt.rs +++ b/src/login_attempt.rs @@ -1,7 +1,9 @@ +use regex::Regex; + pub struct LoginAttempt { pub ip: String, - user: String, - port: String, + pub user: String, + pub port: String, } impl LoginAttempt { @@ -12,4 +14,14 @@ impl LoginAttempt { port: port.to_string(), } } + + pub fn capture(line: &str) -> Option { + let regex = Regex::new(r#"Failed password for (?:invalid user )?(?P\S+) from (?P\S+) port (?P\d+)"#).unwrap(); + let captured = regex.captures(line)?; + Some(Self::new( + captured.name("ip").unwrap().as_str(), + captured.name("user").unwrap().as_str(), + captured.name("port").unwrap().as_str(), + )) + } } diff --git a/src/main.rs b/src/main.rs index 7530727..802d855 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ +pub mod iptables_save; pub mod login_attempt; use linemux::MuxedLines; use login_attempt::LoginAttempt; -use regex::Regex; use std::collections::HashMap; #[tokio::main] @@ -12,23 +12,31 @@ async fn main() -> std::io::Result<()> { lines.add_file("/host_ssh/auth.log").await?; let mut login_attempts: HashMap = HashMap::new(); - println!("Listening to changes over /host_ssh/auth.log"); + println!("listening to changes over /host_ssh/auth.log"); while let Ok(Some(line)) = lines.next_line().await { - if let Some(login_attempt) = capture_failed_login_attempt(line.line()) { + if let Some(login_attempt) = LoginAttempt::capture(line.line()) { + println!( + "failed login attempt from {}@{}:{}", + login_attempt.user, login_attempt.ip, login_attempt.port + ); match login_attempts.get_mut(&login_attempt.ip) { Some(count) => { *count += 1; if *count == 3 { - iptables - .append_unique( - "filter", - "INPUT", - &format!("--source {} -j DROP", login_attempt.ip), - ) - .unwrap(); + match iptables.append_unique( + "filter", + "INPUT", + &format!("--source {} -j DROP", login_attempt.ip), + ) { + Ok(_) => { + println!("{} banned", login_attempt.ip); + } + Err(_) => { + println!("{} already banned", login_attempt.ip); + } + } login_attempts.remove(&login_attempt.ip); - println!("{} banned", login_attempt.ip); } } None => { @@ -39,13 +47,3 @@ async fn main() -> std::io::Result<()> { } Ok(()) } - -fn capture_failed_login_attempt(line: &str) -> Option { - let regex = Regex::new(r#"Failed password for (?:invalid user )?(?P\S+) from (?P\S+) port (?P\d+)"#).unwrap(); - let captured = regex.captures(line)?; - Some(LoginAttempt::new( - captured.name("ip").unwrap().as_str(), - captured.name("user").unwrap().as_str(), - captured.name("port").unwrap().as_str(), - )) -}