From 890c5d8b5a3460c07894a9fb1088d2bd228812cc Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Mon, 4 Jul 2022 22:58:43 +0200 Subject: [PATCH] Credential policies --- warpgate-common/src/config.rs | 10 ++++- warpgate-common/src/config_providers/file.rs | 31 +++++++++----- warpgate-common/src/config_providers/mod.rs | 3 +- warpgate-protocol-http/src/api/auth.rs | 2 +- warpgate-protocol-ssh/src/server/session.rs | 2 +- warpgate/src/config.rs | 45 +++++++++++++++++++- 6 files changed, 77 insertions(+), 16 deletions(-) diff --git a/warpgate-common/src/config.rs b/warpgate-common/src/config.rs index e169ab4..47d36a1 100644 --- a/warpgate-common/src/config.rs +++ b/warpgate-common/src/config.rs @@ -127,12 +127,20 @@ pub enum UserAuthCredential { }, } +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct UserRequireCredentialsPolicy { + #[serde(skip_serializing_if = "Option::is_none")] + pub http: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub ssh: Option>, +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct User { pub username: String, pub credentials: Vec, #[serde(skip_serializing_if = "Option::is_none")] - pub require: Option>, + pub require: Option, pub roles: Vec, } diff --git a/warpgate-common/src/config_providers/file.rs b/warpgate-common/src/config_providers/file.rs index c02a54c..8215452 100644 --- a/warpgate-common/src/config_providers/file.rs +++ b/warpgate-common/src/config_providers/file.rs @@ -2,7 +2,8 @@ use super::ConfigProvider; use crate::helpers::hash::verify_password_hash; use crate::helpers::otp::verify_totp; use crate::{ - AuthCredential, AuthResult, Target, User, UserAuthCredential, UserSnapshot, WarpgateConfig, + AuthCredential, AuthResult, ProtocolName, Target, User, UserAuthCredential, UserSnapshot, + WarpgateConfig, }; use anyhow::Result; use async_trait::async_trait; @@ -71,6 +72,7 @@ impl ConfigProvider for FileConfigProvider { &mut self, username: &str, credentials: &[AuthCredential], + protocol: ProtocolName, ) -> Result { if credentials.is_empty() { return Ok(AuthResult::Rejected); @@ -159,8 +161,16 @@ impl ConfigProvider for FileConfigProvider { ); } - match user.require { - Some(ref required_kinds) => { + if let Some(ref policy) = user.require { + let required_kinds = match protocol { + "SSH" => &policy.ssh, + "HTTP" => &policy.http, + _ => { + error!(%protocol, "Unkown protocol"); + return Ok(AuthResult::Rejected); + } + }; + if let Some(required_kinds) = required_kinds { let mut remaining_required_kinds = HashSet::new(); remaining_required_kinds.extend(required_kinds); for kind in required_kinds { @@ -181,14 +191,15 @@ impl ConfigProvider for FileConfigProvider { return Ok(AuthResult::Rejected); } } - None => Ok(if !valid_credentials.is_empty() { - AuthResult::Accepted { - username: user.username.clone(), - } - } else { - AuthResult::Rejected - }), } + + Ok(if !valid_credentials.is_empty() { + AuthResult::Accepted { + username: user.username.clone(), + } + } else { + AuthResult::Rejected + }) } async fn authorize_target(&mut self, username: &str, target_name: &str) -> Result { diff --git a/warpgate-common/src/config_providers/mod.rs b/warpgate-common/src/config_providers/mod.rs index 112d59c..fa24592 100644 --- a/warpgate-common/src/config_providers/mod.rs +++ b/warpgate-common/src/config_providers/mod.rs @@ -1,5 +1,5 @@ mod file; -use crate::{Secret, Target, UserSnapshot}; +use crate::{ProtocolName, Secret, Target, UserSnapshot}; use anyhow::Result; use async_trait::async_trait; use bytes::Bytes; @@ -36,6 +36,7 @@ pub trait ConfigProvider { &mut self, username: &str, credentials: &[AuthCredential], + protocol: ProtocolName, ) -> Result; async fn authorize_target(&mut self, username: &str, target: &str) -> Result; diff --git a/warpgate-protocol-http/src/api/auth.rs b/warpgate-protocol-http/src/api/auth.rs index 30b8e7a..b7dc14c 100644 --- a/warpgate-protocol-http/src/api/auth.rs +++ b/warpgate-protocol-http/src/api/auth.rs @@ -64,7 +64,7 @@ impl Api { let result = { let mut config_provider = services.config_provider.lock().await; config_provider - .authorize(&body.username, &credentials) + .authorize(&body.username, &credentials, crate::common::PROTOCOL_NAME) .await .map_err(|e| e.context("Failed to authorize user"))? }; diff --git a/warpgate-protocol-ssh/src/server/session.rs b/warpgate-protocol-ssh/src/server/session.rs index 96dec85..ee5d12e 100644 --- a/warpgate-protocol-ssh/src/server/session.rs +++ b/warpgate-protocol-ssh/src/server/session.rs @@ -952,7 +952,7 @@ impl ServerSession { .config_provider .lock() .await - .authorize(username, &self.credentials) + .authorize(username, &self.credentials, crate::PROTOCOL_NAME) .await? }; diff --git a/warpgate/src/config.rs b/warpgate/src/config.rs index 726c9c7..ed2fd84 100644 --- a/warpgate/src/config.rs +++ b/warpgate/src/config.rs @@ -13,13 +13,18 @@ pub fn load_config(path: &Path, secure: bool) -> Result { secure_file(path).context("Could not secure config")?; } - let store: WarpgateConfigStore = Config::builder() + let mut store: serde_yaml::Value = Config::builder() .add_source(File::from(path)) .add_source(Environment::with_prefix("WARPGATE")) .build() .context("Could not load config")? .try_deserialize() - .context("Could not parse config")?; + .context("Could not parse YAML")?; + + check_and_migrate_config(&mut store); + + let store: WarpgateConfigStore = + serde_yaml::from_value(store).context("Could not load config")?; let config = WarpgateConfig { store, @@ -35,6 +40,42 @@ pub fn load_config(path: &Path, secure: bool) -> Result { Ok(config) } +fn check_and_migrate_config(store: &mut serde_yaml::Value) { + use serde_yaml::Value; + if let Some(map) = store.as_mapping_mut() { + if let Some(web_admin) = map.remove(&Value::String("web_admin".into())) { + warn!("The `web_admin` config section is deprecated. Rename it to `http`."); + map.insert(Value::String("http".into()), web_admin); + } + + if let Some(Value::Sequence(ref mut users)) = map.get_mut(&Value::String("users".into())) { + for user in users { + if let Value::Mapping(ref mut user) = user { + if let Some(new_require) = match user.get(&Value::String("require".into())) { + Some(Value::Sequence(ref old_requires)) => Some(Value::Mapping( + vec![ + ( + Value::String("ssh".into()), + Value::Sequence(old_requires.clone()), + ), + ( + Value::String("http".into()), + Value::Sequence(old_requires.clone()), + ), + ] + .into_iter() + .collect(), + )), + x => x.cloned(), + } { + user.insert(Value::String("require".into()), new_require); + } + } + } + } + } +} + pub async fn watch_config>( path: P, config: Arc>,