]>
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 FG |
47 | .filter(|job: &VerificationJobConfig| { |
48 | let privs = user_info.lookup_privs(&auth_id, &["datastore", &job.store]); | |
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, | |
84 | &["datastore", &config.store], | |
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, | |
135 | &["datastore", &verification_job.store], | |
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, | |
9b2bad7a HL |
160 | } |
161 | ||
162 | #[api( | |
163 | protected: true, | |
164 | input: { | |
165 | properties: { | |
166 | id: { | |
167 | schema: JOB_ID_SCHEMA, | |
168 | }, | |
ffa403b5 DM |
169 | update: { |
170 | type: VerificationJobConfigUpdater, | |
171 | flatten: true, | |
9b2bad7a HL |
172 | }, |
173 | delete: { | |
174 | description: "List of properties to delete.", | |
175 | type: Array, | |
176 | optional: true, | |
177 | items: { | |
178 | type: DeletableProperty, | |
179 | } | |
180 | }, | |
181 | digest: { | |
182 | optional: true, | |
183 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
184 | }, | |
185 | }, | |
186 | }, | |
b7ce2e57 | 187 | access: { |
35c80d69 FG |
188 | permission: &Permission::Anybody, |
189 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 190 | }, |
9b2bad7a HL |
191 | )] |
192 | /// Update verification job config. | |
367c0ff7 | 193 | #[allow(clippy::too_many_arguments)] |
9b2bad7a HL |
194 | pub fn update_verification_job( |
195 | id: String, | |
ffa403b5 | 196 | update: VerificationJobConfigUpdater, |
9b2bad7a HL |
197 | delete: Option<Vec<DeletableProperty>>, |
198 | digest: Option<String>, | |
35c80d69 | 199 | rpcenv: &mut dyn RpcEnvironment, |
9b2bad7a | 200 | ) -> Result<(), Error> { |
35c80d69 FG |
201 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
202 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 203 | |
802189f7 | 204 | let _lock = verify::lock_config()?; |
9b2bad7a HL |
205 | |
206 | // pass/compare digest | |
207 | let (mut config, expected_digest) = verify::config()?; | |
208 | ||
209 | if let Some(ref digest) = digest { | |
25877d05 | 210 | let digest = <[u8; 32]>::from_hex(digest)?; |
9b2bad7a HL |
211 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
212 | } | |
213 | ||
e3619d41 | 214 | let mut data: VerificationJobConfig = config.lookup("verification", &id)?; |
9b2bad7a | 215 | |
35c80d69 | 216 | // check existing store |
dc7a5b34 TL |
217 | user_info.check_privs( |
218 | &auth_id, | |
219 | &["datastore", &data.store], | |
220 | PRIV_DATASTORE_VERIFY, | |
221 | true, | |
222 | )?; | |
35c80d69 FG |
223 | |
224 | if let Some(delete) = delete { | |
9b2bad7a HL |
225 | for delete_prop in delete { |
226 | match delete_prop { | |
dc7a5b34 TL |
227 | DeletableProperty::IgnoreVerified => { |
228 | data.ignore_verified = None; | |
229 | } | |
230 | DeletableProperty::OutdatedAfter => { | |
231 | data.outdated_after = None; | |
232 | } | |
233 | DeletableProperty::Comment => { | |
234 | data.comment = None; | |
235 | } | |
236 | DeletableProperty::Schedule => { | |
237 | data.schedule = None; | |
238 | } | |
59229bd7 TL |
239 | DeletableProperty::Ns => { |
240 | data.ns = None; | |
241 | } | |
9b2bad7a HL |
242 | } |
243 | } | |
244 | } | |
245 | ||
ffa403b5 | 246 | if let Some(comment) = update.comment { |
9b2bad7a HL |
247 | let comment = comment.trim().to_string(); |
248 | if comment.is_empty() { | |
249 | data.comment = None; | |
250 | } else { | |
251 | data.comment = Some(comment); | |
252 | } | |
253 | } | |
254 | ||
ffa403b5 | 255 | if let Some(store) = update.store { |
35c80d69 | 256 | // check new store |
dc7a5b34 TL |
257 | user_info.check_privs( |
258 | &auth_id, | |
259 | &["datastore", &store], | |
260 | PRIV_DATASTORE_VERIFY, | |
261 | true, | |
262 | )?; | |
35c80d69 FG |
263 | data.store = store; |
264 | } | |
265 | ||
dc7a5b34 TL |
266 | if update.ignore_verified.is_some() { |
267 | data.ignore_verified = update.ignore_verified; | |
268 | } | |
269 | if update.outdated_after.is_some() { | |
270 | data.outdated_after = update.outdated_after; | |
271 | } | |
ffa403b5 | 272 | let schedule_changed = data.schedule != update.schedule; |
dc7a5b34 TL |
273 | if update.schedule.is_some() { |
274 | data.schedule = update.schedule; | |
275 | } | |
59229bd7 TL |
276 | if let Some(ns) = update.ns { |
277 | if !ns.is_root() { | |
278 | data.ns = Some(ns); | |
279 | } | |
280 | } | |
9b2bad7a HL |
281 | |
282 | config.set_data(&id, "verification", &data)?; | |
283 | ||
284 | verify::save_config(&config)?; | |
285 | ||
951fe0cb | 286 | if schedule_changed { |
37a634f5 | 287 | crate::server::jobstate::update_job_last_run_time("verificationjob", &id)?; |
951fe0cb DC |
288 | } |
289 | ||
9b2bad7a HL |
290 | Ok(()) |
291 | } | |
292 | ||
293 | #[api( | |
294 | protected: true, | |
295 | input: { | |
296 | properties: { | |
297 | id: { | |
298 | schema: JOB_ID_SCHEMA, | |
299 | }, | |
300 | digest: { | |
301 | optional: true, | |
302 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
303 | }, | |
304 | }, | |
305 | }, | |
b7ce2e57 | 306 | access: { |
35c80d69 FG |
307 | permission: &Permission::Anybody, |
308 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 309 | }, |
9b2bad7a HL |
310 | )] |
311 | /// Remove a verification job configuration | |
35c80d69 FG |
312 | pub fn delete_verification_job( |
313 | id: String, | |
314 | digest: Option<String>, | |
315 | rpcenv: &mut dyn RpcEnvironment, | |
316 | ) -> Result<(), Error> { | |
317 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
318 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 319 | |
802189f7 | 320 | let _lock = verify::lock_config()?; |
9b2bad7a HL |
321 | |
322 | let (mut config, expected_digest) = verify::config()?; | |
323 | ||
e3619d41 | 324 | let job: VerificationJobConfig = config.lookup("verification", &id)?; |
dc7a5b34 TL |
325 | user_info.check_privs( |
326 | &auth_id, | |
327 | &["datastore", &job.store], | |
328 | PRIV_DATASTORE_VERIFY, | |
329 | true, | |
330 | )?; | |
35c80d69 | 331 | |
9b2bad7a | 332 | if let Some(ref digest) = digest { |
25877d05 | 333 | let digest = <[u8; 32]>::from_hex(digest)?; |
9b2bad7a HL |
334 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
335 | } | |
336 | ||
337 | match config.sections.get(&id) { | |
dc7a5b34 TL |
338 | Some(_) => { |
339 | config.sections.remove(&id); | |
340 | } | |
dcd1518e | 341 | None => http_bail!(NOT_FOUND, "job '{}' does not exist.", id), |
9b2bad7a HL |
342 | } |
343 | ||
344 | verify::save_config(&config)?; | |
345 | ||
1298618a | 346 | crate::server::jobstate::remove_state_file("verificationjob", &id)?; |
9b2bad7a HL |
347 | |
348 | Ok(()) | |
349 | } | |
350 | ||
351 | const ITEM_ROUTER: Router = Router::new() | |
352 | .get(&API_METHOD_READ_VERIFICATION_JOB) | |
353 | .put(&API_METHOD_UPDATE_VERIFICATION_JOB) | |
354 | .delete(&API_METHOD_DELETE_VERIFICATION_JOB); | |
355 | ||
356 | pub const ROUTER: Router = Router::new() | |
357 | .get(&API_METHOD_LIST_VERIFICATION_JOBS) | |
358 | .post(&API_METHOD_CREATE_VERIFICATION_JOB) | |
1298618a | 359 | .match_all("id", &ITEM_ROUTER); |