2 use std
::collections
::{HashMap, HashSet, BTreeMap, BTreeSet}
;
3 use std
::path
::{PathBuf, Path}
;
4 use std
::sync
::{Arc, RwLock}
;
6 use anyhow
::{bail, Error}
;
8 use lazy_static
::lazy_static
;
10 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
12 // define Privilege bitfield
14 pub const PRIV_SYS_AUDIT
: u64 = 1 << 0;
15 pub const PRIV_SYS_MODIFY
: u64 = 1 << 1;
16 pub const PRIV_SYS_POWER_MANAGEMENT
: u64 = 1 << 2;
18 pub const PRIV_DATASTORE_AUDIT
: u64 = 1 << 3;
19 pub const PRIV_DATASTORE_MODIFY
: u64 = 1 << 4;
20 pub const PRIV_DATASTORE_READ
: u64 = 1 << 5;
22 /// Datastore.Backup also requires backup ownership
23 pub const PRIV_DATASTORE_BACKUP
: u64 = 1 << 6;
24 /// Datastore.Prune also requires backup ownership
25 pub const PRIV_DATASTORE_PRUNE
: u64 = 1 << 7;
27 pub const PRIV_PERMISSIONS_MODIFY
: u64 = 1 << 8;
29 pub const PRIV_REMOTE_AUDIT
: u64 = 1 << 9;
30 pub const PRIV_REMOTE_MODIFY
: u64 = 1 << 10;
31 pub const PRIV_REMOTE_READ
: u64 = 1 << 11;
32 pub const PRIV_REMOTE_PRUNE
: u64 = 1 << 12;
34 pub const ROLE_ADMIN
: u64 = std
::u64::MAX
;
35 pub const ROLE_NO_ACCESS
: u64 = 0;
37 pub const ROLE_AUDIT
: u64 =
41 /// Datastore.Admin can do anything on the datastore.
42 pub const ROLE_DATASTORE_ADMIN
: u64 =
43 PRIV_DATASTORE_AUDIT
|
44 PRIV_DATASTORE_MODIFY
|
46 PRIV_DATASTORE_BACKUP
|
49 /// Datastore.Reader can read datastore content an do restore
50 pub const ROLE_DATASTORE_READER
: u64 =
51 PRIV_DATASTORE_AUDIT
|
54 /// Datastore.Backup can do backup and restore, but no prune.
55 pub const ROLE_DATASTORE_BACKUP
: u64 =
56 PRIV_DATASTORE_BACKUP
;
58 /// Datastore.PowerUser can do backup, restore, and prune.
59 pub const ROLE_DATASTORE_POWERUSER
: u64 =
60 PRIV_DATASTORE_PRUNE
|
61 PRIV_DATASTORE_BACKUP
;
63 /// Datastore.Audit can audit the datastore.
64 pub const ROLE_DATASTORE_AUDIT
: u64 =
67 /// Remote.Audit can audit the remote
68 pub const ROLE_REMOTE_AUDIT
: u64 =
71 /// Remote.Admin can do anything on the remote.
72 pub const ROLE_REMOTE_ADMIN
: u64 =
78 /// Remote.SyncOperator can do read and prune on the remote.
79 pub const ROLE_REMOTE_SYNC_OPERATOR
: u64 =
84 pub const ROLE_NAME_NO_ACCESS
: &str ="NoAccess";
87 pub static ref ROLE_NAMES
: HashMap
<&'
static str, (u64, &'
static str)> = {
88 let mut map
= HashMap
::new();
98 map
.insert(ROLE_NAME_NO_ACCESS
, (
103 map
.insert("Datastore.Admin", (
104 ROLE_DATASTORE_ADMIN
,
105 "Datastore Administrator",
107 map
.insert("Datastore.Reader", (
108 ROLE_DATASTORE_READER
,
109 "Datastore Reader (inspect datastore content and do restores)",
111 map
.insert("Datastore.Backup", (
112 ROLE_DATASTORE_BACKUP
,
113 "Datastore Backup (backup and restore owned backups)",
115 map
.insert("Datastore.PowerUser", (
116 ROLE_DATASTORE_POWERUSER
,
117 "Datastore PowerUser (backup, restore and prune owned backup)",
119 map
.insert("Datastore.Audit", (
120 ROLE_DATASTORE_AUDIT
,
124 map
.insert("Remote.Audit", (
128 map
.insert("Remote.Admin", (
130 "Remote Administrator",
132 map
.insert("Remote.SyncOperator", (
133 ROLE_REMOTE_SYNC_OPERATOR
,
134 "Syncronisation Opertator",
141 pub fn split_acl_path(path
: &str) -> Vec
<&str> {
143 let items
= path
.split('
/'
);
145 let mut components
= vec
![];
148 if name
.is_empty() { continue; }
149 components
.push(name
);
156 pub root
: AclTreeNode
,
159 pub struct AclTreeNode
{
160 pub users
: HashMap
<String
, HashMap
<String
, bool
>>,
161 pub groups
: HashMap
<String
, HashMap
<String
, bool
>>,
162 pub children
: BTreeMap
<String
, AclTreeNode
>,
167 pub fn new() -> Self {
169 users
: HashMap
::new(),
170 groups
: HashMap
::new(),
171 children
: BTreeMap
::new(),
175 pub fn extract_roles(&self, user
: &str, all
: bool
) -> HashSet
<String
> {
176 let user_roles
= self.extract_user_roles(user
, all
);
177 if !user_roles
.is_empty() {
178 // user privs always override group privs
182 self.extract_group_roles(user
, all
)
185 pub fn extract_user_roles(&self, user
: &str, all
: bool
) -> HashSet
<String
> {
187 let mut set
= HashSet
::new();
189 let roles
= match self.users
.get(user
) {
194 for (role
, propagate
) in roles
{
195 if *propagate
|| all
{
196 if role
== ROLE_NAME_NO_ACCESS
{
197 // return a set with a single role 'NoAccess'
198 let mut set
= HashSet
::new();
199 set
.insert(role
.to_string());
202 set
.insert(role
.to_string());
209 pub fn extract_group_roles(&self, _user
: &str, all
: bool
) -> HashSet
<String
> {
211 let mut set
= HashSet
::new();
213 for (_group
, roles
) in &self.groups
{
214 let is_member
= false; // fixme: check if user is member of the group
215 if !is_member { continue; }
217 for (role
, propagate
) in roles
{
218 if *propagate
|| all
{
219 if role
== ROLE_NAME_NO_ACCESS
{
220 // return a set with a single role 'NoAccess'
221 let mut set
= HashSet
::new();
222 set
.insert(role
.to_string());
225 set
.insert(role
.to_string());
233 pub fn delete_group_role(&mut self, group
: &str, role
: &str) {
234 let roles
= match self.groups
.get_mut(group
) {
241 pub fn delete_user_role(&mut self, userid
: &str, role
: &str) {
242 let roles
= match self.users
.get_mut(userid
) {
249 pub fn insert_group_role(&mut self, group
: String
, role
: String
, propagate
: bool
) {
250 let map
= self.groups
.entry(group
).or_insert_with(|| HashMap
::new());
251 if role
== ROLE_NAME_NO_ACCESS
{
253 map
.insert(role
, propagate
);
255 map
.remove(ROLE_NAME_NO_ACCESS
);
256 map
.insert(role
, propagate
);
260 pub fn insert_user_role(&mut self, user
: String
, role
: String
, propagate
: bool
) {
261 let map
= self.users
.entry(user
).or_insert_with(|| HashMap
::new());
262 if role
== ROLE_NAME_NO_ACCESS
{
264 map
.insert(role
, propagate
);
266 map
.remove(ROLE_NAME_NO_ACCESS
);
267 map
.insert(role
, propagate
);
274 pub fn new() -> Self {
275 Self { root: AclTreeNode::new() }
278 fn get_node(&mut self, path
: &[&str]) -> Option
<&mut AclTreeNode
> {
279 let mut node
= &mut self.root
;
281 node
= match node
.children
.get_mut(*comp
) {
289 fn get_or_insert_node(&mut self, path
: &[&str]) -> &mut AclTreeNode
{
290 let mut node
= &mut self.root
;
292 node
= node
.children
.entry(String
::from(*comp
))
293 .or_insert_with(|| AclTreeNode
::new());
298 pub fn delete_group_role(&mut self, path
: &str, group
: &str, role
: &str) {
299 let path
= split_acl_path(path
);
300 let node
= match self.get_node(&path
) {
304 node
.delete_group_role(group
, role
);
307 pub fn delete_user_role(&mut self, path
: &str, userid
: &str, role
: &str) {
308 let path
= split_acl_path(path
);
309 let node
= match self.get_node(&path
) {
313 node
.delete_user_role(userid
, role
);
316 pub fn insert_group_role(&mut self, path
: &str, group
: &str, role
: &str, propagate
: bool
) {
317 let path
= split_acl_path(path
);
318 let node
= self.get_or_insert_node(&path
);
319 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
322 pub fn insert_user_role(&mut self, path
: &str, user
: &str, role
: &str, propagate
: bool
) {
323 let path
= split_acl_path(path
);
324 let node
= self.get_or_insert_node(&path
);
325 node
.insert_user_role(user
.to_string(), role
.to_string(), propagate
);
328 fn write_node_config(
332 ) -> Result
<(), Error
> {
334 let mut role_ug_map0
= HashMap
::new();
335 let mut role_ug_map1
= HashMap
::new();
337 for (user
, roles
) in &node
.users
{
338 // no need to save, because root is always 'Administrator'
339 if user
== "root@pam" { continue; }
340 for (role
, propagate
) in roles
{
341 let role
= role
.as_str();
342 let user
= user
.to_string();
344 role_ug_map1
.entry(role
).or_insert_with(|| BTreeSet
::new())
347 role_ug_map0
.entry(role
).or_insert_with(|| BTreeSet
::new())
353 for (group
, roles
) in &node
.groups
{
354 for (role
, propagate
) in roles
{
355 let group
= format
!("@{}", group
);
357 role_ug_map1
.entry(role
).or_insert_with(|| BTreeSet
::new())
360 role_ug_map0
.entry(role
).or_insert_with(|| BTreeSet
::new())
366 fn group_by_property_list(
367 item_property_map
: &HashMap
<&str, BTreeSet
<String
>>,
368 ) -> BTreeMap
<String
, BTreeSet
<String
>> {
369 let mut result_map
= BTreeMap
::new();
370 for (item
, property_map
) in item_property_map
{
371 let item_list
= property_map
.iter().fold(String
::new(), |mut acc
, v
| {
372 if !acc
.is_empty() { acc.push(','); }
376 result_map
.entry(item_list
).or_insert_with(|| BTreeSet
::new())
377 .insert(item
.to_string());
382 let uglist_role_map0
= group_by_property_list(&role_ug_map0
);
383 let uglist_role_map1
= group_by_property_list(&role_ug_map1
);
385 fn role_list(roles
: &BTreeSet
<String
>) -> String
{
386 if roles
.contains(ROLE_NAME_NO_ACCESS
) { return String::from(ROLE_NAME_NO_ACCESS); }
387 roles
.iter().fold(String
::new(), |mut acc
, v
| {
388 if !acc
.is_empty() { acc.push(','); }
394 for (uglist
, roles
) in &uglist_role_map0
{
395 let role_list
= role_list(roles
);
396 writeln
!(w
, "acl:0:{}:{}:{}", if path
.is_empty() { "/" }
else { path }
, uglist
, role_list
)?
;
399 for (uglist
, roles
) in &uglist_role_map1
{
400 let role_list
= role_list(roles
);
401 writeln
!(w
, "acl:1:{}:{}:{}", if path
.is_empty() { "/" }
else { path }
, uglist
, role_list
)?
;
404 for (name
, child
) in node
.children
.iter() {
405 let child_path
= format
!("{}/{}", path
, name
);
406 Self::write_node_config(child
, &child_path
, w
)?
;
412 pub fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
413 Self::write_node_config(&self.root
, "", w
)
416 fn parse_acl_line(&mut self, line
: &str) -> Result
<(), Error
> {
418 let items
: Vec
<&str> = line
.split('
:'
).collect();
420 if items
.len() != 5 {
421 bail
!("wrong number of items.");
424 if items
[0] != "acl" {
425 bail
!("line does not start with 'acl'.");
428 let propagate
= if items
[1] == "0" {
430 } else if items
[1] == "1" {
433 bail
!("expected '0' or '1' for propagate flag.");
436 let path
= split_acl_path(items
[2]);
437 let node
= self.get_or_insert_node(&path
);
439 let uglist
: Vec
<&str> = items
[3].split('
,'
).map(|v
| v
.trim()).collect();
441 let rolelist
: Vec
<&str> = items
[4].split('
,'
).map(|v
| v
.trim()).collect();
443 for user_or_group
in &uglist
{
444 for role
in &rolelist
{
445 if !ROLE_NAMES
.contains_key(role
) {
446 bail
!("unknown role '{}'", role
);
448 if user_or_group
.starts_with('@'
) {
449 let group
= &user_or_group
[1..];
450 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
452 node
.insert_user_role(user_or_group
.to_string(), role
.to_string(), propagate
);
460 pub fn load(filename
: &Path
) -> Result
<(Self, [u8;32]), Error
> {
461 let mut tree
= Self::new();
463 let raw
= match std
::fs
::read_to_string(filename
) {
466 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
469 bail
!("unable to read acl config {:?} - {}", filename
, err
);
474 let digest
= openssl
::sha
::sha256(raw
.as_bytes());
476 for (linenr
, line
) in raw
.lines().enumerate() {
477 let line
= line
.trim();
478 if line
.is_empty() { continue; }
479 if let Err(err
) = tree
.parse_acl_line(line
) {
480 bail
!("unable to parse acl config {:?}, line {} - {}",
481 filename
, linenr
+1, err
);
488 pub fn from_raw(raw
: &str) -> Result
<Self, Error
> {
489 let mut tree
= Self::new();
490 for (linenr
, line
) in raw
.lines().enumerate() {
491 let line
= line
.trim();
492 if line
.is_empty() { continue; }
493 if let Err(err
) = tree
.parse_acl_line(line
) {
494 bail
!("unable to parse acl config data, line {} - {}", linenr
+1, err
);
500 pub fn roles(&self, userid
: &str, path
: &[&str]) -> HashSet
<String
> {
502 let mut node
= &self.root
;
503 let mut role_set
= node
.extract_roles(userid
, path
.is_empty());
505 for (pos
, comp
) in path
.iter().enumerate() {
506 let last_comp
= (pos
+ 1) == path
.len();
507 node
= match node
.children
.get(*comp
) {
509 None
=> return role_set
, // path not found
511 let new_set
= node
.extract_roles(userid
, last_comp
);
512 if !new_set
.is_empty() {
513 // overwrite previous settings
522 pub const ACL_CFG_FILENAME
: &str = "/etc/proxmox-backup/acl.cfg";
523 pub const ACL_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.acl.lck";
525 pub fn config() -> Result
<(AclTree
, [u8; 32]), Error
> {
526 let path
= PathBuf
::from(ACL_CFG_FILENAME
);
530 pub fn cached_config() -> Result
<Arc
<AclTree
>, Error
> {
533 data
: Option
<Arc
<AclTree
>>,
535 last_mtime_nsec
: i64,
539 static ref CACHED_CONFIG
: RwLock
<ConfigCache
> = RwLock
::new(
540 ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 }
);
543 let stat
= match nix
::sys
::stat
::stat(ACL_CFG_FILENAME
) {
544 Ok(stat
) => Some(stat
),
545 Err(nix
::Error
::Sys(nix
::errno
::Errno
::ENOENT
)) => None
,
546 Err(err
) => bail
!("unable to stat '{}' - {}", ACL_CFG_FILENAME
, err
),
549 if let Some(stat
) = stat
{
550 let cache
= CACHED_CONFIG
.read().unwrap();
551 if stat
.st_mtime
== cache
.last_mtime
&& stat
.st_mtime_nsec
== cache
.last_mtime_nsec
{
552 if let Some(ref config
) = cache
.data
{
553 return Ok(config
.clone());
558 let (config
, _digest
) = config()?
;
559 let config
= Arc
::new(config
);
561 let mut cache
= CACHED_CONFIG
.write().unwrap();
562 if let Some(stat
) = stat
{
563 cache
.last_mtime
= stat
.st_mtime
;
564 cache
.last_mtime_nsec
= stat
.st_mtime_nsec
;
566 cache
.data
= Some(config
.clone());
571 pub fn save_config(acl
: &AclTree
) -> Result
<(), Error
> {
572 let mut raw
: Vec
<u8> = Vec
::new();
574 acl
.write_config(&mut raw
)?
;
576 let backup_user
= crate::backup
::backup_user()?
;
577 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640);
578 // set the correct owner/group/permissions while saving file
579 // owner(rw) = root, group(r)= backup
580 let options
= CreateOptions
::new()
582 .owner(nix
::unistd
::ROOT
)
583 .group(backup_user
.gid
);
585 replace_file(ACL_CFG_FILENAME
, &raw
, options
)?
;
600 expected_roles
: &str,
603 let path_vec
= super::split_acl_path(path
);
604 let mut roles
= tree
.roles(user
, &path_vec
)
605 .iter().map(|v
| v
.clone()).collect
::<Vec
<String
>>();
607 let roles
= roles
.join(",");
609 assert_eq
!(roles
, expected_roles
, "\nat check_roles for '{}' on '{}'", user
, path
);
613 fn test_acl_line_compression() -> Result
<(), Error
> {
615 let tree
= AclTree
::from_raw(r
###"
616 acl:0:/store/store2:user1:Admin
617 acl:0:/store/store2:user2:Admin
618 acl:0:/store/store2:user1:Datastore.Backup
619 acl:0:/store/store2:user2:Datastore.Backup
622 let mut raw
: Vec
<u8> = Vec
::new();
623 tree
.write_config(&mut raw
)?
;
624 let raw
= std
::str::from_utf8(&raw
)?
;
626 assert_eq
!(raw
, "acl:0:/store/store2:user1,user2:Admin,Datastore.Backup\n");
632 fn test_roles_1() -> Result
<(), Error
> {
634 let tree
= AclTree
::from_raw(r
###"
635 acl:1:/storage:user1@pbs:Admin
636 acl:1:/storage/store1:user1@pbs:Datastore.Backup
637 acl:1:/storage/store2:user2@pbs:Datastore.Backup
639 check_roles(&tree
, "user1@pbs", "/", "");
640 check_roles(&tree
, "user1@pbs", "/storage", "Admin");
641 check_roles(&tree
, "user1@pbs", "/storage/store1", "Datastore.Backup");
642 check_roles(&tree
, "user1@pbs", "/storage/store2", "Admin");
644 check_roles(&tree
, "user2@pbs", "/", "");
645 check_roles(&tree
, "user2@pbs", "/storage", "");
646 check_roles(&tree
, "user2@pbs", "/storage/store1", "");
647 check_roles(&tree
, "user2@pbs", "/storage/store2", "Datastore.Backup");
653 fn test_role_no_access() -> Result
<(), Error
> {
655 let tree
= AclTree
::from_raw(r
###"
656 acl:1:/:user1@pbs:Admin
657 acl:1:/storage:user1@pbs:NoAccess
658 acl:1:/storage/store1:user1@pbs:Datastore.Backup
660 check_roles(&tree
, "user1@pbs", "/", "Admin");
661 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");
662 check_roles(&tree
, "user1@pbs", "/storage/store1", "Datastore.Backup");
663 check_roles(&tree
, "user1@pbs", "/storage/store2", "NoAccess");
664 check_roles(&tree
, "user1@pbs", "/system", "Admin");
666 let tree
= AclTree
::from_raw(r
###"
667 acl:1:/:user1@pbs:Admin
668 acl:0:/storage:user1@pbs:NoAccess
669 acl:1:/storage/store1:user1@pbs:Datastore.Backup
671 check_roles(&tree
, "user1@pbs", "/", "Admin");
672 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");
673 check_roles(&tree
, "user1@pbs", "/storage/store1", "Datastore.Backup");
674 check_roles(&tree
, "user1@pbs", "/storage/store2", "Admin");
675 check_roles(&tree
, "user1@pbs", "/system", "Admin");
681 fn test_role_add_delete() -> Result
<(), Error
> {
683 let mut tree
= AclTree
::new();
685 tree
.insert_user_role("/", "user1@pbs", "Admin", true);
686 tree
.insert_user_role("/", "user1@pbs", "Audit", true);
688 check_roles(&tree
, "user1@pbs", "/", "Admin,Audit");
690 tree
.insert_user_role("/", "user1@pbs", "NoAccess", true);
691 check_roles(&tree
, "user1@pbs", "/", "NoAccess");
693 let mut raw
: Vec
<u8> = Vec
::new();
694 tree
.write_config(&mut raw
)?
;
695 let raw
= std
::str::from_utf8(&raw
)?
;
697 assert_eq
!(raw
, "acl:1:/:user1@pbs:NoAccess\n");
703 fn test_no_access_overwrite() -> Result
<(), Error
> {
705 let mut tree
= AclTree
::new();
707 tree
.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
709 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");
711 tree
.insert_user_role("/storage", "user1@pbs", "Admin", true);
712 tree
.insert_user_role("/storage", "user1@pbs", "Audit", true);
714 check_roles(&tree
, "user1@pbs", "/storage", "Admin,Audit");
716 tree
.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
718 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");