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