unattended setup - fixes #409

This commit is contained in:
Eugene Pankov 2022-10-17 21:54:39 +02:00
parent 8831d2006f
commit f96760982c
No known key found for this signature in database
GPG key ID: 5896FCBBDD1CF4F4
2 changed files with 131 additions and 53 deletions

View file

@ -1,6 +1,6 @@
use std::fs::{create_dir_all, File}; use std::fs::{create_dir_all, File};
use std::io::Write; use std::io::Write;
use std::net::ToSocketAddrs; use std::net::{SocketAddr, ToSocketAddrs};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::Result; use anyhow::Result;
@ -21,6 +21,7 @@ use warpgate_db_entities::{Role, User, UserRoleAssignment};
use crate::commands::common::{assert_interactive_terminal, is_docker}; use crate::commands::common::{assert_interactive_terminal, is_docker};
use crate::config::load_config; use crate::config::load_config;
use crate::Commands;
fn prompt_endpoint(prompt: &str, default: ListenEndpoint) -> ListenEndpoint { fn prompt_endpoint(prompt: &str, default: ListenEndpoint) -> ListenEndpoint {
loop { loop {
@ -53,7 +54,9 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
std::process::exit(1); std::process::exit(1);
} }
if let Commands::Setup = cli.command {
assert_interactive_terminal(); assert_interactive_terminal();
}
let mut config_dir = cli.config.parent().unwrap_or_else(|| Path::new(&".")); let mut config_dir = cli.config.parent().unwrap_or_else(|| Path::new(&"."));
if config_dir.as_os_str().is_empty() { if config_dir.as_os_str().is_empty() {
@ -87,18 +90,22 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
// --- // ---
let data_path: String = if let Commands::UnattendedSetup { data_path, .. } = &cli.command {
data_path.to_owned()
} else {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
let default_data_path = "/var/lib/warpgate".to_string(); let default_data_path = "/var/lib/warpgate".to_string();
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let default_data_path = "/usr/local/var/lib/warpgate".to_string(); let default_data_path = "/usr/local/var/lib/warpgate".to_string();
let data_path: String = if is_docker() { if is_docker() {
"/data".to_owned() "/data".to_owned()
} else { } else {
dialoguer::Input::with_theme(&theme) dialoguer::Input::with_theme(&theme)
.default(default_data_path) .default(default_data_path)
.with_prompt("Directory to store app data (up to a few MB) in") .with_prompt("Directory to store app data (up to a few MB) in")
.interact_text()? .interact_text()?
}
}; };
let db_path = PathBuf::from(&data_path).join("db"); let db_path = PathBuf::from(&data_path).join("db");
@ -115,13 +122,25 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
database_url.push_str(&db_path); database_url.push_str(&db_path);
store.database_url = Secret::new(database_url); store.database_url = Secret::new(database_url);
// --- if let Commands::UnattendedSetup { http_port, .. } = &cli.command {
store.http.enable = true;
store.http.listen = ListenEndpoint(SocketAddr::from(([0, 0, 0, 0], *http_port)));
} else {
if !is_docker() { if !is_docker() {
store.http.listen = prompt_endpoint( store.http.listen = prompt_endpoint(
"Endpoint to listen for HTTP connections on", "Endpoint to listen for HTTP connections on",
HTTPConfig::default().listen, HTTPConfig::default().listen,
); );
}
}
if let Commands::UnattendedSetup { ssh_port, .. } = &cli.command {
if let Some(ssh_port) = ssh_port {
store.ssh.enable = true;
store.ssh.listen = ListenEndpoint(SocketAddr::from(([0, 0, 0, 0], *ssh_port)));
}
} else {
if !is_docker() {
info!("You will now choose specific protocol listeners to be enabled."); info!("You will now choose specific protocol listeners to be enabled.");
info!(""); info!("");
info!("NB: Nothing will be exposed by default -"); info!("NB: Nothing will be exposed by default -");
@ -138,9 +157,16 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
SSHConfig::default().listen, SSHConfig::default().listen,
); );
} }
}
}
// --- if let Commands::UnattendedSetup { ssh_port, .. } = &cli.command {
if let Some(ssh_port) = ssh_port {
store.ssh.enable = true;
store.ssh.listen = ListenEndpoint(SocketAddr::from(([0, 0, 0, 0], *ssh_port)));
}
} else {
if !is_docker() {
store.mysql.enable = dialoguer::Confirm::with_theme(&theme) store.mysql.enable = dialoguer::Confirm::with_theme(&theme)
.default(true) .default(true)
.with_prompt("Accept MySQL connections?") .with_prompt("Accept MySQL connections?")
@ -153,6 +179,7 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
); );
} }
} }
}
store.http.certificate = PathBuf::from(&data_path) store.http.certificate = PathBuf::from(&data_path)
.join("tls.certificate.pem") .join("tls.certificate.pem")
@ -176,10 +203,17 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
// --- // ---
if let Commands::UnattendedSetup {
record_sessions, ..
} = &cli.command
{
store.recordings.enable = *record_sessions;
} else {
store.recordings.enable = dialoguer::Confirm::with_theme(&theme) store.recordings.enable = dialoguer::Confirm::with_theme(&theme)
.default(true) .default(true)
.with_prompt("Do you want to record user sessions?") .with_prompt("Do you want to record user sessions?")
.interact()?; .interact()?;
}
store.recordings.path = PathBuf::from(&data_path) store.recordings.path = PathBuf::from(&data_path)
.join("recordings") .join("recordings")
.to_string_lossy() .to_string_lossy()
@ -187,9 +221,25 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
// --- // ---
let admin_password = dialoguer::Password::with_theme(&theme) let admin_password = if let Commands::UnattendedSetup { admin_password, .. } = &cli.command {
if let Some(admin_password) = admin_password {
admin_password.to_owned()
} else {
if let Ok(admin_password) = std::env::var("WARPGATE_ADMIN_PASSWORD") {
admin_password
} else {
error!(
"You must supply the admin password either through the --admin-password option"
);
error!("or the WARPGATE_ADMIN_PASSWORD environment variable.");
std::process::exit(1);
}
}
} else {
dialoguer::Password::with_theme(&theme)
.with_prompt("Set a password for the Warpgate admin user") .with_prompt("Set a password for the Warpgate admin user")
.interact()?; .interact()?
};
// --- // ---

View file

@ -30,9 +30,35 @@ pub struct Cli {
} }
#[derive(clap::Subcommand)] #[derive(clap::Subcommand)]
enum Commands { pub(crate) enum Commands {
/// Run first-time setup and generate a config file /// Run first-time setup and generate a config file
Setup, Setup,
/// Run first-time setup non-interactively
UnattendedSetup {
/// Directory to store data in
#[clap(long)]
data_path: String,
/// HTTP port
#[clap(long)]
http_port: u16,
/// Enable SSH and set port
#[clap(long)]
ssh_port: Option<u16>,
/// Enable MySQL and set port
#[clap(long)]
mysql_port: Option<u16>,
/// Enable session recording
#[clap(long)]
record_sessions: bool,
/// Password for the initial user (required if WARPGATE_ADMIN_PASSWORD env var is not set)
#[clap(long)]
admin_password: Option<String>,
},
/// Show Warpgate's SSH client keys /// Show Warpgate's SSH client keys
ClientKeys, ClientKeys,
/// Run Warpgate /// Run Warpgate
@ -62,7 +88,9 @@ async fn _main() -> Result<()> {
Commands::TestTarget { target_name } => { Commands::TestTarget { target_name } => {
crate::commands::test_target::command(&cli, target_name).await crate::commands::test_target::command(&cli, target_name).await
} }
Commands::Setup => crate::commands::setup::command(&cli).await, Commands::Setup | Commands::UnattendedSetup { .. } => {
crate::commands::setup::command(&cli).await
}
Commands::ClientKeys => crate::commands::client_keys::command(&cli).await, Commands::ClientKeys => crate::commands::client_keys::command(&cli).await,
Commands::RecoverAccess { username } => { Commands::RecoverAccess { username } => {
crate::commands::recover_access::command(&cli, username).await crate::commands::recover_access::command(&cli, username).await