From ed3e60ae69257e6cfca75d2efd27996077964a08 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Mon, 13 Apr 2020 11:09:44 +0200 Subject: [PATCH] start ACL api --- src/api2/access.rs | 2 + src/api2/access/acl.rs | 121 ++++++++++++++++++++++++++++++ src/api2/types.rs | 5 ++ src/bin/proxmox-backup-manager.rs | 55 ++++++++++++++ src/config/acl.rs | 10 +-- 5 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 src/api2/access/acl.rs diff --git a/src/api2/access.rs b/src/api2/access.rs index 72698b4a..6d819349 100644 --- a/src/api2/access.rs +++ b/src/api2/access.rs @@ -14,6 +14,7 @@ use crate::api2::types::*; pub mod user; pub mod domain; +pub mod acl; fn authenticate_user(username: &str, password: &str) -> Result<(), Error> { @@ -130,6 +131,7 @@ fn change_password( #[sortable] const SUBDIRS: SubdirMap = &sorted!([ + ("acl", &acl::ROUTER), ( "password", &Router::new() .put(&API_METHOD_CHANGE_PASSWORD) diff --git a/src/api2/access/acl.rs b/src/api2/access/acl.rs new file mode 100644 index 00000000..04be51f6 --- /dev/null +++ b/src/api2/access/acl.rs @@ -0,0 +1,121 @@ +use failure::*; +use serde_json::Value; +use ::serde::{Deserialize, Serialize}; + +use proxmox::api::{api, ApiMethod, Router, RpcEnvironment}; +use proxmox::api::schema::{Schema, StringSchema, BooleanSchema, ApiStringFormat}; + +use crate::api2::types::*; +use crate::config::acl; + +pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new( + "Allow to propagate (inherit) permissions.") + .default(true) + .schema(); + +pub const ACL_PATH_SCHEMA: Schema = StringSchema::new( + "Access control path.") + .format(&ACL_PATH_FORMAT) + .min_length(1) + .max_length(128) + .schema(); + +pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new( + "Type of 'ugid' property.") + .format(&ApiStringFormat::Enum(&["user", "group"])) + .schema(); + +pub const ACL_ROLE_SCHEMA: Schema = StringSchema::new( + "Role.") + .format(&ApiStringFormat::Enum(&["Admin", "User", "Audit", "NoAccess"])) + .schema(); + +#[api( + properties: { + propagate: { + schema: ACL_PROPAGATE_SCHEMA, + }, + path: { + schema: ACL_PATH_SCHEMA, + }, + ugid_type: { + schema: ACL_UGID_TYPE_SCHEMA, + }, + ugid: { + type: String, + description: "User or Group ID.", + }, + roleid: { + schema: ACL_ROLE_SCHEMA, + } + } +)] +#[derive(Serialize, Deserialize)] +/// ACL list entry. +pub struct AclListItem { + path: String, + ugid: String, + ugid_type: String, + propagate: bool, + roleid: String, +} + +fn extract_acl_node_data( + node: &acl::AclTreeNode, + path: &str, + list: &mut Vec, +) { + for (user, roles) in &node.users { + for (role, propagate) in roles { + list.push(AclListItem { + path: if path.is_empty() { String::from("/") } else { path.to_string() }, + propagate: *propagate, + ugid_type: String::from("user"), + ugid: user.to_string(), + roleid: role.to_string(), + }); + } + } + for (group, roles) in &node.groups { + for (role, propagate) in roles { + list.push(AclListItem { + path: if path.is_empty() { String::from("/") } else { path.to_string() }, + propagate: *propagate, + ugid_type: String::from("group"), + ugid: group.to_string(), + roleid: role.to_string(), + }); + } + } + for (comp, child) in &node.children { + let new_path = format!("{}/{}", path, comp); + extract_acl_node_data(child, &new_path, list); + } +} + +#[api( + returns: { + description: "ACL entry list.", + type: Array, + items: { + type: AclListItem, + } + } +)] +/// Read Access Control List (ACLs). +pub fn read_acl( + _rpcenv: &mut dyn RpcEnvironment, +) -> Result, Error> { + + //let auth_user = rpcenv.get_user().unwrap(); + + let (tree, digest) = acl::config()?; + + let mut list: Vec = Vec::new(); + extract_acl_node_data(&tree.root, "", &mut list); + + Ok(list) +} + +pub const ROUTER: Router = Router::new() + .get(&API_METHOD_READ_ACL); diff --git a/src/api2/types.rs b/src/api2/types.rs index 23770928..7132b4be 100644 --- a/src/api2/types.rs +++ b/src/api2/types.rs @@ -55,6 +55,8 @@ const_regex!{ pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_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 const SYSTEMD_DATETIME_FORMAT: ApiStringFormat = @@ -90,6 +92,9 @@ pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat = pub const PASSWORD_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PASSWORD_REGEX); +pub const ACL_PATH_FORMAT: ApiStringFormat = + ApiStringFormat::Pattern(&ACL_PATH_REGEX); + pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.") .format(&PASSWORD_FORMAT) diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index c7c2f51a..0229d1ce 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -171,6 +171,60 @@ fn user_commands() -> CommandLineInterface { cmd_def.into() } +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + } + } +)] +/// Access Control list. +fn list_acls(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result { + + let output_format = get_output_format(¶m); + + let info = &api2::access::acl::API_METHOD_READ_ACL; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + fn render_ugid(value: &Value, record: &Value) -> Result { + if value.is_null() { return Ok(String::new()); } + let ugid = value.as_str().unwrap(); + let ugid_type = record["ugid_type"].as_str().unwrap(); + + if ugid_type == "user" { + Ok(ugid.to_string()) + } else if ugid_type == "group" { + Ok(format!("@{}", ugid)) + } else { + bail!("render_ugid: got unknown ugid_type"); + } + } + + let options = default_table_format_options() + .column(ColumnConfig::new("ugid").renderer(render_ugid)) + .column(ColumnConfig::new("path")) + .column(ColumnConfig::new("propagate")) + .column(ColumnConfig::new("roleid")); + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(Value::Null) +} + +fn acl_commands() -> CommandLineInterface { + + let cmd_def = CliCommandMap::new() + .insert("list", CliCommand::new(&&API_METHOD_LIST_ACLS)); + + cmd_def.into() +} + fn datastore_commands() -> CommandLineInterface { let cmd_def = CliCommandMap::new() @@ -539,6 +593,7 @@ async fn pull_datastore( fn main() { let cmd_def = CliCommandMap::new() + .insert("acl", acl_commands()) .insert("datastore", datastore_commands()) .insert("user", user_commands()) .insert("remote", remote_commands()) diff --git a/src/config/acl.rs b/src/config/acl.rs index 0105e041..27b9b51a 100644 --- a/src/config/acl.rs +++ b/src/config/acl.rs @@ -64,13 +64,13 @@ fn split_acl_path(path: &str) -> Vec<&str> { } pub struct AclTree { - root: AclTreeNode, + pub root: AclTreeNode, } -struct AclTreeNode { - users: HashMap>, - groups: HashMap>, - children: BTreeMap, +pub struct AclTreeNode { + pub users: HashMap>, + pub groups: HashMap>, + pub children: BTreeMap, } impl AclTreeNode {