wrote the skeleton of ACME. Involved solving the cosmetic warnings about CamelCase for enums (without changing the API).

This commit is contained in:
adrien 2021-09-10 18:41:39 +02:00
parent 195aec2cfe
commit 4d76c3d78a
9 changed files with 126 additions and 22 deletions

70
src/acme_actor.rs Normal file
View file

@ -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<messages::PublicExposedPorts>,
}
impl AcmeActor {
pub async fn new(
config: Option<RuntimeConfigAcme>,
rxp: &watch::Receiver<messages::PublicExposedPorts>
) -> Result<Option<Self>> {
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(())
}
}

View file

@ -34,6 +34,8 @@ pub struct ConfigOptsAcme {
/// The default domain holder's e-mail [default: None] /// The default domain holder's e-mail [default: None]
pub email: Option<String>, pub email: Option<String>,
/// Refresh time for firewall rules [default: 300]
pub refresh_time: Option<u16>,
} }
/// Firewall configuration options /// Firewall configuration options

View file

@ -20,6 +20,7 @@ pub struct RuntimeConfigConsul {
#[derive(Debug)] #[derive(Debug)]
pub struct RuntimeConfigAcme { pub struct RuntimeConfigAcme {
pub email: String, pub email: String,
pub refresh_time: Duration,
} }
#[derive(Debug)] #[derive(Debug)]
@ -79,9 +80,12 @@ impl RuntimeConfigAcme {
let email = opts.email.expect( let email = opts.email.expect(
"'DIPLONAT_ACME_EMAIL' is required if ACME is enabled"); "'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 { Ok(Some(Self {
email, email,
refresh_time,
})) }))
} }
} }

View file

@ -14,9 +14,11 @@ use crate::consul;
use crate::messages; use crate::messages;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum DiplonatParameter { pub enum DiplonatParameter {
tcp_port(HashSet<u16>), TcpPort(HashSet<u16>),
udp_port(HashSet<u16>) UdpPort(HashSet<u16>),
Acme(HashSet<String>)
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -57,17 +59,19 @@ fn to_parameters(catalog: &consul::CatalogNode) -> Vec<DiplonatConsul> {
} }
fn to_open_ports(params: &Vec<DiplonatConsul>) -> messages::PublicExposedPorts { fn to_open_ports(params: &Vec<DiplonatConsul>) -> messages::PublicExposedPorts {
let mut op = messages::PublicExposedPorts { // let mut op = messages::PublicExposedPorts {
tcp_ports: HashSet::new(), // tcp_ports: HashSet::new(),
udp_ports: HashSet::new() // udp_ports: HashSet::new()
}; // };
let mut op = messages::PublicExposedPorts::new();
for conf in params { for conf in params {
let DiplonatConsul::diplonat(c) = conf; let DiplonatConsul::diplonat(c) = conf;
for parameter in c { for parameter in c {
match parameter { match parameter {
DiplonatParameter::tcp_port(p) => op.tcp_ports.extend(p), DiplonatParameter::TcpPort(p) => op.tcp_ports.extend(p),
DiplonatParameter::udp_port(p) => op.udp_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<DiplonatConsul>) -> messages::PublicExposedPorts {
impl ConsulActor { impl ConsulActor {
pub fn new(config: RuntimeConfigConsul) -> Self { pub fn new(config: RuntimeConfigConsul) -> Self {
let (tx, rx) = watch::channel(messages::PublicExposedPorts{ let (tx, rx) = watch::channel(messages::PublicExposedPorts::new());
tcp_ports: HashSet::new(), // let (tx, rx) = watch::channel(messages::PublicExposedPorts{
udp_ports: HashSet::new() // tcp_ports: HashSet::new(),
}); // udp_ports: HashSet::new()
// });
return Self { return Self {
consul: consul::Consul::new(&config.url), consul: consul::Consul::new(&config.url),

View file

@ -1,14 +1,17 @@
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use log::debug;
use tokio::try_join; use tokio::try_join;
use crate::config::ConfigOpts; use crate::config::ConfigOpts;
use crate::consul_actor::ConsulActor; use crate::consul_actor::ConsulActor;
use crate::acme_actor::AcmeActor;
use crate::fw_actor::FirewallActor; use crate::fw_actor::FirewallActor;
use crate::igd_actor::IgdActor; use crate::igd_actor::IgdActor;
pub struct Diplonat { pub struct Diplonat {
consul: ConsulActor, consul: ConsulActor,
acme: Option<AcmeActor>,
firewall: Option<FirewallActor>, firewall: Option<FirewallActor>,
igd: Option<IgdActor>, igd: Option<IgdActor>,
} }
@ -16,10 +19,15 @@ pub struct Diplonat {
impl Diplonat { impl Diplonat {
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
let config = ConfigOpts::from_env()?; let config = ConfigOpts::from_env()?;
println!("{:#?}", config); debug!("{:#?}", config);
let consul_actor = ConsulActor::new(config.consul); 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( let firewall_actor = FirewallActor::new(
config.firewall, config.firewall,
&consul_actor.rx_open_ports &consul_actor.rx_open_ports
@ -30,13 +38,16 @@ impl Diplonat {
&consul_actor.rx_open_ports &consul_actor.rx_open_ports
).await?; ).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!( return Err(anyhow!(
"At least enable *one* module, otherwise it's boring!")); "At least enable *one* module, otherwise it's boring!"));
} }
let ctx = Self { let ctx = Self {
consul: consul_actor, consul: consul_actor,
acme: acme_actor,
firewall: firewall_actor, firewall: firewall_actor,
igd: igd_actor, igd: igd_actor,
}; };
@ -45,11 +56,18 @@ impl Diplonat {
} }
pub async fn listen(&mut self) -> Result<()> { pub async fn listen(&mut self) -> Result<()> {
let acme = &mut self.acme;
let firewall = &mut self.firewall; let firewall = &mut self.firewall;
let igd = &mut self.igd; let igd = &mut self.igd;
try_join!( try_join!(
self.consul.listen(), self.consul.listen(),
async {
match acme {
Some(x) => x.listen().await,
None => Ok(())
}
},
async { async {
match firewall { match firewall {
Some(x) => x.listen().await, Some(x) => x.listen().await,

View file

@ -1,4 +1,4 @@
use std::collections::HashSet; // use std::collections::HashSet;
use anyhow::{Result,Context}; use anyhow::{Result,Context};
use iptables; 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<messages::PublicExposedPorts> { pub fn get_opened_ports(ipt: &iptables::IPTables) -> Result<messages::PublicExposedPorts> {
let mut ports = messages::PublicExposedPorts { let mut ports = messages::PublicExposedPorts::new();
tcp_ports: HashSet::new(), // let mut ports = messages::PublicExposedPorts {
udp_ports: HashSet::new() // tcp_ports: HashSet::new(),
}; // udp_ports: HashSet::new()
// };
let list = ipt.list("filter", "DIPLONAT")?; 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")?; let re = Regex::new(r"\-A.*? \-p (\w+).*\-\-dport (\d+).*?\-j ACCEPT").context("Regex matching open ports encountered an unexpected rule")?;

View file

@ -73,7 +73,8 @@ impl FirewallActor {
let ports_to_open = messages::PublicExposedPorts { let ports_to_open = messages::PublicExposedPorts {
tcp_ports: diff_tcp, tcp_ports: diff_tcp,
udp_ports: diff_udp udp_ports: diff_udp,
acme: HashSet::new()
}; };
fw::open_ports(&self.ipt, ports_to_open)?; fw::open_ports(&self.ipt, ports_to_open)?;

View file

@ -1,3 +1,4 @@
mod acme_actor;
mod config; mod config;
mod consul; mod consul;
mod consul_actor; mod consul_actor;

View file

@ -3,14 +3,16 @@ use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicExposedPorts { pub struct PublicExposedPorts {
pub tcp_ports: HashSet<u16>, pub tcp_ports: HashSet<u16>,
pub udp_ports: HashSet<u16> pub udp_ports: HashSet<u16>,
pub acme: HashSet<String>
} }
impl PublicExposedPorts { impl PublicExposedPorts {
pub fn new() -> Self { pub fn new() -> Self {
return Self { return Self {
tcp_ports: HashSet::new(), tcp_ports: HashSet::new(),
udp_ports: HashSet::new() udp_ports: HashSet::new(),
acme: HashSet::new()
} }
} }
} }