Skip to content

Commit

Permalink
Merge branch 'igornovg/icbn-firewall' into 'master'
Browse files Browse the repository at this point in the history
feat(BOUN-935): ic-boundary: add firewall generator

Add a bit dirty firewall generator to replace similar `control-plane` functionality to be able to remove it from BN.

* This stuff is to be removed from the code after we move to a a decentralized BN
* The feature is optional and is enabled only if `--nftables-system-replicas-path` CLI parameter is specified
* Firewall rules are only updated when a new registry version is published and contain all system nodes.

  This is nicer than `control-plane` that persisted them with every routing table change, which is not needed at all and only caused too many restarts when the network is flaky.
* Use `reload` instead of `restart` for `nftables` which does not involve flushing all the rules first and re-adding them. See `ExecStop` below

  ```plaintext
  # /usr/lib/systemd/system/nftables.service
  ...
  ExecStart=/usr/sbin/nft -f /etc/nftables.conf
  ExecReload=/usr/sbin/nft -f /etc/nftables.conf
  ExecStop=/usr/sbin/nft flush ruleset
  ``` 

See merge request dfinity-lab/public/ic!15733
  • Loading branch information
blind-oracle committed Oct 30, 2023
2 parents 4c89e7a + 7361f06 commit 0919118
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 8 deletions.
6 changes: 3 additions & 3 deletions rs/boundary_node/ic_boundary/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ pub struct HealthChecksConfig {
#[derive(Args)]
pub struct FirewallConfig {
/// The path to the nftables replica ruleset file to update
#[clap(long, default_value = "system_replicas.ruleset")]
pub nftables_system_replicas_path: PathBuf,
#[clap(long)]
pub nftables_system_replicas_path: Option<PathBuf>,

/// The name of the nftables variable to export
#[clap(long, default_value = "system_replica_ips")]
#[clap(long, default_value = "ipv6_system_replica_ips")]
pub nftables_system_replicas_var: String,
}

Expand Down
17 changes: 15 additions & 2 deletions rs/boundary_node/ic_boundary/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use crate::{
WithDeduplication,
},
dns::DnsResolver,
firewall::{FirewallGenerator, SystemdReloader},
http::ReqwestClient,
management,
metrics::{
Expand All @@ -53,7 +54,7 @@ use crate::{
persist,
rate_limiting::RateLimit,
routes::{self, Health, Lookup, Proxy, ProxyRouter, RootKey},
snapshot::Runner as SnapshotRunner,
snapshot::{Runner as SnapshotRunner, SnapshotPersister},
tls_verify::TlsVerifier,
};

Expand All @@ -69,6 +70,7 @@ use crate::{
pub const SERVICE_NAME: &str = "ic_boundary";
pub const AUTHOR_NAME: &str = "Boundary Node Team <boundary-nodes@dfinity.org>";
const DER_PREFIX: &[u8; 37] = b"\x30\x81\x82\x30\x1d\x06\x0d\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x01\x02\x01\x06\x0c\x2b\x06\x01\x04\x01\x82\xdc\x7c\x05\x03\x02\x01\x03\x61\x00";
const SYSTEMCTL_BIN: &str = "/usr/bin/systemctl";

const SECOND: Duration = Duration::from_secs(1);
#[cfg(feature = "tls")]
Expand Down Expand Up @@ -358,7 +360,18 @@ pub async fn main(cli: Cli) -> Result<(), Error> {
);

// Snapshots
let snapshot_runner = SnapshotRunner::new(Arc::clone(&registry_snapshot), registry_client);
let mut snapshot_runner = SnapshotRunner::new(Arc::clone(&registry_snapshot), registry_client);

if let Some(v) = &cli.firewall.nftables_system_replicas_path {
let fw_reloader = SystemdReloader::new(SYSTEMCTL_BIN.into(), "nftables", "reload");

let fw_generator =
FirewallGenerator::new(v.clone(), cli.firewall.nftables_system_replicas_var.clone());

let persister = SnapshotPersister::new(fw_generator, fw_reloader);
snapshot_runner.set_persister(persister);
}

let snapshot_runner = WithMetrics(
snapshot_runner,
MetricParams::new(&registry, "run_snapshot"),
Expand Down
77 changes: 77 additions & 0 deletions rs/boundary_node/ic_boundary/src/firewall.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,79 @@
// TODO remove after moving to a decentralized BN

use std::{
fs,
io::{BufWriter, Write},
path::PathBuf,
process::{Command, Stdio},
};

use anyhow::{Context, Error};
use ic_registry_subnet_type::SubnetType;

use crate::snapshot::RegistrySnapshot;

#[derive(Debug, Clone, PartialEq)]
pub struct Rule {}

pub struct SystemdReloader {
bin_path: PathBuf,
service: String,
command: String,
}

impl SystemdReloader {
pub fn new(bin_path: PathBuf, service: &str, command: &str) -> Self {
Self {
bin_path,
service: service.into(),
command: command.into(),
}
}

pub async fn reload(&self) -> Result<(), Error> {
let mut child = Command::new(&self.bin_path)
.args([&self.command, &self.service])
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?;

child.wait()?;

Ok(())
}
}

pub struct FirewallGenerator {
path: PathBuf,
var: String,
}

impl FirewallGenerator {
pub fn new(path: PathBuf, var: String) -> Self {
Self { path, var }
}

pub fn generate(&self, r: RegistrySnapshot) -> Result<(), Error> {
let mut inner: Vec<u8> = Vec::new();
let mut buf = BufWriter::new(&mut inner);

// Retain system subnets only
let subnets = r
.subnets
.iter()
.filter(|&subnet| subnet.subnet_type == SubnetType::System);

buf.write_all(format!("define {} = {{\n", self.var).as_bytes())?;
for subnet in subnets {
for node in &subnet.nodes {
buf.write_all(format!(" {},\n", &node.addr).as_bytes())?;
}
}
buf.write_all("}".as_bytes())?;

buf.flush()?;
drop(buf);

fs::write(&self.path, inner).context("failed to write firewall rules")
}
}
43 changes: 40 additions & 3 deletions rs/boundary_node/ic_boundary/src/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use std::{collections::HashMap, fmt, net::IpAddr, str::FromStr, sync::Arc};
use tracing::info;
use x509_parser::{certificate::X509Certificate, prelude::FromDer};

use crate::core::Run;
use crate::{
core::Run,
firewall::{FirewallGenerator, SystemdReloader},
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Node {
Expand Down Expand Up @@ -55,6 +58,26 @@ impl fmt::Display for Subnet {
}
}

// TODO remove after decentralization and clean up all loose ends
pub struct SnapshotPersister {
generator: FirewallGenerator,
reloader: SystemdReloader,
}

impl SnapshotPersister {
pub fn new(generator: FirewallGenerator, reloader: SystemdReloader) -> Self {
Self {
generator,
reloader,
}
}

pub async fn persist(&self, s: RegistrySnapshot) -> Result<(), Error> {
self.generator.generate(s)?;
self.reloader.reload().await
}
}

#[derive(Debug, Clone)]
pub struct RegistrySnapshot {
pub registry_version: u64,
Expand All @@ -67,6 +90,7 @@ pub struct Runner {
published_registry_snapshot: Arc<ArcSwapOption<RegistrySnapshot>>,
registry_client: Arc<dyn RegistryClient>,
registry_version: Option<RegistryVersion>,
persister: Option<SnapshotPersister>,
}

impl Runner {
Expand All @@ -78,9 +102,14 @@ impl Runner {
published_registry_snapshot,
registry_client,
registry_version: None,
persister: None,
}
}

pub fn set_persister(&mut self, persister: SnapshotPersister) {
self.persister = Some(persister);
}

// Creates a snapshot of the registry for given version
fn get_snapshot(&mut self, version: RegistryVersion) -> Result<RegistrySnapshot, Error> {
// Get routing table with canister ranges
Expand Down Expand Up @@ -217,14 +246,22 @@ impl Run for Runner {

// Otherwise create a snapshot & publish it
let rt = self.get_snapshot(version)?;
self.published_registry_snapshot.store(Some(Arc::new(rt)));

self.published_registry_snapshot
.store(Some(Arc::new(rt.clone())));

self.registry_version = Some(version);

info!(
version_old = self.registry_version.map(|x| x.get()),
version_new = version.get(),
"New registry snapshot published",
);

self.registry_version = Some(version);
// Persist the firewall rules if configured
if let Some(v) = &self.persister {
v.persist(rt).await?;
}

Ok(())
}
Expand Down

0 comments on commit 0919118

Please sign in to comment.