]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/verify.rs
update proxmox-metrics dependency to 0.3.1
[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.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(&auth_id, &config.acl_path(), PRIV_DATASTORE_VERIFY, false)?;
83
84 let _lock = verify::lock_config()?;
85
86 let (mut section_config, _digest) = verify::config()?;
87
88 if section_config.sections.get(&config.id).is_some() {
89 param_bail!("id", "job '{}' already exists.", config.id);
90 }
91
92 section_config.set_data(&config.id, "verification", &config)?;
93
94 verify::save_config(&section_config)?;
95
96 crate::server::jobstate::create_state_file("verificationjob", &config.id)?;
97
98 Ok(())
99 }
100
101 #[api(
102 input: {
103 properties: {
104 id: {
105 schema: JOB_ID_SCHEMA,
106 },
107 },
108 },
109 returns: { type: VerificationJobConfig },
110 access: {
111 permission: &Permission::Anybody,
112 description: "Requires Datastore.Audit or Datastore.Verify on job's datastore.",
113 },
114 )]
115 /// Read a verification job configuration.
116 pub fn read_verification_job(
117 id: String,
118 rpcenv: &mut dyn RpcEnvironment,
119 ) -> Result<VerificationJobConfig, Error> {
120 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
121 let user_info = CachedUserInfo::new()?;
122
123 let (config, digest) = verify::config()?;
124
125 let verification_job: VerificationJobConfig = config.lookup("verification", &id)?;
126
127 let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY;
128 user_info.check_privs(&auth_id, &verification_job.acl_path(), required_privs, true)?;
129
130 rpcenv["digest"] = hex::encode(digest).into();
131
132 Ok(verification_job)
133 }
134
135 #[api()]
136 #[derive(Serialize, Deserialize)]
137 #[serde(rename_all = "kebab-case")]
138 /// Deletable property name
139 pub enum DeletableProperty {
140 /// Delete the ignore verified property.
141 IgnoreVerified,
142 /// Delete the comment property.
143 Comment,
144 /// Delete the job schedule.
145 Schedule,
146 /// Delete outdated after property.
147 OutdatedAfter,
148 /// Delete namespace property, defaulting to root namespace then.
149 Ns,
150 /// Delete max-depth property, defaulting to full recursion again
151 MaxDepth,
152 }
153
154 #[api(
155 protected: true,
156 input: {
157 properties: {
158 id: {
159 schema: JOB_ID_SCHEMA,
160 },
161 update: {
162 type: VerificationJobConfigUpdater,
163 flatten: true,
164 },
165 delete: {
166 description: "List of properties to delete.",
167 type: Array,
168 optional: true,
169 items: {
170 type: DeletableProperty,
171 }
172 },
173 digest: {
174 optional: true,
175 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
176 },
177 },
178 },
179 access: {
180 permission: &Permission::Anybody,
181 description: "Requires Datastore.Verify on job's datastore.",
182 },
183 )]
184 /// Update verification job config.
185 #[allow(clippy::too_many_arguments)]
186 pub fn update_verification_job(
187 id: String,
188 update: VerificationJobConfigUpdater,
189 delete: Option<Vec<DeletableProperty>>,
190 digest: Option<String>,
191 rpcenv: &mut dyn RpcEnvironment,
192 ) -> Result<(), Error> {
193 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
194 let user_info = CachedUserInfo::new()?;
195
196 let _lock = verify::lock_config()?;
197
198 // pass/compare digest
199 let (mut config, expected_digest) = verify::config()?;
200
201 if let Some(ref digest) = digest {
202 let digest = <[u8; 32]>::from_hex(digest)?;
203 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
204 }
205
206 let mut data: VerificationJobConfig = config.lookup("verification", &id)?;
207
208 // check existing store and NS
209 user_info.check_privs(&auth_id, &data.acl_path(), PRIV_DATASTORE_VERIFY, true)?;
210
211 if let Some(delete) = delete {
212 for delete_prop in delete {
213 match delete_prop {
214 DeletableProperty::IgnoreVerified => {
215 data.ignore_verified = None;
216 }
217 DeletableProperty::OutdatedAfter => {
218 data.outdated_after = None;
219 }
220 DeletableProperty::Comment => {
221 data.comment = None;
222 }
223 DeletableProperty::Schedule => {
224 data.schedule = None;
225 }
226 DeletableProperty::Ns => {
227 data.ns = None;
228 }
229 DeletableProperty::MaxDepth => {
230 data.max_depth = None;
231 }
232 }
233 }
234 }
235
236 if let Some(comment) = update.comment {
237 let comment = comment.trim().to_string();
238 if comment.is_empty() {
239 data.comment = None;
240 } else {
241 data.comment = Some(comment);
242 }
243 }
244
245 if let Some(store) = update.store {
246 data.store = store;
247 }
248
249 if update.ignore_verified.is_some() {
250 data.ignore_verified = update.ignore_verified;
251 }
252 if update.outdated_after.is_some() {
253 data.outdated_after = update.outdated_after;
254 }
255 let schedule_changed = data.schedule != update.schedule;
256 if update.schedule.is_some() {
257 data.schedule = update.schedule;
258 }
259 if let Some(ns) = update.ns {
260 if !ns.is_root() {
261 data.ns = Some(ns);
262 }
263 }
264 if let Some(max_depth) = update.max_depth {
265 if max_depth <= pbs_api_types::MAX_NAMESPACE_DEPTH {
266 data.max_depth = Some(max_depth);
267 }
268 }
269
270 // check new store and NS
271 user_info.check_privs(&auth_id, &data.acl_path(), PRIV_DATASTORE_VERIFY, true)?;
272
273 config.set_data(&id, "verification", &data)?;
274
275 verify::save_config(&config)?;
276
277 if schedule_changed {
278 crate::server::jobstate::update_job_last_run_time("verificationjob", &id)?;
279 }
280
281 Ok(())
282 }
283
284 #[api(
285 protected: true,
286 input: {
287 properties: {
288 id: {
289 schema: JOB_ID_SCHEMA,
290 },
291 digest: {
292 optional: true,
293 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
294 },
295 },
296 },
297 access: {
298 permission: &Permission::Anybody,
299 description: "Requires Datastore.Verify on job's datastore.",
300 },
301 )]
302 /// Remove a verification job configuration
303 pub fn delete_verification_job(
304 id: String,
305 digest: Option<String>,
306 rpcenv: &mut dyn RpcEnvironment,
307 ) -> Result<(), Error> {
308 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
309 let user_info = CachedUserInfo::new()?;
310
311 let _lock = verify::lock_config()?;
312
313 let (mut config, expected_digest) = verify::config()?;
314
315 let job: VerificationJobConfig = config.lookup("verification", &id)?;
316 user_info.check_privs(&auth_id, &job.acl_path(), PRIV_DATASTORE_VERIFY, true)?;
317
318 if let Some(ref digest) = digest {
319 let digest = <[u8; 32]>::from_hex(digest)?;
320 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
321 }
322
323 match config.sections.get(&id) {
324 Some(_) => {
325 config.sections.remove(&id);
326 }
327 None => http_bail!(NOT_FOUND, "job '{}' does not exist.", id),
328 }
329
330 verify::save_config(&config)?;
331
332 crate::server::jobstate::remove_state_file("verificationjob", &id)?;
333
334 Ok(())
335 }
336
337 const ITEM_ROUTER: Router = Router::new()
338 .get(&API_METHOD_READ_VERIFICATION_JOB)
339 .put(&API_METHOD_UPDATE_VERIFICATION_JOB)
340 .delete(&API_METHOD_DELETE_VERIFICATION_JOB);
341
342 pub const ROUTER: Router = Router::new()
343 .get(&API_METHOD_LIST_VERIFICATION_JOBS)
344 .post(&API_METHOD_CREATE_VERIFICATION_JOB)
345 .match_all("id", &ITEM_ROUTER);