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]
pub email: Option<String>,
/// Refresh time for firewall rules [default: 300]
pub refresh_time: Option<u16>,
}
/// Firewall configuration options

View File

@ -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,
}))
}
}

View File

@ -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<u16>),
udp_port(HashSet<u16>)
TcpPort(HashSet<u16>),
UdpPort(HashSet<u16>),
Acme(HashSet<String>)
}
#[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 {
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<DiplonatConsul>) -> 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),

View File

@ -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<AcmeActor>,
firewall: Option<FirewallActor>,
igd: Option<IgdActor>,
}
@ -16,10 +19,15 @@ pub struct Diplonat {
impl Diplonat {
pub async fn new() -> Result<Self> {
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,

View File

@ -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<messages::PublicExposedPorts> {
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")?;

View File

@ -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)?;

View File

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

View File

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