]> git.proxmox.com Git - proxmox-backup.git/blob - src/config/acl.rs
add acl config
[proxmox-backup.git] / src / config / acl.rs
1 use std::io::Write;
2 use std::collections::{HashMap, HashSet};
3 use std::path::{PathBuf, Path};
4
5 use failure::*;
6
7 use lazy_static::lazy_static;
8
9 use proxmox::tools::{fs::replace_file, fs::CreateOptions};
10
11 // define Privilege bitfield
12
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;
16
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;
20
21 pub const ROLE_AUDIT: u64 =
22 PRIV_SYS_AUDIT |
23 PRIV_STORE_AUDIT;
24
25 pub const ROLE_STORE_ADMIN: u64 =
26 PRIV_STORE_AUDIT |
27 PRIV_STORE_ALLOCATE |
28 PRIV_STORE_ALLOCATE_SPACE;
29
30 pub const ROLE_STORE_USER: u64 =
31 PRIV_STORE_AUDIT |
32 PRIV_STORE_ALLOCATE_SPACE;
33
34 lazy_static! {
35 static ref ROLE_NAMES: HashMap<&'static str, u64> = {
36 let mut map = HashMap::new();
37
38 map.insert("Admin", std::u64::MAX);
39 map.insert("Audit", ROLE_AUDIT);
40
41
42 map.insert("Store.Admin", ROLE_STORE_ADMIN);
43 map.insert("Store.User", ROLE_STORE_USER);
44
45 map
46 };
47 }
48
49 fn split_acl_path(path: &str) -> Vec<&str> {
50
51 let items = path.split('/');
52
53 let mut components = vec![];
54
55 for name in items {
56 if name.is_empty() { continue; }
57 components.push(name);
58 }
59
60 components
61 }
62
63 pub struct AclTree {
64 root: AclTreeNode,
65 }
66
67 struct AclTreeNode {
68 users: HashMap<String, HashMap<String, bool>>,
69 groups: HashMap<String, HashMap<String, bool>>,
70 children: HashMap<String, AclTreeNode>,
71 }
72
73 impl AclTreeNode {
74
75 pub fn new() -> Self {
76 Self {
77 users: HashMap::new(),
78 groups: HashMap::new(),
79 children: HashMap::new(),
80 }
81 }
82
83 pub fn insert_group_role(&mut self, group: String, role: String, propagate: bool) {
84 self.groups
85 .entry(group).or_insert_with(|| HashMap::new())
86 .insert(role, propagate);
87 }
88
89 pub fn insert_user_role(&mut self, user: String, role: String, propagate: bool) {
90 self.users
91 .entry(user).or_insert_with(|| HashMap::new())
92 .insert(role, propagate);
93 }
94 }
95
96 impl AclTree {
97
98 pub fn new() -> Self {
99 Self { root: AclTreeNode::new() }
100 }
101
102 fn get_or_insert_node(&mut self, path: &[&str]) -> &mut AclTreeNode {
103 let mut node = &mut self.root;
104 for comp in path {
105 node = node.children.entry(String::from(*comp))
106 .or_insert_with(|| AclTreeNode::new());
107 }
108 node
109 }
110
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);
115 }
116
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);
121 }
122
123 fn write_node_config(
124 node: &AclTreeNode,
125 path: &str,
126 w: &mut dyn Write,
127 ) -> Result<(), Error> {
128
129 let mut role_ug_map0 = HashMap::new();
130 let mut role_ug_map1 = HashMap::new();
131
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();
138 if *propagate {
139 role_ug_map1.entry(role).or_insert_with(|| HashSet::new())
140 .insert(user);
141 } else {
142 role_ug_map0.entry(role).or_insert_with(|| HashSet::new())
143 .insert(user);
144 }
145 }
146 }
147
148 for (group, roles) in &node.groups {
149 for (role, propagate) in roles {
150 let group = format!("@{}", group);
151 if *propagate {
152 role_ug_map1.entry(role).or_insert_with(|| HashSet::new())
153 .insert(group);
154 } else {
155 role_ug_map0.entry(role).or_insert_with(|| HashSet::new())
156 .insert(group);
157 }
158 }
159 }
160
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>>();
168 item_list.sort();
169 let item_list = item_list.join(",");
170 result_map.entry(item_list).or_insert_with(|| HashSet::new())
171 .insert(item.to_string());
172 }
173 result_map
174 }
175
176 let mut uglist_role_map0 = group_by_property_list(&role_ug_map0)
177 .into_iter()
178 .collect::<Vec<(String, HashSet<String>)>>();
179 uglist_role_map0.sort_unstable_by(|a,b| a.0.cmp(&b.0));
180
181 let mut uglist_role_map1 = group_by_property_list(&role_ug_map1)
182 .into_iter()
183 .collect::<Vec<(String, HashSet<String>)>>();
184 uglist_role_map1.sort_unstable_by(|a,b| a.0.cmp(&b.0));
185
186
187 for (uglist, roles) in uglist_role_map0 {
188 let mut role_list = roles.iter().map(|v| v.as_str())
189 .collect::<Vec<&str>>();
190 role_list.sort();
191 writeln!(w, "acl:0:{}:{}:{}", path, uglist, role_list.join(","))?;
192 }
193
194 for (uglist, roles) in uglist_role_map1 {
195 let mut role_list = roles.iter().map(|v| v.as_str())
196 .collect::<Vec<&str>>();
197 role_list.sort();
198 writeln!(w, "acl:1:{}:{}:{}", path, uglist, role_list.join(","))?;
199 }
200
201 let mut child_names = node.children.keys().map(|v| v.as_str()).collect::<Vec<&str>>();
202 child_names.sort();
203
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)?;
208 }
209
210 Ok(())
211 }
212
213 pub fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> {
214 Self::write_node_config(&self.root, "", w)
215 }
216
217 fn parse_acl_line(&mut self, line: &str) -> Result<(), Error> {
218
219 let items: Vec<&str> = line.split(':').collect();
220
221 if items.len() != 5 {
222 bail!("wrong number of items.");
223 }
224
225 if items[0] != "acl" {
226 bail!("line does not start with 'acl'.");
227 }
228
229 let propagate = if items[1] == "0" {
230 false
231 } else if items[1] == "1" {
232 true
233 } else {
234 bail!("expected '0' or '1' for propagate flag.");
235 };
236
237 let path = split_acl_path(items[2]);
238 let node = self.get_or_insert_node(&path);
239
240 let uglist: Vec<&str> = items[3].split(',').map(|v| v.trim()).collect();
241
242 let rolelist: Vec<&str> = items[4].split(',').map(|v| v.trim()).collect();
243
244 for user_or_group in &uglist {
245 for role in &rolelist {
246 if !ROLE_NAMES.contains_key(role) {
247 bail!("unknown role '{}'", role);
248 }
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);
252 } else {
253 node.insert_user_role(user_or_group.to_string(), role.to_string(), propagate);
254 }
255 }
256 }
257
258 Ok(())
259 }
260
261 pub fn load(filename: &Path) -> Result<(Self, [u8;32]), Error> {
262 let mut tree = Self::new();
263
264 let raw = match std::fs::read_to_string(filename) {
265 Ok(v) => v,
266 Err(err) => {
267 if err.kind() == std::io::ErrorKind::NotFound {
268 String::new()
269 } else {
270 bail!("unable to read acl config {:?} - {}", filename, err);
271 }
272 }
273 };
274
275 let digest = openssl::sha::sha256(raw.as_bytes());
276
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);
280 }
281 }
282
283 Ok((tree, digest))
284 }
285 }
286
287 pub const ACL_CFG_FILENAME: &str = "/etc/proxmox-backup/acl.cfg";
288 pub const ACL_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.acl.lck";
289
290 pub fn config() -> Result<(AclTree, [u8; 32]), Error> {
291 let path = PathBuf::from(ACL_CFG_FILENAME);
292 AclTree::load(&path)
293 }
294
295 pub fn store_config(acl: &AclTree, filename: &Path) -> Result<(), Error> {
296 let mut raw: Vec<u8> = Vec::new();
297
298 acl.write_config(&mut raw)?;
299
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()
305 .perm(mode)
306 .owner(nix::unistd::ROOT)
307 .group(backup_user.gid);
308
309 replace_file(filename, &raw, options)?;
310
311 Ok(())
312 }