acl api: implement update
This commit is contained in:
parent
ed3e60ae69
commit
9765092ede
@ -1,8 +1,7 @@
|
||||
use failure::*;
|
||||
use serde_json::Value;
|
||||
use ::serde::{Deserialize, Serialize};
|
||||
|
||||
use proxmox::api::{api, ApiMethod, Router, RpcEnvironment};
|
||||
use proxmox::api::{api, Router, RpcEnvironment};
|
||||
use proxmox::api::schema::{Schema, StringSchema, BooleanSchema, ApiStringFormat};
|
||||
|
||||
use crate::api2::types::*;
|
||||
@ -27,7 +26,14 @@ pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
|
||||
|
||||
pub const ACL_ROLE_SCHEMA: Schema = StringSchema::new(
|
||||
"Role.")
|
||||
.format(&ApiStringFormat::Enum(&["Admin", "User", "Audit", "NoAccess"]))
|
||||
.format(&ApiStringFormat::Enum(&[
|
||||
"Admin",
|
||||
"Audit",
|
||||
"Datastore.Admin",
|
||||
"Datastore.Audit",
|
||||
"Datastore.User",
|
||||
"NoAccess",
|
||||
]))
|
||||
.schema();
|
||||
|
||||
#[api(
|
||||
@ -109,7 +115,8 @@ pub fn read_acl(
|
||||
|
||||
//let auth_user = rpcenv.get_user().unwrap();
|
||||
|
||||
let (tree, digest) = acl::config()?;
|
||||
// fixme: return digest?
|
||||
let (tree, _digest) = acl::config()?;
|
||||
|
||||
let mut list: Vec<AclListItem> = Vec::new();
|
||||
extract_acl_node_data(&tree.root, "", &mut list);
|
||||
@ -117,5 +124,86 @@ pub fn read_acl(
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
#[api(
|
||||
input: {
|
||||
properties: {
|
||||
path: {
|
||||
schema: ACL_PATH_SCHEMA,
|
||||
},
|
||||
role: {
|
||||
schema: ACL_ROLE_SCHEMA,
|
||||
},
|
||||
propagate: {
|
||||
optional: true,
|
||||
schema: ACL_PROPAGATE_SCHEMA,
|
||||
},
|
||||
userid: {
|
||||
optional: true,
|
||||
schema: PROXMOX_USER_ID_SCHEMA,
|
||||
},
|
||||
group: {
|
||||
optional: true,
|
||||
schema: PROXMOX_GROUP_ID_SCHEMA,
|
||||
},
|
||||
delete: {
|
||||
optional: true,
|
||||
description: "Remove permissions (instead of adding it).",
|
||||
type: bool,
|
||||
},
|
||||
digest: {
|
||||
optional: true,
|
||||
schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
|
||||
},
|
||||
},
|
||||
},
|
||||
)]
|
||||
/// Update Access Control List (ACLs).
|
||||
pub fn update_acl(
|
||||
path: String,
|
||||
role: String,
|
||||
propagate: Option<bool>,
|
||||
userid: Option<String>,
|
||||
group: Option<String>,
|
||||
delete: Option<bool>,
|
||||
digest: Option<String>,
|
||||
_rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let _lock = crate::tools::open_file_locked(acl::ACL_CFG_LOCKFILE, std::time::Duration::new(10, 0))?;
|
||||
|
||||
let (mut tree, expected_digest) = acl::config()?;
|
||||
|
||||
if let Some(ref digest) = digest {
|
||||
let digest = proxmox::tools::hex_to_digest(digest)?;
|
||||
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
|
||||
}
|
||||
|
||||
// fixme: test if user/group exists?
|
||||
|
||||
// fixme: let propagate = propagate.unwrap_or(api_get_default!("propagate"));
|
||||
let propagate = propagate.unwrap_or(true);
|
||||
|
||||
let delete = delete.unwrap_or(false);
|
||||
|
||||
if let Some(userid) = userid {
|
||||
if delete {
|
||||
tree.delete_user_role(&path, &userid, &role);
|
||||
} else {
|
||||
tree.insert_user_role(&path, &userid, &role, propagate);
|
||||
}
|
||||
} else if let Some(group) = group {
|
||||
if delete {
|
||||
tree.delete_group_role(&path, &group, &role);
|
||||
} else {
|
||||
tree.insert_group_role(&path, &group, &role, propagate);
|
||||
}
|
||||
}
|
||||
|
||||
acl::save_config(&tree)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub const ROUTER: Router = Router::new()
|
||||
.get(&API_METHOD_READ_ACL);
|
||||
.get(&API_METHOD_READ_ACL)
|
||||
.put(&API_METHOD_UPDATE_ACL);
|
||||
|
@ -25,6 +25,7 @@ macro_rules! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL
|
||||
// slash is not allowed because it is used as pve API delimiter
|
||||
// also see "man useradd"
|
||||
macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
|
||||
macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
|
||||
|
||||
macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") }
|
||||
|
||||
@ -54,9 +55,11 @@ const_regex!{
|
||||
|
||||
pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
|
||||
|
||||
pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$");
|
||||
|
||||
pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
|
||||
|
||||
pub ACL_PATH_REGEX = concat!(r"^(?:\/|", r"(?:\/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
|
||||
pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
|
||||
}
|
||||
|
||||
pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
|
||||
@ -89,6 +92,9 @@ pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
|
||||
pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
|
||||
|
||||
pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
|
||||
|
||||
pub const PASSWORD_FORMAT: ApiStringFormat =
|
||||
ApiStringFormat::Pattern(&PASSWORD_REGEX);
|
||||
|
||||
@ -218,6 +224,12 @@ pub const PROXMOX_USER_ID_SCHEMA: Schema = StringSchema::new("User ID")
|
||||
.max_length(64)
|
||||
.schema();
|
||||
|
||||
pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID")
|
||||
.format(&PROXMOX_GROUP_ID_FORMAT)
|
||||
.min_length(3)
|
||||
.max_length(64)
|
||||
.schema();
|
||||
|
||||
|
||||
// Complex type definitions
|
||||
|
||||
|
@ -220,7 +220,15 @@ fn list_acls(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Err
|
||||
fn acl_commands() -> CommandLineInterface {
|
||||
|
||||
let cmd_def = CliCommandMap::new()
|
||||
.insert("list", CliCommand::new(&&API_METHOD_LIST_ACLS));
|
||||
.insert("list", CliCommand::new(&&API_METHOD_LIST_ACLS))
|
||||
.insert(
|
||||
"update",
|
||||
CliCommand::new(&api2::access::acl::API_METHOD_UPDATE_ACL)
|
||||
.arg_param(&["path", "role"])
|
||||
.completion_cb("userid", config::user::complete_user_name)
|
||||
.completion_cb("path", config::datastore::complete_acl_path)
|
||||
|
||||
);
|
||||
|
||||
cmd_def.into()
|
||||
}
|
||||
|
@ -10,29 +10,31 @@ use proxmox::tools::{fs::replace_file, fs::CreateOptions};
|
||||
|
||||
// define Privilege bitfield
|
||||
|
||||
pub const PRIV_SYS_AUDIT: u64 = 1 << 0;
|
||||
pub const PRIV_SYS_MODIFY: u64 = 1 << 1;
|
||||
pub const PRIV_SYS_POWER_MANAGEMENT: u64 = 1 << 2;
|
||||
pub const PRIV_SYS_AUDIT: u64 = 1 << 0;
|
||||
pub const PRIV_SYS_MODIFY: u64 = 1 << 1;
|
||||
pub const PRIV_SYS_POWER_MANAGEMENT: u64 = 1 << 2;
|
||||
|
||||
pub const PRIV_STORE_AUDIT: u64 = 1 << 3;
|
||||
pub const PRIV_STORE_ALLOCATE: u64 = 1 << 4;
|
||||
pub const PRIV_STORE_ALLOCATE_SPACE: u64 = 1 << 5;
|
||||
pub const PRIV_DATASTORE_AUDIT: u64 = 1 << 3;
|
||||
pub const PRIV_DATASTORE_ALLOCATE: u64 = 1 << 4;
|
||||
pub const PRIV_DATASTORE_ALLOCATE_SPACE: u64 = 1 << 5;
|
||||
|
||||
pub const ROLE_ADMIN: u64 = std::u64::MAX;
|
||||
pub const ROLE_NO_ACCESS: u64 = 0;
|
||||
|
||||
pub const ROLE_AUDIT: u64 =
|
||||
PRIV_SYS_AUDIT |
|
||||
PRIV_STORE_AUDIT;
|
||||
PRIV_DATASTORE_AUDIT;
|
||||
|
||||
pub const ROLE_STORE_ADMIN: u64 =
|
||||
PRIV_STORE_AUDIT |
|
||||
PRIV_STORE_ALLOCATE |
|
||||
PRIV_STORE_ALLOCATE_SPACE;
|
||||
pub const ROLE_DATASTORE_ADMIN: u64 =
|
||||
PRIV_DATASTORE_AUDIT |
|
||||
PRIV_DATASTORE_ALLOCATE |
|
||||
PRIV_DATASTORE_ALLOCATE_SPACE;
|
||||
|
||||
pub const ROLE_STORE_USER: u64 =
|
||||
PRIV_STORE_AUDIT |
|
||||
PRIV_STORE_ALLOCATE_SPACE;
|
||||
pub const ROLE_DATASTORE_USER: u64 =
|
||||
PRIV_DATASTORE_AUDIT |
|
||||
PRIV_DATASTORE_ALLOCATE_SPACE;
|
||||
|
||||
pub const ROLE_DATASTORE_AUDIT: u64 = PRIV_DATASTORE_AUDIT;
|
||||
|
||||
lazy_static! {
|
||||
static ref ROLE_NAMES: HashMap<&'static str, u64> = {
|
||||
@ -42,8 +44,9 @@ lazy_static! {
|
||||
map.insert("Audit", ROLE_AUDIT);
|
||||
map.insert("NoAccess", ROLE_NO_ACCESS);
|
||||
|
||||
map.insert("Store.Admin", ROLE_STORE_ADMIN);
|
||||
map.insert("Store.User", ROLE_STORE_USER);
|
||||
map.insert("Datastore.Admin", ROLE_DATASTORE_ADMIN);
|
||||
map.insert("Datastore.User", ROLE_DATASTORE_USER);
|
||||
map.insert("Datastore.Audit", ROLE_DATASTORE_AUDIT);
|
||||
|
||||
map
|
||||
};
|
||||
@ -141,6 +144,22 @@ impl AclTreeNode {
|
||||
set
|
||||
}
|
||||
|
||||
pub fn delete_group_role(&mut self, group: &str, role: &str) {
|
||||
let roles = match self.groups.get_mut(group) {
|
||||
Some(r) => r,
|
||||
None => return,
|
||||
};
|
||||
roles.remove(role);
|
||||
}
|
||||
|
||||
pub fn delete_user_role(&mut self, userid: &str, role: &str) {
|
||||
let roles = match self.users.get_mut(userid) {
|
||||
Some(r) => r,
|
||||
None => return,
|
||||
};
|
||||
roles.remove(role);
|
||||
}
|
||||
|
||||
pub fn insert_group_role(&mut self, group: String, role: String, propagate: bool) {
|
||||
self.groups
|
||||
.entry(group).or_insert_with(|| HashMap::new())
|
||||
@ -160,6 +179,17 @@ impl AclTree {
|
||||
Self { root: AclTreeNode::new() }
|
||||
}
|
||||
|
||||
fn get_node(&mut self, path: &[&str]) -> Option<&mut AclTreeNode> {
|
||||
let mut node = &mut self.root;
|
||||
for comp in path {
|
||||
node = match node.children.get_mut(*comp) {
|
||||
Some(n) => n,
|
||||
None => return None,
|
||||
};
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
|
||||
fn get_or_insert_node(&mut self, path: &[&str]) -> &mut AclTreeNode {
|
||||
let mut node = &mut self.root;
|
||||
for comp in path {
|
||||
@ -169,6 +199,24 @@ impl AclTree {
|
||||
node
|
||||
}
|
||||
|
||||
pub fn delete_group_role(&mut self, path: &str, group: &str, role: &str) {
|
||||
let path = split_acl_path(path);
|
||||
let node = match self.get_node(&path) {
|
||||
Some(n) => n,
|
||||
None => return,
|
||||
};
|
||||
node.delete_group_role(group, role);
|
||||
}
|
||||
|
||||
pub fn delete_user_role(&mut self, path: &str, userid: &str, role: &str) {
|
||||
let path = split_acl_path(path);
|
||||
let node = match self.get_node(&path) {
|
||||
Some(n) => n,
|
||||
None => return,
|
||||
};
|
||||
node.delete_user_role(userid, role);
|
||||
}
|
||||
|
||||
pub fn insert_group_role(&mut self, path: &str, group: &str, role: &str, propagate: bool) {
|
||||
let path = split_acl_path(path);
|
||||
let node = self.get_or_insert_node(&path);
|
||||
@ -382,7 +430,7 @@ pub fn config() -> Result<(AclTree, [u8; 32]), Error> {
|
||||
AclTree::load(&path)
|
||||
}
|
||||
|
||||
pub fn store_config(acl: &AclTree, filename: &Path) -> Result<(), Error> {
|
||||
pub fn save_config(acl: &AclTree) -> Result<(), Error> {
|
||||
let mut raw: Vec<u8> = Vec::new();
|
||||
|
||||
acl.write_config(&mut raw)?;
|
||||
@ -396,12 +444,11 @@ pub fn store_config(acl: &AclTree, filename: &Path) -> Result<(), Error> {
|
||||
.owner(nix::unistd::ROOT)
|
||||
.group(backup_user.gid);
|
||||
|
||||
replace_file(filename, &raw, options)?;
|
||||
replace_file(ACL_CFG_FILENAME, &raw, options)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
@ -430,15 +477,15 @@ mod test {
|
||||
let tree = AclTree::from_raw(r###"
|
||||
acl:0:/store/store2:user1:Admin
|
||||
acl:0:/store/store2:user2:Admin
|
||||
acl:0:/store/store2:user1:Store.User
|
||||
acl:0:/store/store2:user2:Store.User
|
||||
acl:0:/store/store2:user1:Datastore.User
|
||||
acl:0:/store/store2:user2:Datastore.User
|
||||
"###)?;
|
||||
|
||||
let mut raw: Vec<u8> = Vec::new();
|
||||
tree.write_config(&mut raw)?;
|
||||
let raw = std::str::from_utf8(&raw)?;
|
||||
|
||||
assert_eq!(raw, "acl:0:/store/store2:user1,user2:Admin,Store.User\n");
|
||||
assert_eq!(raw, "acl:0:/store/store2:user1,user2:Admin,Datastore.User\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -448,18 +495,18 @@ acl:0:/store/store2:user2:Store.User
|
||||
|
||||
let tree = AclTree::from_raw(r###"
|
||||
acl:1:/storage:user1@pbs:Admin
|
||||
acl:1:/storage/store1:user1@pbs:Store.User
|
||||
acl:1:/storage/store2:user2@pbs:Store.User
|
||||
acl:1:/storage/store1:user1@pbs:Datastore.User
|
||||
acl:1:/storage/store2:user2@pbs:Datastore.User
|
||||
"###)?;
|
||||
check_roles(&tree, "user1@pbs", "/", "");
|
||||
check_roles(&tree, "user1@pbs", "/storage", "Admin");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store1", "Store.User");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store1", "Datastore.User");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store2", "Admin");
|
||||
|
||||
check_roles(&tree, "user2@pbs", "/", "");
|
||||
check_roles(&tree, "user2@pbs", "/storage", "");
|
||||
check_roles(&tree, "user2@pbs", "/storage/store1", "");
|
||||
check_roles(&tree, "user2@pbs", "/storage/store2", "Store.User");
|
||||
check_roles(&tree, "user2@pbs", "/storage/store2", "Datastore.User");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -470,22 +517,22 @@ acl:1:/storage/store2:user2@pbs:Store.User
|
||||
let tree = AclTree::from_raw(r###"
|
||||
acl:1:/:user1@pbs:Admin
|
||||
acl:1:/storage:user1@pbs:NoAccess
|
||||
acl:1:/storage/store1:user1@pbs:Store.User
|
||||
acl:1:/storage/store1:user1@pbs:Datastore.User
|
||||
"###)?;
|
||||
check_roles(&tree, "user1@pbs", "/", "Admin");
|
||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store1", "Store.User");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store1", "Datastore.User");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store2", "NoAccess");
|
||||
check_roles(&tree, "user1@pbs", "/system", "Admin");
|
||||
|
||||
let tree = AclTree::from_raw(r###"
|
||||
acl:1:/:user1@pbs:Admin
|
||||
acl:0:/storage:user1@pbs:NoAccess
|
||||
acl:1:/storage/store1:user1@pbs:Store.User
|
||||
acl:1:/storage/store1:user1@pbs:Datastore.User
|
||||
"###)?;
|
||||
check_roles(&tree, "user1@pbs", "/", "Admin");
|
||||
check_roles(&tree, "user1@pbs", "/storage", "NoAccess");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store1", "Store.User");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store1", "Datastore.User");
|
||||
check_roles(&tree, "user1@pbs", "/storage/store2", "Admin");
|
||||
check_roles(&tree, "user1@pbs", "/system", "Admin");
|
||||
|
||||
|
@ -101,3 +101,19 @@ pub fn complete_datastore_name(_arg: &str, _param: &HashMap<String, String>) ->
|
||||
Err(_) => return vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn complete_acl_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
|
||||
let mut list = Vec::new();
|
||||
|
||||
list.push(String::from("/"));
|
||||
list.push(String::from("/storage"));
|
||||
list.push(String::from("/storage/"));
|
||||
|
||||
if let Ok((data, _digest)) = config() {
|
||||
for id in data.sections.keys() {
|
||||
list.push(format!("/storage/{}", id));
|
||||
}
|
||||
}
|
||||
|
||||
list
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user