Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added userpass module to enable login and token creation via username and password. #45

Merged
merged 6 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
InfoHunter marked this conversation as resolved.
Show resolved Hide resolved
}
_ => {}
}

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
Loading