]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/verify.rs
verify_job: fix priv check
[proxmox-backup.git] / src / api2 / config / verify.rs
1 use ::serde::{Deserialize, Serialize};
2 use anyhow::Error;
3 use hex::FromHex;
4 use serde_json::Value;
5
6 use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
7 use proxmox_schema::{api, param_bail};
8
9 use pbs_api_types::{
10 Authid, VerificationJobConfig, VerificationJobConfigUpdater, JOB_ID_SCHEMA,
11 PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_VERIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
12 };
13 use pbs_config::verify;
14
15 use pbs_config::CachedUserInfo;
16
17 #[api(
18 input: {
19 properties: {},
20 },
21 returns: {
22 description: "List configured jobs.",
23 type: Array,
24 items: { type: VerificationJobConfig },
25 },
26 access: {
27 permission: &Permission::Anybody,
28 description: "Requires Datastore.Audit or Datastore.Verify on datastore.",
29 },
30 )]
31 /// List all verification jobs
32 pub fn list_verification_jobs(
33 _param: Value,
34 rpcenv: &mut dyn RpcEnvironment,
35 ) -> Result<Vec<VerificationJobConfig>, Error> {
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;
40
41 let (config, digest) = verify::config()?;
42
43 let list = config.convert_to_typed_array("verification")?;
44
45 let list = list
46 .into_iter()
47 .filter(|job: &VerificationJobConfig| {
48 let privs = user_info.lookup_privs(&auth_id, &job.store_with_ns().acl_path());
49
50 privs & required_privs != 00
51 })
52 .collect();
53
54 rpcenv["digest"] = hex::encode(&digest).into();
55
56 Ok(list)
57 }
58
59 #[api(
60 protected: true,
61 input: {
62 properties: {
63 config: {
64 type: VerificationJobConfig,
65 flatten: true,
66 },
67 },
68 },
69 access: {
70 permission: &Permission::Anybody,
71 description: "Requires Datastore.Verify on job's datastore.",
72 },
73 )]
74 /// Create a new verification job.
75 pub fn create_verification_job(
76 config: VerificationJobConfig,
77 rpcenv: &mut dyn RpcEnvironment,
78 ) -> Result<(), Error> {
79 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
80 let user_info = CachedUserInfo::new()?;
81
82 user_info.check_privs(
83 &auth_id,
84 &config.store_with_ns().acl_path(),
85 PRIV_DATASTORE_VERIFY,
86 false,
87 )?;
88
89 let _lock = verify::lock_config()?;
90
91 let (mut section_config, _digest) = verify::config()?;
92
93 if section_config.sections.get(&config.id).is_some() {
94 param_bail!("id", "job '{}' already exists.", config.id);
95 }
96
97 section_config.set_data(&config.id, "verification", &config)?;
98
99 verify::save_config(&section_config)?;
100
101 crate::server::jobstate::create_state_file("verificationjob", &config.id)?;
102
103 Ok(())
104 }
105
106 #[api(
107 input: {
108 properties: {
109 id: {
110 schema: JOB_ID_SCHEMA,
111 },
112 },
113 },
114 returns: { type: VerificationJobConfig },
115 access: {
116 permission: &Permission::Anybody,
117 description: "Requires Datastore.Audit or Datastore.Verify on job's datastore.",
118 },
119 )]
120 /// Read a verification job configuration.
121 pub fn read_verification_job(
122 id: String,
123 rpcenv: &mut dyn RpcEnvironment,
124 ) -> Result<VerificationJobConfig, Error> {
125 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
126 let user_info = CachedUserInfo::new()?;
127
128 let (config, digest) = verify::config()?;
129
130 let verification_job: VerificationJobConfig = config.lookup("verification", &id)?;
131
132 let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY;
133 user_info.check_privs(
134 &auth_id,
135 &verification_job.store_with_ns().acl_path(),
136 required_privs,
137 true,
138 )?;
139
140 rpcenv["digest"] = hex::encode(&digest).into();
141
142 Ok(verification_job)
143 }
144
145 #[api()]
146 #[derive(Serialize, Deserialize)]
147 #[serde(rename_all = "kebab-case")]
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.
157 OutdatedAfter,
158 /// Delete namespace property, defaulting to root namespace then.
159 Ns,
160 /// Delete max-depth property, defaulting to full recursion again
161 MaxDepth,
162 }
163
164 #[api(
165 protected: true,
166 input: {
167 properties: {
168 id: {
169 schema: JOB_ID_SCHEMA,
170 },
171 update: {
172 type: VerificationJobConfigUpdater,
173 flatten: true,
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 },
189 access: {
190 permission: &Permission::Anybody,
191 description: "Requires Datastore.Verify on job's datastore.",
192 },
193 )]
194 /// Update verification job config.
195 #[allow(clippy::too_many_arguments)]
196 pub fn update_verification_job(
197 id: String,
198 update: VerificationJobConfigUpdater,
199 delete: Option<Vec<DeletableProperty>>,
200 digest: Option<String>,
201 rpcenv: &mut dyn RpcEnvironment,
202 ) -> Result<(), Error> {
203 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
204 let user_info = CachedUserInfo::new()?;
205
206 let _lock = verify::lock_config()?;
207
208 // pass/compare digest
209 let (mut config, expected_digest) = verify::config()?;
210
211 if let Some(ref digest) = digest {
212 let digest = <[u8; 32]>::from_hex(digest)?;
213 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
214 }
215
216 let mut data: VerificationJobConfig = config.lookup("verification", &id)?;
217
218 // check existing store and NS
219 user_info.check_privs(
220 &auth_id,
221 &data.store_with_ns().acl_path(),
222 PRIV_DATASTORE_VERIFY,
223 true,
224 )?;
225
226 if let Some(delete) = delete {
227 for delete_prop in delete {
228 match delete_prop {
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 }
241 DeletableProperty::Ns => {
242 data.ns = None;
243 }
244 DeletableProperty::MaxDepth => {
245 data.max_depth = None;
246 }
247 }
248 }
249 }
250
251 if let Some(comment) = update.comment {
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
260 if let Some(store) = update.store {
261 data.store = store;
262 }
263
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 }
270 let schedule_changed = data.schedule != update.schedule;
271 if update.schedule.is_some() {
272 data.schedule = update.schedule;
273 }
274 if let Some(ns) = update.ns {
275 if !ns.is_root() {
276 data.ns = Some(ns);
277 }
278 }
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 }
284
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
293 config.set_data(&id, "verification", &data)?;
294
295 verify::save_config(&config)?;
296
297 if schedule_changed {
298 crate::server::jobstate::update_job_last_run_time("verificationjob", &id)?;
299 }
300
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 },
317 access: {
318 permission: &Permission::Anybody,
319 description: "Requires Datastore.Verify on job's datastore.",
320 },
321 )]
322 /// Remove a verification job configuration
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()?;
330
331 let _lock = verify::lock_config()?;
332
333 let (mut config, expected_digest) = verify::config()?;
334
335 let job: VerificationJobConfig = config.lookup("verification", &id)?;
336 user_info.check_privs(
337 &auth_id,
338 &job.store_with_ns().acl_path(),
339 PRIV_DATASTORE_VERIFY,
340 true,
341 )?;
342
343 if let Some(ref digest) = digest {
344 let digest = <[u8; 32]>::from_hex(digest)?;
345 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
346 }
347
348 match config.sections.get(&id) {
349 Some(_) => {
350 config.sections.remove(&id);
351 }
352 None => http_bail!(NOT_FOUND, "job '{}' does not exist.", id),
353 }
354
355 verify::save_config(&config)?;
356
357 crate::server::jobstate::remove_state_file("verificationjob", &id)?;
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)
370 .match_all("id", &ITEM_ROUTER);