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
pull/121/head
Jake McGinty 2021-06-14 18:15:31 +09:00
parent 8bd7b6e283
commit 647ec7ca3e
4 changed files with 51 additions and 53 deletions

View File

@ -614,21 +614,23 @@ fn add_peer(interface: &InterfaceName, opts: AddPeerOpts) -> 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, &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(())

View File

@ -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.");

View File

@ -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<P: AsRef<Path>>(
pub fn write_to(
&self,
path: P,
target_file: &mut File,
comments: bool,
mode: Option<u32>,
) -> 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<P: AsRef<Path>>(
&self,
path: P,
comments: bool,
mode: Option<u32>,
) -> 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<PathBuf, Error> {
let path = Self::build_config_file_path(interface)?;

View File

@ -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<Option<(PeerContents, KeyPair)>, Error> {
) -> Result<Option<(PeerContents, KeyPair, String, File)>, 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<Option<Pee
}
/// Confirm and write a innernet invitation file after a peer has been created.
pub fn save_peer_invitation(
pub fn write_peer_invitation(
target_file: &mut File,
target_path: &str,
network_name: &InterfaceName,
peer: &Peer,
server_peer: &Peer,
root_cidr: &Cidr,
keypair: KeyPair,
server_api_addr: &SocketAddr,
config_location: &Option<String>,
) -> 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(())