parent
a1818d9618
commit
6d28e7f4ab
|
@ -1165,6 +1165,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"regex",
|
||||
"serde",
|
||||
"structopt",
|
||||
"toml",
|
||||
"ureq",
|
||||
"wgctrl",
|
||||
|
|
|
@ -3,9 +3,9 @@ use dialoguer::{Confirm, Input};
|
|||
use hostsfile::HostsBuilder;
|
||||
use indoc::printdoc;
|
||||
use shared::{
|
||||
interface_config::InterfaceConfig, prompts, Association, AssociationContents, Cidr, CidrTree,
|
||||
EndpointContents, Interface, IoErrorContext, Peer, RedeemContents, State, CLIENT_CONFIG_PATH,
|
||||
REDEEM_TRANSITION_WAIT,
|
||||
interface_config::InterfaceConfig, prompts, AddCidrContents, AddPeerContents, Association,
|
||||
AssociationContents, Cidr, CidrTree, EndpointContents, Interface, IoErrorContext, Peer,
|
||||
RedeemContents, State, CLIENT_CONFIG_PATH, REDEEM_TRANSITION_WAIT,
|
||||
};
|
||||
use std::{
|
||||
fmt,
|
||||
|
@ -103,10 +103,20 @@ enum Command {
|
|||
Down { interface: Interface },
|
||||
|
||||
/// Add a new peer.
|
||||
AddPeer { interface: Interface },
|
||||
AddPeer {
|
||||
interface: Interface,
|
||||
|
||||
#[structopt(flatten)]
|
||||
args: AddPeerContents,
|
||||
},
|
||||
|
||||
/// Add a new CIDR.
|
||||
AddCidr { interface: Interface },
|
||||
AddCidr {
|
||||
interface: Interface,
|
||||
|
||||
#[structopt(flatten)]
|
||||
args: AddCidrContents,
|
||||
},
|
||||
|
||||
/// Disable an enabled peer.
|
||||
DisablePeer { interface: Interface },
|
||||
|
@ -463,13 +473,13 @@ fn uninstall(interface: &InterfaceName) -> Result<(), Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn add_cidr(interface: &InterfaceName) -> Result<(), Error> {
|
||||
fn add_cidr(interface: &InterfaceName, args: AddCidrContents) -> Result<(), Error> {
|
||||
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
||||
println!("Fetching CIDRs");
|
||||
let api = Api::new(&server);
|
||||
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
||||
|
||||
let cidr_request = prompts::add_cidr(&cidrs)?;
|
||||
let cidr_request = prompts::add_cidr(&cidrs, &args)?;
|
||||
|
||||
println!("Creating CIDR...");
|
||||
let cidr: Cidr = api.http_form("POST", "/admin/cidrs", cidr_request)?;
|
||||
|
@ -489,7 +499,7 @@ fn add_cidr(interface: &InterfaceName) -> Result<(), Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn add_peer(interface: &InterfaceName) -> Result<(), Error> {
|
||||
fn add_peer(interface: &InterfaceName, args: AddPeerContents) -> Result<(), Error> {
|
||||
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
||||
let api = Api::new(&server);
|
||||
|
||||
|
@ -499,7 +509,7 @@ fn add_peer(interface: &InterfaceName) -> Result<(), Error> {
|
|||
let peers: Vec<Peer> = api.http("GET", "/admin/peers")?;
|
||||
let cidr_tree = CidrTree::new(&cidrs[..]);
|
||||
|
||||
if let Some((peer_request, keypair)) = prompts::add_peer(&peers, &cidr_tree)? {
|
||||
if let Some((peer_request, keypair)) = prompts::add_peer(&peers, &cidr_tree, &args)? {
|
||||
println!("Creating peer...");
|
||||
let peer: Peer = api.http_form("POST", "/admin/peers", peer_request)?;
|
||||
let server_peer = peers.iter().find(|p| p.id == 1).unwrap();
|
||||
|
@ -510,6 +520,7 @@ fn add_peer(interface: &InterfaceName) -> Result<(), Error> {
|
|||
&cidr_tree,
|
||||
keypair,
|
||||
&server.internal_endpoint,
|
||||
&args.save_config,
|
||||
)?;
|
||||
} else {
|
||||
println!("exited without creating peer.");
|
||||
|
@ -838,8 +849,8 @@ fn run(opt: Opt) -> Result<(), Error> {
|
|||
)?,
|
||||
Command::Down { interface } => wg::down(&interface)?,
|
||||
Command::Uninstall { interface } => uninstall(&interface)?,
|
||||
Command::AddPeer { interface } => add_peer(&interface)?,
|
||||
Command::AddCidr { interface } => add_cidr(&interface)?,
|
||||
Command::AddPeer { interface, args } => add_peer(&interface, args)?,
|
||||
Command::AddCidr { interface, args } => add_cidr(&interface, args)?,
|
||||
Command::DisablePeer { interface } => enable_or_disable_peer(&interface, false)?,
|
||||
Command::EnablePeer { interface } => enable_or_disable_peer(&interface, true)?,
|
||||
Command::AddAssociation { interface } => add_association(&interface)?,
|
||||
|
|
|
@ -2,8 +2,8 @@ use crate::{ClientError, Error};
|
|||
use colored::*;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use shared::{interface_config::ServerInfo, INNERNET_PUBKEY_HEADER};
|
||||
use ureq::{Agent, AgentBuilder};
|
||||
use std::time::Duration;
|
||||
use ureq::{Agent, AgentBuilder};
|
||||
|
||||
pub fn human_duration(duration: Duration) -> String {
|
||||
match duration.as_secs() {
|
||||
|
@ -81,7 +81,9 @@ impl<'a> Api<'a> {
|
|||
endpoint: &str,
|
||||
form: Option<S>,
|
||||
) -> Result<T, Error> {
|
||||
let request = self.agent.request(
|
||||
let request = self
|
||||
.agent
|
||||
.request(
|
||||
verb,
|
||||
&format!("http://{}/v1{}", self.server.internal_endpoint, endpoint),
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ use ipnetwork::IpNetwork;
|
|||
use parking_lot::Mutex;
|
||||
use rusqlite::Connection;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use shared::{IoErrorContext, INNERNET_PUBKEY_HEADER};
|
||||
use shared::{AddCidrContents, AddPeerContents, IoErrorContext, INNERNET_PUBKEY_HEADER};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
env,
|
||||
|
@ -60,10 +60,20 @@ enum Command {
|
|||
Serve { interface: Interface },
|
||||
|
||||
/// Add a peer to an existing network.
|
||||
AddPeer { interface: Interface },
|
||||
AddPeer {
|
||||
interface: Interface,
|
||||
|
||||
#[structopt(flatten)]
|
||||
args: AddPeerContents,
|
||||
},
|
||||
|
||||
/// Add a new CIDR to an existing network.
|
||||
AddCidr { interface: Interface },
|
||||
AddCidr {
|
||||
interface: Interface,
|
||||
|
||||
#[structopt(flatten)]
|
||||
args: AddCidrContents,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Db = Arc<Mutex<Connection>>;
|
||||
|
@ -201,8 +211,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
},
|
||||
Command::Uninstall { interface } => uninstall(&interface, &conf)?,
|
||||
Command::Serve { interface } => serve(&interface, &conf).await?,
|
||||
Command::AddPeer { interface } => add_peer(&interface, &conf)?,
|
||||
Command::AddCidr { interface } => add_cidr(&interface, &conf)?,
|
||||
Command::AddPeer { interface, args } => add_peer(&interface, &conf, args)?,
|
||||
Command::AddCidr { interface, args } => add_cidr(&interface, &conf, args)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -224,7 +234,11 @@ fn open_database_connection(
|
|||
Ok(Connection::open(&database_path)?)
|
||||
}
|
||||
|
||||
fn add_peer(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error> {
|
||||
fn add_peer(
|
||||
interface: &InterfaceName,
|
||||
conf: &ServerConfig,
|
||||
args: AddPeerContents,
|
||||
) -> Result<(), Error> {
|
||||
let config = ConfigFile::from_file(conf.config_path(interface))?;
|
||||
let conn = open_database_connection(interface, conf)?;
|
||||
let peers = DatabasePeer::list(&conn)?
|
||||
|
@ -234,7 +248,7 @@ fn add_peer(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error>
|
|||
let cidrs = DatabaseCidr::list(&conn)?;
|
||||
let cidr_tree = CidrTree::new(&cidrs[..]);
|
||||
|
||||
if let Some((peer_request, keypair)) = shared::prompts::add_peer(&peers, &cidr_tree)? {
|
||||
if let Some((peer_request, keypair)) = shared::prompts::add_peer(&peers, &cidr_tree, &args)? {
|
||||
let peer = DatabasePeer::create(&conn, peer_request)?;
|
||||
if cfg!(not(test)) && DeviceInfo::get_by_name(interface).is_ok() {
|
||||
// Update the current WireGuard interface with the new peers.
|
||||
|
@ -254,6 +268,7 @@ fn add_peer(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error>
|
|||
&cidr_tree,
|
||||
keypair,
|
||||
&SocketAddr::new(config.address, config.listen_port),
|
||||
&args.save_config,
|
||||
)?;
|
||||
} else {
|
||||
println!("exited without creating peer.");
|
||||
|
@ -262,10 +277,14 @@ fn add_peer(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error>
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn add_cidr(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error> {
|
||||
fn add_cidr(
|
||||
interface: &InterfaceName,
|
||||
conf: &ServerConfig,
|
||||
args: AddCidrContents,
|
||||
) -> Result<(), Error> {
|
||||
let conn = open_database_connection(interface, conf)?;
|
||||
let cidrs = DatabaseCidr::list(&conn)?;
|
||||
if let Some(cidr_request) = shared::prompts::add_cidr(&cidrs)? {
|
||||
if let Some(cidr_request) = shared::prompts::add_cidr(&cidrs, &args)? {
|
||||
let cidr = DatabaseCidr::create(&conn, cidr_request)?;
|
||||
printdoc!(
|
||||
"
|
||||
|
|
|
@ -14,6 +14,7 @@ ipnetwork = { git = "https://github.com/mcginty/ipnetwork" } # pending https://g
|
|||
lazy_static = "1"
|
||||
regex = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
structopt = "0.3"
|
||||
toml = "0.5"
|
||||
ureq = { version = "2", default-features = false }
|
||||
wgctrl = { path = "../wgctrl-rs" }
|
||||
|
|
|
@ -40,7 +40,11 @@ pub fn ensure_dirs_exist(dirs: &[&Path]) -> Result<(), Error> {
|
|||
_ => {
|
||||
let target_file = File::open(dir).with_path(dir)?;
|
||||
if chmod(&target_file, 0o700)? {
|
||||
println!("{} updated permissions for {} to 0700.", "[!]".yellow(), dir.display());
|
||||
println!(
|
||||
"{} updated permissions for {} to 0700.",
|
||||
"[!]".yellow(),
|
||||
dir.display()
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo},
|
||||
Association, Cidr, CidrContents, CidrTree, Error, Peer, PeerContents,
|
||||
PERSISTENT_KEEPALIVE_INTERVAL_SECS,
|
||||
AddCidrContents, AddPeerContents, Association, Cidr, CidrContents, CidrTree, Error, Peer,
|
||||
PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
|
||||
};
|
||||
use colored::*;
|
||||
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
|
||||
|
@ -32,10 +32,27 @@ pub fn hostname_validator(name: &String) -> Result<(), &'static str> {
|
|||
}
|
||||
|
||||
/// Bring up a prompt to create a new CIDR. Returns the peer request.
|
||||
pub fn add_cidr(cidrs: &[Cidr]) -> Result<Option<CidrContents>, Error> {
|
||||
let parent_cidr = choose_cidr(cidrs, "Parent CIDR")?;
|
||||
let name: String = Input::with_theme(&*THEME).with_prompt("Name").interact()?;
|
||||
let cidr: IpNetwork = Input::with_theme(&*THEME).with_prompt("CIDR").interact()?;
|
||||
pub fn add_cidr(cidrs: &[Cidr], request: &AddCidrContents) -> Result<Option<CidrContents>, Error> {
|
||||
let parent_cidr = if let Some(ref parent_name) = request.parent {
|
||||
cidrs
|
||||
.iter()
|
||||
.find(|cidr| &cidr.name == parent_name)
|
||||
.ok_or("No parent CIDR with that name exists.")?
|
||||
} else {
|
||||
choose_cidr(cidrs, "Parent CIDR")?
|
||||
};
|
||||
|
||||
let name = if let Some(ref name) = request.name {
|
||||
name.clone()
|
||||
} else {
|
||||
Input::with_theme(&*THEME).with_prompt("Name").interact()?
|
||||
};
|
||||
|
||||
let cidr = if let Some(cidr) = request.cidr {
|
||||
cidr
|
||||
} else {
|
||||
Input::with_theme(&*THEME).with_prompt("CIDR").interact()?
|
||||
};
|
||||
|
||||
let cidr_request = CidrContents {
|
||||
name,
|
||||
|
@ -44,7 +61,8 @@ pub fn add_cidr(cidrs: &[Cidr]) -> Result<Option<CidrContents>, Error> {
|
|||
};
|
||||
|
||||
Ok(
|
||||
if Confirm::with_theme(&*THEME)
|
||||
if request.force
|
||||
|| Confirm::with_theme(&*THEME)
|
||||
.with_prompt(&format!("Create CIDR \"{}\"?", cidr_request.name))
|
||||
.default(false)
|
||||
.interact()?
|
||||
|
@ -143,10 +161,18 @@ pub fn delete_association<'a>(
|
|||
pub fn add_peer(
|
||||
peers: &[Peer],
|
||||
cidr_tree: &CidrTree,
|
||||
args: &AddPeerContents,
|
||||
) -> Result<Option<(PeerContents, KeyPair)>, Error> {
|
||||
let leaves = cidr_tree.leaves();
|
||||
|
||||
let cidr = choose_cidr(&leaves[..], "Eligible CIDRs for peer")?;
|
||||
let cidr = if let Some(ref parent_name) = args.cidr {
|
||||
leaves
|
||||
.iter()
|
||||
.find(|cidr| &cidr.name == parent_name)
|
||||
.ok_or("No eligible CIDR with that name exists.")?
|
||||
} else {
|
||||
choose_cidr(&leaves[..], "Eligible CIDRs for peer")?
|
||||
};
|
||||
|
||||
let mut available_ip = None;
|
||||
let candidate_ips = cidr.iter().filter(|ip| cidr.is_assignable(*ip));
|
||||
|
@ -159,20 +185,35 @@ pub fn add_peer(
|
|||
|
||||
let available_ip = available_ip.expect("No IPs in this CIDR are avavilable");
|
||||
|
||||
let ip = Input::with_theme(&*THEME)
|
||||
let ip = if let Some(ip) = args.ip {
|
||||
ip
|
||||
} else if args.auto_ip {
|
||||
available_ip
|
||||
} else {
|
||||
Input::with_theme(&*THEME)
|
||||
.with_prompt("IP")
|
||||
.default(available_ip)
|
||||
.interact()?;
|
||||
.interact()?
|
||||
};
|
||||
|
||||
let name: String = Input::with_theme(&*THEME)
|
||||
let name = if let Some(ref name) = args.name {
|
||||
name.clone()
|
||||
} else {
|
||||
Input::with_theme(&*THEME)
|
||||
.with_prompt("Name")
|
||||
.validate_with(hostname_validator)
|
||||
.interact()?;
|
||||
.interact()?
|
||||
};
|
||||
|
||||
let is_admin = Confirm::with_theme(&*THEME)
|
||||
let is_admin = if let Some(is_admin) = args.admin {
|
||||
is_admin
|
||||
} else {
|
||||
Confirm::with_theme(&*THEME)
|
||||
.with_prompt(&format!("Make {} an admin?", name))
|
||||
.default(false)
|
||||
.interact()?;
|
||||
.interact()?
|
||||
};
|
||||
|
||||
let default_keypair = KeyPair::generate();
|
||||
let peer_request = PeerContents {
|
||||
name,
|
||||
|
@ -187,7 +228,8 @@ pub fn add_peer(
|
|||
};
|
||||
|
||||
Ok(
|
||||
if Confirm::with_theme(&*THEME)
|
||||
if args.force
|
||||
|| Confirm::with_theme(&*THEME)
|
||||
.with_prompt(&format!("Create peer {}?", peer_request.name.yellow()))
|
||||
.default(false)
|
||||
.interact()?
|
||||
|
@ -245,6 +287,7 @@ pub fn save_peer_invitation(
|
|||
root_cidr: &Cidr,
|
||||
keypair: KeyPair,
|
||||
server_api_addr: &SocketAddr,
|
||||
config_location: &Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
let peer_invitation = InterfaceConfig {
|
||||
interface: InterfaceInfo {
|
||||
|
@ -262,10 +305,14 @@ pub fn save_peer_invitation(
|
|||
},
|
||||
};
|
||||
|
||||
let invitation_save_path = Input::with_theme(&*THEME)
|
||||
let invitation_save_path = if let Some(location) = config_location {
|
||||
location.clone()
|
||||
} else {
|
||||
Input::with_theme(&*THEME)
|
||||
.with_prompt("Save peer invitation file as")
|
||||
.default(format!("{}.toml", peer.name))
|
||||
.interact()?;
|
||||
.interact()?
|
||||
};
|
||||
|
||||
peer_invitation.write_to_path(&invitation_save_path, true, None)?;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use ipnetwork::IpNetwork;
|
||||
use crate::prompts::hostname_validator;
|
||||
use ipnetwork::IpNetwork;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
|
@ -8,6 +8,7 @@ use std::{
|
|||
path::Path,
|
||||
str::FromStr,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use wgctrl::{InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -165,6 +166,51 @@ pub struct RedeemContents {
|
|||
pub public_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, StructOpt)]
|
||||
pub struct AddPeerContents {
|
||||
/// Name of new peer
|
||||
#[structopt(long)]
|
||||
pub name: Option<String>,
|
||||
|
||||
/// Specify desired IP of new peer (within parent CIDR)
|
||||
#[structopt(long, conflicts_with = "auto-ip")]
|
||||
pub ip: Option<IpAddr>,
|
||||
|
||||
/// Auto-assign the peer the first available IP within the CIDR
|
||||
#[structopt(long = "auto-ip")]
|
||||
pub auto_ip: bool,
|
||||
|
||||
/// Name of CIDR to add new peer under
|
||||
#[structopt(long)]
|
||||
pub cidr: Option<String>,
|
||||
|
||||
/// Make new peer an admin
|
||||
#[structopt(long)]
|
||||
pub admin: Option<bool>,
|
||||
|
||||
/// Force confirmation
|
||||
#[structopt(short, long)]
|
||||
pub force: bool,
|
||||
|
||||
#[structopt(long)]
|
||||
pub save_config: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, StructOpt)]
|
||||
pub struct AddCidrContents {
|
||||
#[structopt(long)]
|
||||
pub name: Option<String>,
|
||||
|
||||
#[structopt(long)]
|
||||
pub cidr: Option<IpNetwork>,
|
||||
|
||||
#[structopt(long)]
|
||||
pub parent: Option<String>,
|
||||
|
||||
#[structopt(short, long)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub struct PeerContents {
|
||||
pub name: String,
|
||||
|
|
|
@ -309,7 +309,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_interface_names() {
|
||||
assert_eq!("wg-01".parse::<InterfaceName>().unwrap().as_str_lossy(), "wg-01");
|
||||
assert_eq!(
|
||||
"wg-01".parse::<InterfaceName>().unwrap().as_str_lossy(),
|
||||
"wg-01"
|
||||
);
|
||||
assert!("longer-nul\0".parse::<InterfaceName>().is_err());
|
||||
|
||||
let invalid_names = &[
|
||||
|
|
Loading…
Reference in New Issue