]>
Commit | Line | Data |
---|---|---|
9b2bad7a HL |
1 | use anyhow::{bail, Error}; |
2 | use serde_json::Value; | |
3 | use ::serde::{Deserialize, Serialize}; | |
25877d05 | 4 | use hex::FromHex; |
9b2bad7a | 5 | |
6ef1b649 WB |
6 | use proxmox_router::{Router, RpcEnvironment, Permission}; |
7 | use proxmox_schema::api; | |
9b2bad7a | 8 | |
e3619d41 | 9 | use pbs_api_types::{ |
ffa403b5 | 10 | Authid, VerificationJobConfig, VerificationJobConfigUpdater, JOB_ID_SCHEMA, |
8cc3760e | 11 | PROXMOX_CONFIG_DIGEST_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_VERIFY, |
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, | |
34 | mut rpcenv: &mut dyn RpcEnvironment, | |
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 | ||
35c80d69 FG |
45 | let list = list.into_iter() |
46 | .filter(|job: &VerificationJobConfig| { | |
47 | let privs = user_info.lookup_privs(&auth_id, &["datastore", &job.store]); | |
48 | ||
49 | privs & required_privs != 00 | |
50 | }).collect(); | |
51 | ||
25877d05 | 52 | rpcenv["digest"] = hex::encode(&digest).into(); |
9b2bad7a HL |
53 | |
54 | Ok(list) | |
55 | } | |
56 | ||
57 | ||
58 | #[api( | |
59 | protected: true, | |
60 | input: { | |
61 | properties: { | |
ffa403b5 DM |
62 | config: { |
63 | type: VerificationJobConfig, | |
64 | flatten: true, | |
9b2bad7a | 65 | }, |
ffa403b5 | 66 | }, |
b7ce2e57 FG |
67 | }, |
68 | access: { | |
35c80d69 FG |
69 | permission: &Permission::Anybody, |
70 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 71 | }, |
9b2bad7a HL |
72 | )] |
73 | /// Create a new verification job. | |
35c80d69 | 74 | pub fn create_verification_job( |
ffa403b5 | 75 | config: VerificationJobConfig, |
35c80d69 FG |
76 | rpcenv: &mut dyn RpcEnvironment |
77 | ) -> Result<(), Error> { | |
78 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
79 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 80 | |
ffa403b5 | 81 | user_info.check_privs(&auth_id, &["datastore", &config.store], PRIV_DATASTORE_VERIFY, false)?; |
35c80d69 | 82 | |
802189f7 | 83 | let _lock = verify::lock_config()?; |
35c80d69 | 84 | |
ffa403b5 | 85 | let (mut section_config, _digest) = verify::config()?; |
9b2bad7a | 86 | |
ffa403b5 DM |
87 | if section_config.sections.get(&config.id).is_some() { |
88 | bail!("job '{}' already exists.", config.id); | |
9b2bad7a HL |
89 | } |
90 | ||
ffa403b5 | 91 | section_config.set_data(&config.id, "verification", &config)?; |
9b2bad7a | 92 | |
ffa403b5 | 93 | verify::save_config(§ion_config)?; |
9b2bad7a | 94 | |
ffa403b5 | 95 | crate::server::jobstate::create_state_file("verificationjob", &config.id)?; |
9b2bad7a HL |
96 | |
97 | Ok(()) | |
98 | } | |
99 | ||
100 | #[api( | |
101 | input: { | |
102 | properties: { | |
103 | id: { | |
104 | schema: JOB_ID_SCHEMA, | |
105 | }, | |
106 | }, | |
107 | }, | |
e3619d41 | 108 | returns: { type: VerificationJobConfig }, |
b7ce2e57 | 109 | access: { |
35c80d69 FG |
110 | permission: &Permission::Anybody, |
111 | description: "Requires Datastore.Audit or Datastore.Verify on job's datastore.", | |
b7ce2e57 | 112 | }, |
9b2bad7a HL |
113 | )] |
114 | /// Read a verification job configuration. | |
115 | pub fn read_verification_job( | |
116 | id: String, | |
117 | mut rpcenv: &mut dyn RpcEnvironment, | |
118 | ) -> Result<VerificationJobConfig, Error> { | |
35c80d69 FG |
119 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
120 | let user_info = CachedUserInfo::new()?; | |
121 | ||
9b2bad7a HL |
122 | let (config, digest) = verify::config()?; |
123 | ||
e3619d41 | 124 | let verification_job: VerificationJobConfig = config.lookup("verification", &id)?; |
35c80d69 FG |
125 | |
126 | let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY; | |
127 | user_info.check_privs(&auth_id, &["datastore", &verification_job.store], required_privs, true)?; | |
128 | ||
25877d05 | 129 | rpcenv["digest"] = hex::encode(&digest).into(); |
9b2bad7a HL |
130 | |
131 | Ok(verification_job) | |
132 | } | |
133 | ||
134 | #[api()] | |
135 | #[derive(Serialize, Deserialize)] | |
136 | #[serde(rename_all="kebab-case")] | |
137 | /// Deletable property name | |
138 | pub enum DeletableProperty { | |
139 | /// Delete the ignore verified property. | |
140 | IgnoreVerified, | |
141 | /// Delete the comment property. | |
142 | Comment, | |
143 | /// Delete the job schedule. | |
144 | Schedule, | |
145 | /// Delete outdated after property. | |
146 | OutdatedAfter | |
147 | } | |
148 | ||
149 | #[api( | |
150 | protected: true, | |
151 | input: { | |
152 | properties: { | |
153 | id: { | |
154 | schema: JOB_ID_SCHEMA, | |
155 | }, | |
ffa403b5 DM |
156 | update: { |
157 | type: VerificationJobConfigUpdater, | |
158 | flatten: true, | |
9b2bad7a HL |
159 | }, |
160 | delete: { | |
161 | description: "List of properties to delete.", | |
162 | type: Array, | |
163 | optional: true, | |
164 | items: { | |
165 | type: DeletableProperty, | |
166 | } | |
167 | }, | |
168 | digest: { | |
169 | optional: true, | |
170 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
171 | }, | |
172 | }, | |
173 | }, | |
b7ce2e57 | 174 | access: { |
35c80d69 FG |
175 | permission: &Permission::Anybody, |
176 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 177 | }, |
9b2bad7a HL |
178 | )] |
179 | /// Update verification job config. | |
367c0ff7 | 180 | #[allow(clippy::too_many_arguments)] |
9b2bad7a HL |
181 | pub fn update_verification_job( |
182 | id: String, | |
ffa403b5 | 183 | update: VerificationJobConfigUpdater, |
9b2bad7a HL |
184 | delete: Option<Vec<DeletableProperty>>, |
185 | digest: Option<String>, | |
35c80d69 | 186 | rpcenv: &mut dyn RpcEnvironment, |
9b2bad7a | 187 | ) -> Result<(), Error> { |
35c80d69 FG |
188 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
189 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 190 | |
802189f7 | 191 | let _lock = verify::lock_config()?; |
9b2bad7a HL |
192 | |
193 | // pass/compare digest | |
194 | let (mut config, expected_digest) = verify::config()?; | |
195 | ||
196 | if let Some(ref digest) = digest { | |
25877d05 | 197 | let digest = <[u8; 32]>::from_hex(digest)?; |
9b2bad7a HL |
198 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
199 | } | |
200 | ||
e3619d41 | 201 | let mut data: VerificationJobConfig = config.lookup("verification", &id)?; |
9b2bad7a | 202 | |
35c80d69 FG |
203 | // check existing store |
204 | user_info.check_privs(&auth_id, &["datastore", &data.store], PRIV_DATASTORE_VERIFY, true)?; | |
205 | ||
206 | if let Some(delete) = delete { | |
9b2bad7a HL |
207 | for delete_prop in delete { |
208 | match delete_prop { | |
209 | DeletableProperty::IgnoreVerified => { data.ignore_verified = None; }, | |
210 | DeletableProperty::OutdatedAfter => { data.outdated_after = None; }, | |
211 | DeletableProperty::Comment => { data.comment = None; }, | |
212 | DeletableProperty::Schedule => { data.schedule = None; }, | |
213 | } | |
214 | } | |
215 | } | |
216 | ||
ffa403b5 | 217 | if let Some(comment) = update.comment { |
9b2bad7a HL |
218 | let comment = comment.trim().to_string(); |
219 | if comment.is_empty() { | |
220 | data.comment = None; | |
221 | } else { | |
222 | data.comment = Some(comment); | |
223 | } | |
224 | } | |
225 | ||
ffa403b5 | 226 | if let Some(store) = update.store { |
35c80d69 FG |
227 | // check new store |
228 | user_info.check_privs(&auth_id, &["datastore", &store], PRIV_DATASTORE_VERIFY, true)?; | |
229 | data.store = store; | |
230 | } | |
231 | ||
9b2bad7a | 232 | |
ffa403b5 DM |
233 | if update.ignore_verified.is_some() { data.ignore_verified = update.ignore_verified; } |
234 | if update.outdated_after.is_some() { data.outdated_after = update.outdated_after; } | |
235 | let schedule_changed = data.schedule != update.schedule; | |
236 | if update.schedule.is_some() { data.schedule = update.schedule; } | |
9b2bad7a HL |
237 | |
238 | config.set_data(&id, "verification", &data)?; | |
239 | ||
240 | verify::save_config(&config)?; | |
241 | ||
951fe0cb | 242 | if schedule_changed { |
37a634f5 | 243 | crate::server::jobstate::update_job_last_run_time("verificationjob", &id)?; |
951fe0cb DC |
244 | } |
245 | ||
9b2bad7a HL |
246 | Ok(()) |
247 | } | |
248 | ||
249 | #[api( | |
250 | protected: true, | |
251 | input: { | |
252 | properties: { | |
253 | id: { | |
254 | schema: JOB_ID_SCHEMA, | |
255 | }, | |
256 | digest: { | |
257 | optional: true, | |
258 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
259 | }, | |
260 | }, | |
261 | }, | |
b7ce2e57 | 262 | access: { |
35c80d69 FG |
263 | permission: &Permission::Anybody, |
264 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 265 | }, |
9b2bad7a HL |
266 | )] |
267 | /// Remove a verification job configuration | |
35c80d69 FG |
268 | pub fn delete_verification_job( |
269 | id: String, | |
270 | digest: Option<String>, | |
271 | rpcenv: &mut dyn RpcEnvironment, | |
272 | ) -> Result<(), Error> { | |
273 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
274 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 275 | |
802189f7 | 276 | let _lock = verify::lock_config()?; |
9b2bad7a HL |
277 | |
278 | let (mut config, expected_digest) = verify::config()?; | |
279 | ||
e3619d41 | 280 | let job: VerificationJobConfig = config.lookup("verification", &id)?; |
35c80d69 FG |
281 | user_info.check_privs(&auth_id, &["datastore", &job.store], PRIV_DATASTORE_VERIFY, true)?; |
282 | ||
9b2bad7a | 283 | if let Some(ref digest) = digest { |
25877d05 | 284 | let digest = <[u8; 32]>::from_hex(digest)?; |
9b2bad7a HL |
285 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
286 | } | |
287 | ||
288 | match config.sections.get(&id) { | |
289 | Some(_) => { config.sections.remove(&id); }, | |
290 | None => bail!("job '{}' does not exist.", id), | |
291 | } | |
292 | ||
293 | verify::save_config(&config)?; | |
294 | ||
1298618a | 295 | crate::server::jobstate::remove_state_file("verificationjob", &id)?; |
9b2bad7a HL |
296 | |
297 | Ok(()) | |
298 | } | |
299 | ||
300 | const ITEM_ROUTER: Router = Router::new() | |
301 | .get(&API_METHOD_READ_VERIFICATION_JOB) | |
302 | .put(&API_METHOD_UPDATE_VERIFICATION_JOB) | |
303 | .delete(&API_METHOD_DELETE_VERIFICATION_JOB); | |
304 | ||
305 | pub const ROUTER: Router = Router::new() | |
306 | .get(&API_METHOD_LIST_VERIFICATION_JOBS) | |
307 | .post(&API_METHOD_CREATE_VERIFICATION_JOB) | |
1298618a | 308 | .match_all("id", &ITEM_ROUTER); |