start ACL api

This commit is contained in:
Dietmar Maurer 2020-04-13 11:09:44 +02:00
parent a83eab3c4d
commit ed3e60ae69
5 changed files with 188 additions and 5 deletions

View File

@ -14,6 +14,7 @@ use crate::api2::types::*;
pub mod user; pub mod user;
pub mod domain; pub mod domain;
pub mod acl;
fn authenticate_user(username: &str, password: &str) -> Result<(), Error> { fn authenticate_user(username: &str, password: &str) -> Result<(), Error> {
@ -130,6 +131,7 @@ fn change_password(
#[sortable] #[sortable]
const SUBDIRS: SubdirMap = &sorted!([ const SUBDIRS: SubdirMap = &sorted!([
("acl", &acl::ROUTER),
( (
"password", &Router::new() "password", &Router::new()
.put(&API_METHOD_CHANGE_PASSWORD) .put(&API_METHOD_CHANGE_PASSWORD)

121
src/api2/access/acl.rs Normal file
View File

@ -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<AclListItem>,
) {
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<Vec<AclListItem>, Error> {
//let auth_user = rpcenv.get_user().unwrap();
let (tree, digest) = acl::config()?;
let mut list: Vec<AclListItem> = Vec::new();
extract_acl_node_data(&tree.root, "", &mut list);
Ok(list)
}
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_READ_ACL);

View File

@ -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 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 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 = pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
@ -90,6 +92,9 @@ pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
pub const PASSWORD_FORMAT: ApiStringFormat = pub const PASSWORD_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&PASSWORD_REGEX); ApiStringFormat::Pattern(&PASSWORD_REGEX);
pub const ACL_PATH_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&ACL_PATH_REGEX);
pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.") pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
.format(&PASSWORD_FORMAT) .format(&PASSWORD_FORMAT)

View File

@ -171,6 +171,60 @@ fn user_commands() -> CommandLineInterface {
cmd_def.into() 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<Value, Error> {
let output_format = get_output_format(&param);
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<String, Error> {
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 { fn datastore_commands() -> CommandLineInterface {
let cmd_def = CliCommandMap::new() let cmd_def = CliCommandMap::new()
@ -539,6 +593,7 @@ async fn pull_datastore(
fn main() { fn main() {
let cmd_def = CliCommandMap::new() let cmd_def = CliCommandMap::new()
.insert("acl", acl_commands())
.insert("datastore", datastore_commands()) .insert("datastore", datastore_commands())
.insert("user", user_commands()) .insert("user", user_commands())
.insert("remote", remote_commands()) .insert("remote", remote_commands())

View File

@ -64,13 +64,13 @@ fn split_acl_path(path: &str) -> Vec<&str> {
} }
pub struct AclTree { pub struct AclTree {
root: AclTreeNode, pub root: AclTreeNode,
} }
struct AclTreeNode { pub struct AclTreeNode {
users: HashMap<String, HashMap<String, bool>>, pub users: HashMap<String, HashMap<String, bool>>,
groups: HashMap<String, HashMap<String, bool>>, pub groups: HashMap<String, HashMap<String, bool>>,
children: BTreeMap<String, AclTreeNode>, pub children: BTreeMap<String, AclTreeNode>,
} }
impl AclTreeNode { impl AclTreeNode {