2 use std
::collections
::{HashMap, HashSet, BTreeMap, BTreeSet}
;
3 use std
::path
::{PathBuf, Path}
;
7 use lazy_static
::lazy_static
;
9 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
11 // define Privilege bitfield
13 pub const PRIV_SYS_AUDIT
: u64 = 1 << 0;
14 pub const PRIV_SYS_MODIFY
: u64 = 1 << 1;
15 pub const PRIV_SYS_POWER_MANAGEMENT
: u64 = 1 << 2;
17 pub const PRIV_STORE_AUDIT
: u64 = 1 << 3;
18 pub const PRIV_STORE_ALLOCATE
: u64 = 1 << 4;
19 pub const PRIV_STORE_ALLOCATE_SPACE
: u64 = 1 << 5;
21 pub const ROLE_ADMIN
: u64 = std
::u64::MAX
;
22 pub const ROLE_NO_ACCESS
: u64 = 0;
24 pub const ROLE_AUDIT
: u64 =
28 pub const ROLE_STORE_ADMIN
: u64 =
31 PRIV_STORE_ALLOCATE_SPACE
;
33 pub const ROLE_STORE_USER
: u64 =
35 PRIV_STORE_ALLOCATE_SPACE
;
38 static ref ROLE_NAMES
: HashMap
<&'
static str, u64> = {
39 let mut map
= HashMap
::new();
41 map
.insert("Admin", ROLE_ADMIN
);
42 map
.insert("Audit", ROLE_AUDIT
);
43 map
.insert("NoAccess", ROLE_NO_ACCESS
);
45 map
.insert("Store.Admin", ROLE_STORE_ADMIN
);
46 map
.insert("Store.User", ROLE_STORE_USER
);
52 fn split_acl_path(path
: &str) -> Vec
<&str> {
54 let items
= path
.split('
/'
);
56 let mut components
= vec
![];
59 if name
.is_empty() { continue; }
60 components
.push(name
);
67 pub root
: AclTreeNode
,
70 pub struct AclTreeNode
{
71 pub users
: HashMap
<String
, HashMap
<String
, bool
>>,
72 pub groups
: HashMap
<String
, HashMap
<String
, bool
>>,
73 pub children
: BTreeMap
<String
, AclTreeNode
>,
78 pub fn new() -> Self {
80 users
: HashMap
::new(),
81 groups
: HashMap
::new(),
82 children
: BTreeMap
::new(),
86 pub fn extract_roles(&self, user
: &str, all
: bool
) -> HashSet
<String
> {
87 let user_roles
= self.extract_user_roles(user
, all
);
88 if !user_roles
.is_empty() {
89 // user privs always override group privs
93 self.extract_group_roles(user
, all
)
96 pub fn extract_user_roles(&self, user
: &str, all
: bool
) -> HashSet
<String
> {
98 let mut set
= HashSet
::new();
100 let roles
= match self.users
.get(user
) {
105 for (role
, propagate
) in roles
{
106 if *propagate
|| all
{
107 if role
== "NoAccess" {
108 // return a set with a single role 'NoAccess'
109 let mut set
= HashSet
::new();
110 set
.insert(role
.to_string());
113 set
.insert(role
.to_string());
120 pub fn extract_group_roles(&self, _user
: &str, all
: bool
) -> HashSet
<String
> {
122 let mut set
= HashSet
::new();
124 for (_group
, roles
) in &self.groups
{
125 let is_member
= false; // fixme: check if user is member of the group
126 if !is_member { continue; }
128 for (role
, propagate
) in roles
{
129 if *propagate
|| all
{
130 if role
== "NoAccess" {
131 // return a set with a single role 'NoAccess'
132 let mut set
= HashSet
::new();
133 set
.insert(role
.to_string());
136 set
.insert(role
.to_string());
144 pub fn insert_group_role(&mut self, group
: String
, role
: String
, propagate
: bool
) {
146 .entry(group
).or_insert_with(|| HashMap
::new())
147 .insert(role
, propagate
);
150 pub fn insert_user_role(&mut self, user
: String
, role
: String
, propagate
: bool
) {
152 .entry(user
).or_insert_with(|| HashMap
::new())
153 .insert(role
, propagate
);
159 pub fn new() -> Self {
160 Self { root: AclTreeNode::new() }
163 fn get_or_insert_node(&mut self, path
: &[&str]) -> &mut AclTreeNode
{
164 let mut node
= &mut self.root
;
166 node
= node
.children
.entry(String
::from(*comp
))
167 .or_insert_with(|| AclTreeNode
::new());
172 pub fn insert_group_role(&mut self, path
: &str, group
: &str, role
: &str, propagate
: bool
) {
173 let path
= split_acl_path(path
);
174 let node
= self.get_or_insert_node(&path
);
175 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
178 pub fn insert_user_role(&mut self, path
: &str, user
: &str, role
: &str, propagate
: bool
) {
179 let path
= split_acl_path(path
);
180 let node
= self.get_or_insert_node(&path
);
181 node
.insert_user_role(user
.to_string(), role
.to_string(), propagate
);
184 fn write_node_config(
188 ) -> Result
<(), Error
> {
190 let mut role_ug_map0
= HashMap
::new();
191 let mut role_ug_map1
= HashMap
::new();
193 for (user
, roles
) in &node
.users
{
194 // no need to save, because root is always 'Administrator'
195 if user
== "root@pam" { continue; }
196 for (role
, propagate
) in roles
{
197 let role
= role
.as_str();
198 let user
= user
.to_string();
200 role_ug_map1
.entry(role
).or_insert_with(|| BTreeSet
::new())
203 role_ug_map0
.entry(role
).or_insert_with(|| BTreeSet
::new())
209 for (group
, roles
) in &node
.groups
{
210 for (role
, propagate
) in roles
{
211 let group
= format
!("@{}", group
);
213 role_ug_map1
.entry(role
).or_insert_with(|| BTreeSet
::new())
216 role_ug_map0
.entry(role
).or_insert_with(|| BTreeSet
::new())
222 fn group_by_property_list(
223 item_property_map
: &HashMap
<&str, BTreeSet
<String
>>,
224 ) -> BTreeMap
<String
, BTreeSet
<String
>> {
225 let mut result_map
= BTreeMap
::new();
226 for (item
, property_map
) in item_property_map
{
227 let item_list
= property_map
.iter().fold(String
::new(), |mut acc
, v
| {
228 if !acc
.is_empty() { acc.push(','); }
232 result_map
.entry(item_list
).or_insert_with(|| BTreeSet
::new())
233 .insert(item
.to_string());
238 let uglist_role_map0
= group_by_property_list(&role_ug_map0
);
239 let uglist_role_map1
= group_by_property_list(&role_ug_map1
);
241 for (uglist
, roles
) in uglist_role_map0
{
242 let role_list
= roles
.iter().fold(String
::new(), |mut acc
, v
| {
243 if !acc
.is_empty() { acc.push(','); }
247 writeln
!(w
, "acl:0:{}:{}:{}", path
, uglist
, role_list
)?
;
250 for (uglist
, roles
) in uglist_role_map1
{
251 let role_list
= roles
.iter().fold(String
::new(), |mut acc
, v
| {
252 if !acc
.is_empty() { acc.push(','); }
256 writeln
!(w
, "acl:1:{}:{}:{}", path
, uglist
, role_list
)?
;
259 for (name
, child
) in node
.children
.iter() {
260 let child_path
= format
!("{}/{}", path
, name
);
261 Self::write_node_config(child
, &child_path
, w
)?
;
267 pub fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
268 Self::write_node_config(&self.root
, "", w
)
271 fn parse_acl_line(&mut self, line
: &str) -> Result
<(), Error
> {
273 let items
: Vec
<&str> = line
.split('
:'
).collect();
275 if items
.len() != 5 {
276 bail
!("wrong number of items.");
279 if items
[0] != "acl" {
280 bail
!("line does not start with 'acl'.");
283 let propagate
= if items
[1] == "0" {
285 } else if items
[1] == "1" {
288 bail
!("expected '0' or '1' for propagate flag.");
291 let path
= split_acl_path(items
[2]);
292 let node
= self.get_or_insert_node(&path
);
294 let uglist
: Vec
<&str> = items
[3].split('
,'
).map(|v
| v
.trim()).collect();
296 let rolelist
: Vec
<&str> = items
[4].split('
,'
).map(|v
| v
.trim()).collect();
298 for user_or_group
in &uglist
{
299 for role
in &rolelist
{
300 if !ROLE_NAMES
.contains_key(role
) {
301 bail
!("unknown role '{}'", role
);
303 if user_or_group
.starts_with('@'
) {
304 let group
= &user_or_group
[1..];
305 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
307 node
.insert_user_role(user_or_group
.to_string(), role
.to_string(), propagate
);
315 pub fn load(filename
: &Path
) -> Result
<(Self, [u8;32]), Error
> {
316 let mut tree
= Self::new();
318 let raw
= match std
::fs
::read_to_string(filename
) {
321 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
324 bail
!("unable to read acl config {:?} - {}", filename
, err
);
329 let digest
= openssl
::sha
::sha256(raw
.as_bytes());
331 for (linenr
, line
) in raw
.lines().enumerate() {
332 let line
= line
.trim();
333 if line
.is_empty() { continue; }
334 if let Err(err
) = tree
.parse_acl_line(line
) {
335 bail
!("unable to parse acl config {:?}, line {} - {}",
336 filename
, linenr
+1, err
);
343 pub fn from_raw(raw
: &str) -> Result
<Self, Error
> {
344 let mut tree
= Self::new();
345 for (linenr
, line
) in raw
.lines().enumerate() {
346 let line
= line
.trim();
347 if line
.is_empty() { continue; }
348 if let Err(err
) = tree
.parse_acl_line(line
) {
349 bail
!("unable to parse acl config data, line {} - {}", linenr
+1, err
);
355 pub fn roles(&self, userid
: &str, path
: &[&str]) -> HashSet
<String
> {
357 let mut node
= &self.root
;
358 let mut role_set
= node
.extract_roles(userid
, path
.is_empty());
360 for (pos
, comp
) in path
.iter().enumerate() {
361 let last_comp
= (pos
+ 1) == path
.len();
362 node
= match node
.children
.get(*comp
) {
364 None
=> return role_set
, // path not found
366 let new_set
= node
.extract_roles(userid
, last_comp
);
367 if !new_set
.is_empty() {
368 // overwrite previous settings
377 pub const ACL_CFG_FILENAME
: &str = "/etc/proxmox-backup/acl.cfg";
378 pub const ACL_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.acl.lck";
380 pub fn config() -> Result
<(AclTree
, [u8; 32]), Error
> {
381 let path
= PathBuf
::from(ACL_CFG_FILENAME
);
385 pub fn store_config(acl
: &AclTree
, filename
: &Path
) -> Result
<(), Error
> {
386 let mut raw
: Vec
<u8> = Vec
::new();
388 acl
.write_config(&mut raw
)?
;
390 let backup_user
= crate::backup
::backup_user()?
;
391 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640);
392 // set the correct owner/group/permissions while saving file
393 // owner(rw) = root, group(r)= backup
394 let options
= CreateOptions
::new()
396 .owner(nix
::unistd
::ROOT
)
397 .group(backup_user
.gid
);
399 replace_file(filename
, &raw
, options
)?
;
415 expected_roles
: &str,
418 let path_vec
= super::split_acl_path(path
);
419 let mut roles
= tree
.roles(user
, &path_vec
)
420 .iter().map(|v
| v
.clone()).collect
::<Vec
<String
>>();
422 let roles
= roles
.join(",");
424 assert_eq
!(roles
, expected_roles
, "\nat check_roles for '{}' on '{}'", user
, path
);
428 fn test_acl_line_compression() -> Result
<(), Error
> {
430 let tree
= AclTree
::from_raw(r
###"
431 acl:0:/store/store2:user1:Admin
432 acl:0:/store/store2:user2:Admin
433 acl:0:/store/store2:user1:Store.User
434 acl:0:/store/store2:user2:Store.User
437 let mut raw
: Vec
<u8> = Vec
::new();
438 tree
.write_config(&mut raw
)?
;
439 let raw
= std
::str::from_utf8(&raw
)?
;
441 assert_eq
!(raw
, "acl:0:/store/store2:user1,user2:Admin,Store.User\n");
447 fn test_roles_1() -> Result
<(), Error
> {
449 let tree
= AclTree
::from_raw(r
###"
450 acl:1:/storage:user1@pbs:Admin
451 acl:1:/storage/store1:user1@pbs:Store.User
452 acl:1:/storage/store2:user2@pbs:Store.User
454 check_roles(&tree
, "user1@pbs", "/", "");
455 check_roles(&tree
, "user1@pbs", "/storage", "Admin");
456 check_roles(&tree
, "user1@pbs", "/storage/store1", "Store.User");
457 check_roles(&tree
, "user1@pbs", "/storage/store2", "Admin");
459 check_roles(&tree
, "user2@pbs", "/", "");
460 check_roles(&tree
, "user2@pbs", "/storage", "");
461 check_roles(&tree
, "user2@pbs", "/storage/store1", "");
462 check_roles(&tree
, "user2@pbs", "/storage/store2", "Store.User");
468 fn test_role_no_access() -> Result
<(), Error
> {
470 let tree
= AclTree
::from_raw(r
###"
471 acl:1:/:user1@pbs:Admin
472 acl:1:/storage:user1@pbs:NoAccess
473 acl:1:/storage/store1:user1@pbs:Store.User
475 check_roles(&tree
, "user1@pbs", "/", "Admin");
476 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");
477 check_roles(&tree
, "user1@pbs", "/storage/store1", "Store.User");
478 check_roles(&tree
, "user1@pbs", "/storage/store2", "NoAccess");
479 check_roles(&tree
, "user1@pbs", "/system", "Admin");
481 let tree
= AclTree
::from_raw(r
###"
482 acl:1:/:user1@pbs:Admin
483 acl:0:/storage:user1@pbs:NoAccess
484 acl:1:/storage/store1:user1@pbs:Store.User
486 check_roles(&tree
, "user1@pbs", "/", "Admin");
487 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");
488 check_roles(&tree
, "user1@pbs", "/storage/store1", "Store.User");
489 check_roles(&tree
, "user1@pbs", "/storage/store2", "Admin");
490 check_roles(&tree
, "user1@pbs", "/system", "Admin");