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

Add policy module for ACL #100

Merged
merged 11 commits into from
Jan 14, 2025
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ regex = "1.11"
clap = { version = "4.5", features = ["wrap_help", "derive", "env", "suggestions"] }
sysexits = { version = "0.7", features = ["std"] }
build-time = "0.1"
hcl-rs = "0.16"
hcl-rs = "0.18"
actix-web = { version = "4.4", features = ["openssl"] }
actix-tls = "3.1"
actix-rt = "2.9"
Expand All @@ -63,7 +63,7 @@ base64 = "0.22"
ipnetwork = "0.20"
blake2b_simd = "1.0"
derive_more = "0.99.17"
dashmap = "5.5"
dashmap = "6.1"
tokio = { version = "1.40", features = ["rt-multi-thread", "macros"] }
ctor = "0.2.8"
better_default = "1.0.5"
Expand All @@ -72,6 +72,8 @@ sysinfo = "0.31.4"
prettytable = "0.10"
rpassword = "7.3"
async-trait = "0.1"
stretto = "0.8"
itertools = "0.14"

# optional dependencies
openssl = { version = "0.10.64", optional = true }
Expand Down
41 changes: 2 additions & 39 deletions src/cli/util.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,5 @@
pub use crate::utils::string::{ensure_no_leading_slash, ensure_no_trailing_slash, ensure_trailing_slash};

pub fn sanitize_path(s: &str) -> String {
ensure_no_trailing_slash(&ensure_no_leading_slash(s))
}

pub fn ensure_trailing_slash(s: &str) -> String {
let s = s.trim();
if s.is_empty() {
return String::new();
}

let mut result = s.to_string();
while !result.is_empty() && !result.ends_with('/') {
result.push('/');
}
result
}

pub fn ensure_no_trailing_slash(s: &str) -> String {
let s = s.trim();
if s.is_empty() {
return String::new();
}

let mut result = s.to_string();
while !result.is_empty() && result.ends_with('/') {
result.pop();
}
result
}

pub fn ensure_no_leading_slash(s: &str) -> String {
let s = s.trim();
if s.is_empty() {
return String::new();
}

let mut result = s.to_string();
while !result.is_empty() && result.starts_with('/') {
result.remove(0);
}
result
}
9 changes: 6 additions & 3 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::{
auth::AuthModule,
credential::{approle::AppRoleModule, cert::CertModule, userpass::UserPassModule},
pki::PkiModule,
policy::PolicyModule,
},
mount::MountTable,
router::Router,
Expand Down Expand Up @@ -119,6 +120,10 @@ impl Core {
let auth_module = AuthModule::new(self);
self.module_manager.add_module(Arc::new(RwLock::new(Box::new(auth_module))))?;

// add policy_module
let policy_module = PolicyModule::new(self);
self.module_manager.add_module(Arc::new(RwLock::new(Box::new(policy_module))))?;

// add pki_module
let pki_module = PkiModule::new(self);
self.module_manager.add_module(Arc::new(RwLock::new(Box::new(pki_module))))?;
Expand All @@ -135,9 +140,7 @@ impl Core {
let cert_module = CertModule::new(self);
self.module_manager.add_module(Arc::new(RwLock::new(Box::new(cert_module))))?;

let handlers = {
self.handlers.read()?.clone()
};
let handlers = { self.handlers.read()?.clone() };
for handler in handlers.iter() {
match handler.post_config(self, config) {
Ok(_) => {
Expand Down
23 changes: 23 additions & 0 deletions src/logical/auth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashMap;

use better_default::Default;
use derive_more::{Deref, DerefMut};
use serde::{Deserialize, Serialize};

Expand All @@ -26,6 +27,9 @@ pub struct Auth {
// Policies is the list of policies that the authenticated user is associated with.
pub policies: Vec<String>,

// token_policies break down the list in policies to help determine where a policy was sourced
pub token_policies: Vec<String>,

// Indicates that the default policy should not be added by core when creating a token.
// The default policy will still be added if it's explicitly defined.
pub no_default_policy: bool,
Expand All @@ -37,4 +41,23 @@ pub struct Auth {
// Metadata is used to attach arbitrary string-type metadata to an authenticated user.
// This metadata will be outputted into the audit log.
pub metadata: HashMap<String, String>,

// policy_results is the set of policies that grant the token access to the requesting path.
pub policy_results: Option<PolicyResults>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PolicyResults {
pub allowed: bool,
pub granting_policies: Vec<PolicyInfo>,
}

#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PolicyInfo {
pub name: String,
pub namespace_id: String,
pub namespace_path: String,
#[serde(rename = "type")]
#[default("acl".into())]
pub policy_type: String,
}
6 changes: 6 additions & 0 deletions src/logical/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ impl Request {
field.get_default()
}

pub fn data_iter(&self) -> impl Iterator<Item = (&String, &Value)> {
let data_iter = self.data.as_ref().into_iter().flat_map(|m| m.iter());
let body_iter = self.body.as_ref().into_iter().flat_map(|m| m.iter());
data_iter.chain(body_iter)
}

//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() {
Expand Down
34 changes: 27 additions & 7 deletions src/modules/auth/token_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,17 @@ impl TokenStoreInner {

self.use_token(&mut entry)?;

let auth = Auth {
let mut auth = Auth {
client_token: token.to_string(),
display_name: entry.display_name,
token_policies: entry.policies.clone(),
policies: entry.policies.clone(),
metadata: entry.meta,
..Auth::default()
};

sanitize_policies(&mut auth.policies, false);

Ok(Some(auth))
}

Expand Down Expand Up @@ -615,9 +618,14 @@ impl Handler for TokenStore {
};

for auth_handler in auth_handlers.iter() {
if let Some(ret) = auth_handler.pre_auth(req).await? {
auth = Some(ret);
break;
match auth_handler.pre_auth(req).await {
Ok(Some(ret)) => {
auth = Some(ret);
break;
}
Ok(None) | Err(RvError::ErrHandlerDefault) => continue,
Err(e) => return Err(e),

}
}

Expand All @@ -635,7 +643,10 @@ impl Handler for TokenStore {
req.handle_phase = HandlePhase::PostAuth;

for auth_handler in auth_handlers.iter() {
auth_handler.post_auth(req).await?;
match auth_handler.post_auth(req).await {
Ok(()) | Err(RvError::ErrHandlerDefault) => continue,
Err(e) => return Err(e),
}
}

Ok(None)
Expand Down Expand Up @@ -696,9 +707,15 @@ impl Handler for TokenStore {
auth.ttl = MAX_LEASE_DURATION_SECS;
}

sanitize_policies(&mut auth.policies, !auth.no_default_policy);

if auth.policies.contains(&"root".to_string()) {
auth.token_policies = auth.policies.clone();
sanitize_policies(&mut auth.token_policies, !auth.no_default_policy);

let all_policies = auth.token_policies.clone();

// TODO: add identity_policies to all_policies

if all_policies.contains(&"root".to_string()) {
return Err(rv_error_response!("auth methods cannot create root tokens"));
}

Expand All @@ -707,6 +724,7 @@ impl Handler for TokenStore {
meta: auth.metadata.clone(),
display_name: auth.display_name.clone(),
ttl: auth.ttl.as_secs(),
policies: auth.token_policies.clone(),
..Default::default()
};

Expand All @@ -715,6 +733,8 @@ impl Handler for TokenStore {
auth.client_token = te.id.clone();

self.expiration.register_auth(&req.path, auth)?;

auth.policies = all_policies;
}

Ok(())
Expand Down
6 changes: 3 additions & 3 deletions src/modules/credential/userpass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ mod test {
let resp = test_login(&core, "pass", "test", "123qwe!@#", true).await;
let login_auth = resp.unwrap().unwrap().auth.unwrap();
let test_client_token = login_auth.client_token.clone();
let resp = test_read_api(&core, &test_client_token, "sys/mounts", true).await;
let resp = test_read_api(&core, &test_client_token, "auth/token/lookup-self", true).await;
println!("test mounts resp: {:?}", resp);
assert!(resp.unwrap().is_some());

Expand All @@ -227,7 +227,7 @@ mod test {
println!("wait 7s");
std::thread::sleep(Duration::from_secs(7));
let test_client_token = login_auth.client_token.clone();
let resp = test_read_api(&core, &test_client_token, "sys/mounts", false).await;
let resp = test_read_api(&core, &test_client_token, "auth/token/lookup-self", false).await;
println!("test mounts resp: {:?}", resp);

// mount userpass auth to path: auth/testpass
Expand All @@ -237,7 +237,7 @@ mod test {
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 resp = test_read_api(&core, &test_client_token, "sys/mounts", true).await;
let resp = test_read_api(&core, &test_client_token, "auth/token/lookup-self", true).await;
println!("test mounts resp: {:?}", resp);
assert!(resp.unwrap().is_some());
}
Expand Down
1 change: 1 addition & 0 deletions src/modules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod crypto;
pub mod kv;
pub mod pki;
pub mod system;
pub mod policy;

pub trait Module: AsAny + Send + Sync {
//! Description for a trait itself.
Expand Down
Loading
Loading