]>
Commit | Line | Data |
---|---|---|
155f657f WB |
1 | //! Basic API types used by most of the PBS code. |
2 | ||
c23192d3 | 3 | use serde::{Deserialize, Serialize}; |
8cc3760e | 4 | use anyhow::bail; |
c23192d3 | 5 | |
6ef1b649 WB |
6 | use proxmox_schema::{ |
7 | api, const_regex, ApiStringFormat, ApiType, ArraySchema, Schema, StringSchema, ReturnType, | |
8 | }; | |
75f83c6a | 9 | use proxmox::{IPRE, IPRE_BRACKET, IPV4OCTET, IPV4RE, IPV6H16, IPV6LS32, IPV6RE}; |
86fb3877 | 10 | |
bfff4eaa WB |
11 | #[rustfmt::skip] |
12 | #[macro_export] | |
13 | macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => { r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)" }; } | |
14 | ||
15 | #[rustfmt::skip] | |
16 | #[macro_export] | |
17 | macro_rules! BACKUP_ID_RE { () => (r"[A-Za-z0-9_][A-Za-z0-9._\-]*") } | |
18 | ||
19 | #[rustfmt::skip] | |
20 | #[macro_export] | |
21 | macro_rules! BACKUP_TYPE_RE { () => (r"(?:host|vm|ct)") } | |
22 | ||
23 | #[rustfmt::skip] | |
24 | #[macro_export] | |
25 | macro_rules! BACKUP_TIME_RE { () => (r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z") } | |
26 | ||
27 | #[rustfmt::skip] | |
28 | #[macro_export] | |
29 | macro_rules! SNAPSHOT_PATH_REGEX_STR { | |
30 | () => ( | |
31 | concat!(r"(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), ")/(", BACKUP_TIME_RE!(), r")") | |
32 | ); | |
33 | } | |
34 | ||
8cc3760e DM |
35 | mod acl; |
36 | pub use acl::*; | |
37 | ||
38 | mod datastore; | |
39 | pub use datastore::*; | |
40 | ||
e3619d41 DM |
41 | mod jobs; |
42 | pub use jobs::*; | |
43 | ||
45d5d873 DM |
44 | mod key_derivation; |
45 | pub use key_derivation::{Kdf, KeyInfo}; | |
46 | ||
6f422880 DM |
47 | mod network; |
48 | pub use network::*; | |
49 | ||
751f6b61 WB |
50 | #[macro_use] |
51 | mod userid; | |
52 | pub use userid::Authid; | |
53 | pub use userid::Userid; | |
54 | pub use userid::{Realm, RealmRef}; | |
55 | pub use userid::{Tokenname, TokennameRef}; | |
56 | pub use userid::{Username, UsernameRef}; | |
57 | pub use userid::{PROXMOX_GROUP_ID_SCHEMA, PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN_NAME_SCHEMA}; | |
58 | ||
2b7f8dd5 WB |
59 | #[macro_use] |
60 | mod user; | |
b65dfff5 | 61 | pub use user::*; |
2b7f8dd5 | 62 | |
6ef1b649 | 63 | pub use proxmox_schema::upid::*; |
95f9d67c | 64 | |
ea584a75 WB |
65 | mod crypto; |
66 | pub use crypto::{CryptMode, Fingerprint}; | |
67 | ||
013b1e8b WB |
68 | pub mod file_restore; |
69 | ||
6afdda88 DM |
70 | mod remote; |
71 | pub use remote::*; | |
72 | ||
1ce8e905 DM |
73 | mod tape; |
74 | pub use tape::*; | |
75 | ||
8cc3760e DM |
76 | mod zfs; |
77 | pub use zfs::*; | |
78 | ||
79 | ||
75f83c6a WB |
80 | #[rustfmt::skip] |
81 | #[macro_use] | |
82 | mod local_macros { | |
83 | macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") } | |
84 | macro_rules! DNS_NAME { () => (concat!(r"(?:(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!(), ")")) } | |
85 | macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) } | |
86 | macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) } | |
87 | macro_rules! DNS_ALIAS_LABEL { () => (r"(?:[a-zA-Z0-9_](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") } | |
88 | macro_rules! DNS_ALIAS_NAME { | |
89 | () => (concat!(r"(?:(?:", DNS_ALIAS_LABEL!() , r"\.)*", DNS_ALIAS_LABEL!(), ")")) | |
90 | } | |
91 | } | |
92 | ||
86fb3877 | 93 | const_regex! { |
75f83c6a WB |
94 | pub IP_V4_REGEX = concat!(r"^", IPV4RE!(), r"$"); |
95 | pub IP_V6_REGEX = concat!(r"^", IPV6RE!(), r"$"); | |
96 | pub IP_REGEX = concat!(r"^", IPRE!(), r"$"); | |
97 | pub CIDR_V4_REGEX = concat!(r"^", CIDR_V4_REGEX_STR!(), r"$"); | |
98 | pub CIDR_V6_REGEX = concat!(r"^", CIDR_V6_REGEX_STR!(), r"$"); | |
99 | pub CIDR_REGEX = concat!(r"^(?:", CIDR_V4_REGEX_STR!(), "|", CIDR_V6_REGEX_STR!(), r")$"); | |
100 | pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$"; | |
101 | pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$"); | |
102 | pub DNS_ALIAS_REGEX = concat!(r"^", DNS_ALIAS_NAME!(), r"$"); | |
103 | pub DNS_NAME_OR_IP_REGEX = concat!(r"^(?:", DNS_NAME!(), "|", IPRE!(), r")$"); | |
104 | ||
105 | pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ? | |
106 | ||
107 | pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters | |
108 | ||
109 | pub UUID_REGEX = r"^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$"; | |
110 | ||
8cc3760e | 111 | pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ? |
bfff4eaa | 112 | |
86fb3877 | 113 | pub FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$"; |
3c430e9a WB |
114 | |
115 | /// Regex for safe identifiers. | |
116 | /// | |
117 | /// This | |
118 | /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html) | |
119 | /// contains further information why it is reasonable to restict | |
120 | /// names this way. This is not only useful for filenames, but for | |
121 | /// any identifier command line tools work with. | |
122 | pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$"); | |
123 | ||
124 | pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$"; | |
75f83c6a WB |
125 | |
126 | pub BACKUP_REPO_URL_REGEX = concat!( | |
127 | r"^^(?:(?:(", | |
128 | USER_ID_REGEX_STR!(), "|", APITOKEN_ID_REGEX_STR!(), | |
129 | ")@)?(", | |
130 | DNS_NAME!(), "|", IPRE_BRACKET!(), | |
131 | "):)?(?:([0-9]{1,5}):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$" | |
132 | ); | |
4c1b7761 WB |
133 | |
134 | pub BLOCKDEVICE_NAME_REGEX = r"^(:?(:?h|s|x?v)d[a-z]+)|(:?nvme\d+n\d+)$"; | |
8cc3760e | 135 | pub SUBSCRIPTION_KEY_REGEX = concat!(r"^pbs(?:[cbsp])-[0-9a-f]{10}$"); |
86fb3877 WB |
136 | } |
137 | ||
75f83c6a WB |
138 | pub const IP_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_V4_REGEX); |
139 | pub const IP_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_V6_REGEX); | |
140 | pub const IP_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_REGEX); | |
141 | pub const CIDR_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V4_REGEX); | |
142 | pub const CIDR_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V6_REGEX); | |
143 | pub const CIDR_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_REGEX); | |
8cc3760e DM |
144 | pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SHA256_HEX_REGEX); |
145 | pub const PASSWORD_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PASSWORD_REGEX); | |
146 | pub const UUID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&UUID_REGEX); | |
147 | pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&BLOCKDEVICE_NAME_REGEX); | |
148 | pub const SUBSCRIPTION_KEY_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SUBSCRIPTION_KEY_REGEX); | |
149 | pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX); | |
150 | pub const HOSTNAME_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&HOSTNAME_REGEX); | |
151 | ||
152 | pub const DNS_ALIAS_FORMAT: ApiStringFormat = | |
153 | ApiStringFormat::Pattern(&DNS_ALIAS_REGEX); | |
154 | ||
155 | pub const SEARCH_DOMAIN_SCHEMA: Schema = | |
156 | StringSchema::new("Search domain for host-name lookup.").schema(); | |
157 | ||
158 | pub const FIRST_DNS_SERVER_SCHEMA: Schema = | |
159 | StringSchema::new("First name server IP address.") | |
160 | .format(&IP_FORMAT) | |
161 | .schema(); | |
162 | ||
163 | pub const SECOND_DNS_SERVER_SCHEMA: Schema = | |
164 | StringSchema::new("Second name server IP address.") | |
165 | .format(&IP_FORMAT) | |
166 | .schema(); | |
167 | ||
168 | pub const THIRD_DNS_SERVER_SCHEMA: Schema = | |
169 | StringSchema::new("Third name server IP address.") | |
170 | .format(&IP_FORMAT) | |
171 | .schema(); | |
172 | ||
173 | pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).") | |
174 | .format(&HOSTNAME_FORMAT) | |
175 | .schema(); | |
75f83c6a | 176 | |
6afdda88 DM |
177 | pub const DNS_NAME_FORMAT: ApiStringFormat = |
178 | ApiStringFormat::Pattern(&DNS_NAME_REGEX); | |
179 | ||
180 | pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat = | |
181 | ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX); | |
182 | ||
183 | pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.") | |
184 | .format(&DNS_NAME_OR_IP_FORMAT) | |
185 | .schema(); | |
186 | ||
8cc3760e DM |
187 | pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')") |
188 | .format(&ApiStringFormat::VerifyFn(|node| { | |
189 | if node == "localhost" || node == proxmox::tools::nodename() { | |
190 | Ok(()) | |
191 | } else { | |
192 | bail!("no such node '{}'", node); | |
193 | } | |
194 | })) | |
ea584a75 | 195 | .schema(); |
8cc3760e DM |
196 | |
197 | pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new( | |
198 | "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.") | |
199 | .format(&SINGLE_LINE_COMMENT_FORMAT) | |
200 | .min_length(2) | |
201 | .max_length(64) | |
ea584a75 WB |
202 | .schema(); |
203 | ||
8cc3760e DM |
204 | pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).") |
205 | .format(&BLOCKDEVICE_NAME_FORMAT) | |
ea584a75 | 206 | .min_length(3) |
8cc3760e DM |
207 | .max_length(64) |
208 | .schema(); | |
209 | ||
210 | pub const DISK_ARRAY_SCHEMA: Schema = ArraySchema::new( | |
211 | "Disk name list.", &BLOCKDEVICE_NAME_SCHEMA) | |
212 | .schema(); | |
213 | ||
214 | pub const DISK_LIST_SCHEMA: Schema = StringSchema::new( | |
215 | "A list of disk names, comma separated.") | |
216 | .format(&ApiStringFormat::PropertyString(&DISK_ARRAY_SCHEMA)) | |
217 | .schema(); | |
218 | ||
219 | pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.") | |
220 | .format(&PASSWORD_FORMAT) | |
221 | .min_length(1) | |
222 | .max_length(1024) | |
223 | .schema(); | |
224 | ||
225 | pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.") | |
226 | .format(&PASSWORD_FORMAT) | |
227 | .min_length(5) | |
228 | .max_length(64) | |
ea584a75 WB |
229 | .schema(); |
230 | ||
21211748 DM |
231 | pub const REALM_ID_SCHEMA: Schema = StringSchema::new("Realm name.") |
232 | .format(&PROXMOX_SAFE_ID_FORMAT) | |
233 | .min_length(2) | |
234 | .max_length(32) | |
235 | .schema(); | |
236 | ||
86fb3877 WB |
237 | pub const FINGERPRINT_SHA256_FORMAT: ApiStringFormat = |
238 | ApiStringFormat::Pattern(&FINGERPRINT_SHA256_REGEX); | |
239 | ||
240 | pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema = | |
241 | StringSchema::new("X509 certificate fingerprint (sha256).") | |
242 | .format(&FINGERPRINT_SHA256_FORMAT) | |
243 | .schema(); | |
3c430e9a WB |
244 | |
245 | pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat = | |
246 | ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX); | |
247 | ||
248 | pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat = | |
249 | ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX); | |
250 | ||
251 | pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).") | |
252 | .format(&SINGLE_LINE_COMMENT_FORMAT) | |
253 | .schema(); | |
bfff4eaa | 254 | |
8cc3760e DM |
255 | pub const SUBSCRIPTION_KEY_SCHEMA: Schema = StringSchema::new("Proxmox Backup Server subscription key.") |
256 | .format(&SUBSCRIPTION_KEY_FORMAT) | |
257 | .min_length(15) | |
258 | .max_length(16) | |
259 | .schema(); | |
260 | ||
261 | pub const SERVICE_ID_SCHEMA: Schema = StringSchema::new("Service ID.") | |
262 | .max_length(256) | |
263 | .schema(); | |
264 | ||
2b7f8dd5 WB |
265 | pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new( |
266 | "Prevent changes if current configuration file has different \ | |
267 | SHA256 digest. This can be used to prevent concurrent \ | |
268 | modifications.", | |
269 | ) | |
270 | .format(&PVE_CONFIG_DIGEST_FORMAT) | |
271 | .schema(); | |
272 | ||
75f83c6a WB |
273 | /// API schema format definition for repository URLs |
274 | pub const BACKUP_REPO_URL: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_REPO_URL_REGEX); | |
275 | ||
75f83c6a | 276 | |
ea584a75 WB |
277 | // Complex type definitions |
278 | ||
51ec8a3c WB |
279 | |
280 | #[api()] | |
281 | #[derive(Default, Serialize, Deserialize)] | |
282 | /// Storage space usage information. | |
283 | pub struct StorageStatus { | |
284 | /// Total space (bytes). | |
285 | pub total: u64, | |
286 | /// Used space (bytes). | |
287 | pub used: u64, | |
288 | /// Available space (bytes). | |
289 | pub avail: u64, | |
290 | } | |
291 | ||
eb5e0ae6 WB |
292 | pub const PASSWORD_HINT_SCHEMA: Schema = StringSchema::new("Password hint.") |
293 | .format(&SINGLE_LINE_COMMENT_FORMAT) | |
294 | .min_length(1) | |
295 | .max_length(64) | |
296 | .schema(); | |
297 | ||
298 | ||
299 | #[api] | |
300 | #[derive(Deserialize, Serialize)] | |
301 | /// RSA public key information | |
302 | pub struct RsaPubKeyInfo { | |
303 | /// Path to key (if stored in a file) | |
304 | #[serde(skip_serializing_if="Option::is_none")] | |
305 | pub path: Option<String>, | |
306 | /// RSA exponent | |
307 | pub exponent: String, | |
308 | /// Hex-encoded RSA modulus | |
309 | pub modulus: String, | |
310 | /// Key (modulus) length in bits | |
311 | pub length: usize, | |
312 | } | |
313 | ||
314 | impl std::convert::TryFrom<openssl::rsa::Rsa<openssl::pkey::Public>> for RsaPubKeyInfo { | |
315 | type Error = anyhow::Error; | |
316 | ||
317 | fn try_from(value: openssl::rsa::Rsa<openssl::pkey::Public>) -> Result<Self, Self::Error> { | |
318 | let modulus = value.n().to_hex_str()?.to_string(); | |
319 | let exponent = value.e().to_dec_str()?.to_string(); | |
320 | let length = value.size() as usize * 8; | |
321 | ||
322 | Ok(Self { | |
323 | path: None, | |
324 | exponent, | |
325 | modulus, | |
326 | length, | |
327 | }) | |
328 | } | |
329 | } | |
7b570c17 | 330 | |
e3619d41 DM |
331 | #[api()] |
332 | #[derive(Debug, Clone, Serialize, Deserialize)] | |
333 | #[serde(rename_all = "PascalCase")] | |
334 | /// Describes a package for which an update is available. | |
335 | pub struct APTUpdateInfo { | |
336 | /// Package name | |
337 | pub package: String, | |
338 | /// Package title | |
339 | pub title: String, | |
340 | /// Package architecture | |
341 | pub arch: String, | |
342 | /// Human readable package description | |
343 | pub description: String, | |
344 | /// New version to be updated to | |
345 | pub version: String, | |
346 | /// Old version currently installed | |
347 | pub old_version: String, | |
348 | /// Package origin | |
349 | pub origin: String, | |
350 | /// Package priority in human-readable form | |
351 | pub priority: String, | |
352 | /// Package section | |
353 | pub section: String, | |
354 | /// URL under which the package's changelog can be retrieved | |
355 | pub change_log_url: String, | |
356 | /// Custom extra field for additional package information | |
357 | #[serde(skip_serializing_if="Option::is_none")] | |
358 | pub extra_info: Option<String>, | |
359 | } | |
8cc3760e | 360 | |
8cc3760e DM |
361 | |
362 | #[api()] | |
363 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] | |
364 | #[serde(rename_all = "lowercase")] | |
365 | /// Node Power command type. | |
366 | pub enum NodePowerCommand { | |
367 | /// Restart the server | |
368 | Reboot, | |
369 | /// Shutdown the server | |
370 | Shutdown, | |
371 | } | |
81867f05 DM |
372 | |
373 | ||
374 | #[api()] | |
375 | #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)] | |
376 | #[serde(rename_all = "lowercase")] | |
377 | pub enum TaskStateType { | |
378 | /// Ok | |
379 | OK, | |
380 | /// Warning | |
381 | Warning, | |
382 | /// Error | |
383 | Error, | |
384 | /// Unknown | |
385 | Unknown, | |
386 | } | |
387 | ||
388 | #[api( | |
389 | properties: { | |
390 | upid: { schema: UPID::API_SCHEMA }, | |
391 | }, | |
392 | )] | |
393 | #[derive(Serialize, Deserialize)] | |
394 | /// Task properties. | |
395 | pub struct TaskListItem { | |
396 | pub upid: String, | |
397 | /// The node name where the task is running on. | |
398 | pub node: String, | |
399 | /// The Unix PID | |
400 | pub pid: i64, | |
401 | /// The task start time (Epoch) | |
402 | pub pstart: u64, | |
403 | /// The task start time (Epoch) | |
404 | pub starttime: i64, | |
405 | /// Worker type (arbitrary ASCII string) | |
406 | pub worker_type: String, | |
407 | /// Worker ID (arbitrary ASCII string) | |
408 | pub worker_id: Option<String>, | |
409 | /// The authenticated entity who started the task | |
410 | pub user: String, | |
411 | /// The task end time (Epoch) | |
412 | #[serde(skip_serializing_if="Option::is_none")] | |
413 | pub endtime: Option<i64>, | |
414 | /// Task end status | |
415 | #[serde(skip_serializing_if="Option::is_none")] | |
416 | pub status: Option<String>, | |
417 | } | |
418 | ||
419 | pub const NODE_TASKS_LIST_TASKS_RETURN_TYPE: ReturnType = ReturnType { | |
420 | optional: false, | |
421 | schema: &ArraySchema::new( | |
422 | "A list of tasks.", | |
423 | &TaskListItem::API_SCHEMA, | |
424 | ).schema(), | |
425 | }; | |
d1c3bc53 DM |
426 | |
427 | pub use proxmox_rrd_api_types::{RRDMode, RRDTimeFrameResolution}; |