1 use std
::collections
::{BTreeMap, BTreeSet, HashMap}
;
3 use std
::path
::{Path, PathBuf}
;
5 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
::api
::{api, schema::*}
;
15 use proxmox
::constnamedbitmap
;
17 use crate::api2
::types
::{Authid, Userid}
;
19 // define Privilege bitfield
22 /// Contains a list of privilege name to privilege value mappings.
24 /// The names are used when displaying/persisting privileges anywhere, the values are used to
25 /// allow easy matching of privileges as bitflags.
27 /// Sys.Audit allows knowing about the system and its status
28 PRIV_SYS_AUDIT("Sys.Audit");
29 /// Sys.Modify allows modifying system-level configuration
30 PRIV_SYS_MODIFY("Sys.Modify");
31 /// Sys.Modify allows to poweroff/reboot/.. the system
32 PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement");
34 /// Datastore.Audit allows knowing about a datastore,
35 /// including reading the configuration entry and listing its contents
36 PRIV_DATASTORE_AUDIT("Datastore.Audit");
37 /// Datastore.Allocate allows creating or deleting datastores
38 PRIV_DATASTORE_ALLOCATE("Datastore.Allocate");
39 /// Datastore.Modify allows modifying a datastore and its contents
40 PRIV_DATASTORE_MODIFY("Datastore.Modify");
41 /// Datastore.Read allows reading arbitrary backup contents
42 PRIV_DATASTORE_READ("Datastore.Read");
43 /// Allows verifying a datastore
44 PRIV_DATASTORE_VERIFY("Datastore.Verify");
46 /// Datastore.Backup allows Datastore.Read|Verify and creating new snapshots,
47 /// but also requires backup ownership
48 PRIV_DATASTORE_BACKUP("Datastore.Backup");
49 /// Datastore.Prune allows deleting snapshots,
50 /// but also requires backup ownership
51 PRIV_DATASTORE_PRUNE("Datastore.Prune");
53 /// Permissions.Modify allows modifying ACLs
54 PRIV_PERMISSIONS_MODIFY("Permissions.Modify");
56 /// Remote.Audit allows reading remote.cfg and sync.cfg entries
57 PRIV_REMOTE_AUDIT("Remote.Audit");
58 /// Remote.Modify allows modifying remote.cfg
59 PRIV_REMOTE_MODIFY("Remote.Modify");
60 /// Remote.Read allows reading data from a configured `Remote`
61 PRIV_REMOTE_READ("Remote.Read");
63 /// Sys.Console allows access to the system's console
64 PRIV_SYS_CONSOLE("Sys.Console");
66 /// Tape.Audit allows reading tape backup configuration and status
67 PRIV_TAPE_AUDIT("Tape.Audit");
68 /// Tape.Modify allows modifying tape backup configuration
69 PRIV_TAPE_MODIFY("Tape.Modify");
70 /// Tape.Write allows writing tape media
71 PRIV_TAPE_WRITE("Tape.Write");
72 /// Tape.Read allows reading tape backup configuration and media contents
73 PRIV_TAPE_READ("Tape.Read");
75 /// Realm.Allocate allows viewing, creating, modifying and deleting realms
76 PRIV_REALM_ALLOCATE("Realm.Allocate");
80 /// Admin always has all privileges. It can do everything except a few actions
81 /// which are limited to the 'root@pam` superuser
82 pub const ROLE_ADMIN
: u64 = std
::u64::MAX
;
84 /// NoAccess can be used to remove privileges from specific (sub-)paths
85 pub const ROLE_NO_ACCESS
: u64 = 0;
88 #[allow(clippy::identity_op)]
89 /// Audit can view configuration and status information, but not modify it.
90 pub const ROLE_AUDIT
: u64 = 0
92 | PRIV_DATASTORE_AUDIT
;
95 #[allow(clippy::identity_op)]
96 /// Datastore.Admin can do anything on the datastore.
97 pub const ROLE_DATASTORE_ADMIN
: u64 = 0
98 | PRIV_DATASTORE_AUDIT
99 | PRIV_DATASTORE_MODIFY
100 | PRIV_DATASTORE_READ
101 | PRIV_DATASTORE_VERIFY
102 | PRIV_DATASTORE_BACKUP
103 | PRIV_DATASTORE_PRUNE
;
106 #[allow(clippy::identity_op)]
107 /// Datastore.Reader can read/verify datastore content and do restore
108 pub const ROLE_DATASTORE_READER
: u64 = 0
109 | PRIV_DATASTORE_AUDIT
110 | PRIV_DATASTORE_VERIFY
111 | PRIV_DATASTORE_READ
;
114 #[allow(clippy::identity_op)]
115 /// Datastore.Backup can do backup and restore, but no prune.
116 pub const ROLE_DATASTORE_BACKUP
: u64 = 0
117 | PRIV_DATASTORE_BACKUP
;
120 #[allow(clippy::identity_op)]
121 /// Datastore.PowerUser can do backup, restore, and prune.
122 pub const ROLE_DATASTORE_POWERUSER
: u64 = 0
123 | PRIV_DATASTORE_PRUNE
124 | PRIV_DATASTORE_BACKUP
;
127 #[allow(clippy::identity_op)]
128 /// Datastore.Audit can audit the datastore.
129 pub const ROLE_DATASTORE_AUDIT
: u64 = 0
130 | PRIV_DATASTORE_AUDIT
;
133 #[allow(clippy::identity_op)]
134 /// Remote.Audit can audit the remote
135 pub const ROLE_REMOTE_AUDIT
: u64 = 0
139 #[allow(clippy::identity_op)]
140 /// Remote.Admin can do anything on the remote.
141 pub const ROLE_REMOTE_ADMIN
: u64 = 0
147 #[allow(clippy::identity_op)]
148 /// Remote.SyncOperator can do read and prune on the remote.
149 pub const ROLE_REMOTE_SYNC_OPERATOR
: u64 = 0
154 #[allow(clippy::identity_op)]
155 /// Tape.Audit can audit the tape backup configuration and media content
156 pub const ROLE_TAPE_AUDIT
: u64 = 0
160 #[allow(clippy::identity_op)]
161 /// Tape.Admin can do anything on the tape backup
162 pub const ROLE_TAPE_ADMIN
: u64 = 0
169 #[allow(clippy::identity_op)]
170 /// Tape.Operator can do tape backup and restore (but no configuration changes)
171 pub const ROLE_TAPE_OPERATOR
: u64 = 0
177 #[allow(clippy::identity_op)]
178 /// Tape.Reader can do read and inspect tape content
179 pub const ROLE_TAPE_READER
: u64 = 0
183 /// NoAccess can be used to remove privileges from specific (sub-)paths
184 pub const ROLE_NAME_NO_ACCESS
: &str = "NoAccess";
190 #[derive(Serialize, Deserialize)]
191 /// Enum representing roles via their [PRIVILEGES] combination.
193 /// Since privileges are implemented as bitflags, each unique combination of privileges maps to a
194 /// single, unique `u64` value that is used in this enum definition.
201 NoAccess
= ROLE_NO_ACCESS
,
202 /// Datastore Administrator
203 DatastoreAdmin
= ROLE_DATASTORE_ADMIN
,
204 /// Datastore Reader (inspect datastore content and do restores)
205 DatastoreReader
= ROLE_DATASTORE_READER
,
206 /// Datastore Backup (backup and restore owned backups)
207 DatastoreBackup
= ROLE_DATASTORE_BACKUP
,
208 /// Datastore PowerUser (backup, restore and prune owned backup)
209 DatastorePowerUser
= ROLE_DATASTORE_POWERUSER
,
210 /// Datastore Auditor
211 DatastoreAudit
= ROLE_DATASTORE_AUDIT
,
213 RemoteAudit
= ROLE_REMOTE_AUDIT
,
214 /// Remote Administrator
215 RemoteAdmin
= ROLE_REMOTE_ADMIN
,
216 /// Syncronisation Opertator
217 RemoteSyncOperator
= ROLE_REMOTE_SYNC_OPERATOR
,
219 TapeAudit
= ROLE_TAPE_AUDIT
,
220 /// Tape Administrator
221 TapeAdmin
= ROLE_TAPE_ADMIN
,
223 TapeOperator
= ROLE_TAPE_OPERATOR
,
225 TapeReader
= ROLE_TAPE_READER
,
228 impl FromStr
for Role
{
229 type Err
= value
::Error
;
231 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
232 Self::deserialize(s
.into_deserializer())
237 /// Map of pre-defined [Roles](Role) to their associated [privileges](PRIVILEGES) combination and
239 pub static ref ROLE_NAMES
: HashMap
<&'
static str, (u64, &'
static str)> = {
240 let mut map
= HashMap
::new();
242 let list
= match Role
::API_SCHEMA
{
243 Schema
::String(StringSchema { format: Some(ApiStringFormat::Enum(list)), .. }
) => list
,
247 for entry
in list
.iter() {
248 let privs
: u64 = Role
::from_str(entry
.value
).unwrap() as u64;
249 map
.insert(entry
.value
, (privs
, entry
.description
));
256 pub(crate) fn split_acl_path(path
: &str) -> Vec
<&str> {
257 let items
= path
.split('
/'
);
259 let mut components
= vec
![];
265 components
.push(name
);
271 /// Check whether a given ACL `path` conforms to the expected schema.
273 /// Currently this just checks for the number of components for various sub-trees.
274 pub fn check_acl_path(path
: &str) -> Result
<(), Error
> {
275 let components
= split_acl_path(path
);
277 let components_len
= components
.len();
279 if components_len
== 0 {
282 match components
[0] {
284 if components_len
== 1 {
287 match components
[1] {
288 "acl" | "users" | "domains" => {
289 if components_len
== 2 {
293 // /access/openid/{endpoint}
295 if components_len
<= 3 {
303 // /datastore/{store}
304 if components_len
<= 2 {
309 // /remote/{remote}/{store}
310 if components_len
<= 3 {
315 if components_len
== 1 {
318 match components
[1] {
319 "certificates" | "disks" | "log" | "status" | "tasks" | "time" => {
320 if components_len
== 2 {
325 // /system/services/{service}
326 if components_len
<= 3 {
331 if components_len
== 2 {
334 match components
[2] {
336 if components_len
== 3 {
341 // /system/network/interfaces/{iface}
342 if components_len
<= 4 {
353 if components_len
== 1 {
356 match components
[1] {
358 // /tape/device/{name}
359 if components_len
<= 3 {
365 if components_len
<= 3 {
371 if components_len
<= 3 {
381 bail
!("invalid acl path '{}'.", path
);
384 /// Tree representing a parsed acl.cfg
387 /// Root node of the tree.
389 /// The rest of the tree is available via [find_node()](AclTree::find_node()) or an
390 /// [AclTreeNode]'s [children](AclTreeNode::children) member.
391 pub root
: AclTreeNode
,
394 /// Node representing ACLs for a certain ACL path.
396 pub struct AclTreeNode
{
397 /// [User](crate::config::user::User) or
398 /// [Token](crate::config::user::ApiToken) ACLs for this node.
399 pub users
: HashMap
<Authid
, HashMap
<String
, bool
>>,
400 /// `Group` ACLs for this node (not yet implemented)
401 pub groups
: HashMap
<String
, HashMap
<String
, bool
>>,
402 /// `AclTreeNodes` representing ACL paths directly below the current one.
403 pub children
: BTreeMap
<String
, AclTreeNode
>,
407 /// Creates a new, empty AclTreeNode.
408 pub fn new() -> Self {
410 users
: HashMap
::new(),
411 groups
: HashMap
::new(),
412 children
: BTreeMap
::new(),
416 /// Returns applicable [Role] and their propagation status for a given
417 /// [Authid](crate::api2::types::Authid).
419 /// If the `Authid` is a [User](crate::config::user::User) that has no specific `Roles` configured on this node,
420 /// applicable `Group` roles will be returned instead.
422 /// If `leaf` is `false`, only those roles where the propagate flag in the ACL is set to `true`
423 /// are returned. Otherwise, all roles will be returned.
424 pub fn extract_roles(&self, auth_id
: &Authid
, leaf
: bool
) -> HashMap
<String
, bool
> {
425 let user_roles
= self.extract_user_roles(auth_id
, leaf
);
426 if !user_roles
.is_empty() || auth_id
.is_token() {
427 // user privs always override group privs
431 self.extract_group_roles(auth_id
.user(), leaf
)
434 fn extract_user_roles(&self, auth_id
: &Authid
, leaf
: bool
) -> HashMap
<String
, bool
> {
435 let mut map
= HashMap
::new();
437 let roles
= match self.users
.get(auth_id
) {
442 for (role
, propagate
) in roles
{
443 if *propagate
|| leaf
{
444 if role
== ROLE_NAME_NO_ACCESS
{
445 // return a map with a single role 'NoAccess'
446 let mut map
= HashMap
::new();
447 map
.insert(role
.to_string(), false);
450 map
.insert(role
.to_string(), *propagate
);
457 fn extract_group_roles(&self, _user
: &Userid
, leaf
: bool
) -> HashMap
<String
, bool
> {
458 let mut map
= HashMap
::new();
460 #[allow(clippy::for_kv_map)]
461 for (_group
, roles
) in &self.groups
{
462 let is_member
= false; // fixme: check if user is member of the group
467 for (role
, propagate
) in roles
{
468 if *propagate
|| leaf
{
469 if role
== ROLE_NAME_NO_ACCESS
{
470 // return a map with a single role 'NoAccess'
471 let mut map
= HashMap
::new();
472 map
.insert(role
.to_string(), false);
475 map
.insert(role
.to_string(), *propagate
);
483 fn delete_group_role(&mut self, group
: &str, role
: &str) {
484 let roles
= match self.groups
.get_mut(group
) {
491 fn delete_user_role(&mut self, auth_id
: &Authid
, role
: &str) {
492 let roles
= match self.users
.get_mut(auth_id
) {
499 fn insert_group_role(&mut self, group
: String
, role
: String
, propagate
: bool
) {
500 let map
= self.groups
.entry(group
).or_default();
501 if role
== ROLE_NAME_NO_ACCESS
{
503 map
.insert(role
, propagate
);
505 map
.remove(ROLE_NAME_NO_ACCESS
);
506 map
.insert(role
, propagate
);
510 fn insert_user_role(&mut self, auth_id
: Authid
, role
: String
, propagate
: bool
) {
511 let map
= self.users
.entry(auth_id
).or_default();
512 if role
== ROLE_NAME_NO_ACCESS
{
514 map
.insert(role
, propagate
);
516 map
.remove(ROLE_NAME_NO_ACCESS
);
517 map
.insert(role
, propagate
);
523 /// Create a new, empty ACL tree with a single, empty root [node](AclTreeNode)
524 pub fn new() -> Self {
526 root
: AclTreeNode
::new(),
530 /// Iterates over the tree looking for a node matching `path`.
531 pub fn find_node(&mut self, path
: &str) -> Option
<&mut AclTreeNode
> {
532 let path
= split_acl_path(path
);
536 fn get_node(&mut self, path
: &[&str]) -> Option
<&mut AclTreeNode
> {
537 let mut node
= &mut self.root
;
539 node
= match node
.children
.get_mut(*comp
) {
547 fn get_or_insert_node(&mut self, path
: &[&str]) -> &mut AclTreeNode
{
548 let mut node
= &mut self.root
;
552 .entry(String
::from(*comp
))
558 /// Deletes the specified `role` from the `group`'s ACL on `path`.
560 /// Never fails, even if the `path` has no ACLs configured, or the `group`/`role` combination
561 /// does not exist on `path`.
562 pub fn delete_group_role(&mut self, path
: &str, group
: &str, role
: &str) {
563 let path
= split_acl_path(path
);
564 let node
= match self.get_node(&path
) {
568 node
.delete_group_role(group
, role
);
571 /// Deletes the specified `role` from the `user`'s ACL on `path`.
573 /// Never fails, even if the `path` has no ACLs configured, or the `user`/`role` combination
574 /// does not exist on `path`.
575 pub fn delete_user_role(&mut self, path
: &str, auth_id
: &Authid
, role
: &str) {
576 let path
= split_acl_path(path
);
577 let node
= match self.get_node(&path
) {
581 node
.delete_user_role(auth_id
, role
);
584 /// Inserts the specified `role` into the `group` ACL on `path`.
586 /// The [AclTreeNode] representing `path` will be created and inserted into the tree if
588 pub fn insert_group_role(&mut self, path
: &str, group
: &str, role
: &str, propagate
: bool
) {
589 let path
= split_acl_path(path
);
590 let node
= self.get_or_insert_node(&path
);
591 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
594 /// Inserts the specified `role` into the `user` ACL on `path`.
596 /// The [AclTreeNode] representing `path` will be created and inserted into the tree if
598 pub fn insert_user_role(&mut self, path
: &str, auth_id
: &Authid
, role
: &str, propagate
: bool
) {
599 let path
= split_acl_path(path
);
600 let node
= self.get_or_insert_node(&path
);
601 node
.insert_user_role(auth_id
.to_owned(), role
.to_string(), propagate
);
604 fn write_node_config(node
: &AclTreeNode
, path
: &str, w
: &mut dyn Write
) -> Result
<(), Error
> {
605 let mut role_ug_map0
= HashMap
::new();
606 let mut role_ug_map1
= HashMap
::new();
608 for (auth_id
, roles
) in &node
.users
{
609 // no need to save, because root is always 'Administrator'
610 if !auth_id
.is_token() && auth_id
.user() == "root@pam" {
613 for (role
, propagate
) in roles
{
614 let role
= role
.as_str();
615 let auth_id
= auth_id
.to_string();
619 .or_insert_with(BTreeSet
::new
)
624 .or_insert_with(BTreeSet
::new
)
630 for (group
, roles
) in &node
.groups
{
631 for (role
, propagate
) in roles
{
632 let group
= format
!("@{}", group
);
636 .or_insert_with(BTreeSet
::new
)
641 .or_insert_with(BTreeSet
::new
)
647 fn group_by_property_list(
648 item_property_map
: &HashMap
<&str, BTreeSet
<String
>>,
649 ) -> BTreeMap
<String
, BTreeSet
<String
>> {
650 let mut result_map
= BTreeMap
::new();
651 for (item
, property_map
) in item_property_map
{
652 let item_list
= property_map
.iter().fold(String
::new(), |mut acc
, v
| {
661 .or_insert_with(BTreeSet
::new
)
662 .insert(item
.to_string());
667 let uglist_role_map0
= group_by_property_list(&role_ug_map0
);
668 let uglist_role_map1
= group_by_property_list(&role_ug_map1
);
670 fn role_list(roles
: &BTreeSet
<String
>) -> String
{
671 if roles
.contains(ROLE_NAME_NO_ACCESS
) {
672 return String
::from(ROLE_NAME_NO_ACCESS
);
674 roles
.iter().fold(String
::new(), |mut acc
, v
| {
683 for (uglist
, roles
) in &uglist_role_map0
{
684 let role_list
= role_list(roles
);
688 if path
.is_empty() { "/" }
else { path }
,
694 for (uglist
, roles
) in &uglist_role_map1
{
695 let role_list
= role_list(roles
);
699 if path
.is_empty() { "/" }
else { path }
,
705 for (name
, child
) in node
.children
.iter() {
706 let child_path
= format
!("{}/{}", path
, name
);
707 Self::write_node_config(child
, &child_path
, w
)?
;
713 fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
714 Self::write_node_config(&self.root
, "", w
)
717 fn parse_acl_line(&mut self, line
: &str) -> Result
<(), Error
> {
718 let items
: Vec
<&str> = line
.split('
:'
).collect();
720 if items
.len() != 5 {
721 bail
!("wrong number of items.");
724 if items
[0] != "acl" {
725 bail
!("line does not start with 'acl'.");
728 let propagate
= if items
[1] == "0" {
730 } else if items
[1] == "1" {
733 bail
!("expected '0' or '1' for propagate flag.");
736 let path_str
= items
[2];
737 let path
= split_acl_path(path_str
);
738 let node
= self.get_or_insert_node(&path
);
740 let uglist
: Vec
<&str> = items
[3].split('
,'
).map(|v
| v
.trim()).collect();
742 let rolelist
: Vec
<&str> = items
[4].split('
,'
).map(|v
| v
.trim()).collect();
744 for user_or_group
in &uglist
{
745 for role
in &rolelist
{
746 if !ROLE_NAMES
.contains_key(role
) {
747 bail
!("unknown role '{}'", role
);
749 if let Some(group
) = user_or_group
.strip_prefix('@'
) {
750 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
752 node
.insert_user_role(user_or_group
.parse()?
, role
.to_string(), propagate
);
760 fn load(filename
: &Path
) -> Result
<(Self, [u8; 32]), Error
> {
761 let mut tree
= Self::new();
763 let raw
= match std
::fs
::read_to_string(filename
) {
766 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
769 bail
!("unable to read acl config {:?} - {}", filename
, err
);
774 let digest
= openssl
::sha
::sha256(raw
.as_bytes());
776 for (linenr
, line
) in raw
.lines().enumerate() {
777 let line
= line
.trim();
781 if let Err(err
) = tree
.parse_acl_line(line
) {
783 "unable to parse acl config {:?}, line {} - {}",
795 pub(crate) fn from_raw(raw
: &str) -> Result
<Self, Error
> {
796 let mut tree
= Self::new();
797 for (linenr
, line
) in raw
.lines().enumerate() {
798 let line
= line
.trim();
802 if let Err(err
) = tree
.parse_acl_line(line
) {
804 "unable to parse acl config data, line {} - {}",
813 /// Returns a map of role name and propagation status for a given `auth_id` and `path`.
815 /// This will collect role mappings according to the following algorithm:
816 /// - iterate over all intermediate nodes along `path` and collect roles with `propagate` set
817 /// - get all (propagating and non-propagating) roles for last component of path
818 /// - more specific role maps replace less specific role maps
819 /// -- user/token is more specific than group at each level
820 /// -- roles lower in the tree are more specific than those higher up along the path
821 pub fn roles(&self, auth_id
: &Authid
, path
: &[&str]) -> HashMap
<String
, bool
> {
822 let mut node
= &self.root
;
823 let mut role_map
= node
.extract_roles(auth_id
, path
.is_empty());
825 for (pos
, comp
) in path
.iter().enumerate() {
826 let last_comp
= (pos
+ 1) == path
.len();
827 node
= match node
.children
.get(*comp
) {
829 None
=> return role_map
, // path not found
832 let new_map
= node
.extract_roles(auth_id
, last_comp
);
833 if !new_map
.is_empty() {
834 // overwrite previous maptings
843 /// Filename where [AclTree] is stored.
844 pub const ACL_CFG_FILENAME
: &str = "/etc/proxmox-backup/acl.cfg";
845 /// Path used to lock the [AclTree] when modifying.
846 pub const ACL_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.acl.lck";
848 /// Reads the [AclTree] from the [default path](ACL_CFG_FILENAME).
849 pub fn config() -> Result
<(AclTree
, [u8; 32]), Error
> {
850 let path
= PathBuf
::from(ACL_CFG_FILENAME
);
854 /// Returns a cached [AclTree] or fresh copy read directly from the [default path](ACL_CFG_FILENAME)
856 /// Since the AclTree is used for every API request's permission check, this caching mechanism
857 /// allows to skip reading and parsing the file again if it is unchanged.
858 pub fn cached_config() -> Result
<Arc
<AclTree
>, Error
> {
860 data
: Option
<Arc
<AclTree
>>,
862 last_mtime_nsec
: i64,
866 static ref CACHED_CONFIG
: RwLock
<ConfigCache
> = RwLock
::new(ConfigCache
{
873 let stat
= match nix
::sys
::stat
::stat(ACL_CFG_FILENAME
) {
874 Ok(stat
) => Some(stat
),
875 Err(nix
::Error
::Sys(nix
::errno
::Errno
::ENOENT
)) => None
,
876 Err(err
) => bail
!("unable to stat '{}' - {}", ACL_CFG_FILENAME
, err
),
881 let cache
= CACHED_CONFIG
.read().unwrap();
882 if let Some(ref config
) = cache
.data
{
883 if let Some(stat
) = stat
{
884 if stat
.st_mtime
== cache
.last_mtime
&& stat
.st_mtime_nsec
== cache
.last_mtime_nsec
886 return Ok(config
.clone());
888 } else if cache
.last_mtime
== 0 && cache
.last_mtime_nsec
== 0 {
889 return Ok(config
.clone());
894 let (config
, _digest
) = config()?
;
895 let config
= Arc
::new(config
);
897 let mut cache
= CACHED_CONFIG
.write().unwrap();
898 if let Some(stat
) = stat
{
899 cache
.last_mtime
= stat
.st_mtime
;
900 cache
.last_mtime_nsec
= stat
.st_mtime_nsec
;
902 cache
.data
= Some(config
.clone());
907 /// Saves an [AclTree] to the [default path](ACL_CFG_FILENAME), ensuring proper ownership and
908 /// file permissions.
909 pub fn save_config(acl
: &AclTree
) -> Result
<(), Error
> {
910 let mut raw
: Vec
<u8> = Vec
::new();
912 acl
.write_config(&mut raw
)?
;
914 pbs_config
::replace_backup_config(ACL_CFG_FILENAME
, &raw
)
922 use crate::api2
::types
::Authid
;
924 fn check_roles(tree
: &AclTree
, auth_id
: &Authid
, path
: &str, expected_roles
: &str) {
925 let path_vec
= super::split_acl_path(path
);
927 .roles(auth_id
, &path_vec
)
929 .map(|(v
, _
)| v
.clone())
930 .collect
::<Vec
<String
>>();
932 let roles
= roles
.join(",");
935 roles
, expected_roles
,
936 "\nat check_roles for '{}' on '{}'",
942 fn test_acl_line_compression() {
943 let tree
= AclTree
::from_raw(
945 acl:0:/store/store2:user1@pbs:Admin\n\
946 acl:0:/store/store2:user2@pbs:Admin\n\
947 acl:0:/store/store2:user1@pbs:DatastoreBackup\n\
948 acl:0:/store/store2:user2@pbs:DatastoreBackup\n\
951 .expect("failed to parse acl tree");
953 let mut raw
: Vec
<u8> = Vec
::new();
954 tree
.write_config(&mut raw
)
955 .expect("failed to write acl tree");
956 let raw
= std
::str::from_utf8(&raw
).expect("acl tree is not valid utf8");
960 "acl:0:/store/store2:user1@pbs,user2@pbs:Admin,DatastoreBackup\n"
965 fn test_roles_1() -> Result
<(), Error
> {
966 let tree
= AclTree
::from_raw(
968 acl:1:/storage:user1@pbs:Admin
969 acl:1:/storage/store1:user1@pbs:DatastoreBackup
970 acl:1:/storage/store2:user2@pbs:DatastoreBackup
973 let user1
: Authid
= "user1@pbs".parse()?
;
974 check_roles(&tree
, &user1
, "/", "");
975 check_roles(&tree
, &user1
, "/storage", "Admin");
976 check_roles(&tree
, &user1
, "/storage/store1", "DatastoreBackup");
977 check_roles(&tree
, &user1
, "/storage/store2", "Admin");
979 let user2
: Authid
= "user2@pbs".parse()?
;
980 check_roles(&tree
, &user2
, "/", "");
981 check_roles(&tree
, &user2
, "/storage", "");
982 check_roles(&tree
, &user2
, "/storage/store1", "");
983 check_roles(&tree
, &user2
, "/storage/store2", "DatastoreBackup");
989 fn test_role_no_access() -> Result
<(), Error
> {
990 let tree
= AclTree
::from_raw(
992 acl:1:/:user1@pbs:Admin
993 acl:1:/storage:user1@pbs:NoAccess
994 acl:1:/storage/store1:user1@pbs:DatastoreBackup
997 let user1
: Authid
= "user1@pbs".parse()?
;
998 check_roles(&tree
, &user1
, "/", "Admin");
999 check_roles(&tree
, &user1
, "/storage", "NoAccess");
1000 check_roles(&tree
, &user1
, "/storage/store1", "DatastoreBackup");
1001 check_roles(&tree
, &user1
, "/storage/store2", "NoAccess");
1002 check_roles(&tree
, &user1
, "/system", "Admin");
1004 let tree
= AclTree
::from_raw(
1006 acl:1:/:user1@pbs:Admin
1007 acl:0:/storage:user1@pbs:NoAccess
1008 acl:1:/storage/store1:user1@pbs:DatastoreBackup
1011 check_roles(&tree
, &user1
, "/", "Admin");
1012 check_roles(&tree
, &user1
, "/storage", "NoAccess");
1013 check_roles(&tree
, &user1
, "/storage/store1", "DatastoreBackup");
1014 check_roles(&tree
, &user1
, "/storage/store2", "Admin");
1015 check_roles(&tree
, &user1
, "/system", "Admin");
1021 fn test_role_add_delete() -> Result
<(), Error
> {
1022 let mut tree
= AclTree
::new();
1024 let user1
: Authid
= "user1@pbs".parse()?
;
1026 tree
.insert_user_role("/", &user1
, "Admin", true);
1027 tree
.insert_user_role("/", &user1
, "Audit", true);
1029 check_roles(&tree
, &user1
, "/", "Admin,Audit");
1031 tree
.insert_user_role("/", &user1
, "NoAccess", true);
1032 check_roles(&tree
, &user1
, "/", "NoAccess");
1034 let mut raw
: Vec
<u8> = Vec
::new();
1035 tree
.write_config(&mut raw
)?
;
1036 let raw
= std
::str::from_utf8(&raw
)?
;
1038 assert_eq
!(raw
, "acl:1:/:user1@pbs:NoAccess\n");
1044 fn test_no_access_overwrite() -> Result
<(), Error
> {
1045 let mut tree
= AclTree
::new();
1047 let user1
: Authid
= "user1@pbs".parse()?
;
1049 tree
.insert_user_role("/storage", &user1
, "NoAccess", true);
1051 check_roles(&tree
, &user1
, "/storage", "NoAccess");
1053 tree
.insert_user_role("/storage", &user1
, "Admin", true);
1054 tree
.insert_user_role("/storage", &user1
, "Audit", true);
1056 check_roles(&tree
, &user1
, "/storage", "Admin,Audit");
1058 tree
.insert_user_role("/storage", &user1
, "NoAccess", true);
1060 check_roles(&tree
, &user1
, "/storage", "NoAccess");