diff --git a/warpgate-common/src/config/mod.rs b/warpgate-common/src/config/mod.rs index 63758e6f7..29d27761a 100644 --- a/warpgate-common/src/config/mod.rs +++ b/warpgate-common/src/config/mod.rs @@ -9,6 +9,7 @@ use poem::http::{self, uri}; use poem_openapi::{Object, Union}; use serde::{Deserialize, Serialize}; pub use target::*; +use tracing::warn; use uri::Scheme; use url::Url; use uuid::Uuid; @@ -102,13 +103,16 @@ pub enum SshHostKeyVerificationMode { } #[derive(Debug, Deserialize, Serialize, Clone)] -pub struct SSHConfig { +pub struct SshConfig { #[serde(default = "_default_false")] pub enable: bool, #[serde(default = "_default_ssh_listen")] pub listen: ListenEndpoint, + #[serde(default)] + pub external_port: Option, + #[serde(default = "_default_ssh_keys_path")] pub keys: String, @@ -116,25 +120,35 @@ pub struct SSHConfig { pub host_key_verification: SshHostKeyVerificationMode, } -impl Default for SSHConfig { +impl Default for SshConfig { fn default() -> Self { - SSHConfig { + SshConfig { enable: false, listen: _default_ssh_listen(), keys: _default_ssh_keys_path(), host_key_verification: Default::default(), + external_port: None, } } } +impl SshConfig { + pub fn external_port(&self) -> u16 { + self.external_port.unwrap_or(self.listen.port()) + } +} + #[derive(Debug, Deserialize, Serialize, Clone)] -pub struct HTTPConfig { +pub struct HttpConfig { #[serde(default = "_default_false")] pub enable: bool, #[serde(default = "_default_http_listen")] pub listen: ListenEndpoint, + #[serde(default)] + pub external_port: Option, + #[serde(default)] pub certificate: String, @@ -151,11 +165,12 @@ pub struct HTTPConfig { pub cookie_max_age: Duration, } -impl Default for HTTPConfig { +impl Default for HttpConfig { fn default() -> Self { - HTTPConfig { + HttpConfig { enable: false, listen: _default_http_listen(), + external_port: None, certificate: "".to_owned(), key: "".to_owned(), trust_x_forwarded_headers: false, @@ -165,14 +180,23 @@ impl Default for HTTPConfig { } } +impl HttpConfig { + pub fn external_port(&self) -> u16 { + self.external_port.unwrap_or(self.listen.port()) + } +} + #[derive(Debug, Deserialize, Serialize, Clone)] -pub struct MySQLConfig { +pub struct MySqlConfig { #[serde(default = "_default_false")] pub enable: bool, #[serde(default = "_default_mysql_listen")] pub listen: ListenEndpoint, + #[serde(default)] + pub external_port: Option, + #[serde(default)] pub certificate: String, @@ -180,17 +204,24 @@ pub struct MySQLConfig { pub key: String, } -impl Default for MySQLConfig { +impl Default for MySqlConfig { fn default() -> Self { - MySQLConfig { + MySqlConfig { enable: false, listen: _default_mysql_listen(), + external_port: None, certificate: "".to_owned(), key: "".to_owned(), } } } +impl MySqlConfig { + pub fn external_port(&self) -> u16 { + self.external_port.unwrap_or(self.listen.port()) + } +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RecordingsConfig { #[serde(default = "_default_false")] @@ -263,13 +294,13 @@ pub struct WarpgateConfigStore { pub database_url: Secret, #[serde(default)] - pub ssh: SSHConfig, + pub ssh: SshConfig, #[serde(default)] - pub http: HTTPConfig, + pub http: HttpConfig, #[serde(default)] - pub mysql: MySQLConfig, + pub mysql: MySqlConfig, #[serde(default)] pub log: LogConfig, @@ -370,4 +401,13 @@ impl WarpgateConfig { }; Url::parse(&url).map_err(WarpgateError::UrlParse) } + + pub fn validate(&self) { + if let Some(ref ext) = self.store.external_host { + if ext.contains(':') { + warn!("Looks like your `external_host` config option contains a port - it will be ignored."); + warn!("Set the external port via the `http.external_port`, `ssh.external_port` or `mysql.external_port` options."); + } + } + } } diff --git a/warpgate-protocol-http/src/api/info.rs b/warpgate-protocol-http/src/api/info.rs index 5dd4174a0..2dc51d58b 100644 --- a/warpgate-protocol-http/src/api/info.rs +++ b/warpgate-protocol-http/src/api/info.rs @@ -33,6 +33,10 @@ enum InstanceInfoResponse { Ok(Json), } +fn strip_port(host: &str) -> Option<&str> { + host.split(':').next() +} + #[OpenApi] impl Api { #[oai(path = "/info", method = "get", operation_id = "get_info")] @@ -47,8 +51,10 @@ impl Api { .store .external_host .as_deref() - .or_else(|| req.header(http::header::HOST)) + .and_then(strip_port) + .or_else(|| req.header(http::header::HOST).and_then(strip_port)) .or_else(|| req.original_uri().host()); + Ok(InstanceInfoResponse::Ok(Json(Info { version: env!("CARGO_PKG_VERSION").to_string(), username: session.get_username(), @@ -61,17 +67,17 @@ impl Api { ports: if session.is_authenticated() { PortsInfo { ssh: if config.store.ssh.enable { - Some(config.store.ssh.listen.port()) + Some(config.store.ssh.external_port()) } else { None }, http: if config.store.http.enable { - Some(config.store.http.listen.port()) + Some(config.store.http.external_port()) } else { None }, mysql: if config.store.mysql.enable { - Some(config.store.mysql.listen.port()) + Some(config.store.mysql.external_port()) } else { None }, diff --git a/warpgate/src/commands/setup.rs b/warpgate/src/commands/setup.rs index 2bb00d4ac..c1a44fe55 100644 --- a/warpgate/src/commands/setup.rs +++ b/warpgate/src/commands/setup.rs @@ -14,7 +14,7 @@ use uuid::Uuid; use warpgate_common::helpers::fs::{secure_directory, secure_file}; use warpgate_common::helpers::hash::hash_password; use warpgate_common::{ - HTTPConfig, ListenEndpoint, MySQLConfig, SSHConfig, Secret, UserAuthCredential, + HttpConfig, ListenEndpoint, MySqlConfig, SshConfig, Secret, UserAuthCredential, UserPasswordCredential, UserRequireCredentialsPolicy, WarpgateConfigStore, WarpgateError, }; use warpgate_core::consts::{BUILTIN_ADMIN_ROLE_NAME, BUILTIN_ADMIN_USERNAME}; @@ -74,7 +74,7 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> { let theme = ColorfulTheme::default(); let mut store = WarpgateConfigStore { - http: HTTPConfig { + http: HttpConfig { enable: true, ..Default::default() }, @@ -141,7 +141,7 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> { if !is_docker() { store.http.listen = prompt_endpoint( "Endpoint to listen for HTTP connections on", - HTTPConfig::default().listen, + HttpConfig::default().listen, ); } } @@ -168,7 +168,7 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> { if store.ssh.enable { store.ssh.listen = prompt_endpoint( "Endpoint to listen for SSH connections on", - SSHConfig::default().listen, + SshConfig::default().listen, ); } } @@ -191,7 +191,7 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> { if store.mysql.enable { store.mysql.listen = prompt_endpoint( "Endpoint to listen for MySQL connections on", - MySQLConfig::default().listen, + MySqlConfig::default().listen, ); } } diff --git a/warpgate/src/config.rs b/warpgate/src/config.rs index 7c383d2b7..1d149fff7 100644 --- a/warpgate/src/config.rs +++ b/warpgate/src/config.rs @@ -33,6 +33,7 @@ pub fn load_config(path: &Path, secure: bool) -> Result { }; info!("Using config: {path:?}"); + config.validate(); Ok(config) }