2 use std
::collections
::{HashMap, HashSet, BTreeMap, BTreeSet}
;
3 use std
::path
::{PathBuf, Path}
;
4 use std
::sync
::{Arc, RwLock}
;
7 use anyhow
::{bail, Error}
;
9 use lazy_static
::lazy_static
;
11 use ::serde
::{Deserialize, Serialize}
;
12 use serde
::de
::{value, IntoDeserializer}
;
14 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
15 use proxmox
::constnamemap
;
16 use proxmox
::api
::{api, schema::*}
;
18 // define Privilege bitfield
21 /// Contains a list of Privileges
23 PRIV_SYS_AUDIT("Sys.Audit") = 1 << 0;
24 PRIV_SYS_MODIFY("Sys.Modify") = 1 << 1;
25 PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement") = 1 << 2;
27 PRIV_DATASTORE_AUDIT("Datastore.Audit") = 1 << 3;
28 PRIV_DATASTORE_MODIFY("Datastore.Modify") = 1 << 4;
29 PRIV_DATASTORE_READ("Datastore.Read") = 1 << 5;
31 /// Datastore.Backup also requires backup ownership
32 PRIV_DATASTORE_BACKUP("Datastore.Backup") = 1 << 6;
33 /// Datastore.Prune also requires backup ownership
34 PRIV_DATASTORE_PRUNE("Datastore.Prune") = 1 << 7;
36 PRIV_PERMISSIONS_MODIFY("Permissions.Modify") = 1 << 8;
38 PRIV_REMOTE_AUDIT("Remote.Audit") = 1 << 9;
39 PRIV_REMOTE_MODIFY("Remote.Modify") = 1 << 10;
40 PRIV_REMOTE_READ("Remote.Read") = 1 << 11;
41 PRIV_REMOTE_PRUNE("Remote.Prune") = 1 << 12;
43 PRIV_SYS_CONSOLE("Sys.Console") = 1 << 13;
48 pub const ROLE_ADMIN
: u64 = std
::u64::MAX
;
49 pub const ROLE_NO_ACCESS
: u64 = 0;
51 pub const ROLE_AUDIT
: u64 =
55 /// Datastore.Admin can do anything on the datastore.
56 pub const ROLE_DATASTORE_ADMIN
: u64 =
57 PRIV_DATASTORE_AUDIT
|
58 PRIV_DATASTORE_MODIFY
|
60 PRIV_DATASTORE_BACKUP
|
63 /// Datastore.Reader can read datastore content an do restore
64 pub const ROLE_DATASTORE_READER
: u64 =
65 PRIV_DATASTORE_AUDIT
|
68 /// Datastore.Backup can do backup and restore, but no prune.
69 pub const ROLE_DATASTORE_BACKUP
: u64 =
70 PRIV_DATASTORE_BACKUP
;
72 /// Datastore.PowerUser can do backup, restore, and prune.
73 pub const ROLE_DATASTORE_POWERUSER
: u64 =
74 PRIV_DATASTORE_PRUNE
|
75 PRIV_DATASTORE_BACKUP
;
77 /// Datastore.Audit can audit the datastore.
78 pub const ROLE_DATASTORE_AUDIT
: u64 =
81 /// Remote.Audit can audit the remote
82 pub const ROLE_REMOTE_AUDIT
: u64 =
85 /// Remote.Admin can do anything on the remote.
86 pub const ROLE_REMOTE_ADMIN
: u64 =
92 /// Remote.SyncOperator can do read and prune on the remote.
93 pub const ROLE_REMOTE_SYNC_OPERATOR
: u64 =
98 pub const ROLE_NAME_NO_ACCESS
: &str ="NoAccess";
102 #[derive(Serialize, Deserialize)]
110 NoAccess
= ROLE_NO_ACCESS
,
111 /// Datastore Administrator
112 DatastoreAdmin
= ROLE_DATASTORE_ADMIN
,
113 /// Datastore Reader (inspect datastore content and do restores)
114 DatastoreReader
= ROLE_DATASTORE_READER
,
115 /// Datastore Backup (backup and restore owned backups)
116 DatastoreBackup
= ROLE_DATASTORE_BACKUP
,
117 /// Datastore PowerUser (backup, restore and prune owned backup)
118 DatastorePowerUser
= ROLE_DATASTORE_POWERUSER
,
119 /// Datastore Auditor
120 DatastoreAudit
= ROLE_DATASTORE_AUDIT
,
122 RemoteAudit
= ROLE_REMOTE_AUDIT
,
123 /// Remote Administrator
124 RemoteAdmin
= ROLE_REMOTE_ADMIN
,
125 /// Syncronisation Opertator
126 RemoteSyncOperator
= ROLE_REMOTE_SYNC_OPERATOR
,
129 impl FromStr
for Role
{
130 type Err
= value
::Error
;
132 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
133 Self::deserialize(s
.into_deserializer())
138 pub static ref ROLE_NAMES
: HashMap
<&'
static str, (u64, &'
static str)> = {
139 let mut map
= HashMap
::new();
141 let list
= match Role
::API_SCHEMA
{
142 Schema
::String(StringSchema { format: Some(ApiStringFormat::Enum(list)), .. }
) => list
,
146 for entry
in list
.iter() {
147 let privs
: u64 = Role
::from_str(entry
.value
).unwrap() as u64;
148 map
.insert(entry
.value
, (privs
, entry
.description
));
155 pub fn split_acl_path(path
: &str) -> Vec
<&str> {
157 let items
= path
.split('
/'
);
159 let mut components
= vec
![];
162 if name
.is_empty() { continue; }
163 components
.push(name
);
169 pub fn check_acl_path(path
: &str) -> Result
<(), Error
> {
171 let components
= split_acl_path(path
);
173 let components_len
= components
.len();
175 if components_len
== 0 { return Ok(()); }
176 match components
[0] {
178 if components_len
== 1 { return Ok(()); }
179 match components
[1] {
181 if components_len
== 2 { return Ok(()); }
186 "datastore" => { // /datastore/{store}
187 if components_len
<= 2 { return Ok(()); }
189 "remote" => { // /remote/{remote}
/{store}
190 if components_len
<= 3 { return Ok(()); }
193 if components_len
== 1 { return Ok(()); }
194 match components
[1] {
195 "disks" | "log" | "status" | "tasks" | "time" => {
196 if components_len
== 2 { return Ok(()); }
198 "services" => { // /system/services/{service}
199 if components_len
<= 3 { return Ok(()); }
202 if components_len
== 2 { return Ok(()); }
203 match components
[2] {
205 if components_len
== 3 { return Ok(()); }
207 "interfaces" => { // /system/network/interfaces/{iface}
208 if components_len
<= 4 { return Ok(()); }
219 bail
!("invalid acl path '{}'.", path
);
223 pub root
: AclTreeNode
,
226 pub struct AclTreeNode
{
227 pub users
: HashMap
<String
, HashMap
<String
, bool
>>,
228 pub groups
: HashMap
<String
, HashMap
<String
, bool
>>,
229 pub children
: BTreeMap
<String
, AclTreeNode
>,
234 pub fn new() -> Self {
236 users
: HashMap
::new(),
237 groups
: HashMap
::new(),
238 children
: BTreeMap
::new(),
242 pub fn extract_roles(&self, user
: &str, all
: bool
) -> HashSet
<String
> {
243 let user_roles
= self.extract_user_roles(user
, all
);
244 if !user_roles
.is_empty() {
245 // user privs always override group privs
249 self.extract_group_roles(user
, all
)
252 pub fn extract_user_roles(&self, user
: &str, all
: bool
) -> HashSet
<String
> {
254 let mut set
= HashSet
::new();
256 let roles
= match self.users
.get(user
) {
261 for (role
, propagate
) in roles
{
262 if *propagate
|| all
{
263 if role
== ROLE_NAME_NO_ACCESS
{
264 // return a set with a single role 'NoAccess'
265 let mut set
= HashSet
::new();
266 set
.insert(role
.to_string());
269 set
.insert(role
.to_string());
276 pub fn extract_group_roles(&self, _user
: &str, all
: bool
) -> HashSet
<String
> {
278 let mut set
= HashSet
::new();
280 for (_group
, roles
) in &self.groups
{
281 let is_member
= false; // fixme: check if user is member of the group
282 if !is_member { continue; }
284 for (role
, propagate
) in roles
{
285 if *propagate
|| all
{
286 if role
== ROLE_NAME_NO_ACCESS
{
287 // return a set with a single role 'NoAccess'
288 let mut set
= HashSet
::new();
289 set
.insert(role
.to_string());
292 set
.insert(role
.to_string());
300 pub fn delete_group_role(&mut self, group
: &str, role
: &str) {
301 let roles
= match self.groups
.get_mut(group
) {
308 pub fn delete_user_role(&mut self, userid
: &str, role
: &str) {
309 let roles
= match self.users
.get_mut(userid
) {
316 pub fn insert_group_role(&mut self, group
: String
, role
: String
, propagate
: bool
) {
317 let map
= self.groups
.entry(group
).or_insert_with(|| HashMap
::new());
318 if role
== ROLE_NAME_NO_ACCESS
{
320 map
.insert(role
, propagate
);
322 map
.remove(ROLE_NAME_NO_ACCESS
);
323 map
.insert(role
, propagate
);
327 pub fn insert_user_role(&mut self, user
: String
, role
: String
, propagate
: bool
) {
328 let map
= self.users
.entry(user
).or_insert_with(|| HashMap
::new());
329 if role
== ROLE_NAME_NO_ACCESS
{
331 map
.insert(role
, propagate
);
333 map
.remove(ROLE_NAME_NO_ACCESS
);
334 map
.insert(role
, propagate
);
341 pub fn new() -> Self {
342 Self { root: AclTreeNode::new() }
345 pub fn find_node(&mut self, path
: &str) -> Option
<&mut AclTreeNode
> {
346 let path
= split_acl_path(path
);
347 return self.get_node(&path
);
350 fn get_node(&mut self, path
: &[&str]) -> Option
<&mut AclTreeNode
> {
351 let mut node
= &mut self.root
;
353 node
= match node
.children
.get_mut(*comp
) {
361 fn get_or_insert_node(&mut self, path
: &[&str]) -> &mut AclTreeNode
{
362 let mut node
= &mut self.root
;
364 node
= node
.children
.entry(String
::from(*comp
))
365 .or_insert_with(|| AclTreeNode
::new());
370 pub fn delete_group_role(&mut self, path
: &str, group
: &str, role
: &str) {
371 let path
= split_acl_path(path
);
372 let node
= match self.get_node(&path
) {
376 node
.delete_group_role(group
, role
);
379 pub fn delete_user_role(&mut self, path
: &str, userid
: &str, role
: &str) {
380 let path
= split_acl_path(path
);
381 let node
= match self.get_node(&path
) {
385 node
.delete_user_role(userid
, role
);
388 pub fn insert_group_role(&mut self, path
: &str, group
: &str, role
: &str, propagate
: bool
) {
389 let path
= split_acl_path(path
);
390 let node
= self.get_or_insert_node(&path
);
391 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
394 pub fn insert_user_role(&mut self, path
: &str, user
: &str, role
: &str, propagate
: bool
) {
395 let path
= split_acl_path(path
);
396 let node
= self.get_or_insert_node(&path
);
397 node
.insert_user_role(user
.to_string(), role
.to_string(), propagate
);
400 fn write_node_config(
404 ) -> Result
<(), Error
> {
406 let mut role_ug_map0
= HashMap
::new();
407 let mut role_ug_map1
= HashMap
::new();
409 for (user
, roles
) in &node
.users
{
410 // no need to save, because root is always 'Administrator'
411 if user
== "root@pam" { continue; }
412 for (role
, propagate
) in roles
{
413 let role
= role
.as_str();
414 let user
= user
.to_string();
416 role_ug_map1
.entry(role
).or_insert_with(|| BTreeSet
::new())
419 role_ug_map0
.entry(role
).or_insert_with(|| BTreeSet
::new())
425 for (group
, roles
) in &node
.groups
{
426 for (role
, propagate
) in roles
{
427 let group
= format
!("@{}", group
);
429 role_ug_map1
.entry(role
).or_insert_with(|| BTreeSet
::new())
432 role_ug_map0
.entry(role
).or_insert_with(|| BTreeSet
::new())
438 fn group_by_property_list(
439 item_property_map
: &HashMap
<&str, BTreeSet
<String
>>,
440 ) -> BTreeMap
<String
, BTreeSet
<String
>> {
441 let mut result_map
= BTreeMap
::new();
442 for (item
, property_map
) in item_property_map
{
443 let item_list
= property_map
.iter().fold(String
::new(), |mut acc
, v
| {
444 if !acc
.is_empty() { acc.push(','); }
448 result_map
.entry(item_list
).or_insert_with(|| BTreeSet
::new())
449 .insert(item
.to_string());
454 let uglist_role_map0
= group_by_property_list(&role_ug_map0
);
455 let uglist_role_map1
= group_by_property_list(&role_ug_map1
);
457 fn role_list(roles
: &BTreeSet
<String
>) -> String
{
458 if roles
.contains(ROLE_NAME_NO_ACCESS
) { return String::from(ROLE_NAME_NO_ACCESS); }
459 roles
.iter().fold(String
::new(), |mut acc
, v
| {
460 if !acc
.is_empty() { acc.push(','); }
466 for (uglist
, roles
) in &uglist_role_map0
{
467 let role_list
= role_list(roles
);
468 writeln
!(w
, "acl:0:{}:{}:{}", if path
.is_empty() { "/" }
else { path }
, uglist
, role_list
)?
;
471 for (uglist
, roles
) in &uglist_role_map1
{
472 let role_list
= role_list(roles
);
473 writeln
!(w
, "acl:1:{}:{}:{}", if path
.is_empty() { "/" }
else { path }
, uglist
, role_list
)?
;
476 for (name
, child
) in node
.children
.iter() {
477 let child_path
= format
!("{}/{}", path
, name
);
478 Self::write_node_config(child
, &child_path
, w
)?
;
484 pub fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
485 Self::write_node_config(&self.root
, "", w
)
488 fn parse_acl_line(&mut self, line
: &str) -> Result
<(), Error
> {
490 let items
: Vec
<&str> = line
.split('
:'
).collect();
492 if items
.len() != 5 {
493 bail
!("wrong number of items.");
496 if items
[0] != "acl" {
497 bail
!("line does not start with 'acl'.");
500 let propagate
= if items
[1] == "0" {
502 } else if items
[1] == "1" {
505 bail
!("expected '0' or '1' for propagate flag.");
508 let path
= split_acl_path(items
[2]);
509 let node
= self.get_or_insert_node(&path
);
511 let uglist
: Vec
<&str> = items
[3].split('
,'
).map(|v
| v
.trim()).collect();
513 let rolelist
: Vec
<&str> = items
[4].split('
,'
).map(|v
| v
.trim()).collect();
515 for user_or_group
in &uglist
{
516 for role
in &rolelist
{
517 if !ROLE_NAMES
.contains_key(role
) {
518 bail
!("unknown role '{}'", role
);
520 if user_or_group
.starts_with('@'
) {
521 let group
= &user_or_group
[1..];
522 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
524 node
.insert_user_role(user_or_group
.to_string(), role
.to_string(), propagate
);
532 pub fn load(filename
: &Path
) -> Result
<(Self, [u8;32]), Error
> {
533 let mut tree
= Self::new();
535 let raw
= match std
::fs
::read_to_string(filename
) {
538 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
541 bail
!("unable to read acl config {:?} - {}", filename
, err
);
546 let digest
= openssl
::sha
::sha256(raw
.as_bytes());
548 for (linenr
, line
) in raw
.lines().enumerate() {
549 let line
= line
.trim();
550 if line
.is_empty() { continue; }
551 if let Err(err
) = tree
.parse_acl_line(line
) {
552 bail
!("unable to parse acl config {:?}, line {} - {}",
553 filename
, linenr
+1, err
);
560 pub fn from_raw(raw
: &str) -> Result
<Self, Error
> {
561 let mut tree
= Self::new();
562 for (linenr
, line
) in raw
.lines().enumerate() {
563 let line
= line
.trim();
564 if line
.is_empty() { continue; }
565 if let Err(err
) = tree
.parse_acl_line(line
) {
566 bail
!("unable to parse acl config data, line {} - {}", linenr
+1, err
);
572 pub fn roles(&self, userid
: &str, path
: &[&str]) -> HashSet
<String
> {
574 let mut node
= &self.root
;
575 let mut role_set
= node
.extract_roles(userid
, path
.is_empty());
577 for (pos
, comp
) in path
.iter().enumerate() {
578 let last_comp
= (pos
+ 1) == path
.len();
579 node
= match node
.children
.get(*comp
) {
581 None
=> return role_set
, // path not found
583 let new_set
= node
.extract_roles(userid
, last_comp
);
584 if !new_set
.is_empty() {
585 // overwrite previous settings
594 pub const ACL_CFG_FILENAME
: &str = "/etc/proxmox-backup/acl.cfg";
595 pub const ACL_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.acl.lck";
597 pub fn config() -> Result
<(AclTree
, [u8; 32]), Error
> {
598 let path
= PathBuf
::from(ACL_CFG_FILENAME
);
602 pub fn cached_config() -> Result
<Arc
<AclTree
>, Error
> {
605 data
: Option
<Arc
<AclTree
>>,
607 last_mtime_nsec
: i64,
611 static ref CACHED_CONFIG
: RwLock
<ConfigCache
> = RwLock
::new(
612 ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 }
);
615 let stat
= match nix
::sys
::stat
::stat(ACL_CFG_FILENAME
) {
616 Ok(stat
) => Some(stat
),
617 Err(nix
::Error
::Sys(nix
::errno
::Errno
::ENOENT
)) => None
,
618 Err(err
) => bail
!("unable to stat '{}' - {}", ACL_CFG_FILENAME
, err
),
622 let cache
= CACHED_CONFIG
.read().unwrap();
623 if let Some(ref config
) = cache
.data
{
624 if let Some(stat
) = stat
{
625 if stat
.st_mtime
== cache
.last_mtime
&& stat
.st_mtime_nsec
== cache
.last_mtime_nsec
{
626 return Ok(config
.clone());
628 } else if cache
.last_mtime
== 0 && cache
.last_mtime_nsec
== 0 {
629 return Ok(config
.clone());
634 let (config
, _digest
) = config()?
;
635 let config
= Arc
::new(config
);
637 let mut cache
= CACHED_CONFIG
.write().unwrap();
638 if let Some(stat
) = stat
{
639 cache
.last_mtime
= stat
.st_mtime
;
640 cache
.last_mtime_nsec
= stat
.st_mtime_nsec
;
642 cache
.data
= Some(config
.clone());
647 pub fn save_config(acl
: &AclTree
) -> Result
<(), Error
> {
648 let mut raw
: Vec
<u8> = Vec
::new();
650 acl
.write_config(&mut raw
)?
;
652 let backup_user
= crate::backup
::backup_user()?
;
653 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640);
654 // set the correct owner/group/permissions while saving file
655 // owner(rw) = root, group(r)= backup
656 let options
= CreateOptions
::new()
658 .owner(nix
::unistd
::ROOT
)
659 .group(backup_user
.gid
);
661 replace_file(ACL_CFG_FILENAME
, &raw
, options
)?
;
676 expected_roles
: &str,
679 let path_vec
= super::split_acl_path(path
);
680 let mut roles
= tree
.roles(user
, &path_vec
)
681 .iter().map(|v
| v
.clone()).collect
::<Vec
<String
>>();
683 let roles
= roles
.join(",");
685 assert_eq
!(roles
, expected_roles
, "\nat check_roles for '{}' on '{}'", user
, path
);
689 fn test_acl_line_compression() -> Result
<(), Error
> {
691 let tree
= AclTree
::from_raw(r
###"
692 acl:0:/store/store2:user1:Admin
693 acl:0:/store/store2:user2:Admin
694 acl:0:/store/store2:user1:DatastoreBackup
695 acl:0:/store/store2:user2:DatastoreBackup
698 let mut raw
: Vec
<u8> = Vec
::new();
699 tree
.write_config(&mut raw
)?
;
700 let raw
= std
::str::from_utf8(&raw
)?
;
702 assert_eq
!(raw
, "acl:0:/store/store2:user1,user2:Admin,DatastoreBackup\n");
708 fn test_roles_1() -> Result
<(), Error
> {
710 let tree
= AclTree
::from_raw(r
###"
711 acl:1:/storage:user1@pbs:Admin
712 acl:1:/storage/store1:user1@pbs:DatastoreBackup
713 acl:1:/storage/store2:user2@pbs:DatastoreBackup
715 check_roles(&tree
, "user1@pbs", "/", "");
716 check_roles(&tree
, "user1@pbs", "/storage", "Admin");
717 check_roles(&tree
, "user1@pbs", "/storage/store1", "DatastoreBackup");
718 check_roles(&tree
, "user1@pbs", "/storage/store2", "Admin");
720 check_roles(&tree
, "user2@pbs", "/", "");
721 check_roles(&tree
, "user2@pbs", "/storage", "");
722 check_roles(&tree
, "user2@pbs", "/storage/store1", "");
723 check_roles(&tree
, "user2@pbs", "/storage/store2", "DatastoreBackup");
729 fn test_role_no_access() -> Result
<(), Error
> {
731 let tree
= AclTree
::from_raw(r
###"
732 acl:1:/:user1@pbs:Admin
733 acl:1:/storage:user1@pbs:NoAccess
734 acl:1:/storage/store1:user1@pbs:DatastoreBackup
736 check_roles(&tree
, "user1@pbs", "/", "Admin");
737 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");
738 check_roles(&tree
, "user1@pbs", "/storage/store1", "DatastoreBackup");
739 check_roles(&tree
, "user1@pbs", "/storage/store2", "NoAccess");
740 check_roles(&tree
, "user1@pbs", "/system", "Admin");
742 let tree
= AclTree
::from_raw(r
###"
743 acl:1:/:user1@pbs:Admin
744 acl:0:/storage:user1@pbs:NoAccess
745 acl:1:/storage/store1:user1@pbs:DatastoreBackup
747 check_roles(&tree
, "user1@pbs", "/", "Admin");
748 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");
749 check_roles(&tree
, "user1@pbs", "/storage/store1", "DatastoreBackup");
750 check_roles(&tree
, "user1@pbs", "/storage/store2", "Admin");
751 check_roles(&tree
, "user1@pbs", "/system", "Admin");
757 fn test_role_add_delete() -> Result
<(), Error
> {
759 let mut tree
= AclTree
::new();
761 tree
.insert_user_role("/", "user1@pbs", "Admin", true);
762 tree
.insert_user_role("/", "user1@pbs", "Audit", true);
764 check_roles(&tree
, "user1@pbs", "/", "Admin,Audit");
766 tree
.insert_user_role("/", "user1@pbs", "NoAccess", true);
767 check_roles(&tree
, "user1@pbs", "/", "NoAccess");
769 let mut raw
: Vec
<u8> = Vec
::new();
770 tree
.write_config(&mut raw
)?
;
771 let raw
= std
::str::from_utf8(&raw
)?
;
773 assert_eq
!(raw
, "acl:1:/:user1@pbs:NoAccess\n");
779 fn test_no_access_overwrite() -> Result
<(), Error
> {
781 let mut tree
= AclTree
::new();
783 tree
.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
785 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");
787 tree
.insert_user_role("/storage", "user1@pbs", "Admin", true);
788 tree
.insert_user_role("/storage", "user1@pbs", "Audit", true);
790 check_roles(&tree
, "user1@pbs", "/storage", "Admin,Audit");
792 tree
.insert_user_role("/storage", "user1@pbs", "NoAccess", true);
794 check_roles(&tree
, "user1@pbs", "/storage", "NoAccess");