server: non-interactive network creation

pull/54/head
Jake McGinty 2021-04-18 01:12:15 +09:00
parent b92ad65b17
commit c4e369ee54
7 changed files with 272 additions and 66 deletions

162
Cargo.lock generated
View File

@ -89,6 +89,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -666,6 +672,15 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -686,9 +701,9 @@ checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
[[package]]
name = "libsqlite3-sys"
version = "0.20.1"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d31059f22935e6c31830db5249ba2b7ecd54fd73a9909286f0a67aa55c2fbd"
checksum = "2f6332d94daa84478d55a6aa9dbb3b305ed6500fb0cb9400cb9e1525d0e0e188"
dependencies = [
"pkg-config",
"vcpkg",
@ -1027,10 +1042,25 @@ dependencies = [
]
[[package]]
name = "rusqlite"
version = "0.24.2"
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38ee71cbab2c827ec0ac24e76f82eca723cee92c509a65f67dee393c25112"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rusqlite"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48381bf52627e7b0e02c4c0e4c0c88fc1cf2228a2eb7461d9499b1372399f1da"
dependencies = [
"bitflags",
"fallible-iterator",
@ -1047,6 +1077,19 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
"base64",
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "ryu"
version = "1.0.5"
@ -1065,6 +1108,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sct"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "serde"
version = "1.0.125"
@ -1137,6 +1190,7 @@ dependencies = [
"thiserror",
"tokio",
"toml",
"ureq",
"warp",
"wgctrl",
]
@ -1199,6 +1253,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strsim"
version = "0.8.0"
@ -1496,6 +1556,12 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "ureq"
version = "2.1.0"
@ -1506,9 +1572,12 @@ dependencies = [
"chunked_transfer",
"log",
"once_cell",
"rustls",
"serde",
"serde_json",
"url",
"webpki",
"webpki-roots",
]
[[package]]
@ -1583,6 +1652,89 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
[[package]]
name = "web-sys"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
dependencies = [
"webpki",
]
[[package]]
name = "wgctrl"
version = "1.1.0"

View File

@ -52,7 +52,8 @@ enum Command {
/// Install a new innernet config.
#[structopt(alias = "redeem")]
Install {
config: PathBuf,
/// Path to the invitation file
invite: PathBuf,
#[structopt(flatten)]
hosts: HostsOpt,
@ -64,9 +65,11 @@ enum Command {
/// Enumerate all innernet connections.
#[structopt(alias = "list")]
Show {
/// One-line peer list
#[structopt(short, long)]
short: bool,
/// Display peers in a tree based on the CIDRs
#[structopt(short, long)]
tree: bool,
@ -841,10 +844,10 @@ fn run(opt: Opt) -> Result<(), Error> {
match command {
Command::Install {
config,
invite,
hosts,
opts,
} => install(&config, hosts.into(), opts)?,
} => install(&invite, hosts.into(), opts)?,
Command::Show {
short,
tree,

View File

@ -21,18 +21,19 @@ hyper = "0.14"
indoc = "1"
ipnetwork = { git = "https://github.com/mcginty/ipnetwork" } # pending https://github.com/achanda/ipnetwork/pull/129
libc = "0.2"
libsqlite3-sys = "0.20"
libsqlite3-sys = "0.22"
log = "0.4"
parking_lot = "0.11"
pretty_env_logger = "0.4"
regex = { version = "1", default-features = false, features = ["std"] }
rusqlite = "0.24"
rusqlite = "0.25"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
shared = { path = "../shared" }
subtle = "2"
structopt = "0.3"
thiserror = "1"
ureq = "2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
toml = "0.5"
warp = { git = "https://github.com/tonarino/warp", default-features = false } # pending https://github.com/seanmonstar/warp/issues/830

View File

@ -20,9 +20,32 @@ fn create_database<P: AsRef<Path>>(
Ok(conn)
}
#[derive(Debug, Default, Clone, PartialEq, StructOpt)]
pub struct InitializeOpts {
/// The network name (ex: evilcorp)
#[structopt(long)]
pub network_name: Option<String>,
/// The network CIDR (ex: 10.42.0.0/16)
#[structopt(long)]
pub network_cidr: Option<IpNetwork>,
/// This server's external endpoint (ex: 100.100.100.100:51820)
#[structopt(long, conflicts_with = "auto-external-endpoint")]
pub external_endpoint: Option<SocketAddr>,
/// Auto-resolve external endpoint
#[structopt(long = "auto-external-endpoint")]
pub auto_external_endpoint: bool,
/// Port to listen on (for the WireGuard interface)
#[structopt(long)]
pub listen_port: Option<u16>,
}
struct DbInitData {
root_cidr_name: String,
root_cidr: IpNetwork,
network_name: String,
network_cidr: IpNetwork,
server_cidr: IpNetwork,
our_ip: IpAddr,
public_key_base64: String,
@ -35,8 +58,8 @@ fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(),
let root_cidr = DatabaseCidr::create(
&conn,
CidrContents {
name: db_init_data.root_cidr_name.clone(),
cidr: db_init_data.root_cidr,
name: db_init_data.network_name.clone(),
cidr: db_init_data.network_cidr,
parent: None,
},
)
@ -71,7 +94,7 @@ fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(),
Ok(())
}
pub fn init_wizard(conf: &ServerConfig) -> Result<(), Error> {
pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Error> {
let theme = ColorfulTheme::default();
shared::ensure_dirs_exist(&[conf.config_dir(), conf.database_dir()]).map_err(|_| {
@ -81,42 +104,57 @@ pub fn init_wizard(conf: &ServerConfig) -> Result<(), Error> {
)
})?;
let (name, root_cidr) = conf.root_cidr.clone().unwrap_or_else(|| {
println!("Please specify a root CIDR, which will define the entire network.");
let name: String = Input::with_theme(&theme)
let name: String = if let Some(name) = opts.network_name {
name.clone()
} else {
println!("Here you'll specify the network CIDR, which will encompass the entire network.");
Input::with_theme(&theme)
.with_prompt("Network name")
.validate_with(hostname_validator)
.interact()
.map_err(|_| println!("failed to get name."))
.unwrap();
.interact()?
};
let root_cidr: IpNetwork = Input::with_theme(&theme)
let root_cidr: IpNetwork = if let Some(cidr) = opts.network_cidr {
cidr.clone()
} else {
Input::with_theme(&theme)
.with_prompt("Network CIDR")
.with_initial_text("10.42.0.0/16")
.interact()
.map_err(|_| println!("failed to get cidr."))
.unwrap();
(name, root_cidr)
});
.interact()?
};
// This probably won't error because of the `hostname_validator` regex.
let name = name.parse()?;
let endpoint: SocketAddr = conf.endpoint.unwrap_or_else(|| {
prompts::ask_endpoint()
.map_err(|_| println!("failed to get endpoint."))
.unwrap()
});
let endpoint: SocketAddr = if let Some(endpoint) = opts.external_endpoint {
endpoint.clone()
} else {
let external_ip: Option<IpAddr> = ureq::get("http://4.icanhazip.com")
.call()
.ok()
.map(|res| res.into_string().ok())
.flatten()
.map(|body| body.trim().to_string())
.and_then(|body| body.parse().ok());
let listen_port: u16 = conf.listen_port.unwrap_or_else(|| {
if opts.auto_external_endpoint {
let ip = external_ip.ok_or("couldn't get external IP")?;
(ip, 51820).into()
} else {
prompts::ask_endpoint(external_ip)?
}
};
let listen_port: u16 = if let Some(listen_port) = opts.listen_port {
listen_port
} else {
Input::with_theme(&theme)
.with_prompt("Listen port")
.default(51820)
.interact()
.map_err(|_| println!("failed to get listen port."))
.unwrap()
});
.map_err(|_| "failed to get listen port.")?
};
let our_ip = root_cidr
.iter()
.find(|ip| root_cidr.is_assignable(*ip))
@ -134,8 +172,8 @@ pub fn init_wizard(conf: &ServerConfig) -> Result<(), Error> {
config.write_to_path(&config_path)?;
let db_init_data = DbInitData {
root_cidr_name: name.to_string(),
root_cidr,
network_name: name.to_string(),
network_cidr: root_cidr,
server_cidr,
our_ip,
public_key_base64: our_keypair.public.to_base64(),

View File

@ -37,6 +37,7 @@ pub use endpoints::Endpoints;
pub use error::ServerError;
use shared::{prompts, wg, CidrTree, Error, Interface, SERVER_CONFIG_DIR, SERVER_DATABASE_DIR};
pub use shared::{Association, AssociationContents};
use initialize::InitializeOpts;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -51,7 +52,10 @@ struct Opt {
enum Command {
/// Create a new network.
#[structopt(alias = "init")]
New,
New {
#[structopt(flatten)]
opts: InitializeOpts
},
/// Permanently uninstall a created network, rendering it unusable. Use with care.
Uninstall { interface: Interface },
@ -153,10 +157,6 @@ impl ConfigFile {
pub struct ServerConfig {
wg_manage_dir_override: Option<PathBuf>,
wg_dir_override: Option<PathBuf>,
root_cidr: Option<(String, IpNetwork)>,
endpoint: Option<SocketAddr>,
listen_port: Option<u16>,
noninteractive: bool,
}
impl ServerConfig {
@ -204,8 +204,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let conf = ServerConfig::default();
match opt.command {
Command::New => {
if let Err(e) = initialize::init_wizard(&conf) {
Command::New { opts } => {
if let Err(e) = initialize::init_wizard(&conf, opts) {
println!("{}: {}.", "creation failed".red(), e);
}
},
@ -237,7 +237,7 @@ fn open_database_connection(
fn add_peer(
interface: &InterfaceName,
conf: &ServerConfig,
args: AddPeerOpts,
opts: AddPeerOpts,
) -> Result<(), Error> {
let config = ConfigFile::from_file(conf.config_path(interface))?;
let conn = open_database_connection(interface, conf)?;
@ -248,7 +248,7 @@ fn add_peer(
let cidrs = DatabaseCidr::list(&conn)?;
let cidr_tree = CidrTree::new(&cidrs[..]);
if let Some((peer_request, keypair)) = shared::prompts::add_peer(&peers, &cidr_tree, &args)? {
if let Some((peer_request, keypair)) = shared::prompts::add_peer(&peers, &cidr_tree, &opts)? {
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.
@ -268,7 +268,7 @@ fn add_peer(
&cidr_tree,
keypair,
&SocketAddr::new(config.address, config.listen_port),
&args.save_config,
&opts.save_config,
)?;
} else {
println!("exited without creating peer.");
@ -280,11 +280,11 @@ fn add_peer(
fn add_cidr(
interface: &InterfaceName,
conf: &ServerConfig,
args: AddCidrOpts,
opts: AddCidrOpts,
) -> Result<(), Error> {
let conn = open_database_connection(interface, conf)?;
let cidrs = DatabaseCidr::list(&conn)?;
if let Some(cidr_request) = shared::prompts::add_cidr(&cidrs, &args)? {
if let Some(cidr_request) = shared::prompts::add_cidr(&cidrs, &opts)? {
let cidr = DatabaseCidr::create(&conn, cidr_request)?;
printdoc!(
"

View File

@ -2,7 +2,7 @@
use crate::{
db::{DatabaseCidr, DatabasePeer},
endpoints::Endpoints,
initialize::init_wizard,
initialize::{init_wizard, InitializeOpts},
Context, ServerConfig,
};
use anyhow::{anyhow, Result};
@ -64,12 +64,16 @@ impl Server {
let conf = ServerConfig {
wg_manage_dir_override: Some(test_dir_path.to_path_buf()),
wg_dir_override: Some(test_dir_path.to_path_buf()),
root_cidr: Some((interface.clone(), ROOT_CIDR.parse()?)),
endpoint: Some("155.155.155.155:54321".parse()?),
listen_port: Some(54321),
noninteractive: true,
};
init_wizard(&conf).map_err(|_| anyhow!("init_wizard failed"))?;
let opts = InitializeOpts {
network_name: Some(interface.clone()),
network_cidr: Some(ROOT_CIDR.parse()?),
external_endpoint: Some("155.155.155.155:54321".parse()?),
listen_port: Some(54321),
auto_external_endpoint: false,
};
init_wizard(&conf, opts).map_err(|_| anyhow!("init_wizard failed"))?;
let interface = interface.parse().unwrap();
// Add developer CIDR and user CIDR and some peers for testing.

View File

@ -362,16 +362,20 @@ pub fn set_listen_port(
}
}
pub fn ask_endpoint() -> Result<SocketAddr, Error> {
pub fn ask_endpoint(external_ip: Option<IpAddr>) -> Result<SocketAddr, Error> {
println!("getting external IP address.");
let external_ip: Option<IpAddr> = ureq::get("http://4.icanhazip.com")
.call()
.ok()
.map(|res| res.into_string().ok())
.flatten()
.map(|body| body.trim().to_string())
.and_then(|body| body.parse().ok());
let external_ip = if external_ip.is_some() {
external_ip
} else {
ureq::get("http://4.icanhazip.com")
.call()
.ok()
.map(|res| res.into_string().ok())
.flatten()
.map(|body| body.trim().to_string())
.and_then(|body| body.parse().ok())
};
let mut endpoint_builder = Input::with_theme(&*THEME);
if let Some(ip) = external_ip {
@ -386,7 +390,11 @@ pub fn ask_endpoint() -> Result<SocketAddr, Error> {
}
pub fn override_endpoint(unset: bool) -> Result<Option<Option<SocketAddr>>, Error> {
let endpoint = if !unset { Some(ask_endpoint()?) } else { None };
let endpoint = if !unset {
Some(ask_endpoint(None)?)
} else {
None
};
Ok(
if Confirm::with_theme(&*THEME)