Skip to content

Commit

Permalink
Merge pull request #90 from wa5i/bugfix
Browse files Browse the repository at this point in the history
Enhance security and fix remount bug
  • Loading branch information
InfoHunter authored Dec 26, 2024
2 parents 33e89ba + ca10b74 commit 4912aad
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 118 deletions.
23 changes: 23 additions & 0 deletions src/cli/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ pub struct Config {
pub daemon_group: String,
#[serde(default = "default_collection_interval")]
pub collection_interval: u64,
#[serde(default = "default_hmac_level")]
pub mount_entry_hmac_level: MountEntryHMACLevel,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum MountEntryHMACLevel {
None,
Compat,
High,
}

fn default_hmac_level() -> MountEntryHMACLevel {
MountEntryHMACLevel::None
}

fn default_collection_interval() -> u64 {
Expand Down Expand Up @@ -225,6 +239,10 @@ impl Config {
if other.pid_file != "" {
self.pid_file = other.pid_file;
}

if other.mount_entry_hmac_level != MountEntryHMACLevel::None {
self.mount_entry_hmac_level = other.mount_entry_hmac_level;
}
}
}

Expand Down Expand Up @@ -405,6 +423,7 @@ mod test {
assert_eq!(json_config.daemon, false);
assert_eq!(json_config.daemon_user.as_str(), "");
assert_eq!(json_config.daemon_group.as_str(), "");
assert_eq!(json_config.mount_entry_hmac_level, MountEntryHMACLevel::None);

let (_, listener) = json_config.listener.iter().next().unwrap();
assert!(listener.tls_disable);
Expand Down Expand Up @@ -440,6 +459,7 @@ mod test {
log_level = "debug"
log_format = "{date} {req.path}"
pid_file = "/tmp/rusty_vault.pid"
mount_entry_hmac_level = "compat"
"#;

assert!(write_file(path, hcl_config_str).is_ok());
Expand Down Expand Up @@ -467,6 +487,7 @@ mod test {
assert!(config.is_ok());
let hcl_config = config.unwrap();
println!("hcl config: {:?}", hcl_config);
assert_eq!(hcl_config.mount_entry_hmac_level, MountEntryHMACLevel::Compat);

let (_, listener) = hcl_config.listener.iter().next().unwrap();
assert!(listener.tls_disable);
Expand Down Expand Up @@ -499,6 +520,7 @@ mod test {
log_level = "debug"
log_format = "{date} {req.path}"
pid_file = "/tmp/rusty_vault.pid"
mount_entry_hmac_level = "high"
"#;

assert!(write_file(path, hcl_config_str).is_ok());
Expand All @@ -518,6 +540,7 @@ mod test {
assert_eq!(hcl_config.daemon, false);
assert_eq!(hcl_config.daemon_user.as_str(), "");
assert_eq!(hcl_config.daemon_group.as_str(), "");
assert_eq!(hcl_config.mount_entry_hmac_level, MountEntryHMACLevel::High);

let (_, listener) = hcl_config.listener.iter().next().unwrap();
assert_eq!(listener.ltype.as_str(), "tcp");
Expand Down
17 changes: 13 additions & 4 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ use serde::{Deserialize, Serialize};
use zeroize::Zeroizing;

use crate::{
cli::config::Config,
cli::config::{Config, MountEntryHMACLevel},
errors::RvError,
handler::Handler,
logical::{Backend, Request, Response},
module_manager::ModuleManager,
modules::{
auth::AuthModule,
credential::{cert::CertModule, approle::AppRoleModule, userpass::UserPassModule},
credential::{approle::AppRoleModule, cert::CertModule, userpass::UserPassModule},
pki::PkiModule,
},
mount::MountTable,
Expand Down Expand Up @@ -76,6 +76,8 @@ pub struct Core {
pub module_manager: ModuleManager,
pub sealed: bool,
pub unseal_key_shares: Vec<Vec<u8>>,
pub hmac_key: Vec<u8>,
pub mount_entry_hmac_level: MountEntryHMACLevel,
}

impl Default for Core {
Expand All @@ -96,12 +98,18 @@ impl Default for Core {
module_manager: ModuleManager::new(),
sealed: true,
unseal_key_shares: Vec::new(),
hmac_key: Vec::new(),
mount_entry_hmac_level: MountEntryHMACLevel::None,
}
}
}

impl Core {
pub fn config(&mut self, core: Arc<RwLock<Core>>, _config: Option<Config>) -> Result<(), RvError> {
pub fn config(&mut self, core: Arc<RwLock<Core>>, config: Option<Config>) -> Result<(), RvError> {
if let Some(conf) = config {
self.mount_entry_hmac_level = conf.mount_entry_hmac_level;
}

self.module_manager.set_default_modules(Arc::clone(&core))?;
self.self_ref = Some(Arc::clone(&core));

Expand Down Expand Up @@ -352,7 +360,8 @@ impl Core {
self.module_manager.setup(self)?;

// Perform initial setup
self.mounts.load_or_default(self.barrier.as_storage())?;
self.hmac_key = self.barrier.derive_hmac_key()?;
self.mounts.load_or_default(self.barrier.as_storage(), Some(&self.hmac_key), self.mount_entry_hmac_level.clone())?;

self.setup_mounts()?;

Expand Down
3 changes: 3 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ pub enum RvError {
ErrModuleConflict,
#[error("Module is not init.")]
ErrModuleNotInit,
#[error("Module is not found.")]
ErrModuleNotFound,
#[error("Auth module is disabled.")]
ErrAuthModuleDisabled,
#[error("Auth token is not found.")]
Expand Down Expand Up @@ -388,6 +390,7 @@ impl PartialEq for RvError {
| (RvError::ErrConfigListenerNotFound, RvError::ErrConfigListenerNotFound)
| (RvError::ErrModuleConflict, RvError::ErrModuleConflict)
| (RvError::ErrModuleNotInit, RvError::ErrModuleNotInit)
| (RvError::ErrModuleNotFound, RvError::ErrModuleNotFound)
| (RvError::ErrAuthModuleDisabled, RvError::ErrAuthModuleDisabled)
| (RvError::ErrAuthTokenNotFound, RvError::ErrAuthTokenNotFound)
| (RvError::ErrAuthTokenIdInvalid, RvError::ErrAuthTokenIdInvalid)
Expand Down
122 changes: 113 additions & 9 deletions src/modules/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ use std::{
use lazy_static::lazy_static;

use crate::{
cli::config::MountEntryHMACLevel,
core::{Core, LogicalBackendNewFunc},
errors::RvError,
handler::Handler,
logical::Backend,
modules::Module,
mount::{MountEntry, MountTable},
router::Router,
rv_error_response_status,
storage::{barrier::SecurityBarrier, barrier_view::BarrierView},
utils::generate_uuid,
utils::{generate_uuid, is_protect_path},
};

pub mod expiration;
Expand All @@ -30,14 +32,18 @@ const AUTH_CONFIG_PATH: &str = "core/auth";
const AUTH_BARRIER_PREFIX: &str = "auth/";
const AUTH_ROUTER_PREFIX: &str = "auth/";

pub const AUTH_TABLE_TYPE: &str = "auth";

lazy_static! {
static ref PROTECTED_AUTHS: Vec<&'static str> = vec!["auth/token",];
static ref DEFAULT_AUTH_MOUNTS: Vec<MountEntry> = vec![MountEntry {
table: AUTH_TABLE_TYPE.to_string(),
tainted: false,
uuid: generate_uuid(),
path: "token/".to_string(),
logical_type: "token".to_string(),
description: "token based credentials".to_string(),
options: None,
..Default::default()
}];
}

Expand Down Expand Up @@ -93,16 +99,20 @@ impl AuthModule {
return Err(RvError::ErrMountFailed);
}

if is_protect_path(&PROTECTED_AUTHS, &[&entry.path]) {
return Err(RvError::ErrMountPathProtected);
}

for (_, mount_entry) in auth_table.iter() {
let ent = mount_entry.read()?;
if ent.path.starts_with(&entry.path) || entry.path.starts_with(&ent.path) {
return Err(RvError::ErrMountPathExist);
return Err(rv_error_response_status!(409, &format!("path is already in use at {}", &entry.path)));
}
}

let match_mount_path = router_store.router.matching_mount(&entry.path)?;
if match_mount_path.len() != 0 {
return Err(RvError::ErrMountPathExist);
return Err(rv_error_response_status!(409, &format!("path is already in use at {}", match_mount_path)));
}

let backend_new_func = self.get_auth_backend(&entry.logical_type)?;
Expand Down Expand Up @@ -158,6 +168,74 @@ impl AuthModule {
Ok(())
}

pub fn remount_auth(&self, src: &str, dst: &str) -> Result<(), RvError> {
let mut src = src.to_string();
let mut dst = dst.to_string();

if !src.ends_with("/") {
src += "/";
}

if !dst.ends_with("/") {
dst += "/";
}

if !src.starts_with(AUTH_ROUTER_PREFIX) {
return Err(rv_error_response_status!(400, &format!("cannot remount non-auth mount {}", src)));
}

if !dst.starts_with(AUTH_ROUTER_PREFIX) {
return Err(rv_error_response_status!(
400,
&format!("cannot remount auth mount to non-auth mount {}", dst)
));
}

if is_protect_path(&PROTECTED_AUTHS, &[&src, &dst]) {
return Err(RvError::ErrMountPathProtected);
}

let router_store = self.router_store.read()?;

let dst_match = router_store.router.matching_mount(&dst)?;
if dst_match.len() != 0 {
return Err(RvError::ErrMountPathExist);
}

let src_match = router_store.router.matching_mount_entry(&src)?;
if src_match.is_none() {
return Err(RvError::ErrMountNotMatch);
}

let mut src_entry = src_match.as_ref().unwrap().write()?;
src_entry.tainted = true;

router_store.router.taint(&src)?;

if router_store.router.matching_mount(&dst)? != "" {
return Err(RvError::ErrMountPathExist);
}

let src_path = src_entry.path.clone();
src_entry.path = dst.as_str().trim_start_matches(AUTH_ROUTER_PREFIX).to_string();
src_entry.tainted = false;

std::mem::drop(src_entry);

if let Err(e) = router_store.mounts.persist(AUTH_CONFIG_PATH, self.barrier.as_storage()) {
let mut src_entry = src_match.as_ref().unwrap().write()?;
src_entry.path = src_path;
src_entry.tainted = true;
return Err(e);
}

router_store.router.remount(&dst, &src)?;

router_store.router.untaint(&dst)?;

Ok(())
}

pub fn remove_auth_entry(&self, path: &str) -> Result<(), RvError> {
let router_store = self.router_store.read()?;
if router_store.mounts.delete(path) {
Expand All @@ -181,14 +259,15 @@ impl AuthModule {
Ok(())
}

pub fn load_auth(&self) -> Result<(), RvError> {
pub fn load_auth(&self, hmac_key: Option<&[u8]>, hmac_level: MountEntryHMACLevel) -> Result<(), RvError> {
let router_store = self.router_store.read()?;
if router_store.mounts.load(self.barrier.as_storage(), AUTH_CONFIG_PATH).is_err() {
router_store.mounts.set_default(DEFAULT_AUTH_MOUNTS.to_vec())?;
if router_store.mounts.load(self.barrier.as_storage(), AUTH_CONFIG_PATH, hmac_key, hmac_level.clone()).is_err()
{
router_store.mounts.set_default(DEFAULT_AUTH_MOUNTS.to_vec(), hmac_key)?;
router_store.mounts.persist(AUTH_CONFIG_PATH, self.barrier.as_storage())?;
}

Ok(())
self.update_auth_mount(hmac_key, hmac_level)
}

pub fn persist_auth(&self) -> Result<(), RvError> {
Expand Down Expand Up @@ -244,6 +323,31 @@ impl AuthModule {
backends.remove(logical_type);
Ok(())
}

fn update_auth_mount(&self, hmac_key: Option<&[u8]>, hmac_level: MountEntryHMACLevel) -> Result<(), RvError> {
let mut need_persist = false;
let router_store = self.router_store.read()?;
let mounts = router_store.mounts.entries.read()?;

for mount_entry in mounts.values() {
let mut entry = mount_entry.write()?;
if entry.table == "" {
entry.table = AUTH_TABLE_TYPE.to_string();
need_persist = true;
}

if entry.hmac == "" && hmac_key.is_some() && hmac_level == MountEntryHMACLevel::Compat {
entry.calc_hmac(hmac_key.unwrap())?;
need_persist = true;
}
}

if need_persist {
self.persist_auth()?;
}

Ok(())
}
}

impl Module for AuthModule {
Expand Down Expand Up @@ -275,7 +379,7 @@ impl Module for AuthModule {
};

self.add_auth_backend("token", Arc::new(token_backend_new_func))?;
self.load_auth()?;
self.load_auth(Some(&core.hmac_key), core.mount_entry_hmac_level.clone())?;
self.setup_auth()?;
self.expiration.restore()?;

Expand Down
Loading

0 comments on commit 4912aad

Please sign in to comment.