client, server: adds ability to rename peers (#92)

This commit adds a subcommand to both the client and server to allow
changing the name of a peer. The peer retains all the same attributes as
before (public keys, IPs, admin/disabled status, etc.).

Closes #87
pull/96/head
Kevin K 2021-05-25 06:58:00 -04:00 committed by GitHub
parent 4226278e5a
commit ec210f9468
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 150 additions and 4 deletions

View File

@ -5,8 +5,8 @@ use indoc::eprintdoc;
use shared::{ use shared::{
interface_config::InterfaceConfig, prompts, AddAssociationOpts, AddCidrOpts, AddPeerOpts, interface_config::InterfaceConfig, prompts, AddAssociationOpts, AddCidrOpts, AddPeerOpts,
Association, AssociationContents, Cidr, CidrTree, DeleteCidrOpts, EndpointContents, Association, AssociationContents, Cidr, CidrTree, DeleteCidrOpts, EndpointContents,
InstallOpts, Interface, IoErrorContext, NetworkOpt, Peer, RedeemContents, State, InstallOpts, Interface, IoErrorContext, NetworkOpt, Peer, RedeemContents, RenamePeerOpts,
CLIENT_CONFIG_DIR, REDEEM_TRANSITION_WAIT, State, CLIENT_CONFIG_DIR, REDEEM_TRANSITION_WAIT,
}; };
use std::{ use std::{
fmt, fmt,
@ -140,6 +140,19 @@ enum Command {
opts: AddPeerOpts, opts: AddPeerOpts,
}, },
/// Rename a peer.
///
/// By default, you'll be prompted interactively to select a peer, but you can
/// also specify all the options in the command, eg:
///
/// --name "person" --new-name "human"
RenamePeer {
interface: Interface,
#[structopt(flatten)]
opts: RenamePeerOpts,
},
/// Add a new CIDR. /// Add a new CIDR.
AddCidr { AddCidr {
interface: Interface, interface: Interface,
@ -618,6 +631,32 @@ fn add_peer(interface: &InterfaceName, opts: AddPeerOpts) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn rename_peer(interface: &InterfaceName, opts: RenamePeerOpts) -> Result<(), Error> {
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
let api = Api::new(&server);
log::info!("Fetching peers");
let peers: Vec<Peer> = api.http("GET", "/admin/peers")?;
if let Some((peer_request, old_name)) = prompts::rename_peer(&peers, &opts)? {
log::info!("Renaming peer...");
let id = peers
.iter()
.filter(|p| p.name == old_name)
.map(|p| p.id)
.next()
.ok_or_else(|| "Peer not found.")?;
let _ = api.http_form("PUT", &format!("/admin/peers/{}", id), peer_request)?;
log::info!("Peer renamed.");
} else {
log::info!("exited without renaming peer.");
}
Ok(())
}
fn enable_or_disable_peer(interface: &InterfaceName, enable: bool) -> Result<(), Error> { fn enable_or_disable_peer(interface: &InterfaceName, enable: bool) -> Result<(), Error> {
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?; let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
let api = Api::new(&server); let api = Api::new(&server);
@ -988,6 +1027,7 @@ fn run(opt: Opts) -> Result<(), Error> {
Command::Down { interface } => wg::down(&interface, opt.network.backend)?, Command::Down { interface } => wg::down(&interface, opt.network.backend)?,
Command::Uninstall { interface } => uninstall(&interface, opt.network)?, Command::Uninstall { interface } => uninstall(&interface, opt.network)?,
Command::AddPeer { interface, opts } => add_peer(&interface, opts)?, Command::AddPeer { interface, opts } => add_peer(&interface, opts)?,
Command::RenamePeer { interface, opts } => rename_peer(&interface, opts)?,
Command::AddCidr { interface, opts } => add_cidr(&interface, opts)?, Command::AddCidr { interface, opts } => add_cidr(&interface, opts)?,
Command::DeleteCidr { interface, opts } => delete_cidr(&interface, opts)?, Command::DeleteCidr { interface, opts } => delete_cidr(&interface, opts)?,
Command::DisablePeer { interface } => enable_or_disable_peer(&interface, false)?, Command::DisablePeer { interface } => enable_or_disable_peer(&interface, false)?,

View File

@ -7,7 +7,8 @@ use parking_lot::{Mutex, RwLock};
use rusqlite::Connection; use rusqlite::Connection;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use shared::{ use shared::{
AddCidrOpts, AddPeerOpts, DeleteCidrOpts, IoErrorContext, NetworkOpt, INNERNET_PUBKEY_HEADER, AddCidrOpts, AddPeerOpts, DeleteCidrOpts, IoErrorContext, NetworkOpt, RenamePeerOpts,
INNERNET_PUBKEY_HEADER,
}; };
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
@ -80,6 +81,14 @@ enum Command {
args: AddPeerOpts, args: AddPeerOpts,
}, },
/// Rename an existing peer.
RenamePeer {
interface: Interface,
#[structopt(flatten)]
args: RenamePeerOpts,
},
/// Add a new CIDR to an existing network. /// Add a new CIDR to an existing network.
AddCidr { AddCidr {
interface: Interface, interface: Interface,
@ -236,6 +245,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
network: routing, network: routing,
} => serve(*interface, &conf, routing).await?, } => serve(*interface, &conf, routing).await?,
Command::AddPeer { interface, args } => add_peer(&interface, &conf, args, opt.network)?, Command::AddPeer { interface, args } => add_peer(&interface, &conf, args, opt.network)?,
Command::RenamePeer { interface, args } => rename_peer(&interface, &conf, args)?,
Command::AddCidr { interface, args } => add_cidr(&interface, &conf, args)?, Command::AddCidr { interface, args } => add_cidr(&interface, &conf, args)?,
Command::DeleteCidr { interface, args } => delete_cidr(&interface, &conf, args)?, Command::DeleteCidr { interface, args } => delete_cidr(&interface, &conf, args)?,
Command::Completions { shell } => { Command::Completions { shell } => {
@ -311,6 +321,30 @@ fn add_peer(
Ok(()) Ok(())
} }
fn rename_peer(
interface: &InterfaceName,
conf: &ServerConfig,
opts: RenamePeerOpts,
) -> Result<(), Error> {
let conn = open_database_connection(interface, conf)?;
let peers = DatabasePeer::list(&conn)?
.into_iter()
.map(|dp| dp.inner)
.collect::<Vec<_>>();
if let Some((peer_request, old_name)) = shared::prompts::rename_peer(&peers, &opts)? {
let mut db_peer = DatabasePeer::list(&conn)?
.into_iter()
.find(|p| p.name == old_name)
.ok_or_else(|| "Peer not found.")?;
let _peer = db_peer.update(&conn, peer_request)?;
} else {
println!("exited without creating peer.");
}
Ok(())
}
fn add_cidr( fn add_cidr(
interface: &InterfaceName, interface: &InterfaceName,
conf: &ServerConfig, conf: &ServerConfig,

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo}, interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo},
AddCidrOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, DeleteCidrOpts, Endpoint, AddCidrOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, DeleteCidrOpts, Endpoint,
Error, Peer, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS, Error, Hostname, Peer, PeerContents, RenamePeerOpts, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
}; };
use colored::*; use colored::*;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select}; use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
@ -274,6 +274,63 @@ pub fn add_peer(
) )
} }
/// Bring up a prompt to create a new peer. Returns the peer request.
pub fn rename_peer(
peers: &[Peer],
args: &RenamePeerOpts,
) -> Result<Option<(PeerContents, Hostname)>, Error> {
let eligible_peers = peers
.iter()
.filter(|p| &*p.name != "innernet-server")
.collect::<Vec<_>>();
let old_peer = if let Some(ref name) = args.name {
eligible_peers
.into_iter()
.find(|p| &p.name == name)
.ok_or_else(|| format!("Peer '{}' does not exist", name))?
.clone()
} else {
let peer_index = Select::with_theme(&*THEME)
.with_prompt("Peer to rename")
.items(
&eligible_peers
.iter()
.map(|ep| ep.name.clone())
.collect::<Vec<_>>(),
)
.interact()?;
eligible_peers[peer_index].clone()
};
let old_name = old_peer.name.clone();
let new_name = if let Some(ref name) = args.new_name {
name.clone()
} else {
Input::with_theme(&*THEME)
.with_prompt("New Name")
.interact()?
};
let mut new_peer = old_peer;
new_peer.contents.name = new_name.clone();
Ok(
if args.yes
|| Confirm::with_theme(&*THEME)
.with_prompt(&format!(
"Rename peer {} to {}?",
old_name.yellow(),
new_name.yellow()
))
.default(false)
.interact()?
{
Some((new_peer.contents, old_name))
} else {
None
},
)
}
/// Presents a selection and confirmation of eligible peers for either disabling or enabling, /// Presents a selection and confirmation of eligible peers for either disabling or enabling,
/// and returns back the ID of the selected peer. /// and returns back the ID of the selected peer.
pub fn enable_or_disable_peer(peers: &[Peer], enable: bool) -> Result<Option<Peer>, Error> { pub fn enable_or_disable_peer(peers: &[Peer], enable: bool) -> Result<Option<Peer>, Error> {

View File

@ -318,6 +318,21 @@ pub struct AddPeerOpts {
pub invite_expires: Option<Timestring>, pub invite_expires: Option<Timestring>,
} }
#[derive(Debug, Clone, PartialEq, StructOpt)]
pub struct RenamePeerOpts {
/// Name of peer to rename
#[structopt(long)]
pub name: Option<Hostname>,
/// The new name of the peer
#[structopt(long)]
pub new_name: Option<Hostname>,
/// Bypass confirmation
#[structopt(long)]
pub yes: bool,
}
#[derive(Debug, Clone, PartialEq, StructOpt)] #[derive(Debug, Clone, PartialEq, StructOpt)]
pub struct AddCidrOpts { pub struct AddCidrOpts {
/// The CIDR name (eg. "engineers") /// The CIDR name (eg. "engineers")