Automatically manage firewall rules (iptables) for services #1

Merged
quentin merged 7 commits from add-firewall-rules into master 2020-07-04 15:16:23 +00:00
4 changed files with 169 additions and 212 deletions
Showing only changes of commit 2dbf9da005 - Show all commits

238
Cargo.lock generated
View file

@ -1,5 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
dependencies = [
"memchr",
]
[[package]]
name = "aho-corasick"
version = "0.7.10"
@ -49,6 +58,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "bitflags"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3"
[[package]]
name = "bitflags"
version = "1.2.1"
@ -127,11 +142,10 @@ dependencies = [
"anyhow",
"futures",
"igd",
"libc",
"iptables",
"log",
"mnl",
"nftnl",
"pretty_env_logger",
"regex 1.3.7",
"reqwest",
"serde",
"serde-lexpr",
@ -163,24 +177,10 @@ dependencies = [
"atty",
"humantime",
"log",
"regex",
"regex 1.3.7",
"termcolor",
]
[[package]]
name = "err-derive"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"rustversion",
"syn",
"synstructure",
]
[[package]]
name = "fnv"
version = "1.0.6"
@ -214,7 +214,7 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
dependencies = [
"bitflags",
"bitflags 1.2.1",
"fuchsia-zircon-sys",
]
@ -509,6 +509,17 @@ dependencies = [
"libc",
]
[[package]]
name = "iptables"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7910549cb022e3112eea631a4c3e62523281b6c61024b2c3a8d61da620010de"
dependencies = [
"lazy_static 0.2.11",
"nix",
"regex 0.2.11",
]
[[package]]
name = "itoa"
version = "0.4.5"
@ -534,6 +545,12 @@ dependencies = [
"winapi-build",
]
[[package]]
name = "lazy_static"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -637,34 +654,13 @@ dependencies = [
"ws2_32-sys",
]
[[package]]
name = "mnl"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6efb50a48dbacd112e7e847b9847fa530ec4a473ba6322a2f82c42ef333e226"
dependencies = [
"libc",
"log",
"mnl-sys",
]
[[package]]
name = "mnl-sys"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5302035599c722b3a5b92a6502ff73c501dc6d100c53b89f0fae0cb932a37122"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "native-tls"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
dependencies = [
"lazy_static",
"lazy_static 1.4.0",
"libc",
"log",
"openssl",
@ -688,27 +684,17 @@ dependencies = [
]
[[package]]
name = "nftnl"
version = "0.3.0"
name = "nix"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3727d1e8c1c9af88857f46539c3030693158a2a7586056b8cab6ded523bf7aa"
dependencies = [
"bitflags",
"err-derive",
"libc",
"log",
"nftnl-sys",
]
[[package]]
name = "nftnl-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dba134c9b125b7d7c13d813388aaeb2aeeba60fb1eb702799163fb845086ca33"
checksum = "a0d95c5fa8b641c10ad0b8887454ebaafa3c92b5cd5350f8fc693adafd178e7b"
dependencies = [
"bitflags 0.4.0",
"cfg-if",
"libc",
"pkg-config",
"rustc_version",
"semver",
"void",
]
[[package]]
@ -733,10 +719,10 @@ version = "0.10.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52"
dependencies = [
"bitflags",
"bitflags 1.2.1",
"cfg-if",
"foreign-types",
"lazy_static",
"lazy_static 1.4.0",
"libc",
"openssl-sys",
]
@ -826,32 +812,6 @@ dependencies = [
"log",
]
[[package]]
name = "proc-macro-error"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check 0.9.1",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn-mid",
"version_check 0.9.1",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.11"
@ -977,16 +937,38 @@ version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "regex"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384"
dependencies = [
"aho-corasick 0.6.10",
"memchr",
"regex-syntax 0.5.6",
"thread_local 0.3.6",
"utf8-ranges",
]
[[package]]
name = "regex"
version = "1.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
dependencies = [
"aho-corasick",
"aho-corasick 0.7.10",
"memchr",
"regex-syntax",
"thread_local",
"regex-syntax 0.6.17",
"thread_local 1.0.1",
]
[[package]]
name = "regex-syntax"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
dependencies = [
"ucd-util",
]
[[package]]
@ -1020,7 +1002,7 @@ dependencies = [
"hyper",
"hyper-tls",
"js-sys",
"lazy_static",
"lazy_static 1.4.0",
"log",
"mime",
"mime_guess",
@ -1041,14 +1023,12 @@ dependencies = [
]
[[package]]
name = "rustversion"
version = "1.0.2"
name = "rustc_version"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6"
checksum = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
dependencies = [
"proc-macro2",
"quote",
"syn",
"semver",
]
[[package]]
@ -1063,7 +1043,7 @@ version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295"
dependencies = [
"lazy_static",
"lazy_static 1.4.0",
"winapi 0.3.8",
]
@ -1088,6 +1068,12 @@ dependencies = [
"core-foundation-sys",
]
[[package]]
name = "semver"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac"
[[package]]
name = "serde"
version = "1.0.107"
@ -1170,29 +1156,6 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "syn-mid"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "synstructure"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "tempfile"
version = "3.1.0"
@ -1216,13 +1179,22 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "thread_local"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
dependencies = [
"lazy_static 1.4.0",
]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
"lazy_static",
"lazy_static 1.4.0",
]
[[package]]
@ -1245,7 +1217,7 @@ dependencies = [
"bytes 0.5.4",
"fnv",
"iovec",
"lazy_static",
"lazy_static 1.4.0",
"memchr",
"mio",
"pin-project-lite",
@ -1300,6 +1272,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382"
[[package]]
name = "ucd-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85f514e095d348c279b1e5cd76795082cf15bd59b93207832abe0b1d8fed236"
[[package]]
name = "unicase"
version = "2.6.0"
@ -1361,6 +1339,12 @@ dependencies = [
"percent-encoding 2.1.0",
]
[[package]]
name = "utf8-ranges"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
[[package]]
name = "vcpkg"
version = "0.2.8"
@ -1379,6 +1363,12 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "want"
version = "0.3.0"
@ -1414,7 +1404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11cdb95816290b525b32587d76419facd99662a07e59d3cdb560488a819d9a45"
dependencies = [
"bumpalo",
"lazy_static",
"lazy_static 1.4.0",
"log",
"proc-macro2",
"quote",
@ -1569,7 +1559,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c1cb601d29fe2c2ac60a2b2e5e293994d87a1f6fa9687a31a15270f909be9c2"
dependencies = [
"bitflags",
"bitflags 1.2.1",
]
[[package]]

View file

@ -17,6 +17,5 @@ serde = { version = "1.0.107", features = ["derive"] }
serde_json = "1.0.53"
serde-lexpr = "0.1.1"
anyhow = "1.0.28"
nftnl = "0.3.0"
mnl = "0.2.0"
libc = "0.2.70"
iptables = "0.2.2"
regex = "1"

141
src/fw.rs
View file

@ -1,101 +1,64 @@
use nftnl::{nft_expr, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table};
use std::{
ffi::{self, CString},
io,
};
use mnl;
use libc;
use iptables;
use regex::Regex;
use std::collections::HashSet;
const TABLE_NAME: &str = "diplonat";
const OUT_CHAIN_NAME: &str = "out";
const IN_CHAIN_NAME: &str = "in";
#[derive(PartialEq,Eq,Debug,Hash)]

Could you add the anyhow crate to handle errors please:

use anyhow::{Result,Context}
Could you add the anyhow crate to handle errors please: ```rust use anyhow::{Result,Context} ```
pub struct Port {
proto: String,
number: u16,
}
#[derive(Debug)]
struct Error(String);
pub fn setup(ipt: &iptables::IPTables) {
ipt.new_chain("filter", "DIPLONAT").unwrap();
ipt.insert("filter", "INPUT", "-j DIPLONAT", 1).unwrap();
}

anyhow will override your Result<> object, taking only a return value, error will be generic then.
eg:

pub fn setup(ipt: &iptables::IPTables) -> Result<()> {

This tip applies for the whole document, and in any case build will fail as soon as you will have added the anyhow use statement.

anyhow will override your Result<> object, taking only a return value, error will be generic then. eg: ```rust pub fn setup(ipt: &iptables::IPTables) -> Result<()> { ``` This tip applies for the whole document, and in any case build will fail as soon as you will have added the anyhow use statement.
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error(error.to_string())
pub fn open_ports(ipt: &iptables::IPTables, ports: Vec<Port>) {

It's very clever to put the rules in a separate chain, well done ;)

It's very clever to put the rules in a separate chain, well done ;)
for p in ports {
ipt.append("filter", "DIPLONAT", &format!("-p {} --dport {} -j ACCEPT", p.proto, p.number)).unwrap();
}
}
impl From<ffi::NulError> for Error {
fn from(error: ffi::NulError) -> Self {
Error(error.to_string())
pub fn get_opened_ports(ipt: &iptables::IPTables) -> HashSet<Port> {
let mut opened_ports: HashSet<Port> = HashSet::new();
let list = ipt.list("filter", "DIPLONAT").unwrap();

Same as before, you can just use Result<()>

Same as before, you can just use `Result<()>`
let re = Regex::new(r"\-A.*? \-p (\w+).*\-\-dport (\d+).*?\-j ACCEPT").unwrap();
for i in list {
let caps = re.captures(&i);
match caps {
Some(c) => {
let raw_proto = c.get(1).unwrap();
let raw_port = c.get(2).unwrap();
let proto = String::from(raw_proto.as_str());
let number = String::from(raw_port.as_str()).parse::<u16>().unwrap();
opened_ports.insert( Port { proto, number } );

Same as before, you can just use Result<messages::PublicExposedPorts>

Same as before, you can just use `Result<messages::PublicExposedPorts>`
},
_ => {}
}
}
opened_ports
}

A convention we chose with LX (we should document it, mea culpa) is to use the anyhow crate and to handle errors without crashing whenever possible but forwarding it "higher" in the call stack.

The idea is the caller can choose to crash or to bypass the error and then your code can be used as a handful library. So, as your return type is already a Result, you can return Ok() or, thanks to anyhow, any kind of error. You can simply rewrite your statement as follow (as soon as you have added anyhow):

let re = Regex::new(r"-A.*? -p (w+).*--dport (d+).*?-j ACCEPT")?

To help debugging, you can add a context to your error:

let re = Regex::new(r"-A.*? -p (w+).*--dport (d+).*?-j ACCEPT").context("Regex matching open ports encountered an unexpected rule")?

As a more general rule, do not use:

  • expect
  • unwrap

Instead use:

  • ?
  • context
  • handle error in place if it's possible
A convention we chose with LX (we should document it, mea culpa) is to use the anyhow crate and to handle errors without crashing whenever possible but forwarding it "higher" in the call stack. The idea is the caller can choose to crash or to bypass the error and then your code can be used as a handful library. So, as your return type is already a `Result`, you can return `Ok()` or, thanks to anyhow, any kind of error. You can simply rewrite your statement as follow (as soon as you have added anyhow): ```rust let re = Regex::new(r"-A.*? -p (w+).*--dport (d+).*?-j ACCEPT")? ``` To help debugging, you can add a context to your error: ```rust let re = Regex::new(r"-A.*? -p (w+).*--dport (d+).*?-j ACCEPT").context("Regex matching open ports encountered an unexpected rule")? ``` As a more general rule, do not use: - `expect` - `unwrap` Instead use: - `?` - `context` - handle error in place if it's possible
fn send_and_process(batch: &FinalizedBatch) -> Result<(), Error> {
let socket = mnl::Socket::new(mnl::Bus::Netfilter)?;
socket.send_all(batch)?;
let portid = socket.portid();
let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize];
while let Some(message) = socket_recv(&socket, &mut buffer[..])? {
match mnl::cb_run(message, 2, portid)? {
mnl::CbResult::Stop => {
break;
}
mnl::CbResult::Ok => (),
}
}
Ok(())
pub fn cleanup(ipt: &iptables::IPTables) {
ipt.flush_chain("filter", "DIPLONAT").unwrap();
ipt.delete("filter", "INPUT", "-j DIPLONAT").unwrap();
ipt.delete_chain("filter", "DIPLONAT").unwrap();

Lines 50, 51 and 54 you can replace unwrap by ?

Lines 50, 51 and 54 you can replace `unwrap` by `?`
}
fn socket_recv<'a>(socket: &mnl::Socket, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
let ret = socket.recv(buf)?;
if ret > 0 {
Ok(Some(&buf[..ret]))
} else {
Ok(None)
}
}
fn add_port_allowed(port: u16) -> Result<(), Error> {
let mut batch = Batch::new();
// TODO: at the moment, I haven't found a way to properly separate setup part (create table and
// chains) and the add rule part because the add rule part needs a reference on the chains.
// apparently creating a table and chains that already exist seems to do nothing so it works
// doing the following. To be done properly though
let table = Table::new(&CString::new(TABLE_NAME).unwrap(), ProtoFamily::Inet);
batch.add(&table, nftnl::MsgType::Add);
let mut out_chain = Chain::new(&CString::new(OUT_CHAIN_NAME).unwrap(), &table);
let mut in_chain = Chain::new(&CString::new(IN_CHAIN_NAME).unwrap(), &table);
out_chain.set_hook(nftnl::Hook::Out, 0);
in_chain.set_hook(nftnl::Hook::In, 0);
out_chain.set_policy(nftnl::Policy::Accept);
in_chain.set_policy(nftnl::Policy::Drop);
batch.add(&out_chain, nftnl::MsgType::Add);
batch.add(&in_chain, nftnl::MsgType::Add);
let mut _rule = Rule::new(&in_chain);
_rule.add_expr(&nft_expr!(meta nfproto));
_rule.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV4 as u8));
_rule.add_expr(&nft_expr!(meta l4proto));
_rule.add_expr(&nft_expr!(cmp == libc::IPPROTO_TCP as u8));
_rule.add_expr(&nftnl::expr::Payload::Transport(
nftnl::expr::TransportHeaderField::Tcp(nftnl::expr::TcpHeaderField::Dport),
));
_rule.add_expr(&nft_expr!(cmp == port.to_be()));
_rule.add_expr(&nft_expr!(verdict accept));
batch.add(&_rule, nftnl::MsgType::Add);
let finalized_batch = batch.finalize();
send_and_process(&finalized_batch)?;
Ok(())
/*
fn main() {
let ipt = iptables::new(false).unwrap();
setup(&ipt);
let mut test: HashSet<Port> = HashSet::new();
test.insert(Port { proto: String::from("tcp"), number: 443 });
let a = get_opened_ports(&ipt);
let l = test.difference(&a).collect::<Vec<&Port>>();
println!("{:?}", l);
}
*/

View file

@ -4,7 +4,9 @@ mod consul;
mod consul_actor;
mod igd_actor;
mod diplonat;
mod fw;
use iptables;
use log::*;
use diplonat::Diplonat;
@ -13,6 +15,9 @@ async fn main() {
pretty_env_logger::init();
info!("Starting Diplonat");
let ipt = iptables::new(false).unwrap();
fw::setup(&ipt).expect("iptables setup failed");
let mut diplo = Diplonat::new().await.expect("Setup failed");
diplo.listen().await.expect("A runtime error occured");
}