]> git.proxmox.com Git - proxmox-backup.git/commitdiff
acl api: implement update
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 14 Apr 2020 06:40:53 +0000 (08:40 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Tue, 14 Apr 2020 08:16:49 +0000 (10:16 +0200)
src/api2/access/acl.rs
src/api2/types.rs
src/bin/proxmox-backup-manager.rs
src/config/acl.rs
src/config/datastore.rs

index 04be51f6702fdf949816999883485d012fc68fac..0f80314cd0f3ec08b947161b2a131ca6632c88e7 100644 (file)
@@ -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);
index 7132b4beba20fae28af9ae1a81a503c0155f7d40..9e153d89727932e9c6bcb1eaa6ce50a35596a626 100644 (file)
@@ -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
 
index 0229d1ce6a8c2796e5443cad539bdcadb95a38ce..d1b91a56561dfd943e98059ba6d0cb7a2f796eda 100644 (file)
@@ -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()
 }
index 27b9b51a46447c8b10e902766a3007777b780923..f12aa21e43568ebb71d30387e764be06f7bf9266 100644 (file)
@@ -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");
 
index 61505aaef839448e98310e41e28b95982c3831aa..d3dad4dbf4a562e3fc9627b836ed33dd1ade5ce9 100644 (file)
@@ -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
+}