1
0

improving martillo-maldito to server to protect also nodes (port, ip, etc..)

This commit is contained in:
midefos 2024-07-23 21:14:51 +02:00
parent 78c0af4d04
commit d6b1502759
6 changed files with 343 additions and 90 deletions

127
Cargo.lock generated
View File

@ -145,6 +145,21 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.30" version = "0.3.30"
@ -231,6 +246,12 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]] [[package]]
name = "kqueue" name = "kqueue"
version = "1.0.8" version = "1.0.8"
@ -287,7 +308,10 @@ version = "0.1.1"
dependencies = [ dependencies = [
"iptables", "iptables",
"linemux", "linemux",
"openssl",
"regex", "regex",
"serde",
"serde_json",
"structopt", "structopt",
"tokio", "tokio",
] ]
@ -367,6 +391,60 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl"
version = "0.10.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.71",
]
[[package]]
name = "openssl-src"
version = "300.3.1+3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.14" version = "0.2.14"
@ -379,6 +457,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -465,6 +549,12 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
@ -474,6 +564,37 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "serde"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.71",
]
[[package]]
name = "serde_json"
version = "1.0.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.2" version = "1.4.2"
@ -599,6 +720,12 @@ version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"

View File

@ -9,3 +9,6 @@ iptables = "0.5.1"
linemux = "0.3.0" linemux = "0.3.0"
regex = "1.10.4" regex = "1.10.4"
tokio = { version = "1.37.0", features = ["macros", "rt", "rt-multi-thread", "signal"]} tokio = { version = "1.37.0", features = ["macros", "rt", "rt-multi-thread", "signal"]}
openssl = { version = "0.10.64", features = ["vendored"] }
serde = {version = "1.0.204", features = ["derive"]}
serde_json = "1.0.120"

2
build.sh Normal file
View File

@ -0,0 +1,2 @@
cargo b --release --quiet --target=x86_64-unknown-linux-musl
echo "Build done, now you can upload to https://git.midefos.com/midefos/martillo-maldito-releases/releases"

View File

@ -2,8 +2,11 @@ use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(name = "martillo-maldito", about = "A simple iptables ban server")] #[structopt(
pub enum Arguments { name = "martillo-maldito",
about = "A simple iptables wrapper, including a ban server"
)]
pub enum Cli {
#[structopt(about = "Initialize ban server")] #[structopt(about = "Initialize ban server")]
BanServer { BanServer {
#[structopt(name = "Ssh auth log file", short = "f", long = "ssh-file")] #[structopt(name = "Ssh auth log file", short = "f", long = "ssh-file")]
@ -11,7 +14,18 @@ pub enum Arguments {
#[structopt(name = "Iptables save file", short = "s", long = "iptables-save")] #[structopt(name = "Iptables save file", short = "s", long = "iptables-save")]
iptables_save: Option<PathBuf>, iptables_save: Option<PathBuf>,
}, },
#[structopt(about = "List all banned ips")]
ListBannedIps, ListBannedIps,
#[structopt(about = "List all secured ports")]
ListSecuredPorts {
#[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool,
},
#[structopt(about = "Map secured ports to allowed ips")]
MapSecuredPortsAllowedIps {
#[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool,
},
#[structopt(about = "Check if a port is secured")] #[structopt(about = "Check if a port is secured")]
IsPortSecured { IsPortSecured {
#[structopt(name = "Port to check", short = "p", long = "port")] #[structopt(name = "Port to check", short = "p", long = "port")]
@ -19,60 +33,54 @@ pub enum Arguments {
#[structopt(name = "Docker", short = "d", long = "docker")] #[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool, docker: bool,
}, },
#[structopt(about = "Ban port")] #[structopt(about = "Ban port")]
BanPort { SecurePort {
#[structopt(name = "Port to ban", short = "p", long = "port")] #[structopt(name = "Port to ban", short = "p", long = "port")]
port: u16, port: u16,
#[structopt(name = "Docker", short = "d", long = "docker")] #[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool, docker: bool,
#[structopt(name = "Position", short = "P", long = "position")] #[structopt(name = "Position", short = "P", long = "position")]
position: Option<i32>, position: Option<i32>,
}, },
#[structopt(about = "Unban port")] #[structopt(about = "Unban port")]
UnbanPort { UnsecurePort {
#[structopt(name = "Port to unban", short = "p", long = "port")] #[structopt(name = "Port to unban", short = "p", long = "port")]
port: u16, port: u16,
#[structopt(name = "Docker", short = "d", long = "docker")] #[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool, docker: bool,
}, },
#[structopt(about = "Allow ip and port")] #[structopt(about = "Allow ip and port")]
AllowIpPort { AllowIpForPort {
#[structopt(name = "Ip to allow", short = "i", long = "ip")] #[structopt(name = "Ip to allow", short = "i", long = "ip")]
ip: String, ip: String,
#[structopt(name = "Port to allow", short = "p", long = "port")] #[structopt(name = "Port to allow", short = "p", long = "port")]
port: u16, port: u16,
#[structopt(name = "Docker", short = "d", long = "docker")] #[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool, docker: bool,
#[structopt(name = "Position", short = "P", long = "position")] #[structopt(name = "Position", short = "P", long = "position")]
position: Option<i32>, position: Option<i32>,
}, },
#[structopt(about = "Allow port for only an ip")] #[structopt(about = "Allow port for only an ip")]
OnlyIpPort { OnlyIpForPort {
#[structopt(name = "Ip to allow", short = "i", long = "ip")] #[structopt(name = "Ip to allow", short = "i", long = "ip")]
ip: String, ip: String,
#[structopt(name = "Port to allow", short = "p", long = "port")] #[structopt(name = "Port to allow", short = "p", long = "port")]
port: u16, port: u16,
#[structopt(name = "Docker", short = "d", long = "docker")] #[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool, docker: bool,
}, },
#[structopt(about = "Removes an allow port for an ip")]
#[structopt(about = "Remove ip and port")] RemoveAllowIpPort {
RemoveIpPort {
#[structopt(name = "Ip to remove", short = "i", long = "ip")] #[structopt(name = "Ip to remove", short = "i", long = "ip")]
ip: String, ip: String,
#[structopt(name = "Port to remove", short = "p", long = "port")] #[structopt(name = "Port to remove", short = "p", long = "port")]
port: u16, port: u16,
#[structopt(name = "Docker", short = "d", long = "docker")] #[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool, docker: bool,
}, },
#[structopt(about = "Saves the IPTables configuration")]
SaveIPTables {
#[structopt(name = "Iptables save file", short = "s", long = "iptables-save")]
iptables_save: Option<PathBuf>,
},
} }

View File

@ -1,4 +1,7 @@
use std::collections::HashMap;
use iptables::IPTables; use iptables::IPTables;
use regex::Regex;
pub fn is_port_secured(port: u16, docker: bool) -> bool { pub fn is_port_secured(port: u16, docker: bool) -> bool {
let iptables = iptables::new(false).unwrap(); let iptables = iptables::new(false).unwrap();
@ -15,6 +18,22 @@ pub fn is_port_secured(port: u16, docker: bool) -> bool {
false false
} }
pub fn list_secured_ports(docker: bool) -> Vec<u16> {
let iptables = iptables::new(false).unwrap();
let rules = iptables.list("filter", &get_chain(docker));
if rules.is_err() {
return vec![];
}
let rgx = get_regex_for_port();
rules
.unwrap()
.iter()
.filter(|r| r.contains("-p tcp -m tcp --dport") && r.contains("-j DROP"))
.map(|r| extract_port(&rgx, r).unwrap())
.collect()
}
pub fn list_banned_ips() -> Vec<String> { pub fn list_banned_ips() -> Vec<String> {
let iptables = iptables::new(false).unwrap(); let iptables = iptables::new(false).unwrap();
let rules = iptables.list("filter", &get_chain(false)); let rules = iptables.list("filter", &get_chain(false));
@ -22,66 +41,98 @@ pub fn list_banned_ips() -> Vec<String> {
return vec![]; return vec![];
} }
// TODO: Remove after testing let rgx = get_regex_for_ip();
println!("{:?}", rules);
rules rules
.unwrap() .unwrap()
.iter() .iter()
.filter(|r| r.contains("--source") && r.contains("-j DROP")) .filter(|r| r.contains("-A INPUT") && r.contains("-j DROP") && r.contains("-s"))
.map(|r| r.to_string()) .map(|r| extract_ip(&rgx, r).unwrap())
.collect() .collect()
} }
pub fn ban_port(port: u16, docker: bool, position: Option<i32>) { pub fn map_secured_ports_allowed_ips(docker: bool) -> HashMap<u16, Vec<String>> {
let mut result: HashMap<u16, Vec<String>> = HashMap::new();
let secured_ports = list_secured_ports(docker);
if secured_ports.is_empty() {
return result;
}
let iptables = iptables::new(false).unwrap();
let rules = iptables.list("filter", &get_chain(docker));
if rules.is_err() {
return result;
}
let rules = rules.unwrap();
let rgx = get_regex_for_ip();
for port in secured_ports {
let ips = rules
.iter()
.filter(|r| {
r.contains("-A INPUT -s")
&& r.contains(&format!("-p tcp -m tcp --dport {} -j ACCEPT", port))
})
.map(|r| extract_ip(&rgx, r).unwrap())
.collect();
result.insert(port, ips);
}
result
}
pub fn secure_port_rule(port: u16) -> String {
format!("-p tcp --dport {} -j DROP", port)
}
pub fn secure_port(port: u16, docker: bool, position: Option<i32>) {
let iptables = iptables::new(false).unwrap(); let iptables = iptables::new(false).unwrap();
let table = "filter"; let table = "filter";
let chain = get_chain(docker); let chain = get_chain(docker);
let rule = format!("-p tcp --dport {} -j DROP", port); let rule = secure_port_rule(port);
if let Some(position) = position { if let Some(position) = position {
insert_unique(&iptables, table, &chain, &rule, position) insert_unique(&iptables, table, &chain, &rule, position)
} else { } else {
append_unique(&iptables, table, &chain, &rule) append_unique(&iptables, table, &chain, &rule)
} }
println!("banned port {}", port); println!("Port {} secured", port);
} }
pub fn unban_port(port: u16, docker: bool) { pub fn unsecure_port(port: u16, docker: bool) {
let iptables = iptables::new(false).unwrap(); let iptables = iptables::new(false).unwrap();
let _ = iptables.delete( let _ = iptables.delete("filter", &get_chain(docker), &secure_port_rule(port));
"filter",
&get_chain(docker),
&format!("-p tcp --dport {} -j DROP", port),
);
println!("unbanned port {}", port); println!("Port {} unsecured", port);
} }
pub fn allow_ip_port(ip: &str, port: u16, docker: bool, position: Option<i32>) { pub fn allow_ip_for_port_rule(port: u16, ip: &str) -> String {
format!("-p tcp --dport {} -s {} -j ACCEPT", port, ip)
}
pub fn allow_ip_for_port(ip: &str, port: u16, docker: bool, position: Option<i32>) {
let iptables = iptables::new(false).unwrap(); let iptables = iptables::new(false).unwrap();
let table = "filter"; let table = "filter";
let chain = get_chain(docker); let chain = get_chain(docker);
let rule = format!("-p tcp --dport {} -s {} -j ACCEPT", port, ip); let rule = allow_ip_for_port_rule(port, ip);
if let Some(position) = position { if let Some(position) = position {
insert_unique(&iptables, table, &chain, &rule, position) insert_unique(&iptables, table, &chain, &rule, position)
} else { } else {
append_unique(&iptables, table, &chain, &rule) append_unique(&iptables, table, &chain, &rule)
} }
println!("allowed {} to access {}", ip, port); println!("Allowed {} to access {}", ip, port);
} }
pub fn remove_ip_port(ip: &str, port: u16, docker: bool) { pub fn remove_allow_ip_for_port(ip: &str, port: u16, docker: bool) {
let iptables = iptables::new(false).unwrap(); let iptables = iptables::new(false).unwrap();
let _ = iptables.delete( let _ = iptables.delete(
"filter", "filter",
&get_chain(docker), &get_chain(docker),
&format!("-p tcp --dport {} -s {} -j ACCEPT", port, ip), &allow_ip_for_port_rule(port, ip),
); );
println!("removed access {} to {}", ip, port); println!("Removed access of {} to {}", ip, port);
} }
fn get_chain(docker: bool) -> String { fn get_chain(docker: bool) -> String {
@ -99,3 +150,42 @@ fn append_unique(iptables: &IPTables, table: &str, chain: &str, rule: &str) {
fn insert_unique(iptables: &IPTables, table: &str, chain: &str, rule: &str, position: i32) { fn insert_unique(iptables: &IPTables, table: &str, chain: &str, rule: &str, position: i32) {
let _ = iptables.insert_unique(table, chain, rule, position); let _ = iptables.insert_unique(table, chain, rule, position);
} }
fn extract_ip(regex: &Regex, input: &str) -> Option<String> {
regex
.captures(input)
.and_then(|caps| caps.get(0).map(|m| m.as_str().to_string()))
}
fn extract_port(regex: &Regex, input: &str) -> Option<u16> {
regex
.captures(input)
.and_then(|caps| caps.get(1).map(|m| m.as_str().parse::<u16>().unwrap()))
}
fn get_regex_for_ip() -> Regex {
Regex::new(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b").unwrap()
}
fn get_regex_for_port() -> Regex {
Regex::new(r"--dport\s+(\d+)").unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn correct_extract_ip() {
let regex = get_regex_for_ip();
let input = "-A INPUT -s 81.69.255.132/32 -j DROP";
assert_eq!(extract_ip(&regex, input), Some("81.69.255.132".to_string()));
}
#[test]
fn no_match_extrac_ip() {
let regex = get_regex_for_ip();
let input = "-A INPUT -j DROP";
assert_eq!(extract_ip(&regex, input), None);
}
}

View File

@ -3,53 +3,72 @@ pub mod iptables_save;
pub mod iptables_wrapper; pub mod iptables_wrapper;
pub mod login_attempt; pub mod login_attempt;
use cli::Arguments; use cli::Cli;
use iptables_wrapper::{ use iptables_wrapper::{
allow_ip_port, ban_port, is_port_secured, list_banned_ips, remove_ip_port, unban_port, allow_ip_for_port, is_port_secured, list_banned_ips, list_secured_ports,
map_secured_ports_allowed_ips, remove_allow_ip_for_port, secure_port, unsecure_port,
}; };
use linemux::MuxedLines; use linemux::MuxedLines;
use login_attempt::LoginAttempt; use login_attempt::LoginAttempt;
use std::path::PathBuf; use std::path::PathBuf;
use std::thread; use std::thread::spawn;
use std::{collections::HashMap, thread::sleep, time::Duration}; use std::{collections::HashMap, thread::sleep, time::Duration};
use structopt::StructOpt; use structopt::StructOpt;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let opts = Arguments::from_args(); match Cli::from_args() {
match opts { Cli::BanServer {
Arguments::BanServer {
ssh_auth_log, ssh_auth_log,
iptables_save, iptables_save,
} => { } => {
let _ = start_ban_server(ssh_auth_log, iptables_save).await; let _ = start_ban_server(ssh_auth_log, iptables_save).await;
} }
Arguments::ListBannedIps => { Cli::ListBannedIps => {
for ip in list_banned_ips() { println!("{}", serde_json::to_string(&list_banned_ips()).unwrap());
println!("{}", ip);
}
} }
Arguments::IsPortSecured { port, docker } => { Cli::ListSecuredPorts { docker } => {
let is_secured = is_port_secured(port, docker); println!(
println!("{}", is_secured); "{}",
serde_json::to_string(&list_secured_ports(docker)).unwrap()
);
} }
Arguments::BanPort { Cli::MapSecuredPortsAllowedIps { docker } => {
println!(
"{}",
serde_json::to_string(&map_secured_ports_allowed_ips(docker)).unwrap()
);
}
Cli::IsPortSecured { port, docker } => {
println!("{}", is_port_secured(port, docker));
}
Cli::SecurePort {
port, port,
docker, docker,
position, position,
} => ban_port(port, docker, position), } => secure_port(port, docker, position),
Arguments::UnbanPort { port, docker } => unban_port(port, docker), Cli::UnsecurePort { port, docker } => unsecure_port(port, docker),
Arguments::AllowIpPort { Cli::AllowIpForPort {
ip, ip,
port, port,
docker, docker,
position, position,
} => allow_ip_port(&ip, port, docker, position), } => allow_ip_for_port(&ip, port, docker, position),
Arguments::OnlyIpPort { ip, port, docker } => { Cli::OnlyIpForPort { ip, port, docker } => {
allow_ip_port(&ip, port, docker, Some(1)); allow_ip_for_port(&ip, port, docker, Some(1));
ban_port(port, docker, Some(2)); secure_port(port, docker, Some(2));
}
Cli::RemoveAllowIpPort { ip, port, docker } => remove_allow_ip_for_port(&ip, port, docker),
Cli::SaveIPTables { iptables_save } => {
let path = if let Some(iptables_save) = iptables_save {
iptables_save
} else {
PathBuf::from("/etc/iptables/rules.v4")
};
iptables_save::save_iptables(&path);
println!("Saved IPTables to {}", path.display());
} }
Arguments::RemoveIpPort { ip, port, docker } => remove_ip_port(&ip, port, docker),
} }
} }
@ -65,47 +84,51 @@ async fn start_ban_server(
if let Some(iptables_save) = iptables_save { if let Some(iptables_save) = iptables_save {
let seconds_iptables = Duration::from_secs(60); let seconds_iptables = Duration::from_secs(60);
println!( println!(
"starting iptables-save, save every {} seconds", "Saving IPTables every {} seconds",
seconds_iptables.as_secs() seconds_iptables.as_secs()
); );
thread::spawn(move || loop {
spawn(move || loop {
sleep(seconds_iptables); sleep(seconds_iptables);
iptables_save::save_iptables(&iptables_save); iptables_save::save_iptables(&iptables_save);
}); });
} }
println!("listening to changes over {}", ssh_auth_log.display()); println!("Listeging to changer over file: {}", ssh_auth_log.display());
while let Ok(Some(line)) = lines.next_line().await { loop {
if let Some(login_attempt) = LoginAttempt::capture(line.line()) { while let Ok(Some(line)) = lines.next_line().await {
println!( if let Some(login_attempt) = LoginAttempt::capture(line.line()) {
"failed login attempt from {}@{}:{}", println!(
login_attempt.user, login_attempt.ip, login_attempt.port "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 { match login_attempts.get_mut(&login_attempt.ip) {
match iptables.append_unique( Some(count) => {
"filter", *count += 1;
"INPUT",
&format!("--source {} -j DROP", login_attempt.ip), if *count == 3 {
) { if iptables
Ok(_) => { .append_unique(
println!("{} banned", login_attempt.ip); "filter",
} "INPUT",
Err(_) => { &format!("--source {} -j DROP", login_attempt.ip),
println!("{} already banned", login_attempt.ip); )
.is_ok()
{
println!("IP {} banned", login_attempt.ip);
} else {
println!("IP {} already banned", login_attempt.ip);
} }
login_attempts.remove(&login_attempt.ip);
} }
login_attempts.remove(&login_attempt.ip);
} }
} None => {
None => { login_attempts.insert(login_attempt.ip.clone(), 1);
login_attempts.insert(login_attempt.ip.clone(), 1); }
} }
} }
} }
} }
Ok(())
} }