Skip to content

Commit

Permalink
Merge pull request Tongsuo-Project#45 from wa5i/credential
Browse files Browse the repository at this point in the history
Added userpass module to enable login and token creation via username and password.
  • Loading branch information
InfoHunter authored Mar 22, 2024
2 parents 1f2d418 + c5a1566 commit 1a8dec7
Show file tree
Hide file tree
Showing 29 changed files with 1,041 additions and 245 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 5 additions & 1 deletion src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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(())
}

Expand Down
5 changes: 5 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)")]
Expand Down
3 changes: 2 additions & 1 deletion src/http/logical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
core: web::Data<Arc<RwLock<Core>>>,
Expand All @@ -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 => {
Expand Down
15 changes: 10 additions & 5 deletions src/http/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Arc<RwLock<Core>>>,
) -> Result<HttpResponse, RvError> {
let payload = serde_json::from_slice::<InitRequest>(&body)?;
body.clear();
let seal_config = SealConfig { secret_shares: payload.secret_shares, secret_threshold: payload.secret_threshold };

let mut core = core.write()?;
Expand Down Expand Up @@ -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<Arc<RwLock<Core>>>,
) -> Result<HttpResponse, RvError> {
// TODO
let payload = serde_json::from_slice::<UnsealRequest>(&body)?;
body.clear();
let key = hex::decode(payload.key)?;

{
Expand All @@ -154,11 +156,12 @@ async fn sys_list_mounts_request_handler(
async fn sys_mount_request_handler(
req: HttpRequest,
path: web::Path<String>,
body: web::Bytes,
mut body: web::Bytes,
core: web::Data<Arc<RwLock<Core>>>,
) -> Result<HttpResponse, RvError> {
let _test = serde_json::from_slice::<MountRequest>(&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, ""));
Expand Down Expand Up @@ -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<Arc<RwLock<Core>>>,
) -> Result<HttpResponse, RvError> {
let _test = serde_json::from_slice::<RemountRequest>(&body)?;
let payload = serde_json::from_slice(&body)?;
body.clear();

let mut r = request_auth(&req);
r.operation = Operation::Write;
Expand All @@ -218,11 +222,12 @@ async fn sys_list_auth_mounts_request_handler(
async fn sys_auth_enable_request_handler(
req: HttpRequest,
path: web::Path<String>,
body: web::Bytes,
mut body: web::Bytes,
core: web::Data<Arc<RwLock<Core>>>,
) -> Result<HttpResponse, RvError> {
let _test = serde_json::from_slice::<MountRequest>(&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, ""));
Expand Down
132 changes: 118 additions & 14 deletions src/logical/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<Response>, RvError> + Send + Sync;

#[derive(Clone)]
pub struct LogicalBackend {
pub paths: Vec<Arc<Path>>,
Expand All @@ -14,6 +16,7 @@ pub struct LogicalBackend {
pub unauth_paths: Arc<Vec<String>>,
pub help: String,
pub secrets: Vec<Arc<Secret>>,
pub auth_renew_handler: Option<Arc<BackendOperationHandler>>,
}

impl Backend for LogicalBackend {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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;
}
}

Expand All @@ -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<Option<Response>, 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<Option<Response>, 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<Option<Response>, RvError> {
Ok(None)
}
Expand All @@ -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]
Expand All @@ -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<Option<Response>, 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
}
};
Expand All @@ -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<Option<Response>, RvError> {
Ok(None)
}
}

#[test]
fn test_logical_backend_match_path() {
let path = "/(?P<aa>.+?)/(?P<bb>.+)";
Expand Down Expand Up @@ -216,6 +302,8 @@ mod test {
assert!(fs::remove_dir_all(&dir).is_ok());
);

let t = MyTest::new();

let mut conf: HashMap<String, Value> = HashMap::new();
conf.insert("path".to_string(), Value::String(dir.to_string_lossy().into_owned()));

Expand All @@ -235,6 +323,10 @@ mod test {
"mypath": {
field_type: FieldType::Str,
description: "hehe"
},
"mypassword": {
field_type: FieldType::SecretStr,
description: "password"
}
},
operations: [
Expand Down Expand Up @@ -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("/");
Expand Down Expand Up @@ -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());
Expand All @@ -330,8 +425,17 @@ mod test {

assert!(logical_backend.handle_request(&mut req).is_err());

let body: Map<String, Value> = 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());
Expand Down
Loading

0 comments on commit 1a8dec7

Please sign in to comment.