2 use std
::collections
::{HashMap, HashSet}
;
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_AUDIT
: u64 =
25 pub const ROLE_STORE_ADMIN
: u64 =
28 PRIV_STORE_ALLOCATE_SPACE
;
30 pub const ROLE_STORE_USER
: u64 =
32 PRIV_STORE_ALLOCATE_SPACE
;
35 static ref ROLE_NAMES
: HashMap
<&'
static str, u64> = {
36 let mut map
= HashMap
::new();
38 map
.insert("Admin", std
::u64::MAX
);
39 map
.insert("Audit", ROLE_AUDIT
);
42 map
.insert("Store.Admin", ROLE_STORE_ADMIN
);
43 map
.insert("Store.User", ROLE_STORE_USER
);
49 fn split_acl_path(path
: &str) -> Vec
<&str> {
51 let items
= path
.split('
/'
);
53 let mut components
= vec
![];
56 if name
.is_empty() { continue; }
57 components
.push(name
);
68 users
: HashMap
<String
, HashMap
<String
, bool
>>,
69 groups
: HashMap
<String
, HashMap
<String
, bool
>>,
70 children
: HashMap
<String
, AclTreeNode
>,
75 pub fn new() -> Self {
77 users
: HashMap
::new(),
78 groups
: HashMap
::new(),
79 children
: HashMap
::new(),
83 pub fn insert_group_role(&mut self, group
: String
, role
: String
, propagate
: bool
) {
85 .entry(group
).or_insert_with(|| HashMap
::new())
86 .insert(role
, propagate
);
89 pub fn insert_user_role(&mut self, user
: String
, role
: String
, propagate
: bool
) {
91 .entry(user
).or_insert_with(|| HashMap
::new())
92 .insert(role
, propagate
);
98 pub fn new() -> Self {
99 Self { root: AclTreeNode::new() }
102 fn get_or_insert_node(&mut self, path
: &[&str]) -> &mut AclTreeNode
{
103 let mut node
= &mut self.root
;
105 node
= node
.children
.entry(String
::from(*comp
))
106 .or_insert_with(|| AclTreeNode
::new());
111 pub fn insert_group_role(&mut self, path
: &str, group
: &str, role
: &str, propagate
: bool
) {
112 let path
= split_acl_path(path
);
113 let node
= self.get_or_insert_node(&path
);
114 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
117 pub fn insert_user_role(&mut self, path
: &str, user
: &str, role
: &str, propagate
: bool
) {
118 let path
= split_acl_path(path
);
119 let node
= self.get_or_insert_node(&path
);
120 node
.insert_user_role(user
.to_string(), role
.to_string(), propagate
);
123 fn write_node_config(
127 ) -> Result
<(), Error
> {
129 let mut role_ug_map0
= HashMap
::new();
130 let mut role_ug_map1
= HashMap
::new();
132 for (user
, roles
) in &node
.users
{
133 // no need to save, because root is always 'Administrator'
134 if user
== "root@pam" { continue; }
135 for (role
, propagate
) in roles
{
136 let role
= role
.as_str();
137 let user
= user
.to_string();
139 role_ug_map1
.entry(role
).or_insert_with(|| HashSet
::new())
142 role_ug_map0
.entry(role
).or_insert_with(|| HashSet
::new())
148 for (group
, roles
) in &node
.groups
{
149 for (role
, propagate
) in roles
{
150 let group
= format
!("@{}", group
);
152 role_ug_map1
.entry(role
).or_insert_with(|| HashSet
::new())
155 role_ug_map0
.entry(role
).or_insert_with(|| HashSet
::new())
161 fn group_by_property_list(
162 item_property_map
: &HashMap
<&str, HashSet
<String
>>,
163 ) -> HashMap
<String
, HashSet
<String
>> {
164 let mut result_map
= HashMap
::new();
165 for (item
, property_map
) in item_property_map
{
166 let mut item_list
= property_map
.iter().map(|v
| v
.as_str())
167 .collect
::<Vec
<&str>>();
169 let item_list
= item_list
.join(",");
170 result_map
.entry(item_list
).or_insert_with(|| HashSet
::new())
171 .insert(item
.to_string());
176 let mut uglist_role_map0
= group_by_property_list(&role_ug_map0
)
178 .collect
::<Vec
<(String
, HashSet
<String
>)>>();
179 uglist_role_map0
.sort_unstable_by(|a
,b
| a
.0.cmp(&b
.0));
181 let mut uglist_role_map1
= group_by_property_list(&role_ug_map1
)
183 .collect
::<Vec
<(String
, HashSet
<String
>)>>();
184 uglist_role_map1
.sort_unstable_by(|a
,b
| a
.0.cmp(&b
.0));
187 for (uglist
, roles
) in uglist_role_map0
{
188 let mut role_list
= roles
.iter().map(|v
| v
.as_str())
189 .collect
::<Vec
<&str>>();
191 writeln
!(w
, "acl:0:{}:{}:{}", path
, uglist
, role_list
.join(","))?
;
194 for (uglist
, roles
) in uglist_role_map1
{
195 let mut role_list
= roles
.iter().map(|v
| v
.as_str())
196 .collect
::<Vec
<&str>>();
198 writeln
!(w
, "acl:1:{}:{}:{}", path
, uglist
, role_list
.join(","))?
;
201 let mut child_names
= node
.children
.keys().map(|v
| v
.as_str()).collect
::<Vec
<&str>>();
204 for name
in child_names
{
205 let child
= node
.children
.get(name
).unwrap();
206 let child_path
= format
!("{}/{}", path
, name
);
207 Self::write_node_config(child
, &child_path
, w
)?
;
213 pub fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
214 Self::write_node_config(&self.root
, "", w
)
217 fn parse_acl_line(&mut self, line
: &str) -> Result
<(), Error
> {
219 let items
: Vec
<&str> = line
.split('
:'
).collect();
221 if items
.len() != 5 {
222 bail
!("wrong number of items.");
225 if items
[0] != "acl" {
226 bail
!("line does not start with 'acl'.");
229 let propagate
= if items
[1] == "0" {
231 } else if items
[1] == "1" {
234 bail
!("expected '0' or '1' for propagate flag.");
237 let path
= split_acl_path(items
[2]);
238 let node
= self.get_or_insert_node(&path
);
240 let uglist
: Vec
<&str> = items
[3].split('
,'
).map(|v
| v
.trim()).collect();
242 let rolelist
: Vec
<&str> = items
[4].split('
,'
).map(|v
| v
.trim()).collect();
244 for user_or_group
in &uglist
{
245 for role
in &rolelist
{
246 if !ROLE_NAMES
.contains_key(role
) {
247 bail
!("unknown role '{}'", role
);
249 if user_or_group
.starts_with('@'
) {
250 let group
= &user_or_group
[1..];
251 node
.insert_group_role(group
.to_string(), role
.to_string(), propagate
);
253 node
.insert_user_role(user_or_group
.to_string(), role
.to_string(), propagate
);
261 pub fn load(filename
: &Path
) -> Result
<(Self, [u8;32]), Error
> {
262 let mut tree
= Self::new();
264 let raw
= match std
::fs
::read_to_string(filename
) {
267 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
270 bail
!("unable to read acl config {:?} - {}", filename
, err
);
275 let digest
= openssl
::sha
::sha256(raw
.as_bytes());
277 for (linenr
, line
) in raw
.lines().enumerate() {
278 if let Err(err
) = tree
.parse_acl_line(line
) {
279 bail
!("unable to parse acl config {:?}, line {} - {}", filename
, linenr
, err
);
287 pub const ACL_CFG_FILENAME
: &str = "/etc/proxmox-backup/acl.cfg";
288 pub const ACL_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.acl.lck";
290 pub fn config() -> Result
<(AclTree
, [u8; 32]), Error
> {
291 let path
= PathBuf
::from(ACL_CFG_FILENAME
);
295 pub fn store_config(acl
: &AclTree
, filename
: &Path
) -> Result
<(), Error
> {
296 let mut raw
: Vec
<u8> = Vec
::new();
298 acl
.write_config(&mut raw
)?
;
300 let backup_user
= crate::backup
::backup_user()?
;
301 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640);
302 // set the correct owner/group/permissions while saving file
303 // owner(rw) = root, group(r)= backup
304 let options
= CreateOptions
::new()
306 .owner(nix
::unistd
::ROOT
)
307 .group(backup_user
.gid
);
309 replace_file(filename
, &raw
, options
)?
;