Skip to content

Commit

Permalink
Add policy module for ACL (#100)
Browse files Browse the repository at this point in the history
* Add policy module for ACL

1. Implemented basic policy storage & fundamental ACL features.
2. Upgraded hcl-rs to version 0.18 and implemented a format compatible with vault policy.
3. Fix the issue of the default functions exiting in the pre_auth and post_auth phases.
4. Add test cases for the policy module.
5. Fix the bugs where the test cases of the userpass and cert modules failed.
6. Add documentation and test cases for utils::string.
  • Loading branch information
wa5i authored Jan 14, 2025
1 parent 82a6971 commit 6acb4ae
Show file tree
Hide file tree
Showing 17 changed files with 4,223 additions and 75 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,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 @@ -66,7 +66,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 @@ -75,6 +75,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
18 changes: 15 additions & 3 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 @@ -704,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 @@ -715,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 @@ -723,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

0 comments on commit 6acb4ae

Please sign in to comment.