]>
Commit | Line | Data |
---|---|---|
9b2bad7a | 1 | use ::serde::{Deserialize, Serialize}; |
dc7a5b34 | 2 | use anyhow::Error; |
25877d05 | 3 | use hex::FromHex; |
dc7a5b34 | 4 | use serde_json::Value; |
9b2bad7a | 5 | |
dc7a5b34 | 6 | use proxmox_router::{http_bail, Permission, Router, RpcEnvironment}; |
8d6425aa | 7 | use proxmox_schema::{api, param_bail}; |
9b2bad7a | 8 | |
e3619d41 | 9 | use pbs_api_types::{ |
ffa403b5 | 10 | Authid, VerificationJobConfig, VerificationJobConfigUpdater, JOB_ID_SCHEMA, |
dc7a5b34 | 11 | PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_VERIFY, PROXMOX_CONFIG_DIGEST_SCHEMA, |
e3619d41 | 12 | }; |
802189f7 | 13 | use pbs_config::verify; |
b7ce2e57 | 14 | |
ba3d7e19 | 15 | use pbs_config::CachedUserInfo; |
9b2bad7a HL |
16 | |
17 | #[api( | |
18 | input: { | |
19 | properties: {}, | |
20 | }, | |
21 | returns: { | |
22 | description: "List configured jobs.", | |
23 | type: Array, | |
e3619d41 | 24 | items: { type: VerificationJobConfig }, |
9b2bad7a | 25 | }, |
b7ce2e57 | 26 | access: { |
35c80d69 FG |
27 | permission: &Permission::Anybody, |
28 | description: "Requires Datastore.Audit or Datastore.Verify on datastore.", | |
b7ce2e57 | 29 | }, |
9b2bad7a HL |
30 | )] |
31 | /// List all verification jobs | |
32 | pub fn list_verification_jobs( | |
33 | _param: Value, | |
41c1a179 | 34 | rpcenv: &mut dyn RpcEnvironment, |
9b2bad7a | 35 | ) -> Result<Vec<VerificationJobConfig>, Error> { |
35c80d69 FG |
36 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
37 | let user_info = CachedUserInfo::new()?; | |
38 | ||
39 | let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY; | |
9b2bad7a HL |
40 | |
41 | let (config, digest) = verify::config()?; | |
42 | ||
43 | let list = config.convert_to_typed_array("verification")?; | |
44 | ||
dc7a5b34 TL |
45 | let list = list |
46 | .into_iter() | |
35c80d69 | 47 | .filter(|job: &VerificationJobConfig| { |
0aa5815f | 48 | let privs = user_info.lookup_privs(&auth_id, &job.store_with_ns().acl_path()); |
35c80d69 FG |
49 | |
50 | privs & required_privs != 00 | |
dc7a5b34 TL |
51 | }) |
52 | .collect(); | |
35c80d69 | 53 | |
25877d05 | 54 | rpcenv["digest"] = hex::encode(&digest).into(); |
9b2bad7a HL |
55 | |
56 | Ok(list) | |
57 | } | |
58 | ||
9b2bad7a HL |
59 | #[api( |
60 | protected: true, | |
61 | input: { | |
62 | properties: { | |
ffa403b5 DM |
63 | config: { |
64 | type: VerificationJobConfig, | |
65 | flatten: true, | |
9b2bad7a | 66 | }, |
ffa403b5 | 67 | }, |
b7ce2e57 FG |
68 | }, |
69 | access: { | |
35c80d69 FG |
70 | permission: &Permission::Anybody, |
71 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 72 | }, |
9b2bad7a HL |
73 | )] |
74 | /// Create a new verification job. | |
35c80d69 | 75 | pub fn create_verification_job( |
ffa403b5 | 76 | config: VerificationJobConfig, |
dc7a5b34 | 77 | rpcenv: &mut dyn RpcEnvironment, |
35c80d69 FG |
78 | ) -> Result<(), Error> { |
79 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
80 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 81 | |
dc7a5b34 TL |
82 | user_info.check_privs( |
83 | &auth_id, | |
0aa5815f | 84 | &config.store_with_ns().acl_path(), |
dc7a5b34 TL |
85 | PRIV_DATASTORE_VERIFY, |
86 | false, | |
87 | )?; | |
35c80d69 | 88 | |
802189f7 | 89 | let _lock = verify::lock_config()?; |
35c80d69 | 90 | |
ffa403b5 | 91 | let (mut section_config, _digest) = verify::config()?; |
9b2bad7a | 92 | |
ffa403b5 | 93 | if section_config.sections.get(&config.id).is_some() { |
8d6425aa | 94 | param_bail!("id", "job '{}' already exists.", config.id); |
9b2bad7a HL |
95 | } |
96 | ||
ffa403b5 | 97 | section_config.set_data(&config.id, "verification", &config)?; |
9b2bad7a | 98 | |
ffa403b5 | 99 | verify::save_config(§ion_config)?; |
9b2bad7a | 100 | |
ffa403b5 | 101 | crate::server::jobstate::create_state_file("verificationjob", &config.id)?; |
9b2bad7a HL |
102 | |
103 | Ok(()) | |
104 | } | |
105 | ||
106 | #[api( | |
107 | input: { | |
108 | properties: { | |
109 | id: { | |
110 | schema: JOB_ID_SCHEMA, | |
111 | }, | |
112 | }, | |
113 | }, | |
e3619d41 | 114 | returns: { type: VerificationJobConfig }, |
b7ce2e57 | 115 | access: { |
35c80d69 FG |
116 | permission: &Permission::Anybody, |
117 | description: "Requires Datastore.Audit or Datastore.Verify on job's datastore.", | |
b7ce2e57 | 118 | }, |
9b2bad7a HL |
119 | )] |
120 | /// Read a verification job configuration. | |
121 | pub fn read_verification_job( | |
122 | id: String, | |
41c1a179 | 123 | rpcenv: &mut dyn RpcEnvironment, |
9b2bad7a | 124 | ) -> Result<VerificationJobConfig, Error> { |
35c80d69 FG |
125 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
126 | let user_info = CachedUserInfo::new()?; | |
127 | ||
9b2bad7a HL |
128 | let (config, digest) = verify::config()?; |
129 | ||
e3619d41 | 130 | let verification_job: VerificationJobConfig = config.lookup("verification", &id)?; |
35c80d69 FG |
131 | |
132 | let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY; | |
dc7a5b34 TL |
133 | user_info.check_privs( |
134 | &auth_id, | |
0aa5815f | 135 | &verification_job.store_with_ns().acl_path(), |
dc7a5b34 TL |
136 | required_privs, |
137 | true, | |
138 | )?; | |
35c80d69 | 139 | |
25877d05 | 140 | rpcenv["digest"] = hex::encode(&digest).into(); |
9b2bad7a HL |
141 | |
142 | Ok(verification_job) | |
143 | } | |
144 | ||
145 | #[api()] | |
146 | #[derive(Serialize, Deserialize)] | |
dc7a5b34 | 147 | #[serde(rename_all = "kebab-case")] |
9b2bad7a HL |
148 | /// Deletable property name |
149 | pub enum DeletableProperty { | |
150 | /// Delete the ignore verified property. | |
151 | IgnoreVerified, | |
152 | /// Delete the comment property. | |
153 | Comment, | |
154 | /// Delete the job schedule. | |
155 | Schedule, | |
156 | /// Delete outdated after property. | |
dc7a5b34 | 157 | OutdatedAfter, |
59229bd7 TL |
158 | /// Delete namespace property, defaulting to root namespace then. |
159 | Ns, | |
0b1edf29 TL |
160 | /// Delete max-depth property, defaulting to full recursion again |
161 | MaxDepth, | |
9b2bad7a HL |
162 | } |
163 | ||
164 | #[api( | |
165 | protected: true, | |
166 | input: { | |
167 | properties: { | |
168 | id: { | |
169 | schema: JOB_ID_SCHEMA, | |
170 | }, | |
ffa403b5 DM |
171 | update: { |
172 | type: VerificationJobConfigUpdater, | |
173 | flatten: true, | |
9b2bad7a HL |
174 | }, |
175 | delete: { | |
176 | description: "List of properties to delete.", | |
177 | type: Array, | |
178 | optional: true, | |
179 | items: { | |
180 | type: DeletableProperty, | |
181 | } | |
182 | }, | |
183 | digest: { | |
184 | optional: true, | |
185 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
186 | }, | |
187 | }, | |
188 | }, | |
b7ce2e57 | 189 | access: { |
35c80d69 FG |
190 | permission: &Permission::Anybody, |
191 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 192 | }, |
9b2bad7a HL |
193 | )] |
194 | /// Update verification job config. | |
367c0ff7 | 195 | #[allow(clippy::too_many_arguments)] |
9b2bad7a HL |
196 | pub fn update_verification_job( |
197 | id: String, | |
ffa403b5 | 198 | update: VerificationJobConfigUpdater, |
9b2bad7a HL |
199 | delete: Option<Vec<DeletableProperty>>, |
200 | digest: Option<String>, | |
35c80d69 | 201 | rpcenv: &mut dyn RpcEnvironment, |
9b2bad7a | 202 | ) -> Result<(), Error> { |
35c80d69 FG |
203 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
204 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 205 | |
802189f7 | 206 | let _lock = verify::lock_config()?; |
9b2bad7a HL |
207 | |
208 | // pass/compare digest | |
209 | let (mut config, expected_digest) = verify::config()?; | |
210 | ||
211 | if let Some(ref digest) = digest { | |
25877d05 | 212 | let digest = <[u8; 32]>::from_hex(digest)?; |
9b2bad7a HL |
213 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
214 | } | |
215 | ||
e3619d41 | 216 | let mut data: VerificationJobConfig = config.lookup("verification", &id)?; |
9b2bad7a | 217 | |
0aa5815f | 218 | // check existing store and NS |
dc7a5b34 TL |
219 | user_info.check_privs( |
220 | &auth_id, | |
0aa5815f | 221 | &data.store_with_ns().acl_path(), |
dc7a5b34 TL |
222 | PRIV_DATASTORE_VERIFY, |
223 | true, | |
224 | )?; | |
35c80d69 FG |
225 | |
226 | if let Some(delete) = delete { | |
9b2bad7a HL |
227 | for delete_prop in delete { |
228 | match delete_prop { | |
dc7a5b34 TL |
229 | DeletableProperty::IgnoreVerified => { |
230 | data.ignore_verified = None; | |
231 | } | |
232 | DeletableProperty::OutdatedAfter => { | |
233 | data.outdated_after = None; | |
234 | } | |
235 | DeletableProperty::Comment => { | |
236 | data.comment = None; | |
237 | } | |
238 | DeletableProperty::Schedule => { | |
239 | data.schedule = None; | |
240 | } | |
59229bd7 TL |
241 | DeletableProperty::Ns => { |
242 | data.ns = None; | |
243 | } | |
0b1edf29 TL |
244 | DeletableProperty::MaxDepth => { |
245 | data.max_depth = None; | |
246 | } | |
9b2bad7a HL |
247 | } |
248 | } | |
249 | } | |
250 | ||
ffa403b5 | 251 | if let Some(comment) = update.comment { |
9b2bad7a HL |
252 | let comment = comment.trim().to_string(); |
253 | if comment.is_empty() { | |
254 | data.comment = None; | |
255 | } else { | |
256 | data.comment = Some(comment); | |
257 | } | |
258 | } | |
259 | ||
ffa403b5 | 260 | if let Some(store) = update.store { |
35c80d69 FG |
261 | data.store = store; |
262 | } | |
263 | ||
dc7a5b34 TL |
264 | if update.ignore_verified.is_some() { |
265 | data.ignore_verified = update.ignore_verified; | |
266 | } | |
267 | if update.outdated_after.is_some() { | |
268 | data.outdated_after = update.outdated_after; | |
269 | } | |
ffa403b5 | 270 | let schedule_changed = data.schedule != update.schedule; |
dc7a5b34 TL |
271 | if update.schedule.is_some() { |
272 | data.schedule = update.schedule; | |
273 | } | |
59229bd7 TL |
274 | if let Some(ns) = update.ns { |
275 | if !ns.is_root() { | |
276 | data.ns = Some(ns); | |
277 | } | |
278 | } | |
0b1edf29 TL |
279 | if let Some(max_depth) = update.max_depth { |
280 | if max_depth <= pbs_api_types::MAX_NAMESPACE_DEPTH { | |
281 | data.max_depth = Some(max_depth); | |
282 | } | |
283 | } | |
9b2bad7a | 284 | |
0aa5815f FG |
285 | // check new store and NS |
286 | user_info.check_privs( | |
287 | &auth_id, | |
288 | &data.store_with_ns().acl_path(), | |
289 | PRIV_DATASTORE_VERIFY, | |
290 | true, | |
291 | )?; | |
292 | ||
9b2bad7a HL |
293 | config.set_data(&id, "verification", &data)?; |
294 | ||
295 | verify::save_config(&config)?; | |
296 | ||
951fe0cb | 297 | if schedule_changed { |
37a634f5 | 298 | crate::server::jobstate::update_job_last_run_time("verificationjob", &id)?; |
951fe0cb DC |
299 | } |
300 | ||
9b2bad7a HL |
301 | Ok(()) |
302 | } | |
303 | ||
304 | #[api( | |
305 | protected: true, | |
306 | input: { | |
307 | properties: { | |
308 | id: { | |
309 | schema: JOB_ID_SCHEMA, | |
310 | }, | |
311 | digest: { | |
312 | optional: true, | |
313 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
314 | }, | |
315 | }, | |
316 | }, | |
b7ce2e57 | 317 | access: { |
35c80d69 FG |
318 | permission: &Permission::Anybody, |
319 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 320 | }, |
9b2bad7a HL |
321 | )] |
322 | /// Remove a verification job configuration | |
35c80d69 FG |
323 | pub fn delete_verification_job( |
324 | id: String, | |
325 | digest: Option<String>, | |
326 | rpcenv: &mut dyn RpcEnvironment, | |
327 | ) -> Result<(), Error> { | |
328 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
329 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 330 | |
802189f7 | 331 | let _lock = verify::lock_config()?; |
9b2bad7a HL |
332 | |
333 | let (mut config, expected_digest) = verify::config()?; | |
334 | ||
e3619d41 | 335 | let job: VerificationJobConfig = config.lookup("verification", &id)?; |
dc7a5b34 TL |
336 | user_info.check_privs( |
337 | &auth_id, | |
0aa5815f | 338 | &job.store_with_ns().acl_path(), |
dc7a5b34 TL |
339 | PRIV_DATASTORE_VERIFY, |
340 | true, | |
341 | )?; | |
35c80d69 | 342 | |
9b2bad7a | 343 | if let Some(ref digest) = digest { |
25877d05 | 344 | let digest = <[u8; 32]>::from_hex(digest)?; |
9b2bad7a HL |
345 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
346 | } | |
347 | ||
348 | match config.sections.get(&id) { | |
dc7a5b34 TL |
349 | Some(_) => { |
350 | config.sections.remove(&id); | |
351 | } | |
dcd1518e | 352 | None => http_bail!(NOT_FOUND, "job '{}' does not exist.", id), |
9b2bad7a HL |
353 | } |
354 | ||
355 | verify::save_config(&config)?; | |
356 | ||
1298618a | 357 | crate::server::jobstate::remove_state_file("verificationjob", &id)?; |
9b2bad7a HL |
358 | |
359 | Ok(()) | |
360 | } | |
361 | ||
362 | const ITEM_ROUTER: Router = Router::new() | |
363 | .get(&API_METHOD_READ_VERIFICATION_JOB) | |
364 | .put(&API_METHOD_UPDATE_VERIFICATION_JOB) | |
365 | .delete(&API_METHOD_DELETE_VERIFICATION_JOB); | |
366 | ||
367 | pub const ROUTER: Router = Router::new() | |
368 | .get(&API_METHOD_LIST_VERIFICATION_JOBS) | |
369 | .post(&API_METHOD_CREATE_VERIFICATION_JOB) | |
1298618a | 370 | .match_all("id", &ITEM_ROUTER); |