diff --git a/src/acme_actor.rs b/src/acme_actor.rs new file mode 100644 index 0000000..3b6aa50 --- /dev/null +++ b/src/acme_actor.rs @@ -0,0 +1,70 @@ +use anyhow::Result; +use log::*; +use tokio::{ + select, + sync::watch, + time::{ + Duration, + self, +}}; + +use crate::config::RuntimeConfigAcme; +use crate::messages; + +pub struct AcmeActor { + email: String, + last_ports: messages::PublicExposedPorts, + refresh: Duration, + + rx_ports: watch::Receiver, +} + +impl AcmeActor { + pub async fn new( + config: Option, + rxp: &watch::Receiver + ) -> Result> { + + if config.is_none() { + return Ok(None); + } + let config = config.unwrap(); + + let ctx = Self { + email: config.email, + last_ports: messages::PublicExposedPorts::new(), + refresh: config.refresh_time, + rx_ports: rxp.clone(), + }; + + Ok(Some(ctx)) + } + + pub async fn listen(&mut self) -> Result<()> { + let mut interval = time::interval(self.refresh); + loop { + // 1. Wait for an event + let new_ports = select! { + Some(ports) = self.rx_ports.recv() => Some(ports), + _ = interval.tick() => None, + else => return Ok(()) // Sender dropped, terminate loop. + }; + + // 2. Update last ports if needed + if let Some(p) = new_ports { self.last_ports = p; } + + // 3. Flush IGD requests + match self.do_acme().await { + Ok(()) => debug!("Successfully updated ACME"), + Err(e) => error!("An error occured while updating ACME. {}", e), + } + } + } + + pub async fn do_acme(&self) -> Result<()> { + debug!("Doing ACME!!!"); + debug!("{:#?}", self.last_ports); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/config/options.rs b/src/config/options.rs index b3a63b0..54e948d 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -34,6 +34,8 @@ pub struct ConfigOptsAcme { /// The default domain holder's e-mail [default: None] pub email: Option, + /// Refresh time for firewall rules [default: 300] + pub refresh_time: Option, } /// Firewall configuration options diff --git a/src/config/runtime.rs b/src/config/runtime.rs index f83a6b5..ee7f682 100644 --- a/src/config/runtime.rs +++ b/src/config/runtime.rs @@ -20,6 +20,7 @@ pub struct RuntimeConfigConsul { #[derive(Debug)] pub struct RuntimeConfigAcme { pub email: String, + pub refresh_time: Duration, } #[derive(Debug)] @@ -79,9 +80,12 @@ impl RuntimeConfigAcme { let email = opts.email.expect( "'DIPLONAT_ACME_EMAIL' is required if ACME is enabled"); + let refresh_time = Duration::from_secs( + opts.refresh_time.unwrap_or(super::REFRESH_TIME).into()); Ok(Some(Self { email, + refresh_time, })) } } diff --git a/src/consul_actor.rs b/src/consul_actor.rs index dcbd79e..fd4d05f 100644 --- a/src/consul_actor.rs +++ b/src/consul_actor.rs @@ -14,9 +14,11 @@ use crate::consul; use crate::messages; #[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] pub enum DiplonatParameter { - tcp_port(HashSet), - udp_port(HashSet) + TcpPort(HashSet), + UdpPort(HashSet), + Acme(HashSet) } #[derive(Serialize, Deserialize, Debug)] @@ -57,17 +59,19 @@ fn to_parameters(catalog: &consul::CatalogNode) -> Vec { } fn to_open_ports(params: &Vec) -> messages::PublicExposedPorts { - let mut op = messages::PublicExposedPorts { - tcp_ports: HashSet::new(), - udp_ports: HashSet::new() - }; + // let mut op = messages::PublicExposedPorts { + // tcp_ports: HashSet::new(), + // udp_ports: HashSet::new() + // }; + let mut op = messages::PublicExposedPorts::new(); for conf in params { let DiplonatConsul::diplonat(c) = conf; for parameter in c { match parameter { - DiplonatParameter::tcp_port(p) => op.tcp_ports.extend(p), - DiplonatParameter::udp_port(p) => op.udp_ports.extend(p), + DiplonatParameter::TcpPort(p) => op.tcp_ports.extend(p), + DiplonatParameter::UdpPort(p) => op.udp_ports.extend(p), + DiplonatParameter::Acme(urls) => op.acme.extend(urls.clone()), }; } } @@ -77,10 +81,11 @@ fn to_open_ports(params: &Vec) -> messages::PublicExposedPorts { impl ConsulActor { pub fn new(config: RuntimeConfigConsul) -> Self { - let (tx, rx) = watch::channel(messages::PublicExposedPorts{ - tcp_ports: HashSet::new(), - udp_ports: HashSet::new() - }); + let (tx, rx) = watch::channel(messages::PublicExposedPorts::new()); + // let (tx, rx) = watch::channel(messages::PublicExposedPorts{ + // tcp_ports: HashSet::new(), + // udp_ports: HashSet::new() + // }); return Self { consul: consul::Consul::new(&config.url), diff --git a/src/diplonat.rs b/src/diplonat.rs index 6334e5b..b2310f9 100644 --- a/src/diplonat.rs +++ b/src/diplonat.rs @@ -1,14 +1,17 @@ use anyhow::{Result, anyhow}; +use log::debug; use tokio::try_join; use crate::config::ConfigOpts; use crate::consul_actor::ConsulActor; +use crate::acme_actor::AcmeActor; use crate::fw_actor::FirewallActor; use crate::igd_actor::IgdActor; pub struct Diplonat { consul: ConsulActor, + acme: Option, firewall: Option, igd: Option, } @@ -16,10 +19,15 @@ pub struct Diplonat { impl Diplonat { pub async fn new() -> Result { let config = ConfigOpts::from_env()?; - println!("{:#?}", config); + debug!("{:#?}", config); let consul_actor = ConsulActor::new(config.consul); + let acme_actor = AcmeActor::new( + config.acme, + &consul_actor.rx_open_ports + ).await?; + let firewall_actor = FirewallActor::new( config.firewall, &consul_actor.rx_open_ports @@ -30,13 +38,16 @@ impl Diplonat { &consul_actor.rx_open_ports ).await?; - if firewall_actor.is_none() && igd_actor.is_none() { + if acme_actor.is_none() && + firewall_actor.is_none() && + igd_actor.is_none() { return Err(anyhow!( "At least enable *one* module, otherwise it's boring!")); } let ctx = Self { consul: consul_actor, + acme: acme_actor, firewall: firewall_actor, igd: igd_actor, }; @@ -45,11 +56,18 @@ impl Diplonat { } pub async fn listen(&mut self) -> Result<()> { + let acme = &mut self.acme; let firewall = &mut self.firewall; let igd = &mut self.igd; try_join!( self.consul.listen(), + async { + match acme { + Some(x) => x.listen().await, + None => Ok(()) + } + }, async { match firewall { Some(x) => x.listen().await, diff --git a/src/fw.rs b/src/fw.rs index a71dd1c..355e928 100644 --- a/src/fw.rs +++ b/src/fw.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +// use std::collections::HashSet; use anyhow::{Result,Context}; use iptables; @@ -31,10 +31,11 @@ pub fn open_ports(ipt: &iptables::IPTables, ports: messages::PublicExposedPorts) } pub fn get_opened_ports(ipt: &iptables::IPTables) -> Result { - let mut ports = messages::PublicExposedPorts { - tcp_ports: HashSet::new(), - udp_ports: HashSet::new() - }; + let mut ports = messages::PublicExposedPorts::new(); + // let mut ports = messages::PublicExposedPorts { + // tcp_ports: HashSet::new(), + // udp_ports: HashSet::new() + // }; let list = ipt.list("filter", "DIPLONAT")?; let re = Regex::new(r"\-A.*? \-p (\w+).*\-\-dport (\d+).*?\-j ACCEPT").context("Regex matching open ports encountered an unexpected rule")?; diff --git a/src/fw_actor.rs b/src/fw_actor.rs index 29e6473..9000048 100644 --- a/src/fw_actor.rs +++ b/src/fw_actor.rs @@ -73,7 +73,8 @@ impl FirewallActor { let ports_to_open = messages::PublicExposedPorts { tcp_ports: diff_tcp, - udp_ports: diff_udp + udp_ports: diff_udp, + acme: HashSet::new() }; fw::open_ports(&self.ipt, ports_to_open)?; diff --git a/src/main.rs b/src/main.rs index 720edf8..48467d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod acme_actor; mod config; mod consul; mod consul_actor; diff --git a/src/messages.rs b/src/messages.rs index 09a7c14..86ba1b2 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -3,14 +3,16 @@ use std::collections::HashSet; #[derive(Debug, Clone, PartialEq, Eq)] pub struct PublicExposedPorts { pub tcp_ports: HashSet, - pub udp_ports: HashSet + pub udp_ports: HashSet, + pub acme: HashSet } impl PublicExposedPorts { pub fn new() -> Self { return Self { tcp_ports: HashSet::new(), - udp_ports: HashSet::new() + udp_ports: HashSet::new(), + acme: HashSet::new() } } }