Credential policies

This commit is contained in:
Eugene Pankov 2022-07-04 22:58:43 +02:00 committed by Eugene
parent 93a1259c38
commit 890c5d8b5a
6 changed files with 77 additions and 16 deletions

View file

@ -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<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ssh: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct User {
pub username: String,
pub credentials: Vec<UserAuthCredential>,
#[serde(skip_serializing_if = "Option::is_none")]
pub require: Option<Vec<String>>,
pub require: Option<UserRequireCredentialsPolicy>,
pub roles: Vec<String>,
}

View file

@ -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<AuthResult> {
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() {
}
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<bool> {

View file

@ -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<AuthResult>;
async fn authorize_target(&mut self, username: &str, target: &str) -> Result<bool>;

View file

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

View file

@ -952,7 +952,7 @@ impl ServerSession {
.config_provider
.lock()
.await
.authorize(username, &self.credentials)
.authorize(username, &self.credentials, crate::PROTOCOL_NAME)
.await?
};

View file

@ -13,13 +13,18 @@ pub fn load_config(path: &Path, secure: bool) -> Result<WarpgateConfig> {
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<WarpgateConfig> {
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<P: AsRef<Path>>(
path: P,
config: Arc<Mutex<WarpgateConfig>>,