]>
Commit | Line | Data |
---|---|---|
bf78f708 DM |
1 | //! Manage Access Control Lists |
2 | ||
f7d4e4b5 | 3 | use anyhow::{bail, Error}; |
ed3e60ae | 4 | |
6ef1b649 WB |
5 | use proxmox_router::{Router, RpcEnvironment, Permission}; |
6 | use proxmox_schema::api; | |
ed3e60ae | 7 | |
8cc3760e DM |
8 | use pbs_api_types::{ |
9 | Authid, AclListItem, Role, | |
10 | ACL_PATH_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA, PROXMOX_GROUP_ID_SCHEMA, | |
11 | ACL_PROPAGATE_SCHEMA, PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY, | |
12 | }; | |
13 | ||
14 | use pbs_config::acl::AclTreeNode; | |
15 | ||
ba3d7e19 | 16 | use pbs_config::CachedUserInfo; |
ed3e60ae | 17 | |
ed3e60ae | 18 | fn extract_acl_node_data( |
8cc3760e | 19 | node: &AclTreeNode, |
ed3e60ae DM |
20 | path: &str, |
21 | list: &mut Vec<AclListItem>, | |
11b6391c | 22 | exact: bool, |
cf90a369 | 23 | auth_id_filter: &Option<Authid>, |
ed3e60ae | 24 | ) { |
b2da7fbd | 25 | // tokens can't have tokens, so we can early return |
cf90a369 DM |
26 | if let Some(auth_id_filter) = auth_id_filter { |
27 | if auth_id_filter.is_token() { | |
b2da7fbd FG |
28 | return; |
29 | } | |
30 | } | |
31 | ||
ed3e60ae | 32 | for (user, roles) in &node.users { |
cf90a369 | 33 | if let Some(auth_id_filter) = auth_id_filter { |
b2da7fbd | 34 | if !user.is_token() |
cf90a369 | 35 | || user.user() != auth_id_filter.user() { |
b2da7fbd FG |
36 | continue; |
37 | } | |
38 | } | |
39 | ||
ed3e60ae DM |
40 | for (role, propagate) in roles { |
41 | list.push(AclListItem { | |
42 | path: if path.is_empty() { String::from("/") } else { path.to_string() }, | |
43 | propagate: *propagate, | |
44 | ugid_type: String::from("user"), | |
45 | ugid: user.to_string(), | |
46 | roleid: role.to_string(), | |
47 | }); | |
48 | } | |
49 | } | |
50 | for (group, roles) in &node.groups { | |
cf90a369 | 51 | if auth_id_filter.is_some() { |
b2da7fbd FG |
52 | continue; |
53 | } | |
54 | ||
ed3e60ae DM |
55 | for (role, propagate) in roles { |
56 | list.push(AclListItem { | |
57 | path: if path.is_empty() { String::from("/") } else { path.to_string() }, | |
58 | propagate: *propagate, | |
59 | ugid_type: String::from("group"), | |
60 | ugid: group.to_string(), | |
61 | roleid: role.to_string(), | |
62 | }); | |
63 | } | |
64 | } | |
11b6391c DC |
65 | if exact { |
66 | return; | |
67 | } | |
ed3e60ae DM |
68 | for (comp, child) in &node.children { |
69 | let new_path = format!("{}/{}", path, comp); | |
cf90a369 | 70 | extract_acl_node_data(child, &new_path, list, exact, auth_id_filter); |
ed3e60ae DM |
71 | } |
72 | } | |
73 | ||
74 | #[api( | |
2882c881 DC |
75 | input: { |
76 | properties: { | |
77 | path: { | |
78 | schema: ACL_PATH_SCHEMA, | |
79 | optional: true, | |
80 | }, | |
81 | exact: { | |
82 | description: "If set, returns only ACL for the exact path.", | |
83 | type: bool, | |
84 | optional: true, | |
85 | default: false, | |
86 | }, | |
87 | }, | |
88 | }, | |
ed3e60ae DM |
89 | returns: { |
90 | description: "ACL entry list.", | |
91 | type: Array, | |
92 | items: { | |
93 | type: AclListItem, | |
94 | } | |
d28ddb8e DM |
95 | }, |
96 | access: { | |
b2da7fbd FG |
97 | permission: &Permission::Anybody, |
98 | description: "Returns all ACLs if user has Sys.Audit on '/access/acl', or just the ACLs containing the user's API tokens.", | |
d28ddb8e | 99 | }, |
ed3e60ae DM |
100 | )] |
101 | /// Read Access Control List (ACLs). | |
102 | pub fn read_acl( | |
2882c881 DC |
103 | path: Option<String>, |
104 | exact: bool, | |
105 | mut rpcenv: &mut dyn RpcEnvironment, | |
ed3e60ae | 106 | ) -> Result<Vec<AclListItem>, Error> { |
b2da7fbd | 107 | let auth_id = rpcenv.get_auth_id().unwrap().parse()?; |
ed3e60ae | 108 | |
b2da7fbd FG |
109 | let user_info = CachedUserInfo::new()?; |
110 | ||
111 | let top_level_privs = user_info.lookup_privs(&auth_id, &["access", "acl"]); | |
112 | let auth_id_filter = if (top_level_privs & PRIV_SYS_AUDIT) == 0 { | |
113 | Some(auth_id) | |
114 | } else { | |
115 | None | |
116 | }; | |
ed3e60ae | 117 | |
8cc3760e | 118 | let (mut tree, digest) = pbs_config::acl::config()?; |
ed3e60ae DM |
119 | |
120 | let mut list: Vec<AclListItem> = Vec::new(); | |
2882c881 DC |
121 | if let Some(path) = &path { |
122 | if let Some(node) = &tree.find_node(path) { | |
b2da7fbd | 123 | extract_acl_node_data(&node, path, &mut list, exact, &auth_id_filter); |
2882c881 DC |
124 | } |
125 | } else { | |
b2da7fbd | 126 | extract_acl_node_data(&tree.root, "", &mut list, exact, &auth_id_filter); |
2882c881 DC |
127 | } |
128 | ||
129 | rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); | |
ed3e60ae DM |
130 | |
131 | Ok(list) | |
132 | } | |
133 | ||
9765092e | 134 | #[api( |
12e38953 | 135 | protected: true, |
9765092e DM |
136 | input: { |
137 | properties: { | |
138 | path: { | |
139 | schema: ACL_PATH_SCHEMA, | |
140 | }, | |
141 | role: { | |
bc0d0388 | 142 | type: Role, |
9765092e DM |
143 | }, |
144 | propagate: { | |
145 | optional: true, | |
146 | schema: ACL_PROPAGATE_SCHEMA, | |
147 | }, | |
8b600f99 | 148 | "auth-id": { |
9765092e | 149 | optional: true, |
e6dc35ac | 150 | type: Authid, |
9765092e DM |
151 | }, |
152 | group: { | |
153 | optional: true, | |
154 | schema: PROXMOX_GROUP_ID_SCHEMA, | |
155 | }, | |
d83175dd | 156 | delete: { |
9765092e DM |
157 | optional: true, |
158 | description: "Remove permissions (instead of adding it).", | |
159 | type: bool, | |
160 | }, | |
161 | digest: { | |
162 | optional: true, | |
163 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
164 | }, | |
165 | }, | |
166 | }, | |
d28ddb8e | 167 | access: { |
b2da7fbd FG |
168 | permission: &Permission::Anybody, |
169 | description: "Requires Permissions.Modify on '/access/acl', limited to updating ACLs of the user's API tokens otherwise." | |
d28ddb8e | 170 | }, |
9765092e DM |
171 | )] |
172 | /// Update Access Control List (ACLs). | |
367c0ff7 | 173 | #[allow(clippy::too_many_arguments)] |
9765092e DM |
174 | pub fn update_acl( |
175 | path: String, | |
176 | role: String, | |
177 | propagate: Option<bool>, | |
e6dc35ac | 178 | auth_id: Option<Authid>, |
9765092e DM |
179 | group: Option<String>, |
180 | delete: Option<bool>, | |
181 | digest: Option<String>, | |
b2da7fbd | 182 | rpcenv: &mut dyn RpcEnvironment, |
9765092e | 183 | ) -> Result<(), Error> { |
b2da7fbd FG |
184 | let current_auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
185 | ||
186 | let user_info = CachedUserInfo::new()?; | |
187 | ||
188 | let top_level_privs = user_info.lookup_privs(¤t_auth_id, &["access", "acl"]); | |
189 | if top_level_privs & PRIV_PERMISSIONS_MODIFY == 0 { | |
3984a5fd | 190 | if group.is_some() { |
b2da7fbd FG |
191 | bail!("Unprivileged users are not allowed to create group ACL item."); |
192 | } | |
193 | ||
194 | match &auth_id { | |
195 | Some(auth_id) => { | |
196 | if current_auth_id.is_token() { | |
197 | bail!("Unprivileged API tokens can't set ACL items."); | |
198 | } else if !auth_id.is_token() { | |
199 | bail!("Unprivileged users can only set ACL items for API tokens."); | |
200 | } else if auth_id.user() != current_auth_id.user() { | |
201 | bail!("Unprivileged users can only set ACL items for their own API tokens."); | |
202 | } | |
203 | }, | |
204 | None => { bail!("Unprivileged user needs to provide auth_id to update ACL item."); }, | |
205 | }; | |
206 | } | |
9765092e | 207 | |
8cc3760e | 208 | let _lock = pbs_config::acl::lock_config()?; |
9765092e | 209 | |
8cc3760e | 210 | let (mut tree, expected_digest) = pbs_config::acl::config()?; |
9765092e DM |
211 | |
212 | if let Some(ref digest) = digest { | |
213 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
214 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
215 | } | |
216 | ||
9765092e DM |
217 | let propagate = propagate.unwrap_or(true); |
218 | ||
219 | let delete = delete.unwrap_or(false); | |
220 | ||
9f4e47dd | 221 | if let Some(ref _group) = group { |
d83175dd | 222 | bail!("parameter 'group' - groups are currently not supported."); |
e6dc35ac | 223 | } else if let Some(ref auth_id) = auth_id { |
d83175dd | 224 | if !delete { // Note: we allow to delete non-existent users |
ba3d7e19 | 225 | let user_cfg = pbs_config::user::cached_config()?; |
e6dc35ac FG |
226 | if user_cfg.sections.get(&auth_id.to_string()).is_none() { |
227 | bail!(format!("no such {}.", | |
228 | if auth_id.is_token() { "API token" } else { "user" })); | |
d83175dd DM |
229 | } |
230 | } | |
231 | } else { | |
232 | bail!("missing 'userid' or 'group' parameter."); | |
233 | } | |
234 | ||
9f4e47dd | 235 | if !delete { // Note: we allow to delete entries with invalid path |
8cc3760e | 236 | pbs_config::acl::check_acl_path(&path)?; |
9f4e47dd DM |
237 | } |
238 | ||
e6dc35ac | 239 | if let Some(auth_id) = auth_id { |
9765092e | 240 | if delete { |
e6dc35ac | 241 | tree.delete_user_role(&path, &auth_id, &role); |
9765092e | 242 | } else { |
e6dc35ac | 243 | tree.insert_user_role(&path, &auth_id, &role, propagate); |
9765092e DM |
244 | } |
245 | } else if let Some(group) = group { | |
246 | if delete { | |
247 | tree.delete_group_role(&path, &group, &role); | |
248 | } else { | |
249 | tree.insert_group_role(&path, &group, &role, propagate); | |
250 | } | |
251 | } | |
252 | ||
8cc3760e | 253 | pbs_config::acl::save_config(&tree)?; |
9765092e DM |
254 | |
255 | Ok(()) | |
256 | } | |
257 | ||
ed3e60ae | 258 | pub const ROUTER: Router = Router::new() |
9765092e DM |
259 | .get(&API_METHOD_READ_ACL) |
260 | .put(&API_METHOD_UPDATE_ACL); |