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