diff --git a/Cargo.toml b/Cargo.toml index 20164b62..27af6039 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,8 @@ delay_timer = "0.11" as-any = "0.3.1" pem = "3.0" chrono = "0.4" -zeroize = { version = "1.4.0", features= ["derive"] } +zeroize = { version = "1.7.0", features= ["zeroize_derive"] } +bcrypt = "0.15" [target.'cfg(unix)'.dependencies] daemonize = "0.5" diff --git a/src/core.rs b/src/core.rs index 37e04cda..f5a8eb5c 100644 --- a/src/core.rs +++ b/src/core.rs @@ -14,7 +14,7 @@ use crate::{ handler::Handler, logical::{Backend, Request, Response}, module_manager::ModuleManager, - modules::{auth::AuthModule, pki::PkiModule}, + modules::{auth::AuthModule, credential::userpass::UserPassModule, pki::PkiModule}, mount::MountTable, router::Router, shamir::{ShamirSecret, SHAMIR_OVERHEAD}, @@ -104,6 +104,10 @@ impl Core { let pki_module = PkiModule::new(self); self.module_manager.add_module(Arc::new(RwLock::new(Box::new(pki_module))))?; + // add credential module: userpass + let userpass_module = UserPassModule::new(self); + self.module_manager.add_module(Arc::new(RwLock::new(Box::new(userpass_module))))?; + Ok(()) } diff --git a/src/errors.rs b/src/errors.rs index bd45246d..1c45fac6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -201,6 +201,11 @@ pub enum RvError { #[from] source: delay_timer::error::TaskError, }, + #[error("Some bcrypt error happened, {:?}", .source)] + BcryptError { + #[from] + source: bcrypt::BcryptError, + }, #[error("RwLock was poisoned (reading)")] ErrRwLockReadPoison, #[error("RwLock was poisoned (writing)")] diff --git a/src/http/logical.rs b/src/http/logical.rs index c387d9a8..92c2cd8a 100644 --- a/src/http/logical.rs +++ b/src/http/logical.rs @@ -47,7 +47,7 @@ impl Default for LogicalResponse { async fn logical_request_handler( req: HttpRequest, - body: web::Bytes, + mut body: web::Bytes, method: Method, path: web::Path, core: web::Data>>, @@ -67,6 +67,7 @@ async fn logical_request_handler( if body.len() > 0 { let payload = serde_json::from_slice(&body)?; r.body = Some(payload); + body.clear(); } } Method::DELETE => { diff --git a/src/http/sys.rs b/src/http/sys.rs index 29df8e95..a48fef90 100644 --- a/src/http/sys.rs +++ b/src/http/sys.rs @@ -90,10 +90,11 @@ async fn sys_init_get_request_handler( async fn sys_init_put_request_handler( _req: HttpRequest, - body: web::Bytes, + mut body: web::Bytes, core: web::Data>>, ) -> Result { let payload = serde_json::from_slice::(&body)?; + body.clear(); let seal_config = SealConfig { secret_shares: payload.secret_shares, secret_threshold: payload.secret_threshold }; let mut core = core.write()?; @@ -125,11 +126,12 @@ async fn sys_seal_request_handler( async fn sys_unseal_request_handler( _req: HttpRequest, - body: web::Bytes, + mut body: web::Bytes, core: web::Data>>, ) -> Result { // TODO let payload = serde_json::from_slice::(&body)?; + body.clear(); let key = hex::decode(payload.key)?; { @@ -154,11 +156,12 @@ async fn sys_list_mounts_request_handler( async fn sys_mount_request_handler( req: HttpRequest, path: web::Path, - body: web::Bytes, + mut body: web::Bytes, core: web::Data>>, ) -> Result { let _test = serde_json::from_slice::(&body)?; let payload = serde_json::from_slice(&body)?; + body.clear(); let mount_path = path.into_inner(); if mount_path.len() == 0 { return Ok(response_error(StatusCode::NOT_FOUND, "")); @@ -191,11 +194,12 @@ async fn sys_unmount_request_handler( async fn sys_remount_request_handler( req: HttpRequest, - body: web::Bytes, + mut body: web::Bytes, core: web::Data>>, ) -> Result { let _test = serde_json::from_slice::(&body)?; let payload = serde_json::from_slice(&body)?; + body.clear(); let mut r = request_auth(&req); r.operation = Operation::Write; @@ -218,11 +222,12 @@ async fn sys_list_auth_mounts_request_handler( async fn sys_auth_enable_request_handler( req: HttpRequest, path: web::Path, - body: web::Bytes, + mut body: web::Bytes, core: web::Data>>, ) -> Result { let _test = serde_json::from_slice::(&body)?; let payload = serde_json::from_slice(&body)?; + body.clear(); let mount_path = path.into_inner(); if mount_path.len() == 0 { return Ok(response_error(StatusCode::NOT_FOUND, "")); diff --git a/src/logical/backend.rs b/src/logical/backend.rs index 5dc35304..bbf67a46 100644 --- a/src/logical/backend.rs +++ b/src/logical/backend.rs @@ -3,9 +3,11 @@ use std::{collections::HashMap, sync::Arc}; use regex::Regex; use serde_json::{Map, Value}; -use super::{path::Path, request::Request, response::Response, secret::Secret, Backend, Operation}; +use super::{path::Path, request::Request, response::Response, secret::Secret, FieldType, Backend, Operation}; use crate::errors::RvError; +type BackendOperationHandler = dyn Fn(&dyn Backend, &mut Request) -> Result, RvError> + Send + Sync; + #[derive(Clone)] pub struct LogicalBackend { pub paths: Vec>, @@ -14,6 +16,7 @@ pub struct LogicalBackend { pub unauth_paths: Arc>, pub help: String, pub secrets: Vec>, + pub auth_renew_handler: Option>, } impl Backend for LogicalBackend { @@ -60,6 +63,13 @@ impl Backend for LogicalBackend { return Err(RvError::ErrRequestNotReady); } + match req.operation { + Operation::Renew | Operation::Revoke => { + return self.handle_revoke_renew(req); + } + _ => {} + } + if req.path == "" && req.operation == Operation::Help { return self.handle_root_help(req); } @@ -76,7 +86,9 @@ impl Backend for LogicalBackend { req.match_path = Some(path.clone()); for operation in &path.operations { if operation.op == req.operation { - return operation.handle_request(self, req); + let ret = operation.handle_request(self, req); + self.clear_secret_field(req); + return ret; } } @@ -100,9 +112,52 @@ impl LogicalBackend { unauth_paths: Arc::new(Vec::new()), help: String::new(), secrets: Vec::new(), + auth_renew_handler: None, } } + pub fn handle_auth_renew(&self, req: &mut Request) -> Result, RvError> { + if self.auth_renew_handler.is_none() { + log::error!("this auth type doesn't support renew"); + return Err(RvError::ErrLogicalOperationUnsupported); + } + + (self.auth_renew_handler.as_ref().unwrap())(self, req) + } + + pub fn handle_revoke_renew(&self, req: &mut Request) -> Result, RvError> { + if req.operation == Operation::Renew && req.auth.is_some() { + return self.handle_auth_renew(req); + } + + if req.secret.is_none() { + log::error!("request has no secret"); + return Ok(None); + } + + if let Some(raw_secret_type) = req.secret.as_ref().unwrap().internal_data.get("secret_type") { + if let Some(secret_type) = raw_secret_type.as_str() { + if let Some(secret) = self.secret(secret_type) { + match req.operation { + Operation::Renew => { + return secret.renew(self, req); + } + Operation::Revoke => { + return secret.revoke(self, req); + } + _ => { + log::error!("invalid operation for revoke/renew: {}", req.operation); + return Ok(None); + } + } + } + } + } + + log::error!("secret is unsupported by this backend"); + return Ok(None); + } + pub fn handle_root_help(&self, _req: &mut Request) -> Result, RvError> { Ok(None) } @@ -124,6 +179,16 @@ impl LogicalBackend { None } + + fn clear_secret_field(&self, req: &mut Request) { + for path in &self.paths { + for (key, field) in &path.fields { + if field.field_type == FieldType::SecretStr { + req.clear_data(key); + } + } + } + } } #[macro_export] @@ -136,34 +201,42 @@ macro_rules! new_logical_backend { #[macro_export] #[doc(hidden)] macro_rules! new_logical_backend_internal { - (@object $object:ident paths: [$($path:tt),*]) => { + (@object $object:ident () {}) => { + }; + (@object $object:ident () {paths: [$($path:tt),*], $($rest:tt)*}) => { $( $object.paths.push(Arc::new(new_path!($path))); )* + new_logical_backend_internal!(@object $object () {$($rest)*}); }; - (@object $object:ident unauth_paths: [$($unauth:expr),*]) => { + (@object $object:ident () {unauth_paths: [$($unauth:expr),*], $($rest:tt)*}) => { $object.unauth_paths = Arc::new(vec![$($unauth.to_string()),*]); + new_logical_backend_internal!(@object $object () {$($rest)*}); }; - (@object $object:ident root_paths: [$($root:expr),*]) => { + (@object $object:ident () {root_paths: [$($root:expr),*], $($rest:tt)*}) => { $object.root_paths = Arc::new(vec![$($root.to_string()),*]); + new_logical_backend_internal!(@object $object () {$($rest)*}); }; - (@object $object:ident help: $help:expr) => { + (@object $object:ident () {help: $help:expr, $($rest:tt)*}) => { $object.help = $help.to_string(); + new_logical_backend_internal!(@object $object () {$($rest)*}); }; - (@object $object:ident secrets: [$($secrets:tt),* $(,)?]) => { + (@object $object:ident () {secrets: [$($secrets:tt),* $(,)?], $($rest:tt)*}) => { $( $object.secrets.push(Arc::new(new_secret!($secrets))); )* + new_logical_backend_internal!(@object $object () {$($rest)*}); }; - (@object $object:ident () $($key:ident: $value:tt),*) => { - $( - new_logical_backend_internal!(@object $object $key: $value); - )* + (@object $object:ident () {auth_renew_handler: $handler_obj:ident$(.$handler_method:ident)*, $($rest:tt)*}) => { + $object.auth_renew_handler = Some(Arc::new(move |backend: &dyn Backend, req: &mut Request| -> Result, RvError> { + $handler_obj$(.$handler_method)*(backend, req) + })); + new_logical_backend_internal!(@object $object () {$($rest)*}); }; ({ $($tt:tt)+ }) => { { let mut backend = LogicalBackend::new(); - new_logical_backend_internal!(@object backend () $($tt)+); + new_logical_backend_internal!(@object backend () {$($tt)+}); backend } }; @@ -174,14 +247,27 @@ mod test { use std::{collections::HashMap, env, fs, sync::Arc, time::Duration}; use go_defer::defer; + use serde_json::json; use super::*; use crate::{ logical::{Field, FieldType, PathOperation}, - new_path, new_path_internal, new_secret, new_secret_internal, new_fields, new_fields_internal, + new_fields, new_fields_internal, new_path, new_path_internal, new_secret, new_secret_internal, storage::{barrier_aes_gcm::AESGCMBarrier, physical}, }; + struct MyTest; + + impl MyTest { + pub fn new() -> Self { + MyTest + } + + pub fn noop(&self, _backend: &dyn Backend, _req: &mut Request) -> Result, RvError> { + Ok(None) + } + } + #[test] fn test_logical_backend_match_path() { let path = "/(?P.+?)/(?P.+)"; @@ -216,6 +302,8 @@ mod test { assert!(fs::remove_dir_all(&dir).is_ok()); ); + let t = MyTest::new(); + let mut conf: HashMap = HashMap::new(); conf.insert("path".to_string(), Value::String(dir.to_string_lossy().into_owned())); @@ -235,6 +323,10 @@ mod test { "mypath": { field_type: FieldType::Str, description: "hehe" + }, + "mypassword": { + field_type: FieldType::SecretStr, + description: "password" } }, operations: [ @@ -280,9 +372,10 @@ mod test { renew_handler: renew_noop_handler, revoke_handler: revoke_noop_handler, }], + auth_renew_handler: t.noop, unauth_paths: ["/login"], root_paths: ["/"], - help: "help content" + help: "help content", }); let mut req = Request::new("/"); @@ -319,6 +412,8 @@ mod test { assert_eq!(&logical_backend.root_paths[0], "/"); assert_eq!(&logical_backend.help, "help content"); + assert!(logical_backend.auth_renew_handler.is_some()); + assert_eq!(logical_backend.paths_re.len(), 0); assert!(logical_backend.init().is_ok()); @@ -330,8 +425,17 @@ mod test { assert!(logical_backend.handle_request(&mut req).is_err()); + let body: Map = json!({ + "mytype": 1, + "mypath": "/pp", + "mypassword": "123qwe", + }).as_object().unwrap().clone(); + req.body = Some(body); req.storage = Some(Arc::new(barrier)); assert!(logical_backend.handle_request(&mut req).is_ok()); + let mypassword = req.body.as_ref().unwrap().get("mypassword"); + assert!(mypassword.is_some()); + assert_eq!(mypassword.unwrap(), ""); let unauth_paths = logical_backend.get_unauth_paths(); assert!(unauth_paths.is_some()); diff --git a/src/logical/field.rs b/src/logical/field.rs index 44504b72..6c143012 100644 --- a/src/logical/field.rs +++ b/src/logical/field.rs @@ -11,6 +11,8 @@ use crate::errors::RvError; pub enum FieldType { #[strum(to_string = "string")] Str, + #[strum(to_string = "secret_string")] + SecretStr, #[strum(to_string = "int")] Int, #[strum(to_string = "bool")] @@ -40,6 +42,7 @@ impl Field { pub fn get_default(&self) -> Result { match &self.field_type { FieldType::Str => self.cast_value::(), + FieldType::SecretStr => self.cast_value::(), FieldType::Int => self.cast_value::(), FieldType::Bool => self.cast_value::(), FieldType::Map => self.cast_value::(), diff --git a/src/logical/request.rs b/src/logical/request.rs index 92cb7db2..cf8232d0 100644 --- a/src/logical/request.rs +++ b/src/logical/request.rs @@ -97,6 +97,25 @@ impl Request { return field.get_default(); } + //TODO: the sensitive data is still in the memory. Need to totally resolve this in `serde_json` someday. + pub fn clear_data(&mut self, key: &str) { + if self.data.is_some() { + if let Some(secret_str) = self.data.as_mut().unwrap().get_mut(key) { + if let Value::String(ref mut s) = *secret_str { + *s = "".to_owned(); + } + } + } + + if self.body.is_some() { + if let Some(secret_str) = self.body.as_mut().unwrap().get_mut(key) { + if let Value::String(ref mut s) = *secret_str { + *s = "".to_owned(); + } + } + } + } + pub fn storage_list(&self, prefix: &str) -> Result, RvError> { if self.storage.is_none() { return Err(RvError::ErrRequestNotReady); diff --git a/src/modules/auth/mod.rs b/src/modules/auth/mod.rs index 718c655c..571f01aa 100644 --- a/src/modules/auth/mod.rs +++ b/src/modules/auth/mod.rs @@ -85,7 +85,7 @@ impl AuthModule { return Err(RvError::ErrMountPathProtected); } - if entry.logical_type == "type" { + if entry.logical_type == "token" { return Err(RvError::ErrMountFailed); } @@ -101,7 +101,7 @@ impl AuthModule { return Err(RvError::ErrMountPathExist); } - let backend_new_func = self.get_backend(&entry.logical_type)?; + let backend_new_func = self.get_auth_backend(&entry.logical_type)?; let backend = backend_new_func(Arc::clone(&self.core))?; entry.uuid = generate_uuid(); @@ -201,7 +201,7 @@ impl AuthModule { let entry = mount_entry.read()?; let barrier_path = format!("{}{}/", AUTH_BARRIER_PREFIX, &entry.uuid); - let backend_new_func = self.get_backend(&entry.logical_type)?; + let backend_new_func = self.get_auth_backend(&entry.logical_type)?; let backend = backend_new_func(Arc::clone(&self.core))?; let view = BarrierView::new(Arc::clone(&self.barrier), &barrier_path); @@ -217,7 +217,7 @@ impl AuthModule { Ok(()) } - pub fn get_backend(&self, logical_type: &str) -> Result, RvError> { + pub fn get_auth_backend(&self, logical_type: &str) -> Result, RvError> { let backends = self.backends.lock().unwrap(); if let Some(backend) = backends.get(logical_type) { Ok(backend.clone()) @@ -226,7 +226,7 @@ impl AuthModule { } } - pub fn add_backend(&self, logical_type: &str, backend: Arc) -> Result<(), RvError> { + pub fn add_auth_backend(&self, logical_type: &str, backend: Arc) -> Result<(), RvError> { let mut backends = self.backends.lock().unwrap(); if backends.contains_key(logical_type) { return Err(RvError::ErrCoreLogicalBackendExist); @@ -235,7 +235,7 @@ impl AuthModule { Ok(()) } - pub fn delete_backend(&self, logical_type: &str) -> Result<(), RvError> { + pub fn delete_auth_backend(&self, logical_type: &str) -> Result<(), RvError> { let mut backends = self.backends.lock().unwrap(); backends.remove(logical_type); Ok(()) @@ -270,7 +270,7 @@ impl Module for AuthModule { Ok(Arc::new(backend)) }; - self.add_backend("token", Arc::new(token_backend_new_func))?; + self.add_auth_backend("token", Arc::new(token_backend_new_func))?; self.load_auth()?; self.setup_auth()?; self.expiration.restore()?; @@ -283,7 +283,7 @@ impl Module for AuthModule { fn cleanup(&mut self, core: &Core) -> Result<(), RvError> { core.delete_handler(Arc::clone(&self.token_store) as Arc)?; - self.delete_backend("token")?; + self.delete_auth_backend("token")?; self.teardown_auth()?; Ok(()) } diff --git a/src/modules/auth/token_store.rs b/src/modules/auth/token_store.rs index 82998322..4a231084 100644 --- a/src/modules/auth/token_store.rs +++ b/src/modules/auth/token_store.rs @@ -17,7 +17,7 @@ use crate::{ logical::{ Auth, Backend, Field, FieldType, Lease, LogicalBackend, Operation, Path, PathOperation, Request, Response, }, - new_logical_backend, new_logical_backend_internal, new_path, new_path_internal, new_fields, new_fields_internal, + new_fields, new_fields_internal, new_logical_backend, new_logical_backend_internal, new_path, new_path_internal, router::Router, storage::{Storage, StorageEntry}, utils::{generate_uuid, is_str_subset, sha1}, @@ -241,7 +241,7 @@ impl TokenStore { } ], root_paths: ["revoke-orphan/*"], - help: AUTH_TOKEN_HELP + help: AUTH_TOKEN_HELP, }); backend @@ -698,6 +698,18 @@ impl Handler for TokenStore { auth.ttl = MAX_LEASE_DURATION_SECS; } + let mut te = TokenEntry { + path: req.path.clone(), + meta: auth.metadata.clone(), + display_name: auth.display_name.clone(), + ttl: auth.ttl.as_secs(), + ..Default::default() + }; + + self.create(&mut te)?; + + auth.client_token = te.id.clone(); + self.expiration.register_auth(&req.path, auth)?; } diff --git a/src/modules/credential/mod.rs b/src/modules/credential/mod.rs new file mode 100644 index 00000000..7134d2e0 --- /dev/null +++ b/src/modules/credential/mod.rs @@ -0,0 +1 @@ +pub mod userpass; diff --git a/src/modules/credential/userpass/mod.rs b/src/modules/credential/userpass/mod.rs new file mode 100644 index 00000000..aa8b7465 --- /dev/null +++ b/src/modules/credential/userpass/mod.rs @@ -0,0 +1,370 @@ +use std::{ + ops::Deref, + sync::{Arc, RwLock}, +}; + +use as_any::Downcast; + +use crate::{ + core::Core, + errors::RvError, + logical::{Backend, LogicalBackend, Request, Response}, + modules::{auth::AuthModule, Module}, + new_logical_backend, new_logical_backend_internal, +}; + +pub mod path_login; +pub mod path_users; + +static USERPASS_BACKEND_HELP: &str = r#" +The "userpass" credential provider allows authentication using a combination of +a username and password. No additional factors are supported. + +The username/password combination is configured using the "users/" endpoints by +a user with root access. Authentication is then done by supplying the two fields +for "login". +"#; + +pub struct UserPassModule { + pub name: String, + pub backend: Arc, +} + +pub struct UserPassBackendInner { + pub core: Arc>, +} + +pub struct UserPassBackend { + pub inner: Arc, +} + +impl Deref for UserPassBackend { + type Target = UserPassBackendInner; + + fn deref(&self) -> &UserPassBackendInner { + &self.inner + } +} + +impl UserPassBackend { + pub fn new(core: Arc>) -> Self { + Self { inner: Arc::new(UserPassBackendInner { core }) } + } + + pub fn new_backend(&self) -> LogicalBackend { + let userpass_backend_ref = Arc::clone(&self.inner); + + let mut backend = new_logical_backend!({ + unauth_paths: ["login/*"], + auth_renew_handler: userpass_backend_ref.renew_path_login, + help: USERPASS_BACKEND_HELP, + }); + + backend.paths.push(Arc::new(self.users_path())); + backend.paths.push(Arc::new(self.user_list_path())); + backend.paths.push(Arc::new(self.user_password_path())); + backend.paths.push(Arc::new(self.login_path())); + + backend + } +} + +impl UserPassBackendInner { + pub fn renew_path_login(&self, _backend: &dyn Backend, _req: &mut Request) -> Result, RvError> { + Ok(None) + } +} + +impl UserPassModule { + pub fn new(core: &Core) -> Self { + Self { + name: "userpass".to_string(), + backend: Arc::new(UserPassBackend::new(Arc::clone(core.self_ref.as_ref().unwrap()))), + } + } +} + +impl Module for UserPassModule { + fn name(&self) -> String { + return self.name.clone(); + } + + fn setup(&mut self, core: &Core) -> Result<(), RvError> { + let userpass = Arc::clone(&self.backend); + let userpass_backend_new_func = move |_c: Arc>| -> Result, RvError> { + let mut userpass_backend = userpass.new_backend(); + userpass_backend.init()?; + Ok(Arc::new(userpass_backend)) + }; + + if let Some(module) = core.module_manager.get_module("auth") { + let auth_mod = module.read()?; + if let Some(auth_module) = auth_mod.as_ref().downcast_ref::() { + return auth_module.add_auth_backend("userpass", Arc::new(userpass_backend_new_func)); + } else { + log::error!("downcast auth module failed!"); + } + } else { + log::error!("get auth module failed!"); + } + + Ok(()) + } + + fn cleanup(&mut self, core: &Core) -> Result<(), RvError> { + if let Some(module) = core.module_manager.get_module("auth") { + let auth_mod = module.read()?; + if let Some(auth_module) = auth_mod.as_ref().downcast_ref::() { + return auth_module.delete_auth_backend("userpass"); + } else { + log::error!("downcast auth module failed!"); + } + } else { + log::error!("get auth module failed!"); + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::{ + collections::HashMap, + default::Default, + env, fs, + sync::{Arc, RwLock}, + time::Duration, + }; + + use go_defer::defer; + use serde_json::{json, Map, Value}; + + use super::*; + use crate::{ + core::{Core, SealConfig}, + logical::{Operation, Request}, + storage::{barrier_aes_gcm, physical}, + }; + + fn test_read_api(core: &Core, token: &str, path: &str, is_ok: bool) -> Result, RvError> { + let mut req = Request::new(path); + req.operation = Operation::Read; + req.client_token = token.to_string(); + let resp = core.handle_request(&mut req); + assert_eq!(resp.is_ok(), is_ok); + resp + } + + fn test_write_api( + core: &Core, + token: &str, + path: &str, + is_ok: bool, + data: Option>, + ) -> Result, RvError> { + let mut req = Request::new(path); + req.operation = Operation::Write; + req.client_token = token.to_string(); + req.body = data; + + let resp = core.handle_request(&mut req); + assert_eq!(resp.is_ok(), is_ok); + resp + } + + fn test_delete_api(core: &Core, token: &str, path: &str, is_ok: bool) -> Result, RvError> { + let mut req = Request::new(path); + req.operation = Operation::Delete; + req.client_token = token.to_string(); + let resp = core.handle_request(&mut req); + assert_eq!(resp.is_ok(), is_ok); + resp + } + + fn test_mount_userpass_auth(core: Arc>, token: &str, path: &str) { + let core = core.read().unwrap(); + + let auth_data = json!({ + "type": "userpass", + }) + .as_object() + .unwrap() + .clone(); + + let resp = test_write_api(&core, token, format!("sys/auth/{}", path).as_str(), true, Some(auth_data)); + assert!(resp.is_ok()); + } + + fn test_write_user(core: Arc>, token: &str, path: &str, username: &str, password: &str, ttl: i32) { + let core = core.read().unwrap(); + + let user_data = json!({ + "password": password, + "ttl": ttl, + }) + .as_object() + .unwrap() + .clone(); + + let resp = + test_write_api(&core, token, format!("auth/{}/users/{}", path, username).as_str(), true, Some(user_data)); + assert!(resp.is_ok()); + } + + fn test_read_user(core: Arc>, token: &str, username: &str) -> Result, RvError> { + let core = core.read().unwrap(); + + let resp = test_read_api(&core, token, format!("auth/pass/users/{}", username).as_str(), true); + assert!(resp.is_ok()); + resp + } + + fn test_delete_user(core: Arc>, token: &str, username: &str) { + let core = core.read().unwrap(); + + let resp = test_delete_api(&core, token, format!("auth/pass/users/{}", username).as_str(), true); + assert!(resp.is_ok()); + } + + fn test_login( + core: Arc>, + path: &str, + username: &str, + password: &str, + is_ok: bool, + ) -> Result, RvError> { + let core = core.read().unwrap(); + + let login_data = json!({ + "password": password, + }) + .as_object() + .unwrap() + .clone(); + + let mut req = Request::new(format!("auth/{}/login/{}", path, username).as_str()); + req.operation = Operation::Write; + req.body = Some(login_data); + + let resp = core.handle_request(&mut req); + assert!(resp.is_ok()); + if is_ok { + let resp = resp.as_ref().unwrap(); + assert!(resp.is_some()); + } + resp + } + + #[test] + fn test_userpass_module() { + let dir = env::temp_dir().join("rusty_vault_credential_userpass_module"); + assert!(fs::create_dir(&dir).is_ok()); + defer! ( + assert!(fs::remove_dir_all(&dir).is_ok()); + ); + + let mut root_token = String::new(); + println!("root_token: {:?}", root_token); + + let mut conf: HashMap = HashMap::new(); + conf.insert("path".to_string(), Value::String(dir.to_string_lossy().into_owned())); + + let backend = physical::new_backend("file", &conf).unwrap(); + + let barrier = barrier_aes_gcm::AESGCMBarrier::new(Arc::clone(&backend)); + + let c = Arc::new(RwLock::new(Core { physical: backend, barrier: Arc::new(barrier), ..Default::default() })); + + { + let mut core = c.write().unwrap(); + assert!(core.config(Arc::clone(&c), None).is_ok()); + + let seal_config = SealConfig { secret_shares: 10, secret_threshold: 5 }; + + let result = core.init(&seal_config); + assert!(result.is_ok()); + let init_result = result.unwrap(); + println!("init_result: {:?}", init_result); + + let mut unsealed = false; + for i in 0..seal_config.secret_threshold { + let key = &init_result.secret_shares[i as usize]; + let unseal = core.unseal(key); + assert!(unseal.is_ok()); + unsealed = unseal.unwrap(); + } + + root_token = init_result.root_token; + + assert!(unsealed); + } + + { + println!("root_token: {:?}", root_token); + + // mount userpass auth to path: auth/pass + test_mount_userpass_auth(Arc::clone(&c), &root_token, "pass"); + test_write_user(Arc::clone(&c), &root_token, "pass", "test", "123qwe!@#", 0); + let resp = test_read_user(Arc::clone(&c), &root_token, "test").unwrap(); + assert!(resp.is_some()); + + test_delete_user(Arc::clone(&c), &root_token, "test"); + let resp = test_read_user(Arc::clone(&c), &root_token, "test").unwrap(); + assert!(resp.is_none()); + + test_write_user(Arc::clone(&c), &root_token, "pass", "test", "123qwe!@#", 0); + let _ = test_login(Arc::clone(&c), "pass", "test", "123qwe!@#", true); + let _ = test_login(Arc::clone(&c), "pass", "test", "xxxxxxx", false); + let _ = test_login(Arc::clone(&c), "pass", "xxxx", "123qwe!@#", false); + let resp = test_login(Arc::clone(&c), "pass", "test", "123qwe!@#", true); + let login_auth = resp.unwrap().unwrap().auth.unwrap(); + let test_client_token = login_auth.client_token.clone(); + { + let c1 = Arc::clone(&c); + let c2 = c1.read().unwrap(); + let resp = test_read_api(&c2, &test_client_token, "sys/mounts", true); + println!("test mounts resp: {:?}", resp); + assert!(resp.unwrap().is_some()); + } + + test_delete_user(Arc::clone(&c), &root_token, "test"); + let resp = test_login(Arc::clone(&c), "pass", "test", "123qwe!@#", false); + let login_resp = resp.unwrap().unwrap(); + assert!(login_resp.auth.is_none()); + + test_write_user(Arc::clone(&c), &root_token, "pass", "test2", "123qwe", 5); + let resp = test_read_user(Arc::clone(&c), &root_token, "test").unwrap(); + assert!(resp.is_none()); + let resp = test_login(Arc::clone(&c), "pass", "test2", "123qwe", true); + let login_auth = resp.unwrap().unwrap().auth.unwrap(); + println!("user login_auth: {:?}", login_auth); + assert_eq!(login_auth.lease.ttl.as_secs(), 5); + + println!("wait 7s"); + std::thread::sleep(Duration::from_secs(7)); + let test_client_token = login_auth.client_token.clone(); + { + let c1 = Arc::clone(&c); + let c2 = c1.read().unwrap(); + let resp = test_read_api(&c2, &test_client_token, "sys/mounts", false); + println!("test mounts resp: {:?}", resp); + } + + // mount userpass auth to path: auth/testpass + test_mount_userpass_auth(Arc::clone(&c), &root_token, "testpass"); + test_write_user(Arc::clone(&c), &root_token, "testpass", "testuser", "123qwe!@#", 0); + let resp = test_login(Arc::clone(&c), "testpass", "testuser", "123qwe!@#", true); + let login_auth = resp.unwrap().unwrap().auth.unwrap(); + let test_client_token = login_auth.client_token.clone(); + println!("test_client_token: {}", test_client_token); + { + let c1 = Arc::clone(&c); + let c2 = c1.read().unwrap(); + let resp = test_read_api(&c2, &test_client_token, "sys/mounts", true); + println!("test mounts resp: {:?}", resp); + assert!(resp.unwrap().is_some()); + } + } + } +} diff --git a/src/modules/credential/userpass/path_login.rs b/src/modules/credential/userpass/path_login.rs new file mode 100644 index 00000000..744d4324 --- /dev/null +++ b/src/modules/credential/userpass/path_login.rs @@ -0,0 +1,78 @@ +use std::{collections::HashMap, sync::Arc}; + +use super::{UserPassBackend, UserPassBackendInner}; +use crate::{ + errors::RvError, + logical::{Auth, Backend, Field, FieldType, Lease, Operation, Path, PathOperation, Request, Response}, + new_fields, new_fields_internal, new_path, new_path_internal, +}; + +impl UserPassBackend { + pub fn login_path(&self) -> Path { + let userpass_backend_ref = Arc::clone(&self.inner); + + let path = new_path!({ + pattern: r"login/(?P\w[\w-]+\w)", + fields: { + "username": { + field_type: FieldType::Str, + required: true, + description: "Username of the user." + }, + "password": { + field_type: FieldType::SecretStr, + required: true, + description: "Password for this user." + } + }, + operations: [ + {op: Operation::Write, handler: userpass_backend_ref.login} + ], + help: r#"This endpoint authenticates using a username and password."# + }); + + path + } +} + +impl UserPassBackendInner { + pub fn login(&self, _backend: &dyn Backend, req: &mut Request) -> Result, RvError> { + let err_info = "invalid username or password"; + let username_value = req.get_data("username")?; + let username = username_value.as_str().unwrap().to_lowercase(); + let password_value = req.get_data("password")?; + let password = password_value.as_str().unwrap(); + + let user = self.get_user(req, &username)?; + if user.is_none() { + log::error!("{}", err_info); + let resp = Response::error_response(&err_info); + return Ok(Some(resp)); + } + + let user = user.unwrap(); + + let check = self.verify_password_hash(password, &user.password_hash)?; + if !check { + log::error!("{}", err_info); + let resp = Response::error_response(&err_info); + return Ok(Some(resp)); + } + + let mut auth = Auth { + lease: Lease { + ttl: user.ttl, + max_ttl: user.max_ttl, + renewable: user.ttl.as_secs() > 0, + ..Default::default() + }, + display_name: username.to_string(), + policies: user.policies.clone(), + ..Default::default() + }; + auth.metadata.insert("username".to_string(), username.to_string()); + let resp = Response { auth: Some(auth), ..Response::default() }; + + Ok(Some(resp)) + } +} diff --git a/src/modules/credential/userpass/path_users.rs b/src/modules/credential/userpass/path_users.rs new file mode 100644 index 00000000..4d97e742 --- /dev/null +++ b/src/modules/credential/userpass/path_users.rs @@ -0,0 +1,251 @@ +use std::{collections::HashMap, sync::Arc, time::Duration}; + +use serde::{Deserialize, Serialize}; + +use super::{UserPassBackend, UserPassBackendInner}; +use crate::{ + errors::RvError, + logical::{Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response}, + new_fields, new_fields_internal, new_path, new_path_internal, + storage::StorageEntry, + utils::{deserialize_duration, serialize_duration}, +}; + +//const DEFAULT_MAX_TTL: Duration = Duration::from_secs(365*24*60*60 as u64); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UserEntry { + pub password_hash: String, + pub policies: Vec, + #[serde(serialize_with = "serialize_duration", deserialize_with = "deserialize_duration")] + pub ttl: Duration, + #[serde(serialize_with = "serialize_duration", deserialize_with = "deserialize_duration")] + pub max_ttl: Duration, +} + +impl Default for UserEntry { + fn default() -> Self { + Self { + password_hash: String::new(), + policies: Vec::new(), + ttl: Duration::from_secs(0), + max_ttl: Duration::from_secs(0), + } + } +} + +impl UserPassBackend { + pub fn users_path(&self) -> Path { + let userpass_backend_ref1 = Arc::clone(&self.inner); + let userpass_backend_ref2 = Arc::clone(&self.inner); + let userpass_backend_ref3 = Arc::clone(&self.inner); + + let path = new_path!({ + pattern: r"users/(?P\w[\w-]+\w)", + fields: { + "username": { + field_type: FieldType::Str, + required: true, + description: "Username of the user." + }, + "password": { + field_type: FieldType::SecretStr, + required: false, + description: "Password for this user." + }, + "policies": { + field_type: FieldType::Str, + required: false, + description: "Policies for this user." + }, + "ttl": { + field_type: FieldType::Int, + default: 0, + description: "TTL for this user." + }, + "max_ttl": { + field_type: FieldType::Int, + default: 0, + description: "TTL for this user." + } + }, + operations: [ + {op: Operation::Read, handler: userpass_backend_ref1.read_user}, + {op: Operation::Write, handler: userpass_backend_ref2.write_user}, + {op: Operation::Delete, handler: userpass_backend_ref3.delete_user} + ], + help: r#" +This endpoint allows you to create, read, update, and delete users +that are allowed to authenticate. +Deleting a user will not revoke auth for prior authenticated users +with that name. To do this, do a revoke on "login/" for +the username you want revoked. If you don't need to revoke login immediately, +then the next renew will cause the lease to expire. + "# + }); + + path + } + + pub fn user_list_path(&self) -> Path { + let userpass_backend_ref = Arc::clone(&self.inner); + + let path = new_path!({ + pattern: r"users/?", + operations: [ + {op: Operation::List, handler: userpass_backend_ref.list_user} + ], + help: r#"This endpoint allows you to list users"# + }); + + path + } + + pub fn user_password_path(&self) -> Path { + let userpass_backend_ref = Arc::clone(&self.inner); + + let path = new_path!({ + pattern: r"users/(?P\w[\w-]+\w)/password$", + fields: { + "username": { + field_type: FieldType::Str, + required: true, + description: "Username of the user." + }, + "password": { + field_type: FieldType::SecretStr, + required: true, + description: "Password for this user." + } + }, + operations: [ + {op: Operation::Write, handler: userpass_backend_ref.write_user_password} + ], + help: r#"This endpoint allows resetting the user's password."# + }); + + path + } +} + +impl UserPassBackendInner { + pub fn get_user(&self, req: &mut Request, name: &str) -> Result, RvError> { + let key = format!("user/{}", name.to_lowercase()); + let storage_entry = req.storage_get(&key)?; + if storage_entry.is_none() { + return Ok(None); + } + + let entry = storage_entry.unwrap(); + let user_entry: UserEntry = serde_json::from_slice(entry.value.as_slice())?; + Ok(Some(user_entry)) + } + + pub fn set_user(&self, req: &mut Request, name: &str, user_entry: &UserEntry) -> Result<(), RvError> { + let entry = StorageEntry::new(format!("user/{}", name).as_str(), user_entry)?; + + req.storage_put(&entry) + } + + pub fn read_user(&self, _backend: &dyn Backend, req: &mut Request) -> Result, RvError> { + let username_value = req.get_data("username")?; + let username = username_value.as_str().unwrap().to_lowercase(); + + let entry = self.get_user(req, &username)?; + if entry.is_none() { + return Ok(None); + } + + let user_entry = entry.unwrap(); + let mut user_entry_data = serde_json::to_value(&user_entry)?; + let data = user_entry_data.as_object_mut().unwrap(); + data.remove("password_hash"); + Ok(Some(Response::data_response(Some(data.clone())))) + } + + pub fn write_user(&self, _backend: &dyn Backend, req: &mut Request) -> Result, RvError> { + let username_value = req.get_data("username")?; + let username = username_value.as_str().unwrap(); + + let mut user_entry = UserEntry::default(); + + let entry = self.get_user(req, username)?; + if entry.is_some() { + user_entry = entry.unwrap(); + } + + let password_value = req.get_data("password")?; + let password = password_value.as_str().unwrap(); + if password != "" { + let password_hash = self.gen_password_hash(password)?; + + user_entry.password_hash = password_hash; + } + + let ttl_value = req.get_data("ttl")?; + let ttl = ttl_value.as_u64().unwrap(); + if ttl > 0 { + user_entry.ttl = Duration::from_secs(ttl); + } + + let max_ttl_value = req.get_data("max_ttl")?; + let max_ttl = max_ttl_value.as_u64().unwrap(); + if max_ttl > 0 { + user_entry.max_ttl = Duration::from_secs(max_ttl); + } + + self.set_user(req, username, &user_entry)?; + + Ok(None) + } + + pub fn delete_user(&self, _backend: &dyn Backend, req: &mut Request) -> Result, RvError> { + let username_value = req.get_data("username")?; + let username = username_value.as_str().unwrap(); + if username == "" { + return Err(RvError::ErrRequestNoDataField); + } + + req.storage_delete(format!("user/{}", username.to_lowercase()).as_str())?; + Ok(None) + } + + pub fn list_user(&self, _backend: &dyn Backend, req: &mut Request) -> Result, RvError> { + let users = req.storage_list("user/")?; + let resp = Response::list_response(&users); + Ok(Some(resp)) + } + + pub fn write_user_password(&self, _backend: &dyn Backend, req: &mut Request) -> Result, RvError> { + let username_value = req.get_data("username")?; + let username = username_value.as_str().unwrap(); + + let mut user_entry = UserEntry::default(); + + let entry = self.get_user(req, username)?; + if entry.is_some() { + user_entry = entry.unwrap(); + } + + let password_value = req.get_data("password")?; + let password = password_value.as_str().unwrap(); + + let password_hash = self.gen_password_hash(password)?; + + user_entry.password_hash = password_hash; + + self.set_user(req, username, &user_entry)?; + + Ok(None) + } + + pub fn gen_password_hash(&self, password: &str) -> Result { + let pwd_hash = bcrypt::hash(password, bcrypt::DEFAULT_COST)?; + Ok(pwd_hash) + } + + pub fn verify_password_hash(&self, password: &str, password_hash: &str) -> Result { + let result = bcrypt::verify(password, password_hash)?; + Ok(result) + } +} diff --git a/src/modules/kv/mod.rs b/src/modules/kv/mod.rs index 571c0e15..b7c820b3 100644 --- a/src/modules/kv/mod.rs +++ b/src/modules/kv/mod.rs @@ -15,7 +15,8 @@ use crate::{ secret::Secret, Backend, Field, FieldType, LogicalBackend, Operation, Path, PathOperation, Request, Response, }, modules::Module, - new_logical_backend, new_logical_backend_internal, new_path, new_path_internal, new_secret, new_secret_internal, new_fields, new_fields_internal, + new_fields, new_fields_internal, new_logical_backend, new_logical_backend_internal, new_path, new_path_internal, + new_secret, new_secret_internal, storage::StorageEntry, }; @@ -90,7 +91,7 @@ impl KvBackend { renew_handler: kv_backend_renew.handle_read, revoke_handler: kv_backend_revoke.handle_noop, }], - help: KV_BACKEND_HELP + help: KV_BACKEND_HELP, }); backend diff --git a/src/modules/mod.rs b/src/modules/mod.rs index b19d1096..c775ec51 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -3,6 +3,7 @@ use as_any::AsAny; use crate::{core::Core, errors::RvError}; pub mod auth; +pub mod credential; pub mod kv; pub mod pki; pub mod system; diff --git a/src/modules/pki/field.rs b/src/modules/pki/field.rs index 6b5547aa..de6e947c 100644 --- a/src/modules/pki/field.rs +++ b/src/modules/pki/field.rs @@ -1,7 +1,4 @@ -use std::{ - collections::HashMap, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use crate::{ logical::{Field, FieldType}, @@ -14,7 +11,7 @@ pub fn ca_common_fields() -> HashMap> { field_type: FieldType::Str, required: false, description: r#"The requested Subject Alternative Names, if any, -in a comma-delimited list. May contain both DNS names and email addresses."# + in a comma-delimited list. May contain both DNS names and email addresses."# }, "common_name": { field_type: FieldType::Str, @@ -38,7 +35,7 @@ CA cert, not when generating a CSR for an intermediate CA. field_type: FieldType::Int, default: 30, description: r#" -The duration before now which the certificate needs to be backdated by."# + The duration before now which the certificate needs to be backdated by."# }, "not_after": { field_type: FieldType::Str, @@ -110,7 +107,7 @@ chance to retrieve the private key!"# field_type: FieldType::Str, default: "rsa", description: r#"The type of key to use; defaults to RSA. "rsa" "ec", -"ed25519" and "any" are the only valid values."# + "ed25519" and "any" are the only valid values."# }, "key_bits": { field_type: FieldType::Int, @@ -131,7 +128,7 @@ the curve size for NIST P-Curves)."# field_type: FieldType::Bool, default: false, description: r#"Whether or not to use PSS signatures when using a -RSA key-type issuer. Defaults to false."# + RSA key-type issuer. Defaults to false."# } }); diff --git a/src/modules/pki/mod.rs b/src/modules/pki/mod.rs index 3c68b1c4..73f20a06 100644 --- a/src/modules/pki/mod.rs +++ b/src/modules/pki/mod.rs @@ -7,14 +7,11 @@ use std::{ use crate::{ core::Core, errors::RvError, - logical::{ - secret::Secret, Backend, LogicalBackend, Request, Response, - }, + logical::{secret::Secret, Backend, LogicalBackend, Request, Response}, modules::Module, new_logical_backend, new_logical_backend_internal, new_secret, new_secret_internal, }; -pub mod util; pub mod field; pub mod path_config_ca; pub mod path_config_crl; @@ -24,6 +21,7 @@ pub mod path_keys; pub mod path_revoke; pub mod path_roles; pub mod path_root; +pub mod util; static PKI_BACKEND_HELP: &str = r#" The PKI backend dynamically generates X509 server and client certificates. @@ -79,7 +77,7 @@ impl PkiBackend { revoke_handler: pki_backend_ref1.revoke_secret_creds, renew_handler: pki_backend_ref2.renew_secret_creds, }], - help: PKI_BACKEND_HELP + help: PKI_BACKEND_HELP, }); backend.paths.push(Arc::new(self.roles_path())); @@ -153,7 +151,7 @@ mod test { }; use go_defer::defer; - use openssl::{asn1::Asn1Time, ec::EcKey, pkey::PKey, rsa::Rsa, x509::X509, nid::Nid}; + use openssl::{asn1::Asn1Time, ec::EcKey, nid::Nid, pkey::PKey, rsa::Rsa, x509::X509}; use serde_json::{json, Map, Value}; use super::*; @@ -1499,12 +1497,7 @@ xxxxxxxxxxxxxx fn test_pki_delete_root(core: Arc>, token: &str, is_ok: bool) { let core = core.read().unwrap(); - let resp = test_delete_api( - &core, - token, - "pki/root", - is_ok - ); + let resp = test_delete_api(&core, token, "pki/root", is_ok); if !is_ok { return; } diff --git a/src/modules/pki/path_config_ca.rs b/src/modules/pki/path_config_ca.rs index 4e699fdf..56b41d8a 100644 --- a/src/modules/pki/path_config_ca.rs +++ b/src/modules/pki/path_config_ca.rs @@ -1,7 +1,5 @@ -use std::{ - sync::Arc, - collections::HashMap, -}; +use std::{collections::HashMap, sync::Arc}; + use openssl::{ pkey::{Id, PKey}, x509::X509, @@ -11,12 +9,10 @@ use pem; use super::{PkiBackend, PkiBackendInner}; use crate::{ errors::RvError, - logical::{ - Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response, - }, + logical::{Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response}, + new_fields, new_fields_internal, new_path, new_path_internal, storage::StorageEntry, utils::{cert, cert::CertBundle}, - new_path, new_path_internal, new_fields, new_fields_internal, }; impl PkiBackend { @@ -104,10 +100,7 @@ impl PkiBackendInner { self.store_ca_bundle(req, &cert_bundle)?; - let entry = StorageEntry { - key: "crl".to_string(), - value: Vec::new(), - }; + let entry = StorageEntry { key: "crl".to_string(), value: Vec::new() }; req.storage_put(&entry)?; diff --git a/src/modules/pki/path_config_crl.rs b/src/modules/pki/path_config_crl.rs index c8874126..4e7d23bc 100644 --- a/src/modules/pki/path_config_crl.rs +++ b/src/modules/pki/path_config_crl.rs @@ -1,15 +1,10 @@ -use std::{ - sync::Arc, - collections::HashMap, -}; +use std::{collections::HashMap, sync::Arc}; use super::{PkiBackend, PkiBackendInner}; use crate::{ errors::RvError, - logical::{ - Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response, - }, - new_path, new_path_internal, new_fields, new_fields_internal, + logical::{Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response}, + new_fields, new_fields_internal, new_path, new_path_internal, }; impl PkiBackend { diff --git a/src/modules/pki/path_fetch.rs b/src/modules/pki/path_fetch.rs index 44e73fd8..4e2d954a 100644 --- a/src/modules/pki/path_fetch.rs +++ b/src/modules/pki/path_fetch.rs @@ -1,19 +1,15 @@ -use std::{ - sync::Arc, - collections::HashMap, -}; +use std::{collections::HashMap, sync::Arc}; + +use openssl::x509::X509; use serde_json::json; -use openssl::{x509::X509}; use super::{PkiBackend, PkiBackendInner}; use crate::{ errors::RvError, - utils::cert::CertBundle, - logical::{ - Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response, - }, + logical::{Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response}, + new_fields, new_fields_internal, new_path, new_path_internal, storage::StorageEntry, - new_path, new_path_internal, new_fields, new_fields_internal, + utils::cert::CertBundle, }; impl PkiBackend { @@ -174,10 +170,7 @@ impl PkiBackendInner { pub fn store_cert(&self, req: &Request, serial_number: &str, cert: &X509) -> Result<(), RvError> { let value = cert.to_der()?; - let entry = StorageEntry { - key: format!("certs/{}", serial_number), - value: value - }; + let entry = StorageEntry { key: format!("certs/{}", serial_number), value }; req.storage_put(&entry)?; Ok(()) } diff --git a/src/modules/pki/path_issue.rs b/src/modules/pki/path_issue.rs index afbd759d..1277e7e2 100644 --- a/src/modules/pki/path_issue.rs +++ b/src/modules/pki/path_issue.rs @@ -1,6 +1,6 @@ use std::{ - sync::Arc, collections::HashMap, + sync::Arc, time::{Duration, SystemTime, UNIX_EPOCH}, }; @@ -11,12 +11,9 @@ use serde_json::{json, Map, Value}; use super::{PkiBackend, PkiBackendInner}; use crate::{ errors::RvError, - logical::{ - Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response, - }, - utils, - utils::{cert}, - new_path, new_path_internal, new_fields, new_fields_internal, + logical::{Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response}, + new_fields, new_fields_internal, new_path, new_path_internal, utils, + utils::cert, }; impl PkiBackend { @@ -33,13 +30,13 @@ impl PkiBackend { "common_name": { field_type: FieldType::Str, description: r#" -The requested common name; if you want more than one, specify the alternative names in the alt_names map"# + The requested common name; if you want more than one, specify the alternative names in the alt_names map"# }, "alt_names": { required: false, field_type: FieldType::Str, description: r#" -The requested Subject Alternative Names, if any, in a comma-delimited list"# + The requested Subject Alternative Names, if any, in a comma-delimited list"# }, "ip_sans": { required: false, diff --git a/src/modules/pki/path_keys.rs b/src/modules/pki/path_keys.rs index 6c8079cd..6123267b 100644 --- a/src/modules/pki/path_keys.rs +++ b/src/modules/pki/path_keys.rs @@ -1,19 +1,15 @@ -use std::{ - sync::Arc, - collections::HashMap, -}; +use std::{collections::HashMap, sync::Arc}; + use openssl::{ec::EcKey, rsa::Rsa}; use serde_json::{json, Value}; use super::{PkiBackend, PkiBackendInner}; use crate::{ errors::RvError, - logical::{ - Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response, - }, + logical::{Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response}, + new_fields, new_fields_internal, new_path, new_path_internal, storage::StorageEntry, utils::key::KeyBundle, - new_path, new_path_internal, new_fields, new_fields_internal, }; const PKI_CONFIG_KEY_PREFIX: &str = "config/key/"; diff --git a/src/modules/pki/path_revoke.rs b/src/modules/pki/path_revoke.rs index b5753350..7423c9ac 100644 --- a/src/modules/pki/path_revoke.rs +++ b/src/modules/pki/path_revoke.rs @@ -1,15 +1,10 @@ -use std::{ - sync::Arc, - collections::HashMap, -}; +use std::{collections::HashMap, sync::Arc}; use super::{PkiBackend, PkiBackendInner}; use crate::{ errors::RvError, - logical::{ - Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response, - }, - new_path, new_path_internal, new_fields, new_fields_internal, + logical::{Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response}, + new_fields, new_fields_internal, new_path, new_path_internal, }; impl PkiBackend { diff --git a/src/modules/pki/path_roles.rs b/src/modules/pki/path_roles.rs index f194d70e..3d932341 100644 --- a/src/modules/pki/path_roles.rs +++ b/src/modules/pki/path_roles.rs @@ -1,8 +1,4 @@ -use std::{ - sync::Arc, - collections::HashMap, - time::Duration, -}; +use std::{collections::HashMap, sync::Arc, time::Duration}; use humantime::parse_duration; use serde::{Deserialize, Serialize}; @@ -10,15 +6,13 @@ use serde::{Deserialize, Serialize}; use super::{PkiBackend, PkiBackendInner}; use crate::{ errors::RvError, - logical::{ - Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response, - }, + logical::{Backend, Field, FieldType, Operation, Path, PathOperation, Request, Response}, + new_fields, new_fields_internal, new_path, new_path_internal, storage::StorageEntry, utils::{deserialize_duration, serialize_duration}, - new_path, new_path_internal, new_fields, new_fields_internal, }; -const DEFAULT_MAX_TTL: Duration = Duration::from_secs(365*24*60*60 as u64); +const DEFAULT_MAX_TTL: Duration = Duration::from_secs(365 * 24 * 60 * 60 as u64); #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RoleEntry { @@ -112,13 +106,13 @@ max_ttl, whichever is shorter."# field_type: FieldType::Str, required: true, description: r#" -The maximum allowed lease duration. If not set, defaults to the system maximum lease TTL."# + The maximum allowed lease duration. If not set, defaults to the system maximum lease TTL."# }, "use_pss": { field_type: FieldType::Bool, default: false, description: r#" -Whether or not to use PSS signatures when using a RSA key-type issuer. Defaults to false."# + Whether or not to use PSS signatures when using a RSA key-type issuer. Defaults to false."# }, "allow_localhost": { field_type: FieldType::Bool, @@ -163,30 +157,30 @@ See the documentation for more information."# field_type: FieldType::Bool, default: true, description: r#" -If set, IP Subject Alternative Names are allowed. Any valid IP is accepted and No authorization checking is performed."# + If set, IP Subject Alternative Names are allowed. Any valid IP is accepted and No authorization checking is performed."# }, "server_flag": { field_type: FieldType::Bool, default: true, description: r#" -If set, certificates are flagged for server auth use. defaults to true. See also RFC 5280 Section 4.2.1.12."# + If set, certificates are flagged for server auth use. defaults to true. See also RFC 5280 Section 4.2.1.12."# }, "client_flag": { field_type: FieldType::Bool, default: true, description: r#" -If set, certificates are flagged for client auth use. defaults to true. See also RFC 5280 Section 4.2.1.12."# + If set, certificates are flagged for client auth use. defaults to true. See also RFC 5280 Section 4.2.1.12."# }, "code_signing_flag": { field_type: FieldType::Bool, description: r#" -If set, certificates are flagged for code signing use. defaults to false. See also RFC 5280 Section 4.2.1.12."# + If set, certificates are flagged for code signing use. defaults to false. See also RFC 5280 Section 4.2.1.12."# }, "key_type": { field_type: FieldType::Str, default: "rsa", description: r#" -The type of key to use; defaults to RSA. "rsa" "ec", "ed25519" and "any" are the only valid values."# + The type of key to use; defaults to RSA. "rsa" "ec", "ed25519" and "any" are the only valid values."# }, "key_bits": { field_type: FieldType::Int, @@ -208,7 +202,7 @@ based on key length (SHA-2-256 for RSA keys, and matching the curve size for NIS field_type: FieldType::Int, default: 30, description: r#" -The duration before now which the certificate needs to be backdated by."# + The duration before now which the certificate needs to be backdated by."# }, "not_after": { field_type: FieldType::Str, @@ -221,43 +215,43 @@ The value format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ."# required: false, field_type: FieldType::Str, description: r#" -If set, OU (OrganizationalUnit) will be set to this value in certificates issued by this role."# + If set, OU (OrganizationalUnit) will be set to this value in certificates issued by this role."# }, "organization": { required: false, field_type: FieldType::Str, description: r#" -If set, O (Organization) will be set to this value in certificates issued by this role."# + If set, O (Organization) will be set to this value in certificates issued by this role."# }, "country": { required: false, field_type: FieldType::Str, description: r#" -If set, Country will be set to this value in certificates issued by this role."# + If set, Country will be set to this value in certificates issued by this role."# }, "locality": { required: false, field_type: FieldType::Str, description: r#" -If set, Locality will be set to this value in certificates issued by this role."# + If set, Locality will be set to this value in certificates issued by this role."# }, "province": { required: false, field_type: FieldType::Str, description: r#" -If set, Province will be set to this value in certificates issued by this role."# + If set, Province will be set to this value in certificates issued by this role."# }, "street_address": { required: false, field_type: FieldType::Str, description: r#" -If set, Street Address will be set to this value."# + If set, Street Address will be set to this value."# }, "postal_code": { required: false, field_type: FieldType::Str, description: r#" -If set, Postal Code will be set to this value."# + If set, Postal Code will be set to this value."# }, "use_csr_common_name": { field_type: FieldType::Bool, diff --git a/src/modules/pki/path_root.rs b/src/modules/pki/path_root.rs index fe58c4de..c74f5b46 100644 --- a/src/modules/pki/path_root.rs +++ b/src/modules/pki/path_root.rs @@ -1,18 +1,12 @@ -use std::{ - collections::HashMap, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use serde_json::{json, Value}; -use super::{util, field, PkiBackend, PkiBackendInner}; +use super::{field, util, PkiBackend, PkiBackendInner}; use crate::{ errors::RvError, - logical::{ - Backend, Operation, Path, PathOperation, Request, Response, - }, - new_path, new_path_internal, - utils, + logical::{Backend, Operation, Path, PathOperation, Request, Response}, + new_path, new_path_internal, utils, }; impl PkiBackend { @@ -81,10 +75,13 @@ impl PkiBackendInner { .clone(); if export_private_key { - resp_data.insert("private_key".to_string(), - Value::String(String::from_utf8_lossy(&cert_bundle.private_key.private_key_to_pem_pkcs8()?).to_string())); - resp_data.insert("private_key_type".to_string(), - Value::String(cert_bundle.private_key_type.clone())); + resp_data.insert( + "private_key".to_string(), + Value::String( + String::from_utf8_lossy(&cert_bundle.private_key.private_key_to_pem_pkcs8()?).to_string(), + ), + ); + resp_data.insert("private_key_type".to_string(), Value::String(cert_bundle.private_key_type.clone())); } Ok(Some(Response::data_response(Some(resp_data)))) diff --git a/src/modules/pki/util.rs b/src/modules/pki/util.rs index b325d7e2..eb7fd62a 100644 --- a/src/modules/pki/util.rs +++ b/src/modules/pki/util.rs @@ -1,18 +1,10 @@ -use std::{ - time::{Duration, SystemTime}, -}; +use std::time::{Duration, SystemTime}; use humantime::{parse_duration, parse_rfc3339}; -use openssl::{x509::X509NameBuilder}; +use openssl::x509::X509NameBuilder; -use super::{path_roles::RoleEntry}; -use crate::{ - errors::RvError, - logical::{ - Request, - }, - utils::cert::Certificate, -}; +use super::path_roles::RoleEntry; +use crate::{errors::RvError, logical::Request, utils::cert::Certificate}; pub fn get_role_params(req: &mut Request) -> Result { let ttl_vale = req.get_data("ttl")?; @@ -93,80 +85,80 @@ pub fn get_role_params(req: &mut Request) -> Result { } pub fn generate_certificate(role_entry: &RoleEntry, req: &mut Request) -> Result { - let mut common_names = Vec::new(); + let mut common_names = Vec::new(); - let common_name_value = req.get_data("common_name")?; - let common_name = common_name_value.as_str().unwrap(); - if common_name != "" { - common_names.push(common_name.to_string()); - } + let common_name_value = req.get_data("common_name")?; + let common_name = common_name_value.as_str().unwrap(); + if common_name != "" { + common_names.push(common_name.to_string()); + } - let alt_names_value = req.get_data("alt_names"); - if alt_names_value.is_ok() { - let alt_names_val = alt_names_value.unwrap(); - let alt_names = alt_names_val.as_str().unwrap(); - if alt_names != "" { - for v in alt_names.split(',') { - common_names.push(v.to_string()); - } + let alt_names_value = req.get_data("alt_names"); + if alt_names_value.is_ok() { + let alt_names_val = alt_names_value.unwrap(); + let alt_names = alt_names_val.as_str().unwrap(); + if alt_names != "" { + for v in alt_names.split(',') { + common_names.push(v.to_string()); } } + } - let mut ip_sans = Vec::new(); - let ip_sans_value = req.get_data("ip_sans"); - if ip_sans_value.is_ok() { - let ip_sans_val = ip_sans_value.unwrap(); - let ip_sans_str = ip_sans_val.as_str().unwrap(); - if ip_sans_str != "" { - for v in ip_sans_str.split(',') { - ip_sans.push(v.to_string()); - } + let mut ip_sans = Vec::new(); + let ip_sans_value = req.get_data("ip_sans"); + if ip_sans_value.is_ok() { + let ip_sans_val = ip_sans_value.unwrap(); + let ip_sans_str = ip_sans_val.as_str().unwrap(); + if ip_sans_str != "" { + for v in ip_sans_str.split(',') { + ip_sans.push(v.to_string()); } } + } - let not_before = SystemTime::now() - Duration::from_secs(10); - let not_after: SystemTime; - if role_entry.not_after.len() > 18 { - let parsed_time = parse_rfc3339(&role_entry.not_after)?; - not_after = parsed_time.into(); + let not_before = SystemTime::now() - Duration::from_secs(10); + let not_after: SystemTime; + if role_entry.not_after.len() > 18 { + let parsed_time = parse_rfc3339(&role_entry.not_after)?; + not_after = parsed_time.into(); + } else { + if role_entry.ttl != Duration::from_secs(0) { + not_after = not_before + role_entry.ttl; } else { - if role_entry.ttl != Duration::from_secs(0) { - not_after = not_before + role_entry.ttl; - } else { - not_after = not_before + role_entry.max_ttl; - } + not_after = not_before + role_entry.max_ttl; } + } - let mut subject_name = X509NameBuilder::new().unwrap(); - if role_entry.country.len() > 0 { - subject_name.append_entry_by_text("C", &role_entry.country).unwrap(); - } - if role_entry.province.len() > 0 { - subject_name.append_entry_by_text("ST", &role_entry.province).unwrap(); - } - if role_entry.locality.len() > 0 { - subject_name.append_entry_by_text("L", &role_entry.locality).unwrap(); - } - if role_entry.organization.len() > 0 { - subject_name.append_entry_by_text("O", &role_entry.organization).unwrap(); - } - if role_entry.ou.len() > 0 { - subject_name.append_entry_by_text("OU", &role_entry.ou).unwrap(); - } - if common_name != "" { - subject_name.append_entry_by_text("CN", common_name).unwrap(); - } - let subject = subject_name.build(); - - let cert = Certificate { - not_before, - not_after, - subject, - dns_sans: common_names, - ip_sans, - key_bits: role_entry.key_bits, - ..Default::default() - }; - - Ok(cert) + let mut subject_name = X509NameBuilder::new().unwrap(); + if role_entry.country.len() > 0 { + subject_name.append_entry_by_text("C", &role_entry.country).unwrap(); + } + if role_entry.province.len() > 0 { + subject_name.append_entry_by_text("ST", &role_entry.province).unwrap(); + } + if role_entry.locality.len() > 0 { + subject_name.append_entry_by_text("L", &role_entry.locality).unwrap(); + } + if role_entry.organization.len() > 0 { + subject_name.append_entry_by_text("O", &role_entry.organization).unwrap(); + } + if role_entry.ou.len() > 0 { + subject_name.append_entry_by_text("OU", &role_entry.ou).unwrap(); + } + if common_name != "" { + subject_name.append_entry_by_text("CN", common_name).unwrap(); + } + let subject = subject_name.build(); + + let cert = Certificate { + not_before, + not_after, + subject, + dns_sans: common_names, + ip_sans, + key_bits: role_entry.key_bits, + ..Default::default() + }; + + Ok(cert) } diff --git a/src/modules/system/mod.rs b/src/modules/system/mod.rs index d4102345..d6c26220 100644 --- a/src/modules/system/mod.rs +++ b/src/modules/system/mod.rs @@ -13,7 +13,7 @@ use crate::{ logical::{Backend, Field, FieldType, LogicalBackend, Operation, Path, PathOperation, Request, Response}, modules::{auth::AuthModule, Module}, mount::MountEntry, - new_logical_backend, new_logical_backend_internal, new_path, new_path_internal, new_fields, new_fields_internal, + new_fields, new_fields_internal, new_logical_backend, new_logical_backend_internal, new_path, new_path_internal, storage::StorageEntry, }; @@ -257,7 +257,7 @@ impl SystemBackend { } ], root_paths: ["mounts/*", "auth/*", "remount", "policy", "policy/*", "audit", "audit/*", "seal", "raw/*", "revoke-prefix/*"], - help: SYSTEM_BACKEND_HELP + help: SYSTEM_BACKEND_HELP, }); backend diff --git a/src/utils/cert.rs b/src/utils/cert.rs index c7d75b2f..e9a594a6 100644 --- a/src/utils/cert.rs +++ b/src/utils/cert.rs @@ -13,10 +13,10 @@ use openssl::{ rsa::Rsa, x509::{ extension::{ - AuthorityKeyIdentifier, BasicConstraints, KeyUsage, ExtendedKeyUsage, - SubjectAlternativeName, SubjectKeyIdentifier + AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, SubjectAlternativeName, + SubjectKeyIdentifier, }, - X509Builder, X509Extension, X509Name, X509NameBuilder, X509, X509Ref, + X509Builder, X509Extension, X509Name, X509NameBuilder, X509Ref, X509, }, }; use serde::{ser::SerializeTuple, Deserialize, Deserializer, Serialize, Serializer}; @@ -267,26 +267,20 @@ impl Certificate { if self.is_ca { builder.append_extension(BasicConstraints::new().critical().ca().build()?)?; - builder.append_extension( - KeyUsage::new().critical().key_cert_sign().crl_sign().build()?, - )?; + builder.append_extension(KeyUsage::new().critical().key_cert_sign().crl_sign().build()?)?; } else { builder.append_extension(BasicConstraints::new().critical().build()?)?; builder.append_extension( KeyUsage::new().critical().non_repudiation().digital_signature().key_encipherment().build()?, )?; - builder.append_extension( - ExtendedKeyUsage::new().server_auth().client_auth().build()?, - )?; + builder.append_extension(ExtendedKeyUsage::new().server_auth().client_auth().build()?)?; } let subject_key_id = SubjectKeyIdentifier::new().build(&builder.x509v3_context(ca_cert, None))?; builder.append_extension(subject_key_id)?; - let authority_key_id = AuthorityKeyIdentifier::new() - .keyid(true) - .issuer(false) - .build(&builder.x509v3_context(ca_cert, None))?; + let authority_key_id = + AuthorityKeyIdentifier::new().keyid(true).issuer(false).build(&builder.x509v3_context(ca_cert, None))?; builder.append_extension(authority_key_id)?; if ca_key.is_some() { @@ -298,7 +292,11 @@ impl Certificate { Ok(builder.build()) } - pub fn to_cert_bundle(&mut self, ca_cert: Option<&X509Ref>, ca_key: Option<&PKey>) -> Result { + pub fn to_cert_bundle( + &mut self, + ca_cert: Option<&X509Ref>, + ca_key: Option<&PKey>, + ) -> Result { let key_bits = self.key_bits; let priv_key = match self.key_type.as_str() { "rsa" => {