shared: add types module
parent
e8790f3178
commit
05d78eb253
|
@ -1,25 +1,20 @@
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use ipnetwork::IpNetwork;
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prompts::hostname_validator;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Display, Formatter},
|
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io,
|
io,
|
||||||
net::{IpAddr, SocketAddr},
|
|
||||||
ops::Deref,
|
|
||||||
os::unix::fs::PermissionsExt,
|
os::unix::fs::PermissionsExt,
|
||||||
path::Path,
|
path::Path,
|
||||||
str::FromStr,
|
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use wgctrl::{InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder};
|
|
||||||
|
|
||||||
pub mod interface_config;
|
pub mod interface_config;
|
||||||
pub mod prompts;
|
pub mod prompts;
|
||||||
|
pub mod types;
|
||||||
pub mod wg;
|
pub mod wg;
|
||||||
|
|
||||||
|
pub use types::*;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref CLIENT_CONFIG_PATH: &'static Path = Path::new("/etc/innernet");
|
pub static ref CLIENT_CONFIG_PATH: &'static Path = Path::new("/etc/innernet");
|
||||||
pub static ref CLIENT_DATA_PATH: &'static Path = Path::new("/var/lib/innernet");
|
pub static ref CLIENT_DATA_PATH: &'static Path = Path::new("/var/lib/innernet");
|
||||||
|
@ -33,330 +28,6 @@ pub const INNERNET_PUBKEY_HEADER: &str = "X-Innernet-Server-Key";
|
||||||
|
|
||||||
pub type Error = Box<dyn std::error::Error>;
|
pub type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
pub trait IoErrorContext<T> {
|
|
||||||
fn with_path<P: AsRef<Path>>(self, path: P) -> Result<T, WrappedIoError>;
|
|
||||||
fn with_str<S: Into<String>>(self, context: S) -> Result<T, WrappedIoError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> IoErrorContext<T> for Result<T, std::io::Error> {
|
|
||||||
fn with_path<P: AsRef<Path>>(self, path: P) -> Result<T, WrappedIoError> {
|
|
||||||
self.with_str(path.as_ref().to_string_lossy())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_str<S: Into<String>>(self, context: S) -> Result<T, WrappedIoError> {
|
|
||||||
self.map_err(|e| WrappedIoError {
|
|
||||||
io_error: e,
|
|
||||||
context: context.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct WrappedIoError {
|
|
||||||
io_error: std::io::Error,
|
|
||||||
context: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for WrappedIoError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
write!(f, "{} - {}", self.context, self.io_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for WrappedIoError {}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Interface {
|
|
||||||
name: InterfaceName,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Interface {
|
|
||||||
type Err = String;
|
|
||||||
|
|
||||||
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
|
||||||
let name = name.to_string();
|
|
||||||
hostname_validator(&name)?;
|
|
||||||
let name = name
|
|
||||||
.parse()
|
|
||||||
.map_err(|e: InvalidInterfaceName| e.to_string())?;
|
|
||||||
Ok(Self { name })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Interface {
|
|
||||||
type Target = InterfaceName;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
#[serde(tag = "option", content = "content")]
|
|
||||||
pub enum EndpointContents {
|
|
||||||
Set(SocketAddr),
|
|
||||||
Unset,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<Option<SocketAddr>> for EndpointContents {
|
|
||||||
fn into(self) -> Option<SocketAddr> {
|
|
||||||
match self {
|
|
||||||
Self::Set(addr) => Some(addr),
|
|
||||||
Self::Unset => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Option<SocketAddr>> for EndpointContents {
|
|
||||||
fn from(option: Option<SocketAddr>) -> Self {
|
|
||||||
match option {
|
|
||||||
Some(addr) => Self::Set(addr),
|
|
||||||
None => Self::Unset,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct AssociationContents {
|
|
||||||
pub cidr_id_1: i64,
|
|
||||||
pub cidr_id_2: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct Association {
|
|
||||||
pub id: i64,
|
|
||||||
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub contents: AssociationContents,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Association {
|
|
||||||
type Target = AssociationContents;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.contents
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
|
||||||
pub struct CidrContents {
|
|
||||||
pub name: String,
|
|
||||||
pub cidr: IpNetwork,
|
|
||||||
pub parent: Option<i64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for CidrContents {
|
|
||||||
type Target = IpNetwork;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.cidr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
|
||||||
pub struct Cidr {
|
|
||||||
pub id: i64,
|
|
||||||
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub contents: CidrContents,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Cidr {
|
|
||||||
type Target = CidrContents;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.contents
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CidrTree<'a> {
|
|
||||||
cidrs: &'a [Cidr],
|
|
||||||
contents: &'a Cidr,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> std::ops::Deref for CidrTree<'a> {
|
|
||||||
type Target = Cidr;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.contents
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> CidrTree<'a> {
|
|
||||||
pub fn new(cidrs: &'a [Cidr]) -> Self {
|
|
||||||
let root = cidrs
|
|
||||||
.iter()
|
|
||||||
.min_by_key(|c| c.cidr.prefix())
|
|
||||||
.expect("failed to find root CIDR");
|
|
||||||
Self {
|
|
||||||
cidrs,
|
|
||||||
contents: root,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn children(&self) -> impl Iterator<Item = CidrTree> {
|
|
||||||
self.cidrs
|
|
||||||
.iter()
|
|
||||||
.filter(move |c| c.parent == Some(self.contents.id))
|
|
||||||
.map(move |c| Self {
|
|
||||||
cidrs: self.cidrs,
|
|
||||||
contents: c,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn leaves(&self) -> Vec<Cidr> {
|
|
||||||
let mut leaves = vec![];
|
|
||||||
for cidr in self.cidrs {
|
|
||||||
if !self.cidrs.iter().any(|c| c.parent == Some(cidr.id)) {
|
|
||||||
leaves.push(cidr.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
leaves
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
|
||||||
pub struct RedeemContents {
|
|
||||||
pub public_key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
|
||||||
pub struct PeerContents {
|
|
||||||
pub name: String,
|
|
||||||
pub ip: IpAddr,
|
|
||||||
pub cidr_id: i64,
|
|
||||||
pub public_key: String,
|
|
||||||
pub endpoint: Option<SocketAddr>,
|
|
||||||
pub persistent_keepalive_interval: Option<u16>,
|
|
||||||
pub is_admin: bool,
|
|
||||||
pub is_disabled: bool,
|
|
||||||
pub is_redeemed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
|
||||||
pub struct Peer {
|
|
||||||
pub id: i64,
|
|
||||||
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub contents: PeerContents,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Peer {
|
|
||||||
type Target = PeerContents;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.contents
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Peer {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{} ({})", &self.name, &self.public_key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct PeerDiff {
|
|
||||||
pub public_key: String,
|
|
||||||
pub endpoint: Option<SocketAddr>,
|
|
||||||
pub persistent_keepalive_interval: Option<u16>,
|
|
||||||
pub is_disabled: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Peer {
|
|
||||||
pub fn diff(&self, peer: &PeerConfig) -> Option<PeerDiff> {
|
|
||||||
assert_eq!(self.public_key, peer.public_key.to_base64());
|
|
||||||
|
|
||||||
let endpoint_diff = if peer.endpoint != self.endpoint {
|
|
||||||
self.endpoint
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let keepalive_diff =
|
|
||||||
if peer.persistent_keepalive_interval != self.persistent_keepalive_interval {
|
|
||||||
self.persistent_keepalive_interval
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if endpoint_diff.is_none() && keepalive_diff.is_none() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(PeerDiff {
|
|
||||||
public_key: self.public_key.clone(),
|
|
||||||
endpoint: endpoint_diff,
|
|
||||||
persistent_keepalive_interval: keepalive_diff,
|
|
||||||
is_disabled: self.is_disabled,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a Peer> for PeerConfigBuilder {
|
|
||||||
fn from(peer: &Peer) -> Self {
|
|
||||||
let builder = PeerConfigBuilder::new(&Key::from_base64(&peer.public_key).unwrap())
|
|
||||||
.replace_allowed_ips()
|
|
||||||
.add_allowed_ip(peer.ip, if peer.ip.is_ipv4() { 32 } else { 128 });
|
|
||||||
|
|
||||||
let builder = if peer.is_disabled {
|
|
||||||
builder.remove()
|
|
||||||
} else {
|
|
||||||
builder
|
|
||||||
};
|
|
||||||
|
|
||||||
let builder = if let Some(interval) = peer.persistent_keepalive_interval {
|
|
||||||
builder.set_persistent_keepalive_interval(interval)
|
|
||||||
} else {
|
|
||||||
builder
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(endpoint) = peer.endpoint {
|
|
||||||
builder.set_endpoint(endpoint)
|
|
||||||
} else {
|
|
||||||
builder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a PeerDiff> for PeerConfigBuilder {
|
|
||||||
fn from(peer: &PeerDiff) -> Self {
|
|
||||||
let builder = PeerConfigBuilder::new(&Key::from_base64(&peer.public_key).unwrap());
|
|
||||||
|
|
||||||
let builder = if peer.is_disabled {
|
|
||||||
builder.remove()
|
|
||||||
} else {
|
|
||||||
builder
|
|
||||||
};
|
|
||||||
|
|
||||||
let builder = if let Some(interval) = peer.persistent_keepalive_interval {
|
|
||||||
builder.set_persistent_keepalive_interval(interval)
|
|
||||||
} else {
|
|
||||||
builder
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(endpoint) = peer.endpoint {
|
|
||||||
builder.set_endpoint(endpoint)
|
|
||||||
} else {
|
|
||||||
builder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This model is sent as a response to the /state endpoint, and is meant
|
|
||||||
/// to include all the data a client needs to update its WireGuard interface.
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct State {
|
|
||||||
/// This list will be only the peers visible to the user requesting this
|
|
||||||
/// information, not including disabled peers or peers from other CIDRs
|
|
||||||
/// that the user's CIDR is not authorized to communicate with.
|
|
||||||
pub peers: Vec<Peer>,
|
|
||||||
|
|
||||||
/// At the moment, this is all CIDRs, regardless of whether the peer is
|
|
||||||
/// eligible to communicate with them or not.
|
|
||||||
pub cidrs: Vec<Cidr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static WG_MANAGE_DIR: &str = "/etc/innernet";
|
pub static WG_MANAGE_DIR: &str = "/etc/innernet";
|
||||||
pub static WG_DIR: &str = "/etc/wireguard";
|
pub static WG_DIR: &str = "/etc/wireguard";
|
||||||
|
|
||||||
|
@ -394,62 +65,3 @@ pub fn chmod(file: &File, new_mode: u32) -> Result<bool, Error> {
|
||||||
|
|
||||||
Ok(updated)
|
Ok(updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_peer_no_diff() {
|
|
||||||
const PUBKEY: &str = "4CNZorWVtohO64n6AAaH/JyFjIIgBFrfJK2SGtKjzEE=";
|
|
||||||
let ip: IpAddr = "10.0.0.1".parse().unwrap();
|
|
||||||
let peer = Peer {
|
|
||||||
id: 1,
|
|
||||||
contents: PeerContents {
|
|
||||||
name: "peer1".to_owned(),
|
|
||||||
ip,
|
|
||||||
cidr_id: 1,
|
|
||||||
public_key: PUBKEY.to_owned(),
|
|
||||||
endpoint: None,
|
|
||||||
persistent_keepalive_interval: None,
|
|
||||||
is_admin: false,
|
|
||||||
is_disabled: false,
|
|
||||||
is_redeemed: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let builder =
|
|
||||||
PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32);
|
|
||||||
|
|
||||||
let config = builder.into_peer_config();
|
|
||||||
|
|
||||||
assert_eq!(peer.diff(&config), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_peer_diff() {
|
|
||||||
const PUBKEY: &str = "4CNZorWVtohO64n6AAaH/JyFjIIgBFrfJK2SGtKjzEE=";
|
|
||||||
let ip: IpAddr = "10.0.0.1".parse().unwrap();
|
|
||||||
let peer = Peer {
|
|
||||||
id: 1,
|
|
||||||
contents: PeerContents {
|
|
||||||
name: "peer1".to_owned(),
|
|
||||||
ip,
|
|
||||||
cidr_id: 1,
|
|
||||||
public_key: PUBKEY.to_owned(),
|
|
||||||
endpoint: None,
|
|
||||||
persistent_keepalive_interval: Some(15),
|
|
||||||
is_admin: false,
|
|
||||||
is_disabled: false,
|
|
||||||
is_redeemed: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let builder =
|
|
||||||
PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32);
|
|
||||||
|
|
||||||
let config = builder.into_peer_config();
|
|
||||||
|
|
||||||
println!("{:?}", peer);
|
|
||||||
println!("{:?}", config);
|
|
||||||
assert!(matches!(peer.diff(&config), Some(_)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,396 @@
|
||||||
|
use ipnetwork::IpNetwork;
|
||||||
|
use crate::prompts::hostname_validator;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
net::{IpAddr, SocketAddr},
|
||||||
|
ops::Deref,
|
||||||
|
path::Path,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
use wgctrl::{InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Interface {
|
||||||
|
name: InterfaceName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Interface {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||||
|
let name = name.to_string();
|
||||||
|
hostname_validator(&name)?;
|
||||||
|
let name = name
|
||||||
|
.parse()
|
||||||
|
.map_err(|e: InvalidInterfaceName| e.to_string())?;
|
||||||
|
Ok(Self { name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Interface {
|
||||||
|
type Target = InterfaceName;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(tag = "option", content = "content")]
|
||||||
|
pub enum EndpointContents {
|
||||||
|
Set(SocketAddr),
|
||||||
|
Unset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Option<SocketAddr>> for EndpointContents {
|
||||||
|
fn into(self) -> Option<SocketAddr> {
|
||||||
|
match self {
|
||||||
|
Self::Set(addr) => Some(addr),
|
||||||
|
Self::Unset => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Option<SocketAddr>> for EndpointContents {
|
||||||
|
fn from(option: Option<SocketAddr>) -> Self {
|
||||||
|
match option {
|
||||||
|
Some(addr) => Self::Set(addr),
|
||||||
|
None => Self::Unset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct AssociationContents {
|
||||||
|
pub cidr_id_1: i64,
|
||||||
|
pub cidr_id_2: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct Association {
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub contents: AssociationContents,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Association {
|
||||||
|
type Target = AssociationContents;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub struct CidrContents {
|
||||||
|
pub name: String,
|
||||||
|
pub cidr: IpNetwork,
|
||||||
|
pub parent: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for CidrContents {
|
||||||
|
type Target = IpNetwork;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.cidr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub struct Cidr {
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub contents: CidrContents,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Cidr {
|
||||||
|
type Target = CidrContents;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CidrTree<'a> {
|
||||||
|
cidrs: &'a [Cidr],
|
||||||
|
contents: &'a Cidr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::ops::Deref for CidrTree<'a> {
|
||||||
|
type Target = Cidr;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CidrTree<'a> {
|
||||||
|
pub fn new(cidrs: &'a [Cidr]) -> Self {
|
||||||
|
let root = cidrs
|
||||||
|
.iter()
|
||||||
|
.min_by_key(|c| c.cidr.prefix())
|
||||||
|
.expect("failed to find root CIDR");
|
||||||
|
Self {
|
||||||
|
cidrs,
|
||||||
|
contents: root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children(&self) -> impl Iterator<Item = CidrTree> {
|
||||||
|
self.cidrs
|
||||||
|
.iter()
|
||||||
|
.filter(move |c| c.parent == Some(self.contents.id))
|
||||||
|
.map(move |c| Self {
|
||||||
|
cidrs: self.cidrs,
|
||||||
|
contents: c,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn leaves(&self) -> Vec<Cidr> {
|
||||||
|
let mut leaves = vec![];
|
||||||
|
for cidr in self.cidrs {
|
||||||
|
if !self.cidrs.iter().any(|c| c.parent == Some(cidr.id)) {
|
||||||
|
leaves.push(cidr.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leaves
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub struct RedeemContents {
|
||||||
|
pub public_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub struct PeerContents {
|
||||||
|
pub name: String,
|
||||||
|
pub ip: IpAddr,
|
||||||
|
pub cidr_id: i64,
|
||||||
|
pub public_key: String,
|
||||||
|
pub endpoint: Option<SocketAddr>,
|
||||||
|
pub persistent_keepalive_interval: Option<u16>,
|
||||||
|
pub is_admin: bool,
|
||||||
|
pub is_disabled: bool,
|
||||||
|
pub is_redeemed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub struct Peer {
|
||||||
|
pub id: i64,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub contents: PeerContents,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Peer {
|
||||||
|
type Target = PeerContents;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Peer {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} ({})", &self.name, &self.public_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct PeerDiff {
|
||||||
|
pub public_key: String,
|
||||||
|
pub endpoint: Option<SocketAddr>,
|
||||||
|
pub persistent_keepalive_interval: Option<u16>,
|
||||||
|
pub is_disabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Peer {
|
||||||
|
pub fn diff(&self, peer: &PeerConfig) -> Option<PeerDiff> {
|
||||||
|
assert_eq!(self.public_key, peer.public_key.to_base64());
|
||||||
|
|
||||||
|
let endpoint_diff = if peer.endpoint != self.endpoint {
|
||||||
|
self.endpoint
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let keepalive_diff =
|
||||||
|
if peer.persistent_keepalive_interval != self.persistent_keepalive_interval {
|
||||||
|
self.persistent_keepalive_interval
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if endpoint_diff.is_none() && keepalive_diff.is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(PeerDiff {
|
||||||
|
public_key: self.public_key.clone(),
|
||||||
|
endpoint: endpoint_diff,
|
||||||
|
persistent_keepalive_interval: keepalive_diff,
|
||||||
|
is_disabled: self.is_disabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a Peer> for PeerConfigBuilder {
|
||||||
|
fn from(peer: &Peer) -> Self {
|
||||||
|
let builder = PeerConfigBuilder::new(&Key::from_base64(&peer.public_key).unwrap())
|
||||||
|
.replace_allowed_ips()
|
||||||
|
.add_allowed_ip(peer.ip, if peer.ip.is_ipv4() { 32 } else { 128 });
|
||||||
|
|
||||||
|
let builder = if peer.is_disabled {
|
||||||
|
builder.remove()
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
|
||||||
|
let builder = if let Some(interval) = peer.persistent_keepalive_interval {
|
||||||
|
builder.set_persistent_keepalive_interval(interval)
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(endpoint) = peer.endpoint {
|
||||||
|
builder.set_endpoint(endpoint)
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a PeerDiff> for PeerConfigBuilder {
|
||||||
|
fn from(peer: &PeerDiff) -> Self {
|
||||||
|
let builder = PeerConfigBuilder::new(&Key::from_base64(&peer.public_key).unwrap());
|
||||||
|
|
||||||
|
let builder = if peer.is_disabled {
|
||||||
|
builder.remove()
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
|
||||||
|
let builder = if let Some(interval) = peer.persistent_keepalive_interval {
|
||||||
|
builder.set_persistent_keepalive_interval(interval)
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(endpoint) = peer.endpoint {
|
||||||
|
builder.set_endpoint(endpoint)
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model is sent as a response to the /state endpoint, and is meant
|
||||||
|
/// to include all the data a client needs to update its WireGuard interface.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct State {
|
||||||
|
/// This list will be only the peers visible to the user requesting this
|
||||||
|
/// information, not including disabled peers or peers from other CIDRs
|
||||||
|
/// that the user's CIDR is not authorized to communicate with.
|
||||||
|
pub peers: Vec<Peer>,
|
||||||
|
|
||||||
|
/// At the moment, this is all CIDRs, regardless of whether the peer is
|
||||||
|
/// eligible to communicate with them or not.
|
||||||
|
pub cidrs: Vec<Cidr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IoErrorContext<T> {
|
||||||
|
fn with_path<P: AsRef<Path>>(self, path: P) -> Result<T, WrappedIoError>;
|
||||||
|
fn with_str<S: Into<String>>(self, context: S) -> Result<T, WrappedIoError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IoErrorContext<T> for Result<T, std::io::Error> {
|
||||||
|
fn with_path<P: AsRef<Path>>(self, path: P) -> Result<T, WrappedIoError> {
|
||||||
|
self.with_str(path.as_ref().to_string_lossy())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_str<S: Into<String>>(self, context: S) -> Result<T, WrappedIoError> {
|
||||||
|
self.map_err(|e| WrappedIoError {
|
||||||
|
io_error: e,
|
||||||
|
context: context.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WrappedIoError {
|
||||||
|
io_error: std::io::Error,
|
||||||
|
context: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for WrappedIoError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
write!(f, "{} - {}", self.context, self.io_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for WrappedIoError {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use wgctrl::{Key, PeerConfigBuilder};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_peer_no_diff() {
|
||||||
|
const PUBKEY: &str = "4CNZorWVtohO64n6AAaH/JyFjIIgBFrfJK2SGtKjzEE=";
|
||||||
|
let ip: IpAddr = "10.0.0.1".parse().unwrap();
|
||||||
|
let peer = Peer {
|
||||||
|
id: 1,
|
||||||
|
contents: PeerContents {
|
||||||
|
name: "peer1".to_owned(),
|
||||||
|
ip,
|
||||||
|
cidr_id: 1,
|
||||||
|
public_key: PUBKEY.to_owned(),
|
||||||
|
endpoint: None,
|
||||||
|
persistent_keepalive_interval: None,
|
||||||
|
is_admin: false,
|
||||||
|
is_disabled: false,
|
||||||
|
is_redeemed: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let builder =
|
||||||
|
PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32);
|
||||||
|
|
||||||
|
let config = builder.into_peer_config();
|
||||||
|
|
||||||
|
assert_eq!(peer.diff(&config), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_peer_diff() {
|
||||||
|
const PUBKEY: &str = "4CNZorWVtohO64n6AAaH/JyFjIIgBFrfJK2SGtKjzEE=";
|
||||||
|
let ip: IpAddr = "10.0.0.1".parse().unwrap();
|
||||||
|
let peer = Peer {
|
||||||
|
id: 1,
|
||||||
|
contents: PeerContents {
|
||||||
|
name: "peer1".to_owned(),
|
||||||
|
ip,
|
||||||
|
cidr_id: 1,
|
||||||
|
public_key: PUBKEY.to_owned(),
|
||||||
|
endpoint: None,
|
||||||
|
persistent_keepalive_interval: Some(15),
|
||||||
|
is_admin: false,
|
||||||
|
is_disabled: false,
|
||||||
|
is_redeemed: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let builder =
|
||||||
|
PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32);
|
||||||
|
|
||||||
|
let config = builder.into_peer_config();
|
||||||
|
|
||||||
|
println!("{:?}", peer);
|
||||||
|
println!("{:?}", config);
|
||||||
|
assert!(matches!(peer.diff(&config), Some(_)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue