1
0

WIP moving code into lib

This commit is contained in:
midefos 2024-12-31 03:57:40 +01:00
parent 41abf04a63
commit 20f1038ff8
9 changed files with 448 additions and 420 deletions

196
Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -35,13 +35,62 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [ dependencies = [
"hermit-abi 0.1.19", "hermit-abi",
"libc", "libc",
"winapi", "winapi",
] ]
@ -75,9 +124,9 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.1" version = "1.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@ -110,16 +159,32 @@ dependencies = [
] ]
[[package]] [[package]]
name = "env_logger" name = "colorchoice"
version = "0.7.1" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [ dependencies = [
"atty",
"humantime",
"log", "log",
"regex", "regex",
"termcolor", ]
[[package]]
name = "env_logger"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
] ]
[[package]] [[package]]
@ -161,20 +226,11 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "1.3.0" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
dependencies = [
"quick-error",
]
[[package]] [[package]]
name = "iptables" name = "iptables"
@ -187,6 +243,12 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.14" version = "1.0.14"
@ -201,9 +263,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.166" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "log" name = "log"
@ -212,23 +274,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]] [[package]]
name = "logfmt_logger" name = "martillo_maldito"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60da8a0835af8f55c23833348fae381a75380e4748465acc7faf094e426e2f0c"
dependencies = [
"env_logger",
"log",
"termcolor",
]
[[package]]
name = "martillo-maldito"
version = "0.1.2" version = "0.1.2"
dependencies = [ dependencies = [
"env_logger",
"iptables", "iptables",
"log", "log",
"logfmt_logger",
"openssl", "openssl",
"regex", "regex",
"serde", "serde",
@ -245,20 +296,19 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [ dependencies = [
"hermit-abi 0.3.9",
"libc", "libc",
"wasi", "wasi",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -278,9 +328,9 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.5" version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -314,7 +364,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.93",
] ]
[[package]] [[package]]
@ -384,17 +434,11 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.37" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -442,29 +486,29 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.215" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.215" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.93",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.133" version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -530,24 +574,15 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.89" version = "2.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.11.0" version = "0.11.0"
@ -559,9 +594,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.41.1" version = "1.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"libc", "libc",
@ -580,7 +615,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.89", "syn 2.0.93",
] ]
[[package]] [[package]]
@ -601,6 +636,12 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@ -641,15 +682,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View File

@ -1,5 +1,5 @@
[package] [package]
name = "martillo-maldito" name = "martillo_maldito"
version = "0.1.2" version = "0.1.2"
edition = "2021" edition = "2021"
@ -8,12 +8,16 @@ structopt = "0.3.26"
iptables = "0.5.2" iptables = "0.5.2"
regex = "1.11.1" regex = "1.11.1"
tokio = { version = "1.41.1", features = ["macros", "rt", "rt-multi-thread", "signal"]} tokio = { version = "1.42.0", features = ["macros", "rt", "rt-multi-thread", "signal"]}
serde = {version = "1.0.215", features = ["derive"]} serde = {version = "1.0.217", features = ["derive"]}
serde_json = "1.0.133" serde_json = "1.0.134"
log = "0.4.22" log = {version = "0.4.22", features = ["kv"]}
logfmt_logger = "0.1.1" env_logger = {version = "0.11.6", features = ["unstable-kv"]}
openssl = { version = "0.10.68", features = ["vendored"] } openssl = { version = "0.10.68", features = ["vendored"] }
[lib]
name = "martillo_maldito"
path = "src/lib.rs"

View File

@ -3,11 +3,12 @@ FROM rust:latest as builder
COPY . . COPY . .
RUN cargo build --release RUN cargo build --release
# ===============================================================================
FROM ubuntu:latest FROM ubuntu:latest
RUN apt update && apt upgrade -y && apt install iptables iptables-persistent systemd -y RUN apt update \
&& apt upgrade -y \
&& apt install iptables iptables-persistent systemd -y
COPY --from=builder /target/release/martillo-maldito ./ COPY --from=builder /target/release/martillo-maldito ./
CMD [ "/martillo-maldito", "ban-server" ] CMD [ "/martillo-maldito", "ban-service" ]

View File

@ -2,14 +2,14 @@ use structopt::StructOpt;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt( #[structopt(
name = "martillo-maldito", name = "martillo_maldito",
about = "A simple iptables wrapper, including a ban server" about = "A IPTables wrapper, including a ban service"
)] )]
pub enum Cli { pub enum Cli {
#[structopt(about = "Initialize ban server")] #[structopt(about = "Initialize ban service, monitoring SSH logs for login attempts")]
BanServer, BanService,
#[structopt(about = "List all banned ips")] #[structopt(about = "List all banned ips")]
ListBannedIps { GetBannedIps {
#[structopt(name = "Docker", short = "d", long = "docker")] #[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool, docker: bool,
}, },
@ -30,51 +30,42 @@ pub enum Cli {
#[structopt(name = "Docker", short = "d", long = "docker")] #[structopt(name = "Docker", short = "d", long = "docker")]
docker: bool, docker: bool,
}, },
#[structopt(about = "Ban port")] #[structopt(about = "Secure a port")]
SecurePort { 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 = "Position", short = "P", long = "position")]
position: Option<usize>,
#[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")]
position: Option<i32>,
}, },
#[structopt(about = "Unban port")] #[structopt(about = "Unsecure a port")]
UnsecurePort { 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 an IP for port")]
AllowIpForPort { 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")]
docker: bool,
#[structopt(name = "Position", short = "P", long = "position")] #[structopt(name = "Position", short = "P", long = "position")]
position: Option<i32>, position: Option<usize>,
},
#[structopt(about = "Allow port for only an ip")]
OnlyIpForPort {
#[structopt(name = "Ip to allow", short = "i", long = "ip")]
ip: String,
#[structopt(name = "Port to allow", short = "p", long = "port")]
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 = "Removes an allowance IP for a port")]
RemoveAllowIpPort { RemoveAllowIpPort {
#[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")] #[structopt(about = "Saves the configuration")]
SaveIPTables, SaveRules,
} }

View File

@ -1,7 +0,0 @@
use std::process::Command;
pub fn save_iptables() -> std::io::Result<std::process::Output> {
Command::new("iptables-save")
.args(["-f", "/etc/iptables/rules.v4"])
.output()
}

View File

@ -1,229 +0,0 @@
use iptables::IPTables;
use regex::Regex;
use std::collections::HashMap;
pub fn is_port_secured(port: u16, docker: bool) -> bool {
let iptables = iptables::new(false).unwrap();
let rules = iptables.list("filter", &get_chain(docker));
if rules.is_err() {
return false;
}
for rule in rules.unwrap() {
if rule.contains(&format!("-p tcp -m tcp --dport {} -j DROP", port)) {
return true;
}
}
false
}
pub fn list_secured_ports(docker: bool) -> Vec<u16> {
let iptables = iptables::new(false).unwrap();
let chain = get_chain(docker);
let rules = iptables.list("filter", &chain);
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(docker: bool) -> Vec<String> {
let iptables = iptables::new(false).unwrap();
let chain = get_chain(docker);
let rules = iptables.list("filter", &chain);
if rules.is_err() {
return vec![];
}
let rgx = get_regex_for_ip();
rules
.unwrap()
.iter()
.filter(|r| {
r.contains(&format!("-A {}", chain)) && r.contains("-j DROP") && r.contains("-s")
})
.map(|r| extract_ip(&rgx, r).unwrap())
.collect()
}
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 chain = get_chain(docker);
let rules = iptables.list("filter", &chain);
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(&format!("-A {} -s", chain))
&& r.contains(&format!("-p tcp -m tcp --dport {} -j ACCEPT", port))
})
.map(|r| extract_ip(&rgx, r).unwrap())
.collect();
result.insert(port, ips);
}
result
}
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>,
) -> Result<(), Box<dyn std::error::Error>> {
let iptables = iptables::new(false).unwrap();
let table = "filter";
let chain = get_chain(docker);
let rule = secure_port_rule(port);
let position = if docker && position.is_none() {
let all_docker_rules = iptables.list("filter", &chain).unwrap();
Some(all_docker_rules.len() as i32 - 1)
} else {
position
};
if let Some(position) = position {
insert_unique(&iptables, table, &chain, &rule, position)
} else {
append_unique(&iptables, table, &chain, &rule)
}
}
pub fn unsecure_port(port: u16, docker: bool) -> Result<(), Box<dyn std::error::Error>> {
let iptables = iptables::new(false).unwrap();
let rule = secure_port_rule(port);
iptables.delete("filter", &get_chain(docker), &rule)
}
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>,
) -> Result<(), Box<dyn std::error::Error>> {
let iptables = iptables::new(false).unwrap();
let table = "filter";
let chain = get_chain(docker);
let rule = allow_ip_for_port_rule(port, ip);
if let Some(position) = position {
insert_unique(&iptables, table, &chain, &rule, position)
} else {
append_unique(&iptables, table, &chain, &rule)
}
}
pub fn remove_allow_ip_for_port(
ip: &str,
port: u16,
docker: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let iptables = iptables::new(false).unwrap();
let rule = allow_ip_for_port_rule(port, ip);
iptables.delete("filter", &get_chain(docker), &rule)
}
fn get_chain(docker: bool) -> String {
if docker {
"DOCKER-USER".to_string()
} else {
"INPUT".to_string()
}
}
fn append_unique(
iptables: &IPTables,
table: &str,
chain: &str,
rule: &str,
) -> Result<(), Box<dyn std::error::Error>> {
iptables.append_unique(table, chain, rule)
}
fn insert_unique(
iptables: &IPTables,
table: &str,
chain: &str,
rule: &str,
position: i32,
) -> Result<(), Box<dyn std::error::Error>> {
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_extract_ip() {
let regex = get_regex_for_ip();
let input = "-A INPUT -j DROP";
assert_eq!(extract_ip(&regex, input), None);
}
#[test]
fn docker_extract_ip() {
let regex = get_regex_for_ip();
let input = "-A DOCKER -d 172.18.0.2/32 ! -i br-127d33df48a4 -o br-127d33df48a4 -p tcp -m tcp --dport 8078 -j ACCEPT";
assert_eq!(extract_ip(&regex, input), Some("172.18.0.2".to_string()));
}
}

3
src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod cli;
pub mod login_attempt;
pub mod martillo_maldito;

View File

@ -1,15 +1,6 @@
pub mod cli; use env_logger::Builder;
pub mod iptables_save;
pub mod iptables_wrapper;
pub mod login_attempt;
use cli::Cli;
use iptables_wrapper::{
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 log::{error, info}; use log::{error, info};
use login_attempt::LoginAttempt; use martillo_maldito::{cli::Cli, login_attempt::LoginAttempt, martillo_maldito::MartilloMaldito};
use std::{ use std::{
collections::HashMap, collections::HashMap,
io::BufRead, io::BufRead,
@ -21,71 +12,80 @@ use structopt::StructOpt;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
match Cli::from_args() {
Cli::BanService => {
start_logger(); start_logger();
match Cli::from_args() { if let Err(err) = start_ban_service().await {
Cli::BanServer => { error!(err = err.to_string().as_str();
if let Err(error) = start_ban_server().await { "Ban service"
error!("ban server: {error}");
}
}
Cli::ListBannedIps { docker } => {
println!(
"{}",
serde_json::to_string(&list_banned_ips(docker)).unwrap()
); );
} }
}
Cli::GetBannedIps { docker } => {
let banned_ips = MartilloMaldito::ipv4(docker).get_banned_ips();
println!("{}", serde_json::to_string(&banned_ips).unwrap());
}
Cli::ListSecuredPorts { docker } => { Cli::ListSecuredPorts { docker } => {
println!( let secured_ports = MartilloMaldito::ipv4(docker).get_secured_ports();
"{}", println!("{}", serde_json::to_string(&secured_ports).unwrap());
serde_json::to_string(&list_secured_ports(docker)).unwrap()
);
} }
Cli::MapSecuredPortsAllowedIps { docker } => { Cli::MapSecuredPortsAllowedIps { docker } => {
let secured_ports_with_allowed_ips =
MartilloMaldito::ipv4(docker).get_secured_ports_with_allowed_ips();
println!( println!(
"{}", "{}",
serde_json::to_string(&map_secured_ports_allowed_ips(docker)).unwrap() serde_json::to_string(&secured_ports_with_allowed_ips).unwrap()
); );
} }
Cli::IsPortSecured { port, docker } => { Cli::IsPortSecured { port, docker } => {
println!("{}", is_port_secured(port, docker)); let is_port_secured = MartilloMaldito::ipv4(docker).is_port_secured(port);
println!("{}", is_port_secured);
} }
Cli::SecurePort { Cli::SecurePort {
port, port,
docker,
position, position,
} => println!("{}", secure_port(port, docker, position).is_ok()), docker,
Cli::UnsecurePort { port, docker } => println!("{}", unsecure_port(port, docker).is_ok()), } => {
let port_secured = MartilloMaldito::ipv4(docker).secure_port(port, position);
println!("{}", port_secured.is_ok())
}
Cli::UnsecurePort { port, docker } => {
let port_unsecured = MartilloMaldito::ipv4(docker).unsecure_port(port);
println!("{}", port_unsecured.is_ok())
}
Cli::AllowIpForPort { Cli::AllowIpForPort {
ip, ip,
port, port,
docker,
position, position,
} => println!("{}", allow_ip_for_port(&ip, port, docker, position).is_ok()), docker,
Cli::OnlyIpForPort { ip, port, docker } => { } => {
let allowed = allow_ip_for_port(&ip, port, docker, Some(1)); let allowed_ip = MartilloMaldito::ipv4(docker).allow_ip_for_port(&ip, port, position);
let secured = secure_port(port, docker, Some(2)); println!("{}", allowed_ip.is_ok())
println!("{}", allowed.is_ok() && secured.is_ok());
} }
Cli::RemoveAllowIpPort { ip, port, docker } => { Cli::RemoveAllowIpPort { ip, port, docker } => {
println!("{}", remove_allow_ip_for_port(&ip, port, docker).is_ok()) let removed_allow_ip =
MartilloMaldito::ipv4(docker).remove_allow_ip_for_port(&ip, port);
println!("{}", removed_allow_ip.is_ok())
} }
Cli::SaveIPTables => { Cli::SaveRules => {
println!("{}", iptables_save::save_iptables().is_ok()) println!("{}", MartilloMaldito::save_rules().is_ok())
} }
} }
} }
async fn start_ban_server() -> std::io::Result<()> { async fn start_ban_service() -> std::io::Result<()> {
let seconds_iptables = Duration::from_secs(60); let seconds_iptables = Duration::from_secs(60);
info!("Saving IPTables every {} secs", seconds_iptables.as_secs()); info!(every_seconds = seconds_iptables.as_secs();
"Saving IPTables"
);
spawn(move || loop { spawn(move || loop {
sleep(seconds_iptables); sleep(seconds_iptables);
iptables_save::save_iptables().expect("Failed to save IPTables"); MartilloMaldito::save_rules().expect("Failed to save rules");
}); });
let mut child: Child = Command::new("journalctl") let child: Child = Command::new("journalctl")
.arg("-D") .arg("-D")
.arg("/var/log/journal") .arg("/var/log/journal")
.arg("-u") .arg("-u")
@ -95,17 +95,18 @@ async fn start_ban_server() -> std::io::Result<()> {
.spawn() .spawn()
.expect("Failed to start journalctl"); .expect("Failed to start journalctl");
let stdout = child.stdout.as_mut().expect("Failed to capture stdout"); let stdout = child.stdout.expect("Failed to capture stdout");
let mut reader = std::io::BufReader::new(stdout); let mut reader = std::io::BufReader::new(stdout);
let iptables = iptables::new(false).unwrap();
let martillo_maldito = MartilloMaldito::ipv4(false);
let mut login_attempts: HashMap<String, usize> = HashMap::new(); let mut login_attempts: HashMap<String, usize> = HashMap::new();
info!("Start reading logins from SSH");
loop { loop {
let mut line = String::new(); let mut line = String::new();
if let Err(err) = reader.read_line(&mut line) { if let Err(err) = reader.read_line(&mut line) {
error!("Reading line: {}", err); error!(err = err.to_string().as_str();
"Reading line"
);
continue; continue;
} }
@ -114,9 +115,9 @@ async fn start_ban_server() -> std::io::Result<()> {
} }
if let Some(login_attempt) = LoginAttempt::capture(&line) { if let Some(login_attempt) = LoginAttempt::capture(&line) {
info!( info!(ip = login_attempt.ip.as_str(),
"Login attempt from {} using {} user", user = login_attempt.user.as_str();
login_attempt.ip, login_attempt.user "Login attempt",
); );
match login_attempts.get_mut(&login_attempt.ip) { match login_attempts.get_mut(&login_attempt.ip) {
@ -124,15 +125,10 @@ async fn start_ban_server() -> std::io::Result<()> {
*count += 1; *count += 1;
if *count == 3 { if *count == 3 {
if iptables if martillo_maldito.ban_ip(&login_attempt.ip).is_ok() {
.append_unique( info!(ip = login_attempt.ip.as_str();
"filter", "Banned IP"
"INPUT", );
&format!("--source {} -j DROP", login_attempt.ip),
)
.is_ok()
{
info!("IP {} banned", login_attempt.ip);
} }
login_attempts.remove(&login_attempt.ip); login_attempts.remove(&login_attempt.ip);
@ -147,11 +143,5 @@ async fn start_ban_server() -> std::io::Result<()> {
} }
fn start_logger() { fn start_logger() {
unsafe { Builder::from_default_env().init();
std::env::set_var(
"RUST_LOG",
std::env::var("RUST_LOG").unwrap_or("INFO".to_string()),
);
}
logfmt_logger::init();
} }

243
src/martillo_maldito.rs Normal file
View File

@ -0,0 +1,243 @@
use regex::Regex;
use std::{collections::HashMap, process::Command};
pub struct MartilloMaldito {
iptables: iptables::IPTables,
chain: String,
}
impl MartilloMaldito {
pub fn ipv4(docker: bool) -> MartilloMaldito {
MartilloMaldito {
iptables: iptables::new(false).unwrap(),
chain: Self::get_chain(docker).to_string(),
}
}
pub fn ipv6(docker: bool) -> MartilloMaldito {
MartilloMaldito {
iptables: iptables::new(true).unwrap(),
chain: Self::get_chain(docker).to_string(),
}
}
pub fn save_rules() -> std::io::Result<std::process::Output> {
Command::new("iptables-save")
.args(["-f", "/etc/iptables/rules.v4"])
.output()
}
pub fn is_port_secured(&self, port: u16) -> bool {
let rules = self.get_rules();
if rules.is_err() {
return false;
}
for rule in rules.unwrap() {
if rule.contains(&format!("-p tcp -m tcp --dport {} -j DROP", port)) {
return true;
}
}
false
}
pub fn get_secured_ports(&self) -> Vec<u16> {
let rules = self.get_rules();
if rules.is_err() {
return vec![];
}
let port_regex = iptables_regex_for_port();
rules
.unwrap()
.iter()
.filter(|r| r.contains("-p tcp -m tcp --dport") && r.contains("-j DROP"))
.map(|r| extract_port(&port_regex, r).unwrap())
.collect()
}
pub fn get_banned_ips(&self) -> Vec<String> {
let rules = self.get_rules();
if rules.is_err() {
return vec![];
}
let ip_regex = iptables_regex_for_ip();
rules
.unwrap()
.iter()
.filter(|r| {
r.contains(&format!("-A {}", self.chain))
&& r.contains("-j DROP")
&& r.contains("-s")
})
.map(|r| extract_ip(&ip_regex, r).unwrap())
.collect()
}
pub fn get_secured_ports_with_allowed_ips(&self) -> HashMap<u16, Vec<String>> {
let mut result: HashMap<u16, Vec<String>> = HashMap::new();
let secured_ports = self.get_secured_ports();
if secured_ports.is_empty() {
return result;
}
let rules = self.get_rules();
if rules.is_err() {
return result;
}
let rules = rules.unwrap();
let ip_regex = iptables_regex_for_ip();
for port in secured_ports {
let ips = rules
.iter()
.filter(|r| {
r.contains(&format!("-A {} -s", self.chain))
&& r.contains(&format!("-p tcp -m tcp --dport {} -j ACCEPT", port))
})
.map(|r| extract_ip(&ip_regex, r).unwrap())
.collect();
result.insert(port, ips);
}
result
}
pub fn ban_ip(&self, ip: &str) -> Result<(), Box<dyn std::error::Error>> {
self.append_unique("filter", &format!("-s {} -j DROP", ip))
}
pub fn secure_port(
&self,
port: u16,
position: Option<usize>,
) -> Result<(), Box<dyn std::error::Error>> {
let table = "filter";
let rule = secure_port_rule(port);
let position = if self.chain == "DOCKER-USER" && position.is_none() {
let all_docker_rules = self.get_rules()?;
Some(all_docker_rules.len() - 1)
} else {
position
};
if let Some(position) = position {
self.insert_unique(table, &rule, position)
} else {
self.append_unique(table, &rule)
}
}
pub fn unsecure_port(&self, port: u16) -> Result<(), Box<dyn std::error::Error>> {
let rule = secure_port_rule(port);
self.iptables.delete("filter", &self.chain, &rule)
}
pub fn allow_ip_for_port(
&self,
ip: &str,
port: u16,
position: Option<usize>,
) -> Result<(), Box<dyn std::error::Error>> {
let table = "filter";
let rule = allow_ip_for_port_rule(port, ip);
if let Some(position) = position {
self.insert_unique(table, &rule, position)
} else {
self.append_unique(table, &rule)
}
}
pub fn remove_allow_ip_for_port(
&self,
ip: &str,
port: u16,
) -> Result<(), Box<dyn std::error::Error>> {
let rule = allow_ip_for_port_rule(port, ip);
self.iptables.delete("filter", &self.chain, &rule)
}
fn get_rules(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
self.iptables.list("filter", &self.chain)
}
fn append_unique(&self, table: &str, rule: &str) -> Result<(), Box<dyn std::error::Error>> {
self.iptables.append_unique(table, &self.chain, rule)
}
fn insert_unique(
&self,
table: &str,
rule: &str,
position: usize,
) -> Result<(), Box<dyn std::error::Error>> {
self.iptables
.insert_unique(table, &self.chain, rule, position as i32)
}
fn get_chain(docker: bool) -> &'static str {
if docker {
"DOCKER-USER"
} else {
"INPUT"
}
}
}
fn secure_port_rule(port: u16) -> String {
format!("-p tcp --dport {} -j DROP", port)
}
fn allow_ip_for_port_rule(port: u16, ip: &str) -> String {
format!("-p tcp --dport {} -s {} -j ACCEPT", port, ip)
}
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 iptables_regex_for_ip() -> Regex {
Regex::new(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b").unwrap()
}
fn iptables_regex_for_port() -> Regex {
Regex::new(r"--dport\s+(\d+)").unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn correct_extract_ip() {
let regex = iptables_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_extract_ip() {
let regex = iptables_regex_for_ip();
let input = "-A INPUT -j DROP";
assert_eq!(extract_ip(&regex, input), None);
}
#[test]
fn docker_extract_ip() {
let regex = iptables_regex_for_ip();
let input = "-A DOCKER -d 172.18.0.2/32 ! -i br-127d33df48a4 -o br-127d33df48a4 -p tcp -m tcp --dport 8078 -j ACCEPT";
assert_eq!(extract_ip(&regex, input), Some("172.18.0.2".to_string()));
}
}