From 647ec7ca3e6ed3b1128f5884fc835f71ca8ec103 Mon Sep 17 00:00:00 2001 From: Jake McGinty Date: Mon, 14 Jun 2021 18:15:31 +0900 Subject: [PATCH] shared: proactively create invite file to ensure we have permission This won't clean up an empty file if a later step fails, but this is still better than the previous solution. Closes #91 --- client/src/main.rs | 10 ++++--- server/src/main.rs | 8 +++--- shared/src/interface_config.rs | 48 +++++++++++++++------------------- shared/src/prompts.rs | 38 +++++++++++++-------------- 4 files changed, 51 insertions(+), 53 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index ba2ed3d..af23bf9 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -614,21 +614,23 @@ fn add_peer(interface: &InterfaceName, opts: AddPeerOpts) -> Result<(), Error> { let peers: Vec = api.http("GET", "/admin/peers")?; let cidr_tree = CidrTree::new(&cidrs[..]); - if let Some((peer_request, keypair)) = prompts::add_peer(&peers, &cidr_tree, &opts)? { + if let Some(result) = prompts::add_peer(&peers, &cidr_tree, &opts)? { + let (peer_request, keypair, target_path, mut target_file) = result; log::info!("Creating peer..."); let peer: Peer = api.http_form("POST", "/admin/peers", peer_request)?; let server_peer = peers.iter().find(|p| p.id == 1).unwrap(); - prompts::save_peer_invitation( + prompts::write_peer_invitation( + &mut target_file, + &target_path, interface, &peer, server_peer, &cidr_tree, keypair, &server.internal_endpoint, - &opts.save_config, )?; } else { - log::info!("exited without creating peer."); + log::info!("Exited without creating peer."); } Ok(()) diff --git a/server/src/main.rs b/server/src/main.rs index 14a86a8..038209e 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -293,7 +293,8 @@ 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, &opts)? { + if let Some(result) = shared::prompts::add_peer(&peers, &cidr_tree, &opts)? { + let (peer_request, keypair, target_path, mut target_file) = result; let peer = DatabasePeer::create(&conn, peer_request)?; if cfg!(not(test)) && Device::get(interface, network.backend).is_ok() { // Update the current WireGuard interface with the new peers. @@ -306,14 +307,15 @@ fn add_peer( } let server_peer = DatabasePeer::get(&conn, 1)?; - prompts::save_peer_invitation( + prompts::write_peer_invitation( + &mut target_file, + &target_path, interface, &peer, &*server_peer, &cidr_tree, keypair, &SocketAddr::new(config.address, config.listen_port), - &opts.save_config, )?; } else { println!("exited without creating peer."); diff --git a/shared/src/interface_config.rs b/shared/src/interface_config.rs index a5004ba..a01be4e 100644 --- a/shared/src/interface_config.rs +++ b/shared/src/interface_config.rs @@ -1,15 +1,8 @@ -use crate::{ensure_dirs_exist, Endpoint, Error, IoErrorContext, CLIENT_CONFIG_DIR}; -use colored::*; +use crate::{CLIENT_CONFIG_DIR, Endpoint, Error, IoErrorContext, WrappedIoError, ensure_dirs_exist}; use indoc::writedoc; use ipnetwork::IpNetwork; use serde::{Deserialize, Serialize}; -use std::{ - fs::{File, OpenOptions}, - io::Write, - net::SocketAddr, - os::unix::fs::PermissionsExt, - path::{Path, PathBuf}, -}; +use std::{fs::{File, OpenOptions}, io::{self, Write}, net::SocketAddr, os::unix::fs::PermissionsExt, path::{Path, PathBuf}}; use wgctrl::InterfaceName; #[derive(Clone, Deserialize, Serialize, Debug)] @@ -53,26 +46,13 @@ pub struct ServerInfo { } impl InterfaceConfig { - pub fn write_to_path>( + pub fn write_to( &self, - path: P, + target_file: &mut File, comments: bool, mode: Option, - ) -> Result<(), Error> { - let path = path.as_ref(); - let mut target_file = OpenOptions::new() - .create_new(true) - .write(true) - .open(path) - .with_path(path)?; + ) -> Result<(), io::Error> { if let Some(val) = mode { - if crate::chmod(&target_file, 0o600)? { - println!( - "{} updated permissions for {} to 0600.", - "[!]".yellow(), - path.display() - ); - } let metadata = target_file.metadata()?; let mut permissions = metadata.permissions(); permissions.set_mode(val); @@ -96,11 +76,25 @@ impl InterfaceConfig { )?; } target_file - .write_all(toml::to_string(self).unwrap().as_bytes()) - .with_path(path)?; + .write_all(toml::to_string(self).unwrap().as_bytes())?; Ok(()) } + pub fn write_to_path>( + &self, + path: P, + comments: bool, + mode: Option, + ) -> Result<(), WrappedIoError> { + let path = path.as_ref(); + let mut target_file = OpenOptions::new() + .create_new(true) + .write(true) + .open(path) + .with_path(path)?; + self.write_to(&mut target_file, comments, mode).with_path(path) + } + /// Overwrites the config file if it already exists. pub fn write_to_interface(&self, interface: &InterfaceName) -> Result { let path = Self::build_config_file_path(interface)?; diff --git a/shared/src/prompts.rs b/shared/src/prompts.rs index 945a7c0..3f92f63 100644 --- a/shared/src/prompts.rs +++ b/shared/src/prompts.rs @@ -9,13 +9,7 @@ use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select}; use ipnetwork::IpNetwork; use lazy_static::lazy_static; use publicip::Preference; -use std::{ - fmt::{Debug, Display}, - io, - net::SocketAddr, - str::FromStr, - time::SystemTime, -}; +use std::{fmt::{Debug, Display}, fs::{File, OpenOptions}, io, net::SocketAddr, str::FromStr, time::SystemTime}; use wgctrl::{InterfaceName, KeyPair}; lazy_static! { @@ -206,7 +200,7 @@ pub fn add_peer( peers: &[Peer], cidr_tree: &CidrTree, args: &AddPeerOpts, -) -> Result, Error> { +) -> Result, Error> { let leaves = cidr_tree.leaves(); let cidr = if let Some(ref parent_name) = args.cidr { @@ -255,6 +249,12 @@ pub fn add_peer( input("Invite expires after", Prefill::Default("14d".parse().map_err(|s: &str| anyhow!(s))?))? }; + let invite_save_path = if let Some(ref location) = args.save_config { + location.clone() + } else { + input("Save peer invitation file to", Prefill::Default(format!("{}.toml", name)))? + }; + let default_keypair = KeyPair::generate(); let peer_request = PeerContents { name, @@ -271,7 +271,12 @@ pub fn add_peer( Ok( if args.yes || confirm(&format!("Create peer {}?", peer_request.name.yellow()))? { - Some((peer_request, default_keypair)) + let invite_file = OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .open(&invite_save_path)?; + Some((peer_request, default_keypair, invite_save_path, invite_file)) } else { None }, @@ -360,14 +365,15 @@ pub fn enable_or_disable_peer(peers: &[Peer], enable: bool) -> Result, ) -> Result<(), Error> { let peer_invitation = InterfaceConfig { interface: InterfaceInfo { @@ -386,13 +392,7 @@ pub fn save_peer_invitation( }, }; - let invitation_save_path = if let Some(location) = config_location { - location.clone() - } else { - input("Save peer invitation file as", Prefill::Default(format!("{}.toml", peer.name)))? - }; - - peer_invitation.write_to_path(&invitation_save_path, true, None)?; + peer_invitation.write_to(target_file, true, None)?; println!( "\nPeer \"{}\" added\n\ @@ -400,7 +400,7 @@ pub fn save_peer_invitation( Please send it to them securely (eg. via magic-wormhole) \ to bootstrap them onto the network.", peer.name.bold(), - invitation_save_path.bold() + target_path.bold() ); Ok(())