Compare commits

...

1682 Commits

Author SHA1 Message Date
9db7bc90f6 Merge remote-tracking branch 'upstream/master' 2025-03-28 15:28:55 +03:00
Lukas Wagner
4097d3697d notify: gotify: use constant from http crate for 'Authorization' header
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2025-03-25 19:37:37 +01:00
Lukas Wagner
6d4c115f05 notify: webhook: gotify: set Content-Length header
To quote from RFC 9110 [1]:

  A user agent SHOULD send Content-Length in a request when
  the method defines a meaning for enclosed content and it
  is not sending Transfer-Encoding. For example, a user agent
  normally sends Content-Length in a POST request even when
  the value is 0 (indicating empty content).
  A user agent SHOULD NOT send a Content-Length header field
  when the request message does not contain content and the
  method semantics do not anticipate such data.

It seemed like our HTTP client lib did not set the header
automatically, which is why we should do it manually.

While most services seemed to have worked fine without setting
the header, some Microsoft services seem to require it
to accept the webhook request [2].

[1] https://datatracker.ietf.org/doc/html/rfc9110#name-content-length
[2] https://forum.proxmox.com/threads/158827

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2025-03-25 19:37:37 +01:00
Thomas Lamprecht
7abd2da759 pbs-api-types: acl: fix indentation error in macro
expand tabs to spaces

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-03-24 17:56:13 +01:00
Dietmar Maurer
ec8a3de133 sys: procfs: split read_meminfo into read and parse functions
So that we can write tests.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2025-03-20 18:42:05 +01:00
Wolfgang Bumiller
da8fdea632 rest-server: bump to 0.8.8-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-20 14:45:19 +01:00
Christian Ebner
10cf5ed7b4 rest-server: worker task: include context in state error message
Currently the anyhow error context of a given error is not included
in the error message, as `to_string` does use the default formatting
[0].

Include the error context, formatting it as single line as the
message is also shown to the users in e.g. the Proxmox Backup Severs
task state in the UI.

[0] https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2025-03-20 14:40:58 +01:00
Maximiliano Sandoval
25c08ad247 sys: add variable bindings for temporaries in unsafe blocks
These will produce an error in edition 2024 otherwise. The reason this
is needed is because the `unsafe` block has its own scope.

The bytes were defined inside of the let-mut block to preserve the
lifetime they had before this commit.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2025-03-19 12:32:42 +01:00
Wolfgang Bumiller
e06277ac7a log: bump to 0.2.8-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-19 12:19:43 +01:00
Wolfgang Bumiller
86a517d087 sys: bump to 0.6.6-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-19 12:19:43 +01:00
Wolfgang Bumiller
57eb5a36e9 sys, shared-memory: deny unsafe_op_in_unsafe_fn explicitly
can be removed in these and the other crates when switching to edition
2024

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-19 12:19:43 +01:00
Wolfgang Bumiller
d42810e3c1 log, rest-server: cargo fmt / formatting cleanups
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-19 12:19:43 +01:00
Maximiliano Sandoval
3db442fb8f async: accommodate to edition 2024 changes to RPIT
Prevents the following error:

```
error[E0597]: `inner` does not live long enough
   --> proxmox-async/src/broadcast_future.rs:109:24
    |
107 |         inner: Arc<Mutex<BroadCastFutureBinding<T>>>,
    |         ----- binding `inner` declared here
108 |     ) -> impl Future<Output = Result<T, Error>> {
109 |         let mut data = inner.lock().unwrap();
    |                        ^^^^^ borrowed value does not live long enough
...
121 |         data.broadcast.listen()
    |         ----------------------- argument requires that `inner` is borrowed for `'static`
122 |     }
    |     - `inner` dropped here while still borrowed

error[E0597]: `data` does not live long enough
   --> proxmox-async/src/broadcast_future.rs:121:9
    |
109 |         let mut data = inner.lock().unwrap();
    |             -------- binding `data` declared here
...
121 |         data.broadcast.listen()
    |         ^^^^-------------------
    |         |
    |         borrowed value does not live long enough
    |         argument requires that `data` is borrowed for `'static`
122 |     }
    |     - `data` dropped here while still borrowed
```

The use<...> pattern was introduced in rust 1.82.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2025-03-19 12:19:33 +01:00
Maximiliano Sandoval
51c3a31115 mark blocks inside unsafe fns unsafe
In edition 2024 unsafe code inside unsafe functions has to be explicitly
marked as such.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2025-03-19 12:19:19 +01:00
Maximiliano Sandoval
9be42ea5ad daemon: set_var is now unsafe
In edition 2024 set_var is unsafe.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2025-03-19 12:19:19 +01:00
Maximiliano Sandoval
abd07ffcff mark extern C blocks as unsafe
This is required in edition 2024.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2025-03-19 12:19:19 +01:00
Gabriel Goller
a75b97da76 log, rest-server: worker_task: add log_unfiltered
To write result message manually, bypassing tracing.

The workertasks currently get their status from parsing the log
messages in the task-log file. The problem is that if these messages are
filtered – which is now possible using the PBS_LOG env variable – some
workertasks will end up with a "stopped: unknown" status. This is not
desirable so write the message manually to the workertask file and
bypass tracing.

This way we are guaranteed that, regardless of the max logging level the
user sets, the final message (and status) is written.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2025-03-19 12:19:02 +01:00
Wolfgang Bumiller
c99308ecfc log: factor out NoWorkerTask filter
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-19 11:52:08 +01:00
Wolfgang Bumiller
6bdd07075d log: fix doctests
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-19 11:51:53 +01:00
Gabriel Goller
656fedb0c4 log: add layer for pve workertasks in perlmod crates
Add a layer that outputs messages to stderr in a specific format. In
PVE, stderr is rerouted to the tasklog if the we are within a
workertask. Therefore, ensure the stderr output is formatted
appropriately.

Reported-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2025-03-19 09:52:35 +01:00
Gabriel Goller
f6269b800d log: introduce logging builder
Add a builder-like struct to compose a tracing logger using different
layers. Instead of having an init function per product/binary or
super-specific init functions that describe the logger, have a dynamic
builder. The builder improves the usability and makes the logging
initialization more self-explaining.

Suggested-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2025-03-19 09:52:35 +01:00
Shannon Sterz
3e382fd29c auth-api: set content type header for the new HttpOnly ticket endpoint
otherwise some clients might struggle to interpret the body correctly

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-14 09:28:11 +01:00
Fabian Grünbichler
b82e51f15a bump proxmox-router to 3.1.1-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2025-03-13 13:58:50 +01:00
Fabian Grünbichler
e4bc435beb env_logger: bump to 0.11
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2025-03-13 13:55:29 +01:00
Wolfgang Bumiller
f32f48b119 router: cli: avoid unnecessary clones/allocation
The `new_args` Vec is directly passed to the other Vec's `.extend()`,
which takes an `IntoIterator` consuming it, so just pass the
intermediate `Iterator`.

The `rest` Vec owns its strings and we don't need it afterwards, so
similarly, we can consume it via `.extend()` instead of a manual
push(s.clone()) loop.

The .truncate(0) can just be .clear() - they are equivalent according
to their documentation.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-13 11:46:52 +01:00
586535e456 Merge remote-tracking branch 'upstream/master' 2025-03-07 14:06:22 +03:00
Shannon Sterz
00c75c734d tree-wide: fix private intra doc links
a previous commit fixed up all intra doc links that were present on
public apis, this also fixes the links for private members.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-07 11:35:03 +01:00
9e27dfa8a5 Use 'alt-linux' feature by default 2025-03-07 05:44:25 +03:00
Shannon Sterz
a9a7bbdabc auth-api: fix intra doc link for Empty
`Display` isn't used directly anymore, so fix up the intra doc link
here again.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 16:35:51 +01:00
Wolfgang Bumiller
5c7b1ab4ab tfa, auth-api: simplify and restyle Display implementation
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
4836cb5334 tree-wide: fix intra doc links
this fixes intra document links or rephrases the documentation in a
more appropriate way to remove all `broken_intra_doc_links` warnings.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
ccb34b33e2 api-macro: re-order ObjectSchema fields to be sorted
this panics when running `cargo test` otherwise, as the api macro
requires fields in `ObjectSchema`s to be sorted now.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
f0b23def30 router: fix nested doc test cases to match inteded output
commit 68b13965 (router: docs: add horizontal line before nested
command docs) broke the nested command group test case. this commit
adapts the expected output accordingly.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
4fc074b4ba network-api: ignore clippy lint about upper case acronyms
while the lint is correct about how these enum members should be
capitalized, the enum is marked as `pub` and all users of it would
need to adapt. so ignore the lint for now [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
730f908458 apt: ignore clippy lint about new having to return Self
while this is a reasonable convention to follow, in this case the new
function is part of a public trait and changing the signature would
force all users to adapt. so ignore the lint for now [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
8fc324ee73 apt: ignore clippy lint about using a slice reference instead of &Vec
while the function would be more useful as pointed out by the clippy
lint, it i currently `pub` and users of the function would need to
adapt to the change here. so ignore the lint for now.

[1]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
59898d0177 rest-server/router: ignore type complexity clippy lint
the `type_complexity` clippy lint [1] is intended to make the code
more legible in most cases. however, the lint triggers on a member of
a private enum, an example minimal rest server and a private static
variable here. so the benefits of declaring a new type that would
encapsulate this complexity is minimal. hence, ignore the warnings for
now.

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
f9dd576783 router: ignore clippy lint missing_transmute_annotations
the `ApiHandler`'s `PartialEq` implementation makes heavy use of
`transmute`. clippy wants the types to be explicitly stated here and
not inferred, to avoid potential undefined behaviour if an unexpected
type is inferred. however, the types that would be inferred here are
so complex, that the code would become illegible anyway, so ignore
this lint for now.

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#missing_transmute_annotations

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
dcc6eb9918 shared-memory: specify generic types for transmute
this annotates a `transmute` call with proper types to avoid possible
undefined behaviour, as suggested by clippy [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#missing_transmute_annotations

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
ab2d5c9777 acme/auth-api: add Default for types with un-parameterized new()
this fixes a clippy lint for types that have a `new()` function that
has no parameters [1]. this should allow using these types with
functions such as `unwrap_or_default()`.

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:25:24 +01:00
Shannon Sterz
efc8556c27 auth-api/tfa: prefer Display over ToString/an inherent function
this fixes two clippy lints that check if either `ToString` or an
inherent `to_string()` function is implement [1, 2]. `Display`
provides `ToString` for free and, thus, is preferable.

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#/inherent_to_string
[2]:
https://rust-lang.github.io/rust-clippy/master/index.html#to_string_trait_impl

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:17:35 +01:00
Shannon Sterz
d95a4f25e0 router: allow from_str on Confirmation that is not for FromStr
while usually this would improve ergonomics, in this case it isn't
clear whether all uses of `FromStr` would be considered valid here.
renaming the function would also make the type more confusing to use
as `from_str_with_default` also exists, so keep this for consistency.
this ignores a clippy lint [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:16:04 +01:00
Shannon Sterz
92ecc301b6 sys: add truncate option to OpenOptions in test case
this resolves a clippy lint that checks for uses of `create()` without
`truncate()` [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_open_options

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:15:43 +01:00
Shannon Sterz
946d95cfcd access-control/tfa: use ? instead of unnecessary match statements
this makes the code more concise and legible. fixes a clippy lint [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#question_mark

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:15:22 +01:00
Shannon Sterz
a318fcedd3 tfa: remove needless as_bytes call
len() already returns the length in bytes, no need to call `as_bytes`
first. this fixes a clippy lint [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#needless_as_bytes

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:15:11 +01:00
Shannon Sterz
0b9c1485c0 tfa: don't use block in conditions
this fixes a clippy style lint that does not allow blocks in
conditionals. moving the block out and the result into a temporary
variable should make this more legible [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_conditions

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:14:19 +01:00
Shannon Sterz
d980c2229b tree-wide: remove clone calls on types that are Copy
this resolves a clippy lint that checks that `clone()` isn't called on
`Copy` types as that is unnecessary [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:12:16 +01:00
Shannon Sterz
2c07729ff3 tree-wide: add parantheses to clarify precedence
this resolves a clippy lint that aims to improve legibility for people
unaware of rust's precendence rules [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#precedence

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:11:00 +01:00
Shannon Sterz
2134657529 io: clippy fix: replace map() followed by any() with just any()
this fixes a clippy lint that complains about map invocations followed
by any invocations that are just checking for identity as this can be
replaced by just the any invocation alone [1].

[1]:
https://rust-lang.github.io/rust-clippy/master/index.html#map_all_any_identity

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-06 15:03:10 +01:00
Wolfgang Bumiller
b5e238613e auth-api: bump to 0.4.8-1
While *technically* breaking as it changes the method signature for
the `create_ticket` call to use a struct for its parameters, this is
only (supposed to be) used via its `CREATE_TICKET_API_METHOD` handler
to be passed to a router. Direct use of this does not make sense.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-04 16:03:17 +01:00
Wolfgang Bumiller
e73bc1509d rest-server: bump to 0.8.7-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-04 15:54:19 +01:00
Wolfgang Bumiller
3f6345021c router: bump to 3.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-04 15:54:19 +01:00
Wolfgang Bumiller
fda6fc9def client: bump to 0.5.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-04 15:54:19 +01:00
Wolfgang Bumiller
86336f6c88 login: bump to 0.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-04 15:54:19 +01:00
Wolfgang Bumiller
986b465d48 time: bump to 2.0.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-03-04 15:54:19 +01:00
Shannon Sterz
0c9ed7daa4 client: specify cookie names for authentication headers where possible
if the client knows the auth cookie's name, it now passes it on to the
relevant parts of `proxmox-login` so that the ticket is send the
correct cookie

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
bedf92c0df client: add compatibility with HttpOnly cookies
this should make it possible to use the proxmox-client crate outside
of context where HttpOnly cookies are handled for us. if a cookie
name is provided to a client, it tries to find a corresponding
`Set-Cookie` header in the login response and passes tries to parse
it as a ticket. that ticket is then passed on to proxmox-login like
any regular ticket.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
7dee2d7016 login: add functions to specify full cookie names
previously the name in which the ticket was send was derived by the
product abbreviation in the ticket itself. the assumption was that
authentication cookies would always have a name like this:
`<PRODUCT_ABBREVIATION>AuthCookie`.

this commit adds helpers that allow specifying the cookie's name by
users of this crate.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
f199b02a7a login: add helper to check whether a ticket is just informational
tickets that end in `::ticketinfo` are not properly signed and just
include information such as the timestamp when the ticket was created.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
867e890141 login: add TicketResult::HttpOnly member
this allows client to be aware that the ticket they manage is
informational only and that the real ticket should have been set via
a HttpOnly cookie.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
b28e98ca99 login: add helpers to pass cookie values when parsing login responses
depending on the context a client may or may not have access to
HttpOnly cookies. this change allows them to pass such values to
`proxmox-login` to take them into account when parsing login
responses.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
f137b5e528 login: make password optional when creating Login requests
in certain context (for example, the browser), no password needs to be
provided when using HttpOnly cookies as they are handle by said
context. so make renewing ticket with password optional and add a new
helper function that does not require a password.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
9c6d6b8d2a login: add optional field for ticket_info and make password optional
tickets created through the new HttpOnly ticket endpoint won't return
a ticket in the password field. so this field will be left empty.
hence make it optional.

the endpoint does return a ticket_info parameter, though, that
includes the information when a ticket needs to be refreshed. so add
a new optional field for that too.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
1b9def4736 auth-api: add logout method
adds a new endpoint that is useful when dealing with HttpOnly cookies
that cannot be removed by client-side javascript (and by extension
wasm) code. the logout handle simply removes the cookie that is used
for storing the current ticket. this works the same way as it does in
the front-end: by setting an expired cookie with the same name.

as cookies are now prefixed with `__Host-` by default, the cookie here
also needs to be `Secure` and have the same `Path` to not be rejected
by the browser before it can remove the old cookie.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
6a7f631709 auth-api: make regular ticket endpoint use the new types and handler
so we can re-use more code between the different ticket endpoints

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
8405154c6d auth-api: add endpoint for issuing tickets as HttpOnly tickets
this adds a new endpoint for requesting tickets. instead of returning
the ticket in the responses body, the ticket is set as a HttpOnly
cookie. this has a couple of advantages:

- the cookie cannot be stolen if an attacker downgrades the connection
  to http and injects malicious javascript (`HttpOnly`)
- we don't need to rely on the client to make sure that the cookie is
  only send in the appropriate context and only over https
  connections (`Secure`, `SameSite`).
- the cookie cannot be overwritten by other subdomains, insecure
  connections etc. (the default is to prefix them with `__Host-`)

this follows the best practice guide for secure cookies from MDN
[1]. we also set the cookies to expire when the ticket would so that
the browser removes the cookie once the ticket isn't valid anymore.

the endpoint still returns a ticket that only contains the
informational portions of the ticket but not a valid signature. this
is helpful to let clients know when to refresh the ticket by querying
this endpoint again. it still protects the cookie, though, as it
isn't a valid ticket by itself.

[1]: https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Cookies

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
acaffffaf9 auth-api: introduce new CreateTicket and CreateTickeReponse api types
these types are used for creating a ticket and responding to a new
ticket request.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
6f61b991a0 auth-api: check for new prefixed cookies as well
this makes sure that newly generated cookies that are prefixed with,
for example, `__Host-`, for security purposes, are correctly picked
up on. otherwise, the new cookies would not be able to yield proper
authentication.

currently this still falls back to insecure non-prefixed cookies. we
should deprecate them in the long-term and remove this fallback.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
b598e03287 auth-api: extend AuthContext with prefixed cookie name
this adds the function `prefixed_auth_cookie_name` to the
`AuthContext` trait. said function can be used by users of this crate
to modify the expected prefix of the auth cookie. most products
should be able to use the default of `__Host-` though, so this also
adds a default implementation.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
1f58e40f3f router/rest-server: add new AsyncHttpBodyParameters api handler type
this allows us to write api handlers that have access to a request's
headers and to create a low level response while being able to also
specify the parameter in the request's body. this is useful for
endpoints that should not use url parameters, but still need to
access/set specific headers.

previously, `AsyncHttp` did not allow for parameters that were marked
as non-optional to be passed in the body of a request.

as a side-effect, the body is parsed by the rest server to check for
parameters and consumed. so it cannot be passed on by the handler.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
b1ed231ed2 rest-server: borrow parts parameter in get_request_parameter
this function does not require ownership of the parts parameter, so we
can simply borrow it. this way we can pass the parameter on to a
handler that consumes them even when using `get_request_parameter`

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
Shannon Sterz
316fe71133 time: add new epoch_to_http_date helper
this makes it easy to generate RFC9110 preferred HTTP Dates.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2025-03-04 15:53:29 +01:00
b6d1410151 Better add_repository_handle, proper GostCrypto 2025-02-23 08:59:23 +03:00
9c7993f1b4 Merge remote-tracking branch 'upstream/master' 2025-02-23 04:45:35 +03:00
d8128614d3 (ALT) Adapted proxmox-apt tests 2025-02-23 04:28:46 +03:00
93dd6ea520 (ALT) Proper default repo's checks and creation 2025-02-23 04:22:43 +03:00
6339b60fc6 (ALT) Better apt repos list checks 2025-02-22 07:54:58 +03:00
Dietmar Maurer
68b1396553 router: docs: add horizontal line before nested command docs
Lines before commanmd groups are missing (i.e. see proxmox-backup-client command
syntax docs)

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2025-02-21 10:05:46 +01:00
Christian Ebner
29c1ceefd5 pbs api types: relax influx organization and bucket length
Relax the length limit for organization and bucket name for InfluxDB
metric servers in Proxmox Backup Server.

Commit 57fa20406 ("pbs-api-types: add metrics api types") introduced
the api schema definition for InfluxDB metric servers, limiting the
name of the organization and the name of the bucket to a length of 3,
most likely as a result of reusing the same values as for the
corresponding config id schema restrictions.

This is however not enforced by the InfluxDB REST api [0, 1] and
stricter than what is defined in Proxmox VE [2].

Reported in the community forum [3].

[0] https://docs.influxdata.com/influxdb/v2/api/#operation/PostOrgs
[1] https://docs.influxdata.com/influxdb/v2/api/#operation/PostBuckets
[2] https://git.proxmox.com/?p=pve-manager.git;a=blob;f=PVE/Status/InfluxDB.pm;h=13a96711e766e2f1ea71424ed6d9d8ec8450504c;hb=HEAD#l24
[3] https://forum.proxmox.com/threads/162521/

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2025-02-20 16:11:50 +01:00
Wolfgang Bumiller
052d84ef37 schema: drop useless doc comment
(it's on the wrong function, and also it's wrong - std's str
comparison forwards to the byte comparison anyway since this is fine
for utf-8 if we're not doing anything natural-language-aware...)

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-02-20 10:07:08 +01:00
Wolfgang Bumiller
ce54bd1b11 schema: verify object fields/variants are sorted and unique
This verifies *at compile time* that the properties and variants of
ObjectSchemas and OneOfSchemas are sorted.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-02-19 13:02:18 +01:00
Wolfgang Bumiller
59a9ddbf06 schema: add const-fn helper to compare strings
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-02-19 13:02:18 +01:00
Wolfgang Bumiller
e3286d3758 notify: bump d/control
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-02-19 13:02:00 +01:00
Wolfgang Bumiller
ad3657011e notify: bump to 0.5.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-02-19 13:00:56 +01:00
Wolfgang Bumiller
ef54afe65f api-macro: bump to 1.3.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-02-19 12:55:38 +01:00
Wolfgang Bumiller
a36f0f3bf9 api-macro: enums: sort OneOf variants
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-02-19 12:53:46 +01:00
Wolfgang Bumiller
d38ce91384 notify: fix dummy user schema property ordering
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-02-19 12:53:46 +01:00
Lukas Wagner
0f1b84e93c sys: fs: derive Copy for CreateOptions
Pretty much all functions accepting `CreateOptions` take a value and not
a reference, so I've found myself using `.clone()` quite often in code
I've written recently.
The struct is only 24 bytes large (verified by a
`std::mem::size_of::<CreateOptions>()`), so it should be absolutely fine
to just derive Copy for it.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2025-01-29 18:09:24 +01:00
Thomas Lamprecht
31ae72ba6a schema: property ReST docs: correctly separate nested list from parent
This fixes the reStructuredText (ReST) output for more complex formats
where we got, for example, a definition list of all properties of a
(section) configuration with one or more properties then having a
sub-format that is also rendered as a list.

Such nested lists are possible in ReST but need to be separated by a
blank line [0].

Without this we got quite a few warnings/errors from the Sphinx/ReST
buildsystem looking like:

> config/datastore/config.rst:19: ERROR: Unexpected indentation.
> config/datastore/config.rst:20: WARNING: Block quote ends without a blank line; unexpected unindent.

[0]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#lists-and-quote-like-blocks

Reported-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-01-28 14:25:21 +01:00
Thomas Lamprecht
c0641c6e13 schema: use inline named params in format macro calls
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-01-28 14:25:21 +01:00
Wolfgang Bumiller
703c2dee04 api-macro: mark parameter defaults as #[allow(dead_code)]
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-28 10:56:28 +01:00
Laurențiu Leahu-Vlăducu
922c605d69 rest-server: Improved panic errors with formatted strings
Improved errors when panics occur and the panic message is a
formatted (not static) string. This worked already for &str literals,
but not for Strings.

Downcasting to both &str and String is also done by the Rust Standard
Library in the default panic handler. See:
b605c65b6e/library/std/src/panicking.rs (L777)

Signed-off-by: Laurențiu Leahu-Vlăducu <l.leahu-vladucu@proxmox.com>
2025-01-27 15:12:59 +01:00
Dietmar Maurer
f639695c87 pbs-api-types: add debian/control to git repository
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2025-01-22 12:33:33 +01:00
Dietmar Maurer
57b0477657 pbs-api-type: add as workspace member with correct debian files
Also changed the version to 0.2.0

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2025-01-22 11:57:00 +01:00
Thomas Lamprecht
b7b00b5205 Merge remote-tracking branch 'prepare-pbs-api-types-for-move/prepare-pbs-api-types-for-move'
Move over the whole history of pbs-api-types from the proxmox-backup
git repo to the common workspace as we want to re-use that in PDM and
the yew UI components, and thus require a real source-code package for
this crate.

Choose this repo over proxmox-api-types to avoid the need to copy this
build-system over there, rather we want to also merge pve-api-types
from that repo into ours here in the future.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-01-22 11:55:09 +01:00
Thomas Lamprecht
0477adaf88 client: fix code-style issue when formatting error
Fixes a style issue introduced by b31ab119 ("client: improve api error
message (avoid duplicate status code)"), as one always should use
inline variables for format template strings even if there are some
that cannot be used, e.g. as some method needs to be called on them
like here. The reason for this is to reduce the amount of
free-standing "{}" and parameters as that reduces the need to manually
match what "{}" resolves to which expression. While it is not that bad
for only two format variables it's still not winning us anything if we
don't do it and breaks consistency with newer code style.

Noticed as that commit made the line overly long, causing rustfmt
changing the line.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-01-21 09:22:08 +01:00
Thomas Lamprecht
daff73bdd6 sys: use correct pointer type for mkdtemp return value
The libc function mkdtemp takes a C char pointer while we previously
cast our OSString buffer as i8 pointer, but that's not valid on
platforms like AArch64 (ARM), where char is equivalent with a u8.

Fix that by using the c_char type that was explicitly made to always
get the correct, platform-independent type for C chars when doing FFI.

This was reported by OJaksch on our Arch Linux User Repo (AUR) package
[0].

https://aur.archlinux.org/packages/proxmox-backup-client#comment-1006851

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2025-01-21 09:14:43 +01:00
Wolfgang Bumiller
5a7993beb0 update to proxmox-schema 4
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 13:03:42 +01:00
Wolfgang Bumiller
4be2392d59 time-api: bump to 0.1.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:54:28 +01:00
Wolfgang Bumiller
e0bdcda6e6 syslog-api: bump to 0.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:53:28 +01:00
Wolfgang Bumiller
27c50bd037 rrd-api-types: bump to 1.0.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:52:15 +01:00
Wolfgang Bumiller
975f29bd7f network-api: bump to 0.1.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:51:19 +01:00
Wolfgang Bumiller
ce206f0a96 dns-api: bump to 0.1.5-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:50:26 +01:00
Wolfgang Bumiller
891413ffb7 access-control: bump to 0.2.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:49:23 +01:00
Wolfgang Bumiller
f01f934963 auth-api: bump to 0.4.7-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:47:08 +01:00
Wolfgang Bumiller
0807248c15 acme-api: bump to 0.1.7-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:46:13 +01:00
Wolfgang Bumiller
7e25958623 tfa: bump to 5.0.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:43:41 +01:00
Wolfgang Bumiller
40f0a0bdd3 subscription: bump to 0.5.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:42:10 +01:00
Wolfgang Bumiller
c84376e2c8 apt: bump to 0.11.7-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:40:14 +01:00
Wolfgang Bumiller
5382b8ce13 apt-api-types: bump to 1.0.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:38:33 +01:00
Wolfgang Bumiller
ca92328569 api-macro: bump to 1.3.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:36:57 +01:00
Wolfgang Bumiller
59d1ca87e5 rrd: bump to 0.4.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:36:56 +01:00
Wolfgang Bumiller
106aa95ced rest-server: bump to 0.8.6-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:35:43 +01:00
Wolfgang Bumiller
698b6782cd router: bump to 3.0.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:35:43 +01:00
Wolfgang Bumiller
69298c8d20 acme: bump to 0.5.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:28:06 +01:00
Wolfgang Bumiller
ed74a6a8a2 simple-config: bump to 0.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:26:42 +01:00
Wolfgang Bumiller
2782c72747 config-digest: bump to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:25:33 +01:00
Wolfgang Bumiller
94b9f8db60 notify: bump to 0.5.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:25:31 +01:00
Wolfgang Bumiller
f2fefb7ffd section-config: bump to 2.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:20:00 +01:00
Wolfgang Bumiller
c7f4afb3fc human-byte: bump to 0.1.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:20:00 +01:00
Wolfgang Bumiller
e4f5179ae6 schema: bump to 4.0.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 12:07:49 +01:00
Wolfgang Bumiller
2e2edcf833 schema: add Schema::unwrap_*_schema_cloned
This way we can copy and modify a schema.

Eg. via the following ways:

    const FOO_SCHEMA: Schema = SOME_SCHEMA
        .unwrap_integer_schema_cloned()
        .description("Foo")
        .schema();

Note that for example there is currently no builder to set a
`default_key` for an `ObjectSchema` back to None, so one could do:

    const FOO_SCHEMA: Schema = const {
        let mut schema = SOME_SCHEMA.unwrap_object_schema_cloned();
        schema.default_key = None;
        schema.schema()
    };

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 11:56:57 +01:00
Wolfgang Bumiller
c040054827 schema: add 'description' builder methods to schema types
To cover the use case where we want to change only the description of
an existing type.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 11:07:08 +01:00
Wolfgang Bumiller
72c95c35bb schema: make schema types #[non_exhaustive]
This way extending them is not a breaking change anymore.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 10:56:58 +01:00
Wolfgang Bumiller
c794c42919 schema: drop deprecated code
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 10:56:58 +01:00
Wolfgang Bumiller
8d9cf97611 schema: doc: replace use of deprecated methods
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 10:56:58 +01:00
Wolfgang Bumiller
dc232f8bd4 schema: support PVE's "keyAlias" legacy property strings
Because the UI kept producing them...
And still does...

This is NOT meant to be used by anything other than generated legacy
code for PDM (pve-api-types) and only affects the serde based
deserializer. The "old" `schema.parse_property_string() -> Value`
method does not utilize this for now.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 10:56:58 +01:00
Wolfgang Bumiller
4378d44bcb notify: use builder for ObjectSchema
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-15 10:56:58 +01:00
Wolfgang Bumiller
3c34ef5573 section-config: use builder pattern for ObjectSchema
To prepare for a breaking change in proxmox-schema.
Since new fields in the schema constitute a breaking change, using the
builder methods will let this compile later as well.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-14 11:48:52 +01:00
Wolfgang Bumiller
c99a31c66f router: test: sort object schema properties
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-14 10:34:29 +01:00
Dominik Csapak
be57fb0122 schema: updater: add blanked implementation for PropertyString
so that we can have a property:

```
foo: Option<PropertyString<Bar>>,
```

within a struct that derives `Updater`

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2025-01-14 10:19:54 +01:00
Wolfgang Bumiller
8c5fa1c562 schema: fix pointer/length confusion in str_slice_to_range
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-13 15:22:40 +01:00
Wolfgang Bumiller
62e105a34e api-macro: bump to 1.3.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-09 14:21:53 +01:00
Wolfgang Bumiller
dce9102163 api-macro: add json_schema!() macro
This allows using the json schema notation to generate `Schema`
expressions.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-09 14:19:42 +01:00
Wolfgang Bumiller
619c290cf8 client: bump to 0.5.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-09 14:16:03 +01:00
Dietmar Maurer
b31ab119bb client: improve api error message (avoid duplicate status code)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2025-01-09 12:02:28 +01:00
Wolfgang Bumiller
ddc154e5cd notify: add missing tracing::error macro import
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2025-01-07 10:20:37 +01:00
Thomas Lamprecht
b74391cada daemon: bump version to 0.1.2-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-30 15:34:16 +01:00
Thomas Lamprecht
1db49cb269 daemon: try to remove existing unix socket in bind directly
We tried this unconditionally on start-up in the PDM for the priv. API
daemon, but we actually only want to clean-up on fresh bind, not on
restoring the FD on daemon reload. Otherwise the unprivileged daemon
cannot connect to the privileged one anymore after the latter got
reloaded.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-30 15:30:10 +01:00
Thomas Lamprecht
7648aabf42 daemon: output debug log when restoring FD passed through env
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-30 15:29:34 +01:00
Thomas Lamprecht
cc4ee60452 apt: bump version to 0.11.6-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-19 12:46:05 +01:00
Thomas Lamprecht
aba50ebc38 apt: bump version to 0.11.6-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-19 12:42:45 +01:00
Thomas Lamprecht
942186e2b7 apt: pdm uses separator for test repo
For consistency with it's other repositories, we might migrate other
products also to this schema with a future major release (and nginx
rewrite config for backward compatibility)

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-19 12:40:26 +01:00
ca7e4c9874 Merge remote-tracking branch 'upstream/master' 2024-12-09 21:17:41 +03:00
Gabriel Goller
df6b705f56 notify: migrate from log to tracing
Migrated from `log` to `tracing`. Imported `tracing` only as it has a
smaller footprint (and less dependencies) than `proxmox_log`.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-12-05 13:22:24 +01:00
Thomas Lamprecht
212249e009 sys: bump version to 0.6.5-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-05 08:32:47 +01:00
Thomas Lamprecht
87dd908530 client: run cargo fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-05 08:32:47 +01:00
Shannon Sterz
8eae5a8330 product-config: use inner mutability for PRODUCT_CONFIG
with edition 2024 `static mut` references will be disallowed [1]. the
recommended way to work around this is to use inner mutability, so use a
`OnceLock` for the `PRODUCT_CONFIG` as that should not change throughout
the run time of an application.

[1]:
https://doc.rust-lang.org/nightly/edition-guide/rust-2024/static-mut-references.html

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-12-04 09:54:12 +01:00
Shannon Sterz
115adf42bd acme-api: use inner mutability for ACME_ACME_CONFIG
in edition 2024 references to mutable static will be disallowed [1]. the
recommended way around this is to use types with inner mutability. so
use a `OnceLock` for `ACME_ACME_CONFIG` as the directoryfor ACME
configuration purposes shouldn't change throughout the run time of an
application.

[1]:
https://doc.rust-lang.org/nightly/edition-guide/rust-2024/static-mut-references.html

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-12-04 09:53:51 +01:00
Maximiliano Sandoval
b256ee391c apt: repositories: remove unnecessary if-let in iterator
Fixes the manual_flatten clippy lint:

```
warning: unnecessary `if let` since only the `Some` variant of the iterator element is used
  --> proxmox-apt/src/repositories/mod.rs:40:5
   |
40 |       for digest in digests.values() {
   |       ^             ---------------- help: try: `digests.values().copied().flatten()`
   |  _____|
   | |
41 | |         if let Some(digest) = digest {
42 | |             common_raw.extend_from_slice(&digest[..]);
43 | |         }
44 | |     }
   | |_____^
   |
help: ...and remove the `if let` statement in the for loop
  --> proxmox-apt/src/repositories/mod.rs:41:9
   |
41 | /         if let Some(digest) = digest {
42 | |             common_raw.extend_from_slice(&digest[..]);
43 | |         }
   | |_________^
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
   = note: `#[warn(clippy::manual_flatten)]` on by default
```

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
FG: use `into_values().flatten()` instead of `values().copied().flatten()
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-12-03 13:50:23 +01:00
Maximiliano Sandoval
34c66e1542 sys: systemd: remove empty line after outer attribute
Fixes the clippy lint:

```
warning: empty line after outer attribute
 --> proxmox-sys/src/systemd.rs:7:1
  |
7 | / #[allow(clippy::manual_range_contains)]
8 | |
  | |_
9 |   fn parse_hex_digit(d: u8) -> Result<u8, Error> {
  |   ---------------------------------------------- the attribute applies to this function
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
  = note: `#[warn(clippy::empty_line_after_outer_attr)]` on by default
  = help: if the empty line is unintentional remove it
```

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-12-03 13:44:05 +01:00
Maximiliano Sandoval
c8d49a7d38 docs: remove empty lines in docs
Fixes the clippy lints:

```
warning: empty line after doc comment
  --> proxmox-lang/src/lib.rs:33:1
   |
33 | / /// ```
34 | |
   | |_
35 |   #[macro_export]
36 |   macro_rules! try_block {
   |   ---------------------- the comment documents this macro
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_doc_comments
   = note: `#[warn(clippy::empty_line_after_doc_comments)]` on by default
   = help: if the empty line is unintentional remove it

warning: empty line after doc comment
   --> proxmox-router/src/cli/mod.rs:308:5
    |
308 | /     /// Can be used multiple times.
309 | |
    | |_
310 |       /// Finish the command line interface.
311 |       pub fn build(self) -> CommandLineInterface {
    |       ------------------------------------------ the comment documents this method
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_doc_comments
    = note: `#[warn(clippy::empty_line_after_doc_comments)]` on by default
    = help: if the empty line is unintentional remove it
help: if the documentation should include the empty line include it in the comment
    |
309 |     ///
    |
```

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-12-03 13:44:05 +01:00
Maximiliano Sandoval
3f62a4dacf remove unnecessary return statement
Fixes the clippy lint:

```
warning: unneeded `return` statement
  --> proxmox-time/src/week_days.rs:31:14
   |
31 |         _ => return Err(parse_error(text, "weekday")),
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
   = note: `#[warn(clippy::needless_return)]` on by default
help: remove `return`
   |
31 |         _ => Err(parse_error(text, "weekday")),
   |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-12-03 13:44:05 +01:00
Maximiliano Sandoval
3492a0cee5 elide lifetimes where possible
This is possible on newer rustc.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-12-03 13:44:05 +01:00
Maximiliano Sandoval
4cb3579786 router: parsing: docs: fix 'instead' typo
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-12-03 13:44:05 +01:00
Maximiliano Sandoval
10b15fb2ad router: parsing: docs: fix Records::from link
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-12-03 13:44:05 +01:00
Maximiliano Sandoval
1b5e8ee0dd apt: repositories: use if-let instead of match for Option
Fixes the single_match clippy lint:

```
warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
  --> proxmox-apt/src/repositories/mod.rs:41:9
   |
41 | /         match digest {
42 | |             Some(digest) => common_raw.extend_from_slice(&digest[..]),
43 | |             None => (),
44 | |         }
   | |_________^ help: try: `if let Some(digest) = digest { common_raw.extend_from_slice(&digest[..]) }`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_match
   = note: `#[warn(clippy::single_match)]` on by default
```

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-12-03 13:44:05 +01:00
Maximiliano Sandoval
90e4dc5f8f api: webhook: doc: add indentation to list item
```
warning: doc list item without indentation
   --> proxmox-notify/src/api/webhook.rs:131:5
    |
131 | ///   (`400 Bad request`)
    |     ^^
    |
    = help: if this is supposed to be its own paragraph, add a blank line
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
    = note: `#[warn(clippy::doc_lazy_continuation)]` on by default
help: indent this line
    |
131 | ///     (`400 Bad request`)
    |       ++
```

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-12-03 13:44:05 +01:00
Maximiliano Sandoval
8062e1abb3 apt: file: Use unwrap_or_default instead of match
Fixes the manual_unwrap_or_default clippy lint:

```
warning: match can be simplified with `.unwrap_or_default()`
   --> proxmox-apt/src/repositories/file.rs:369:30
    |
369 |               let mut origin = match repo.get_cached_origin(apt_lists_dir) {
    |  ______________________________^
370 | |                 Ok(option) => option,
371 | |                 Err(_) => None,
372 | |             };
    | |_____________^ help: replace it with: `repo.get_cached_origin(apt_lists_dir).unwrap_or_default()`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default
    = note: `#[warn(clippy::manual_unwrap_or_default)]` on by default
```

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-12-03 13:44:05 +01:00
Fabian Grünbichler
2f3e985b5f docs: escape <foo> in doc comments
warning: unclosed HTML tag `nodename`
   --> pbs-api-types/src/metrics.rs:224:5
    |
224 | /     /// Unique identifier for this metric object, for instance 'node/<nodename>'
225 | |     /// or 'qemu/<vmid>'.
    | |_________________________^
    |
    = note: `#[warn(rustdoc::invalid_html_tags)]` on by default

warning: unclosed HTML tag `vmid`
   --> pbs-api-types/src/metrics.rs:224:5
    |
224 | /     /// Unique identifier for this metric object, for instance 'node/<nodename>'
225 | |     /// or 'qemu/<vmid>'.
    | |_________________________^

warning: `pbs-api-types` (lib doc) generated 2 warnings

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-12-03 11:52:50 +01:00
cf85c7c277 Removed CheckInstall repo's proccessing 2024-12-02 20:44:07 +03:00
491814c1cd Merge remote-tracking branch 'upstream/master' 2024-12-02 20:25:20 +03:00
Thomas Lamprecht
9205e65d21 rest-server: bump version to 0.8.5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-02 18:15:11 +01:00
Dominik Csapak
787c2e03d7 sys: open directories with O_CLOEXEC
Factor out the open-flags to use for directories and add the CLOEXEC
flag to ensure that open FDs do not get passed to any child process.

A prominent cases where this can happen is the proxmox-daemon reload
code, which re-execs itself in a forked child-process and thus gets
all FDs passed.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Reviewed-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
 [ TL: expand doc-comment and reword commit message to point at actual
   thing this fixes (exec not daemon reload) ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-02 17:06:25 +01:00
Dominik Csapak
0547659e2c sys: fs: set CLOEXEC when creating temp files
In general we want all open files to have set CLOEXEC since our
reloading mechanism can basically fork at any moment and we don't want
newer daemons to carry around old file descriptors, especially lock
files.

Since `make_tmp_file` is called by many things (e.g. open_file_locked,
logrotate, rrd), set O_CLOEXEC with mkostemp.

This fixes issues with leftover file descriptors e.g. tape backups not
working because of lingering locks after a reload, or having deleted
rrd files open.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Reviewed-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-12-02 16:59:44 +01:00
Fabian Grünbichler
b2d31f075d rest-server: increase task index lock timeout to 15s
this lock can be quite contended, until the surrounding code is properly split
to reduce this contention it should help to give the worker task
creation/cleanup code a bit more breathing room.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-12-02 16:56:46 +01:00
Fabian Grünbichler
57c8242dc2 rest-server: close race window when updating worker task count
this mimics how the count is updated when spawning a new task - the lock scope
needs to cover the count update itself, else there's a race when multiple
worker's log their result at the same time..

Co-developed-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-12-02 16:54:52 +01:00
Fabian Grünbichler
4e51ac3527 rest-server: handle failure in worker task setup correctly
if setting up a new worker fails after it has been inserted into the
WORKER_TASK_LIST, we need to clean it up instead of bubbling up the error right
away, else we "leak" the worker task and it never finishes..

a worker task that never finishes will indefinitely block shutdown
of the rest server process, including the "old" process when reloading
the rest server.

this issue was found in the wild on a system with lock contention on the
file-based lock covering task index updating leading to lock acquiring
timeouts.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-12-02 16:54:18 +01:00
Shannon Sterz
6e600c74a8 notify: use proxmox-sendmail forward implementation
moves to depending on `proxmox-sendmail` for forwarding mails via
`sendmail` too.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
2024-12-02 15:38:48 +01:00
Shannon Sterz
043fec42f8 sendmail: add mail-forwarder feature
this moves the mail forwarding implementation from `proxmox-notify` into
`proxmox-sendmail` to cover more `sendmail` related use-cases in one
place.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
2024-12-02 15:38:48 +01:00
Shannon Sterz
57c84dbfb5 notify: switch sendmail endpoint over to new crate
use the new `proxmox-sendmail` crate instead of the bespoke
implementation in `proxmox-notify`.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
2024-12-02 15:38:48 +01:00
Shannon Sterz
a69e86dff1 sendmail: add sendmail crate
add the `proxmox-sendmail` crate that makes it easier to send mails via
the `sendmail` utility. features include:

- multipart/alternative support for html+plain text mails
- multipart/mixed support for mails with attachments
- automatic nesting of multipart/alternative and multipart/mixed parts
- masks multiple receivers by default, can be disabled
- encoding Subject, To, From, and attachment file names correctly
- adding an `Auto-Submitted` header to avoid triggering automated mails

also includes several tests to ensure that mails are formatted
correctly. debian packaging is also provided.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
 [ TL: update years in d/copyright ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-12-02 15:38:48 +01:00
Christian Ebner
00a1980903 api types: version: implement traits to allow for version comparison
Derive and implement the traits to allow comparison of two
`ApiVersion` instances for more direct and easy api version
comparisons. Further, add some basic test cases to reduce risk of
regressions.

This is useful for e.g. feature compatibility checks by comparing api
versions of remote instances.

Example comparison:
```
api_version >= ApiVersion::new(3, 3, 0)
```

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-12-02 15:27:37 +01:00
Christian Ebner
4308bb0ca9 api types: version: drop unused repoid field
The `ApiVersion` type was introduced in commit ba850a25
("api/api-types: refactor api endpoint version, add api types")
including the `repoid`, added for completeness when converting from
a pre-existing `ApiVersionInfo` instance, as returned by the
`version` api endpoint.

Drop the additional `repoid` field, since this is currently not used,
can be obtained fro the `ApiVersionInfo` as well and only hinders the
implementation for easy api version comparison.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-12-02 15:27:37 +01:00
Fabian Grünbichler
a36d3fdf29 datastore: extract nesting check into helper
and improve the variable namign while we are at it. this allows the check to be
re-used in other code paths, like when starting a garbage collection.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-11-27 15:25:37 +01:00
Thomas Lamprecht
8a1166be4b log: bump version to 0.2.7
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-27 13:31:57 +01:00
Gabriel Goller
cc79b2f08a log: ignore to_stdout parameter
This parameter causes the FileLogger to duplicate the log output to
stdout. This causes duplicate output on proxmox-backup-manager because
this is now handled by tracing. This should be removed completely in the
future.
In the worst case this will only result in missing log lines on stdout
(which is visible only on proxmox-backup-manager/client invocations
anyway).

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
 [ TL: add doc-comment to struct, note why it can be removed ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-27 13:29:14 +01:00
Thomas Lamprecht
7200cd7e23 time: bump version to 2.0.3-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-26 16:53:50 +01:00
Christian Ebner
17bc0ac616 time: also implement From<&TimeSpan> for f64
Extend the already present `From<TimeSpan> for f64` implementation to
allow using the reference as well. There is no need to take ownership
and consume the `TimeSpan` object for conversion.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-26 16:50:56 +01:00
Christian Ebner
548411808e time: fix typos in TimeSpan related docstring
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-26 16:50:56 +01:00
Dominik Csapak
7ae942f941 sync jobs: remove superfluous direction property
since the SyncJobConfig struct now contains a 'sync-direction' property, we can
omit the 'direction' property of the SyncJobStatus struct. This makes a
few adaptions in the ui necessary:

* use the correct field
* handle 'pull' as default (since we don't necessarily get a
  'sync-direction' in that case)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-26 16:02:22 +01:00
Christian Ebner
ce7239c24a api types: drop unused config type helpers for sync direction
Jobs for both sync directions are now stored using the same `sync`
config section type, so drop the outdated helpers.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-26 16:00:50 +01:00
Christian Ebner
d70f6d9f09 api: admin/config: introduce sync direction as job config parameter
Add the sync direction for the sync job as optional config parameter
and refrain from using the config section type for conditional
direction check, as they are now the same (see previous commit).

Use the configured sync job parameter instead of passing it to the
various methods as function parameter and only filter based on sync
direction if an optional api parameter to distingush/filter based on
direction is given.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-26 16:00:40 +01:00
Dominik Csapak
efc45db20c api: admin: sync: add direction to sync job status
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-26 14:54:33 +01:00
Thomas Lamprecht
f4868ff519 tree-wide: check in d/control meta changes for newer debcargo
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-26 14:44:33 +01:00
Thomas Lamprecht
42d1128d9d noitfy: bump version to 0.5.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-26 14:38:56 +01:00
Thomas Lamprecht
bfe099f4f1 workspace: update proxmox-http-client to 0.9.4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-26 14:37:43 +01:00
Lukas Wagner
dfe81b5db8 notify: webhook, gotify: set HTTP request timeout of 10s
By default, the sync client from proxmox-http (powered by ureq) does not
have any request timeout. To avoid blocking the current thread for a
prolonged period of time, we now make use of the previously added
`Client::new_with_timeout` function to create a new HTTP client with a
default timeout of 10 seconds.

In the long run it would be nicer to have a higher timeout here, say
60s, to cope with flaky and high-latency networks and potentially
overloaded targets. But for that we need to change the architecture of
how notifications are send out to ensure that now thread accepting
connections can be blocked.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
 [ TL: Change timeout from 5s to 10s as trade-off and expand commit
   message slightly with some reasoning for that still relatively
   short time value ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-26 14:12:57 +01:00
Thomas Lamprecht
6664b4150d http: bump version to 0.9.4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-26 14:00:42 +01:00
Lukas Wagner
a7c68a3166 http: sync client: add HTTP request timeout option
This commits adds the possibility to set a HTTP request timeout for the
sync client.

For now, I've opted to add this as a separate option than can be set via
a separate new_with_timeout method as compared to adding it to the HttpOptions
struct. While it of course would make a lot of sense to add it to the
latter, this would require adding support for request timeouts to the
async client as well. Some users of the async client handle request
timeouts externally via tokio::time::timeout, so these would need to
modified as well. I don't want to touch this at the moment,
so I've opted to introduce the timeout to the sync client only for now.
We can always revisit this at a later time and move the option to the
HttpOptions struct.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-11-26 13:57:11 +01:00
Christian Ebner
bfffbef9b2 api types: add missing conf to blob archive name mapping
Commit 0d66acd3 ("api types: introduce `BackupArchiveName` type")
introduced a dedicated archive name api type to add rust type
checking and bundle helpers to the api type. Since this, the backup
archive name to server archive name mapping is handled by its parser.

This however did not cover the `.conf` extension used for VM config
files. Add the missing `.conf` to `.conf.blob` to the match statement
and the test cases.

Fixes: 0d66acd3 ("api types: introduce `BackupArchiveName` type")
Reported-by: Stoiko Ivanov <s.ivanov@proxmox.com>
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-26 13:28:17 +01:00
Hannes Laimer
f1f8c65c70 api: types: add 'mount_status' to schema
... and deserialize with default if field is missing in data.

Reported-by: Aaron Lauterer <a.lauterer@proxmox.com>
Fixes: 35fb5d4f7f ("pbs-api-types: add mount_status field to DataStoreListItem")
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2024-11-26 13:08:04 +01:00
Hannes Laimer
2ed9c4bfca api: maintenance: allow setting of maintenance mode if 'unmounting'
So it is possible to reset it after a failed unmount, or abort an
unmount task by resetting it through the API.

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2024-11-26 13:07:53 +01:00
Lukas Wagner
622e43d5c3 notify: remove irritating 'html template not found' log message
The proxmox-notify crate can render notification text based on two
different templates, plaintext and html. The html template is at the
moment only used for email-based notifications. If we try to render
a html-formatted message but there is no html template, we try to
fall back to the plaintext template and wrap the rendered message
in <pre> tags.
As a preparation for user-supplied/overridden templates, I added a log
message "html template not found, falling back to plaintext ..." to
educate the user about this behavior.

In Proxmox Backup Server, we only ship plaintext templates at the
moment, meaning that this log message will be shown for every single
(email) notification that is sent out. This might be a bit confusing,
because the log message can be interpreted as an error, which it isn't.

This commit removes the log message completely for now. Once we add
support for user-overridable notification templates we could consider
adding it back it, but maybe phrased a bit differently, to avoid it
being interpreted as an error.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-11-26 12:08:49 +01:00
Lukas Wagner
549cb082ef notify: sendmail: code style improvements
No functional changes intended.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-11-25 23:13:46 +01:00
Lukas Wagner
296e2a2117 notify: sendmail: always send multi-part message
Even if we don't have an HTML template available, we always
send an HTML part (the plain text part wrapped in <pre>) to
improve rendering in certain mail clients. This means
we can simply message formatting, since we do not have to
distinguish between single-part and multi-part messages.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-11-25 23:13:46 +01:00
Lukas Wagner
b09ee57341 notify: move mail formatting to separate function
This way we can test this in a sane manner and refactor
safely.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-11-25 23:13:46 +01:00
Lukas Wagner
888ec2efe7 notify: sendmail: make mailfrom and author non-optional
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-11-25 23:13:46 +01:00
Hannes Laimer
35fb5d4f7f pbs-api-types: add mount_status field to DataStoreListItem
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2024-11-25 21:34:22 +01:00
Dietmar Maurer
fd1f8413f7 maintenance: add 'Unmount' maintenance type
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2024-11-25 21:34:22 +01:00
Hannes Laimer
6134a73b1e maintenance: make is_offline more generic
... and add MaintenanceType::Delete to it. We also want to clear any
cach entries if we are deleting the datastore, not just if it is marked
as offline.

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2024-11-25 21:34:22 +01:00
Hannes Laimer
d291f67236 pbs-api-types: add backing-device to DataStoreConfig
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2024-11-25 21:34:22 +01:00
Thomas Lamprecht
6fed7301ea rrd: bump version to 0.4.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-25 18:42:35 +01:00
Thomas Lamprecht
993e1fc878 rrd: selective code style clean-up
Selective because there are quite a few more such old-style format
strings, but I had those already adapted and currently do not have
time do clean-up tree-wide, but it's fine to change this
incrementally.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-25 17:46:44 +01:00
Thomas Lamprecht
08cee13c03 rrd: do not log tree info-level messages on applying journal
That's rather excessive and has not much value for users. So degrade
two of the messages to debug-level.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-25 17:43:56 +01:00
Thomas Lamprecht
9f135cf16e time: run cargo fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-25 17:20:01 +01:00
Thomas Lamprecht
cc85a72391 rest-server: bump version to 0.8.4-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-25 17:16:16 +01:00
Gabriel Goller
34e307461b rest-server: add custom handlebars escape fn
Add a custom handlebars escape function. It's nearly identical to the
default `html_escape` fn [0], but it does not escape the '='. This is
needed to support base64 encoded values.

[0]: https://docs.rs/handlebars/latest/handlebars/fn.html_escape.html

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
 [ TL: use full width for comment ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-25 16:34:30 +01:00
Shannon Sterz
b51b0be153 api: enforce minimum character limit of 8 on new passwords
we already have two different password schemas, `PBS_PASSWORD_SCHEMA`
being the stricter one, which ensures a minimum length of new
passwords. however, this wasn't used on the change password endpoint
before, so add it there too. this is also in-line with NIST's latest
recommendations [1].

[1]: https://pages.nist.gov/800-63-4/sp800-63b.html#passwordver

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-11-25 15:51:47 +01:00
Fabian Grünbichler
6e3c5afce5 api types: replace PathPatterns with Vec<PathPattern>
PathPatterns is hard to distinguish from PathPattern, so would need to be
renamed anyway.. but there isn't really a reason to define a separate API type
just for this.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-11-25 12:28:40 +01:00
Christian Ebner
85256a6b6c api-types: implement dedicated api type for match patterns
Introduces a dedicated api type `PathPattern` and the corresponding
format and input validation schema. Further, add a `PathPatterns`
type for collections of path patterns and implement required traits
to be able to replace currently defined api parameters.

In preparation for using this common api type for all api endpoints
exposing a match pattern parameter.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-25 11:57:07 +01:00
Gabriel Goller
32969b47e1 fix #3786: api: add resync-corrupt option to sync jobs
This option allows us to "fix" corrupt snapshots (and/or their chunks)
by pulling them from another remote. When traversing the remote
snapshots, we check if it exists locally, and if it is, we check if the
last verification of it failed. If the local snapshot is broken and the
`resync-corrupt` option is turned on, we pull in the remote snapshot,
overwriting the local one.

This is very useful and has been requested a lot, as there is currently
no way to "fix" corrupt chunks/snapshots even if the user has a healthy
version of it on their offsite instance.

Originally-by: Shannon Sterz <s.sterz@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
Reviewed-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-11-25 10:53:26 +01:00
Fabian Grünbichler
916c46905b api types: extend backup archive name parsing tests
and also test the error triggered by a directory path being passed in.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-11-22 13:47:25 +01:00
Christian Ebner
5a22076e67 api types: add unit tests for backup archive name parsing
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-22 13:47:25 +01:00
Christian Ebner
0d66acd390 api types: introduce BackupArchiveName type
Introduces a dedicated wrapper type to be used for backup archive
names instead of plain strings and associated helper methods for
archive type checks and archive name mappings.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>

FG: use LazyLock for constant archive names reduces churn, and saves some
allocations

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-11-22 13:46:35 +01:00
Christian Ebner
a2773ddd79 datastore: move ArchiveType to api types
Moving the `ArchiveType` to avoid crate dependencies on
`pbs-datastore`.

In preparation for introducing a dedicated `BackupArchiveName` api
type, allowing to set the corresponding archive type variant when
parsing the archive name based on it's filename.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-22 11:45:43 +01:00
Fabian Grünbichler
fda1f99479 version: remove named features
and use version comparison for the push code that previously used it.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-11-21 11:53:49 +01:00
Christian Ebner
2f4c9f784e api types/config: add sync-push config type for push sync jobs
In order for sync jobs to be either pull or push jobs, allow to
configure the direction of the job.

Adds an additional config type `sync-push` to the sync job config, to
clearly distinguish sync jobs configured in pull and in push
direction and defines and implements the required `SyncDirection` api
type.

This approach was chosen in order to limit possible misconfiguration,
as unintentionally switching the sync direction could potentially
delete still required snapshots.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-21 10:14:53 +01:00
Christian Ebner
ba850a25a3 api/api-types: refactor api endpoint version, add api types
Add a dedicated api type for the `version` api endpoint and helper
methods for supported feature comparison.
This will be used to detect api incompatibility of older hosts, not
supporting some features.

Use the new api type to refactor the version endpoint and set it as
return type.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-21 10:14:53 +01:00
Christian Ebner
9aaad591c6 api types: implement api type for BackupGroupDeleteStats
Make the `BackupGroupDeleteStats` exposable via the API by implementing
the ApiTypes trait via the api macro invocation and add an additional
field to account for the number of deleted groups.
Further, add a method to add up the statistics.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-21 10:14:53 +01:00
Christian Ebner
62270f8fef datastore: move BackupGroupDeleteStats to api types
In preparation for the delete stats to be exposed as return type to
the backup group delete api endpoint.

Also, rename the private field `unremoved_protected` to a better
fitting `protected_snapshots` to be in line with the method names.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-21 10:14:53 +01:00
Christian Ebner
92b652935b api types: define remote permissions and roles for push sync
Adding the privileges to allow backup, namespace creation and prune
on remote targets, to be used for sync jobs in push direction.

Also adds dedicated roles setting the required privileges.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-21 10:14:53 +01:00
Christian Ebner
8614be4ceb api types: implement remote acl path method for sync job
Add `remote_acl_path` method which generates the acl path from the sync
job configuration. This helper allows to easily generate the acl path
from a given sync job config for privilege checks.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-21 10:14:53 +01:00
Christian Ebner
48809ab0db api types: add remote acl path method for BackupNamespace
Add a `remote_acl_path` helper method for creating acl paths for
remote namespaces, to be used by the priv checks on remote datastore
namespaces for e.g. the sync job in push direction.

Factor out the common path extension into a dedicated method.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-11-21 10:14:53 +01:00
Wolfgang Bumiller
072ca695f5 README: describe [patch.crates-io] and sysext workflow
For how to work on the crates in this workspace while actually working
on a separate project without having to constantly reinstall `.deb`
files.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-11-19 14:08:34 +01:00
Wolfgang Bumiller
2f25debee6 buildsys: clean old sysext dir before installing
So version bumps don't getted mixed into previous builds.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-11-19 13:58:16 +01:00
Wolfgang Bumiller
96e76d7f72 client: use correct error for protocol errors
The 'Anyhow' error is not useful and meant for throw-away errors which
cannot be dealt with anyway, and we'd like to be able to tell apart
network problems from actual HTTP responses, so that we can
potentially try a different node in a cluster connection.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-11-19 13:41:09 +01:00
Thomas Lamprecht
c01318d966 log: bump version to 0.2.6
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-19 11:33:11 +01:00
Dietmar Maurer
7bffb9fe92 config: factor out method to get the absolute datastore path
removable datastores will have a PBS-managed mountpoint as path, direct
access to the field needs to be replaced with a helper that can account
for this.

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2024-11-17 19:57:33 +01:00
Thomas Lamprecht
1e7c0fc3ac rest-server: bump version to 0.8.3-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-15 11:28:04 +01:00
Thomas Lamprecht
9529f730e0 rest-server: drop log intended for debugging again
I considered keeping it as log::trace level, but IMO that's just not
worth it, as just the peek_len is not giving one much more and can
also be basically also gathered through strace.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-15 10:29:33 +01:00
Thomas Lamprecht
1539bc1ce3 rest-server: bump version to 0.8.2-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-14 14:39:19 +01:00
Dominik Csapak
dc9531d302 fix #5868: rest-server: handshake detection: avoid infinite loop on connections abort
When a connection is closed by the client before we have enough data
to determine if it contains a TLS Handshake or not, the socket stays
in a readable state.
While we setup a tokio backed timeout of 10s for the connection
build-up here, this timeout does not trigger on said early connection
abort from the client side, causing then the async_io loop to
endlessly loop around peeking into the client, which always returns
the last available bytes before the connection was closed. This in
turn causes 100% CPU usage for one of the PBS threads.
The timeout not triggering is rather odd, and does indicate some
potential for further improvement in tokio itself, but our
questionable use of the WouldBlock error does violate the API
contract, so this is not a clear cut.

Such an early connection abort is often triggered by monitoring
solutions, which use it to relatively cheaply check if TCP on a port
still works as "is service up" heuristic.

To fix this, save the amount of bytes peek returned and if they did
not change between invocations of the callback, we can assume that the
connection was closed and thus exit the connection attempt with an
error.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
 [ TL: reword commit message and change error to ConnectionAborted ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-14 14:31:47 +01:00
Thomas Lamprecht
f22fae3852 apt: bump version to 0.11.5-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-11 21:10:23 +01:00
Thomas Lamprecht
fd48033644 apt: add Ceph Squid to standard repos for PVE
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-11 21:08:10 +01:00
Thomas Lamprecht
2cc7eadb45 notify: bump version to 0.5.0-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-10 18:58:59 +01:00
Lukas Wagner
64943d0a3c notify: add api for webhook targets
All in all pretty similar to other endpoint APIs.
One thing worth noting is how secrets are handled. We never ever
return the values of previously stored secrets in get_endpoint(s)
calls, but only a list of the names of all secrets. This is needed
to build the UI, where we display all secrets that were set before in
a table.

For update calls, one is supposed to send all secrets that should be
kept and updated. If the value should be updated, the name and value
is expected, and if the current value should preseved, only the name
is sent. If a secret's name is not present in the updater, it will be
dropped. If 'secret' is present in the 'delete' array, all secrets
will be dropped, apart from those which are also set/preserved in the
same update call.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-By: Stefan Hanreich <s.hanreich@proxmox.com>
2024-11-10 18:55:11 +01:00
Lukas Wagner
16260195b9 notify: implement webhook targets
This target type allows users to perform HTTP requests to arbitrary
third party (notification) services, for instance
ntfy.sh/Discord/Slack.

The configuration for these endpoints allows one to freely configure
the URL, HTTP Method, headers and body. The URL, header values and
body support handlebars templating to inject notification text,
metadata and secrets. Secrets are stored in the protected
configuration file (e.g. /etc/pve/priv/notification.cfg) as key value
pairs, allowing users to protect sensitive tokens/passwords.
Secrets are accessible in handlebar templating via the secrets.*
namespace, e.g. if there is a secret named 'token', a body
could contain '{{ secrets.token }}' to inject the token into the
payload.

A couple of handlebars helpers are also provided:
  - url-encoding (useful for templating in URLs)
  - escape (escape any control characters in strings)
  - json (print a property as json)

In the configuration, the body, header values and secret values
are stored in base64 encoding so that we can store any string we want.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-By: Stefan Hanreich <s.hanreich@proxmox.com>
2024-11-10 18:55:11 +01:00
Lukas Wagner
0517d7b94e notify: renderer: adapt to changes in proxmox-time
A recent commit [1] changed the `Display` implementation of `TimeSpan` such
that minutes are now displayed as `20m` instead  of `20min`.
This commit adapts the tests for the notification template renderer
accordingly.

[1] 19129960 ("time: display minute/month such that it can be parsed again")

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-11-10 18:55:11 +01:00
Thomas Lamprecht
3817b3ba50 apt: bump version to 0.11.4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-10 18:53:26 +01:00
Thomas Lamprecht
272953d72d apt: add support for Ceph Squid repositories
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-10 18:48:03 +01:00
Thomas Lamprecht
6158d53697 apt-api-types: bump version to 1.0.2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-10 18:45:57 +01:00
Thomas Lamprecht
8e74afbca7 apt-api-types: add Ceph Squid as valid Proxmox APT repository handle
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-10 18:42:13 +01:00
Wolfgang Bumiller
0ea27021a2 rest-server: bump to 0.8.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-11-08 12:09:53 +01:00
Wolfgang Bumiller
a092b06d9f rest-server: shorten some format strings
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-11-08 12:09:53 +01:00
Gabriel Goller
d8fa495a50 rest-server: check permissions on proxy.key and proxy.pem files
To avoid openssl's unhelpful error messages when the proxy.key or
proxy.pem files have the wrong permissions, we open the files. To load
the private key, we can simply read from the file and pass it to the
`set_private_key` openssl function. Sadly such a function does not exist
for loading certificate chains, so we have to open and close the file
before calling the `set_certificate_chain_file` fn.

Motivation: https://forum.proxmox.com/threads/proxmox-backup-tailscale-proxmox-backup-proxy-service-wont-boot.153204

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-11-08 12:09:53 +01:00
Wolfgang Bumiller
db69867d4d rest-server: pass cipher suite/list to acceptor
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Reviewed-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-11-08 12:09:53 +01:00
Dietmar Maurer
35c60f652b subscription: use correct debian release name
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-11-07 13:35:00 +01:00
Dietmar Maurer
996c86bb32 subscription: bump version to 0.5.0-1
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-11-07 13:19:13 +01:00
d1141ab2ec fix url to apt-pkg-native crate
Signed-off-by: Алексей Шабалин <shaba@noreply.gitea.basealt.ru>
2024-11-07 14:57:14 +03:00
Dominik Csapak
ae55575f2a subscription: move most of the implmentation into impl feature
so we can use the types without having openssl, proxmox-sys, etc. as
dependencies.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-11-07 12:43:17 +01:00
58f1446429 Merge pull request 'ALT linux support for APT crates' (#1) from konevsa/proxmox:sisyphus into master
Reviewed-on: #1
2024-11-07 14:30:35 +03:00
9fea8ca0ce Tests fixed 2024-11-07 13:51:06 +03:00
5a02867d44 Added more URLs to account as good Classic repos 2024-11-07 12:58:38 +03:00
dbaa2df500 Small fix for cross-platforming of low-level C bindings 2024-11-07 02:15:55 +03:00
a6f5fd8c24 (ALT) Better parsing host from uri 2024-11-06 19:26:44 +03:00
bbceaa13d7 Test fixed according to correct suite check 2024-11-06 15:34:33 +03:00
284c483b6b Adapted repo file suite check for ALT 2024-11-06 15:27:38 +03:00
f5ef96d506 (ALT) Now files with .sisyphus (corresponds to README.sisyphus), .rpmnew and .rpmsave ignored during repo lists parsing 2024-11-01 12:40:58 +03:00
Fabian Grünbichler
f96c0e6036 http: update d/control
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-10-30 12:20:36 +01:00
Lukas Wagner
e8e5c11c6a fix #5808: http: use native-tls instead of rustls for the sync client
In the reference Bugzilla entry, a certificate with an IP address as a
SAN was used. rustls seems to have problems with that [1].
Also, pretty much all of our code uses native-tls at the moment, so
it makes sense to not pull in a second TLS implementation.

Tested by rebuilding libpve-rs-perl and testing a Gotify notification
target with a self-signed TLS certificate (one that is accepted by
OpenSSL but not by rusttls).

[1] https://github.com/rustls/rustls/issues/184

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-10-30 12:19:56 +01:00
b945c8f6b7 Fix(tests): upstream tests after a merge request 2024-10-29 15:47:06 +03:00
5908f1261e Commented out upstream config.toml 2024-10-28 17:44:15 +03:00
35b1de18d0 Changed .toml deps to depend on Alt's crates with alt's feature 2024-10-28 15:37:45 +03:00
314e563210 Altlinux tests fixed according to changed API 2024-10-28 14:58:42 +03:00
c1ec0a7ee0 Some Traits and usings fixed 2024-10-28 13:57:45 +03:00
59d0e20da9 Applied "alt-linux" feature to cache api and proxmox-apt-api-types crate 2024-10-28 13:50:10 +03:00
f559a28390 Adapted supplementary structures (taken from Alexander Burmatov commits at git://git.altlinux.org/people/thatman/packages/proxmox.new.git) 2024-10-28 13:42:54 +03:00
235ed36f10 Initial merge with Alt's proxmox 2024-10-28 12:50:49 +03:00
56d7012996 Merge remote-tracking branch 'proxmox.new/altlinux' 2024-10-28 12:37:10 +03:00
Fabian Grünbichler
f5e7f4ed7f proxmox-apt-api-types: use workspace excludes
else the `debian` dir is contained in the .crate archive, breaking the build..

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-10-23 13:29:22 +02:00
Thomas Lamprecht
cba72020dd api-types: add missing doc-comment description for api enums
this is used as description in the api schema

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-22 15:25:52 +02:00
Thomas Lamprecht
ba3ee7a4db api-marcro: throw compiler error if description for enums is empty
A description is required for the API schema types and we fallback to
the rust doc-comment when no explicit one is set.
But a empty string was returned if no doc-comment existed, so check
for the comment to be non-empty and throw a compile-time error
otherwise.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-22 15:14:43 +02:00
Thomas Lamprecht
1c4467841d schema: property description: output indentation where its required
The wrap_text helper accepts and initial indentation, so use that as
central point to add the indentation that glues the list entry
together with its description.

Mostly a small optimization, should not matter in practice, i.e. where
all properties should have a description.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-22 14:51:19 +02:00
Thomas Lamprecht
bd1133fcd2 notify: add missing doc-comment description for api enums
this is used as description in the api schema

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-22 14:49:10 +02:00
Thomas Lamprecht
331fa7a732 apt-api-types: add missing doc-comment description for api enums
this is used as description in the api schema

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-22 14:48:47 +02:00
Gabriel Goller
1b70270b2d log: only print error level to syslog/stderr
We only want to print the error level, and not all the levels below
error to stderr/syslog.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-10-18 18:28:37 +02:00
Thomas Lamprecht
21c314b56e schema: property description: switch format strings to inline template variables
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-18 17:37:30 +02:00
Thomas Lamprecht
b809d86d73 cli: format: switch some format strings to inline template variables
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-18 17:29:17 +02:00
Thomas Lamprecht
aa12dcbba0 time: bump version to 2.0.2-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-17 16:16:56 +02:00
Thomas Lamprecht
44e7ca98cd time: add some simple unit tests for time span conversions
For starters, could definitively be expanded.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-17 14:23:27 +02:00
Thomas Lamprecht
17bf3ec9fe time: add module level docs for time span
There was basically no documentation at all, so try to document the
basic format syntax and where it comes from. The text is partially
adapted from the systemd docs.

Could be still expanded with some example code and the methods might
do good with getting some docs too, but those parts can be relatively
easily be figured out from the code itself, the basic underlying
design and format background is much harder to guess..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-17 14:23:27 +02:00
Thomas Lamprecht
191299605f time: display minute/month such that it can be parsed again
Previously we displayed, e.g., "4m 1h 1min", i.e. using "m" for months
and "min" for minutes but "m"  was not accepted as month when parsing
a timespan string, so a 4 month timespan would be printed "4m" but if
parsed again it would result in a timespan of 4 minutes.

So switch month to an uppercase "M" and minute to the lower case "m",
which makes renderings of common timespans nicer, as in most of our
use cases they are in the range of minutes to hours, sometimes days
but seldom longer than weeks. So using single letters for all but
"min" stuck out quite a bit, e.g.: "1h 5min 2s" looks odd compared to
"1h 5m 1s"

While the duplicate letter is not 100% ideal it's still better than
the status quo, where rendering and parsing would interpret things
differently.
Also, the order still makes it quite clear, e.g.:
"7m 2w 3d 1h 5min 44s" now becomes "7M 2w 3d 1h 5m 44s"

As a side effect this also brings the display format closer to what is
used inside PVE backup job taks logs.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-17 14:22:44 +02:00
Thomas Lamprecht
9ae91303fd time: switch to inline template variables
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-10-17 11:40:21 +02:00
Christian Ebner
67822186a2 time: drop trailing space when not showing seconds at end
Seconds are not displayed when the value is smaller than 0.1s and
they are not at the start of the display output, e.g. `1h 2m`.
Drop the additional whitespace currently appended for this edge case.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
2024-10-17 10:38:53 +02:00
Wolfgang Bumiller
c76090b907 tfa: clean up unused 'use' statements
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-10-15 15:19:03 +02:00
Lukas Wagner
deb07eeb31 pbs-api-types: add types for the new metrics endpoint
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-10-15 14:09:41 +02:00
Wolfgang Bumiller
11930517ef acme: bump to 0.5.3
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-10-03 09:52:46 +02:00
Wolfgang Bumiller
b52b3739be acme: deny(unsafe_code)
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-10-02 12:49:17 +02:00
Wolfgang Bumiller
f298ed6aec acme: detect base64 vs base64url encoded hmac keys
We do this in the PVE code as well.

Link: https://forum.proxmox.com/threads/acme-with-custom-acme-directory-doesnt-work.147058/
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-10-02 12:45:31 +02:00
Wolfgang Bumiller
c30169d08f tfa: bump d/control
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-10-01 12:51:04 +02:00
Wolfgang Bumiller
2a1458126c tfa: bump to 5.0.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-10-01 12:48:36 +02:00
Wolfgang Bumiller
8698f3afc7 tfa: provide TfaUser via the 'types' feature and module
So we can access it from UI code.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-10-01 12:48:27 +02:00
Wolfgang Bumiller
ee113bf244 login: bump to 0.1.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-30 11:48:12 +02:00
Dominik Csapak
eb3dd9453b login: boolean parser: also accept "1" and "0" as strings
since that's what the pve api sometimes returns for booleans

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-09-30 11:45:06 +02:00
Wolfgang Bumiller
7379bdfb9c README: extend Adding Cates section, convert to markdown
(conveted with pandoc and some minor manual fixups)

Mention that the crates should activate `doc_cfg, doc_auto_cfg` and
ideally `#[deny(unsafe_op_in_unsafe_fn)]` and `#[deny(missing_docs)]`.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-30 10:26:56 +02:00
Wolfgang Bumiller
f7e130d5b5 api-macro: bump d/control
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-26 14:56:56 +02:00
Wolfgang Bumiller
d601b57fd0 api-macro: bump to 1.2.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-26 14:52:52 +02:00
Wolfgang Bumiller
532d4d3e9a login: bump to 0.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-26 14:51:53 +02:00
Wolfgang Bumiller
2b3c356ece api-macro: allow declaring an additional-properties field
Object schemas can now declare a field which causes
'additional_properties' to be set to true and the field being ignored
in the schema.

This allows adding a flattened HashMap<String, Value> to gather the
additional unspecified properties.

    #[api(additional_properties: "rest")]
    struct Something {
        #[serde(flatten)]
        rest: HashMap<String, Value>,
    }

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-26 12:47:58 +02:00
Wolfgang Bumiller
e72528ca70 login: add 'raw' webauthn challenge access
So we can get going on the wasm side where we don't yet have access to
the webauthn-rs crate.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-26 12:40:32 +02:00
Wolfgang Bumiller
c85b534837 readme: update cargo config path
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-20 08:44:19 +02:00
Wolfgang Bumiller
005678cec2 buildsys: add a 'make list-packages' target
To ease development on new machines, this provides an easy way to just
do

    # apt install $(make list-packages)

to get started.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-20 08:43:08 +02:00
Wolfgang Bumiller
d6e86d670b tree-wide: unify workspace inherited attributes
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-20 08:42:45 +02:00
Wolfgang Bumiller
6e8ad21227 rrd-api-types: bump to 1.0.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-19 15:17:08 +02:00
Wolfgang Bumiller
9ed8f7f110 rrd-api-types: follor acronym capitalization guidelines
Link: https://rust-lang.github.io/api-guidelines/naming.html
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-19 15:13:26 +02:00
Wolfgang Bumiller
111a883788 rrd-api-types: bump to 1.0.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-19 14:59:08 +02:00
Wolfgang Bumiller
4f787391de rrd-api-types: make mode and timeframe +Eq+PartialEq+Debug
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-19 14:55:51 +02:00
Wolfgang Bumiller
b38568158a sys: bump to 0.6.4-1
This should also fix a build issue on aarch64 caused by a signed-ness
differences of c_char.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-19 09:07:01 +02:00
Wolfgang Bumiller
fb8a706066 rrd-api-types: check in d/control
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-19 09:06:55 +02:00
Wolfgang Bumiller
8f3eecab68 rrd-api-types: bump to 1.0.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-18 16:05:21 +02:00
Wolfgang Bumiller
3cf67472a1 rrd: bump to 0.4.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-18 16:05:21 +02:00
Wolfgang Bumiller
0177b1d975 add proxmox-rrd-api-types crate - moved out of proxmox-rrd
so we can access them from wasm without pulling in proxmox-rrd as it
also pulls in sys...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-18 15:55:43 +02:00
Wolfgang Bumiller
e57a65879e rrd: bump to 0.3.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-18 15:24:16 +02:00
Lukas Wagner
9426af0abf rrd: derive Display and FromStr for api types
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-09-18 15:21:58 +02:00
Wolfgang Bumiller
fda2cdb7ed log: bump to 0.2.5-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-16 15:12:11 +02:00
Gabriel Goller
5d33d870ee log: print error if env-var parsing failed, print correct name
Print error if the parsing of the env-var fails on the proxmox-backup-*
daemons as well. Output correct env-var on binaries that use different
variables.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-09-16 15:08:41 +02:00
Gabriel Goller
c9c9ade96e log: fallback to stderr if syslog not available
Don't panic when the syslog is not available - which happens commonly in
containers and sbuild environments (chroot and unshare) - instead
fallback to stderr.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
Tested-by: Fiona Ebner <f.ebner@proxmox.com>
2024-09-16 15:07:43 +02:00
Wolfgang Bumiller
c6fd5604df access-control: bump to 0.2.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:25:49 +02:00
Wolfgang Bumiller
80503e409d buildsys: add targets for raw installation and sysexts
The following targets should be self-explanatory:

    # make install
    $ make DESTDIR=$SOME_PATH install
    # make proxmox-sys-install
    $ make DESTDIR=$SOME_PATH proxmox-sys-install
    # make proxmox-<any other crate name>-install

Additionally, these are used as building blocks to create
systemd-sysext(8) images:

    $ make proxmox-sys-sysext

builds an `extensions/proxmox-sys.raw`

This can be copied/symlinked to `/run/extensions/` and then activated.
As root:

    # ln -s $REPO_DIR/extensions/proxmox-sys.raw /run/extensions/
    # systemd-systext refresh

For the complete workspace, an `extensions/proxmox-workspace.raw` can
be built via

    $ make sysext

This also takes a `CRATES` var to limit the crates which should be
included, and takes an optional `NOCLEAN=1` which prevents cleaning
out the previously installed to "add" new crates on the go:
Assuming there's a symlink like:

    # ln -s $REPO_DIR/extensions/proxmox-workspace.raw /run/extensions/proxmox-workspace.raw

One can modify the installed crates like this:

    $ make CRATES=proxmox-sys sysext
    $ sudo systemd-sysext refresh

Now only the current proxmox-sys crate is overridden.

    $ make NOCLEAN=1 CRATES=proxmox-time sysext
    $ sudo systemd-sysext refresh

Now proxmox-sys as well as proxmox-time are installed.

To undo the changes, either just do, as root:

    # systemd-sysext unmerge

or remove the files which should specifically be dropped from
/run/extensions/ and run as root:

    # systemd-sysext refresh

Another way to temporarily install single crates is to just have the
extensions/ folder *be* the `/run/extensions` folder:

    # rmdir /run/extensions
    # ln -s $REPO_DIR/extensions /run/extensions

Then just build individual extensions:

    $ make proxmox-sys-sysext
    $ sudo systemd-sysext refresh
    $ make proxmox-router-sysext
    $ sudo systemd-sysext refresh

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
3d812952bc auth-api: bump to 0.4.6
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
420285ea8c acme-api: bump to 0.1.6-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
b1ddac7c45 client: bump to 0.5.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
61e4d35dea schema: bump to 3.2.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
dfc4e5f866 api-macro: bump to 1.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
ddbada0668 rest-server: bump to 0.8.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
98ed1bb28b router: bump to 3.0.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
d469463904 compression: bump to 0.2.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
3d6b3c4786 router: split streaming reader impl into 'stream' feature
so 'no-default-features' compiles again

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
2a4cf83799 rest-server: utilize flush_window for streams
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
78c29b33ef compression: add flush_window to DeflateEncoder
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
b22de1864a router: add stream helpers to async-decode json-seq
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
04923dd601 client: expose body, add generic request methods and streaming
The get/put/post/put_without_body/... methods now have a default
implementation forwarding to a generic `request` method as all our
implementations do the same already anyway.

Additionally, in order to allow easy access to a "streaming body", the
Body type is now exposed.

In the future, this crate may also require a wrapper to standardize
the handling of `application/json-seq` streams if we end up using
them, but for now, a simple way to expose the body is enough to get
going.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
8021f0a7f6 api-macro: support new streaming api methods
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
17bd32e90e router, rest-server: add StreamSync and StreamAsync API handlers
These are Iterators or Streams which continuously produce output. They
can either be formatted, in which they are serialized like the as
usually, or, if the client caccepts `application/json-seq` via an
`Accept` header, it will be streamed as a sequence directly.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
c31eaf0018 router, rest-server, api-macro: rename Streaming api to Serializing
This does not "stream", but rather skips the intermediate step to
serialize the entire output into a local json string.

We now reserve the "Stream*" prefix for actual *streaming*, that is,
producing an API response which gets streamed continuously as it is
asynchronously produced.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 14:15:11 +02:00
Wolfgang Bumiller
e8b8060b17 log: bump to 0.2.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-09-05 13:58:56 +02:00
Gabriel Goller
5933c2f47b log: write to stderr when using init_cli_logger, export tracing::Level
Previously when using `env_logger` all of our cli-tools logged to
stderr, make tracing do the same. Export `tracing::Level` so that we can
use the `tracing::enabled!` macro.

Tested-by: Christian Ebner <c.ebner@proxmox.com>
Reviewed-by: Christian Ebner <c.ebner@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-09-05 13:45:43 +02:00
Wolfgang Bumiller
a46dd1a60a router: bump to 2.2.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-30 13:53:03 +02:00
Wolfgang Bumiller
9a17ff15f7 log: bump to 0.2.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-30 13:50:24 +02:00
Gabriel Goller
021bab9304 add tracing init_cli_logger and deprecate old one
Deprecate the proxmox-router init_cli_logger function used in client
binaries such as `proxmox-backup-client`, `proxmox-backup-manager`,
'pxar', etc... Add a new init_cli_logger function that uses tracing
instead of env_logger. It checks if the task is in a workertask and
prints the message either to stdout or to the tasklog (this is
neccessary for commands in proxmox-backup-manager that call api handlers
that start workerthreads from the client).

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-08-30 13:47:55 +02:00
Dominik Csapak
237b317678 router: sort cli properties in usage output
If we don't do this, then properties from a serde flattened struct will
be positioned at the end of the list, rather than properly sorted with
the other properties.

Since the tests also feature non-sorted properties, we have to adapt
them too.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-08-30 13:26:13 +02:00
Dominik Csapak
1d5d7b7f98 fix #5622: backup client: properly handle rate/burst parameters
The rate and burst parameters are integers, so the mapping from value
with `.as_str()` will always return `None` effectively never
applying any rate limit at all.

Fix it by turning them into a HumanByte instead of an integer.

To not crowd the parameter section so much, create a
ClientRateLimitConfig struct that gets flattened into the parameter list
of the backup client.

To adapt the description of the parameters, add new schemas that copy
the `HumanByte` schema but change the description.

With this, the rate limit actually works, and there is no lower limit
any more.

The old TRAFFIC_CONTROL_RATE/BURST_SCHEMAs can be deleted since the
client was the only user of them.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-08-30 13:21:29 +02:00
Wolfgang Bumiller
d72ea40c80 schema: bump to 3.1.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-28 14:51:23 +02:00
Wolfgang Bumiller
5704cb43b9 schema: add Schema::unwrap_any_object_schema
so we have a version we can use in const fns and const{} expressions

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-28 14:50:21 +02:00
Wolfgang Bumiller
00ca04698d schema: seal ObjectSchemaType and assert Send + Sync
While this is technically a breaking API change since the trait is
public, we don't implement it anywhere and it isn't meant to be
implemented from the outside.

Also, encode that these types are all Send + Sync via a super trait
notation.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-28 14:43:42 +02:00
Wolfgang Bumiller
e750bce69b schema: make Schema::any_object a const fn
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-28 14:32:39 +02:00
Wolfgang Bumiller
d166dcff0a schema: rustfmt
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-28 14:32:21 +02:00
Wolfgang Bumiller
3d044a5acf systemd: bump d/control to add missing dependencies
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-28 14:18:58 +02:00
Maximiliano Sandoval
a41da1f371 systemd: debcargo: add libsystemd-dev to dependencies
`Build-Depends` and `Depends` in d/control are missing `libsystemd-dev`,
resulting in mk-build-deps not being able to install all dependencies
needed by `make deb`.

After running `make deb` the control file looks:

```diff
modified   proxmox-systemd/debian/control
@@ -6,7 +6,8 @@ Build-Depends: debhelper (>= 12),
  cargo:native <!nocheck>,
  rustc:native <!nocheck>,
  libstd-rust-dev <!nocheck>,
- librust-libc-0.2+default-dev (>= 0.2.107-~~) <!nocheck>
+ librust-libc-0.2+default-dev (>= 0.2.107-~~) <!nocheck>,
+ libsystemd-dev <!nocheck>
 Maintainer: Proxmox Support Team <support@proxmox.com>
 Standards-Version: 4.6.2
 Vcs-Git: git://git.proxmox.com/git/proxmox.git
@@ -19,7 +20,8 @@ Architecture: any
 Multi-Arch: same
 Depends:
  ${misc:Depends},
- librust-libc-0.2+default-dev (>= 0.2.107-~~)
+ librust-libc-0.2+default-dev (>= 0.2.107-~~),
+ libsystemd-dev
 Provides:
  librust-proxmox-systemd+default-dev (= ${binary:Version}),
  librust-proxmox-systemd-0-dev (= ${binary:Version}),
```

Suggested-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-28 14:18:22 +02:00
Maximiliano Sandoval
de004f736d section-config: fix link to SectionConfigData
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-28 13:09:15 +02:00
Maximiliano Sandoval
ec3e8e05e4 section-config: fix link to section_config
Fixes:

warning: unresolved link to `seccfg`
  --> proxmox-section-config/src/typed.rs:18:71
   |
18 |     /// If the [`SectionConfig`] returned by the [`section_config()`][seccfg] method includes the
   |                                                                       ^^^^^^ no item named `seccfg` in scope
   |
   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
   = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default

warning: unresolved link to `seccfg`
  --> proxmox-section-config/src/typed.rs:22:10
   |
22 |     /// [seccfg] ApiSectionDataEntry::section_config()
   |          ^^^^^^ no item named `seccfg` in scope
   |
   = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-28 13:09:15 +02:00
Maximiliano Sandoval
0157b6fdea access-control: acl: add indentation to docs
Fixes:

warning: doc list item missing indentation
   --> proxmox-access-control/src/acl.rs:518:9
    |
518 |     /// -- user/token is more specific than group at each level
    |         ^
    |
    = help: if this is supposed to be its own paragraph, add a blank line
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
    = note: `#[warn(clippy::doc_lazy_continuation)]` on by default
help: indent this line
    |
518 |     ///   -- user/token is more specific than group at each level
    |         ++

warning: doc list item missing indentation
   --> proxmox-access-control/src/acl.rs:519:9
    |
519 |     /// -- roles lower in the tree are more specific than those higher up along the path
    |         ^
    |
    = help: if this is supposed to be its own paragraph, add a blank line
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
help: indent this line
    |
519 |     ///   -- roles lower in the tree are more specific than those higher up along the path
    |         ++

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-28 13:09:15 +02:00
Maximiliano Sandoval
e60a53d80b apt: sources_parser: remove needless borrow
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-28 13:09:15 +02:00
Maximiliano Sandoval
f8f57540c6 apt: cache_api: simplify match with unwrap_or_default
Fixes:

warning: match can be simplified with `.unwrap_or_default()`
  --> proxmox-apt/src/cache_api.rs:77:28
   |
77 |           let mut notified = match cache.notified {
   |  ____________________________^
78 | |             Some(notified) => notified,
79 | |             None => std::collections::HashMap::new(),
80 | |         };
   | |_________^ help: replace it with: `cache.notified.unwrap_or_default()`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default
   = note: `#[warn(clippy::manual_unwrap_or_default)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-28 13:09:15 +02:00
Maximiliano Sandoval
a2e5973982 router: completion: remove needles borrow
Fixes:

warning: this expression creates a reference which is immediately dereferenced by the compiler
   --> proxmox-router/src/cli/completion.rs:154:25
    |
154 |                         &completion_functions,
    |                         ^^^^^^^^^^^^^^^^^^^^^ help: change this to: `completion_functions`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
    = note: `#[warn(clippy::needless_borrow)]` on by default

warning: this expression creates a reference which is immediately dereferenced by the compiler
   --> proxmox-router/src/cli/completion.rs:201:21
    |
201 |                     &completion_functions,
    |                     ^^^^^^^^^^^^^^^^^^^^^ help: change this to: `completion_functions`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-28 13:09:15 +02:00
Maximiliano Sandoval
b7100ecc1e rrd_map: remove unnecessary use of get().is_some()
Fixes:

warning: unnecessary use of `get(rel_path).is_some()`
   --> proxmox-rrd/src/cache/rrd_map.rs:107:21
    |
107 |         if self.map.get(rel_path).is_some() {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `contains_key(rel_path)`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_get_then_check

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-28 13:09:15 +02:00
Maximiliano Sandoval
5586eeaba8 rrd_map: remove unneded return statement
Fixes:

warning: unneeded `return` statement
   --> proxmox-rrd/src/cache/rrd_map.rs:117:13
    |
117 |             return Ok(true);
    |             ^^^^^^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
    = note: `#[warn(clippy::needless_return)]` on by default
help: remove `return`
    |
117 -             return Ok(true);
117 +             Ok(true)
    |

warning: unneeded `return` statement
   --> proxmox-rrd/src/cache/rrd_map.rs:119:13
    |
119 |             return Ok(false);
    |             ^^^^^^^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
help: remove `return`
    |
119 -             return Ok(false);
119 +             Ok(false)

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-28 13:09:15 +02:00
Wolfgang Bumiller
2da3121492 sys: crypt: style + drop unnecessary length check
These are statically sized arrays, not slices, they cannot be empty.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-28 13:04:43 +02:00
Alexander Burmatov
4f4b172c71 Disable Deb-822 format for ALT
There is no need for the deb822-style format.
2024-08-21 17:56:31 +03:00
Alexander Burmatov
30de6a59f5 Add tests for ALT
1. New tests for ALT Linux.
2. Move test files for Debian to a separate directory.
2024-08-21 17:56:31 +03:00
Alexander Burmatov
5a53078041 Set correct filename for cached file 2024-08-21 17:56:31 +03:00
Alexander Burmatov
3891685a18 Change Repository option to Vendor option
Apt uses vendor ID (alt, p10 etc) in square brackets of the source lists.
2024-08-21 17:56:31 +03:00
Alexander Burmatov
b99a6ccd0b New APTRepositoryHandles
ALT Linux uses checkinstall, classic, debuginfo and gostcrypto.
2024-08-21 17:56:31 +03:00
Alexander Burmatov
89ace6a311 Add RPM package type 2024-08-21 17:37:25 +03:00
Alexander Burmatov
6d4d707935 Release like in ALT
Refactoring the code for correct /etc/os-release processing.
2024-08-16 19:14:11 +03:00
Alexander Burmatov
661c1e14af New feature alt-linux
A new feature that adds support for ALT Linux.
2024-08-15 21:09:34 +03:00
Maximiliano Sandoval
959b7745f7 api-types: remove unused lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 12:08:01 +02:00
Wolfgang Bumiller
3129752da9 router: bump to 2.2.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 12:02:13 +02:00
Wolfgang Bumiller
c1a947c998 openid: bump to 0.10.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 12:00:58 +02:00
Wolfgang Bumiller
9ae3df424e product-config: bump to 0.2.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 12:00:02 +02:00
Wolfgang Bumiller
5544c17ede log: bump to 0.2.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:52:03 +02:00
Wolfgang Bumiller
ca04e129e4 client: bump to 0.4.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:51:22 +02:00
Wolfgang Bumiller
11e6173097 apt: bump to 0.11.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:48:32 +02:00
Wolfgang Bumiller
256bcfb0f1 rrd: bump to 0.3.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:47:35 +02:00
Wolfgang Bumiller
c1709d994e simple-config: bump to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:44:26 +02:00
Wolfgang Bumiller
fc7a4be4eb uuid: bump to 1.0.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:43:11 +02:00
Wolfgang Bumiller
2c1d5fd741 daemon: bump to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:42:00 +02:00
Maximiliano Sandoval
af9aa40d8b router: remove unused deps
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Maximiliano Sandoval
89df077104 openid: remove unused dependencies
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Maximiliano Sandoval
c33cc9e350 product-config: remove unused dependencies
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Maximiliano Sandoval
d44ceba86d log: remove unused log dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Maximiliano Sandoval
5e2179cf0a client: remove unused dependencies
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Maximiliano Sandoval
b01230f70c apt: remove unused dependencies
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Maximiliano Sandoval
9031fc88e1 rrd: remove unused libc dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Maximiliano Sandoval
b6c6834d1f simple-config: remove unused log dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Maximiliano Sandoval
c594d847b5 uuid: remove unused libc dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Maximiliano Sandoval
709c237ebc daemon: Remove unused once_cell dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:38:48 +02:00
Wolfgang Bumiller
f36d0e5137 dns-api: bump to 0.1.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:38:45 +02:00
Maximiliano Sandoval
58e2fa5983 dns-api: remove unused dependencies
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:36:49 +02:00
Wolfgang Bumiller
f94dc5b0a3 acme-api: bump to 0.1.5-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:36:32 +02:00
Maximiliano Sandoval
358bb76960 acme-api: remove unused dependencies
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:35:45 +02:00
Wolfgang Bumiller
c319c5925d auth-api: bump to 0.4.5
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:35:15 +02:00
Wolfgang Bumiller
bdb28cc8bf network-api: bump to 0.1.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:34:36 +02:00
Wolfgang Bumiller
693d206c94 rest-server: bump to 0.7.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:34:15 +02:00
Maximiliano Sandoval
21a39b3cda rest-server: remove use of once_cell
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:32:23 +02:00
Maximiliano Sandoval
901f2fb1bb rest-server: remove unused dependencies
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:32:23 +02:00
Wolfgang Bumiller
294a551e0e subscription: bump to 0.4.6-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:32:23 +02:00
Wolfgang Bumiller
3e8fa8b81c sys: bump to 0.6.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:32:20 +02:00
Maximiliano Sandoval
a86deb2783 sys: remove unused base64 dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 11:28:10 +02:00
Wolfgang Bumiller
a88be21074 time: bump to 2.0.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:08:23 +02:00
Wolfgang Bumiller
3fabb14d62 schema: bump to 3.1.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:06:44 +02:00
Wolfgang Bumiller
122f88d273 async: bump to 0.4.3
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-14 11:06:22 +02:00
Maximiliano Sandoval
b5f806797f cargo: remove lazy_static dependency on workspace
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
ab3f4a2fc4 schema: remove lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
9530b75286 acme-api: remove lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
0b19e344d7 auth-api: remove lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
dde994ab57 network-api: remove lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
8d5e864bf1 sys: remove lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
669c39c59f time: remove lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
25f83bce19 rest-server: remove lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
692231d2a4 subscription: remove lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
6c0f6890e4 async: remove lazy_static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
9693ceca7a dns-api: remove lazy-static dependency
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Maximiliano Sandoval
9f9f736cfc cargo: set msrv to 1.80
In the following commits we make use of std::sync::LazyLock;

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-14 10:33:42 +02:00
Wolfgang Bumiller
cb509a1e6a client: bump to 0.4.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-13 15:45:20 +02:00
Wolfgang Bumiller
c54d5e06e6 client: change Token struct
API tokens between rust & perl code bases are inconsistent... this
needs fixing, but for now this is faster and more compatible.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-13 15:45:20 +02:00
Wolfgang Bumiller
9923f29622 section-config: bump to 2.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-13 15:45:20 +02:00
Wolfgang Bumiller
61d2eb891e section-config: make typed data usable with .with_type_key()
The original typed section config data would insert and remove the
type properties. With the introduction of `.with_type_key()` this is
done on the parse/write side instead, so we need to be able to opt out
of this.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-13 15:45:20 +02:00
Lukas Wagner
3e83acfd89 api-types: rrd: use api-types from proxmox-rrd
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-09 13:04:57 +02:00
Wolfgang Bumiller
88af4ca549 rrd: bump to 0.3.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 13:03:27 +02:00
Wolfgang Bumiller
01947a9802 shared-cache: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 12:59:09 +02:00
Wolfgang Bumiller
7c6701aab9 shared-cache: remove unused dependency on proxmox-schema
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 12:57:42 +02:00
Wolfgang Bumiller
c352cb87a0 shared-cache: group and sort dependencies
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 12:56:30 +02:00
Wolfgang Bumiller
fa3c7690e7 sys: bump to 0.6.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 12:53:44 +02:00
Wolfgang Bumiller
93d42ad488 rrd: rustfmt and style fix
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 11:20:15 +02:00
Wolfgang Bumiller
c66e5432e7 shared-cache: minor style adaptation
(more concise & readable)

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 11:10:48 +02:00
Wolfgang Bumiller
2d4050825d sys: don't duplicate the template path
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 11:06:25 +02:00
Lukas Wagner
88731c52f0 rrd: add api-types
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-09 11:01:30 +02:00
Lukas Wagner
7ea63a6fb9 rrd: try to load database if not already present in cache
Before, a call to `update` was necessary to load an existing database
into the cache. If `update` was never called, `extract_cached_data`
would simply return no data.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-09 11:01:30 +02:00
Lukas Wagner
f01c1e0ce9 rrd: cache: add update_value_ignore_old
This function is the same as the regular `update_value`, but it
sets the `new_only` flag when updating the value in the rrd map.
This avoids "time in past" messages being logged in case a data point
happens to be added twice. This new function will just silently reject
values that have an older timestamp than the most recent one.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-09 11:01:30 +02:00
Lukas Wagner
4ca2e01442 rrd: deprecate create_pbs_default_rrd
This one should be in the client code.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-09 11:01:30 +02:00
Lukas Wagner
846e85ed2e cache: add benchmark example
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-09 11:01:30 +02:00
Lukas Wagner
0febb5045a cache: add new crate 'proxmox-shared-cache'
This crate contains a file-backed, rotating cache.
The cache should be safe to be accessed from multiple processes at
once.

The cache stores the value at the provided path.
If `keep_old` is >0, the cache will keep up to `keep_old` versions
around which can be queried via the `get_last` method.
The value and its history are stored in the same file,
each generation represented by a single line of JSON.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-09 11:01:30 +02:00
Lukas Wagner
2b6ecfd38d sys: add make_tmp_dir
Under the hood, this function calls `mkdtemp` from libc. Unfortunatly
the nix crate did not provide bindings for this function, so we have
to call into libc directly.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-09 11:01:30 +02:00
Wolfgang Bumiller
83c5ced7a9 sys: get rid of a try_block
by skipping over a utf8 scheck (serde_json can take a byte slice
already via `from_slice()`)

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 09:13:39 +02:00
Wolfgang Bumiller
b266779ec0 sys: drop unused proxmox-time dependency
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-09 09:11:17 +02:00
Maximiliano Sandoval
642db84474 auth-api: docs: remove wrong return info
The method returns a boolean.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-07 20:58:04 +02:00
Maximiliano Sandoval
920fc41b2f server: docs: fix unresolved link to systemd_notify
Fixes the cargo docs warning:

warning: unresolved link to `systemd_notify`
   --> proxmox-daemon/src/server.rs:314:6
    |
314 | /// [systemd_notify] with [SystemdNotify::Ready](proxmox_systemd::notify::SystemdNotify) when the
    |      ^^^^^^^^^^^^^^ no item named `systemd_notify` in scope
    |
    = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
    = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-07 20:58:04 +02:00
Maximiliano Sandoval
c8352531e4 serde: docs: escape html tags
Fixes the cargo docs warning:

warning: unclosed HTML tag `u8`
  --> proxmox-serde/src/lib.rs:55:18
   |
55 | /// Serialize Vec<u8> as base64 encoded string.
   |                  ^^^^
   |
   = note: `#[warn(rustdoc::invalid_html_tags)]` on by default
help: try marking as source code
   |
55 | /// Serialize `Vec<u8>` as base64 encoded string.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-07 20:58:04 +02:00
Maximiliano Sandoval
2794c137d5 login: docs: fix typo and add escape html tags
Fixes the cargo docs warning:

warning: unclosed HTML tag `username`
  --> proxmox-login/src/api.rs:35:47
   |
35 |     /// realm is simply added to the username <username>@<relam>.
   |                                               ^^^^^^^^^^
   |
   = note: `#[warn(rustdoc::invalid_html_tags)]` on by default

warning: unclosed HTML tag `relam`
  --> proxmox-login/src/api.rs:35:58
   |
35 |     /// realm is simply added to the username <username>@<relam>.
   |                                                          ^^^^^^^

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-07 20:58:04 +02:00
Maximiliano Sandoval
f80cb33993 ldap: docs: turn uri into link
Fixes the following cargo doc warning:

warning: this URL is not a hyperlink
   --> proxmox-ldap/src/lib.rs:199:9
    |
199 |     /// https://www.rfc-editor.org/rfc/rfc4512#section-5.1
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use an automatic link instead: `<https://www.rfc-editor.org/rfc/rfc4512#section-5.1>`
    |
    = note: bare URLs are not automatically turned into clickable links
    = note: `#[warn(rustdoc::bare_urls)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-07 20:58:04 +02:00
Maximiliano Sandoval
0c889ff2da client: docs: remove redundant link
Fixes the cargo doc warnings:

warning: redundant explicit link target
 --> proxmox-http/src/client/mod.rs:4:19
  |
4 | //! in [`Client`](crate::client::Client).
  |         --------  ^^^^^^^^^^^^^^^^^^^^^ explicit target is redundant
  |         |
  |         because label contains path that resolves to same destination
  |
  = note: when a link's destination is not specified,
          the label is used to resolve intra-doc links
  = note: `#[warn(rustdoc::redundant_explicit_links)]` on by default
help: remove explicit link target
  |
4 | //! in [`Client`].
  |        ~~~~~~~~~~

warning: redundant explicit link target
 --> proxmox-http/src/client/mod.rs:7:22
  |
7 | //! [`sync::Client`](crate::client::sync::Client).
  |      --------------  ^^^^^^^^^^^^^^^^^^^^^^^^^^^ explicit target is redundant
  |      |
  |      because label contains path that resolves to same destination
  |
  = note: when a link's destination is not specified,
          the label is used to resolve intra-doc links
help: remove explicit link target
  |
7 | //! [`sync::Client`].
  |     ~~~~~~~~~~~~~~~~

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-07 20:58:04 +02:00
Maximiliano Sandoval
fe7f37e9b3 property_string: clippy: define bound once
Fixes the clippy lint:

warning: bound is defined in more than one place
   --> proxmox-schema/src/property_string.rs:352:14
    |
352 | pub fn parse<T: ApiType>(value: &str) -> Result<T, Error>
    |              ^
353 | where
354 |     T: for<'de> Deserialize<'de>,
    |     ^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#multiple_bound_locations
    = note: `#[warn(clippy::multiple_bound_locations)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-07 20:58:04 +02:00
Maximiliano Sandoval
77fe0f6954 docs: clippy: add indentation to doc list items
Fixes the clippy warning:

warning: doc list item missing indentation
   --> proxmox-subscription/src/subscription_info.rs:179:9
    |
179 |     ///  (this mode is used to decide whether to refresh the subscription information)
    |         ^
    |
    = help: if this is supposed to be its own paragraph, add a blank line
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
    = note: `#[warn(clippy::doc_lazy_continuation)]` on by default
help: indent this line
    |
179 |     ///   (this mode is used to decide whether to refresh the subscription information)
    |          +

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-07 20:58:04 +02:00
Maximiliano Sandoval
7052972212 apt: clippy: don't clone types implementing Copy
Fixes the clippy warnings:

warning: `proxmox-apt` (lib) generated 1 warning
warning: using `clone` on type `Option<[u8; 32]>` which implements the `Copy` trait
   --> proxmox-apt/tests/repositories.rs:117:22
    |
117 |     let old_digest = file.digest.clone().unwrap();
    |                      ^^^^^^^^^^^^^^^^^^^ help: try removing the `clone` call: `file.digest`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
    = note: `#[warn(clippy::clone_on_copy)]` on by default

warning: using `clone` on type `[u8; 32]` which implements the `Copy` trait
   --> proxmox-apt/tests/repositories.rs:135:24
    |
135 |     file.digest = Some(old_digest.clone());
    |                        ^^^^^^^^^^^^^^^^^^ help: try removing the `clone` call: `old_digest`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-08-07 20:58:04 +02:00
Maximiliano Sandoval
35e466410c fix typos in strings
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-07 16:49:31 +02:00
Maximiliano Sandoval
0106a14d43 fix typos in rust documentation blocks
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-08-07 16:49:31 +02:00
Wolfgang Bumiller
c3713cffe5 api-macro: fix warnings in tests
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 14:23:38 +02:00
Wolfgang Bumiller
a7ab26a9d8 http-error: fix a warning in tests
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 14:22:25 +02:00
Wolfgang Bumiller
15c64a5d00 api-macro: bump to 1.1.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 14:15:57 +02:00
Wolfgang Bumiller
451d3b0adf section-config: bump to 2.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 14:15:34 +02:00
Wolfgang Bumiller
839f508f55 api-macro: type-key support for derived enums
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 14:14:42 +02:00
Wolfgang Bumiller
4a154b3cb5 section-config: support a type_key property
For when the underlying datatype is supposed to contain the type
property and the schema does not mark it as optional.

The use case here is to support flat `Remote` type where the "type" of
pve/pmg/pbs is a property which is present in the `Remote` struct
while being derived from the section type.

This will implicitly include and strip the type of the json object
after/before de/serializing.

Alternatives would be
- to mark the type as optional and just fill it out later when loading
  the data, but that is technically wrong...
- have a 2nd version of the struct with the type field removed and
  From/Into implemented, but that's even more unwieldy.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 14:14:42 +02:00
Wolfgang Bumiller
4d96aa52d2 section-config, api-macro: add SectionConfig enum support
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 14:14:42 +02:00
Wolfgang Bumiller
51d78fdd2b api-macro: handle renames in updater derive
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 14:14:42 +02:00
Wolfgang Bumiller
4c37db22d2 sys: bump d/control
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 14:14:33 +02:00
Wolfgang Bumiller
1cb2bf85ba sys: bump to 0.6.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-06 13:50:57 +02:00
Fabian Grünbichler
d0dab46539 sys: make fd::cwd crate-internal
it's not used by anything outside of proxmox-sys.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-08-06 13:37:36 +02:00
Fabian Grünbichler
dc0b9dec4a sys: adapt to IO Safety changes in rustc
`OwnedFd`s are now (rustc 1.80+) checked for validity when dropped in a debug
build, to catch usage after closing. Unfortunately those checks don't account
for the special value `AT_FDCWD` (-100) which is not a "real" FD, but a magic
constant used by many libc functions to signify operations starting at the
current working directory.

changing our `cwd` helper to open the CWD for real, instead of just returning
the magic value that pretends to be an FD, works around those limitations with
the least API churn.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-08-06 13:37:34 +02:00
Wolfgang Bumiller
9d921901d3 rest-server: update examples code to daemon split
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-08-01 10:27:44 +02:00
Wolfgang Bumiller
bedbaae252 router: bump to 2.2.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-31 10:08:25 +02:00
Wolfgang Bumiller
65715bc096 io, serde, schema: doc fixups
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-30 16:16:16 +02:00
Wolfgang Bumiller
ffd45c642f http: replace deprecated io_err_other
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-30 16:11:44 +02:00
Wolfgang Bumiller
482fd62423 io: drop the valgrind support code
valgrind-request is currently not packaged and we haven't used this in
a while...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-30 16:09:09 +02:00
Fabian Grünbichler
a44fda92ef bump proxmox-log to 0.2.1-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-07-29 09:27:18 +02:00
Wolfgang Bumiller
7c403de278 lang: d/control bump
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-26 13:23:53 +02:00
Wolfgang Bumiller
a7ba12d0b8 lang: bump to 1.4.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-26 13:13:29 +02:00
Wolfgang Bumiller
e3a5ff78f4 async, sys: replace deprecated io_err_other
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-26 13:09:33 +02:00
Wolfgang Bumiller
ce0b21805c lang: deprecate io_err_other
For regular error cases, `std::io::Error` can now also box errors and
provides an `Error::other()` function since rust 1.74.

For the case where it was used directly with strings
(io_err_other("string")) -> that's what `io_format_err!()` and
`io_bail!()` are for.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-26 12:14:03 +02:00
Wolfgang Bumiller
ea3c37fd36 sys: drop unused import
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-26 12:14:03 +02:00
Wolfgang Bumiller
034bb9cdda router: cli: add OutputFormat enum api type
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-26 11:46:33 +02:00
Wolfgang Bumiller
dd36fec23d sys: replace CStr::from_bytes_... with c"literals"
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-26 11:46:33 +02:00
Maximiliano Sandoval
ba304a4f83 compression: make Deflate{De, En}coderBuilder public
These structs are returned by the public method
`Deflate{En,De}coder::builder`.

Reported-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-07-26 11:43:01 +02:00
Wolfgang Bumiller
2598699fcb daemon: remove useless comment
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 16:30:14 +02:00
Wolfgang Bumiller
35372b5337 daemon: boxed FnOnce has been usable for a while
While technically an API break, we don't use the public API for this
anywhere and the trait we're changing is explicitly marked as
`#[doc(hidden)]`.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 16:27:32 +02:00
Wolfgang Bumiller
4502bc3b73 acme-api: bump to 0.1.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:33:36 +02:00
Wolfgang Bumiller
150f203209 auth-api: bump to 0.4.4
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:32:28 +02:00
Wolfgang Bumiller
15200b9f64 rest-server: bump to 0.7.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:32:01 +02:00
Wolfgang Bumiller
921d2bcc13 daemon: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:29:06 +02:00
Wolfgang Bumiller
f67c651929 systemd: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:29:06 +02:00
Wolfgang Bumiller
b54b4d3324 log: bump to 0.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:29:06 +02:00
Wolfgang Bumiller
d3abd366c4 introduce proxmox-daemon crate
split from rest-server:
- "state" module (shutdown/reload state)
- shutdown futures
- "daemon" module (named 'server' module in proxmox-daemon)
- command socket

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:25:50 +02:00
Wolfgang Bumiller
fb1a75d48f systemd: add fd-store support
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:25:50 +02:00
Wolfgang Bumiller
2783f062a6 update rest-server to use proxmox-systemd crate
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:25:50 +02:00
Wolfgang Bumiller
ab41d326e4 introduce proxmox-systemd crate
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:25:50 +02:00
Gabriel Goller
cecd08df58 log: remove unused init_logger argument
The `_application_name` argument is not used anymore. It was previously
used to set the correct journald unit-name, which is now handled
automatically.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-07-24 14:25:50 +02:00
Wolfgang Bumiller
a38b46b27d rest-server: drop proxmox-io dependency
Used only for read/writing a pid_t (an integer) to a socket.
The standard to_ne_bytes()/from_ne_bytes() should be sufficient here,
we already have libc::pid_t which we can use to get the correct type
namespace for accessing ::from_ne_bytes().

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:06:40 +02:00
Wolfgang Bumiller
c0e5776edd log: fix filter condition for journal layer
The condition was inverted, it would only send to the journal for
worker tasks.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-24 14:05:51 +02:00
Wolfgang Bumiller
f2130b69c8 router: bump to 2.2.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-22 09:07:22 +02:00
Wolfgang Bumiller
45a1e580c8 access-control: bump to 0.2.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-22 09:05:28 +02:00
Wolfgang Bumiller
5eb7cbd250 cli: deal with commands without positional args
When reaching the final command we relied on the positional parameters
"ending" the global option parsing. This means we did not get global
options at the end, and failed with "unknown option" instead.
Fix this by simply retaining unknown options in that case and not
stopping at positional parameters.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-22 09:03:56 +02:00
Wolfgang Bumiller
fee00addab access-control: add init_user_config() method
So that we can make sure root@pam exists at the product level.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-22 09:03:56 +02:00
Wolfgang Bumiller
140fc0ad08 access-control: cleanup use statements
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-22 09:03:56 +02:00
Maximiliano Sandoval
c8b975799b fix typos in strings
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-07-22 08:49:42 +02:00
Maximiliano Sandoval
c88cdd7e67 fix typos in variable and function names
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-07-22 08:49:42 +02:00
Maximiliano Sandoval
254a37ae07 fix typos in code documentation
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-07-22 08:49:42 +02:00
Maximiliano Sandoval
72ab48eb55 fix typos in rust api documentation
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-07-22 08:49:42 +02:00
Maximiliano Sandoval
a50d2c715e rest-server: Encode with zlib headers
As per [RFC9110] the Deflate encoding is a "zlib" data format. This
makes the rest-server compatible with the http-client.

[RFC9110] https://www.rfc-editor.org/rfc/rfc9110#field.content-encoding

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
Tested-by: Max Carrara <m.carrara@proxmox.com>
2024-07-22 08:11:14 +02:00
Gabriel Goller
500fb592f9 log: reorder filters as a small optimization
Reorder the filters for the journald layer. This sets the LevelFilter
last, which means tracing can disable all log statements lower than the
current level without evaluating the LogContext::exists function.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
 [ TL: note that this is just an optimization in the subject ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-07-18 12:11:43 +02:00
Wolfgang Bumiller
5262cefd34 router: bump to 2.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 14:28:07 +02:00
Wolfgang Bumiller
ff17bf5a2b router: don't deprecate generate_usage_str
This is used for the 'debug' binary to show the usage of an API
method.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 14:28:07 +02:00
Wolfgang Bumiller
3dc013bb57 schema: bump to 3.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 14:18:04 +02:00
Wolfgang Bumiller
4de22b9728 api-macro: fix warnigns
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
cff04a4502 router: cli: add parser option tests
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
492cac4346 router: cli: simplify parsing logic
Deduplicate the check for whether the argument exists by trying to
fetch the schema once and use check the option instead of calling
'.contains_key()' multiple times.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
9288c00372 router: cli: move freestanding new parse loop into its method
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
95c0614ccd router: cli: improve doc-gen global options handling
Passing the &GlobalOptions through is more telling than an opaque
Iterator<&str>...

Also: actually generate the property descriptions in non-ReST mode for
global options as well, so the `help` output for a specific command
includes the property documentation instead of only showing the name.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
d872eb9d7e router: cli: rework newline handling for doc and help output
The rules are as follows:
- An "item" by itself does neither start nor end with a newline.
- Where items are connected, the appropriate amount of newlines must
  be inserted, assuming items do not have any trailing newlines.

Otherwise this just gets WAY too confusing everywhere!

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
667fa6bc6b router: cli: doc generation with global options
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
69c2f94aab schema: drop extra newline in proeprty description
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
f02ce77ad6 schema: make wrap_text less awkward
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
dc7273e888 schema: drop trailing double-newlines in wrap_text
This is completely wrong and make working with it extremely annoying.
Whether or not there should be separation should be decided where
multiple elements are connected, they shouldn't automatically come
with a bunch of trailing new lines for absolutely no reason.

Places using this will need to be fixed as they get noticed.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
d240ef1e92 router: set help context on help invocation
instead of during parsing...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
83b3c1794a router: completion callbacks for global options
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
afe746b02f router: let completion take global options into account
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
420e238126 router: hook help/completion/docgen into new cli parser
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
eb5614adf1 router: new cli parser with global option support
This one does *explicitly* *not* support long options with a single
dash because it is too ambiguous if we want to add support for short
options at some point.

The parsing of the command line and invoking of the command is
separated. `CommandLine::parse` returns an `Invocation` which is
called and consumed via its `call` method.
This allows updating the CLI environment between parsing and invoking
the command, in order to allow *handling* the global options in
between those two steps if desired.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
59f1bdbe85 router: cli: store extra CLI args by type
The CLI environment can now contain ApiType structs which can be
accessed by their type.
The TypeId is used since the options inside must be unique anyway and
we can't have the same type specified multiple times. It also makes
for a somewhat convenient interface:

    env.take_global_option::<ConnectInfo>()

where ConnectInfo is a struct containing the server, user, port, ...
since these will not be passed as *parameters* to the API functions.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Wolfgang Bumiller
41b08323a7 router: AsAny: add as_any_mut
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-17 13:43:37 +02:00
Thomas Lamprecht
f78c28dd11 acme: update d/control
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-07-17 11:41:11 +02:00
Thomas Lamprecht
d1aa14eb71 apt: bump version to 0.11.2-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-07-17 11:39:46 +02:00
Thomas Lamprecht
f41664e087 workspace: bump dependency for apt-api-types to 1.0.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-07-17 10:07:43 +02:00
Thomas Lamprecht
301e268fc4 apt-api-types: bump version to 1.0.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-07-17 09:54:33 +02:00
Dietmar Maurer
79f2b89d29 apt: updates for changed api (digest as array)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-17 09:50:29 +02:00
Dietmar Maurer
bcca060a93 apt-api-types: fix backward compatibility by encoding digest as array
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-17 09:45:07 +02:00
Wolfgang Bumiller
282e00d429 syslog-api: bump to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-12 11:13:24 +02:00
Wolfgang Bumiller
870ec33574 log: documentation fixup
The scope and sync_scope methods simply activate the context, they
don't affect the counter, the counter is initialized when creating the
context with LogContext::new().

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-12 10:51:08 +02:00
Wolfgang Bumiller
64ff97d8e2 time-api: bump to 0.1.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:31:50 +02:00
Wolfgang Bumiller
ae92195687 network-api: bump to 0.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:28:39 +02:00
Wolfgang Bumiller
98adeb73f2 dns-api: bump to 0.1.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:27:13 +02:00
Wolfgang Bumiller
540e9a8134 auth-api: bump to 0.4.3
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:26:00 +02:00
Wolfgang Bumiller
e294d74026 acme-api: bump to 0.1.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:25:25 +02:00
Wolfgang Bumiller
978f28d67c subscription: bump to 0.4.5-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:24:52 +02:00
Wolfgang Bumiller
a6a9ca1d70 rrd: bump to 0.2.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:24:52 +02:00
Wolfgang Bumiller
9d1758dfa2 openid: bump to 0.10.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:24:52 +02:00
Wolfgang Bumiller
69e410d130 notify: bump to 0.4.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:24:52 +02:00
Wolfgang Bumiller
aa29c54859 shared-memory: bump to 0.3.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:24:52 +02:00
Wolfgang Bumiller
ad60e1bde5 rest-server: bump to 0.6.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:13:36 +02:00
Wolfgang Bumiller
b21034b485 http: bump to 0.9.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:13:36 +02:00
Wolfgang Bumiller
68125e67fc product-config: bump to 0.2.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 15:11:39 +02:00
Wolfgang Bumiller
24210a3a86 apt: bump 0.11.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:50:54 +02:00
Wolfgang Bumiller
bf02bebbac access-control: bump to 0.2.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:47:30 +02:00
Wolfgang Bumiller
355d949cd4 log: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:47:28 +02:00
Wolfgang Bumiller
1a00570898 sys: bump to 0.6.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:47:27 +02:00
Wolfgang Bumiller
36e552de47 worker-task: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:47:25 +02:00
Wolfgang Bumiller
4b9c907b68 log: introduce a shareable LogContext struct
Since hyper can spawn() more tasks, when we stop passing `WorkerTask`
references down the stack, we still need to be able to *inherit* the
current logging context. Hyper provides a way to replace its used
`spawn()` method, so we need to provide a way to reuse the logging
context.

Instead of having the `FileLogger` and warn counter separately
available with local-only access, put them behind an Arc<Mutex<>>.
Previously they already *were* behind an Arc<Mutex<>> as part of the
WorkerTaskState.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:45:26 +02:00
Wolfgang Bumiller
847a57740b new worker-task crate, move WorkerTaskContext from sys
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:43:37 +02:00
Wolfgang Bumiller
f3021e686a sys: remove email module
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:43:37 +02:00
Lukas Wagner
3c1c34043e notify: copy sendmail/forward fn's from proxmox_sys
proxmox_notify is the only user of those functions, so it makes
sense to move them here. A future commit will mark the
original functions from proxmox_sys as deprecated.

The functions were slightly modified, mostly to not
rely on anyhow for error reporting. Also they
are now private functions.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-07-11 14:43:37 +02:00
Wolfgang Bumiller
c6cccff92e sys: remove deprecations
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:43:37 +02:00
Wolfgang Bumiller
3dde52e5ce log: deny(unsafe_op_in_unsafe_fn) and feature(doc_cfg)
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:43:37 +02:00
Wolfgang Bumiller
cbc30882e7 acme-api: adapt to tracing infrastructure
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:43:37 +02:00
Wolfgang Bumiller
2fd7b13fbe log: reexport the logging macros
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:43:37 +02:00
Gabriel Goller
ddb91a6594 enable tracing logger, remove task_log macros
Enable the tracing-system by setting the LOGGER task local variable
to a instance of a FileLogger and initializing the WARN_COUNTER.
Removed the task_log! macros and some occurences.

Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
[WB: remove flog! import in doctests]
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:43:37 +02:00
Gabriel Goller
0550659cd1 proxmox-log: add tracing infrastructure
Add the `proxmox_log` crate which includes the new logging infra.
Export the `init_logger` function, which creates the `tracing` logger
that includes the default subscriber and two layer.

The first layer comes from the tracing-journald crate and logs
everything that does not come from a worker-task/thread to the syslog.
The second layer filters the exact opposite and writes the logs into the
corresponding task-log file.

Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-07-11 14:43:37 +02:00
Wolfgang Bumiller
f1920d9b94 rest-server: fix a build warning
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-11 14:43:37 +02:00
Wolfgang Bumiller
9c3e4d5ccf rest-server: bump version to 0.5.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-10 12:44:29 +02:00
Wolfgang Bumiller
ce802d8320 rest-server: drop some unnecessary 'pub's
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-10 12:37:51 +02:00
Max Carrara
f6bacbb58f fix #5105: rest-server: connection: overhaul TLS handshake check logic
On rare occasions, the TLS "client hello" message [1] is delayed after
a connection with the server was established, which causes HTTPS
requests to fail before TLS was even negotiated. In these cases, the
server would incorrectly respond with "HTTP/1.1 400 Bad Request"
instead of closing the connection (or similar).

The reasons for the "client hello" being delayed seem to vary; one
user noticed that the issue went away completely after they turned off
UFW [2]. Another user noticed (during private correspondence) that the
issue only appeared when connecting to their PBS instance via WAN, but
not from within their VPN. In the WAN case a firewall was also
present. The same user kindly provided tcpdumps and strace logs on
request.

The issue was finally reproduced with the following Python script:

  import socket
  import time

  HOST: str = ...
  PORT: int = ...

  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
      sock.connect((HOST, PORT))
      time.sleep(1.5) # simulate firewall / proxy / etc. delay
      sock.sendall(b"\x16\x03\x01\x02\x00")
      data = sock.recv(256)
      print(data)

The additional delay before sending the first 5 bytes of the "client
hello" message causes the handshake checking logic to incorrectly fall
back to plain HTTP.

All of this is fixed by the following:

  1. Increase the timeout duration to 10 seconds (from 1)
  2. Instead of falling back to plain HTTP, refuse to accept the
     connection if the TLS handshake wasn't initiated before the
     timeout limit is reached
  3. Only accept plain HTTP if the first 5 bytes do not correspond to
     a TLS handshake fragment [3]
  4. Do not take the last number of bytes that were in the buffer into
     account; instead, only perform the actual handshake check if
     5 bytes are in the peek buffer using some of tokio's low-level
     functionality

Regarding 1.: This should be generous enough for any client to be able
to initiate a TLS handshake, despite its surrounding circumstances.

Regarding 4.: While this is not 100% related to the issue, peeking into
the buffer in this manner should ensure that our implementation here
remains correct, even if the kernel's underlying behaviour regarding
edge-triggering is changed [4]. At the same time, there's no need for
busy-waiting and continuously yielding to the event loop anymore.

[1]: https://www.rfc-editor.org/rfc/rfc8446.html#section-4.1.2
[2]: https://forum.proxmox.com/threads/disable-default-http-redirects-on-8007.142312/post-675352
[3]: https://www.rfc-editor.org/rfc/rfc8446.html#section-5.1
[4]: https://lwn.net/Articles/864947/

Signed-off-by: Max Carrara <m.carrara@proxmox.com>
2024-07-10 12:22:17 +02:00
Max Carrara
847ca5d14d rest-server: connection: log peer address on error
.. in order to make debugging easier and logs more helpful.

Signed-off-by: Max Carrara <m.carrara@proxmox.com>
2024-07-10 12:22:17 +02:00
Max Carrara
0d3e7c8eaf rest-server: connection: clean up accept data flow
This adds the structs `AcceptState` and `AcceptFlags` and adapts
relevant method signatures of `AcceptBuilder` accordingly. This makes
it easier to add further parameters in the future.

Signed-off-by: Max Carrara <m.carrara@proxmox.com>
2024-07-10 12:22:17 +02:00
Wolfgang Bumiller
9f33be3078 http: bump version to 0.9.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-10 12:17:59 +02:00
Wolfgang Bumiller
2269153522 compression: bump version to 0.2.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-10 12:15:54 +02:00
Maximiliano Sandoval
4d1c4ec829 http: teach the Client how to decode deflate content
The Backup Server can compress the content using deflate so we teach the
client how to decode it.

If a request is sent with the `Accept-Encoding` [2] header set to
`deflate`, and the response's `Content-Encoding` [1] header is equal to
`deflate` we wrap the Body stream with a stream that can decode `zlib`
on the run.

Note that from the `Accept-Encoding` docs [2], the `deflate` encoding is
actually `zlib`.

This can be also tested against
http://eu.httpbin.org/#/Response_formats/get_deflate by adding the
following test:

```rust
    #[tokio::test]
    async fn test_client() {
        let client = Client::new();
        let headers = HashMap::from([(
            hyper::header::ACCEPT_ENCODING.to_string(),
            "deflate".to_string(),
        )]);
        let response = client
            .get_string("https://eu.httpbin.org/deflate", Some(&headers))
            .await;
        assert!(response.is_ok());
    }
```

at `proxmox-http/src/client/simple.rs` and running

```
cargo test --features=client,client-trait
```

[1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
[2] https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding

Suggested-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
Tested-by: Max Carrara <m.carrara@proxmox.com>
2024-07-10 11:37:36 +02:00
Maximiliano Sandoval
8b8957b5ba compression: deflate: add test module
We test the deflate encoder against the deflate decoder using (or not)
zlib and with different small buffer sizes. We also test compression and
decompression against the flate2 crate.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
Tested-by: Max Carrara <m.carrara@proxmox.com>
2024-07-10 11:37:36 +02:00
Maximiliano Sandoval
e6ca8d6049 compression: deflate: add a decoder
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
Tested-by: Max Carrara <m.carrara@proxmox.com>
2024-07-10 11:37:36 +02:00
Maximiliano Sandoval
3f09610d86 compression: deflate: add builder pattern
This allows creating a encoder in a more general way and allows to
specify whether we want to set zlib headers. This is useful to compress
HTTP traffic, as per [1].

[1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding#directives

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
Tested-by: Max Carrara <m.carrara@proxmox.com>
2024-07-10 11:37:36 +02:00
Maximiliano Sandoval
70a71e0c8e compression: deflate: move encoder into a mod
This allows to add a decompression mod inside the deflate mod. This does
not touch the public API.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
Tested-by: Max Carrara <m.carrara@proxmox.com>
2024-07-10 11:37:36 +02:00
Dietmar Maurer
7ae351854e apt-api-types: derive PartialEq for all types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-09 13:15:16 +02:00
Dietmar Maurer
54dcb0942c syslog-api: add helper for mini-journalreader
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-09 11:29:50 +02:00
Dietmar Maurer
4a69e1cf64 use new apt/apt-api-types crate 2024-07-08 15:28:59 +02:00
Wolfgang Bumiller
3479a9afe4 apt: bump version to 0.11.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-08 15:21:49 +02:00
Wolfgang Bumiller
67671399e8 apt-api-types: bump version to 1.0.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-08 15:20:40 +02:00
Dietmar Maurer
75c62574c6 apt: avoid global apt config
Because it was only used for the test setup. Instead, we simply
add an apt_lists_dir parameter where we need it.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-08 15:17:10 +02:00
Dietmar Maurer
f451a643ae apt: add cache feature
Save/read package state from a file, and add the api functions to manipulate
that state.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-08 15:17:10 +02:00
Dietmar Maurer
f536a91b2f apt: use api types from apt-api-types crate
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-08 15:17:10 +02:00
Dietmar Maurer
e56e39185a apt: avoid direct impl on api types (use traits instead)
So that we can use api types from expternal crate proxmox-apt-api-types.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-08 15:17:10 +02:00
Dietmar Maurer
960131925e apt-api-types: use serde-plain to display/parse enums
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-08 15:17:10 +02:00
Dietmar Maurer
b11dbfd105 apt-api-types: new crate
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-07-08 15:17:10 +02:00
Wolfgang Bumiller
9ec5a48701 access-control: bump to 0.2.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-04 14:33:42 +02:00
Wolfgang Bumiller
e8b5ad6b45 access-control: use ConfigDigest for digests
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-04 14:31:24 +02:00
Wolfgang Bumiller
3545d67b1f auth-api: bump version to 0.4.2
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-03 15:26:23 +02:00
Wolfgang Bumiller
2ca71fe601 tfa: bump version to 5.0.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-03 15:25:39 +02:00
Maximiliano Sandoval
5079ff6a3c tfa: webauthn: serialize OriginUrl following RFC6454
We serialize `OriginUrl` using the ASCII serialization mentioned at
[RFC6454] section 6.2 or [1]. Note that the unicode serialization is not
used widely adopted [2].

Note that `url::Url` serialize with a trailign slash, e.g.
https://foo.bar serializes as https://foo.bar/ which is not the origin
for this domain.

[RFC6454] https://www.rfc-editor.org/rfc/rfc6454
[1] https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin
[2] https://html.spec.whatwg.org/multipage/browsers.html#unicode-serialisation-of-an-origin

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-07-03 15:20:30 +02:00
Wolfgang Bumiller
0652d81977 tree-wide: enable doc_cfg and doc_auto_cfg for docs
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-07-02 11:59:53 +02:00
Wolfgang Bumiller
635c8bcbed client: clippy: factor out complex type
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-28 11:38:59 +02:00
Wolfgang Bumiller
1f9cb87576 sys: process_locker: explicitly don't truncate the lock file
clippy rightfully complains about a create() with an unspecified
truncation behavior. This file has no contents so let's just not
truncate it in case we ever want to also have data in it...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-28 11:18:04 +02:00
Wolfgang Bumiller
b2dd0117d1 acme-api: allow clippy::manual_map where .map doesn't make sense
The code chooses whichever one of a multitude of functions returns
Some, switching to .map for the final else would make it less
readable.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-28 11:14:30 +02:00
Wolfgang Bumiller
a3c1d9d456 network-api: drop unnecessary string allocations
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-28 11:09:48 +02:00
Maximiliano Sandoval
52cf0c05f5 auth-api: do not clone struct implementing Copy
Fixes the clippy warning:

warning: using `clone` on type `Option<&dyn AuthContext>` which implements the `Copy` trait
   --> proxmox-auth-api/src/api/mod.rs:111:5
    |
111 | /     AUTH_CONTEXT
112 | |         .lock()
113 | |         .unwrap()
114 | |         .clone()
    | |________________^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
    = note: `#[warn(clippy::clone_on_copy)]` on by default
help: try dereferencing it
    |
111 ~     (*AUTH_CONTEXT
112 +         .lock()
113 +         .unwrap())
    |

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 11:09:48 +02:00
Maximiliano Sandoval
229cc6ae02 acl: directly return struct rather than a binding
Fixes the following clippy warning:

warning: returning the result of a `let` binding from a block
   --> proxmox-access-control/src/acl.rs:687:13
    |
686 |             let config = TestAcmConfig { roles };
    |             ------------------------------------- unnecessary `let` binding
687 |             config
    |             ^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return
    = note: `#[warn(clippy::let_and_return)]` on by default
help: return the expression directly
    |
686 ~
687 ~             TestAcmConfig { roles }
    |

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
a4b57d6c3c acme: derive Default for Status
Fixes the clippy warning:

warning: this `impl` can be derived
  --> proxmox-acme/src/order.rs:36:1
   |
36 | / impl Default for Status {
37 | |     fn default() -> Self {
38 | |         Status::New
39 | |     }
40 | | }
   | |_^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls
   = note: `#[warn(clippy::derivable_impls)]` on by default
   = help: remove the manual implementation...
help: ...and instead derive it...
   |
12 + #[derive(Default)]
13 | pub enum Status {
   |
help: ...and mark the default variant
   |
15 ~     #[default]
16 ~     New,
   |

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
baccbda477 acme: elide explicit lifetimes
Fixes the clippy warning:

warning: the following explicit lifetimes could be elided: 'a
  --> proxmox-acme/src/async_client.rs:65:30
   |
65 |     pub async fn new_account<'a>(
   |                              ^^
66 |         &'a mut self,
   |          ^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
   = note: `#[warn(clippy::needless_lifetimes)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
6b1e4b83bb http: remove unnecessary cast
Fixes the clippy warning:

warning: casting to the same type is unnecessary (`usize` -> `usize`)
   --> proxmox-http/src/websocket/mod.rs:446:40
    |
446 |             mask.copy_from_slice(&data[mask_offset as usize..payload_offset as usize]);
    |                                        ^^^^^^^^^^^^^^^^^^^^ help: try: `mask_offset`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
    = note: `#[warn(clippy::unnecessary_cast)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
b006e66361 http: remove redundant redefinition of binding
Fixes the clippy error:

error: redundant redefinition of a binding `data`
   --> proxmox-http/src/websocket/mod.rs:375:9
    |
375 |         let data = data;
    |         ^^^^^^^^^^^^^^^^
    |
help: `data` is initially defined here
   --> proxmox-http/src/websocket/mod.rs:369:27
    |
369 |     pub fn try_from_bytes(data: &[u8]) -> Result<Option<FrameHeader>, WebSocketError> {
    |                           ^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals
    = note: `#[deny(clippy::redundant_locals)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
82bc4db723 network-api: remove useless uses of format!
Fixes the clippy warning:

warning: useless use of `format!`
   --> proxmox-network-api/src/config/mod.rs:632:13
    |
632 | /             format!(
633 | |                 r#"
634 | | iface enp3s0 inet static
635 | |     address 10.0.0.100/16
636 | |     gateway 10.0.0.1"#
637 | |             )
    | |_____________^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
help: consider using `.to_string()`
    |
632 ~             r#"
633 + iface enp3s0 inet static
634 ~     address 10.0.0.100/16
635 ~     gateway 10.0.0.1"#.to_string()

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
d965713565 shared-memory: remove unneeded generic parameter
Fixes the clippy warning:

warning: type parameter `T` goes unused in function definition
  --> proxmox-shared-memory/tests/raw_shared_mutex.rs:80:19
   |
80 | fn create_test_dir<T: Init>(filename: &str) -> Option<PathBuf> {
   |                   ^^^^^^^^^ help: consider removing the parameter
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_type_parameters
   = note: `#[warn(clippy::extra_unused_type_parameters)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
a87d52dad3 remove unneeded returns
Fixes the clippy warning:

warning: unneeded `return` statement
   --> proxmox-tfa/src/api/mod.rs:468:17
    |
468 | /                 return TfaResult::Failure {
469 | |                     needs_saving: true,
470 | |                     tfa_limit_reached,
471 | |                     totp_limit_reached,
472 | |                 };
    | |_________________^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
    = note: `#[warn(clippy::needless_return)]` on by default
help: remove `return`
    |
468 ~                 TfaResult::Failure {
469 +                     needs_saving: true,
470 +                     tfa_limit_reached,
471 +                     totp_limit_reached,
472 ~                 }
    |

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
d07a0243f4 use const blocks in thread_local! calls
Fixes the clippy warning:

warning: initializer for `thread_local` value can be made `const`
   --> proxmox-router/src/cli/command.rs:221:71
    |
221 |     static HELP_CONTEXT: RefCell<Option<Arc<CommandLineInterface>>> = RefCell::new(None);
    |                                                                       ^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(None) }`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#thread_local_initializer_can_be_made_const
    = note: `#[warn(clippy::thread_local_initializer_can_be_made_const)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
e3602c1943 acl: remove null pointer cast
Fixes the clippy warning:

warning: casting raw pointers to the same type and constness is unnecessary (`*mut fs::acl::libc::c_void` -> `*mut fs::acl::libc::c_void`)
   --> proxmox-sys/src/fs/acl.rs:130:23
    |
130 |         let mut ptr = ptr::null_mut() as *mut c_void;
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr::null_mut()`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
    = note: `#[warn(clippy::unnecessary_cast)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
30461d091c acme: remove duplicated attribute
Fixes the following clippy warning:

warning: duplicated attribute
  --> proxmox-acme/src/lib.rs:42:7
   |
42 | #[cfg(feature = "impl")]
   |       ^^^^^^^^^^^^^^^^
   |
note: first defined here
  --> proxmox-acme/src/lib.rs:41:7
   |
41 | #[cfg(feature = "impl")]
   |       ^^^^^^^^^^^^^^^^
help: remove this attribute
  --> proxmox-acme/src/lib.rs:42:7
   |
42 | #[cfg(feature = "impl")]
   |       ^^^^^^^^^^^^^^^^
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes
   = note: `#[warn(clippy::duplicated_attributes)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
bf68d540b8 time-api: remove redundant field names
Fixes the clippy warning:

warning: redundant field names in struct initialization
  --> proxmox-time-api/src/time_impl.rs:53:9
   |
53 |         localtime: localtime,
   |         ^^^^^^^^^^^^^^^^^^^^ help: replace it with: `localtime`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
18dda8106b remove unnecesary pub(self)
Fixes the clippy warning:

warning: unnecessary `pub(self)`
    --> proxmox-tfa/src/api/mod.rs:1268:1
     |
1268 | pub(self) fn bool_is_false(v: &bool) -> bool {
     | ^^^^^^^^^ help: remove it
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pub_self

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
aff76f9e0e remove needless borrows
Fixes the following clippy warnings:

warning: the borrowed expression implements the required traits
  --> proxmox-tfa/src/api/recovery.rs:86:24
   |
86 |         Ok(hex::encode(&hmac))
   |                        ^^^^^ help: change this to: `hmac`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrows_for_generic_args

and

warning: this expression creates a reference which is immediately dereferenced by the compiler
   --> proxmox-network-api/src/api_impl.rs:108:47
    |
108 |                 interface.set_bond_slave_list(&slaves)?;
    |                                               ^^^^^^^ help: change this to: `slaves`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
    = note: `#[warn(clippy::needless_borrow)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
cb4acf6d93 use contains_key instead of .get().is_{some, none}()
Fixes the clippy lints:

warning: unnecessary use of `get("lo").is_none()`
   --> proxmox-network-api/src/config/parser.rs:603:30
    |
603 |         if config.interfaces.get("lo").is_none() {
    |            ------------------^^^^^^^^^^^^^^^^^^^
    |            |
    |            help: replace it with: `!config.interfaces.contains_key("lo")`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_get_then_check
    = note: `#[warn(clippy::unnecessary_get_then_check)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Maximiliano Sandoval
16aa2b74bc use unwrap_or_default instead of unwrap_or(Vec::new)
Fixes the clippy warning:

warning: use of `unwrap_or_else` to construct default value
    --> proxmox-tfa/src/api/mod.rs:1355:43
     |
1355 |         |cap| cap.map(Vec::with_capacity).unwrap_or_else(Vec::new),
     |                                           ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()`
     |
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_default
     = note: `#[warn(clippy::unwrap_or_default)]` on by default

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-06-28 10:22:58 +02:00
Fabian Grünbichler
8c9eb85706 ldap: fix Cargo.toml syntax
this throws a warning now..

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-25 13:54:26 +02:00
Fabian Grünbichler
af353659c8 run cargo fmt
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-24 10:02:31 +02:00
Fabian Grünbichler
1ba198265c trivial clippy fix
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-24 10:01:13 +02:00
Wolfgang Bumiller
3663ae8255 async: bump to 0.4.2
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 14:30:10 +02:00
Wolfgang Bumiller
0e17606caf rest-server: bump to 0.5.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 14:06:12 +02:00
Wolfgang Bumiller
51680ea77e openid: bump to 0.10.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 14:04:41 +02:00
Wolfgang Bumiller
0a0d8a4d73 notify: bump to 0.4.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 14:02:10 +02:00
Wolfgang Bumiller
eededbeb93 dns-api: bump to 0.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 14:00:49 +02:00
Wolfgang Bumiller
eca6f31a22 compression: bump to 0.2.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:59:46 +02:00
Wolfgang Bumiller
80baf82df2 tfa: bump to 4.1.3
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:58:55 +02:00
Wolfgang Bumiller
f1a5583932 acme-api: bump to 0.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:57:53 +02:00
Wolfgang Bumiller
2f77909a6d access-control: bump to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:56:31 +02:00
Wolfgang Bumiller
19200f7415 rrd: bump to 0.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:56:06 +02:00
Wolfgang Bumiller
54bc3abdfd subscription: bump to 0.4.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:55:39 +02:00
Wolfgang Bumiller
821fdb63b0 serde: bump to 0.1.2
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:55:39 +02:00
Wolfgang Bumiller
3cd7223cc1 sys: bump to 0.5.8-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:55:39 +02:00
Wolfgang Bumiller
ab41b5ce43 time-api: bump to 0.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:49:56 +02:00
Wolfgang Bumiller
bb8460bc0f time: bump to 2.0.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:49:56 +02:00
Wolfgang Bumiller
214dbdf9a5 time: remove deprecated functions
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:48:42 +02:00
Wolfgang Bumiller
533954ed38 bump bitflags dependency to 2.4
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 13:48:32 +02:00
Fabian Grünbichler
c96ad06247 move .cargo/config to .cargo/config.toml
the old location has been deprecated for a while, and rustc 1.78 will start to warn about it.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-20 12:29:30 +02:00
Wolfgang Bumiller
0f33d603ef router: bump to 2.1.5-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 10:56:05 +02:00
Wolfgang Bumiller
57fb1004b8 sys: bump to 0.5.7-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 10:56:05 +02:00
Wolfgang Bumiller
d8eb6d1bde lang: bump to 1.3.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 10:56:05 +02:00
Wolfgang Bumiller
b16922860a sys: make xattr CStrs constants, repalce c_str! macro
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 10:56:05 +02:00
Wolfgang Bumiller
0233c8c63b router: repalce c_str! with c"literals"
we can now drop the proxmox-lang dependency here

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 10:55:10 +02:00
Wolfgang Bumiller
38992a588a lang: deprecate c_str! and offsetof
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 10:55:10 +02:00
Wolfgang Bumiller
2e9526dcdd tfa: fix a compile warning
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-20 10:55:10 +02:00
Fabian Grünbichler
937d985489 build: adapt workspace member command
to work with cargo 1.77

Originally-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-19 16:04:35 +02:00
Wolfgang Bumiller
1c5f27014c time: drop TryFrom/TryInto imports
they're in the prelude by now

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 14:55:41 +02:00
Shannon Sterz
a4be52d4a6 time: exclude certain use statements and impl block on wasm32
otherwise the compiler will complain that they aren't used when
compiling the code for wasm32.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-06-19 14:55:41 +02:00
Wolfgang Bumiller
c547ea07ae access-control: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 14:45:52 +02:00
Wolfgang Bumiller
4197c0e26e access-control: minor code cleanup
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 14:42:43 +02:00
Wolfgang Bumiller
5daf898b14 access-control: cleanup comment in Cargo.toml
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 14:42:43 +02:00
Shannon Sterz
46d8423d72 access-control: split crate in default and impl features
this way the types defined in this crate can be re-used in places
without necessarily having to use the ACL, token shadow and
(cached) user config implementations.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-06-19 14:42:43 +02:00
Shannon Sterz
1dc88b5e3c access-control: move to flatten User into UserWithToken
Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-06-19 14:42:41 +02:00
Shannon Sterz
48bd72763f access-control: increment user cache generation when saving acl config
since `CachedUserInfo` takes care of both, the user config and the acl
config, we need to also bump the cache generation when storing the
acl config.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-06-19 14:42:39 +02:00
Shannon Sterz
84537a02b1 access-control: factor out user config handling
this commit factors out the user config. it also add two new functions
to the `AccessControlConfig` trait to handle caching in a more
generalized way.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-06-19 13:38:56 +02:00
Shannon Sterz
ed6a17cec9 access-control: make token shadow implementation re-usable
this commit factors out the token shadow implementation from
`proxmox-backup` so it can be used in other products.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-06-19 13:38:54 +02:00
Shannon Sterz
47eeecf711 access-control: define User, UserWithTokens and ApiTokens types
these types are used by the user config in `proxmox-backup` server.
this commit factors them out so we can re-use them in other products
as well as this crate.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-06-19 13:38:52 +02:00
Shannon Sterz
86ffeef24a access-control: add the proxmox-access crate to reuse acl trees
this commit factors out the acl tree from proxmox-backup so we can
re-use it accross other products. to use it, the product needs to
implement the `AcmConfig` trait and provide this crate with a
location to safe its configuration files.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
2024-06-19 13:38:50 +02:00
Wolfgang Bumiller
c336cb9ab7 io: bump to 1.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 12:21:43 +02:00
Wolfgang Bumiller
74ecd47421 lang: bump to 1.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 12:20:57 +02:00
Wolfgang Bumiller
36032b892b time-api: bump to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 12:14:04 +02:00
Wolfgang Bumiller
dfa1c0ce39 network-api: bump to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 12:13:08 +02:00
Wolfgang Bumiller
ef7d4c6155 dns-api: bump to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 12:11:43 +02:00
Wolfgang Bumiller
90f954005a auth-api: bump to 0.4.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 12:10:41 +02:00
Wolfgang Bumiller
a0c1369000 acme-api: bump to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 12:08:04 +02:00
Wolfgang Bumiller
e2cd917394 product-config: bump to 0.2.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 12:07:37 +02:00
Wolfgang Bumiller
cf5efb5c0a cleanup use statements
much more merge friendly this way...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 11:52:39 +02:00
Wolfgang Bumiller
e20cdbf8e2 router: bump to 2.1.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 11:33:02 +02:00
Wolfgang Bumiller
29b55dbcb3 router: make regex dep optional
It's only used in cli code.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 10:26:20 +02:00
Gabriel Goller
e4afb0fe20 router: cli: add confirmation helper
Add confirmation helper that outputs a prompt and lets the user
confirm or deny it.
Implemented to close #4763.

Co-authored-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-06-19 10:26:20 +02:00
Gabriel Goller
8240e5022f router: cli: print fatal errors including causes
as a first step of improving our error handling story, printing context
and causes if the error contains them.

The downside to adding context is that the default Display implementation
will *just* print the context, which hides the root cause. This is why
we print the errors using the pretty-print formatter in this change.

Originally-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
[WB: prefix commit message with crate]
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-19 10:15:51 +02:00
Wolfgang Bumiller
5295da1b8a sys: bump d/control
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-17 14:10:56 +02:00
Wolfgang Bumiller
245d1ec2c1 sys: bmp to 0.5.6-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-17 14:09:19 +02:00
Dietmar Maurer
b25edb67de sys: use anyhow Error type for create_dir, and improve error messages
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-13 11:51:44 +02:00
Dietmar Maurer
86898b9a59 Revert "sys: cleanup, remove unnecessary crate prefix"
This reverts commit 26922d179685d24bbd7697433a095a4066310c9a, because
it is necessary.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-13 11:51:44 +02:00
Wolfgang Bumiller
e5c8d70324 auth-api: add PasswordAuthenticator
This is the PbsAuthenticator with the hardcoded shadow.json/lock
configurable.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-13 11:14:36 +02:00
Wolfgang Bumiller
c12bbf6241 product-config: add open_secret_lockfile
We need this for things like shadow.json.lock.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-13 11:14:36 +02:00
Dietmar Maurer
26922d1796 sys: cleanup, remove unnecessary crate prefix
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-13 10:20:18 +02:00
Dietmar Maurer
0b17987c67 acme-api: show all certificate subject_alt_names (DNS, IP, EMAIL, URI)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-11 14:24:23 +02:00
Wolfgang Bumiller
2c2475da5e simple-config: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-07 11:06:08 +02:00
Dietmar Maurer
7e4121d26e acme-api: add function to extract certificate data from .pem data
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-06 14:07:43 +02:00
Dietmar Maurer
fcaa4f6758 acme-api: implement funtion to create self signed certificates
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-06 14:07:43 +02:00
Fabian Grünbichler
f7a22604ae apt: update d/control
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-06 11:40:49 +02:00
Fabian Grünbichler
818ddf1283 bump proxmox-apt to 0.10.10-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-06 11:40:49 +02:00
Fabian Grünbichler
4126a83e05 apt: fix test output dir
under autopkgtest, the rundir is not writable, but cargo gives us a tmpdir that
we can use in all cases.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-06 11:40:49 +02:00
Fabian Grünbichler
6f532dfb7d various clippy fixes
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-06 11:40:49 +02:00
Dietmar Maurer
7a8e948ee7 acme-api: pass parameters by reference
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-06 11:09:56 +02:00
Fiona Ebner
2c2497e5be fix #5513: apt: do not assume that sources.list file exists
Some users might want to switch to using only the newer .sources files
already, which Debian is going to switch to in the long run.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2024-06-06 11:06:41 +02:00
Dietmar Maurer
04505ada7a acme-api: implement revoke certificate helper
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-06 10:23:35 +02:00
Dietmar Maurer
53ff71772f simple-config: new crate to read/write proxmox simple text config files
Copied from proxmox-backup/src/tools/config.rs

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-06 10:16:28 +02:00
Fabian Grünbichler
a17430b38f run cargo fmt
(again)

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-06-05 13:31:06 +02:00
Wolfgang Bumiller
7ab17e262c acme-api: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-05 12:12:52 +02:00
Wolfgang Bumiller
3787764db7 time-api: bump version to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-05 11:28:59 +02:00
Wolfgang Bumiller
c3d9d21308 syslog-api: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-05 11:28:10 +02:00
Wolfgang Bumiller
af75a203da network-api: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-05 11:27:30 +02:00
Wolfgang Bumiller
3eea0fd8ce dns-api: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-05 11:27:16 +02:00
Wolfgang Bumiller
1a0b39710c product-config: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-05 11:27:16 +02:00
Wolfgang Bumiller
053bb3d3d3 config-digest: bump to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-06-05 11:24:38 +02:00
Dietmar Maurer
10f32d4312 acme-api: use replace_secret_config to write acme config files
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-04 13:14:20 +02:00
Dietmar Maurer
ccbef4be87 acme-api: use create_secret_dir from product-config
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-04 12:56:57 +02:00
Dietmar Maurer
cb971b402f product-config: new create_secret_dir function
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-04 12:53:41 +02:00
Dietmar Maurer
6c30be2280 product-config: code cleanup
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-04 12:53:18 +02:00
Dietmar Maurer
8219565d6a acme-api: create all directorties inside init 2024-06-04 12:22:16 +02:00
Dietmar Maurer
2270f7bf94 product_config: introduce priviledged user.
Normally root, but can be the same as the api_user if the product
does not use priviledge separation.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-04 11:14:07 +02:00
Dietmar Maurer
0033f67e37 product-config: export get_api_user
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-04 11:02:23 +02:00
Dietmar Maurer
3aa07c117b acme-api: export ChallengeSchemaWrapper
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 12:55:43 +02:00
Dietmar Maurer
cae2b556fa acme-api: export account_config_filename
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 12:29:16 +02:00
Dietmar Maurer
c5731f916b acme-api: make register_account directory parameter optional
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 10:50:12 +02:00
Dietmar Maurer
95ea61183f acme-api: export known directories
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 10:40:12 +02:00
Dietmar Maurer
0582a13281 acme-api: export api types at top-level
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 10:03:06 +02:00
Dietmar Maurer
5250493e05 syslog-api: add debian control file
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 09:32:11 +02:00
Dietmar Maurer
a334886f14 network-api: add debian control file
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 09:29:57 +02:00
Dietmar Maurer
6c1798fb31 time-api: add copyright file
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 09:27:38 +02:00
Dietmar Maurer
bd5c1ade4b dns-api: add debian control file
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 09:24:26 +02:00
Dietmar Maurer
ba49720837 dns-api: avoid auto-generated cargo features
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 09:21:42 +02:00
Dietmar Maurer
3de2812254 product-config: add debian control file
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 09:14:14 +02:00
Dietmar Maurer
2665b566c0 acme-api: remove stale feature gate and always compile api types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-06-03 09:08:16 +02:00
Dietmar Maurer
79a6f97c39 acme-api: commit missing file
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-31 12:35:42 +02:00
Dietmar Maurer
5e00ee7bb0 acme-api: remove useless api-types feature
We always need those types, so there is no need to make this a feature.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-31 12:23:05 +02:00
Dietmar Maurer
d152e47d78 acme-api: add init method to setup directories
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-31 12:12:23 +02:00
Dietmar Maurer
e913330e09 product-config: simplify by removing the configuration directory
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-31 11:29:59 +02:00
Dietmar Maurer
40f812f324 remove system-management-api
Which is now split into separate crates:

- time-api
- network-api
- dns-api
- syslog-api
2024-05-30 09:53:59 +02:00
Dietmar Maurer
f6bcb6b50b syslog-api: new crate, split out from system-management-api 2024-05-30 09:44:48 +02:00
Dietmar Maurer
6bb74338b4 network-api: new crate, split out from system-management-api
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-30 09:27:49 +02:00
Dietmar Maurer
83b6f673b3 time-api: new crate, split out from system-managent-api
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-30 09:06:19 +02:00
Dietmar Maurer
b7f0cc7c1e dns-api: new crate, split out from system-management-api
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-30 08:44:50 +02:00
Dietmar Maurer
4768ad2200 product-config: remove digest implementation (move to proxmox-config-digest crate)
And use the new proxmox-config-digest crate instead.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-29 18:41:28 +02:00
Dietmar Maurer
34b21106dd config-digest: split out config digest api type into separate crate
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-29 17:59:03 +02:00
Dietmar Maurer
3497e9edc7 sys: use 0750 as default directory permissions
Should not make a difference because default umask is 022 ...

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-29 11:04:41 +02:00
Dietmar Maurer
06d25870ed product-config: remove functions to check permissions, which are now in proxmox-sys.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-29 10:22:05 +02:00
Dietmar Maurer
57723e98fd sys: add helpers to check file and directory permissions
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-29 10:14:24 +02:00
Dietmar Maurer
37c9dbf1eb sys: create options: make file parameter generic
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-29 09:32:48 +02:00
Dietmar Maurer
237f6218b0 product-config: factor out methods to create different file creation options
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-29 07:47:17 +02:00
Dietmar Maurer
7cd240bbad product-config: use Path instead of str everywhere
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-29 06:52:04 +02:00
Wolfgang Bumiller
484f12f3aa lang: drop commented-out c_str! implementation variants
This was an old version of a const-fn compatible checked c_str
implementation which was never enabled.

When we get rust 1.72, `CStr::from_bytes_with_nul` becomes usable in
const contexts.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-05-24 12:58:06 +02:00
Gabriel Goller
1d68cc33a3 metrics: influxdb test uri creation
Extract the URI creation for write and health URIs. Add unit test to
test the encoding of special characters in the organization and bucket
parameters.

Follow-up-to: bfa73aad ("metrics: encode influxdb org and bucket parameters")

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>

FG: downgraded form_urlencoded version to packaged one
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-05-24 10:10:38 +02:00
Wolfgang Bumiller
aae8a03dc4 auth-api: bump version to 0.4.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-05-22 16:04:04 +02:00
Wolfgang Bumiller
bf9dc73246 sys: bump version to 0.5.5-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-05-22 15:48:34 +02:00
Wolfgang Bumiller
9c95b4d66e auth-api: rustfmt
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-05-22 15:41:56 +02:00
Wolfgang Bumiller
fc75d98cb3 auth-api: cleanup a warning
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-05-22 15:40:12 +02:00
Wolfgang Bumiller
a6dc4d322d auth-api: remove unnecessary allocation
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-05-22 15:38:26 +02:00
Stefan Sterz
5b4cb9b124 auth-api: fix types compilefail test
due to missing `use` statements they failed, as they should, but for
the wrong reasons. also adapt a test case that presumably was meant
to test whether `TokennameRef` can be compared, but instead
duplicated the `UsernameRef` test case.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2024-05-22 10:26:44 +02:00
Stefan Sterz
eef12f91a1 sys: crypt: use constant time comparison for password verification
by using `openssl::memcmp::eq()` we can avoid potential timing side
channels as its runtime only depends on the length of the arrays, not
the contents. this requires the two arrays to have the same length, but
that should be a given since the hashes should always have the same
length.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2024-05-22 10:26:43 +02:00
Stefan Sterz
f82bb2fc2b sys: crypt: move to yescrypt for password hashing
previously we used `sha256scrypt` for password hashing. while this may
by safe if used with the correct parameters, we used the default
parameters which are considered unsafe. according to `man crypt(5)`:

> The default CPU time cost parameter is 5000, which is too low for
> modern hardware.

hence, we needed to adapt this code anyway. conveniently, verification
with crypt also works for older hashes as the parameters for the
hashing function are encoded in the output of crypt. so this is a drop
in replacement that will simply use yescrypt for new hashes while
old hashes will still verify properly.

this commit also adds a wrapper for `crypt_gensalt_rn` to more easily
generate correctly formatted salt strings. this is also useful for
switching the cpu time hardness parameter, as otherwise we'd need to
encode that ourselves.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2024-05-22 10:26:41 +02:00
Stefan Sterz
4d6922e2c4 auth-api: move to hmac signing for csrf tokens
previously we used our own hmac-like implementation for csrf token
signing that simply appended the key to the message (csrf token).
however, this is possibly insecure as an attacker that finds a
collision in the hash function can easily forge a signature. after all,
two messages would then produce the same start conditions before
hashing the key. while this is probably a theoretic attack on our csrf
implementation, it does not hurt to move to the safer standard hmac
implementation that avoids such pitfalls.

this commit re-uses the hmac key wrapper used for the keyring. it also
keeps the old construction around so we can use it for a transition
period between old and new csrf token implementations.

this is a breaking change as it changes the signature of the
`csrf_secret` method of the `AuthContext` trait to return an hmac
key.

also exposes `assemble_csrf_prevention_toke` so we can re-use this
code here instead of duplicating it in e.g. proxmox-backup's
auth_helpers.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2024-05-22 10:26:40 +02:00
Stefan Sterz
8609fb58ef auth-api: use constant time comparison for csrf tokens
by using openssl's `memcmp::eq()` we can avoid potential side-channel
attack on the csrf token comparison. this comparison's runtime only
depends on the length of the two byte vectors, not their contents.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2024-05-22 10:26:38 +02:00
Stefan Sterz
b926ea1f5c auth-api: add ability to use hmac singing in keyring
previously we only used asymmetric cryptographic schemes to
authenticate tickets. this is fairly costly and not necessary in every
instance. imagine a service that runs as a single daemon. this daemon
is then the only party that needs to sign and verify tickets. this
makes hmac perfectly suitable for such usecases. hmac has some
advantages over asymmetric schemes:

- much simpler and well reviewed construction
- much faster and better optimized crypto primitives (hash functions)

this commit first introduces a new hmac key wrapper that uses openssl's
hmac implementation and can easily be reused by other parts of the
code. it also refactors the keyring code to make it easier to rotate
new hmac keys into place so switching to hmac keys is easier.

hmac keys are symmetric, so the verification key is the same key as the
signing key. this breaks the previous assumption by the keyring that
these correspond to public and private keys. thus, this commit
introduces two wrapper enums to distinguish between hmac and asymmetric
signature schemes.

the verification of hmac keys is also done via `openssl::memcmp::eq()`
to avoid potential timing side-channel attacks.

below are some simple benchmarks done with criterion.rs to show how much
faster hmac is, no matter the actual hash function:

rsa 4096 + sha256        time:   [2.7825 ms 2.7907 ms 2.7995 ms]
ed25519                  time:   [94.411 µs 94.840 µs 95.324 µs]
hmac sha256              time:   [5.7202 µs 5.7412 µs 5.7645 µs]
hmac sha384              time:   [6.6577 µs 6.6780 µs 6.7006 µs]
hmac sha3_256            time:   [5.6930 µs 5.7114 µs 5.7322 µs]

rsa with 4096 bit keys and a sha256 digest is our current default. the
test itself consists of a single sign + verification cycle. criterion
repeats this test as it sees fit to arrive at the above numbers.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2024-05-22 10:26:36 +02:00
Stefan Sterz
09d31a1a8b auth-api: move to Ed25519 signatures
previously we used P-256 as the curve of our choice for ec signatures.
however, in the meantime Ed25519 has become a lot more wide-spread.
this simplifies our ec generation code significantly while keeping the
same security level. Ed25519 was also specifically designed and
reviewed to avoid implementation errors likely making it a more secure
choice

note that Ed25519 as a signature scheme always uses sha512, so signing
or verifying with a chosen digest is not supported.

as this mostly affects newly generated keys, this should not break any
existing setups.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2024-05-22 10:26:35 +02:00
Stefan Sterz
8e566591d5 auth-api: move signing into the private key
this commit moves the current ticket signing code into the private key
implementation. the upside is that the caller does not need to deal
with openssl's `Signer` directly. it also simplifies and unifies the
code by using the same helper for verifying a signature and creating it.

also derive `Clone` on `PrivateKey` and `PublicKey`. as they are
essentially thin wrappers around `openssl::pkey::PKey<Private>` and
`openssl::pkey::PKey<Public>`, which can be cloned, deriving `Clone`
just makes them easier to use.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2024-05-22 10:26:33 +02:00
Dietmar Maurer
f240a2bfaa acme-api: add debian packaging
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-17 12:13:02 +02:00
Dietmar Maurer
7c899090e4 acme-api: use product-config instead of custom acme api configuration
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-17 11:52:57 +02:00
Dietmar Maurer
0ffe40fcfa bump proxmox-section-config to 2.0.2-1
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-17 11:04:10 +02:00
Dietmar Maurer
a2693c7046 section-config: pass filesystem paths as AsRef<Path>
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-17 10:59:20 +02:00
Dietmar Maurer
cfc155a06b acme-api: reusable ACME api implementation.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-16 12:35:14 +02:00
Dietmar Maurer
870948f1d7 bump proxmox-acme to 0.5.2
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-16 11:53:52 +02:00
Dietmar Maurer
c07c46cd82 acme: add async-client feature
The client code is copied from propxmox-backup, without the load/safe
account functionality.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-16 11:48:17 +02:00
Dietmar Maurer
c2450691c6 acme: allow to compile/use api types separately.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-16 11:23:19 +02:00
Dietmar Maurer
b082d7dafa system-management-api: network: add create_interface and update_interface
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-16 10:24:41 +02:00
Dietmar Maurer
2c0c7ca478 system-management: use ip/cidr schema types from proxmox-schema
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-16 09:42:10 +02:00
Dietmar Maurer
49b97b6a5f bump proxmox-schema to 3.1.1-1
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-16 09:35:50 +02:00
Dietmar Maurer
31b7b070b5 schema: api-types: add ip/cidr api schemas
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-16 09:09:37 +02:00
Dietmar Maurer
b74583dffe system-management-api: rename features (add suffix -api-types)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-15 13:33:55 +02:00
Dietmar Maurer
3c19dd757a rename proxmox-system-config-api to proxmox-system-management-api
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-15 13:23:42 +02:00
Dietmar Maurer
15e3779331 system-config-api: add syslog feature
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-15 12:31:50 +02:00
Dietmar Maurer
87aaa4e30a Revert "system-config-api: network: add alias 'iface' for interface name."
This reverts commit a4de726601d7f003833e4c040b9fcebe5adc26b4.

Turn out we do not need this.
2024-05-14 11:50:44 +02:00
Dominik Csapak
e226ddcc90 tape: include drive activity in status
Since we don't query each drives status seperately, but rely on a single
call to the drives listing parameter for that, we now add the option
to query the activity there too. This makes that data avaiable for us
to show in a seperate (by default hidden) column.

Also we show the activity in the 'State' column when the drive is idle
from our perspective. This is useful when e.g. an LTO-9 tape is loaded
the first time and is calibrating, since that happens automatically.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-05-14 10:31:33 +02:00
Dominik Csapak
2bf32cb820 tape: add drive activity to drive status api
and show it in the gui for single drives. Adds the known values for the
activity to the UI.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-05-14 10:25:42 +02:00
Dominik Csapak
175a9b3cd5 tape: add functions to parse drive device activity
we use the VHF part from the DT Device Activity page for that.
This is intended to query the drive for it's current state and activity.

Currently only the activity is parsed and used.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-05-14 10:11:06 +02:00
Dominik Csapak
8002011f7c tape: save 'bytes used' in tape inventory
and show them on the ui. This can help uses with seeing how much a tape
is used.

The value is updated on 'commit' and when the tape is changed during a
backup.

For drives not supporting the volume statistics, this is simply skipped.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2024-05-14 10:07:57 +02:00
Dietmar Maurer
26c7a591eb system-config-api: expose helpers to set ports/slaves as string (list)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-13 12:25:07 +02:00
Dietmar Maurer
805b1d366b system-config-api: network: add api type to update network interfaces
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-13 11:06:53 +02:00
Dietmar Maurer
c101194f5a system-config-api: cleanup: remove useless serde rename property 2024-05-10 11:15:12 +02:00
Dietmar Maurer
a4de726601 system-config-api: network: add alias 'iface' for interface name.
So that we can use the Interface struct with create and update api calls (which
currently use 'iface' instead of 'name').

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-10 10:23:27 +02:00
Dietmar Maurer
e210c85d8e system-config-api: network: add helpers to check for duplicate gateway propertie
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-10 10:08:23 +02:00
Dietmar Maurer
943cfd5417 system-config-api: network: add helpers to set bridge ports and bond slaves
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-10 09:53:27 +02:00
Dietmar Maurer
729817efd3 system-config-api: add network feature
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-10 09:53:05 +02:00
Dietmar Maurer
3e8b0ee567 system-config-api: use cargo features to sparate functionality
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-08 11:13:20 +02:00
Dietmar Maurer
770c5dbd03 system-config-api: add functions to read/write time and timezone
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-07 14:18:25 +02:00
Dietmar Maurer
751b578d6e rename proxmox-dns-api to proxmox-system-config-api
Because we want to bundle system configuration APIs in one crate,
i.e. Time, DNS, Network. We may separate them in future using
cargo features.
2024-05-07 13:32:26 +02:00
Dietmar Maurer
78bd8eea24 dns-api: add feature "impl"
So the we can use the api types with our UI crates.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-06 13:52:57 +02:00
Dietmar Maurer
96a3656dd2 product-config: add feature "impl"
So the we can use the ConfigDigest with our UI crates.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-06 13:40:32 +02:00
Dietmar Maurer
53ee3f92ea product-config: add method to detect config digest modifications.
Using an object method with strong typing is considered cleaner.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-06 13:01:36 +02:00
Dietmar Maurer
cb4e3776f2 dns-api: do not serlialize option None
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-06 10:29:49 +02:00
Dietmar Maurer
5d3c3a8770 dns-api: export all defined api types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-03 09:51:34 +02:00
Dietmar Maurer
58bb112375 dns-api: add debian packaging
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-03 09:30:40 +02:00
Dietmar Maurer
03b4aed510 product-config: add debian packaging
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-03 09:29:46 +02:00
Dietmar Maurer
64e8a72a0b fix typo in proxmox-product-config workspace dependency
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-03 09:28:33 +02:00
Dietmar Maurer
c2f85d418a dns-api: new crate which implements the DNS api
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-02 13:04:43 +02:00
Dietmar Maurer
e64575b6a7 product-config: add rust API type for configuration digest
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-02 12:28:11 +02:00
Dietmar Maurer
9fc48d96d2 new crate for commonly used functions to read and write configuration files
Factor out functions to read and write configuration files with
product specific permissions.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-05-02 11:17:27 +02:00
Gabriel Goller
16ac3ef458 api-types: remove influxdb bucket name restrictions
Remove the regex for influxdb organizations and buckets. Influxdb does
not place any constraints on these names and allows all characters. This
allows influxdb organization names with slashes.

Also remove a duplicate comment and add some missing ones.

This also aligns the behavior to PVE as there are no restrictions there
either.

The motivation for this patch is this forum post:
https://forum.proxmox.com/threads/influx-db-organization-doesnt-allow-slash.145402/

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-04-26 17:54:51 +02:00
Thomas Lamprecht
afb48baca5 metrics: bump version to 0.3.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-26 17:36:19 +02:00
Thomas Lamprecht
d426b6fe03 metrics: influxdb: inline variables into template format string
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-26 17:31:22 +02:00
Gabriel Goller
bfa73aadf8 metrics: encode influxdb org and bucket parameters
In order to remove the current limitations on the bucket and
organization names, we need to make sure that they are transmitted
correctly. In order to do this, we encode them using the url crate.

This way we support organization/bucket names that include slashes,
whitespaces, etc.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-04-26 17:25:52 +02:00
Thomas Lamprecht
5987eb0c3f sys: bump version to 0.5.4-2
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-26 17:24:23 +02:00
Thomas Lamprecht
c68b69e949 sys: bump version to 0.5.4-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-26 16:52:46 +02:00
Stefan Lendl
f2633b462f config: write vlan network interface
* Add vlan_id and vlan_raw_device fields to the Interface api type
* Write to the network config the vlan specific properties for vlan
  interface type
* Add several tests to verify the functionally

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Folke Gleumes <f.gleumes@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-24 21:48:50 +02:00
Thomas Lamprecht
863d760340 auth-api: bump version to 0.3.5
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-24 21:22:33 +02:00
Thomas Lamprecht
bd944b06f9 ldap: bump version to 0.2.2-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-24 21:13:45 +02:00
Christoph Heiss
7db5cd8c48 realm sync: add sync job for AD realms
Basically just a thin wrapper over the existing LDAP-based realm sync
job, which retrieves the appropriate config and sets the correct user
attributes.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
2024-04-24 21:06:14 +02:00
Christoph Heiss
8bdf9ac45c api: access: add routes for managing AD realms
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
2024-04-24 21:06:14 +02:00
Fabian Grünbichler
f4a9afd17c notify: fix TemplateType::Subject doc comment
wrongly copied

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-04-24 08:05:16 +02:00
Lukas Wagner
1372617876 api-types: api: tape: add notification-mode parameter
Same as with datastores, this option determines whether we send
notifications the old way (send email via sendmail to a user's email
address) or the new way (emit matchable notification events to the
notification stack).

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Gabriel Goller <g.goller@proxmox.com>
Reviewed-by: Gabriel Goller <g.goller@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-23 23:14:46 +02:00
Lukas Wagner
90603f6e25 api-types: api: datatore: add notification-mode parameter
This one lets the user choose between the old notification behavior
(selecting an email address/user and always/error/never behavior per
datastore) and the new one (emit notification events to the
notification system)

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Gabriel Goller <g.goller@proxmox.com>
Reviewed-by: Gabriel Goller <g.goller@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-23 23:14:46 +02:00
Thomas Lamprecht
0af0bad742 notify: bump version to 0.4.0-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-04-23 23:14:19 +02:00
Lukas Wagner
8f408ea4af notify: add getter for notification timestamp
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-04-23 23:06:56 +02:00
Lukas Wagner
08b7c501ac notify: endpoints: matcher: improve descriptions for API types
proxmox-schema will automatically append text (e.g. 'Can be specified
more than once'), so we should end every comment with a '.'.

Also copy over some text from PVE docs, since these doc comments will
now be visible in the PBS documentation.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-04-23 23:06:56 +02:00
Lukas Wagner
b2000d1f75 notify: pbs-context: exclude successful prunes in default matcher
PBS sends notifications for all events but successful prune jobs.
There we only care about errors.

This commit adapts the 'default-matcher' to reflect that behavior
as well.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-04-23 23:06:56 +02:00
Lukas Wagner
7035d57312 notify: use std::sync::OnceCell instead of lazy_static!
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-04-23 23:06:56 +02:00
Lukas Wagner
d0b1502803 notify: expose config module
This is needed because we want to access CONFIG and PRIVATE_CONFIG
from the docgen helper in PBS.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-04-23 23:06:56 +02:00
Lukas Wagner
c028a32c1e notify: renderer: add relative-percentage helper from PBS
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
803bf7cdc7 notify: pbs context: include nodename in default sendmail author
The old notification stack in proxmox-backup includes the nodename, so
we include it here as well.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
c55f37b8c4 notify: derive Deserialize/Serialize for Notification struct
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
1a40d34083 notify: derive api for Deleteable*Property
The API endpoints in Proxmox Backup Server require ApiType to be
implemented for any deserialized parameter.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
87f7dfa111 notify: api: add get_targets
This method allows us to get a list of all notification targets.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
e83269be1d notify: give each notification a unique ID
We need this for queuing notifications on PBS from the unprivileged
proxy process.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
efb576385b notify: cargo.toml: add spaces before curly braces
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
2f40b79f8f notify: make the mail-forwarder feature depend on proxmox-sys
It uses proxmox_sys::nodename - the dep is needed, otherwise the code
does not compile in some feature flag permutations.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Folke Gleumes <f.gleumes@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
50d80328e5 notify: don't make tests require pve-context
Tests now have their own context, so requiring pve-context is not
necessary any more.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Folke Gleumes <f.gleumes@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
d61e3fc759 notify: convert Option<Vec<T>> -> Vec<T> in config structs
Suggested-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Folke Gleumes <f.gleumes@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
a4d5594721 notify: make api methods take config struct ownership
This saves us from some of the awkward cloning steps when updating.

Suggested-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Folke Gleumes <f.gleumes@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Lukas Wagner
1516cc26d2 notify: switch to file-based templating system
Instead of passing the template strings for subject and body when
constructing a notification, we pass only the name of a template.
When rendering the template, the name of the template is used to find
corresponding template files. For PVE, they are located at
/usr/share/proxmox-ve/templates/default. The `default` part is
the 'template namespace', which is a preparation for user-customizable
and/or translatable notifications.

Previously, the same template string was used to render HTML and
plaintext notifications. This was achieved by providing some template
helpers that 'abstract away' HTML/plaintext formatting. However,
in hindsight this turned out to be pretty finicky. Since the
current changes lay the foundations for user-customizable notification
templates, I ripped these abstractions out. Now there are simply two
templates, one for plaintext, one for HTML.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Folke Gleumes <f.gleumes@proxmox.com>
Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
2024-04-23 23:06:52 +02:00
Fabian Grünbichler
730f4e58ff GC: flatten existing status into job status
to avoid drifting definitions and reduce duplication. with the next major
release, the 'upid' field could then be renamed and aliased to be in line with
the other jobs, which all use 'last-run-upid'. doing it now would break
existing callers of the GC status endpoint (or consumers of the on-disk status
file).

the main difference is that the GC status fields are now not optional (except
for the UPID) in the job status, since flattening an optional value is not
possible. this only affects datastores that were never GCed at all, and only
direct API consumers, since the UI handles those fields correctly.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-04-22 13:58:08 +02:00
Stefan Lendl
163732177d api: garbage collect job status
Adds an api endpoint on the datastore that reports the gc job status
such as:
 - Schedule
 - State (of last run)
 - Duration (of last run)
 - Last Run
 - Next Run (if scheduled)
 - Pending Chunks (of last run)
 - Pending Bytes (of last run)
 - Removed Chunks (of last run)
 - Removed Bytes (of last run)

Adds a dedicated endpoint admin/gc that reports gc job status for all
datastores including the onces without a gc-schedule.

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Originally-by: Gabriel Goller <g.goller@proxmox.com>
Tested-by: Gabriel Goller <g.goller@proxmox.com>
Reviewd-by: Gabriel Goller <g.goller@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-04-22 13:58:08 +02:00
Dietmar Maurer
bec18b8e60 api: assert that maintenance mode transitions are valid
Maintenance mode Delete locks the datastore. It must not be possible to go
back to normal modes, because the datastore may be in undefined state.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-04-22 12:19:22 +02:00
Dietmar Maurer
15c013f758 maintenance: derive Copy for maintenance type and make maintenance mode fields public
Because it is a public api type.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-04-22 12:19:22 +02:00
Dietmar Maurer
73bf2b1994 pbs-api-types: use SchemaDeserializer for maintenance mode
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2024-04-22 12:19:22 +02:00
Fabian Grünbichler
42fb9ed26b fix #5249: apt: allow parsing Packages without Priority field
it seems there are repositories out there that don't (always) include
it, and while it is required for the .deb packages themselves in Debian,
the repository "spec" doesn't make it mandatory.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-04-19 08:47:34 +02:00
Fabian Grünbichler
f03f16d643 fix #5249: apt: allow parsing Packages without Priority field
it seems there are repositories out there that don't (always) include
it, and while it is required for the .deb packages themselves in Debian,
the repository "spec" doesn't make it mandatory.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2024-04-19 08:43:54 +02:00
Lukas Wagner
6b393ac0ce notify: fix #5274: also set 'X-Gotify-Key' header for authentication
Versions of Gotify < 2.2.0 only supported the 'X-Gotify-Key' header
for passing the API token. This comment sets this header in addition
to the regular 'Authorization' header in order to be compatible with
older Gotify servers.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-04-04 16:51:55 +02:00
Wolfgang Bumiller
6858672642 bump proxmox-http to 0.9.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-28 13:18:21 +01:00
Wolfgang Bumiller
9be9d4b6ab http: support ALPN negotiated http2
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-28 11:19:56 +01:00
Thomas Lamprecht
d73eb3dcf1 tree-wide: run cargo fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-03-25 17:04:17 +01:00
Christoph Heiss
0475421498 auth-api: implement Display for Realm{, Ref}
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-03-25 17:03:27 +01:00
Christoph Heiss
72afba8b5b ldap: add method for retrieving root DSE attributes
The root DSE holds common attributes about the LDAP server itself.
Needed to e.g. support Active Directory-based LDAP servers to retrieve
the base DN from the server itself, based on an valid bind.

See also RFC 4512, Section 5.1 [0] for more information about this
special object.

[0] https://www.rfc-editor.org/rfc/rfc4512#section-5.1

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-03-25 17:03:27 +01:00
Christoph Heiss
84fbfb22ec ldap: avoid superfluous allocation when calling .search()
The `attrs` parameter of `Ldap::search()` is an `impl AsRef<[impl
AsRef<str>]>` anyway, so replace `vec![..]` with `&[..]`.

Suggested-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2024-03-25 17:03:27 +01:00
Gabriel Goller
78bf05a458 fix: use fragmented block size for space calculation
We currently calculate the size of a datastore using `statfs64`, which
returns the number of blocks in the fs and the two block sizes:
fragemented block size(f_frsize) and block size (f_bsize). To calculate
eg the total space in a datastore we use total_blocks * f_bsize, which
is not always correct.

`f_frsize` is the minimum unit of allocation on the filesystem (in
bytes) and in 99% of the cases equal to `f_bsize`, but in some cases
it differs. For example some filesystems allow smaller blocks for small
files, in case f_frsize < f_bsize. In that case, f_frsize * total_blocks
returns (mostly) the correct result (ceph also did some weird stuff, which is
now being fixed though [0][1]). `statvfs` also documents this as the
recommended way ('fsblkcnt_t f_blocks;   /* Size of fs in f_frsize units */')[2].

This patch aligns the the behavior with the libc utilities (also used by
`df`) [3].

Motivation: [4] (Forum post)

[0]: https://tracker.ceph.com/issues/3793
[1]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=92a49fb0f79f3300e6e50ddf56238e70678e4202
[2]: https://www.man7.org/linux/man-pages/man3/statvfs.3.html
[3]: https://git.savannah.gnu.org/cgit/gnulib.git/tree/lib/fsusage.c#n147
[4]: https://forum.proxmox.com/threads/pbs-3-1-2-wrong-datastore-information-sshfs.139875/#post-626959

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-03-25 16:59:17 +01:00
Hannes Laimer
71ff7c3344 datastore: remove datastore from internal cache based on maintenance mode
We keep a DataStore cache, so ChunkStore's and lock files are kept by
the proxy process and don't have to be reopened every time. However,
for specific maintenance modes, e.g. 'offline', our process should not
keep file in that datastore open. This clears the cache entry of a
datastore if it is in a specific maintanance mode and the last task
finished, which also drops any files still open by the process.

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
Reviewed-by: Gabriel Goller <g.goller@proxmox.com>
Tested-by: Gabriel Goller <g.goller@proxmox.com>
2024-03-25 16:12:41 +01:00
Wolfgang Bumiller
158f98fe72 cargo fmt (import reordering)
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-20 11:13:13 +01:00
Dietmar Maurer
b8f2582bd9 pbs-api-types: use const_format and new api-types from proxmox-schema
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-03-20 11:09:26 +01:00
Thomas Lamprecht
55f4d532c7 sys: d/copyright: update years
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-03-19 11:23:43 +01:00
Wolfgang Bumiller
e32081ea5f bump proxmox-notify to 0.3.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-19 11:11:02 +01:00
Wolfgang Bumiller
2c2783451f bump proxmox-auth-api to 0.3.4
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-19 11:09:49 +01:00
Wolfgang Bumiller
d653ac343b bump proxmox-schema to 3.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-19 11:08:28 +01:00
Wolfgang Bumiller
6f1d439f09 notify: adapt to proxmox_schema changes, use const_format
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-19 10:29:27 +01:00
Wolfgang Bumiller
686453a28c notify: sort and group dependencies
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-19 10:27:25 +01:00
Dietmar Maurer
1ac8b7f652 proxmox-schema: moved common api types from pbs-api-types
We want to use those types in all of our products.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-03-18 10:19:52 +01:00
Dietmar Maurer
d74fa06253 proxmox-schema: add IP address regex/api-types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-03-18 10:19:50 +01:00
Dietmar Maurer
a6f1b36fa6 proxmox-auth-api: use const_format to define static strings
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-03-18 10:19:48 +01:00
Dietmar Maurer
0c5e2640d0 proxmox-schema: use const_format to define static strings.
Macro rules are not hygienic, and current rust macro visibility rules
are a nightmare. Using const_format::concatcp!() is a much cleaner
solution.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-03-18 10:19:46 +01:00
Wolfgang Bumiller
c67a13f1d7 bump proxmox-acme to 0.5.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-07 13:27:47 +01:00
Wolfgang Bumiller
fbb3049768 acme: formatting fixups
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-07 13:24:42 +01:00
Wolfgang Bumiller
b5255f1868 acme: drop api-types feature from schema dependency
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-03-07 13:24:37 +01:00
Dietmar Maurer
0370723261 proxmox-acme: derive PartialEq for API types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-03-07 13:21:54 +01:00
Dietmar Maurer
619414d4f1 proxmox-acme: add api-types feature
Because AccountData is exposed via our API (currently as type Object).

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2024-03-07 13:21:52 +01:00
Alexander Burmatov
279142df30 Fix warning
Variable does not need to be mutable
2024-03-02 02:59:35 +03:00
Wolfgang Bumiller
724c3dda6f rrd: fixup examples with the renamed types
Some types were recently renamed but the examples not updated
accordingly.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Fixes: 2f942833672d "rrd: spell out hard to understand abbreviations in public types"
2024-02-21 12:28:10 +01:00
Wolfgang Bumiller
05ff6b545a bump proxmox-schema to 3.0.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-21 12:28:02 +01:00
Wolfgang Bumiller
49b2bdf9c6 schema: drop periods after errors
lower case start + period = wrong

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-21 12:05:44 +01:00
Wolfgang Bumiller
9c40144214 schema: add regression tests for additional_properties in AllOf
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-21 12:02:00 +01:00
Wolfgang Bumiller
bae2cf75de schema: AllOf/OneOf: actually perform additional_properties() check
rather than just always allowing additional properties, only return
true if any of the available schemas allows it

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-21 11:43:20 +01:00
Maximiliano Sandoval R
3f92e6286b router: Use safe wrapper for libc::isatty
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-02-16 16:56:02 +01:00
Maximiliano Sandoval R
430df21720 sys: Use safe wrapper for libc::isatty
Use the `std::io::IsTerminal` trait introduced in Rust 1.70.

Internally it calls `libc::isatty`, see [1, 2]. Note that it switches
the comparison from `== 1` to `!= 0` which shouldn't make a difference
assuming that libc::isatty upholds the promises made in its man page.

The MSRV was set on the workspace to reflect this change.

[1] https://doc.rust-lang.org/src/std/io/stdio.rs.html#1079
[2] https://doc.rust-lang.org/src/std/sys/unix/io.rs.html#79

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2024-02-16 16:55:58 +01:00
Gabriel Goller
8fabade99d fix #5190: api: OIDC: accept generic URIs for the ACR value
Allow more complex strings for the acr-value when using openid. The
openid documentation only specifies the acr-value *should* be an URI
[0].  Implemented a regex that loosely disallows some of the reserved
URI characters specified in the RFC [1].

Currently values like:
- "urn:mace:incommon:iap:silver"
- "urn:comsolve.nl:idp:contract:rba:location"
do NOT work, although they are correct URI's and common acr tokens.

For Proxmox VE we had to actually make this more strict to align with
each other, as there we accepted any string.

[0]: https://openid.net/specs/openid-connect-core-1_0.html
[1]: https://www.rfc-editor.org/rfc/rfc2396.txt

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2024-02-08 18:14:30 +01:00
Wolfgang Bumiller
7126249102 bump proxmox-subscription to 0.4.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 14:24:54 +01:00
Wolfgang Bumiller
304e1c544f bump proxmox-rrd to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 14:22:45 +01:00
Wolfgang Bumiller
364b21f3d2 bump proxmox-notify to 0.3.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 14:21:14 +01:00
Wolfgang Bumiller
77672b1253 bump proxmox-client to 0.3.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 14:06:08 +01:00
Wolfgang Bumiller
b5b563e215 bump proxmox-section-config to 2.0.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 14:03:51 +01:00
Wolfgang Bumiller
d9b783f1a8 bump proxmox-auth-api to 0.3.3
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 14:01:57 +01:00
Wolfgang Bumiller
9da7b3ad49 bump proxmox-tfa to 4.1.2
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 14:00:49 +01:00
Wolfgang Bumiller
890d9e58f7 bump proxmox-rest-server to 0.5.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 13:58:05 +01:00
Wolfgang Bumiller
f67ea142bc bump proxmox-router to 2.1.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 13:56:34 +01:00
Wolfgang Bumiller
d143efd01a bump proxmox-human-byte to 0.1.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 13:56:01 +01:00
Wolfgang Bumiller
d7876b1837 bump proxmox-apt to 0.10.8-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 13:56:01 +01:00
Wolfgang Bumiller
ca6ca904ad bump proxmox-api-macro to 1.0.8-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 13:56:01 +01:00
Wolfgang Bumiller
245524d0d8 bump proxmox-schema to 3.0.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-02 13:53:12 +01:00
Wolfgang Bumiller
5df9da2af4 schema: implement split_list iterator
and reuse splitting code in no_schema's SeqAccess as well

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-02 12:06:46 +01:00
Wolfgang Bumiller
ae7454b05e router: OneOfSchema support
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-02 12:06:28 +01:00
Wolfgang Bumiller
d48a150835 schema: implement oneOf schema support
A 'oneOf' schema is basically exactly what a rust `enum` is.
Exactly one of the possible values must match the data.

This should ultimately be the base to allow using the
`#[api]` macro on a newtype style enum as well as using this
schema as a configuration for our section config parser.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-02 12:06:28 +01:00
Wolfgang Bumiller
1c0edfb518 router: cli: option to specify args explicitly
so CLI tools can pre-parse out non-api parameters before
passing the remaining stuff to the router

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-02 12:05:34 +01:00
Wolfgang Bumiller
2bba40f604 rest-server: support configuring the privileged connection
Adds a privileged_addr to ApiConfig, and some helpers for
hyper (both server and client)

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-01 14:00:22 +01:00
Wolfgang Bumiller
aad01f7a90 rest-server: support unix sockets in create_daemon
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-01 13:59:48 +01:00
Wolfgang Bumiller
440c7e3361 bump proxmox-rrd to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-02-01 13:46:28 +01:00
Lukas Wagner
2f94283367 rrd: spell out hard to understand abbreviations in public types
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-01 10:32:19 +01:00
Lukas Wagner
f9e8ebfdc8 rrd: fix a few typos
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-01 10:32:19 +01:00
Lukas Wagner
6eed8ed992 rrd: feature-gate support for the v1 format
new users of this crate might not really need support for the v1
format.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-01 10:32:19 +01:00
Lukas Wagner
4d150d35c7 add debian packaging for proxmox-rrd
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-01 10:32:19 +01:00
Lukas Wagner
5cbc8a4b66 add proxmox-rrd to workspace
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-01 10:30:57 +01:00
Lukas Wagner
b8c56e7b6c Merge branch proxmox-rrd
The proxmox-backup repo was filtered using `git filter-repo` using the
following paths:

proxmox-rrd
proxmox-rrd-api-types
src/rrd

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-02-01 10:30:23 +01:00
Wolfgang Bumiller
2b56eba35c bump proxmox-login to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-24 12:49:06 +01:00
Wolfgang Bumiller
de8fd435fb Makefile: enforce the use packaged cargo
Nightly currently produces a different output format so this command
doesn't work right now when +system is not the default cargo.
Let's hope this is just a temporary hiccup in nightly, given that
there is an explicit `--format-version=1` parameter...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-24 09:26:03 +01:00
Wolfgang Bumiller
6ee541d5f2 login: parse helpers for floats
Of course PVE also stringifies those in the API, duh...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-24 09:20:49 +01:00
Wolfgang Bumiller
2fa645af2e schema: cli: simplify can_default check
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-24 09:20:42 +01:00
Wolfgang Bumiller
fe8b11eeec http: concat! user agent instead of format!
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-15 11:17:57 +01:00
Wolfgang Bumiller
3c453f7468 notify: bump d/control
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-10 14:15:48 +01:00
Wolfgang Bumiller
1484676ac4 bump proxmox-notify to 0.3.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-10 14:04:36 +01:00
Lukas Wagner
ead4190e7b notify: matcher: support lists of values for 'exact' match-field mode
For example, one can now use:
  match-field exact:type=vzdump,replication
to match on vzdump AND replication events.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-10 12:38:32 +01:00
Lukas Wagner
39c4d7d85e notify: include 'type' metadata field for forwarded mails
Seems like this was forgotten in the initial version. Without it,
it's not really possible to create matchers for forwarded mails.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-10 12:38:32 +01:00
Lukas Wagner
870d9c2739 notify: include 'hostname' metadata field for forwarded mails
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-10 12:38:32 +01:00
Lukas Wagner
f0bf95f53b notify: add separate context for unit-tests
... as using PVEContext for tests is brittle and annoying for some
tests.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-10 12:29:26 +01:00
Lukas Wagner
50fa98e241 notify: api: allow resetting built-in targets if used by a matcher
In the 'delete'-handler targets, we check if a
target is still referenced by a matcher - if it is, we return an
error. For built-in targets, this is actually not necessary, since
'deleting' a built-in only resets it to its default settings - it will
continue to exist after that.
The user could easily trigger this if 'mail-to-root', which is
referenced by 'default-matcher' is modified and then reset to its
defaults: An error is shown, the built-in target is not reset.

This commit disables this check if it is a built-in target.

Renamed the helper 'ensure_unused' to 'ensure_safe_to_delete' in the
process.

Also fixed the tests in api::test - they were never executed due to a
faulty #[cfg] directive.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-10 12:29:26 +01:00
Lukas Wagner
efa607f1b5 notify: smtp: add Auto-Submitted header to email body
`Auto-Submitted` is defined in the rfc 5436 [1] and describes how
an automatic response (f.e. ooo replies, etc.) should behave on the
emails. When using `Auto-Submitted: auto-generated` (or any value
other than `none`) automatic replies won't be triggered.

[1]: https://www.rfc-editor.org/rfc/rfc3834.html

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-10 12:20:41 +01:00
Lukas Wagner
b03c394039 notify: smtp: forward original message instead nesting
For mails forwarded by `proxmox-mail-forward` to an SMTP target, the
original message was nested as a 'message/rfc822' message part.
Originally this approach was chosen to avoid having to rewrite
message headers.
Good email-clients, such as Thunderbird can display these inline.
Other, more limited clients will show these messages as an attached
.eml file, which is not really a good user experience.

This patch changes the approach for message forwarding to be more like
forwarding mails in a mail client. We create a new message and
add the original message body as a body. Additionally, we also copy
over all message headers that are relevant to correctly display the
original message body (e.g. Content-Type, Content-Transfer-Encoding)

Tested with a couple of different email messages (varying in
structure, body parts, encoding, etc.) against the following SMTP
relays:
  - gmail
  - outlook
  - our own webmail service

Originally reported in our community forum:
https://forum.proxmox.com/threads/proxmox-mail-forward-sends-mails-as-eml.137710/

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-10 12:20:41 +01:00
Wolfgang Bumiller
c2545b6540 move api-types tests to api-types and drop vec![] macro
we don't need to allocate here

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-10 10:13:45 +01:00
Wolfgang Bumiller
c5714ff06f api-types: doc improvements
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-10 10:13:45 +01:00
Wolfgang Bumiller
01618ea991 api-types: impl Display for FilterType
as the previous commit: simply keep the previous Display impl and call
it from out of the new GroupFilter impl

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-10 10:13:45 +01:00
Wolfgang Bumiller
e98fb9d5b1 api-types: factor out FilterType parsing
simply keep the previous FromStr implementation and call it the new
GroupFilter impl

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-10 10:13:45 +01:00
Wolfgang Bumiller
601098729a fixup import grouping
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-10 10:13:45 +01:00
Philipp Hufnagl
c7b80d5e04 fix #4315: jobs: modify GroupFilter so include/exclude is tracked
After some discussion I canged the include/exclude behavior to first run
all include filter and after that all exclude filter (rather then
allowing to alternate inbetween). This is done by splitting them into 2
lists, running include first.

A lot of discussion happened how edge cases should be handled and we
came to following conclusion:

no include filter + no exclude filter => include all
some include filter + no exclude filter => filter as always
no include filter +  some exclude filter => include all then exclude

Since a GroupFilter now also features an behavior, the Struct has been
renamed To GroupType (since simply type is a keyword). The new
GroupFilter now has a behaviour as a flag 'is_exclude'.

I considered calling it 'is_include' but a reader later then might not
know what the opposite of 'include' is (do not include?  deactivate?). I
also considered making a new enum 'behaviour' but since there are only 2
values I considered it over engeneered.

Signed-off-by: Philipp Hufnagl <p.hufnagl@proxmox.com>
2024-01-10 10:13:45 +01:00
Wolfgang Bumiller
674ab33a43 bump proxmox-sys to 0.5.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-08 12:48:57 +01:00
Wolfgang Bumiller
8fdf696eed bump proxmox-time dependency to 1.1.6
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-08 12:18:12 +01:00
Wolfgang Bumiller
ea05268cde bump proxmox-time to 1.1.6
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2024-01-08 12:18:12 +01:00
Lukas Wagner
dc72878d37 sys: email: use epoch_to_rfc2822 from proxmox_time
`strftime`'s formatting is locale-dependent. If the system locale was
set to e.g. de_DE.UTF-8, the `Date` header became invalid
(e.g Mo instead of Mon for 'Monday'), tripping up some mail clients
(e.g. KMail).

This commit should fix this by using the new `epoch_to_rfc2822`
function from proxmox_time. Under the hood, this function uses
`strftime_l` with a fixed locale (C).

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-08 12:04:47 +01:00
Lukas Wagner
1384bd9161 time: posix: add epoch_to_rfc2822
This is the format used in the 'Date' header in mails.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-08 12:04:11 +01:00
Lukas Wagner
795b3a57a7 time: posix: add bindings for strftime_l
This variant of strftime can be provided with a locale_t, which
determines the locale used for time formatting.

A struct `Locale` was also introduced as a safe wrapper around
locale_t.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-08 11:59:26 +01:00
Lukas Wagner
5b25e7cc90 time: posix: inline vars in string formatting
No functional changes.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-08 11:57:25 +01:00
Lukas Wagner
7033c497a0 time: posix: use strftime from the libc crate.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-08 11:57:15 +01:00
Lukas Wagner
228ce9d69c client: do a POST instead of PUT in post_without_body
Probably a copy-paste mistake.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2024-01-04 08:15:01 +01:00
Dominik Csapak
904bef0231 tape: move 'eject-before-unload' to a plain changer config option
instead of having it in a property string. For now this should be fine,
and if we need many more such options, we can still move them into a
property string if we want.

Also update the cli command in the docs on how to set it now.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-12-14 10:23:49 +01:00
Dominik Csapak
2cfa31a218 tape: fix 'eject-before-unload' api type
by converting the bool into an option, otherwise having the options not
set at all will fail the unload while deserializing with
'eject-before-unload is not optional'

Also if we can automatically decide this in the future, we can now
detect if the option was explicitely set or not.

Fixes: 99f24b20 ("fix #4904: tape changer: add option to eject before unload")
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-12-13 12:14:33 +01:00
Dominik Csapak
99f24b2079 fix #4904: tape changer: add option to eject before unload
some tape libraries need the tape being ejected from the drive before
doing an unload. Since we cannot easily detect if that's the case,
introduce an 'eject_before_unload' option.

Instead of just adding a bool flag to the config, add a new 'options'
property string where we can put such niche options similar to how we
handle the datastore tuning options.

Extend the LtoTapeHandle with 'medium_present' which just uses a
TEST UNIT READY command to check for present medium, so we don't
try to eject an already ejected tape.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-12-12 14:36:19 +01:00
Dominik Csapak
a82bcf8ad1 tape: changer: save whole LtoTapeDrive config in MtxMediaChanger
we'll need more info from there in the future, so derive clone for it
and save the whole config instead of adding an additional field.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-12-12 14:36:08 +01:00
Dominik Csapak
e9283e93e7 tape: derive PartialEq and PartialOrd for TapeDensity
so that we can compare more easily

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-12-12 13:24:58 +01:00
Gabriel Goller
4abc2ec487 status: use Option on avail/used datastore attrs
Instead of returning -1 if we can't get the attributes, we use an
Option which will not be serialized on `None`.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2023-12-11 13:09:13 +01:00
Wolfgang Bumiller
1f377da07c bump api-macro to 1.0.7-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 16:02:42 +01:00
Wolfgang Bumiller
3c1103e7d5 bump proxmox-tfa to 4.1.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 16:01:13 +01:00
Wolfgang Bumiller
01e68eb40e bump proxmox-router to 2.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 16:00:10 +01:00
Wolfgang Bumiller
1d6174d36d bump proxmox-apt to 0.10.7-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 15:59:59 +01:00
Wolfgang Bumiller
dc53be1b9a api-types: add a missing serde(default)
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 15:53:57 +01:00
Wolfgang Bumiller
5d9e33d1d9 tfa: fix deserialize-default in TfaUser
Note that this was currently not deserialized anywhere, so this was
not an issue, but the api-macro now treats this as an error.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 14:54:47 +01:00
Wolfgang Bumiller
2435ab29e2 api-macro: make skip_serializing_if without default an error
except for Option types, since this causes deserialization issues

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 14:39:47 +01:00
Wolfgang Bumiller
89b29415a4 api-macro: add VariantAttrib
separated out of FieldAttrib without the `flatten` attribute, since we
don't support this on enum variants

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 14:39:47 +01:00
Wolfgang Bumiller
fa9a50a0b7 api-macro: rename SerdeAttrib to FieldAttrib
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 14:39:47 +01:00
Wolfgang Bumiller
5b9bac09da router: fix warning
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-06 14:39:47 +01:00
Wolfgang Bumiller
bd3013690f Merge branch 'proxmox-acme-merge' 2023-12-04 11:52:39 +01:00
Wolfgang Bumiller
54784e591e bump proxmox-acme to 0.5.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-04 11:47:28 +01:00
Wolfgang Bumiller
e703725049 add proxmox-acme to workspace
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-04 11:45:00 +01:00
Wolfgang Bumiller
83df0d3903 Merge branch 'proxmox-acme' 2023-12-04 11:43:15 +01:00
Wolfgang Bumiller
b212febefc drop rustfmt.toml
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-04 11:42:54 +01:00
Wolfgang Bumiller
6773460d89 drop -rs suffix
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-04 11:42:42 +01:00
Wolfgang Bumiller
a1b59c8a23 move to proxmox-acme
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-04 11:41:59 +01:00
Folke Gleumes
6e1e835739 expand helper function by eab credentials
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
2023-12-04 10:14:44 +01:00
Wolfgang Bumiller
d07e4fdb9a Option<Vec<>> -> Vec<>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-12-04 10:13:46 +01:00
Dietmar Maurer
b57e1fb347 proxmox-apt: fix digest api type in APTRepositoryFile
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-12-04 09:39:54 +01:00
Folke Gleumes
53416e358f add meta fields returned by the directory
According to the rfc, the meta field contains additional fields that
weren't covered by the Meta struct. Of the additional fields, only
external_account_required will be used in the near future, but others
were added for completeness and the case that they might be used in the
future.

Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
2023-12-04 09:37:42 +01:00
Folke Gleumes
88f7e190ec add external account binding
Functionality was added as a additional setter function, which hopefully
prevents any breakages. Since a placeholder Option an the AccountData
was already present, but has never been used, replacing the field with
an Option of a fully defined type should also be minimally intrusive.

Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
2023-12-04 09:37:41 +01:00
Dietmar Maurer
1859be3588 proxmox-apt: fix serde attributes for API types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-12-02 14:45:57 +01:00
Thomas Lamprecht
109902fbf0 tree-wide: fix various typos
found with codespell

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-29 18:32:07 +01:00
Thomas Lamprecht
ea95d57759 tree-wide: fix various typos
found with codespell

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-29 18:32:07 +01:00
Gabriel Goller
325dbbc97f node: status: declutter kernel-version
Return a struct with all the components of the kernel version like it
has been done in pve. Also return the legacy `kversion` to keep
backwards compat.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-29 15:31:35 +01:00
Gabriel Goller
5d77ea0cd2 node: status: added bootmode
Added field that shows the bootmode of the node. The bootmode is either
Legacy Bios, EFI, or EFI (Secure Boot). To detect the mode we use the
exact same method as in pve: We check if the `/sys/firmware/efi` folder
exists, then check if the `SecureBoot-xx...` file in the `efivars`
directory has the SecureBoot flag enabled.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-29 15:31:35 +01:00
Wolfgang Bumiller
50b79198f8 sys: bump to 0.5.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-29 15:26:21 +01:00
Gabriel Goller
5517d6f839 sys: email: move Auto-Submitted header up
Move the Auto-Submitted header out of the multipart section.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2023-11-29 15:17:53 +01:00
Wolfgang Bumiller
8e5c164bf5 sys: cleanup assigned and immediately returned var
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-29 14:57:29 +01:00
Gabriel Goller
12657f89b3 sys: add helper to get bootmode and secureboot status
Helper that return the current boot_mode and secureboot status.
Detection works the same as in pve, we use `/sys/firmware/efi` and
the `efivars/SecureBoot-xxx..` file.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2023-11-29 14:54:29 +01:00
Markus Frank
25d26d83b1 api types: add regex, format & schema for partition names
The new regex is similar to BLOCKDEVICE_NAME_REGEX but also allows
numbers at the end of the device name (also allows partitions names).
For nvme partitions it also allows the letter p and a number.

Signed-off-by: Markus Frank <m.frank@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
Tested-by: Max Carrara <m.carrara@proxmox.com>
2023-11-28 18:24:12 +01:00
Wolfgang Bumiller
a815fc4f56 bump rest-server to 0.5.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-28 11:34:54 +01:00
Dietmar Maurer
804b7e82ff proxmox-rest-server: do not use formatter for AuthErr
We want to get a 401 error at HTTP level.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-28 11:29:39 +01:00
Dietmar Maurer
6b59158aaf proxmox-rest-server: return status code with ExtJsFormatter
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-28 11:29:35 +01:00
Thomas Lamprecht
d10394fc66 tree-wide: run cargo fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-27 15:37:35 +01:00
Maximiliano Sandoval R
b01c0f572b fix-3211: manager: Document --notify argument
Signed-off-by: Maximiliano Sandoval R <m.sandoval@proxmox.com>
2023-11-27 15:33:51 +01:00
Wolfgang Bumiller
3932e5bedf bump proxmox-auth-api to 0.3.2
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-27 14:46:59 +01:00
Wolfgang Bumiller
4a8cadc7e0 bump proxmox-rest-server to 0.5.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-27 14:39:22 +01:00
Max Carrara
7d29269986 rest-server: Add Redirector
The `Redirector` is a simple `Service` that redirects HTTP requests
to HTTPS and can be served by a `hyper::Server`.

Signed-off-by: Max Carrara <m.carrara@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-27 14:28:18 +01:00
Max Carrara
57b4c4624b rest-server: Refactor AcceptBuilder, provide support for optional TLS
The new public function `accept_tls_optional()` is added, which
accepts both plain TCP streams and TCP streams running TLS. Plain TCP
streams are sent along via a separate channel in order to clearly
distinguish between "secure" and "insecure" connections.

Furthermore, instead of `AcceptBuilder` itself holding a reference to
an `SslAcceptor`, its public functions now take the acceptor as an
argument. The public functions' names are changed to distinguish
between their functionality in a more explicit manner:

  * `accept()` --> `accept_tls()`
  *        NEW --> `accept_tls_optional()`

Signed-off-by: Max Carrara <m.carrara@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-27 14:28:15 +01:00
Fabian Grünbichler
8eff15b0b0 subscription: update d/control
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-11-27 14:08:18 +01:00
Fabian Grünbichler
eadf2e06c3 bump proxmox-subscription to 0.4.2-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-11-27 13:59:40 +01:00
Stefan Sterz
ee7a908ebc subscription: expose the next_due_date as an i64
internally `SubscriptionInfo` already uses the `parse_next_due` helper
to parse the next due date to an epoch. this exposes a function that
allows us to use the epoch outside of this crate too. for example, a
user of pom may have multiple subscription for the same system. in
that case we want to apply the one with the due date that is furthest
in the future.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-11-27 13:59:40 +01:00
Fabian Grünbichler
81cdba6181 subscription: let ProductType derive Hash
for usage in HashMap keys

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-11-27 13:59:40 +01:00
Stefan Sterz
82e742f8f5 type: move ProductType type to proxmox-subscription from pom
previously this type lived inside of pom. this made it harder to
access the product type from a `SubscriptionInfo` trait in other
products. move the type here so we can check product types more
consistently across products (e. g. in pom and pbs)

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-11-27 13:31:59 +01:00
Hannes Laimer
bf9b3e7ac0 api: make Remote for SyncJob optional
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Tested-by: Tested-by: Gabriel Goller <g.goller@proxmox.com>
2023-11-25 17:07:42 +01:00
Dietmar Maurer
56575dfc62 pbs-api-types: derive Clone and PartialEq for BackupContent, SnapshotVerifyState, SnapshotListItem and GroupListItem
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-11-23 09:53:30 +01:00
Wolfgang Bumiller
4da2fee6fa bump tfa to 4.1.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-23 09:21:38 +01:00
Wolfgang Bumiller
74c3943a89 bump schema to 2.0.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-11-23 09:16:46 +01:00
Dietmar Maurer
578f994e6b proxmox-schema: derive PartialEq for UPID (for GUI)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-11-20 11:10:22 +01:00
Thomas Lamprecht
eb6df88120 notify: bump version to 0.3.0-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-17 13:36:55 +01:00
Thomas Lamprecht
af660f1fee sys: bump version to 0.5.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-17 10:42:38 +01:00
Thomas Lamprecht
ca76122b16 notify: update d/control
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-17 08:37:36 +01:00
Thomas Lamprecht
a3fbe14f44 sys: purge pty module
it was only used in the terminal proxy and got moved there.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-17 08:35:18 +01:00
Lukas Wagner
9bea76c6b9 notify: add built-in config and 'origin' parameter
This allows us to define a (modifiable) builtin-config, which is
at the moment hardcoded in PVEContext

The 'origin' parameter indicates whether a config entry was created by
a user, builtin or a modified builtin.

These changes require context to be set for tests, so we set
PVEContext by default if in a test context. There might be a nicer
solution for that, but for now this should work.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
306f4005a1 notify: add 'disable' parameter for matchers and targets.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
20b290893a notify: add api for smtp endpoints
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
53627a1952 notify: add 'smtp' endpoint
This commit adds a new endpoint type, namely 'smtp'. This endpoint
uses the `lettre` crate to directly send emails to SMTP relays.

The `lettre` crate was chosen since it is by far the most popular SMTP
implementation for Rust that looks like it is well maintained.
Also, it includes async support (for when we want to extend
proxmox-notify to be async).

For this new endpoint type, a new section-config type was introduced
(smtp). It has the same fields as the type for `sendmail`, with the
addition of some new options (smtp server, authentication, tls mode,
etc.).

Some of the behavior that is shared between sendmail and smtp
endpoints has been moved to a new `endpoints::common::mail` module.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
c1a3505e51 notify: add PVE/PBS context
This commit moves PVEContext from `proxmox-perl-rs` into the
`proxmox-notify` crate, since we now also need to access it from
`promxox-mail-forward`. The context is now hidden behind a feature
flag `pve-context`, ensuring that we only compile it when needed.

This commit adds PBSContext, since we now require it for
`proxmox-mail-forward`. Some of the code for PBSContext comes
from `proxmox-mail-forward`.

This commit also changes the global context from being stored in a
`once_cell` to a regular `Mutex`, since we now need to set/reset
the context in `proxmox-mail-forward`.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
5f7ac875f6 notify: add mechanisms for email message forwarding
As preparation for the integration of `proxmox-mail-foward` into the
notification system, this commit makes a few changes that allow us to
forward raw email messages (as passed from postfix).

For mail-based notification targets, the email will be forwarded
as-is, including all headers. The only thing that changes is the
message envelope.
For other notification targets, the mail is parsed using the
`mail-parser` crate, which allows us to extract a subject and a body.
As a body we use the plain-text version of the mail. If an email is
HTML-only, the `mail-parser` crate will automatically attempt to
transform the HTML into readable plain text.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
d20d9ec1aa sys: email: add forward
This new function forwards an email to new recipients.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
80c9069389 notify: let a matcher always match if it has no matching directives
This should be a bit more intuitive to users than the current
behavior, which is 'always match' for mode==all and 'never match' for
mode==any. The current behavior originates in the neutral element of
the underlying logical operation (and, or).

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
190d483b2d notify: matcher: introduce common trait for match directives
This allows us to make the match-checking code a bit shorter.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
bdbd55ccff notify: add calendar matcher
This allows matching by a notification's timestamp:

matcher: foo
  match-calendar mon..fri 8-12

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
b421a7ca24 notify: replace filters and groups with matcher-based system
This shifts notification routing into the matcher-system. Every
notification has associated metadata (key-value fields, severity -
to be extended) that can be match with match directives in
notification matchers. Right now, there are 2 matching directives,
match-field and match-severity. The first one allows one to do a
regex match/exact match on a metadata field, the other one allows one
to match one or more severites.
Every matcher also allows 'target' directives, these decide which
target(s) will be notified if a matcher matches a notification.

Since routing now happens in matchers, the API for sending is
simplified, since we do not need to specify a target any more.

The API routes for filters and groups have been removed completely.
The parser for the configuration file will still accept filter/group
entries, but will delete them once the config is saved again. This is
needed to allow a smooth transition from the old system to the new
system, since the old system was already available on pvetest.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
df4858e989 notify: factor out notification content into its own type
This will be useful later for system mail forwarding, where
the content of the mail should be forwarded unchanged.

This moves notification properties into this new type and calls them
'data'. They will exclusively used for template rendering.
`Notification` will receive a separate field for metadata, which
will be useful for notification filtering. This decouples
template rendering and filtering, which enables us to be very precise
about which metadata fields we allow to be used in filters.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Lukas Wagner
b2089c37c5 notify: introduce Error::Generic
... as leaf error-type for anything for which we do not necessarily
want a separate enum variant.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00
Dietmar Maurer
486415f517 PruneJobConfig: remove stale optional flag from the API macro.
The property is not optional - it is defined as "String".

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-11-16 17:47:38 +01:00
Dietmar Maurer
dca6c270a0 pbs-api-types: derive Clone and PartialEq for job config/status types (for GUI)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-11-15 16:37:44 +01:00
Fabian Grünbichler
3fca8ef10d apt: use apt changelog for changelog fetching
support for it got added to Proxmox repositories, so there is no need to use
custom logic and manual fetching for this anymore.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-11-14 08:43:47 +01:00
Dietmar Maurer
7cb2d72b97 DatastoreTuning: fix serde attributes
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-11-12 12:02:19 +01:00
Dietmar Maurer
ecca38b94b DatastoteNotify: fix serde attributes
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-11-12 11:25:09 +01:00
Lukas Wagner
3ac6f2d9c0 http: rate limited stream: fix typo in rustdoc comment
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-08 09:56:26 +01:00
Fiona Ebner
f844271990 apt: repositories: document status property for standard repository
Suggested-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-11-06 19:48:30 +01:00
Thomas Lamprecht
69edfdf985 rest-server: parse upid: improve on comments and variables
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-26 10:41:15 +02:00
Thomas Lamprecht
06fed255cb rest-server: extend documentation for reading status of a UPID
Including some possible pitfalls when using this.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-26 10:26:11 +02:00
Thomas Lamprecht
ba59b84d46 rest-server: code complexity clean-up in task-log rotation
avoid using the negative in the if check and extract the value to be
passed upfront.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-26 10:13:45 +02:00
Thomas Lamprecht
4197e94e25 rest-server: better document task-log archive rotation
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-26 10:13:45 +02:00
Thomas Lamprecht
c8a0ba04ca sys: depreacate PTY module, moves to termproxy directly
As termproxy only used this module from the huge proxmox-sys crate,
and this module was also only used here, it makes sense to move it
over there (and dropping proxmox-sys halved build-time from 8.5 s to
4.2 s).

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-24 13:52:16 +02:00
Gabriel Goller
c1c062bf56 schema: beautify parameter error output
If there is only one error, output in a single line, without any
excessive newlines at the end. If there are multiple errors, show them
in a bulleted list.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2023-10-23 17:40:34 +02:00
Dietmar Maurer
34e86078c2 proxmox-tfa: derive Copy, Clone and PartialEq on TfaType (for GUI)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-10-23 11:44:45 +02:00
Dietmar Maurer
c1819c2549 DailyDuration: derive PartialEq (for GUI)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-10-20 12:01:27 +02:00
Thomas Lamprecht
1e64feeaad rest-server: factor out task-log directory and creation
We had two call sites deriving the directory "shard" where the task
log file is actually saved to, this can lead to ugly bugs and is
better done in a central single-source-of-truth way.

While at it factor out the creation of the log file (and it's shard
directory) to avoid crowding the WorkerTask new fn to much.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-10-18 19:36:37 +02:00
Wolfgang Bumiller
45152c5e3e bump proxmox-client to 0.3.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-10-16 13:27:13 +02:00
Wolfgang Bumiller
2c9c43ca4c human-byte: d/control bump
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-10-16 13:04:48 +02:00
Wolfgang Bumiller
f47ad56812 bump proxmox-human-byte to 0.1.2
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-10-16 13:03:12 +02:00
Dietmar Maurer
83af1cdce4 HumanByte: make fields public
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-10-16 12:27:54 +02:00
Wolfgang Bumiller
9e8f90e667 tfa: more optional dependency cleanup
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-10-16 09:00:18 +02:00
Wolfgang Bumiller
6940908a8a tfa: make totp a feature and mark all optional deps as optional
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-10-16 08:58:09 +02:00
Wolfgang Bumiller
1a6f1efe63 workspace: set resolver to 2 to silence a warning
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-10-16 08:53:35 +02:00
Dietmar Maurer
a5f67f200d TrafficControlCurrentRate: derive Clone and PartalEq (for GUI)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-10-15 11:09:42 +02:00
Dietmar Maurer
a2234de54a move TrafficControlCurrentRate to pbs-api-types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-10-15 11:07:58 +02:00
Dietmar Maurer
d6dab3cf24 TrafficControlRule: derive Clone and PartialEq (for GUI) 2023-10-15 09:25:26 +02:00
Dietmar Maurer
235cde7f03 Interface: add missing serde skip_serializing_if to bond_xmit_hash_policy
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-10-12 12:39:18 +02:00
Dietmar Maurer
9404f0ff9f Interface: fix deserialize (add default)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-10-11 11:38:01 +02:00
Dietmar Maurer
1fd995c54c Interface: derive Clone + PartialEq (for GUI)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-10-11 11:37:15 +02:00
Dietmar Maurer
3b42bca410 move MetricServerInfo definition to pbs-api-types
And derive Clone, Eq and Ord so that we can sort the list in the GUI.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-10-09 08:19:08 +02:00
Wolfgang Bumiller
7e6aa2733a replace deprecated X509Extension::new_nid
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-10-03 13:45:10 +02:00
Fabian Grünbichler
04e2d0e5c3 bump proxmox-api-macro to 1.0.6-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-10-03 09:37:01 +02:00
Fabian Grünbichler
c83627b1a6 bump proxmox-sortable-macro to 0.1.3-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-10-03 09:37:01 +02:00
Fabian Grünbichler
4fd7359677 bump proxmox-router to 2.1.1-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-10-02 09:25:10 +02:00
Wolfgang Bumiller
b232b580a0 update to syn 2
This mostly affected attribute parsing (due to the syn::Meta changes).
Also creating `DelimSpan`s for custom-built `syn::Attribute`s is a
bit... ugly.
Upshot: turns out we can drop some helpers in util.rs with the new
`syn::Meta` changes.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-09-29 13:52:21 +02:00
Wolfgang Bumiller
eb1abe45b6 router: bump env_logger to 0.10 and move to workspace
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-09-28 09:59:48 +02:00
Dietmar Maurer
ee4d9a5567 pbs-api-types: move node status types from src/api2/types/mod.rs
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-09-28 08:13:30 +02:00
Dominik Csapak
9fa4185196 client: fix optional data for errors
previously we changed the internal type of the 'data' property
from Option<T> to T in the assumption the api always returns
'data:null'.

this is actually only the case when the api call succeeds. in an error
case there is no data property at all.

to fix this issue while behaving the same for 'data:null' we have to
revert to Option<T> for RawApiResponse but instead of always throwing an
error for 'data:null' in 'check' we now try there to deserialize from
Value::Null for T if there was no data. This will succeed for the Type
'()' which was the motivation for the original change.

The only downside is that the RawApiResponse now has a trait bound that
T is deserializeable, but was a requirement for using it anyway
(as there was no other way of constructing it)

Fixes: 271a55f ("client: remove option from inner RawApiResponse")
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-09-27 11:27:47 +02:00
Thomas Lamprecht
dc9ee73751 schema: bump version to 2.0.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-20 11:46:02 +02:00
Gabriel Goller
bcdcb181ee fix #4162: sys: added auto-submitted header to email body
`Auto-Submitted` is defined in the rfc 5436 [1] and describes how
an automatic response (f.e. ooo replies, etc.) should behave on the
emails. When using `Auto-Submitted: auto-generated` (or any value
other than `none`) automatic replies won't be triggered.

[1]: https://www.rfc-editor.org/rfc/rfc3834.html

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
2023-09-18 16:41:06 +02:00
Dominik Csapak
359da67e9b rest-server: accept empty body as valid parameters
technically an empty string is not valid json, but when sending an api
request without any parameters, treating the empty body as an empty
parameter hash instead of an error, makes the the api more robust for
clients

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-09-07 18:12:51 +02:00
Hannes Laimer
92be86d776 schema: serialize enum unit variants
... since deserializing them already works

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2023-09-07 17:29:36 +02:00
Thomas Lamprecht
498341ec0d apt: bump version to 0.10.6-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-05 15:29:49 +02:00
Thomas Lamprecht
fb90d53caf apt: use modern format string variables and small style cleanups
note: not complete, there's other code to check and rework, but I had
this already done so commit it, better than nothing.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-05 15:19:38 +02:00
Thomas Lamprecht
4e2cc6fd53 tests: factor out directory cleanup+creation
This moves the clean-up to happen up-front. That way one can still
inspect the test data after, e.g., a failed test.

Originally done almost like this in a patch from Fiona [0] that I just
overlooked, but now also factored out, avoid crowding the test code to
much with duplicate code.

[0]: https://lists.proxmox.com/pipermail/pve-devel/2023-June/057136.html

Suggested-by: Fiona Ebner <f.ebner@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-05 15:15:46 +02:00
Thomas Lamprecht
195e9e1cdf apt: fixup description for Reef repo
the "main" repo only exists for Quincy to allow an easier transition
from Proxmox VE 7 to Proxmox VE 8, for when the enterprise repo got
added for ceph too.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-05 10:09:00 +02:00
Thomas Lamprecht
1e47036528 apt: add Ceph Reef to standard repo list
For now just duplicate the Ceph Quincy entries, as I want to avoid
using macros and we do not yet have support for enums inside enums
with the api macro.

Adapt and expand the tests slightly to have at least some simple
coverage there too.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-04 17:58:45 +02:00
Thomas Lamprecht
767582a2eb apt: unify match-arm for ceph sources.list location
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-04 17:58:45 +02:00
Thomas Lamprecht
ae3c04a76b apt: tests: allow re-running digest test without clean-up
Files inside CARGO_TARGET_TMPDIR are only cleaned on `cargo clean`, so
tests that expect files to not exist need to cleanup themselves.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-09-04 17:58:45 +02:00
Dietmar Maurer
c879704337 AclListItem: derive Clone and PartialEq
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-09-02 16:24:45 +02:00
Lukas Wagner
bcd134a349 sys: fs: move tests to a sub-module
This ensures that test code is not compiled in regular builds

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-30 17:37:31 +02:00
Dietmar Maurer
51bae22b4d TaskListItem: derive Clone and PartialEq 2023-08-30 13:34:51 +02:00
Dominik Csapak
299a478f15 proxmox-time: implement epoch_to_rfc3339 for wasm
we just printed out the UTC version, this implements a localized version

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-08-30 09:20:38 +02:00
Dominik Csapak
151e2cfdfd time: make RFC3339 format in wasm conform to usual format
on other targets we print the timestamp without fractional seconds
('.xxxZ'), so we should remove that too on wasm

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-08-30 09:18:44 +02:00
Dominik Csapak
271a55f187 client: remove option from inner RawApiResponse
when using the client for an api call that does not return any data
(it returns '{"data":null}'), we would always get an error 'api returned
no data'. The message is technically correct, but it should not be an
error when we expect no data (e.g. most of our CRUD PUT/POST calls)

instead of having the Option<T> in the RawApiResponse type itself, move
it into to the 'nodata' function intended for api calls where we don't
expect any data.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-08-29 14:14:52 +02:00
Dietmar Maurer
022fdacb25 proxmox-client: add post_without_body
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-08-27 16:19:31 +02:00
Lukas Wagner
d49fc1aa2f notify: make template rendering helpers more robust
This commit has the aim of making template rendering a bit more
robust. It does so by a.) Accepting also strings for helpers that
expect a number, parsing the number if needed, and b.) Ignoring errors
if a template helper fails to render a value and showing an error in
the logs, instead of failing to render the whole template (leading
to no notification being sent).

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-25 15:32:34 +02:00
Wolfgang Bumiller
c81068097b bump proxmox-client to 0.2.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-25 09:03:00 +02:00
Wolfgang Bumiller
8617442560 client: fixup checks for api calls not returning data
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-25 09:03:00 +02:00
Wolfgang Bumiller
f20f9bb9f7 client: set content type header on requests
this got lost with the recent refactoring

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-25 08:56:41 +02:00
Wolfgang Bumiller
6286ff4eeb bump proxmox-client to 0.2.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-25 08:37:25 +02:00
Wolfgang Bumiller
d7f6fc4db5 client: fix content type parsing with included charset
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-25 08:36:19 +02:00
Max Carrara
1f351625a5 async: runtime: Modernise module and update docs
This commit updates all helper functions, taking into account recent
developments regarding `tokio`.

In particular, the `block_in_place()` and `block_on()` functions now
don't panic anymore if used within the single-threaded `tokio` runtime
and instead behave as expected in both runtime flavours.

Furthermore, because `tokio` may add more runtime flavours in the
future, all helpers will now panic if used within an unsupported
runtime. This is to prevent unforeseen behavioural quirks and
interactions with `tokio` internals.

The above changes make `BlockingGuard` redundant; it is consequently
removed.

The documentation is also updated, describing the behaviour of the
helper functions and the purpose of the `runtime.rs` module in more
detail.

Signed-off-by: Max Carrara <m.carrara@proxmox.com>
2023-08-23 09:51:52 +02:00
Max Carrara
ede73a6561 client/login: clippy fixes
Signed-off-by: Max Carrara <m.carrara@proxmox.com>
2023-08-22 10:04:16 +02:00
Wolfgang Bumiller
68fe8baf95 bump proxmox-client to 0.2.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-16 14:33:01 +02:00
Wolfgang Bumiller
97025d4143 package proxmox-login 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-16 14:30:38 +02:00
Stefan Sterz
8e3e83318b fix: api-types: add support for lto 9 tape density
lto 9 tapes have a new density code which leads to these tapes not
being recognized properly. add the new density code and TapeDensity to
improve lto 9 support. since the documentation states that we support
lto 5 and above this constitutes a bug fix for lto 9 support.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-08-11 08:52:50 +02:00
Wolfgang Bumiller
a909d5789c client: convenience helper to get a serialized ticket
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-10 14:33:55 +02:00
Wolfgang Bumiller
490008d596 client: expose AuthenticationKind
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-10 14:27:31 +02:00
Wolfgang Bumiller
bea97ccce1 client: add Client::set_authentication method
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-10 14:14:06 +02:00
Wolfgang Bumiller
2fd5502321 bump proxmox-client to 0.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-10 11:15:36 +02:00
Wolfgang Bumiller
604e467684 client: add TlsOptions::parse_fingerprint
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-10 10:44:04 +02:00
Wolfgang Bumiller
dff830ba04 client: impl HttpApiClient for refs, Arcs and Rcs
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-09 15:29:38 +02:00
Wolfgang Bumiller
a3322e49b9 client: put requests
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-09 15:29:38 +02:00
Wolfgang Bumiller
ffe908f636 client: handle response data
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-09 13:21:02 +02:00
Wolfgang Bumiller
1c96afd0ec client: turn Client inside out
Since the WASM client cannot actually use a `http::Request` the way we
expect it to, that is, it cannot manually along cookies, we turn the
client bit inside out:

This crate mainly defines the `HttpApiClient` trait which expects the
http client to perform *authenticated* API calls, that is, the
handling of API tokens and tickets should happen at the *implementor*
side.

The product clients will require *this* trait to be implemented, and
will not themselves offer a way to login.

As for the `Client` struct, this will now instead *implement* this
trait and will *not* be used in the `wasm` ecosystem. Rather, this is
the ticket handling http client that already exists in the PWT based
ui code.

The PVE client in `pve-api-types` will not *contain* a `Client`
anymore, but rather, it will provide PVE api call implementations for
something implementing `HttpApiClient`.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-09 13:21:02 +02:00
Wolfgang Bumiller
0f19f2125f client: drop environment and login methods
The environment trait was useful on the CLI, but does not really
translate well to eg. the wasm ui (or pdm for that matter), so drop it
and instead have `.login` and `.login_tfa` just take the
`proxmox_login` type and handle the updating of authentication data.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-09 13:21:02 +02:00
Wolfgang Bumiller
a9a267f04f client: replace Error trait with a type
Because we ultimately also want to drop the `Environment` trait since
it is not suitable for all use cases (eg. wasm ui)

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-09 13:21:02 +02:00
Wolfgang Bumiller
e0b102d932 client: prepare to get rid of Error trait
First rename it so it's clear what "Error" refers to in the following
patches.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-09 13:21:02 +02:00
Wolfgang Bumiller
0c45d51406 login: add userid and api_url getters
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-09 13:21:02 +02:00
Max Carrara
8841b389e3 proxmox-io: fix sparse_copy not copying sparsely on irregular read operations
In the uncommon circumstance that calls to `read()` end up reading any number of
bytes other than 4096, the subsequently read bytes become misaligned, causing
blocks of zeroes to be written unnecessarily.

To illustrate, imagine you have a 12KiB file:

  [x][x][x][x][ ][ ][ ][ ][x][x][x][x]
   └──4096──┘  └──4096──┘  └──4096──┘

The first and last block are filled with some data, whereas the middle block is
empty and will therefore result in only zeroes being read.

In order for the empty block to be skipped with `seek()`, the entire buffer has
to be filled with zeroes.

If, for example, the first `read()` ends up putting only 3KiB into the buffer,
the empty block in the middle won't be detected properly, as the buffer will
now always contain some data. What results are four misaligned reads:

  [x][x][x][x][ ][ ][ ][ ][x][x][x][x]
   ├─────┘  ├────────┘  ├────────┘  │
   1        2           3           4

This is fixed by ensuring chunks of 4KiB are always read into the buffer,
except when the last block is truncated. In order to prevent frequent small
reads, the incoming reader is also buffered via `io::BufReader`.

Signed-off-by: Max Carrara <m.carrara@proxmox.com>
2023-08-09 12:48:04 +02:00
Wolfgang Bumiller
e9499bbcf2 bump proxmox-ldap to 0.2.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-08 14:09:19 +02:00
Wolfgang Bumiller
ade1d19b0a bump proxmox-apt to 0.10.5-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-08 14:05:51 +02:00
Stefan Sterz
c74167f528 ldap: only search base of base_dn when checking connection
this should avoid most common size limitations. the search should also
complete quicker as fewer results need to be computed. note that this
way a configuration may be accepted, but the related sync job can
fail due to and exceeded size limit warning for some ldap servers
(such as 2.5.14+dfsg-0ubuntu0.22.04.2).

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-08-08 14:01:27 +02:00
Stefan Sterz
92e02f6e33 ldap: add an integration test for check_connection
Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-08-08 14:01:25 +02:00
Fabian Grünbichler
445e032eee fix #4868: map missing section field to 'unknown'
needed for supporting some third-party repositories.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-08-08 13:59:17 +02:00
Christoph Heiss
9fc23c1335 api-types: drop unused leftover file
Commit 5720ba2d ("use new auth api crate") moved all auth-related code
into it's own crate inside the `proxmox` repo, including this file. Thus
drop it here, it's not even included in the compile.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
2023-08-08 12:00:09 +02:00
Lukas Wagner
5ea70421b3 clippy fix: casting to the same type is unnecessary
See: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:48:01 +02:00
Lukas Wagner
41b2e49123 clippy fix: deref on an immutable reference
See:
https://rust-lang.github.io/rust-clippy/master/index.html#borrow_deref_ref

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
a36769b11a clippy fix: complex type definitions
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
3c7c9fc55d clippy fix: the following explicit lifetimes could be elided
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
6c38a997af clippy fix: useless use of format!
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
58b29dfbcf clippy fix: warning: this let-binding has unit value
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
b6abb52a78 clippy fix: binary comparison to literal Option::None
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
8f7566209c clippy fix: unnecessary use of to_string
See:
https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
b272279ef1 clippy fix: you should consider adding a Default implementation
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
268fcfb43a clippy fix: this (Default) impl can be derived
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
6f9cc14b9e clippy fix: redundant closure
See:
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
82164203bd clippy fix: unneeded return statement
See:
https://rust-lang.github.io/rust-clippy/master/index.html#needless_return

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
81ca4ae6a1 clippy fix: needless borrow
See:
https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
b4b186c544 clippy fix: calls to drop with a value that implements Copy
Dropping a copy leaves the original intact

See:
https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy

I assume the `drop` was used to silence a 'unused variable' warning,
so I silenced it by other means.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:29:36 +02:00
Lukas Wagner
de6337ae6d clippy fix: the borrowed expression implements the required traits
See: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-08-08 11:05:20 +02:00
Dietmar Maurer
b7a64cd4a2 proxmox-login: depend on js_sys on wasm32
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-08-07 12:03:49 +02:00
Dietmar Maurer
ffa64bead7 add special impl for epoch_i64() on target_arch="wasm32" 2023-08-07 11:56:15 +02:00
Wolfgang Bumiller
17f5eac57a client: drop Send for non-wasm as well on response future
To see if it is even still necessary given that it's not a trait
object type where auto traits would need to be explicit...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-07 11:06:10 +02:00
Wolfgang Bumiller
1531a619ab client: rename response future to ResponseFuture
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-07 10:59:07 +02:00
Wolfgang Bumiller
a7435e757b client: drop retry logic
This should be moved to where we actually need it, not be part of the
generic product client.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-07 10:57:39 +02:00
Dietmar Maurer
2b212cf4e3 proxmox-client: do not require Send for wasm32 target
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-08-05 09:10:28 +02:00
Wolfgang Bumiller
748588f81c proxmox-login: add 'source' impls for errors
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-03 13:03:30 +02:00
Wolfgang Bumiller
34a79cdd0c bump proxmox-api-macro to 1.0.5-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-03 08:25:13 +02:00
Wolfgang Bumiller
ba6a628601 client: getters for the inner client
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-03 08:22:45 +02:00
Dietmar Maurer
9599cb6fd6 proxmox-login: fix ticket userid check for PMG quarantine tickets
We simply strip the "@quarantine" at the end.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2023-08-02 15:10:44 +02:00
Wolfgang Bumiller
25024fa687 import proxmox-client crate
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-01 15:49:43 +02:00
Wolfgang Bumiller
a9191c2253 update README
include repository.workspace=true for the [package] base section

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-01 15:48:19 +02:00
Wolfgang Bumiller
d6a550b71c login: improve response handling
we have use cases where we have bytes, and serde_json has a from_slice
method, doing the utf-8 check unnecessarily is pointless, while going
from &str to &[u8] is free...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-01 15:21:09 +02:00
Wolfgang Bumiller
2226107cc6 api-types: set serde defaults for UserWithTokens
since `totp_locked` is not wrapped in an `Option` we need to
explicitly tell serde about its default

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-08-01 09:12:43 +02:00
Wolfgang Bumiller
5859017061 bump proxmox-notify to 0.2.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-28 11:56:56 +02:00
Wolfgang Bumiller
17cf5f9593 more import cleanups
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-28 11:47:39 +02:00
Wolfgang Bumiller
7cb339dfa3 notify: cleanup all the imports sections
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-28 11:47:39 +02:00
Wolfgang Bumiller
985717d948 bump proxmox-router to 2.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-28 11:26:01 +02:00
Wolfgang Bumiller
1ce7294e06 http-error: add debian packaging and bump as 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-28 11:06:48 +02:00
Lukas Wagner
1a75668dc9 notify: use HttpError from proxmox-http-error
Also improve API documentation in terms of which HttpError is
returned when.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-28 10:47:07 +02:00
Lukas Wagner
add38769f8 router: re-export HttpError from proxmox-http-error
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-28 10:47:05 +02:00
Lukas Wagner
779c45eaad http-error: add new http-error crate
Break out proxmox-router's HttpError into it's own crate so that it can
be used without pulling in proxmox-router.

This commit also implements `Serialize` for `HttpError` so that it can
be returned from perlmod bindings, allowing Perl code to access the
status code as well as the message.

Also add some smoke-tests to make sure that the `http_bail` and
`http_err` macros actually produce valid code.

Suggested-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-28 10:47:03 +02:00
Lukas Wagner
a1cbaea766 notify: fix tests if not all features are enabled
Some tests are now disabled if not all required features are enabled.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-28 10:43:57 +02:00
Lukas Wagner
f6fa851d1f notify: fix build warnings if not all features are enabled
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-28 10:43:55 +02:00
Wolfgang Bumiller
1e095e5ab8 bump proxmox-notify to 0.1.0-1, initial release
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 11:05:45 +02:00
Wolfgang Bumiller
d951b3329f bump proxmox-auth-api to 0.3.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 11:03:49 +02:00
Wolfgang Bumiller
7ea502b351 bump proxmox-section-config to 2.0.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 11:03:15 +02:00
Wolfgang Bumiller
9c5579d755 bump proxmox-subscription to 0.4.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 10:57:50 +02:00
Wolfgang Bumiller
1a8a3668a5 bump proxmox-tfa to 4.0.5
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 10:56:14 +02:00
Wolfgang Bumiller
b86a0883ea bump proxmox-rest-server to 0.4.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 10:54:18 +02:00
Wolfgang Bumiller
33d9ea78ef bump proxmox-apt to 0.10.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 10:53:44 +02:00
Wolfgang Bumiller
d0bce77e50 bump proxmox-human-byte to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 10:53:44 +02:00
Wolfgang Bumiller
93fc5b503a bump proxmox-router to 2.0.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 10:53:44 +02:00
Wolfgang Bumiller
ac6a52dfd1 bump proxmox-schema to 2.0.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 10:42:14 +02:00
Lukas Wagner
b46d333d98 notify: add debian packaging
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
[w.bumiller@proxmox.com: set d/changelog to UNRELEASED]
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-24 10:34:52 +02:00
Lukas Wagner
9b726bb072 notify: additional logging when sending a notification
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:26:08 +02:00
Lukas Wagner
2756c11c2f notify: ensure that filter/group/endpoint names are unique
Otherwise, a filter with the same name as an already existing
endpoint or group can overwrite it.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:26:06 +02:00
Lukas Wagner
62ae1cf959 notify: on deletion, check if a filter/endp. is still used by anything
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:26:04 +02:00
Lukas Wagner
3c9584296f notify: api: allow to query entities referenced by filter/target
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:26:02 +02:00
Lukas Wagner
d44ce2c70d notify: gotify: add proxy support
The proxy configuration will be read from datacenter.cfg via
a new method of the `Context` trait.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:26:01 +02:00
Lukas Wagner
da2e7b8da9 notify: sendmail: query default author/mailfrom from context
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:59 +02:00
Lukas Wagner
c5f91aa1c8 notify: sendmail: allow users as recipients
This introduces a new configuration parameter `mailto-user`.
A user's email address will be looked up in the product-specific
user database.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:57 +02:00
Lukas Wagner
d6c1f181d6 notify: add context
Since `proxmox-notify` is intended to be used by multiple products,
there needs to be a way to inject product-specific behavior.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:51 +02:00
Lukas Wagner
97dac11823 notify: add example for template rendering
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:49 +02:00
Lukas Wagner
4865711339 notify: add template rendering
This commit adds template rendering to the `proxmox-notify` crate, based
on the `handlebars` crate.

Title and body of a notification are rendered using any `properties`
passed along with the notification. There are also a few helpers,
allowing to render tables from `serde_json::Value`.

'Value' renderers. These can also be used in table cells using the
'renderer' property in a table schema:
  - {{human-bytes val}}
    Render bytes with human-readable units (base 2)
  - {{duration val}}
    Render a duration (based on seconds)
  - {{timestamp val}}
    Render a unix-epoch (based on seconds)

There are also a few 'block-level' helpers.
  - {{table val}}
    Render a table from given val (containing a schema for the columns,
    as well as the table data)
  - {{object val}}
    Render a value as a pretty-printed json
  - {{heading_1 val}}
    Render a top-level heading
  - {{heading_2 val}}
    Render a not-so-top-level heading
  - {{verbatim val}} or {{/verbatim}}<content>{{#verbatim}}
    Do not reflow text. NOP for plain text, but for HTML output the text
    will be contained in a <pre> with a regular font.
  - {{verbatim-monospaced val}} or
      {{/verbatim-monospaced}}<content>{{#verbatim-monospaced}}
    Do not reflow text. NOP for plain text, but for HTML output the text
    will be contained in a <pre> with a monospaced font.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:48 +02:00
Lukas Wagner
109a936b6b notify: api: add API for filters
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:46 +02:00
Lukas Wagner
ee0ab52b9b notify: add notification filter mechanism
This commit adds a way to filter notifications based on severity. The
filter module also has the necessary foundation work for more complex
filters, e.g. matching on properties or for creating arbitarily complex
filter structures using nested sub-filters.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:43 +02:00
Lukas Wagner
ee44fdca04 notify: api: add API for groups
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:41 +02:00
Lukas Wagner
ed5d27ba24 notify: add notification groups
When notifying via a group, all endpoints contained in that group
will send out the notification.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:39 +02:00
Lukas Wagner
055db2d107 notify: api: add API for gotify endpoints
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:37 +02:00
Lukas Wagner
990fc8efd2 notify: add gotify endpoint
Add an endpoint for Gotify [1], showing the how easy it is to add new
endpoint implementations.

[1] https://gotify.net/

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:35 +02:00
Lukas Wagner
21c5c9a0c7 notify: api: add API for sendmail endpoints
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:33 +02:00
Lukas Wagner
7c42752690 notify: add sendmail plugin
This plugin uses the 'sendmail' command to send an email
to one or more recipients.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:31 +02:00
Lukas Wagner
714ef27786 notify: api: add API for sending notifications/testing endpoints
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:30 +02:00
Lukas Wagner
ad3f78a315 notify: preparation for the API
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:28 +02:00
Lukas Wagner
2726e68afe notify: preparation for the first endpoint plugin
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:27 +02:00
Lukas Wagner
b8040a23cb add proxmox-notify crate
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:25 +02:00
Lukas Wagner
82be261447 schema: add schema/format for comments
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:22 +02:00
Lukas Wagner
8013a80b41 section-config: derive Clone for SectionConfigData
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-07-24 10:25:20 +02:00
Wolfgang Bumiller
f6e089555e schema: verify property strings w/ new serde code
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-13 16:19:19 +02:00
Wolfgang Bumiller
12da5121ff schema: predictable order of errors for tests
Otherwise we'd have to "search" & match the errors...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-13 16:19:17 +02:00
Wolfgang Bumiller
c702638bd8 schema: fixup empty error list handling
Some(<empty list of errors>) does not actually signal an error...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-13 16:19:16 +02:00
Wolfgang Bumiller
aa10025366 schema: guard property string constraint checking
StringSchema::check_constraint runs `parse_property_string` for
property strings, but when we deserialize a `PropertyString` we
immediately follow that up with deserializing it using the schema, so
there's no need to check it beforehand.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-13 16:19:13 +02:00
Wolfgang Bumiller
1a46283b78 schema: get rid of some unsafe code
the borrow tracking won't hurt...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-13 16:19:11 +02:00
Wolfgang Bumiller
73a20c7f5f schema: use schema when serializing property strings
Adds a Schema to the `PropertyString` type and uses it for better
serialization.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-13 16:19:09 +02:00
Wolfgang Bumiller
413d631fa6 schema: convenience accessors to schema subtypes
Adds `const fn <type>(&self) -> Option<&<Type>Schema>` methods to
`Schema`.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-13 16:19:05 +02:00
Wolfgang Bumiller
5a64f3258a schema: serde based property string de- and serialization
This provides `proxmox_schema::property_string::PropertyString<T>` for
a typed property-string.

To facilitate this, this introduces
`proxmox_schema:🇩🇪:SchemaDeserializer` which is a serde deserializer
for property strings given a schema.

This basically maps to one of `de::SeqAccess` (for array schemas) or
`de::MapAccess` (for object schemas).

Additionally, a `de::NoSchemaDeserializer` is added, since properties
within the strings may have string schemas with no format to it, while
the type we serialize to may ask for an array (a simple "list") via
serde.

The deserializers support borrowing, for which a helper `Cow3` needed
to be added, since property strings support quoting with escape
sequences where an intermediate string would be allocated and with an
intermediate lifetime distinct from the `'de` lifetime.

A `de::verify` module is added which uses serde infrastructure to
validate schemas without first having to deserialize a complete
`serde_json::Value`.

For serialization, `proxmox_schema::ser::PropertyStringSerializer` is
added split into similar parts `ser::SerializeStruct` and
`ser::SerializeSeq` at the top level, and the same prefixed with
`Element` for inside the actual string. This should also properly
quote the contents if required.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-13 16:18:58 +02:00
Wolfgang Bumiller
4ca8dbf74f auth-api: fixup examples
These were missing the new client-ip parameter in the auth
function calls which was introduced to support `PAM_RHOST`.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-10 09:06:35 +02:00
Thomas Lamprecht
ec5e2a5d90 proxmox-apt: bump to 0.10.3-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-07-05 12:38:08 +02:00
Wolfgang Bumiller
50136f1817 bump proxmox-tfa to 4.0.4
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-05 10:46:12 +02:00
Wolfgang Bumiller
8547ee31da tfa: also reset counters when unlocking tfa
Since this requires access to the user data, we need to add
a generic parameter to the unlock methods.
To avoid having to create another major API bump affecting
all our products this short after release, we keep the old
version around with the old behavior.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-07-04 14:45:18 +02:00
Fabian Grünbichler
415d60daf9 release file: extend component fixup to bookworm
else mirroring bookworm-security will skip *all* components..

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-06-29 13:11:17 +02:00
Thomas Lamprecht
2a070da065 proxmox-rest-server: bump to 0.4.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-27 12:44:52 +02:00
Thomas Lamprecht
880abd859b proxmox-apt: bump version to 0.10.2-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-27 12:36:05 +02:00
Wolfgang Bumiller
902a0e8cb5 api: include tfa lock status in user list
Like in PVE.
This means that /access/users is now a 'protected' call to
get access to 'tfa.cfg'.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-26 19:47:16 +02:00
Stefan Sterz
f486f8485c access: ldap check connection on creation and change
this commit makes the ldap realm endpoints check whether a new or
updated configuration works correctly. it uses the new
`check_connection` function to make sure that a configuration can be
successfully used to connect to and query an ldap directory.

doing so allows us to remove the ldap domain regex. instead of relying
on a regex to make sure that a given distinguished name (dn) could be
correct, we simply let the ldap directory tell us whether it accepts
it. this should also aid with usability as a dn that looks correct
could still be invalid.

this also implicitly removes unauthenticated binds, since the new
`check_connection` function does not support those. it will simply
bail out of the check if a `bind_dn` but no password is configured.
therefore, this is a breaking change.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-06-26 16:08:24 +02:00
Wolfgang Bumiller
54cb9be8ed bump proxmox-ldap to 0.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-26 14:22:50 +02:00
Stefan Sterz
7f135263a9 ldap: add check_connection function
this function checks if a given connection could work. it uses the
current config to connect to an ldap directory and perform a search
with the provided base_dn. this enables us to verify a connection
before storing it in a more meaningful way than with a regex.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-06-26 14:15:35 +02:00
Stefan Sterz
599a6a49da ldap: remove support for unauthenticated binds
by using the default empty string if no password was provided,
unauthenticated binds were possible. to bring pbs in-line with pve,
switch to throwing an error in this case instead. however, this will
break any pre-existing setup that relied on this behavior.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
2023-06-26 14:15:33 +02:00
Lukas Wagner
053e83c3c7 api-types: client: datastore: tools: use proxmox-human-bytes crate
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-06-26 13:56:45 +02:00
Wolfgang Bumiller
962ce920a0 bump proxmox-human-byte to 0.1.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-26 13:37:29 +02:00
Wolfgang Bumiller
c611afcf0d human-byte: update copyright format
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-26 13:37:29 +02:00
Lukas Wagner
77dbc2fe18 human-byte: move tests to their sub module
The `#[cfg(test)]` directive ensures that the tests are not compiled
for non-test builds.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-06-26 13:24:51 +02:00
Lukas Wagner
c3545d6644 add proxmox-human-byte crate
The module previously lived in `pbs-api-types`, however turned out to
be useful in other places as well (POM, proxmox-notify), so it is moved
here as its own micro-crate.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-06-26 13:24:48 +02:00
Wolfgang Bumiller
d5b9f166a2 cargo fmt
bigger changes are only in the new crates
rest are minor ones

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-23 11:50:39 +02:00
Fabian Grünbichler
b687edc1a0 rest: remove full static file path from error messages
this triggers certain security scanners, and having the requested path instead
gives basically the same information anyhow.

reported on the forum: https://forum.proxmox.com/threads/404-path-disclosure-vulnerability.129187/

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-06-23 11:46:52 +02:00
Wolfgang Bumiller
5791af8ff4 ldap: rustfmt
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-23 11:20:25 +02:00
Lukas Wagner
378e2380b7 ldap: surround user filter expression in parenthesis if not already
In PVE, the `filter` attribute is surrounded in () if it is not already,
allowing "uid=test" as well as "(uid=test)" [1].

A forum user [2] just ran into this inconsistency, so I decided to adjust
the behavior.

[1] https://git.proxmox.com/?p=pve-common.git;a=blob;f=src/PVE/LDAP.pm;h=ff98e367e63265bf76c0f302847c3749eea095a6;hb=HEAD#l115
[2] https://forum.proxmox.com/threads/ldap-query-for-security-group-members.127882/

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-06-23 11:19:04 +02:00
Maximiliano Sandoval
84edb20eb8 tfa: Improve TOTP algorithm parsing
It is very common for TOTP URIs to contain the algorithm in lowercase,
hence we convert to lowercase when doing From<&str> for Algorithm.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
2023-06-20 12:39:46 +02:00
Wolfgang Bumiller
16d512b4d9 bump proxmox-compression to 0.2.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-15 11:01:19 +02:00
Wolfgang Bumiller
5b55ea797a compression: match style fixup
if the match arms are this far away from the actual `match`
keyword, this needs to be split up...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-15 10:37:59 +02:00
Dominik Csapak
af46b655d4 compression: zip_directory: improve error handling
when zipping a directory, our intention was to skip over files that
cannot be zipped (e.g. the file can't be read/vanished/etc.), so we
ignored errors and simply logged it.

but when 'add_entry' fails, we will never actually restore, since every
error there is fatal to the point that the zip cannot be finished thats
because we take the 'target' sink out of self, and only insert it again
after all writes succeeded. so if an error occurs in between 'target' is
not put into self again (and never will be) and the zip cannot be
finished (even if we would catch all those intermediate errors and
restore 'target', we don't know in which state the output was, so we're
unable to finish a valid zip)

to fix that, split the actual 'add_entry' part there out of the async
move block and treat its errors always as fatal

without this, we generate heaps of log lines even after an error
occurred, and can never recover

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-06-15 10:28:50 +02:00
Wolfgang Bumiller
39a486f574 bump proxmox-tfa to 4.0.3
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-14 13:52:20 +02:00
Wolfgang Bumiller
5f1a0bc0c9 tfa: make TfaUser fields public
So we can print them in the proxmox-backup-manager CLI text
output.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-14 13:50:38 +02:00
Wolfgang Bumiller
ba39c5f990 auth-api: bump d/control
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-14 13:50:29 +02:00
Wolfgang Bumiller
cc091c4fe5 bump proxmox-auth-api to 0.3.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-14 09:55:31 +02:00
Wolfgang Bumiller
a228a22918 auth-api: set PAM_RHOST during pam authentication
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-14 09:32:03 +02:00
Wolfgang Bumiller
8f08039e7e auth-api: drop pam crate
it's too limited

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-14 08:51:43 +02:00
Fabian Grünbichler
c8433e3219 fix #4653: (In)Release file: improve handling of special suites
APT doesn't mind a repository with either "/" or "./" as suite/distribution,
such as

 deb https://example.com/debian ./

in that case, the 'dists' part of the URL and the trailing slash (which would
be encoded as '_') is dropped in the file name in '/var/lib/apt/lists/'.

Other suite values with a trailing or leading '/' are rejected with an error by APT:

 E: Malformed entry 1 in sources file /etc/apt/sources.list.d/test.list (absolute Suite Component)
 E: The list of sources could not be read.

so this should be the only special case requiring handling.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-06-06 18:24:39 +02:00
Fabian Grünbichler
27cd025053 fallback to Release file for Origin retrieval
APT will not store the InRelease file in some cases, and some repositories
might not even have one in the first place.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-06-06 18:24:39 +02:00
Wolfgang Bumiller
35dc1b0b8d bump proxmox-tfa to 4.0.2
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-05 08:59:55 +02:00
Thomas Lamprecht
0fb7ec0c32 buildsys: also cleanup *.build files for convenience
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-04 18:27:46 +02:00
Thomas Lamprecht
45432b689d apt: bump version to 0.10.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 18:29:04 +02:00
Thomas Lamprecht
a367f6a0d8 build.sh: split overly long line
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 16:48:10 +02:00
Thomas Lamprecht
ce6a87d513 buildsys: don't try to resolve dependency metadata when assembling crate list
A build-system that needs all build-dependencies even for a simple
make clean invocation is a PITA..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-06-03 16:39:39 +02:00
Fiona Ebner
cc17861f54 apt: tests: add tests for Ceph Quincy repository detection on Bookworm
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-03 14:39:05 +02:00
Fiona Ebner
06ad528c94 apt: tests: code cleanup to avoid useless vector
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-03 14:39:05 +02:00
Fiona Ebner
9b7c5339d7 apt: tests: create temporary test directories in CARGO_TARGET_TMPDIR
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-03 14:39:05 +02:00
Fiona Ebner
bae15e2408 apt: split Ceph main repository into no-subscription and enterprise
The old 'main' component stays valid, pointing to no-subscription,
which means the is_referenced_repository() check needs a special case
for it. It will eventually go away, together with the handles for
Quincy.

Alternatively, the standard repository's info() could've been changed
to return multiple possible components, similar to URLs, but as
opposed to URLs, there could be a standard repository that wants to
have multiple components and it feels a bit unnatural, because
multiple components are usually not aliases of the same. And adapting
is_referenced_repository() would be needed here too. So overall, the
above alternative just felt better.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-03 14:39:05 +02:00
Fiona Ebner
21b58c3384 apt: drop older Ceph standard repositories
On Proxmox VE 8, only Quincy and newer will be supported.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
2023-06-03 14:39:05 +02:00
Wolfgang Bumiller
4508c8b23c add some missing d/source/format files
For crates which already used (native) versioning.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-02 08:44:50 +02:00
Wolfgang Bumiller
abd2558b01 tfa: reduce default lockout time to an hour
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-01 15:10:53 +02:00
Wolfgang Bumiller
adb868ee08 tfa: include lockout status in the tfa user list
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-06-01 12:22:18 +02:00
Wolfgang Bumiller
c5a9fa8595 bump proxmox-tfa to 4.0.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-31 12:54:47 +02:00
Wolfgang Bumiller
f3666afd22 tfa: add d/source/format
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-31 12:54:47 +02:00
Wolfgang Bumiller
a26ec45d74 tfa: add api::methods::unlock_tfa
This mostly serves as documentation for the API call to be
implemented across our products. It's otherwise already just
a oneliner on the TfaConfig.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-31 12:54:47 +02:00
Wolfgang Bumiller
46c15171e6 buildsys: remove format.debcarg.hint file
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-31 12:54:47 +02:00
Wolfgang Bumiller
656ec1e7db bump proxmox-openid to 0.10.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-24 09:50:33 +02:00
Wolfgang Bumiller
c25c1cf4cd bump proxmox-apt to 0.10.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-24 09:50:33 +02:00
Wolfgang Bumiller
3c85df6830 proxmox-apt: update tests to expect bookworm
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-24 09:50:33 +02:00
Wolfgang Bumiller
077a83f401 add proxmox-apt and proxmox-openid to workspace
and fixup d/copyright

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-24 09:24:27 +02:00
Wolfgang Bumiller
6253f263ce Merge branch 'proxmox-openid-merge' 2023-05-24 09:22:09 +02:00
Wolfgang Bumiller
68ebe9ec8a Merge branch 'proxmox-apt-merge' 2023-05-24 09:22:04 +02:00
Wolfgang Bumiller
0ff81719ad move to proxmox-apt/
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-24 09:21:55 +02:00
Wolfgang Bumiller
88d1783a65 move to proxmox-openid/
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-24 09:20:44 +02:00
Wolfgang Bumiller
6c8191471e bump proxmox-subscription to 0.4.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Wolfgang Bumiller
e5ff0dc40b bump proxmox-auth-api to 0.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Wolfgang Bumiller
c531c314c6 bump proxmox-rest-server to 0.4.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Wolfgang Bumiller
ca56a67251 bump proxmox-metrics to 0.3.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Wolfgang Bumiller
af2d4c6c86 bump proxmox-http to 0.9.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Wolfgang Bumiller
644852296d bump proxmox-shared-memory to 0.3.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Wolfgang Bumiller
4f2a7d971b bump proxmox-compression to 0.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Wolfgang Bumiller
dd87a120bd bump proxmox-sys to 0.5.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Wolfgang Bumiller
bd63af3c3b make upload: bump dist to bookworm
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Wolfgang Bumiller
818ac8e708 update zstd 0.6 -> 0.12 for bookworm
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2023-05-23 13:02:51 +02:00
Stefan Hanreich
f971b8c1e6 partial fix #3701: sync job: pull: add transfer-last parameter
Specifying the transfer-last parameter limits the amount of backups
that get synced via the pull command/sync job. The parameter specifies
how many of the N latest backups should get pulled/synced. All other
backups will get skipped.

This is particularly useful in situations where the sync target has
less disk space than the source. Syncing all backups from the source
is not possible if there is not enough disk space on the target.
Additionally this can be used for limiting the amount of data
transferred, reducing load on the network.

The newest backup will always get re-synced, regardless of the setting
of the transfer-last parameter.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
2023-04-24 15:18:51 +02:00
Dominik Csapak
175a8c6d7e api types: fix non-capturing group syntax
a non capturing group is '(?:)' not '(:?)' so fix that.
None of these regexes are used where would use capturing groups.
DATASTORE_MAP_REGEX and TAPE_RESTORE_SNAPSHOT_REGEX are only used
as api types and BLOCKDEVICE_NAME_REGEX is only used once outside of the
api and there we also don't look at the capturing groups.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-04-06 07:28:58 +02:00
Thomas Lamprecht
cd0d1cbc62 api-types: anchor datastore-map regex
Fixes: 4c4e5c2b ("api2/tape/restore: enable restore mapping of datastores")
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-29 11:58:59 +02:00
Lukas Wagner
92cca750d9 api-types: ldap: properly anchor DN regex
Otherwise, a substring match is enough to fulfill the constraint.

Fixes: 3aba0d9a ("api-types: ldap: add verification regex for LDAP DNs")
Reported-by: Friedrich Weber <f.weber@proxmox.com>
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-03-29 11:29:10 +02:00
Lukas Wagner
3aba0d9aa6 api-types: ldap: add verification regex for LDAP DNs
Regex was taken from the LDAP implementation in PVE.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-03-28 16:37:14 +02:00
Wolfgang Bumiller
5720ba2dce use new auth api crate
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-03-02 17:01:35 +01:00
Lukas Wagner
8b3d568beb server: add LDAP realm sync job
This commit adds sync jobs for LDAP user sync. As of now, they
can only be started manually.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-02-10 12:46:19 +01:00
Lukas Wagner
b6b18f65bc api-types: add config options for LDAP user sync
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-02-10 12:46:19 +01:00
Lukas Wagner
dd67737942 api-types: add LDAP configuration type
The properties are mainly based on the ones from PVE, except:
  * consistent use of kebab-cases
  * `mode` replaces deprecated `secure`

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-02-10 12:46:19 +01:00
Thomas Lamprecht
ecf59cbb74 bump version to 0.9.9-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-11 18:42:20 +01:00
Thomas Lamprecht
2f4254b414 cargo: update openidconnect to 2.4
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-01-11 18:41:06 +01:00
Fabian Grünbichler
ac1f71eddb update d/control
after debcargo update

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-01-05 15:07:21 +01:00
Fabian Grünbichler
093afb985f bump version to 0.9.8-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-01-05 12:26:47 +01:00
Fabian Grünbichler
ae6bf664dd update nix to 0.26
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-01-05 12:24:51 +01:00
Fabian Grünbichler
773400829a update d/control
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2023-01-05 12:24:46 +01:00
Wolfgang Bumiller
61c3ac5b1b fix non-camel-case enums
This should have never been started to begin with...
2023-01-05 11:13:46 +01:00
Dietmar Maurer
915f6ab5d0 derive Clone and PartialEq for some API types
This is useful for react-lik GUI toolkits which need to do VDOM diffs.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2022-12-15 17:34:13 +01:00
Fabian Grünbichler
10f56e9358 sort dependencies
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-12 09:08:56 +01:00
Fabian Grünbichler
89052a009d switch remaining member dependencies to workspace
these are only used by a single member at the moment, but we can move them to
the workspace to have a single location for version + base feature set
specification.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-12 09:08:36 +01:00
Fabian Grünbichler
32504b78db switch remaining member dependencies to workspace
these are only used by a single member at the moment, but we can move them to
the workspace to have a single location for version + base feature set
specification.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-12 09:08:36 +01:00
Fabian Grünbichler
8dbc2d7311 switch regular dependencies to workspace ones
where applicable.

notable changes:
- serde now uses 'derive' feature across the board
- serde removed from pbs-tools (not used)
- openssl bumped to 0.40 (and patched comment removed)
- removed invalid zstd comment

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-12 09:07:12 +01:00
Fabian Grünbichler
b659deb529 switch regular dependencies to workspace ones
where applicable.

notable changes:
- serde now uses 'derive' feature across the board
- serde removed from pbs-tools (not used)
- openssl bumped to 0.40 (and patched comment removed)
- removed invalid zstd comment

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-12 09:07:12 +01:00
Fabian Grünbichler
580399b26c switch proxmox dependencies to workspace
besides harmonizing versions, the only global change is that the tokio-io
feature of pxar is now implied since its default anyway, instead of being
spelled out.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-12 09:05:27 +01:00
Fabian Grünbichler
d75e305162 switch proxmox dependencies to workspace
besides harmonizing versions, the only global change is that the tokio-io
feature of pxar is now implied since its default anyway, instead of being
spelled out.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-12 09:05:27 +01:00
Fabian Grünbichler
6623ebdf2a workspace: inherit metadata
pbs-buildcfg is the only one that needs to inherit the version as well, since
it stores it in the compiled crate.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-12 09:05:27 +01:00
Fabian Grünbichler
219af02796 workspace: inherit metadata
pbs-buildcfg is the only one that needs to inherit the version as well, since
it stores it in the compiled crate.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-12 09:05:27 +01:00
Wolfgang Bumiller
378b763408 tree-wide: bump edition to 2021
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-12-06 13:31:01 +01:00
Wolfgang Bumiller
5acca01947 tree-wide: bump edition to 2021
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-12-06 13:31:01 +01:00
Fabian Grünbichler
1aa6f0ea22 clippy 1.65 fixes
and rustfmt

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-12-05 11:40:02 +01:00
Dominik Csapak
d70ae30a43 use derive 'Default' for ChunkOrder
instead of hardcoding the default deep inside the code. This makes it
much easier to see what is the actual default

the first instance of ChunkOrder::None was only for the test case, were
the ordering doe not matter

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-11-28 15:59:55 +01:00
Wolfgang Bumiller
854fb5c08f api-types: add MaintenanceType::Delete
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-11-28 14:45:39 +01:00
Wolfgang Bumiller
cf10742842 api-types: derive Display and FromStr for MaintenanceType
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-11-28 14:45:39 +01:00
Wolfgang Bumiller
5196e0bbee api-types: make Operation Eq
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-11-28 14:45:39 +01:00
Dominik Csapak
017a0652cd datastore: make 'filesystem' the default sync-level
rationale is that it makes the backup much safer than 'none', but does not
incur a big of a performance hit as 'file'.

here some benchmark:

data to be backed up:
~14GiB semi-random test images between 12kiB and 4GiB
that results in ~11GiB chunks (more than ram available on the target)

PBS setup:
virtualized (on an idle machine), PBS itself was also idle
8 cores (kvm64 on Intel 12700k) and 8 GiB memory

all virtual disks are on LVM with discard and iothread on
the HDD is a 4TB Seagate ST4000DM000 drive, and the NVME is a 2TB
Crucial CT2000P5PSSD8

i tested each disk with ext4/xfs/zfs (default created with the gui)
with 5 runs each, inbetween the caches are flushed and the filesystem synced
i removed the biggest and smallest result and from the remaining 3
results built the average (percentage is relative to the 'none' result)

result:

test         none     filesystem         file
hdd - ext4   125.67s  140.39s (+11.71%)  358.10s (+184.95%)
hdd - xfs    92.18s   102.64s (+11.35%)  351.58s (+281.41%)
hdd - zfs    94.82s   104.00s (+9.68%)   309.13s (+226.02%)
nvme - ext4  60.44s   60.26s (-0.30%)    60.47s (+0.05%)
nvme - xfs   60.11s   60.47s (+0.60%)    60.49s (+0.63%)
nvme - zfs   60.83s   60.85s (+0.03%)    60.80s (-0.05%)

So all in all, it does not seem to make a difference for nvme drives,
for hdds 'filesystem' increases backup time by ~10%, while
for 'file' it largely depends on the filesystem, but always
in the range of factor ~3 - ~4

Note that this does not take into account parallel actions, such as gc,
verify or other backups.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-11-28 09:49:55 +01:00
Wolfgang Bumiller
3193237afd rrd: add Entry::get() to access the data
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-11-24 13:53:49 +01:00
Wolfgang Bumiller
b5708459d3 api-types: derive Ord for BackupDir
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-11-15 10:16:54 +01:00
Dominik Csapak
7a98c5d50c datastore: improve sync level code a bit
fixups for DatastoreFSyncLevel:
* use derive for Default
* add some more derives (Clone, Copy)

chunk store:
* drop to_owned for chunk_dir_path

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-10-28 13:04:22 +02:00
Fabian Grünbichler
495da87f80 clippy fixes
the dropped .into() is guarded by the bumped build-dependency on
proxmox-sys 0.4.1, the missing Eq is a new clippy lint.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-10-24 12:10:19 +02:00
Dominik Csapak
e0fb53e41d datastore: implement sync-level tuning for datastores
currently, we don't (f)sync on chunk insertion (or at any point after
that), which can lead to broken chunks in case of e.g. an unexpected
powerloss. To fix that, offer a tuning option for datastores that
controls the level of syncs it does:

* None (default): same as current state, no (f)syncs done at any point
* Filesystem: at the end of a backup, the datastore issues
  a syncfs(2) to the filesystem of the datastore
* File: issues an fsync on each chunk as they get inserted
  (using our 'replace_file' helper) and a fsync on the directory handle

a small benchmark showed the following (times in mm:ss):
setup: virtual pbs, 4 cores, 8GiB memory, ext4 on spinner

size                none    filesystem  file
2GiB (fits in ram)   00:13   0:41        01:00
33GiB                05:21   05:31       13:45

so if the backup fits in memory, there is a large difference between all
of the modes (expected), but as soon as it exceeds the memory size,
the difference between not syncing and syncing the fs at the end becomes
much smaller.

i also tested on an nvme, but there the syncs basically made no difference

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-10-20 14:59:15 +02:00
Thomas Lamprecht
8a7a719aec bump version to 0.9.3-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-10-19 16:17:47 +02:00
Fabian Grünbichler
b522722d9c deb822: source index support
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
 [ T: commit Sources for test & fix white space errors ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-10-19 15:55:54 +02:00
Wolfgang Bumiller
3bbdcf23e0 bump sys dep to 0.4.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-10-19 14:26:00 +02:00
Fabian Grünbichler
ab6c2c7493 packages file: add section field
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-10-19 07:51:35 +02:00
Fabian Grünbichler
008a771f3e cargo fmt
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-10-12 10:42:50 +02:00
Fabian Grünbichler
661b8837f3 clippy fixes
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-10-12 10:42:24 +02:00
Thomas Lamprecht
d97a86c15b cargo: rrd: set license in subcrate too
in preparation of moving this out

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-10-11 15:46:30 +02:00
Fabian Grünbichler
4a13373c4b clippy fixes
and one additional API fn "allow many parameters" addition.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-10-11 09:36:12 +02:00
Dominik Csapak
dbd5906402 fix #4274: implement prune notifications
we converted the prune settings of datastores to prune-jobs, but did
not actually implement the notifications for them, even though
we had the notification options in the gui (they did not work).

implement the basic ok/error notification for prune jobs

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-10-07 08:35:56 +02:00
Dominik Csapak
49e553935f pbs-api-types: add FileRestoreFormat type
intended for passing the format to the file-restore client/daemon

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-10-05 18:40:49 +02:00
Thomas Lamprecht
5450421f03 d/control: update
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-09-16 14:19:07 +02:00
Thomas Lamprecht
6c0c48b97a bump version to 0.9.2-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-09-16 14:17:34 +02:00
Fabian Grünbichler
7e8eab45dd release: fix typo in 'Acquire-By-Hash'
to allow detection of repositories that support downloading indices via
their hash instead of their filename.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-09-16 14:09:29 +02:00
Fabian Grünbichler
f54534cc37 release: add 'architecture' helper
which returns if a file reference is architecture specific, and for
which architecture it is relevant.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-09-16 14:09:29 +02:00
Fabian Grünbichler
566981077c release: add Commands file reference type
used by command-not-found to lookup which package ships which command.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-09-16 14:09:29 +02:00
Fabian Grünbichler
0a9685ed4a bump version to 0.9.1-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-09-06 10:36:55 +02:00
Aaron Lauterer
64d7956788 add ceph quincy repositories
Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
2022-09-06 09:37:46 +02:00
Wolfgang Bumiller
ff3fcebda7 jws: allocate exact capacity
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-08-17 09:07:41 +02:00
Wolfgang Bumiller
e499b084c8 replace deprecated 'affine_coordinates_gfp' call
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-08-17 09:03:27 +02:00
Fabian Grünbichler
26e2bce4a7 release: add proper error message
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-08-05 11:28:58 +02:00
Wolfgang Bumiller
0e84522116 cleanup non-closure parse helpers
The TryFrom impl is already massive enough as it is.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-08-05 09:35:40 +02:00
Fabian Grünbichler
b2f851da87 release-file: improve invalid file-reference handling
if we encounter a file reference pointing to a component that is not
contained in the componenents list, we can just ignore it as unknown.
only treat parsing errors for references pointing to known components as
actual errors.

this currently triggers with (In)Release files for debian-updates and
debian-security, which reference (empty) files for a "non-free-firmware"
component that is not listed in the `Components` field of the release
file.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-08-05 09:10:07 +02:00
Wolfgang Bumiller
5d61126f58 bump proxmox-sys dep to 0.4
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-07-28 13:47:37 +02:00
Wolfgang Bumiller
ce6def2192 bump version to 0.9.7-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-07-28 13:42:52 +02:00
Wolfgang Bumiller
40aae0593a bump proxmox-sys dep to 0.4
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-07-28 13:41:59 +02:00
Wolfgang Bumiller
56b5c28930 rrd: Entry type and clippy fixes
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-07-27 14:54:44 +02:00
Wolfgang Bumiller
3d670c9e4e api-types: clippy fixes
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-07-27 13:29:32 +02:00
Fabian Grünbichler
74092debc5 more clippy fixes and annotations
the remaining ones are:
- type complexity
- fns with many arguments
- new() without default()
- false positives for redundant closures (where closure returns a static
  value)
- expected vs actual length check without match/cmp

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-07-26 14:05:25 +02:00
Thomas Lamprecht
1148b795f3 bump version to 0.9.0-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-07-21 13:32:53 +02:00
Fabian Grünbichler
72dc88fb71 clippy fixes
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-07-21 12:44:13 +02:00
Fabian Grünbichler
3e515dbc7d AptRepositoryFile: make path optional
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-07-21 12:41:36 +02:00
Thomas Lamprecht
03f14b74dd bump version to 0.8.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-07-21 12:10:36 +02:00
Thomas Lamprecht
5a3c084219 deb822 checksums: factor out hash equality check into macro
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-07-21 12:10:23 +02:00
Fabian Grünbichler
77fd3c8bce add test files for deb822 module
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-07-21 12:10:23 +02:00
Fabian Grünbichler
8cdd231153 add module for parsing Packages and Release files
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-07-21 12:10:23 +02:00
Fabian Grünbichler
28aafe6e3c file: add pre-parsed content variant
to allow usage with in-memory contents instead of actual files.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-07-21 12:10:23 +02:00
Wolfgang Bumiller
1647fc93ff api-types: make BackupType::iter an actual iterator
Otherwise we have to use BackupType::iter().iter() whenever
we're not using a `for _ in iter()` construct.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-07-14 11:14:26 +02:00
Stefan Sterz
214317df93 api-types: doc: add crate to Display trait in comments
when creating the documentation (e.g. `cargo doc --open`), it would
warn that `Display` is not in scope.

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-06-30 10:15:22 +02:00
Wolfgang Bumiller
f3fd79be13 bump proxmox-sys dep to 0.3.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-06-29 09:45:11 +02:00
Wolfgang Bumiller
f81e9b259a bump proxmox-router dep to 1.2.4
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-06-21 10:43:19 +02:00
Dominik Csapak
57fa204064 pbs-api-types: add metrics api types
InfluxDbUdp and InfluxDbHttp for now

introduces schemas for host:port and https urls

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-06-13 09:57:16 +02:00
Thomas Lamprecht
d845736270 tree wide: typo fixes through codespell
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-06-07 14:08:09 +02:00
Thomas Lamprecht
2ec6f86f63 tree wide: typo fixes through codespell
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-06-07 14:08:09 +02:00
Thomas Lamprecht
551cd92fa8 api types: clippy lints
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-06-02 15:57:07 +02:00
Fabian Grünbichler
8e042eb130 update to nix 0.24 / rustyline 9 / proxmox-sys 0.3
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-06-02 14:33:33 +02:00
Fabian Grünbichler
0ec392e425 bump version to 0.9.6
for nix 0.24 rebuild

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-06-02 14:12:23 +02:00
Thomas Lamprecht
1d5b46c21e api types: prune keep options: also check weekly in keeps_something
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-06-01 14:30:24 +02:00
Wolfgang Bumiller
288074a018 manager: hidden command to move datastore prune opts into jobs
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-05-30 13:58:43 +02:00
Wolfgang Bumiller
705ee9c93f add prune jobs api
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-05-30 13:58:43 +02:00
Wolfgang Bumiller
27718f2a72 api-types: add PruneJobConfig
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-05-30 13:58:43 +02:00
Thomas Lamprecht
79cf434a79 datastore status: impl empty-status constructor for item type
we can now use it for the error case and will further use it for the
can access namespace but not datastore case in a future patch

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-26 13:34:00 +02:00
Fabian Grünbichler
f6a37f40f6 tree-wide: remove DatastoreWithNamespace
instead move the acl_path helper to BackupNamespace, and introduce a new
helper for printing a store+ns when logging/generating error messages.

Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-05-26 11:42:10 +02:00
Fabian Grünbichler
bbfbd9297f api: add new priv to priv name helper
for usage in permission check error messages, to allow easily indicating
which privs are missing.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-05-25 17:18:56 +02:00
Fabian Grünbichler
d09aadee84 verify_job: fix priv check
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-05-25 17:18:56 +02:00
Fabian Grünbichler
bb0fdee898 sync job: don't require privs on datastore
syncing to a namespace only requires privileges on the namespace (and
potentially its children during execution).

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-05-25 17:18:56 +02:00
Fabian Grünbichler
5925004a7d sync job: fix worker ID parsing
the namespace is optional, but should be captured to allow ACL checks
for unprivileged non-job-owners.

also add FIXME for other job types and workers that (might) need
updating.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-05-25 17:18:56 +02:00
Thomas Lamprecht
9086f422af api types: verify job: fix doc comment typo
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-18 15:45:55 +02:00
Thomas Lamprecht
680d391490 api types: verify job: allow outdated-afer == 0 for backward compat
We can have those in existing verify jobs configs, and that'd break
stuff. So, even while the "bad" commit got released only recently
with `2.1.6-1` (14 April 2022), we still need to cope with those that
used it, and using some serde parser magic to transform on read only
is hard here due to section config (json-value and verify currently
happen before we can do anything about it)

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-18 15:39:59 +02:00
Thomas Lamprecht
d1fc9d87fa Revert "verify: allow '0' days for reverification"
This reverts commit c72fe7d77c560ad7699dd43d80dbfb98bded4d51.

We could already cause the behavior by simply setting ignore-verified
to false, aas that flag is basically an on/off switch for even
considering outdated-after or not.

So avoid the extra logic and just make the gui use the previously
existing way.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-18 12:53:08 +02:00
Fabian Grünbichler
6614f8840f build: bump required log version
else logging using "{var}" in format strings doesn't work properly.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-05-16 15:02:07 +02:00
Thomas Lamprecht
988e614129 api types: namespace: fix typo in error message
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-16 09:50:17 +02:00
Thomas Lamprecht
b724d44153 api types: BackupNamespace: fix depth check on pushing subdir to ns
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-16 09:37:38 +02:00
Thomas Lamprecht
1d7f4ad0aa api types: BackupNamespace: remove unused, commented out code
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-16 09:37:38 +02:00
Dominik Csapak
e3ad1c7f15 api: tape/backup: fix namespace/max-depth parameters
by adding the 'default' serde hint and renaming 'recursion_depth' to
'max_depth' (to be in line with sync job config)

also add the logic to actually add/update the tape backup job config

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-05-13 14:09:53 +02:00
Dominik Csapak
f6950c3ca9 api: tape/restore: add namespace mapping
by adding a new parameter 'namespaces', which contains a mapping
for a namespace like this:

store=datastore,source=foo,target=bar,max-depth=2

if source or target are omitted the root namespace is used for its value

this mapping can be given several times (on the cli) or as an array (via
api) to have mappings for multiple datastores

if a specific snapshot list is given simultaneously, the given snapshots
will be restored according to this mapping, or to the source namespace
if no mapping was found.

to do this, we reutilize the restore_list_worker, but change it so that
it does not hold a lock for the duration of the restore, but fails
if the snapshot does exist at the end. also the snapshot will now
be temporarily restored into the target datastore into the
'.tmp/<media-set-uuid>' folder.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-05-13 14:08:32 +02:00
Dominik Csapak
cb71f94110 tape: add namespaces mapping type
and the relevant parser for it

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-05-13 14:08:32 +02:00
Dominik Csapak
6cbbb57ca7 tape: add namespaces/recursion depth to tape backup jobs
and manual api via TapeBackupJobSetup

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-05-13 14:08:31 +02:00
Dominik Csapak
559017748c pbs-api-types: add parse and print ns_and_snapshot
these are helpers for the few cases where we want to print and parse
from a format that has the namespace and snapshot combined, like for
the on-tape catalog and snapshot archive.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-05-13 13:52:50 +02:00
Thomas Lamprecht
26604f31a5 datastore: inline some format variables
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-13 12:42:41 +02:00
Thomas Lamprecht
f3b18f7233 api types: set NS_MAX_DEPTH schema default to MAX_NAMESPACE_DEPTH
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-13 12:32:25 +02:00
Fabian Grünbichler
ed3dd6644e api: split max-depth schema/types
into the regular one (with default == MAX) and the one used for
pull/sync, where the default is 'None' which actually means the remote
end reduces the scope of sync automatically (or, if needed,
backwards-compat mode without any remote namespaces at all).

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-05-13 12:07:22 +02:00
Fabian Grünbichler
79dae5df9d namespaces: move max-depth check to api type
and use it when creating a sync job, and simplify the check on updating
(only check the final, resulting config instead of each intermediate
version).

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-05-13 12:07:22 +02:00
Fabian Grünbichler
37c6fdafd1 pull/sync: treat unset max-depth as full recursion
to be consistent with tape backup and verification jobs.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-05-12 17:00:38 +02:00
Thomas Lamprecht
92984a159d cargo fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 11:54:21 +02:00
Thomas Lamprecht
51e1d3c1fd datastore: add new Lookup for operations tracking
We sometimes need to do some in-memory only stuff, e.g., to check if
GC is already running for a datastore, which is a try_lock on a mutex
that is in-memory.

Actually the whole thing would be nicer if we could guarantee to hold
the correct contract statically, e.g., like
https://docs.rust-embedded.org/book/static-guarantees/design-contracts.html

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 11:36:56 +02:00
Fabian Grünbichler
718504f164 sync/pull: make namespace aware
Allow pulling all groups from a certain source namespace, and
possibly sub namespaces until max-depth, into a target namespace.

If any sub-namespaces get pulled, they will be mapped relatively from
the source parent namespace to the target parent namespace.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Wolfgang Bumiller
8f21c992a7 api-types: rework BackupNamespace::map_prefix
to use slice::strip_prefix() from std

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Thomas Lamprecht
e941396678 verify job: support max-depth config
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Thomas Lamprecht
c358973e1b api: verify: support namespaces
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Fabian Grünbichler
77d4a752b6 api: add DatastoreWithNamespace helper struct
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Wolfgang Bumiller
21667bed72 api-types: allow empty namespace
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Fabian Grünbichler
586c9f468d api: namespace: return popped component
helpful for places where namespaces need to be (re)created

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Wolfgang Bumiller
afcf4896ba split the namespace out of BackupGroup/Dir api types
We decided to go this route because it'll most likely be
safer in the API as we need to explicitly add namespaces
support to the various API endpoints this way.

For example, 'pull' should have 2 namespaces: local and
remote, and the GroupFilter (which would otherwise contain
exactly *one* namespace parameter) needs to be applied for
both sides (to decide what to pull from the remote, and what
to *remove* locally as cleanup).

The *datastore* types still contain the namespace and have a
`.backup_ns()` getter.

Note that the datastore's `Display` implementations are no
longer safe to use as a deserializable string.

Additionally, some datastore based methods now have been
exposed via the BackupGroup/BackupDir types to avoid a
"round trip" in code.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Thomas Lamprecht
1e0c87d48f api: namespace management endpoints
allow to list any namespace with privileges on it and allow to create
and delete namespaces if the user has modify permissions on the parent
namespace.

Creation is only allowed if the parent NS already exists.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Fabian Grünbichler
47d14e1aed api: add NS_MAX_DEPTH_SCHEMA
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
 [ T: renamed from NAMESPACE_RECURSION_DEPTH_SCHEMA & moved to from
   jobs to datastore ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Fabian Grünbichler
cf93fbb893 api: add prefix-mapping helper to BackupNamespace
given a namespace, a source prefix and a target prefix this helper
strips the source prefix and replaces it with the target one (erroring
out if the prefix doesn't match).

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Fabian Grünbichler
1f35bbc4dc api: derive UpdaterType for BackupNamespace
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Fabian Grünbichler
1b3a49c595 BackupNamespace: fix deserialize of root NS
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Wolfgang Bumiller
e2cf8920ea api-types: fixup backup-ns being optional
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Thomas Lamprecht
2c593cd38c api types: BackupNamespace add pop & parent helpers
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Wolfgang Bumiller
731d4783ce api-types: more regex fixups
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Wolfgang Bumiller
0deda0cacf api-types: add missing slash in optional ns path regex
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Thomas Lamprecht
0cd80471d0 api types: namespace: add from_parent_ns helper
will be used in the (recursive) namespace iterator

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Thomas Lamprecht
1682d9ae0d api types: namespace: include problematic component in error
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Wolfgang Bumiller
0af9b69146 api-types: add namespace to BackupGroup
Make it easier by adding an helper accepting either group or
directory

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Thomas Lamprecht
686c4cd250 ns: max depth: set constant to upper inclusive boundary
makes usage a bit simpler, e.g., the api maximum can use that 1:1
then.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Thomas Lamprecht
c38e22c93c api-types: add BackupNamespace type
The idea is to have namespaces in a datastore to allow grouping and
namespacing backups from different (but similar trusted) sources,
e.g., different PVE clusters, geo sites, use-cases or company
service-branches, without separating the underlying
deduplication domain and thus blowing up data and (GC/verify)
resource usage.

To avoid namespace ID clashes with anything existing or future
usecases use a intermediate `ns` level on *each* depth.

The current implementation treats that as internal and thus hides
that fact from the API, iow., the namespace path the users passes
along or gets returned won't include the `ns` level, they do not
matter there at all.

The max-depth of 8 is chosen with the following in mind:
- assume that end-users already are in a deeper level of a hierarchy,
  most often they'll start at level one or two, as the higher ones
  are used by the seller/admin to namespace different users/groups,
  so lower than four would be very limiting for a lot of target use
  cases

- all the more, a PBS could be used as huge second level archive in a
  big company, so one could imagine a namespace structure like:
  /<state>/<intra-state-location>/<datacenter>/<company-branch>/<workload-type>/<service-type>/
  e.g.: /us/east-coast/dc12345/financial/report-storage/cassandra/
  that's six levels that one can imagine for a reasonable use-case,
  leave some room for the ones harder to imagine ;-)

- on the other hand, we do not want to allow unlimited levels as we
  have request parameter limits and deep nesting can create other
  issues as well (e.g., stack exhaustion), so doubling the minimum
  level of 4 (1st point) we got room to breath even for the
  more odd (or huge) use cases (2nd point)

- a per-level length of 32 (-1 due to separator) is enough to use
  telling names, making lives of users and admin simpler, but not
  blowing up parameter total length with the max depth of 8

- 8 * 32 = 256 which is nice buffer size

Much thanks for Wolfgang for all the great work on the type
implementation and assisting greatly with the design.

Co-authored-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Co-authored-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Thomas Lamprecht
f64272b948 api types: BackupType: add iter for enum
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-12 09:33:50 +02:00
Dominik Csapak
c2049bce7f api: status: return gc-status again
Returning the GC status was dropped by mistake in commit fdcb2694
("datastore status: factor out api type DataStoreStatusListItem")

As this is considered a breaking change which we also felt, due to
the gc-status being used in the web interface for the datastore
overview list (not the dashboard), re add it.

Fixes: fdcb2694
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
 [ T: add reference to breaking commit, reword message ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-05-02 10:11:01 +02:00
Hannes Laimer
a4f552f738 api2: DataStoreListItem add maintenance info
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2022-04-27 19:21:19 +02:00
Thomas Lamprecht
a63b50f79a api types: datastore status: reword doc comment of estimated_full_date
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-25 11:48:25 +02:00
Wolfgang Bumiller
f3d07e6f15 api-types: DataStoreConfig::new for testing
so our examples can more easily access a datastore without
going over a configuration & cache

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-04-20 15:31:04 +02:00
Wolfgang Bumiller
cc65272130 api-types: use BackupType for GroupFilter::BackupType
instead of a string

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-04-20 11:49:01 +02:00
Wolfgang Bumiller
c4c67bdcfb api-types: datastore type improvements
let BackupGroup implement Hash

let BackupGroup and BackupDir be AsRef<BackupGroup>
let BackupDir be AsRef<BackupDir>

the pbs-datastore types will implement these AsRefs as well

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-04-20 10:03:39 +02:00
Dietmar Maurer
b12dc1e501 AuthId: derive Ord and PartialOrd
So the we can sort...

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2022-04-20 09:58:52 +02:00
Dietmar Maurer
027033c17a RemoteWithoutPassword: new API type
To make it explicit that we do not return the password.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2022-04-20 09:42:46 +02:00
Wolfgang Bumiller
32ea4b56a1 api-types: introduce BackupType enum and Group/Dir api types
The type is a real enum.

All are API types and implement Display and FromStr. The
ordering is the same as it is in pbs-datastore.

Also, they are now flattened into a few structs instead of
being copied manually.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-15 13:12:46 +02:00
Thomas Lamprecht
b635dc3ee1 rust fmt for pbs src
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-14 14:03:46 +02:00
Wolfgang Bumiller
71a7566cd3 bump proxmox-schema dependency to 1.3.1 for streaming attribute
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-04-13 08:20:27 +02:00
Wolfgang Bumiller
56dd83740d bump proxmox-router dependency to 1.2
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-04-13 08:17:08 +02:00
Hannes Laimer
50fa7bad49 datastore: add check for maintenance in lookup
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2022-04-12 15:29:14 +02:00
Hannes Laimer
35786fe37e api-types: add maintenance type
+ bump proxmox-schema dep to 1.2.1 (for quoted property string)

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2022-04-12 15:29:14 +02:00
Thomas Lamprecht
908908191e api types: rust fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-10 18:00:18 +02:00
Thomas Lamprecht
d5b9d1f482 rrd: rust fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-06 16:56:33 +02:00
Thomas Lamprecht
0afe853119 bump version to 0.9.5-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-04-01 15:57:00 +02:00
Mira Limbeck
4aff0d7c95 fix Open ID with Azure as provider
Azure doesn't accept `Transfer-Encoding: chunked` on their token endpoint,
but with the switch to ureq we always send requests with this set.

Fix by switching to `Content-Length` in the header instead. ureq only
sets `Transfer-Encoding: chunked` when the body length is not known
beforehand, which is the case when using `send`. See
https://docs.rs/ureq/2.4.0/ureq/index.html#content-length-and-transfer-encoding

See https://forum.proxmox.com/threads/openid-401-with-azure-ad.105892/
for the issue.

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
2022-04-01 15:53:44 +02:00
Stefan Sterz
f8c7bc4fb4 fix #3067: api: add support for multi-line comments in node.cfg
add support for multi-line comments to node.cfg and the api, similar to
how pve handles multi-line comments

Signed-off-by: Stefan Sterz <s.sterz@proxmox.com>
Acked-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-03-23 10:43:43 +01:00
Thomas Lamprecht
1f47f7d3eb bump version to 0.9.4-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-03-22 11:37:59 +01:00
Thomas Lamprecht
23e6c398a2 http client: rust format and whitespace cleanup
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-03-22 10:57:21 +01:00
Mira Limbeck
e0535e56ad add http proxy support
ureq has support for a HTTP proxy, but no support for HTTPS proxy yet.
ureq doesn't query `all_proxy` and `ALL_PROXY` environment variables by
itself, the way curl does. So set the proxy in code if any of the above
environment variables are set.

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
2022-03-22 10:42:57 +01:00
Dominik Csapak
24a10d107a api: datastore_status: restore api/gui compatibility
the latest changes to this api call changed/removed some things that
were actually necessary for the gui. Readd those and document them this
time.

The change from u64 to i64 limits us to 8EiB of Datastore sizes (instead if
16EiB) but if we reach that, we must adapt most other parts to use 128bit
sizes anyway

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-03-22 10:31:25 +01:00
Dietmar Maurer
fdcb2694b4 datastore status: factor out api type DataStoreStatusListItem
And use the rust type instead of json::Value.
2022-03-20 09:38:50 +01:00
Fabian Grünbichler
1a059f3ebe regex: bump to 1.5.5
to ensure CVE fix for DoS on untrusted RE is picked up where it matters

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-03-09 09:55:36 +01:00
Dietmar Maurer
35da5ff9fe Username schema: set min_length to 1
Just to get a better error message (the regex already requires min_length 1)

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2022-03-07 13:47:06 +01:00
Dietmar Maurer
346e422237 cleanup: move BasicRealmInfo to pbs-api-types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2022-03-07 08:06:55 +01:00
Wolfgang Bumiller
42cafaba32 bump proxmox-schema dep to 1.3
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-03-04 09:50:21 +01:00
Dominik Csapak
842a39af35 datastore: add tuning option for chunk order
currently, we sort chunks by inode when verifying or backing up to tape.
we get the inode# by stat'ing each chunk, which may be more expensive
than the gains of reading the chunks in order

Since that is highly dependent on the underlying storage of the datastore,
introduce a tuning option  so that the admin can tune that behaviour
for each datastore.

The default stays the same (sorting by inode)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-02-23 09:06:03 +01:00
Thomas Lamprecht
43b602248d rrd: extract data: avoid always calculating start-time fallback
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-15 07:59:55 +01:00
Thomas Lamprecht
5740f36ef8 rrd: avoid intermediate index, directly loop over data
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-15 07:59:55 +01:00
Thomas Lamprecht
6149c171ca rrd cache: code style, avoid useless intermediate mutable
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-15 07:59:12 +01:00
Fabian Grünbichler
90476cf118 misc clippy fixes
the trivial ones ;)

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-02-08 14:57:16 +01:00
Fabian Grünbichler
d80d195c26 misc clippy fixes
the trivial ones ;)

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-02-08 14:57:16 +01:00
Fabian Ebner
037ce3a0aa check suites: add special check for Debian security repository
since the suffix was changed with Debian Bullseye.

Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-03 08:57:04 +01:00
Fabian Ebner
4bdd6a5148 clippy fixes
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-03 08:55:42 +01:00
Fabian Ebner
9caae7d49e upgrade to edition 2021
std::convert::{TryFrom, TryInto} are now part of the prelude.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-03 08:55:08 +01:00
Wolfgang Bumiller
65f05daf7e doc fixup
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-02 14:13:19 +01:00
Thomas Lamprecht
8b15ac202e d/control: update
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-01 13:18:22 +01:00
Thomas Lamprecht
1ab70b8c21 bump version to 0.9.3-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-01 13:03:57 +01:00
Wolfgang Bumiller
4cab29c57a make upload: drop buster
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-01 11:10:00 +01:00
Wolfgang Bumiller
3531729921 include error messages in error display
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-01 10:39:23 +01:00
Wolfgang Bumiller
bd2bf045cc use native-tls for ureq
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-01 10:38:57 +01:00
Wolfgang Bumiller
abc0bdd09d bump version to 0.4.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-01 10:26:20 +01:00
Wolfgang Bumiller
fe6294cd3a bump edition to 2021
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-01 10:18:32 +01:00
Wolfgang Bumiller
7622380dd7 switch from curl to ureq
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2022-02-01 10:18:23 +01:00
Dietmar Maurer
ce9a84c54f enable gzip feature for ureq
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2022-02-01 09:13:50 +01:00
Thomas Lamprecht
cf18776173 cargo: enable "accept-rfc3339-timestamps" feature for OIDC
It doesn't pull in any new dependency and we require it to be able to
work with the auth0 provider.
https://github.com/ramosbugs/openidconnect-rs/pull/55#issuecomment-1026567725

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2022-02-01 09:03:58 +01:00
Dietmar Maurer
f3ddce5297 use ureq (with native-tls) instead of curl
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2022-02-01 08:16:39 +01:00
Dominik Csapak
c72fe7d77c verify: allow '0' days for reverification
and let it mean that we will always reverify

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2022-01-27 15:31:55 +01:00
Fabian Grünbichler
4e854d32f0 ciphers: simplify API schema
these need to be checked (and are) via libssl anyway before persisting,
and newer versions might contain new ciphers/variants/... (and things
like @STRENGTH or @SECLEVEL=n were missing).

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-01-14 11:02:07 +01:00
Hannes Laimer
a8d5bc32ca config: add tls ciphers to NodeConfig
for TLS 1.3 and for TLS <= 1.2

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
2022-01-14 11:02:07 +01:00
Fabian Grünbichler
d069c91e73 api-types: relax NODENAME_SCHEMA
there isn't really a concept of 'nodes' in PBS (yet) anyway - and if
there ever is, it needs to be handled by the rest-server / specific API
endpoints (like in PVE), and not by the schema.

this allows dropping proxmox-sys from pbs-api-types (and thus nix and
some other transitive deps as well).

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-01-12 15:42:58 +01:00
Fabian Grünbichler
588001cf8d api-types: move RsaPubKeyInfo to pbs-client
it's the only thing requiring openssl in pbs-api-types, and it's only
used by the client to pretty-print the 'master' key, which is
client-specific.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2022-01-12 15:42:58 +01:00
Fabian Grünbichler
c8e73a225a rrd: drop redundant field names
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-12-30 15:02:07 +01:00
Fabian Grünbichler
fb3fe5561e use schema verify methods
the old, deprecated ones only forward to these anyway.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-12-30 15:02:07 +01:00
Fabian Grünbichler
872b5f41cd tree-wide: drop redundant clones
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-12-30 15:02:07 +01:00
Fabian Grünbichler
039d3374d7 tree-wide: fix needless borrows
found and fixed via clippy

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-12-30 13:55:33 +01:00
Fabian Grünbichler
5b19368000 tree-wide: fix needless borrows
found and fixed via clippy

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-12-30 13:55:33 +01:00
Wolfgang Bumiller
da2e372b19 cleanup schema function calls
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-12-16 11:25:02 +01:00
Wolfgang Bumiller
cb32acc703 cleanup schema function calls
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-12-16 11:25:02 +01:00
Wolfgang Bumiller
e207a84d93 bump proxmox-schema to 1.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-12-16 11:25:02 +01:00
Thomas Lamprecht
4ca47bc325 fix #3794: api types: set backup time lower limit to 1
Some users want to import historical backups but they run into the
original lower backuo-time limit one can pass. That original limit
was derived from the initial PBS development start in 2019, it was
assumed that no older backup can exist with PBS before it existing,
but imports of older backups is a legitimate thing.

I pondered using 683071200 (1991-08-25), aka the first time Linux was
publicly announced by Linus Torvalds as new limit but at the end I
did not wanted to risk that and backup software is IMO to serious for
such easter eggs, so I went for 1, to differ between the bogus 0 some
tools fallback too if there's something off with time.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-12-15 14:13:49 +01:00
Dietmar Maurer
8995e899c8 pbs-api-types: remove proxmox-sys dependency for target wasm
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-12-01 09:49:52 +01:00
Dietmar Maurer
4dac564215 pbs-api-types: remove openssl dependency for target wasm
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-12-01 09:28:47 +01:00
Dietmar Maurer
5d570c4b59 pbs-api-types: remove libc dependency
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-12-01 09:10:25 +01:00
Dietmar Maurer
a717b13733 pbs-api-types: removbe usused nix dependency
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-12-01 09:08:25 +01:00
Dominik Csapak
7c04f07525 remove use of deprecated functions from proxmox-time
Depend on proxmox-time 1.1.1

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-12-01 07:23:18 +01:00
Dietmar Maurer
7df207b52c fix typo in comment
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-25 13:15:35 +01:00
Dietmar Maurer
807e474398 move pbs-tools/src/percent_encoding.rs to pbs-api-types/src/percent_encoding.rs
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-25 11:48:52 +01:00
Dietmar Maurer
a092ef9c32 update to proxmox-sys 0.2 crate
- imported pbs-api-types/src/common_regex.rs from old proxmox crate
- use hex crate to generate/parse hex digest
- remove all reference to proxmox crate (use proxmox-sys and
  proxmox-serde instead)

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-24 10:32:27 +01:00
Dietmar Maurer
14f389d563 update to proxmox-sys 0.2 crate
- imported pbs-api-types/src/common_regex.rs from old proxmox crate
- use hex crate to generate/parse hex digest
- remove all reference to proxmox crate (use proxmox-sys and
  proxmox-serde instead)

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-24 10:32:27 +01:00
Dietmar Maurer
bdcecd3214 update debian/control 2021-11-24 10:16:37 +01:00
Dietmar Maurer
a92d77bf1f bump version to 0.9.2-1, depend on proxmox-sys 0.2
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-24 10:14:54 +01:00
Dietmar Maurer
f6799e08af Fingerprint: add new signature method
commit 8c1ec5c8021b11d4ef657a55c67b060045e6ebdc introcuded a bug by
using fp.to_string(). Replace this with fp.signature() which correctly
returns the full fingerprint instead of the short version.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-22 08:29:43 +01:00
Dominik Csapak
27e98af425 set default for 'protected' flag
otherwise we cannot properly parse the api return value from older
versions, since that field does not exist there.

fixes sync from older versions without the protected feature

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-11-22 08:28:37 +01:00
Dietmar Maurer
2984877c5e sync-job: add rate limit
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-22 07:49:41 +01:00
Dietmar Maurer
9815d90136 pbs-api-types: split out type RateLimitConfig 2021-11-22 07:49:41 +01:00
Dietmar Maurer
f2a761f9b1 pbs-api-types: fix HumanByte::auto_scale 2021-11-21 09:13:02 +01:00
Dietmar Maurer
a70a8ef32e use HumanByte for traffic-control config
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-20 19:35:24 +01:00
Thomas Lamprecht
8dbc29bf7a human byte: make proper proxmox API type
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-20 19:35:24 +01:00
Thomas Lamprecht
8ced698304 human byte: add from string parser
Adapted from Dietmar's v3 on pbs-devel but some changes:
- reworked with a strip_suffix fn that does matching, way shorter and
  even easier to read IMO
- make b/B byte symbol fully optional, not just for base-10
- also trim trailing whitespace for SizeUnit::Byte
- simplify the FromStr impl
- adapt parser unit tests such that we actually see the failed test's
  definition line, simplifies debugging a bit

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-20 19:35:24 +01:00
Thomas Lamprecht
52bfb8e819 human byte: add proper unit type and support base-10
The new SizeUnit type takes over the auto scaling logic and could be
used on its own too.

Switch the internal type of HumanByte from u64 to f64, this results
in a slight reduce of usable sizes we can represent (there's no
unsigned float type after all) but we support pebibyte now with quite
the precision and ebibytes should be also work out ok, and that
really should us have covered for a while..

Partially adapted by Dietmar's version, but split up and change so:
* there's no None type, for a SizeUnit that does not makes much sense
* print the unit for byte too, better consistency and one can still
  use as_u64() or as_f64() if they do not want/need the unit rendered
* left the "From usize/u64" impls intact, just convenient to have and
  avoids all over the tree changes to adapt to loosing that
* move auto-scaling into SizeUnit, good fit there and I could see
  some re-use potential for non-human-byte users in the future
* impl Display for SizeUnit instead of the separate unit_str method,
  better usability as it can be used directly in format (with zero
  alloc/copy) and saw no real reason of not having that this way
* switch the place where we auto-scale in HumanByte's to the new_X
  helpers which allows for slightly reduced code usage and simplify
  implementation where possible
* use rounding for the precision limit algorithm. This is a stupid
  problem as in practices there are cases for requiring every variant:
  - flooring would be good for limits, better less than to much
  - ceiling would be good for file sizes, to less can mean ENOSPACE
    and user getting angry if their working value is messed with
  - rounding can be good for rendering benchmark, closer to reality
    and no real impact
  So going always for rounding is really not the best solution..

Some of those changes where naturally opinionated, if there's a good
practical reason we can switch back (or to something completely
different).

The single thing I kept and am not _that_ happy with is being able to
have fractional bytes (1.1 B or even 0.01 B), which just does not
makes much sense as most of those values cannot exist at all in
reality - I say most as multiple of 1/8 Byte can exists, those are
bits.o

Note, the precission also changed from fixed 2 to max 3 (trailing
zeros stripped), while that can be nice we should see if we get
a better precision limiting algorithm, e.g., directly in the printer.
Rust sadly does not supports "limit to precision of 3 but avoid
trailing zeros" so we'd need to adapt their Grisu based algorithm our
own - way to much complexity for this though..

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-20 19:35:24 +01:00
Thomas Lamprecht
adcf38948e move HumanByte to pbs-abi-types crate
Originally-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-20 19:35:24 +01:00
Dietmar Maurer
0fdc15a3f8 use proxmox::tools::fd::fd_change_cloexec from proxmox 0.15.3
Depend on proxmox 0.15.3

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-18 13:43:41 +01:00
Dietmar Maurer
e33f41c72c use proxmox::tools::fd::fd_change_cloexec from proxmox 0.15.3
Depend on proxmox 0.15.3

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-18 13:43:41 +01:00
Fabian Grünbichler
d6e7e2599f bump version to 0.9.1-1
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-11-18 12:56:21 +01:00
Fabian Grünbichler
fb547f5935 bump version to 0.3.2
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-11-18 12:56:16 +01:00
Fabian Grünbichler
a3592355a1 bump openidconnect dep to 2.1
for updated rand/base64 support

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-11-18 12:53:37 +01:00
Fabian Grünbichler
cbc90fe9fc bump base64 dep to 0.13
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-11-18 12:48:40 +01:00
Dietmar Maurer
799df8f345 openid: allow to configure scopes, prompt, ACRs and arbitrary username-claim values
- no longer set prompt to 'login' (makes auto-login possible)
- new prompt configuration
- allow arbitrary username-claim values

Depend on proxmox-openid 0.9.0.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-18 11:20:55 +01:00
Thomas Lamprecht
ac8709e97b group filter: rename CLI/API/Config "groups" option to "group-filter"
we even use that for basically all the related schema names, "groups"
allone is just rather not so telling, i.e., "groups" what?

While due to the additive nature of `group-filter` is not the best
possible name for passing multiple arguments on the CLI (the web-ui
can present this more UX-friendly anyway) due to possible confusion
about if the filter act like AND vs OR it can be documented and even
if a user is confused they still are safe on more being synced than
less. Also, the original param name wasn't really _that_ better in
that regards

Dietmar also suggested to use singular for the CLI option, while
there can be more they're passed over repeating the option, each with
a single filter.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-18 10:36:57 +01:00
Dominik Csapak
f2b4af0322 tape backup jobs: add group filters to config/api
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-18 10:36:57 +01:00
Fabian Grünbichler
c055cdb910 fix #sync.cfg/pull: don't remove by default
and convert existing (manually created/edited) jobs to the previous
default value of 'true'. the GUI has always set this value and defaults
to 'false'.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-18 10:36:57 +01:00
Fabian Grünbichler
aea616987b sync: add group filtering
like for manual pulls, but persisted in the sync job config and visible
in the relevant GUI parts.

GUI is read-only for now (and defaults to no filtering on creation), as
this is a rather advanced feature that requires a complex GUI to be
user-friendly (regex-freeform, type-combobox, remote group scanning +
selector with additional freeform input).

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-18 10:36:57 +01:00
Fabian Grünbichler
506c106a50 api: add GroupFilter(List) type
at the API level, this is a simple (wrapped) Vec of Strings with a
verifier function. all users should use the provided helper to get the
actual GroupFilter enum values, which can't be directly used in the API
schema because of restrictions of the api macro.

validation of the schema + parsing into the proper type uses the same fn
intentionally to avoid running out of sync, even if it means compiling
the REs twice.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-18 10:36:57 +01:00
Fabian Grünbichler
632ab24359 api-types: add schema for backup group
the regex was already there, and we need a simple type/schema for
passing in multiple groups as Vec/Array via the API.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-11-18 10:36:57 +01:00
Wolfgang Bumiller
06f5106145 bump version to 0.3.1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-11-18 09:50:03 +01:00
Wolfgang Bumiller
467d567545 clippy and formatting fixups
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-11-18 09:46:01 +01:00
Stoiko Ivanov
ba2da9d125 client: add support for proxies
by storing the proxy url as string in the struct and setting it on
each invocation of `execute`, since execute calls reset on the
curl::easy::Easy object.

Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-11-18 09:40:31 +01:00
Dietmar Maurer
ea637eec28 bump version to 0.9.0-1 2021-11-18 09:38:47 +01:00
Dietmar Maurer
6454a54704 allow to configure acr values 2021-11-18 09:33:59 +01:00
Dietmar Maurer
897c5c7569 allow to configure prompt behaviour
And do not set it by default.
2021-11-18 09:33:11 +01:00
Dietmar Maurer
f53d242cb0 new helper verify_authorization_code_simple()
Simply return data as serde_json::Value.
2021-11-18 09:32:20 +01:00
Dietmar Maurer
cfecbee92c also return data from UserInfo endpoint 2021-11-18 09:31:44 +01:00
Dietmar Maurer
5937e44062 allow to configure used scopes 2021-11-18 09:23:24 +01:00
Dietmar Maurer
4d7cb99f4a proxmox-systemd: remove crate, use new proxmox-time 1.1.0 instead
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-17 13:07:51 +01:00
Dietmar Maurer
8c1ec5c802 move fingerprint helpers from pbs-tools to pbs-api-types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-17 07:07:40 +01:00
Dietmar Maurer
2b62255aca Add traffic control configuration config with API
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-11-10 10:15:40 +01:00
Dominik Csapak
8e344d3d67 rrd: use saturating_sub to avoid underflow
Without this, the tests fail in debug mode.
Also having start (u64) underflow to a value greater than end does
not really make sense

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-28 12:54:54 +02:00
Dominik Csapak
ee72e63fb9 add protected info of snapshots to api and task logs
adds the info that a snapshot is protected to:
* snapshot list
* manual pruning (also dry-run)
* prune jobs

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-28 11:30:51 +02:00
Wolfgang Bumiller
cb89d97df1 bump version to 0.3.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-21 13:14:10 +02:00
Dominik Csapak
4c5d899c3a directory: make meta object optional
some custom ACME endpoints do not have a TOS, and thus do not return
a meta property at all

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-21 13:09:42 +02:00
Dietmar Maurer
75ca726c29 use new fsync parameter to replace_file and atomic_open_or_create
Depend on proxmox 0.15.0 and proxmox-openid 0.8.1

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-10-21 07:28:32 +02:00
Dietmar Maurer
d154224307 use new fsync parameter to replace_file and atomic_open_or_create
Depend on proxmox 0.15.0 and proxmox-openid 0.8.1

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-10-21 07:28:32 +02:00
Dietmar Maurer
8471451a7b bump version to 0.8.1-1
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-10-21 07:15:11 +02:00
Dietmar Maurer
6aa28f0a08 add fsync parameter to replace_file
Depend on proxmox 0.15.0

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-10-21 07:12:27 +02:00
Dietmar Maurer
412712029c proxmox-rrd: use fsync instead of syncfs
syncfs can sync unrelated data, and we do not want that.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-10-20 11:46:59 +02:00
Dietmar Maurer
a7ee3455da proxmox-rrd: fix regression tests 2021-10-19 18:41:03 +02:00
Dietmar Maurer
86b50e18ed proxmox-rrd: improve dev docs
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-10-19 11:17:09 +02:00
Dietmar Maurer
ed6a7f52e5 proxmox-rrd: cleanup - impl FromStr for JournalEntry
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-10-19 11:17:09 +02:00
Dietmar Maurer
75bb60e7b3 proxmox-rrd: add option to avoid page cache for load/save
use fadvice(.., POSIX_FADV_DONTNEED) for RRD files. We read those files only once,
and always rewrite them.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-10-19 11:17:09 +02:00
Dietmar Maurer
336e8f3e7f proxmox-rrd: use syncfs after writing rrd files
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
2021-10-19 11:17:09 +02:00
Dietmar Maurer
77c2e4668b proxmox-rrd: use fine grained locking in commit_journal_impl
Aquire the rrd_map lock for each file (else we block access for a long time)
2021-10-18 14:55:47 +02:00
Dietmar Maurer
cc0bb59788 proxmox-rrd: log all errors from apply_and_commit_journal_thread (but only once) 2021-10-18 11:57:19 +02:00
Dietmar Maurer
e23f3ec774 proxmox-rrd: cleanup list_old_journals 2021-10-18 10:00:58 +02:00
Dietmar Maurer
a74384f725 proxmox-rrd: cleanup - use struct instead of tuple 2021-10-16 12:45:03 +02:00
Dietmar Maurer
4393b93a8b proxmox-rrd: move RRDMap into extra file 2021-10-16 12:45:03 +02:00
Dietmar Maurer
9dcc64b71a proxmox-rrd: move JournalState into extra file 2021-10-16 12:45:03 +02:00
Dietmar Maurer
30b4800f4f proxmox-rrd: implement non blocking journal
Do not block while applying the journal.
2021-10-16 12:45:03 +02:00
Dietmar Maurer
2c24c1dd22 proxmox-rrd: rename RRDCacheState to JournalState 2021-10-15 09:35:44 +02:00
Dietmar Maurer
2be07c2250 proxmox-rrd: avoild blocking readers while applying the journal
By using and extra RwLock<RRDMap> on the rrd data.
2021-10-15 09:22:07 +02:00
Dietmar Maurer
3275f1ac16 proxmox-rrd: log journal apply/flush times, split apply and flush
We need to apply the journal only once.
2021-10-15 07:16:41 +02:00
Dietmar Maurer
ec5d84f4d3 proxmox-rrd: cleanup - use slot_end_time() 2021-10-14 16:29:00 +02:00
Dietmar Maurer
2b9fb32de1 proxmox-rrd: cleanup - use staturating_add instead of if/else 2021-10-14 16:10:55 +02:00
Dietmar Maurer
26bd6a4f77 proxmox-rrd: improve dev docs 2021-10-14 11:53:54 +02:00
Dietmar Maurer
8619b21e4d proxmox-rrd: make rrd load callback configurable 2021-10-14 11:41:26 +02:00
Dietmar Maurer
52d5f340f2 proxmox-rrd: add more regression tests 2021-10-14 10:55:12 +02:00
Dietmar Maurer
f2e1ab2d44 proxmox-rrd: add regression tests and two minor fixes 2021-10-14 10:17:07 +02:00
Dietmar Maurer
a1eede6918 proxmox-rrd: pass time and value to update function 2021-10-14 08:12:56 +02:00
Dietmar Maurer
4231e2d5f5 proxmox-rrd: add some integration tests (file format tests) 2021-10-13 18:21:23 +02:00
Dietmar Maurer
432374d024 use complete_file_name from proxmox-router 1.1 2021-10-13 14:10:02 +02:00
Dietmar Maurer
6e30f3433f remove proxmox-rrd-api-types crate, s/RRDTimeFrameResolution/RRDTimeFrame/
Because the types used inside the RRD have other requirements
than the API types:

- other serialization format
- the API may not support all RRD features

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Thomas Lamprecht
919ccf713a proxmox-rrd: move unshipped cli tool to examples
it's a rather low-level tool mostly useful for debugging and some of
it is rather "dumb" (by design) anyway, e.g., it does not
transparently applies journal but really only operates on the DB
files as is (which can conflict with daemon operations).

In summary, not (yet) a tool meant for end user consumption.
Move it to examples folder to avoid compilation on packaging (we do
not ship it anyway) which allows us to move the rather expensive
proxmox-router (pulls in hyper) to the dev-dependencies section.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
392d646f7b proxmox-rrd: add more commands to the rrd cli tool
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
7edea7e08c proxmox-rrd: rename last_counter to last_value
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
66dfd1f08f proxmox-rrd: protect against negative update time
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
507f19dd33 proxmox-rrd: new helpers: slot, slot_start_time & slot_end_time
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
334eb9ce48 proxmox-rrd: avoid expensive modulo (%) inside loop
Modulo is very slow, so we try to avoid it inside loops.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
4cd28918e2 proxmox-rrd: add binary to create/manage rrd files
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
2c72c6a7ba proxmox-rrd: split out load_rrd (cleanup)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
e928c24948 proxmox-rrd: support CF::Last
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
4bf2db8666 remove proxmox-rrd-api-types crate, s/RRDTimeFrameResolution/RRDTimeFrame/
Because the types used inside the RRD have other requirements
than the API types:

- other serialization format
- the API may not support all RRD features

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
ab567561b5 proxmox-rrd: extract_data: include values from current slot
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
cf097c5a89 proxmox-rrd: remove dependency to proxmox-rrd-api-types
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
bc68dee171 proxmox-rrd: implement new CBOR based format
Storing much more data points now got get better graphs.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Dietmar Maurer
0355554905 proxmox-rrd: use a journal to reduce amount of bytes written
Append pending changes in a simple text based format that allows for
lockless appends as long as we stay below 4 KiB data per write.

Apply the journal every 30 minutes and on daemon startup.

Note that we do not ensure that the journal is synced, this is a
perfomance optimization we can make as the kernel defaults to
writeback in-flight data every 30s (sysctl vm/dirty_expire_centisecs)
anyway, so we lose at max half a minute of data on a crash, here one
should have in mind that we normally expose 1 minute as finest
granularity anyway, so not really much lost.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-10-13 13:36:02 +02:00
Wolfgang Bumiller
d65b2df750 update to proxmox split and bump version to 0.8.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-11 12:37:13 +02:00
Wolfgang Bumiller
c7b17de1b5 update to proxmox split and bump version to 0.8.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-11 12:00:19 +02:00
Wolfgang Bumiller
d18f79dd4f update to first proxmox crate split
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-11 11:58:49 +02:00
Wolfgang Bumiller
e0ce41b03a update to first proxmox crate split
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-11 11:58:49 +02:00
Wolfgang Bumiller
1aaac3f173 bump proxmox dependency to 0.14.0 and proxmox-http to 0.5.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-08 11:18:22 +02:00
Wolfgang Bumiller
fa9757e67f bump proxmox dependency to 0.14.0 and proxmox-http to 0.5.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-08 11:18:22 +02:00
Dietmar Maurer
071cb7aa8b proxmox-rrd: use correct directory options in create_rrdb_dir 2021-10-07 08:50:50 +02:00
Dietmar Maurer
9c64c09c92 proxmox-rrd: cleanup error handling 2021-10-07 08:01:12 +02:00
Dietmar Maurer
5165bed8c2 proxmox-rrd: use log crate instead of eprintln, avoid duplicate logs 2021-10-06 18:19:22 +02:00
Dietmar Maurer
9c7fd3c936 proxmox-rrd: fix update (do not update) when time is in the past 2021-10-06 18:01:48 +02:00
Dietmar Maurer
881d8f85ea proxmox-rrd: improve developer docs 2021-10-06 12:19:54 +02:00
Dietmar Maurer
54f7a80f97 proxmox-rrd: remove serde dependency 2021-10-06 10:55:46 +02:00
Dietmar Maurer
5b3283c5d4 split out RRD api types into proxmox-rrd-api-types crate 2021-10-06 09:49:51 +02:00
Dietmar Maurer
538e6f66f3 split out RRD api types into proxmox-rrd-api-types crate 2021-10-06 09:49:51 +02:00
Dietmar Maurer
ac17698e4a proxmox-rrd: use create_path instead of std::fs::create_dir_all
To ensure correct file ownership.
2021-10-06 08:37:14 +02:00
Dietmar Maurer
9871af7ece move RRD code into proxmox-rrd crate 2021-10-06 08:13:28 +02:00
Dietmar Maurer
8d1a9d2ec6 move RRD code into proxmox-rrd crate 2021-10-06 08:13:28 +02:00
Wolfgang Bumiller
a5298b2a10 fix deprecated use of std::u64/... modules
integer primitive type modules are deprecated, use
associated constants instead

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-10-04 15:02:30 +02:00
Dietmar Maurer
359b00675a ExtJsFormatter: use ParameterError to correctly compute 'errors'
By default, 'errors' is now empty.

Depend on proxmox 0.13.5.
2021-09-28 10:19:55 +02:00
Dietmar Maurer
59c7c360e8 use UPID and systemd helpers from proxmox 0.13.4 2021-09-23 12:01:43 +02:00
Dietmar Maurer
ecb6b64f18 src/server/worker_task.rs: Avoid using pbs-api-type::Authid
Because we want to move worker_task.rs into proxmox-rest-server crate.
2021-09-23 11:59:25 +02:00
Dietmar Maurer
3d428713c5 rename pbs-systemd to proxmox-systemd 2021-09-21 10:06:27 +02:00
Dietmar Maurer
1eb2dd5dac move ApiConfig, FileLogger and CommandoSocket to proxmox-rest-server workspace
ApiConfig: avoid using  pbs_config::backup_user()
CommandoSocket: avoid using  pbs_config::backup_user()
FileLogger: avoid using  pbs_config::backup_user()
- use atomic_open_or_create_file()

Auth Trait: moved definitions to proxmox-rest-server/src/lib.rs
- removed CachedUserInfo patrameter
- return user as String (not Authid)

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-09-21 08:46:41 +02:00
Dietmar Maurer
a4e56bff60 more api type cleanups: avoid re-exports 2021-09-10 12:25:32 +02:00
Dietmar Maurer
441b34e46e more api type cleanups: avoid re-exports 2021-09-10 12:25:32 +02:00
Dietmar Maurer
157d9a45e1 move PruneOptions to pbs_api_types workspace 2021-09-10 09:21:27 +02:00
Dietmar Maurer
086536e3fa move datastore config to pbs_config workspace 2021-09-10 08:40:58 +02:00
Dietmar Maurer
99ac07d906 cleanup User configuration: use Updater 2021-09-09 13:14:28 +02:00
Dietmar Maurer
d48f612bec move acl to pbs_config workspaces, pbs_api_types cleanups 2021-09-09 10:50:08 +02:00
Dietmar Maurer
db1012b5aa move network config to pbs_config workspace 2021-09-08 12:22:48 +02:00
Dietmar Maurer
56d4dc1034 changer config cleanup: use Updater 2021-09-08 09:29:01 +02:00
Dietmar Maurer
3d9b2c8fd5 tape job cleanup: user Updater 2021-09-08 08:55:55 +02:00
Dietmar Maurer
5f13dcc5fc verify job cleanup: use Updater/flatten 2021-09-08 08:40:32 +02:00
Dietmar Maurer
7760d5679c sync job cleanup: use Updater/flatten 2021-09-08 08:28:09 +02:00
Dietmar Maurer
7240e6374b moved tape_job.rs to pbs_config workspace 2021-09-07 12:40:15 +02:00
Dietmar Maurer
8fe018cfd8 move Kdf and KeyInfo to pbs_api_types workspace 2021-09-07 09:59:59 +02:00
Dietmar Maurer
28e668ddf3 move drive config to pbs_config workspace
Also moved the tape type definitions to pbs_api_types.
2021-09-03 09:10:18 +02:00
Dietmar Maurer
1c30b9da92 add missing file pbs-api-types/src/remote.rs 2021-09-02 17:36:13 +02:00
Dietmar Maurer
6b977533d6 move remote config into pbs-config workspace 2021-09-02 14:25:15 +02:00
Dietmar Maurer
80f7bf3822 start new pbs-config workspace
moved src/config/domains.rs
2021-09-02 12:58:20 +02:00
Dietmar Maurer
3fc017a570 start new pbs-config workspace
moved src/config/domains.rs
2021-09-02 12:58:20 +02:00
Wolfgang Bumiller
401cf57883 another import cleanup
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-09-01 14:46:01 +02:00
Wolfgang Bumiller
199227bd01 move some more API types
ArchiveEntry -> pbs-datastore
RestoreDaemonStatus -> pbs-api-types

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-08-31 11:29:17 +02:00
Wolfgang Bumiller
6dc073fa0f move some API return types to pbs-api-types
they'll be required by the api client

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-08-30 11:39:01 +02:00
Wolfgang Bumiller
12312bcb36 more Updatable -> UpdaterType fixups
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-08-25 10:43:58 +02:00
Wolfgang Bumiller
6970858aad bump proxmox dependency to 0.13.0
and with it:
* bump proxmox-http dependency to 0.4.0
* bump proxmox-apt dependency to 0.7.0

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-08-25 10:43:58 +02:00
Wolfgang Bumiller
35e7f2f48e use ApiType trait
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-08-25 10:43:58 +02:00
Dietmar Maurer
d0103000b8 use new api updater features
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-08-25 10:43:58 +02:00
Wolfgang Bumiller
8d0c0ed699 bump version to 0.7.0, depend on proxmox 0.13.0
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-08-25 10:41:25 +02:00
Wolfgang Bumiller
f9c6f7a18c bump versionto 0.7.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-08-24 15:41:08 +02:00
Thomas Lamprecht
4174305528 bump version to 0.6.0-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-29 18:11:24 +02:00
Fabian Ebner
fb51dcf9db add type DebianCodename
which allows to get rid of an possible error with check_suites, and
easily detect unexpected values with get_current_release_codename.

The check_repos function needs to be adapted, since the type does
not include suite names like oldstable,experimental,etc.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-29 18:00:38 +02:00
Fabian Ebner
51c69d76a5 repo: remove has_suite_variant helper
by exchanging loops in the check_suites function, which was the only
user. Exchanging loops also helps for introducing a type for Debian condenames.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-29 18:00:38 +02:00
Fabian Ebner
13cdf8d1df check repos: have caller specify the current suite
Like that, a potential error is further up the stack, and it's more
consistent with what the standard_repository functions do.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-29 18:00:38 +02:00
Fabian Ebner
c7f6163e2b repo: make suite_variant helper more general
use the first appearance of '-' or '/' to detect the variant instead
of keeping a list of possible variants, which would need to include
things like "-proposed-updates-debug".

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-29 18:00:38 +02:00
Fabian Ebner
f48c12b00a standard repos: add suite parameter for stricter detection
Require that the suite matches too when detecting standard
repositories, since no or invalid updates will be obtained when the
suite is wrong. Thus, it should not be considered to be the same
repository.

Add the parameter for get_standard_repository too, so that the two
related calls have more similar parameters, and the detection of the
current release code name can be done once in the caller once.

This also will fix an issue with the front-end, where adding a
standard repository would end up just enabling an already present
repository with the wrong suite.

Reported-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-29 18:00:38 +02:00
Thomas Lamprecht
0b1ecc260a cargo: update proxmox to 0.12.1
For the FS compat improvement in the atomic create file helper

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-22 10:09:53 +02:00
Wolfgang Bumiller
7bddd33ede move remaining client tools to pbs-tools/datastore
pbs-datastore now ended up depending on tokio after all, but
that's fine for now

for the fuse code I added pbs-fuse-loop (has the old
fuse_loop and its 'loopdev' module)
ultimately only binaries should depend on this to avoid the
library link

the only thins remaining to move out the client binary are
the api method return types, those will need to be moved to
pbs-api-types...

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-21 14:12:24 +02:00
Dietmar Maurer
e7ff5817ec add helpers to write configuration files 2021-07-20 18:54:23 +02:00
Dietmar Maurer
a3de24506a depend on proxmox 0.12.0, bump version to 0.6.1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-20 18:10:48 +02:00
Thomas Lamprecht
68064f65bc buildsys: indent continued command
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-20 18:08:49 +02:00
Thomas Lamprecht
606eac1db7 cargo: disable default-features for proxmox
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-20 17:54:03 +02:00
Dietmar Maurer
39f12a8c05 depend on proxmox 0.12.0, bump version to 0.5.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-20 17:25:09 +02:00
Wolfgang Bumiller
711535cdee move some api types to pbs-api-types
and resolve some imports in the client binary

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-19 15:01:03 +02:00
Wolfgang Bumiller
1081dc8d59 move client to pbs-client subcrate
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-19 12:58:43 +02:00
Thomas Lamprecht
7d88081e0d bump version to 0.5.0-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-16 16:19:28 +02:00
Fabian Ebner
5581858b30 code cleanup: use contains()
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-16 15:22:51 +02:00
Fabian Ebner
2a1fb9bfdd standard repo detection: handle alternative URI for PVE repos
For PVE, URIs without the final "/pve" are also valid.

Make the single URL response a vector and iterate over it, lower
index is preferred.

Reported in the community forum:
https://forum.proxmox.com/threads/pve-7-0-9-no-proxmox-ve-repository-enabled.92427/

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
[ Thomas: extend commit message slightly ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-16 15:21:21 +02:00
Fabian Ebner
652f52a1f2 bump proxmox dependency
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-16 15:18:45 +02:00
Dominik Csapak
a25f4f3b36 api-types: move PRUNE_SCHEMA_KEEP_* to pbs-api-types
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2021-07-16 11:26:09 +02:00
Stefan Reiter
a5093db6f3 api: add support for notes on backup groups
Stored in atomically-updated 'notes' file in backup group directory.
Available via dedicated GET/PUT API calls, as well as the first line
being included in list_groups (similar to list_snapshots).

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-12 07:13:28 +02:00
Wolfgang Bumiller
1e00eae767 move more api types for the client
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-09 15:17:53 +02:00
Wolfgang Bumiller
0b8cd2b305 move some api types and resolve imports
in preparation of moving client & proxmox_client_tools out
into a pbs-client subcrate

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-09 15:17:52 +02:00
Wolfgang Bumiller
0d5d32a76a move chunk_store to pbs-datastore
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-07 14:37:47 +02:00
Wolfgang Bumiller
419a6ce60f move UPID to pbs-api-types, add UPIDExt
pbs-server side related methods are added via the UPIDExt
trait

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-07 13:51:03 +02:00
Wolfgang Bumiller
add07b08c7 move backup id related types to pbs-api-types
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-07 11:34:56 +02:00
Wolfgang Bumiller
4e21b52bc2 move userid types to pbs-api-types
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-07 09:53:48 +02:00
Wolfgang Bumiller
ad4a9aea25 move id and single line comment format to pbs-api-types
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-07 09:49:38 +02:00
Wolfgang Bumiller
4642ba673a move TaskState trait to pbs-datastore
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-07 09:24:39 +02:00
Wolfgang Bumiller
669d53c0bf add pbs-api-types subcrate, move key_derivation
move key_derivation to pbs-datastore

pbs-api-types should only contain "basic" types which
* are usually required by clients
* don't depend on pbs-related code directly

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-07-07 09:04:09 +02:00
Fabian Ebner
9a51a30f46 tests: parse and write the result again
A cheap way to "double" the number of test cases.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-02 13:04:00 +02:00
Thomas Lamprecht
4b0935c621 bump version to 0.4.0-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-01 18:35:09 +02:00
Fabian Ebner
ae7e2360b7 support quote-word parsing for one-line format
so that parsing CD ROM repositories with spaces in the name works too.
But it's not limited to that, and should make one-line parsing rather
similar to what APT does (stanza parsing in APT doesn't use
ParseQuoteWord at all AFAICS).

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-01 18:27:27 +02:00
Fabian Ebner
8265d0ce33 avoid backtick unicode symbol in string
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-07-01 18:27:27 +02:00
Thomas Lamprecht
fd99aa062e bump version to 0.3.1-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 20:45:27 +02:00
Thomas Lamprecht
34074c1b80 slightly adapt repository text
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 20:42:31 +02:00
Fabian Ebner
3c4e441d7c standard repos: allow conversion from handle and improve information
Add a description for the handle, which can be useful to display
alongside the name. The descriptions are essentially the first
sentence from PVE's "Package Repositories" docs, but without the
product name.

Also drop the " Repository" suffix from the names, as it's not useful,
but can be ugly: e.g. for the UI when the label already is
'Repository:'.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-30 20:38:05 +02:00
Thomas Lamprecht
47c2c350b4 bump version to 0.3.0-1
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 13:34:37 +02:00
Thomas Lamprecht
082c868535 buildsys: upload: switch product to devel and dist to bullseye
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-06-30 13:34:37 +02:00
Fabian Ebner
3f7152383b standard repos: drop product acronym from repo name
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-30 13:24:42 +02:00
Fabian Ebner
87ea23ec83 check: return 'origin' property instead of 'badge' for official host
which is obtained from the cached InRelease file and also works for
mirrors, host aliases, direct IPs.

The has_official_uri function was replaced by origin_from_uris.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-30 13:24:42 +02:00
Fabian Ebner
bb0ff2ac73 add get_cached_origin method and an initial config module
Allows obtaining the 'Origin' property from the cached InRelease file.

Used the once_cell crate for the config module, because it is already
used in proxmox-backup and seemed to be the right fit here.

For now, the config module is just used to be able to override the
path for the test environment, but those are actual APT config
variables, and in the future, it can be extended and used to actually
parse the apt.conf(.d/*) on the system.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-30 13:24:42 +02:00
Dietmar Maurer
558fbff1d7 bump version to 0.6.0-2 2021-06-30 08:43:40 +02:00
Dietmar Maurer
6c9ce7dbb6 remove debug output 2021-06-30 08:42:14 +02:00
Dietmar Maurer
60eedc0da2 bump version to 0.6.0-1 2021-06-25 11:09:48 +02:00
Dietmar Maurer
1ecdc2ed72 use one lock file per realm 2021-06-25 11:05:56 +02:00
Fabian Ebner
c8f0c006e7 bump version to 0.2.0-1
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-23 16:00:30 +02:00
Fabian Ebner
8ada17854d add handling of Proxmox standard repositories
Get handles for the available repositories along with their current
configuration status and make it possible to add them.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-23 16:00:30 +02:00
Fabian Ebner
76d3a5ba1f add more functions to check repositories
Currently includes check for suites and check for official URIs

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-23 16:00:30 +02:00
Fabian Ebner
5bf8ddece7 add files for Debian packaging
The Makefile is based on the one from Mira's conntrack series, as it already got
some review.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-23 16:00:30 +02:00
Fabian Ebner
b6be0f3940 initial commit
Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-06-23 16:00:30 +02:00
Dietmar Maurer
42dedabbb1 bump version to 0.5.0-1 2021-06-23 11:29:13 +02:00
Dietmar Maurer
5751689167 avoid unused features "sortable-macro" and "api-macro" 2021-06-23 11:27:55 +02:00
Dietmar Maurer
426f7b6014 bump versionm to 0.4.0-1 2021-06-23 11:17:30 +02:00
Dietmar Maurer
4f90d7009d set "default-features = false" for proxmox crate 2021-06-23 11:15:33 +02:00
Dietmar Maurer
8286806a19 bump version toö 0.3.0-1 2021-06-22 09:23:59 +02:00
Dietmar Maurer
ac034c72da return Url as string
To make perl bindings simple.
2021-06-21 14:12:13 +02:00
Dietmar Maurer
cc64c7e35d bump version to 0.2.0-1 2021-06-21 13:37:43 +02:00
Dietmar Maurer
73c5c4af7c implement Deserialize/Serialize for OpenIdConfig
Useful to create perl bindings.
2021-06-21 13:27:05 +02:00
Fabian Grünbichler
634e35489a add packaging
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-06-18 16:16:40 +02:00
Dietmar Maurer
dd0d18d5ce make state directory configurable (pass state_dir as parameter) 2021-06-18 10:49:38 +02:00
Dietmar Maurer
ca65d297b7 initial import 2021-06-18 10:23:57 +02:00
Fabian Grünbichler
f28a85da5e build: upload to buster and bullseye
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-06-11 14:00:55 +02:00
Wolfgang Bumiller
ee7fe8f93c bump version to 0.2.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-07 13:54:36 +02:00
Wolfgang Bumiller
a145553557 finish client documentation
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-07 12:26:56 +02:00
Wolfgang Bumiller
5f4b571450 add Client::directory_url helper
allows to drop a `mut` requirement in pmg-rs without having
to store the URL twice

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-07 12:21:17 +02:00
Wolfgang Bumiller
1d1f80f5ca and another *new* clippy fixup
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-07 11:50:54 +02:00
Wolfgang Bumiller
f406e6fb34 formatting fixups
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-07 11:49:26 +02:00
Wolfgang Bumiller
9538126247 clippy fixes
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-07 11:47:56 +02:00
Wolfgang Bumiller
dbabed6842 derive Copy for the simple status enums
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-07 11:47:45 +02:00
Wolfgang Bumiller
2dfc6a74bb mark Error as must_use
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-07 11:39:04 +02:00
Wolfgang Bumiller
0ef3c33538 doc fixup
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-05 11:21:16 +02:00
Wolfgang Bumiller
5f0ba96810 finish docs and #[deny(missing_docs)] at the top level
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-05 11:16:13 +02:00
Wolfgang Bumiller
357c0614cf even more documentation
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-04 11:21:30 +02:00
Wolfgang Bumiller
7bd0bfe1b2 more documentation
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-04 11:20:11 +02:00
Wolfgang Bumiller
47af324d94 Some documentation
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-04 11:04:17 +02:00
Wolfgang Bumiller
7c67886e1f add top level doc
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-05-04 09:11:18 +02:00
Wolfgang Bumiller
a6ff69404b bump version to 0.2.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-04-14 14:57:46 +02:00
Wolfgang Bumiller
558f51a167 make revocation workflow accessible without client
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-04-14 14:56:40 +02:00
Wolfgang Bumiller
b624fa1f3c bump version to 0.2.0-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-04-12 15:31:53 +02:00
Wolfgang Bumiller
a947050ec1 add util::Csr for CSR generation
This is essentially taken from pmg-rs and should be used
from there.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-04-12 14:54:21 +02:00
Wolfgang Bumiller
37b4c4f654 add is_valid to all Status enums
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-04-12 13:38:10 +02:00
Wolfgang Bumiller
5a70f0c392 add is_pending to all Status enums
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-04-12 13:32:40 +02:00
Wolfgang Bumiller
afc59f6d15 add more of the ACME workflow to 'Account'
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-18 11:36:42 +01:00
Wolfgang Bumiller
02ecbb499c bump version to 0.1.4-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-17 15:28:55 +01:00
Wolfgang Bumiller
5aee14ac00 collect extra account fields in AccountData
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-17 15:20:50 +01:00
Wolfgang Bumiller
d369de8636 bump version to 0.1.3-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-17 13:35:49 +01:00
Wolfgang Bumiller
937a99a2e1 fix ec signature padding
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-17 13:32:57 +01:00
Wolfgang Bumiller
549b52cf68 don't serialize 'null' for 'expires' when ordering
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-17 12:24:21 +01:00
Wolfgang Bumiller
15fae62c15 bump version to 0.1.2-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-12 15:43:40 +01:00
Wolfgang Bumiller
ca0f57290f explicitly pass Content-Length header
pebble refuses to cooperate without it

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-10 12:01:00 +01:00
Wolfgang Bumiller
c9f137a093 bump version to 0.1.1-1
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-09 13:23:27 +01:00
Wolfgang Bumiller
cbfeb58ce4 make AccountData fields public
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-09 13:22:52 +01:00
Wolfgang Bumiller
aa23068293 import
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2021-03-09 13:07:59 +01:00
Fabian Grünbichler
c94f367dec clippy: more misc fixes
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-01-26 09:54:55 +01:00
Fabian Grünbichler
655ceac1c6 clippy: collapse/rework nested ifs
no semantic changes (intended).

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-01-20 16:22:59 +01:00
Wolfgang Bumiller
4d8bd987a4 clippy fixups
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2020-10-14 11:20:07 +02:00
Stefan Reiter
6eff0b289e rrd: fix integer underflow
Causes a panic if last_update is smaller than RRD_DATA_ENTRIES*reso,
which (I believe) can happen when inserting the first value for a DB.

Clamp the value to 0 in that case.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-01 14:30:32 +02:00
Dietmar Maurer
110ceff08c avoid chrono dependency, depend on proxmox 0.3.8
- remove chrono dependency

- depend on proxmox 0.3.8

- remove epoch_now, epoch_now_u64 and epoch_now_f64

- remove tm_editor (moved to proxmox crate)

- use new helpers from proxmox 0.3.8
  * epoch_i64 and epoch_f64
  * parse_rfc3339
  * epoch_to_rfc3339_utc
  * strftime_local

- BackupDir changes:
  * store epoch and rfc3339 string instead of DateTime
  * backup_time_to_string now return a Result
  * remove unnecessary TryFrom<(BackupGroup, i64)> for BackupDir

- DynamicIndexHeader: change ctime to i64

- FixedIndexHeader: change ctime to i64
2020-09-15 07:12:57 +02:00
Dominik Csapak
50fa6045b3 api2/status: use new rrd::extract_cached_data
and drop the now unused extract_lists function

this also fixes a bug, where we did not add the datastore to the list at
all when there was no rrd data

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2020-06-10 13:31:16 +02:00
Dominik Csapak
11cb8cd008 rrd: move creation of serde value into api
there is now a 'extract_cached_data' which just returns
the data of the specified field, and an api function that converts
a list of fields to the correct serde value

this way we do not have to create a serde value in rrd/cache.rs
(makes for a better interface)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2020-06-10 13:31:14 +02:00
Dominik Csapak
9a860821e4 refactor time functions to tools
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
2020-06-10 13:31:10 +02:00
Dominik Csapak
20ada7a08c rrd: add 'extract_lists'
this is an interface to simply get the Vec<Option<f64>> out of rrd
without going through serde values

we return a list of timestamps and a HashMap with the lists we could find
(otherwise it is not in the map)

if no lists could be extracted, the time list is also empty

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2020-06-09 12:19:06 +02:00
Dietmar Maurer
1fa3341d68 rrd: reduce io by saving data only once a minute 2020-05-29 09:16:13 +02:00
Dietmar Maurer
a76b0e3c09 src/rrd/rrd.rs: do not wrap error and return ErrorKind::NotFound 2020-05-25 10:30:04 +02:00
Dietmar Maurer
9ce21e005d src/rrd/cache.rs: display/log error when RRD load fails 2020-05-25 10:18:53 +02:00
Dietmar Maurer
207d0c7714 src/rrd/rrd.rs: store/verify magic number 2020-05-25 09:21:54 +02:00
Dietmar Maurer
bfc2f8b7a2 src/rrd/rrd.rs: implement DST_COUNTER 2020-05-25 08:14:30 +02:00
Dietmar Maurer
d261516f2d src/rrd/rrd.rs: correctly compute derived values
use f64 for time.
2020-05-25 07:02:04 +02:00
Dietmar Maurer
f4561029ba src/rrd/rrd.rs: implement DST_DERIVE 2020-05-24 19:02:35 +02:00
Dietmar Maurer
7acdec12e8 src/rrd/rrd.rs: restructure whole code 2020-05-24 16:51:28 +02:00
Dietmar Maurer
777f527603 src/rrd/rrd.rs: reduce size by using f64:NAN as UNKNOWN 2020-05-24 09:09:09 +02:00
Dietmar Maurer
fd2c16f704 src/rrd/rrd.rs: simplify an fix old value deletion 2020-05-24 06:44:06 +02:00
Dietmar Maurer
6357746f2e rrd: fix display interval, try to avoid numeric errors 2020-05-23 16:03:43 +02:00
Dietmar Maurer
69da9baf59 rrd: simplify code 2020-05-23 15:37:17 +02:00
Dietmar Maurer
e073e5c107 rrd: pack multiple rrd values into th estat list 2020-05-23 14:03:44 +02:00
Dietmar Maurer
1c0117790f add experimental rrd api to get cpu stats 2020-05-23 11:50:53 +02:00
Dietmar Maurer
5bc85d2d04 add simple rrd implementation 2020-05-23 10:42:48 +02:00
594 changed files with 1201282 additions and 4355 deletions

View File

@ -1,5 +0,0 @@
[source]
[source.debian-packages]
directory = "/usr/share/cargo/registry"
[source.crates-io]
replace-with = "debian-packages"

5
.cargo/config.toml Normal file
View File

@ -0,0 +1,5 @@
# [source]
# [source.debian-packages]
# directory = "/usr/share/cargo/registry"
# [source.crates-io]
# replace-with = "debian-packages"

View File

@ -1,38 +1,68 @@
[workspace]
members = [
"proxmox-access-control",
"proxmox-acme",
"proxmox-acme-api",
"proxmox-api-macro",
"proxmox-apt",
"proxmox-apt-api-types",
"proxmox-async",
"proxmox-auth-api",
"proxmox-borrow",
"proxmox-client",
"proxmox-compression",
"proxmox-config-digest",
"proxmox-daemon",
"proxmox-dns-api",
"proxmox-http",
"proxmox-http-error",
"proxmox-human-byte",
"proxmox-io",
"proxmox-lang",
"proxmox-ldap",
"proxmox-log",
"proxmox-login",
"proxmox-metrics",
"proxmox-network-api",
"proxmox-notify",
"proxmox-openid",
"proxmox-product-config",
"proxmox-rest-server",
"proxmox-router",
"proxmox-rrd",
"proxmox-rrd-api-types",
"proxmox-schema",
"proxmox-section-config",
"proxmox-sendmail",
"proxmox-serde",
"proxmox-shared-cache",
"proxmox-shared-memory",
"proxmox-simple-config",
"proxmox-sortable-macro",
"proxmox-subscription",
"proxmox-sys",
"proxmox-syslog-api",
"proxmox-systemd",
"proxmox-tfa",
"proxmox-time",
"proxmox-time-api",
"proxmox-uuid",
"proxmox-worker-task",
"pbs-api-types",
]
exclude = [
"build",
]
resolver = "2"
[workspace.package]
authors = ["Proxmox Support Team <support@proxmox.com>"]
edition = "2021"
license = "AGPL-3"
repository = "https://git.proxmox.com/?p=proxmox.git"
homepage = "https://proxmox.com"
exclude = [ "debian" ]
rust-version = "1.82"
[workspace.dependencies]
# any features enabled here are enabled on all members using 'workspace = true'!
@ -41,24 +71,29 @@ exclude = [ "debian" ]
anyhow = "1.0"
base32 = "0.4"
base64 = "0.13"
bitflags = "2.4"
bytes = "1.0"
const_format = "0.2"
crc32fast = "1"
crossbeam-channel = "0.5"
endian_trait = "0.6"
env_logger = "0.11"
flate2 = "1.0"
foreign-types = "0.3"
form_urlencoded = "1.1"
futures = "0.3"
handlebars = "3.0"
hex = "0.4"
http = "0.2"
hyper = "0.14.5"
lazy_static = "1.4"
ldap3 = { version = "0.11", default-features = false }
lettre = "0.11.1"
libc = "0.2.107"
log = "0.4.17"
mail-parser = "0.8.2"
native-tls = "0.2"
nix = "0.26.1"
once_cell = "1.3.1"
openssl = "0.10"
pam = "0.7"
pam-sys = "0.5"
percent-encoding = "2.1"
pin-utils = "0.1.0"
@ -66,32 +101,52 @@ proc-macro2 = "1.0"
quote = "1.0"
regex = "1.5"
serde = "1.0"
serde_cbor = "0.11.1"
serde_json = "1.0"
serde_plain = "1.0"
syn = { version = "1.0", features = [ "full", "visit-mut" ] }
syn = { version = "2", features = [ "full", "visit-mut" ] }
tar = "0.4"
tokio = "1.6"
tokio-openssl = "0.6.1"
tokio-stream = "0.1.0"
tower-service = "0.3.0"
tracing = "0.1"
tracing-journald = "0.3.0"
tracing-log = { version = "0.2", default-features = false }
tracing-subscriber = "0.3.16"
url = "2.2"
walkdir = "2"
webauthn-rs = "0.3"
zstd = { version = "0.6", features = [ "bindgen" ] }
zstd = { version = "0.12", features = [ "bindgen" ] }
# workspace dependencies
proxmox-api-macro = { version = "1.0.4", path = "proxmox-api-macro" }
proxmox-acme = { version = "0.5.3", path = "proxmox-acme", default-features = false }
proxmox-api-macro = { version = "1.3.2", path = "proxmox-api-macro" }
proxmox-apt-api-types = { version = "1.0.2", path = "proxmox-apt-api-types" }
proxmox-auth-api = { version = "0.4.0", path = "proxmox-auth-api" }
proxmox-async = { version = "0.4.1", path = "proxmox-async" }
proxmox-compression = { version = "0.1.1", path = "proxmox-compression" }
proxmox-http = { version = "0.8.0", path = "proxmox-http" }
proxmox-io = { version = "1.0.0", path = "proxmox-io" }
proxmox-lang = { version = "1.1", path = "proxmox-lang" }
proxmox-rest-server = { version = "0.3.0", path = "proxmox-rest-server" }
proxmox-router = { version = "1.3.1", path = "proxmox-router" }
proxmox-schema = { version = "1.3.7", path = "proxmox-schema" }
proxmox-compression = { version = "0.2.4", path = "proxmox-compression" }
proxmox-daemon = { version = "0.1.0", path = "proxmox-daemon" }
proxmox-http = { version = "0.9.4", path = "proxmox-http" }
proxmox-http-error = { version = "0.1.0", path = "proxmox-http-error" }
proxmox-human-byte = { version = "0.1.0", path = "proxmox-human-byte" }
proxmox-io = { version = "1.1.0", path = "proxmox-io" }
proxmox-lang = { version = "1.3", path = "proxmox-lang" }
proxmox-log= { version = "0.2.5", path = "proxmox-log" }
proxmox-login = { version = "0.2.0", path = "proxmox-login" }
proxmox-product-config = { version = "0.2.0", path = "proxmox-product-config" }
proxmox-config-digest = { version = "0.1.0", path = "proxmox-config-digest" }
proxmox-rest-server = { version = "0.8.8", path = "proxmox-rest-server" }
proxmox-router = { version = "3.1.1", path = "proxmox-router" }
proxmox-schema = { version = "4.0.0", path = "proxmox-schema" }
proxmox-section-config = { version = "2.1.0", path = "proxmox-section-config" }
proxmox-sendmail = { version = "0.1.0", path = "proxmox-sendmail" }
proxmox-serde = { version = "0.1.1", path = "proxmox-serde", features = [ "serde_json" ] }
proxmox-sortable-macro = { version = "0.1.2", path = "proxmox-sortable-macro" }
proxmox-sys = { version = "0.4.2", path = "proxmox-sys" }
proxmox-tfa = { version = "4.0.0", path = "proxmox-tfa" }
proxmox-time = { version = "1.1.4", path = "proxmox-time" }
proxmox-shared-memory = { version = "0.3.0", path = "proxmox-shared-memory" }
proxmox-sortable-macro = { version = "0.1.3", path = "proxmox-sortable-macro" }
proxmox-sys = { version = "0.6.6", path = "proxmox-sys" }
proxmox-systemd = { version = "0.1.0", path = "proxmox-systemd" }
proxmox-tfa = { version = "5.0.0", path = "proxmox-tfa" }
proxmox-time = { version = "2.0.0", path = "proxmox-time" }
proxmox-uuid = { version = "1.0.1", path = "proxmox-uuid" }
proxmox-worker-task = { version = "0.1.0", path = "proxmox-worker-task" }

View File

@ -1,6 +1,7 @@
# Shortcut for common operations:
CRATES != cargo metadata --format-version=1 | jq -r .workspace_members'[]' | awk '{ print $$1 }'
# see proxmox-backup if we ever want to support other prefixes
CRATES != echo proxmox-*/Cargo.toml | sed -e 's|/Cargo.toml||g'
# By default we just run checks:
.PHONY: all
@ -37,6 +38,12 @@ dinstall:
autopkgtest build/$* build/*.deb -- null
touch $@
.PHONY: list-packages
list-packages:
@for p in $(CRATES); do \
echo "librust-$$p-dev"; \
done
.PHONY: check
check:
cargo test
@ -61,7 +68,7 @@ doc:
clean:
cargo clean
rm -rf build/
rm -f -- *-deb *-dsc *-autopkgtest *.buildinfo *.changes
rm -f -- *-deb *-dsc *-autopkgtest *.build *.buildinfo *.changes
.PHONY: update
update:
@ -72,4 +79,72 @@ update:
dcmd --deb rust-$*_*.changes \
| grep -v '.changes$$' \
| tar -cf "$@.tar" -T-; \
cat "$@.tar" | ssh -X repoman@repo.proxmox.com upload --product devel --dist bullseye
cat "$@.tar" | ssh -X repoman@repo.proxmox.com upload --product devel --dist bookworm
%-install:
rm -rf build/install/$*
mkdir -p build/install/$*
BUILDDIR=build/install/$* BUILDCMD=/usr/bin/true NOCONTROL=1 ./build.sh "$*" || true
version="$$(dpkg-parsechangelog -l $*/debian/changelog -SVersion | sed -e 's/-.*//')"; \
install -m755 -Dd "$(DESTDIR)/usr/share/cargo/registry/$*-$${version}"; \
rm -rf "$(DESTDIR)/usr/share/cargo/registry/$*-$${version}"; \
mv "build/install/$*/$*" \
"$(DESTDIR)/usr/share/cargo/registry/$*-$${version}"; \
mv "$(DESTDIR)/usr/share/cargo/registry/$*-$${version}/debian/cargo-checksum.json" \
"$(DESTDIR)/usr/share/cargo/registry/$*-$${version}/.cargo-checksum.json"; \
rm -rf "$(DESTDIR)/usr/share/cargo/registry/$*-$${version}/debian" \
.PHONY: install
install: $(foreach c,$(CRATES), $c-install)
%-install-overlay: %-install
version="$$(dpkg-parsechangelog -l $*/debian/changelog -SVersion | sed -e 's/-.*//')"; \
setfattr -n trusted.overlay.opaque -v y \
"$(DESTDIR)/usr/share/cargo/registry/$*-$${version}"
install -m755 -Dd $(DESTDIR)/usr/lib/extension-release.d
echo 'ID=_any' >$(DESTDIR)/usr/lib/extension-release.d/extension-release.$*
.PHONY: install-overlay
install-overlay: $(foreach c,$(CRATES), $c-install-overlay)
# To make sure a sysext *replaces* a crate, rather than "merging" with it, we
# need to be able to set the 'trusted.overlay.opaque' xattr. Since we cannot do
# this as a user, we utilize `fakeroot` which keeps track of this for us, and
# turn the final directory into an 'erofs' file system image.
#
# The reason is that if a crate gets changed like this:
#
# old:
# src/foo.rs
# new:
# src/foo/mod.rs
#
# if its /usr/share/cargo/registry/$crate-$version directory was not marked as
# "opaque", the merged file system would end up with both
#
# src/foo.rs
# src/foo/mod.rs
#
# together.
#
# See https://docs.kernel.org/filesystems/overlayfs.html
%-sysext:
fakeroot $(MAKE) $*-sysext-do
%-sysext-do:
rm -f extensions/$*.raw
rm -rf build/sysext/$*
rm -rf build/install/$*
$(MAKE) DESTDIR=build/sysext/$* $*-install-overlay
mkdir -p extensions
mkfs.erofs extensions/$*.raw build/sysext/$*
sysext:
fakeroot $(MAKE) sysext-do
sysext-do:
rm -f extensions/proxmox-workspace.raw
[ -n "$(NOCLEAN)" ] || rm -rf build/sysext/workspace
$(MAKE) DESTDIR=build/sysext/workspace $(foreach c,$(CRATES), $c-install)
install -m755 -Dd build/sysext/workspace/usr/lib/extension-release.d
echo 'ID=_any' >build/sysext/workspace/usr/lib/extension-release.d/extension-release.proxmox-workspace
mkdir -p extensions
mkfs.erofs extensions/proxmox-workspace.raw build/sysext/workspace

153
README.md Normal file
View File

@ -0,0 +1,153 @@
# Local cargo config
This repository ships with a `.cargo/config.toml` that replaces the crates.io
registry with packaged crates located in `/usr/share/cargo/registry`.
A similar config is also applied building with `dh_cargo`. Cargo.lock needs to
be deleted when switching between packaged crates and crates.io, since the
checksums are not compatible.
To reference new dependencies (or updated versions) that are not yet packaged,
the dependency needs to point directly to a path or git source.
# Quickly installing all packages from apt
To a void too many manual installations when `mk-build-deps` etc. fail, a quick
way to install all the main packages of this workspace is to run:
# apt install $(make list-packages)
# Steps for Releases
- Run `./bump.sh <CRATE> [patch|minor|major|<VERSION>]`
- Fill out changelog
- Confirm bump commit
- Build packages with `make <crate>-deb`.
- Don't forget to commit updated d/control!
# Adding Crates
1. At the top level:
- Generate the crate: `cargo new --lib the-name`
- Sort the crate into `Cargo.toml`'s `workspace.members`
2. In the new crate's `Cargo.toml`:
- In `[package]` set:
authors.workspace = true
edition.workspace = true
exclude.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
If a separate ``exclude`` is need it, separate it out as its own
block above the inherited fields.
- Add a meaningful `description`
- Copy `debian/copyright` and `debian/debcargo.toml` from another subcrate.
3. In the new crate\'s `lib.rs`, add the following preamble on top:
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
4. Ideally (but optionally) in the new crate\'s `lib.rs`, add the following
preamble on top as well:
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(missing_docs)]
# Adding a new Dependency
1. At the top level:
- Add it to `[workspace.dependencies]` specifying the version and any
features that should be enabled throughout the workspace
2. In each member\'s `Cargo.toml`:
- Add it to the desired dependencies section with `workspace = true` and no
version specified.
- If this member requires additional features, add only the extra features
to the member dependency.
# Updating a Dependency\'s Version
1. At the top level:
- Bump the version in `[workspace.dependencies]` as desired.
- Check for deprecations or breakage throughout the workspace.
# Notes on Workspace Inheritance
Common metadata (like authors, license, ..) are inherited throughout the
workspace. If new fields are added that are identical for all crates, they
should be defined in the top-level `Cargo.toml` file\'s `[workspace.package]`
section, and inherited in all members explicitly by setting `FIELD.workspace =
true` in the member\'s `[package]` section.
Dependency information is also inherited throughout the workspace, allowing a
single dependency specification in the top-level `Cargo.toml` file to be used
by all members.
Some restrictions apply:
- features can only be added in members, never removed (this includes
`default_features = false`!)
- the base feature set at the workspace level should be the minimum (possibly
empty!) set required by all members
- workspace dependency specifications cannot include `optional`
- if needed, the `optional` flag needs to be set at the member level when
using a workspace dependency
# Working with *other* projects while changing to *single crates here*
When crates from this workspace need changes caused by requirements in projects
*outside* of this repository, it can often be annoying to keep building and
installing `.deb` files.
Additionally, doing so often requires complete rebuilds as cargo will not pick
up *file* changes of external dependencies.
One way to fix this is by actually changing the version. Since we cut away
anything starting at the first hyphen in the version, we need to use a `+`
(build metadata) version suffix.
Eg. turn `5.0.0` into `5.0.0+test8`.
There are 2 faster ways:
## Adding a `#[patch.crates-io]` section to the other project.
Note, however, that this requires *ALL* crates from this workspace to be listed,
otherwise multiple conflicting versions of the same crate AND even the same
numerical *version* might be built, causing *weird* errors.
The advantage, however, is that `cargo` will pick up on file changes and rebuild
the crate on changes.
## An in-between: system extensions
An easy way to quickly get the new package "installed" *temporarily*, such that
real apt package upgrades are unaffected is as a system-extension.
The easiest way — if no other extensions are used — is to just symlink the
`extensions/` directory to `/run` as root via:
```
# ln -s ${THIS_DIR}/extensions /run/extensions
```
This does not persist across reboots.
(Note: that the `extensions/` directory does not need to exist for the above to
work.)
Once this is done, trying a new version of a crate works by:
1. Bump the version: eg. `5.0.0+test8` -> `5.0.0+test9`
While this is technically optional (the sysext would then *replace*
(temporarily) the installed version as long as the sysext is active), just
like with `.deb` files, not doing this causes `cargo` to consider the crate
to be unchanged and it will not rebuild its code.
2. here: `$ make ${crate}-sysext` (rebuilds `extensions/${crate}.raw`)
3. as root: `# systemd-sysext refresh` (activates current extensions images)
4. in the other project: `$ cargo update && cargo build`
In the last step, cargo sees that there's a newer version of the crate available
and use that.

View File

@ -1,79 +0,0 @@
Local cargo config
==================
This repository ships with a ``.cargo/config`` that replaces the crates.io
registry with packaged crates located in ``/usr/share/cargo/registry``.
A similar config is also applied building with dh_cargo. Cargo.lock needs to be
deleted when switching between packaged crates and crates.io, since the
checksums are not compatible.
To reference new dependencies (or updated versions) that are not yet packaged,
the dependency needs to point directly to a path or git source.
Steps for Releases
==================
- Run ./bump.sh <CRATE> [patch|minor|major|<VERSION>]
-- Fill out changelog
-- Confirm bump commit
- Build packages with `make deb`.
-- Don't forget to commit updated d/control!
Adding Crates
=============
1) At the top level:
- Generate the crate: ``cargo new --lib the-name``
- Sort the crate into ``Cargo.toml``'s ``workspace.members``
2) In the new crate's ``Cargo.toml``:
- In ``[package]`` set:
authors.workspace = true
license.workspace = true
edition.workspace = true
exclude.workspace = true
- Add a meaningful ``description``
- Copy ``debian/copyright`` and ``debian/debcargo.toml`` from another subcrate.
Adding a new Dependency
=======================
1) At the top level:
- Add it to ``[workspace.dependencies]`` specifying the version and any
features that should be enabled throughout the workspace
2) In each member's ``Cargo.toml``:
- Add it to the desired dependencies section with ``workspace = true`` and no
version specified.
- If this member requires additional features, add only the extra features to
the member dependency.
Updating a Dependency's Version
===============================
1) At the top level:
- Bump the version in ``[workspace.dependencies]`` as desired.
- Check for deprecations or breakage throughout the workspace.
Notes on Workspace Inheritance
==============================
Common metadata (like authors, license, ..) are inherited throughout the
workspace. If new fields are added that are identical for all crates, they
should be defined in the top-level ``Cargo.toml`` file's
``[workspace.package]`` section, and inherited in all members explicitly by
setting ``FIELD.workspace = true`` in the member's ``[package]`` section.
Dependency information is also inherited throughout the workspace, allowing a
single dependency specification in the top-level Cargo.toml file to be used by
all members.
Some restrictions apply:
- features can only be added in members, never removed (this includes
``default_features = false``!)
- the base feature set at the workspace level should be the minimum (possibly
empty!) set required by all members
- workspace dependency specifications cannot include ``optional``
- if needed, the ``optional`` flag needs to be set at the member level when
using a workspace dependency

View File

@ -7,21 +7,30 @@ export RUSTC=/usr/bin/rustc
CRATE=$1
BUILDCMD=${BUILDCMD:-"dpkg-buildpackage -b -uc -us"}
BUILDDIR="${BUILDDIR:-"build"}"
mkdir -p build
echo system >build/rust-toolchain
rm -rf "build/${CRATE}"
mkdir -p "${BUILDDIR}"
echo system >"${BUILDDIR}"/rust-toolchain
rm -rf ""${BUILDDIR}"/${CRATE}"
CONTROL="$PWD/${CRATE}/debian/control"
if [ -e "$CONTROL" ]; then
# check but only warn, debcargo fails anyway if crates are missing
dpkg-checkbuilddeps $PWD/${CRATE}/debian/control || true
rm -f "$PWD/${CRATE}/debian/control"
[ "x$NOCONTROL" = 'x' ] && rm -f "$PWD/${CRATE}/debian/control"
fi
debcargo package --config "$PWD/${CRATE}/debian/debcargo.toml" --changelog-ready --no-overlay-write-back --directory "$PWD/build/${CRATE}" "${CRATE}" "$(dpkg-parsechangelog -l "${CRATE}/debian/changelog" -SVersion | sed -e 's/-.*//')"
cd "build/${CRATE}"
debcargo package \
--config "$PWD/${CRATE}/debian/debcargo.toml" \
--changelog-ready \
--no-overlay-write-back \
--directory "$PWD/"${BUILDDIR}"/${CRATE}" \
"${CRATE}" \
"$(dpkg-parsechangelog -l "${CRATE}/debian/changelog" -SVersion | sed -e 's/-.*//')"
cd ""${BUILDDIR}"/${CRATE}"
rm -f debian/source/format.debcargo.hint
${BUILDCMD}
cp debian/control "$CONTROL"
[ "x$NOCONTROL" = "x" ] && cp debian/control "$CONTROL"

26
pbs-api-types/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "pbs-api-types"
version = "0.2.0"
license.workspace = true
authors.workspace = true
edition.workspace = true
description = "API types for Proxmox Backup Server"
exclude.workspace = true
[dependencies]
anyhow.workspace = true
const_format.workspace = true
hex.workspace = true
percent-encoding.workspace = true
regex.workspace = true
serde.workspace = true
serde_plain.workspace = true
proxmox-auth-api = { workspace = true, features = [ "api-types" ] }
proxmox-apt-api-types.workspace = true
proxmox-human-byte.workspace = true
proxmox-lang.workspace=true
proxmox-schema = { workspace = true, features = [ "api-macro" ] }
proxmox-serde.workspace = true
proxmox-time.workspace = true
proxmox-uuid = { workspace = true, features = [ "serde" ] }

View File

@ -0,0 +1,7 @@
rust-pbs-api-types (0.2.0) bookworm; urgency=medium
* imported from proxmox-backup repository
-- Proxmox Support Team <support@proxmox.com> Wed, 22 Jan 2025 09:40:51 +0100

View File

@ -0,0 +1,68 @@
Source: rust-pbs-api-types
Section: rust
Priority: optional
Build-Depends: debhelper-compat (= 13),
dh-sequence-cargo,
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-const-format-0.2+default-dev <!nocheck>,
librust-hex-0.4+default-dev <!nocheck>,
librust-percent-encoding-2+default-dev (>= 2.1-~~) <!nocheck>,
librust-proxmox-apt-api-types-1+default-dev (>= 1.0.2-~~) <!nocheck>,
librust-proxmox-auth-api-0.4+api-types-dev <!nocheck>,
librust-proxmox-auth-api-0.4+default-dev <!nocheck>,
librust-proxmox-human-byte-0.1+default-dev <!nocheck>,
librust-proxmox-lang-1+default-dev (>= 1.3-~~) <!nocheck>,
librust-proxmox-schema-4+api-macro-dev <!nocheck>,
librust-proxmox-schema-4+default-dev <!nocheck>,
librust-proxmox-serde-0.1+default-dev (>= 0.1.1-~~) <!nocheck>,
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~) <!nocheck>,
librust-proxmox-time-2+default-dev <!nocheck>,
librust-proxmox-uuid-1+default-dev (>= 1.0.1-~~) <!nocheck>,
librust-proxmox-uuid-1+serde-dev (>= 1.0.1-~~) <!nocheck>,
librust-regex-1+default-dev (>= 1.5-~~) <!nocheck>,
librust-serde-1+default-dev <!nocheck>,
librust-serde-plain-1+default-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.7.0
Vcs-Git: git://git.proxmox.com/git/proxmox.git
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
X-Cargo-Crate: pbs-api-types
Rules-Requires-Root: no
Package: librust-pbs-api-types-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
librust-const-format-0.2+default-dev,
librust-hex-0.4+default-dev,
librust-percent-encoding-2+default-dev (>= 2.1-~~),
librust-proxmox-apt-api-types-1+default-dev (>= 1.0.2-~~),
librust-proxmox-auth-api-0.4+api-types-dev,
librust-proxmox-auth-api-0.4+default-dev,
librust-proxmox-human-byte-0.1+default-dev,
librust-proxmox-lang-1+default-dev (>= 1.3-~~),
librust-proxmox-schema-4+api-macro-dev,
librust-proxmox-schema-4+default-dev,
librust-proxmox-serde-0.1+default-dev (>= 0.1.1-~~),
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~),
librust-proxmox-time-2+default-dev,
librust-proxmox-uuid-1+default-dev (>= 1.0.1-~~),
librust-proxmox-uuid-1+serde-dev (>= 1.0.1-~~),
librust-regex-1+default-dev (>= 1.5-~~),
librust-serde-1+default-dev,
librust-serde-plain-1+default-dev
Provides:
librust-pbs-api-types+default-dev (= ${binary:Version}),
librust-pbs-api-types-0-dev (= ${binary:Version}),
librust-pbs-api-types-0+default-dev (= ${binary:Version}),
librust-pbs-api-types-0.2-dev (= ${binary:Version}),
librust-pbs-api-types-0.2+default-dev (= ${binary:Version}),
librust-pbs-api-types-0.2.0-dev (= ${binary:Version}),
librust-pbs-api-types-0.2.0+default-dev (= ${binary:Version})
Description: API types for Proxmox Backup Server - Rust source code
Source code for Debianized Rust crate "pbs-api-types"

View File

@ -0,0 +1,18 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files:
*
Copyright: 2019 - 2024 Proxmox Server Solutions GmbH <support@proxmox.com>
License: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -0,0 +1,7 @@
overlay = "."
crate_src_path = ".."
maintainer = "Proxmox Support Team <support@proxmox.com>"
[source]
vcs_git = "git://git.proxmox.com/git/proxmox.git"
vcs_browser = "https://git.proxmox.com/?p=proxmox.git"

View File

@ -0,0 +1 @@
3.0 (native)

332
pbs-api-types/src/acl.rs Normal file
View File

@ -0,0 +1,332 @@
use std::str::FromStr;
use const_format::concatcp;
use serde::de::{value, IntoDeserializer};
use serde::{Deserialize, Serialize};
use proxmox_lang::constnamedbitmap;
use proxmox_schema::{
api, const_regex, ApiStringFormat, BooleanSchema, EnumEntry, Schema, StringSchema,
};
use crate::PROXMOX_SAFE_ID_REGEX_STR;
const_regex! {
pub ACL_PATH_REGEX = concatcp!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR, ")+", r")$");
}
// define Privilege bitfield
constnamedbitmap! {
/// Contains a list of privilege name to privilege value mappings.
///
/// The names are used when displaying/persisting privileges anywhere, the values are used to
/// allow easy matching of privileges as bitflags.
PRIVILEGES: u64 => {
/// Sys.Audit allows knowing about the system and its status
PRIV_SYS_AUDIT("Sys.Audit");
/// Sys.Modify allows modifying system-level configuration
PRIV_SYS_MODIFY("Sys.Modify");
/// Sys.Modify allows to poweroff/reboot/.. the system
PRIV_SYS_POWER_MANAGEMENT("Sys.PowerManagement");
/// Datastore.Audit allows knowing about a datastore,
/// including reading the configuration entry and listing its contents
PRIV_DATASTORE_AUDIT("Datastore.Audit");
/// Datastore.Allocate allows creating or deleting datastores
PRIV_DATASTORE_ALLOCATE("Datastore.Allocate");
/// Datastore.Modify allows modifying a datastore and its contents
PRIV_DATASTORE_MODIFY("Datastore.Modify");
/// Datastore.Read allows reading arbitrary backup contents
PRIV_DATASTORE_READ("Datastore.Read");
/// Allows verifying a datastore
PRIV_DATASTORE_VERIFY("Datastore.Verify");
/// Datastore.Backup allows Datastore.Read|Verify and creating new snapshots,
/// but also requires backup ownership
PRIV_DATASTORE_BACKUP("Datastore.Backup");
/// Datastore.Prune allows deleting snapshots,
/// but also requires backup ownership
PRIV_DATASTORE_PRUNE("Datastore.Prune");
/// Permissions.Modify allows modifying ACLs
PRIV_PERMISSIONS_MODIFY("Permissions.Modify");
/// Remote.Audit allows reading remote.cfg and sync.cfg entries
PRIV_REMOTE_AUDIT("Remote.Audit");
/// Remote.Modify allows modifying remote.cfg
PRIV_REMOTE_MODIFY("Remote.Modify");
/// Remote.Read allows reading data from a configured `Remote`
PRIV_REMOTE_READ("Remote.Read");
/// Remote.DatastoreBackup allows creating new snapshots on remote datastores
PRIV_REMOTE_DATASTORE_BACKUP("Remote.DatastoreBackup");
/// Remote.DatastoreModify allows to modify remote datastores
PRIV_REMOTE_DATASTORE_MODIFY("Remote.DatastoreModify");
/// Remote.DatastorePrune allows deleting snapshots on remote datastores
PRIV_REMOTE_DATASTORE_PRUNE("Remote.DatastorePrune");
/// Sys.Console allows access to the system's console
PRIV_SYS_CONSOLE("Sys.Console");
/// Tape.Audit allows reading tape backup configuration and status
PRIV_TAPE_AUDIT("Tape.Audit");
/// Tape.Modify allows modifying tape backup configuration
PRIV_TAPE_MODIFY("Tape.Modify");
/// Tape.Write allows writing tape media
PRIV_TAPE_WRITE("Tape.Write");
/// Tape.Read allows reading tape backup configuration and media contents
PRIV_TAPE_READ("Tape.Read");
/// Realm.Allocate allows viewing, creating, modifying and deleting realms
PRIV_REALM_ALLOCATE("Realm.Allocate");
}
}
pub fn privs_to_priv_names(privs: u64) -> Vec<&'static str> {
PRIVILEGES
.iter()
.fold(Vec::new(), |mut priv_names, (name, value)| {
if value & privs != 0 {
priv_names.push(name);
}
priv_names
})
}
/// Admin always has all privileges. It can do everything except a few actions
/// which are limited to the 'root@pam` superuser
pub const ROLE_ADMIN: u64 = u64::MAX;
/// NoAccess can be used to remove privileges from specific (sub-)paths
pub const ROLE_NO_ACCESS: u64 = 0;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Audit can view configuration and status information, but not modify it.
pub const ROLE_AUDIT: u64 = 0
| PRIV_SYS_AUDIT
| PRIV_DATASTORE_AUDIT;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.Admin can do anything on the datastore.
pub const ROLE_DATASTORE_ADMIN: u64 = 0
| PRIV_DATASTORE_AUDIT
| PRIV_DATASTORE_MODIFY
| PRIV_DATASTORE_READ
| PRIV_DATASTORE_VERIFY
| PRIV_DATASTORE_BACKUP
| PRIV_DATASTORE_PRUNE;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.Reader can read/verify datastore content and do restore
pub const ROLE_DATASTORE_READER: u64 = 0
| PRIV_DATASTORE_AUDIT
| PRIV_DATASTORE_VERIFY
| PRIV_DATASTORE_READ;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.Backup can do backup and restore, but no prune.
pub const ROLE_DATASTORE_BACKUP: u64 = 0
| PRIV_DATASTORE_BACKUP;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.PowerUser can do backup, restore, and prune.
pub const ROLE_DATASTORE_POWERUSER: u64 = 0
| PRIV_DATASTORE_PRUNE
| PRIV_DATASTORE_BACKUP;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Datastore.Audit can audit the datastore.
pub const ROLE_DATASTORE_AUDIT: u64 = 0
| PRIV_DATASTORE_AUDIT;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Remote.Audit can audit the remote
pub const ROLE_REMOTE_AUDIT: u64 = 0
| PRIV_REMOTE_AUDIT;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Remote.Admin can do anything on the remote.
pub const ROLE_REMOTE_ADMIN: u64 = 0
| PRIV_REMOTE_AUDIT
| PRIV_REMOTE_MODIFY
| PRIV_REMOTE_READ;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Remote.SyncOperator can do read and prune on the remote.
pub const ROLE_REMOTE_SYNC_OPERATOR: u64 = 0
| PRIV_REMOTE_AUDIT
| PRIV_REMOTE_READ;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Remote.SyncPushOperator can read and push snapshots to the remote.
pub const ROLE_REMOTE_SYNC_PUSH_OPERATOR: u64 = 0
| PRIV_REMOTE_AUDIT
| PRIV_REMOTE_DATASTORE_BACKUP;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Remote.DatastorePowerUser can read and push snapshots to the remote, and prune owned snapshots
/// and groups but not create or remove namespaces.
pub const ROLE_REMOTE_DATASTORE_POWERUSER: u64 = 0
| PRIV_REMOTE_AUDIT
| PRIV_REMOTE_DATASTORE_BACKUP
| PRIV_REMOTE_DATASTORE_PRUNE;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Remote.DatastoreAdmin can read and push snapshots to the remote, prune owned snapshots
/// and groups, as well as create or remove namespaces.
pub const ROLE_REMOTE_DATASTORE_ADMIN: u64 = 0
| PRIV_REMOTE_AUDIT
| PRIV_REMOTE_DATASTORE_BACKUP
| PRIV_REMOTE_DATASTORE_MODIFY
| PRIV_REMOTE_DATASTORE_PRUNE;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Tape.Audit can audit the tape backup configuration and media content
pub const ROLE_TAPE_AUDIT: u64 = 0
| PRIV_TAPE_AUDIT;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Tape.Admin can do anything on the tape backup
pub const ROLE_TAPE_ADMIN: u64 = 0
| PRIV_TAPE_AUDIT
| PRIV_TAPE_MODIFY
| PRIV_TAPE_READ
| PRIV_TAPE_WRITE;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Tape.Operator can do tape backup and restore (but no configuration changes)
pub const ROLE_TAPE_OPERATOR: u64 = 0
| PRIV_TAPE_AUDIT
| PRIV_TAPE_READ
| PRIV_TAPE_WRITE;
#[rustfmt::skip]
#[allow(clippy::identity_op)]
/// Tape.Reader can do read and inspect tape content
pub const ROLE_TAPE_READER: u64 = 0
| PRIV_TAPE_AUDIT
| PRIV_TAPE_READ;
/// NoAccess can be used to remove privileges from specific (sub-)paths
pub const ROLE_NAME_NO_ACCESS: &str = "NoAccess";
#[api(
type_text: "<role>",
)]
#[repr(u64)]
#[derive(Serialize, Deserialize)]
/// Enum representing roles via their [PRIVILEGES] combination.
///
/// Since privileges are implemented as bitflags, each unique combination of privileges maps to a
/// single, unique `u64` value that is used in this enum definition.
pub enum Role {
/// Administrator
Admin = ROLE_ADMIN,
/// Auditor
Audit = ROLE_AUDIT,
/// Disable Access
NoAccess = ROLE_NO_ACCESS,
/// Datastore Administrator
DatastoreAdmin = ROLE_DATASTORE_ADMIN,
/// Datastore Reader (inspect datastore content and do restores)
DatastoreReader = ROLE_DATASTORE_READER,
/// Datastore Backup (backup and restore owned backups)
DatastoreBackup = ROLE_DATASTORE_BACKUP,
/// Datastore PowerUser (backup, restore and prune owned backup)
DatastorePowerUser = ROLE_DATASTORE_POWERUSER,
/// Datastore Auditor
DatastoreAudit = ROLE_DATASTORE_AUDIT,
/// Remote Auditor
RemoteAudit = ROLE_REMOTE_AUDIT,
/// Remote Administrator
RemoteAdmin = ROLE_REMOTE_ADMIN,
/// Synchronization Operator
RemoteSyncOperator = ROLE_REMOTE_SYNC_OPERATOR,
/// Synchronisation Operator (push direction)
RemoteSyncPushOperator = ROLE_REMOTE_SYNC_PUSH_OPERATOR,
/// Remote Datastore Prune
RemoteDatastorePowerUser = ROLE_REMOTE_DATASTORE_POWERUSER,
/// Remote Datastore Admin
RemoteDatastoreAdmin = ROLE_REMOTE_DATASTORE_ADMIN,
/// Tape Auditor
TapeAudit = ROLE_TAPE_AUDIT,
/// Tape Administrator
TapeAdmin = ROLE_TAPE_ADMIN,
/// Tape Operator
TapeOperator = ROLE_TAPE_OPERATOR,
/// Tape Reader
TapeReader = ROLE_TAPE_READER,
}
impl FromStr for Role {
type Err = value::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::deserialize(s.into_deserializer())
}
}
pub const ACL_PATH_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&ACL_PATH_REGEX);
pub const ACL_PATH_SCHEMA: Schema = StringSchema::new("Access control path.")
.format(&ACL_PATH_FORMAT)
.min_length(1)
.max_length(128)
.schema();
pub const ACL_PROPAGATE_SCHEMA: Schema =
BooleanSchema::new("Allow to propagate (inherit) permissions.")
.default(true)
.schema();
pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new("Type of 'ugid' property.")
.format(&ApiStringFormat::Enum(&[
EnumEntry::new("user", "User"),
EnumEntry::new("group", "Group"),
]))
.schema();
#[api(
properties: {
propagate: {
schema: ACL_PROPAGATE_SCHEMA,
},
path: {
schema: ACL_PATH_SCHEMA,
},
ugid_type: {
schema: ACL_UGID_TYPE_SCHEMA,
},
ugid: {
type: String,
description: "User or Group ID.",
},
roleid: {
type: Role,
}
}
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
/// ACL list entry.
pub struct AclListItem {
pub path: String,
pub ugid: String,
pub ugid_type: String,
pub propagate: bool,
pub roleid: String,
}

98
pbs-api-types/src/ad.rs Normal file
View File

@ -0,0 +1,98 @@
use serde::{Deserialize, Serialize};
use proxmox_schema::{api, Updater};
use super::{
LdapMode, LDAP_DOMAIN_SCHEMA, REALM_ID_SCHEMA, SINGLE_LINE_COMMENT_SCHEMA,
SYNC_ATTRIBUTES_SCHEMA, SYNC_DEFAULTS_STRING_SCHEMA, USER_CLASSES_SCHEMA,
};
#[api(
properties: {
"realm": {
schema: REALM_ID_SCHEMA,
},
"comment": {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
"verify": {
optional: true,
default: false,
},
"sync-defaults-options": {
schema: SYNC_DEFAULTS_STRING_SCHEMA,
optional: true,
},
"sync-attributes": {
schema: SYNC_ATTRIBUTES_SCHEMA,
optional: true,
},
"user-classes" : {
optional: true,
schema: USER_CLASSES_SCHEMA,
},
"base-dn" : {
schema: LDAP_DOMAIN_SCHEMA,
optional: true,
},
"bind-dn" : {
schema: LDAP_DOMAIN_SCHEMA,
optional: true,
}
},
)]
#[derive(Serialize, Deserialize, Updater, Clone)]
#[serde(rename_all = "kebab-case")]
/// AD realm configuration properties.
pub struct AdRealmConfig {
#[updater(skip)]
pub realm: String,
/// AD server address
pub server1: String,
/// Fallback AD server address
#[serde(skip_serializing_if = "Option::is_none")]
pub server2: Option<String>,
/// AD server Port
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
/// Base domain name. Users are searched under this domain using a `subtree search`.
/// Expected to be set only internally to `defaultNamingContext` of the AD server, but can be
/// overridden if the need arises.
#[serde(skip_serializing_if = "Option::is_none")]
pub base_dn: Option<String>,
/// Comment
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
/// Connection security
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<LdapMode>,
/// Verify server certificate
#[serde(skip_serializing_if = "Option::is_none")]
pub verify: Option<bool>,
/// CA certificate to use for the server. The path can point to
/// either a file, or a directory. If it points to a file,
/// the PEM-formatted X.509 certificate stored at the path
/// will be added as a trusted certificate.
/// If the path points to a directory,
/// the directory replaces the system's default certificate
/// store at `/etc/ssl/certs` - Every file in the directory
/// will be loaded as a trusted certificate.
#[serde(skip_serializing_if = "Option::is_none")]
pub capath: Option<String>,
/// Bind domain to use for looking up users
#[serde(skip_serializing_if = "Option::is_none")]
pub bind_dn: Option<String>,
/// Custom LDAP search filter for user sync
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<String>,
/// Default options for AD sync
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_defaults_options: Option<String>,
/// List of LDAP attributes to sync from AD to user config
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_attributes: Option<String>,
/// User ``objectClass`` classes to sync
#[serde(skip_serializing_if = "Option::is_none")]
pub user_classes: Option<String>,
}

View File

@ -0,0 +1,95 @@
use std::fmt::{self, Display};
use anyhow::Error;
use serde::{Deserialize, Serialize};
use proxmox_schema::api;
#[api(default: "encrypt")]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
/// Defines whether data is encrypted (using an AEAD cipher), only signed, or neither.
pub enum CryptMode {
/// Don't encrypt.
None,
/// Encrypt.
Encrypt,
/// Only sign.
SignOnly,
}
#[derive(Debug, Eq, PartialEq, Hash, Clone, Deserialize, Serialize)]
#[serde(transparent)]
/// 32-byte fingerprint, usually calculated with SHA256.
pub struct Fingerprint {
#[serde(with = "bytes_as_fingerprint")]
bytes: [u8; 32],
}
impl Fingerprint {
pub fn new(bytes: [u8; 32]) -> Self {
Self { bytes }
}
pub fn bytes(&self) -> &[u8; 32] {
&self.bytes
}
pub fn signature(&self) -> String {
as_fingerprint(&self.bytes)
}
}
/// Display as short key ID
impl Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", as_fingerprint(&self.bytes[0..8]))
}
}
impl std::str::FromStr for Fingerprint {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let mut tmp = s.to_string();
tmp.retain(|c| c != ':');
let mut bytes = [0u8; 32];
hex::decode_to_slice(&tmp, &mut bytes)?;
Ok(Fingerprint::new(bytes))
}
}
fn as_fingerprint(bytes: &[u8]) -> String {
hex::encode(bytes)
.as_bytes()
.chunks(2)
.map(|v| unsafe { std::str::from_utf8_unchecked(v) }) // it's a hex string
.collect::<Vec<&str>>()
.join(":")
}
pub mod bytes_as_fingerprint {
use std::mem::MaybeUninit;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(bytes: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = super::as_fingerprint(bytes);
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
where
D: Deserializer<'de>,
{
// TODO: more efficiently implement with a Visitor implementing visit_str using split() and
// hex::decode by-byte
let mut s = String::deserialize(deserializer)?;
s.retain(|c| c != ':');
let mut out = MaybeUninit::<[u8; 32]>::uninit();
hex::decode_to_slice(s.as_bytes(), unsafe { &mut (*out.as_mut_ptr())[..] })
.map_err(serde::de::Error::custom)?;
Ok(unsafe { out.assume_init() })
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
use serde::{Deserialize, Serialize};
use proxmox_schema::api;
#[api]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// General status information about a running VM file-restore daemon
pub struct RestoreDaemonStatus {
/// VM uptime in seconds
pub uptime: i64,
/// time left until auto-shutdown, keep in mind that this is useless when 'keep-timeout' is
/// not set, as then the status call will have reset the timer before returning the value
pub timeout: i64,
}
#[api]
#[derive(Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
/// The desired format of the result.
pub enum FileRestoreFormat {
/// Plain file (only works for single files)
Plain,
/// PXAR archive
Pxar,
/// ZIP archive
Zip,
/// TAR archive
Tar,
}

844
pbs-api-types/src/jobs.rs Normal file
View File

@ -0,0 +1,844 @@
use std::str::FromStr;
use anyhow::bail;
use const_format::concatcp;
use regex::Regex;
use serde::{Deserialize, Serialize};
use proxmox_schema::*;
use crate::{
Authid, BackupNamespace, BackupType, NotificationMode, RateLimitConfig, Userid,
BACKUP_GROUP_SCHEMA, BACKUP_NAMESPACE_SCHEMA, BACKUP_NS_RE, DATASTORE_SCHEMA,
DRIVE_NAME_SCHEMA, MEDIA_POOL_NAME_SCHEMA, NS_MAX_DEPTH_REDUCED_SCHEMA, PROXMOX_SAFE_ID_FORMAT,
PROXMOX_SAFE_ID_REGEX_STR, REMOTE_ID_SCHEMA, SINGLE_LINE_COMMENT_SCHEMA,
};
const_regex! {
/// Regex for verification jobs 'DATASTORE:ACTUAL_JOB_ID'
pub VERIFICATION_JOB_WORKER_ID_REGEX = concatcp!(r"^(", PROXMOX_SAFE_ID_REGEX_STR, r"):");
/// Regex for sync jobs '(REMOTE|\-):REMOTE_DATASTORE:LOCAL_DATASTORE:(?:LOCAL_NS_ANCHOR:)ACTUAL_JOB_ID'
pub SYNC_JOB_WORKER_ID_REGEX = concatcp!(r"^(", PROXMOX_SAFE_ID_REGEX_STR, r"|\-):(", PROXMOX_SAFE_ID_REGEX_STR, r"):(", PROXMOX_SAFE_ID_REGEX_STR, r")(?::(", BACKUP_NS_RE, r"))?:");
}
pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(3)
.max_length(32)
.schema();
pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new("Run sync job at specified schedule.")
.format(&ApiStringFormat::VerifyFn(
proxmox_time::verify_calendar_event,
))
.type_text("<calendar-event>")
.schema();
pub const GC_SCHEDULE_SCHEMA: Schema =
StringSchema::new("Run garbage collection job at specified schedule.")
.format(&ApiStringFormat::VerifyFn(
proxmox_time::verify_calendar_event,
))
.type_text("<calendar-event>")
.schema();
pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new("Run prune job at specified schedule.")
.format(&ApiStringFormat::VerifyFn(
proxmox_time::verify_calendar_event,
))
.type_text("<calendar-event>")
.schema();
pub const VERIFICATION_SCHEDULE_SCHEMA: Schema =
StringSchema::new("Run verify job at specified schedule.")
.format(&ApiStringFormat::VerifyFn(
proxmox_time::verify_calendar_event,
))
.type_text("<calendar-event>")
.schema();
pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
"Delete vanished backups. This remove the local copy if the remote backup was deleted.",
)
.default(false)
.schema();
#[api(
properties: {
"next-run": {
description: "Estimated time of the next run (UNIX epoch).",
optional: true,
type: Integer,
},
"last-run-state": {
description: "Result of the last run.",
optional: true,
type: String,
},
"last-run-upid": {
description: "Task UPID of the last run.",
optional: true,
type: String,
},
"last-run-endtime": {
description: "Endtime of the last run.",
optional: true,
type: Integer,
},
}
)]
#[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Job Scheduling Status
pub struct JobScheduleStatus {
#[serde(skip_serializing_if = "Option::is_none")]
pub next_run: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_run_state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_run_upid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_run_endtime: Option<i64>,
}
#[api()]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// When do we send notifications
pub enum Notify {
/// Never send notification
Never,
/// Send notifications for failed and successful jobs
Always,
/// Send notifications for failed jobs only
Error,
}
#[api(
properties: {
gc: {
type: Notify,
optional: true,
},
verify: {
type: Notify,
optional: true,
},
sync: {
type: Notify,
optional: true,
},
prune: {
type: Notify,
optional: true,
},
},
)]
#[derive(Debug, Serialize, Deserialize)]
/// Datastore notify settings
pub struct DatastoreNotify {
/// Garbage collection settings
#[serde(skip_serializing_if = "Option::is_none")]
pub gc: Option<Notify>,
/// Verify job setting
#[serde(skip_serializing_if = "Option::is_none")]
pub verify: Option<Notify>,
/// Sync job setting
#[serde(skip_serializing_if = "Option::is_none")]
pub sync: Option<Notify>,
/// Prune job setting
#[serde(skip_serializing_if = "Option::is_none")]
pub prune: Option<Notify>,
}
pub const DATASTORE_NOTIFY_STRING_SCHEMA: Schema = StringSchema::new(
"Datastore notification setting, enum can be one of 'always', 'never', or 'error'.",
)
.format(&ApiStringFormat::PropertyString(
&DatastoreNotify::API_SCHEMA,
))
.schema();
pub const IGNORE_VERIFIED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
"Do not verify backups that are already verified if their verification is not outdated.",
)
.default(true)
.schema();
pub const VERIFICATION_OUTDATED_AFTER_SCHEMA: Schema =
IntegerSchema::new("Days after that a verification becomes outdated. (0 is deprecated)'")
.minimum(0)
.schema();
#[api(
properties: {
id: {
schema: JOB_ID_SCHEMA,
},
store: {
schema: DATASTORE_SCHEMA,
},
"ignore-verified": {
optional: true,
schema: IGNORE_VERIFIED_BACKUPS_SCHEMA,
},
"outdated-after": {
optional: true,
schema: VERIFICATION_OUTDATED_AFTER_SCHEMA,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
schedule: {
optional: true,
schema: VERIFICATION_SCHEDULE_SCHEMA,
},
ns: {
optional: true,
schema: BACKUP_NAMESPACE_SCHEMA,
},
"max-depth": {
optional: true,
schema: crate::NS_MAX_DEPTH_SCHEMA,
},
}
)]
#[derive(Serialize, Deserialize, Updater, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Verification Job
pub struct VerificationJobConfig {
/// unique ID to address this job
#[updater(skip)]
pub id: String,
/// the datastore ID this verification job affects
pub store: String,
#[serde(skip_serializing_if = "Option::is_none")]
/// if not set to false, check the age of the last snapshot verification to filter
/// out recent ones, depending on 'outdated_after' configuration.
pub ignore_verified: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
/// Reverify snapshots after X days, never if 0. Ignored if 'ignore_verified' is false.
pub outdated_after: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// when to schedule this job in calendar event notation
pub schedule: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
/// on which backup namespace to run the verification recursively
pub ns: Option<BackupNamespace>,
#[serde(skip_serializing_if = "Option::is_none", default)]
/// how deep the verify should go from the `ns` level downwards. Passing 0 verifies only the
/// snapshots on the same level as the passed `ns`, or the datastore root if none.
pub max_depth: Option<usize>,
}
impl VerificationJobConfig {
pub fn acl_path(&self) -> Vec<&str> {
match self.ns.as_ref() {
Some(ns) => ns.acl_path(&self.store),
None => vec!["datastore", &self.store],
}
}
}
#[api(
properties: {
config: {
type: VerificationJobConfig,
},
status: {
type: JobScheduleStatus,
},
},
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Status of Verification Job
pub struct VerificationJobStatus {
#[serde(flatten)]
pub config: VerificationJobConfig,
#[serde(flatten)]
pub status: JobScheduleStatus,
}
#[api(
properties: {
store: {
schema: DATASTORE_SCHEMA,
},
pool: {
schema: MEDIA_POOL_NAME_SCHEMA,
},
drive: {
schema: DRIVE_NAME_SCHEMA,
},
"eject-media": {
description: "Eject media upon job completion.",
type: bool,
optional: true,
},
"export-media-set": {
description: "Export media set upon job completion.",
type: bool,
optional: true,
},
"latest-only": {
description: "Backup latest snapshots only.",
type: bool,
optional: true,
},
"notify-user": {
optional: true,
type: Userid,
},
"group-filter": {
schema: GROUP_FILTER_LIST_SCHEMA,
optional: true,
},
ns: {
type: BackupNamespace,
optional: true,
},
"max-depth": {
schema: crate::NS_MAX_DEPTH_SCHEMA,
optional: true,
},
}
)]
#[derive(Serialize, Deserialize, Clone, Updater, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Tape Backup Job Setup
pub struct TapeBackupJobSetup {
pub store: String,
pub pool: String,
pub drive: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub eject_media: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub export_media_set: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub latest_only: Option<bool>,
/// Send job email notification to this user
#[serde(skip_serializing_if = "Option::is_none")]
pub notify_user: Option<Userid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notification_mode: Option<NotificationMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group_filter: Option<Vec<GroupFilter>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub ns: Option<BackupNamespace>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub max_depth: Option<usize>,
}
#[api(
properties: {
id: {
schema: JOB_ID_SCHEMA,
},
setup: {
type: TapeBackupJobSetup,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
schedule: {
optional: true,
schema: SYNC_SCHEDULE_SCHEMA,
},
}
)]
#[derive(Serialize, Deserialize, Clone, Updater, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Tape Backup Job
pub struct TapeBackupJobConfig {
#[updater(skip)]
pub id: String,
#[serde(flatten)]
pub setup: TapeBackupJobSetup,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schedule: Option<String>,
}
#[api(
properties: {
config: {
type: TapeBackupJobConfig,
},
status: {
type: JobScheduleStatus,
},
},
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Status of Tape Backup Job
pub struct TapeBackupJobStatus {
#[serde(flatten)]
pub config: TapeBackupJobConfig,
#[serde(flatten)]
pub status: JobScheduleStatus,
/// Next tape used (best guess)
#[serde(skip_serializing_if = "Option::is_none")]
pub next_media_label: Option<String>,
}
#[derive(Clone, Debug)]
/// Filter for matching `BackupGroup`s, for use with `BackupGroup::filter`.
pub enum FilterType {
/// BackupGroup type - either `vm`, `ct`, or `host`.
BackupType(BackupType),
/// Full identifier of BackupGroup, including type
Group(String),
/// A regular expression matched against the full identifier of the BackupGroup
Regex(Regex),
}
impl PartialEq for FilterType {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::BackupType(a), Self::BackupType(b)) => a == b,
(Self::Group(a), Self::Group(b)) => a == b,
(Self::Regex(a), Self::Regex(b)) => a.as_str() == b.as_str(),
_ => false,
}
}
}
impl std::str::FromStr for FilterType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s.split_once(':') {
Some(("group", value)) => BACKUP_GROUP_SCHEMA.parse_simple_value(value).map(|_| FilterType::Group(value.to_string()))?,
Some(("type", value)) => FilterType::BackupType(value.parse()?),
Some(("regex", value)) => FilterType::Regex(Regex::new(value)?),
Some((ty, _value)) => bail!("expected 'group', 'type' or 'regex' prefix, got '{}'", ty),
None => bail!("input doesn't match expected format '<group:GROUP||type:<vm|ct|host>|regex:REGEX>'"),
})
}
}
// used for serializing below, caution!
impl std::fmt::Display for FilterType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FilterType::BackupType(backup_type) => write!(f, "type:{}", backup_type),
FilterType::Group(backup_group) => write!(f, "group:{}", backup_group),
FilterType::Regex(regex) => write!(f, "regex:{}", regex.as_str()),
}
}
}
#[derive(Clone, Debug)]
pub struct GroupFilter {
pub is_exclude: bool,
pub filter_type: FilterType,
}
impl PartialEq for GroupFilter {
fn eq(&self, other: &Self) -> bool {
self.filter_type == other.filter_type && self.is_exclude == other.is_exclude
}
}
impl Eq for GroupFilter {}
impl std::str::FromStr for GroupFilter {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (is_exclude, type_str) = match s.split_once(':') {
Some(("include", value)) => (false, value),
Some(("exclude", value)) => (true, value),
_ => (false, s),
};
Ok(GroupFilter {
is_exclude,
filter_type: type_str.parse()?,
})
}
}
// used for serializing below, caution!
impl std::fmt::Display for GroupFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_exclude {
f.write_str("exclude:")?;
}
std::fmt::Display::fmt(&self.filter_type, f)
}
}
proxmox_serde::forward_deserialize_to_from_str!(GroupFilter);
proxmox_serde::forward_serialize_to_display!(GroupFilter);
fn verify_group_filter(input: &str) -> Result<(), anyhow::Error> {
GroupFilter::from_str(input).map(|_| ())
}
pub const GROUP_FILTER_SCHEMA: Schema = StringSchema::new(
"Group filter based on group identifier ('group:GROUP'), group type ('type:<vm|ct|host>'), or regex ('regex:RE'). Can be inverted by prepending 'exclude:'.")
.format(&ApiStringFormat::VerifyFn(verify_group_filter))
.type_text("[<exclude:|include:>]<type:<vm|ct|host>|group:GROUP|regex:RE>")
.schema();
pub const GROUP_FILTER_LIST_SCHEMA: Schema =
ArraySchema::new("List of group filters.", &GROUP_FILTER_SCHEMA).schema();
pub const TRANSFER_LAST_SCHEMA: Schema =
IntegerSchema::new("Limit transfer to last N snapshots (per group), skipping others")
.minimum(1)
.schema();
#[api()]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Direction of the sync job, push or pull
pub enum SyncDirection {
/// Sync direction pull
#[default]
Pull,
/// Sync direction push
Push,
}
impl std::fmt::Display for SyncDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SyncDirection::Pull => f.write_str("pull"),
SyncDirection::Push => f.write_str("push"),
}
}
}
pub const RESYNC_CORRUPT_SCHEMA: Schema =
BooleanSchema::new("If the verification failed for a local snapshot, try to pull it again.")
.schema();
#[api(
properties: {
id: {
schema: JOB_ID_SCHEMA,
},
store: {
schema: DATASTORE_SCHEMA,
},
ns: {
type: BackupNamespace,
optional: true,
},
"owner": {
type: Authid,
optional: true,
},
remote: {
schema: REMOTE_ID_SCHEMA,
optional: true,
},
"remote-store": {
schema: DATASTORE_SCHEMA,
},
"remote-ns": {
type: BackupNamespace,
optional: true,
},
"remove-vanished": {
schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
optional: true,
},
"max-depth": {
schema: NS_MAX_DEPTH_REDUCED_SCHEMA,
optional: true,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
limit: {
type: RateLimitConfig,
},
schedule: {
optional: true,
schema: SYNC_SCHEDULE_SCHEMA,
},
"group-filter": {
schema: GROUP_FILTER_LIST_SCHEMA,
optional: true,
},
"transfer-last": {
schema: TRANSFER_LAST_SCHEMA,
optional: true,
},
"resync-corrupt": {
schema: RESYNC_CORRUPT_SCHEMA,
optional: true,
},
"sync-direction": {
type: SyncDirection,
optional: true,
},
}
)]
#[derive(Serialize, Deserialize, Clone, Updater, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Sync Job
pub struct SyncJobConfig {
#[updater(skip)]
pub id: String,
pub store: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub ns: Option<BackupNamespace>,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner: Option<Authid>,
#[serde(skip_serializing_if = "Option::is_none")]
/// None implies local sync.
pub remote: Option<String>,
pub remote_store: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub remote_ns: Option<BackupNamespace>,
#[serde(skip_serializing_if = "Option::is_none")]
pub remove_vanished: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_depth: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schedule: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group_filter: Option<Vec<GroupFilter>>,
#[serde(flatten)]
pub limit: RateLimitConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub transfer_last: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resync_corrupt: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_direction: Option<SyncDirection>,
}
impl SyncJobConfig {
pub fn acl_path(&self) -> Vec<&str> {
match self.ns.as_ref() {
Some(ns) => ns.acl_path(&self.store),
None => vec!["datastore", &self.store],
}
}
pub fn remote_acl_path(&self) -> Option<Vec<&str>> {
let remote = self.remote.as_ref()?;
match &self.remote_ns {
Some(remote_ns) => Some(remote_ns.remote_acl_path(remote, &self.remote_store)),
None => Some(vec!["remote", remote, &self.remote_store]),
}
}
}
#[api(
properties: {
config: {
type: SyncJobConfig,
},
status: {
type: JobScheduleStatus,
},
},
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Status of Sync Job
pub struct SyncJobStatus {
#[serde(flatten)]
pub config: SyncJobConfig,
#[serde(flatten)]
pub status: JobScheduleStatus,
}
/// These are used separately without `ns`/`max-depth` sometimes in the API, specifically in the API
/// call to prune a specific group, where `max-depth` makes no sense.
#[api(
properties: {
"keep-last": {
schema: crate::PRUNE_SCHEMA_KEEP_LAST,
optional: true,
},
"keep-hourly": {
schema: crate::PRUNE_SCHEMA_KEEP_HOURLY,
optional: true,
},
"keep-daily": {
schema: crate::PRUNE_SCHEMA_KEEP_DAILY,
optional: true,
},
"keep-weekly": {
schema: crate::PRUNE_SCHEMA_KEEP_WEEKLY,
optional: true,
},
"keep-monthly": {
schema: crate::PRUNE_SCHEMA_KEEP_MONTHLY,
optional: true,
},
"keep-yearly": {
schema: crate::PRUNE_SCHEMA_KEEP_YEARLY,
optional: true,
},
}
)]
#[derive(Serialize, Deserialize, Default, Updater, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Common pruning options
pub struct KeepOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub keep_last: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keep_hourly: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keep_daily: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keep_weekly: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keep_monthly: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keep_yearly: Option<u64>,
}
impl KeepOptions {
pub fn keeps_something(&self) -> bool {
self.keep_last.unwrap_or(0)
+ self.keep_hourly.unwrap_or(0)
+ self.keep_daily.unwrap_or(0)
+ self.keep_weekly.unwrap_or(0)
+ self.keep_monthly.unwrap_or(0)
+ self.keep_yearly.unwrap_or(0)
> 0
}
}
#[api(
properties: {
keep: {
type: KeepOptions,
},
ns: {
type: BackupNamespace,
optional: true,
},
"max-depth": {
schema: NS_MAX_DEPTH_REDUCED_SCHEMA,
optional: true,
},
}
)]
#[derive(Serialize, Deserialize, Default, Updater, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Common pruning options
pub struct PruneJobOptions {
#[serde(flatten)]
pub keep: KeepOptions,
/// The (optional) recursion depth
#[serde(skip_serializing_if = "Option::is_none")]
pub max_depth: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ns: Option<BackupNamespace>,
}
impl PruneJobOptions {
pub fn keeps_something(&self) -> bool {
self.keep.keeps_something()
}
pub fn acl_path<'a>(&'a self, store: &'a str) -> Vec<&'a str> {
match &self.ns {
Some(ns) => ns.acl_path(store),
None => vec!["datastore", store],
}
}
}
#[api(
properties: {
disable: {
type: Boolean,
optional: true,
default: false,
},
id: {
schema: JOB_ID_SCHEMA,
},
store: {
schema: DATASTORE_SCHEMA,
},
schedule: {
schema: PRUNE_SCHEDULE_SCHEMA,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
options: {
type: PruneJobOptions,
},
},
)]
#[derive(Deserialize, Serialize, Updater, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Prune configuration.
pub struct PruneJobConfig {
/// unique ID to address this job
#[updater(skip)]
pub id: String,
pub store: String,
/// Disable this job.
#[serde(default, skip_serializing_if = "is_false")]
#[updater(serde(skip_serializing_if = "Option::is_none"))]
pub disable: bool,
pub schedule: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(flatten)]
pub options: PruneJobOptions,
}
impl PruneJobConfig {
pub fn acl_path(&self) -> Vec<&str> {
self.options.acl_path(&self.store)
}
}
fn is_false(b: &bool) -> bool {
!b
}
#[api(
properties: {
config: {
type: PruneJobConfig,
},
status: {
type: JobScheduleStatus,
},
},
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Status of prune job
pub struct PruneJobStatus {
#[serde(flatten)]
pub config: PruneJobConfig,
#[serde(flatten)]
pub status: JobScheduleStatus,
}

View File

@ -0,0 +1,55 @@
use serde::{Deserialize, Serialize};
use proxmox_schema::api;
use crate::CERT_FINGERPRINT_SHA256_SCHEMA;
#[api(default: "scrypt")]
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
/// Key derivation function for password protected encryption keys.
pub enum Kdf {
/// Do not encrypt the key.
None,
/// Encrypt they key with a password using SCrypt.
Scrypt,
/// Encrtypt the Key with a password using PBKDF2
PBKDF2,
}
impl Default for Kdf {
#[inline]
fn default() -> Self {
Kdf::Scrypt
}
}
#[api(
properties: {
kdf: {
type: Kdf,
},
fingerprint: {
schema: CERT_FINGERPRINT_SHA256_SCHEMA,
optional: true,
},
},
)]
#[derive(Deserialize, Serialize)]
/// Encryption Key Information
pub struct KeyInfo {
/// Path to key (if stored in a file)
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
pub kdf: Kdf,
/// Key creation time
pub created: i64,
/// Key modification time
pub modified: i64,
/// Key fingerprint
#[serde(skip_serializing_if = "Option::is_none")]
pub fingerprint: Option<String>,
/// Password hint
#[serde(skip_serializing_if = "Option::is_none")]
pub hint: Option<String>,
}

208
pbs-api-types/src/ldap.rs Normal file
View File

@ -0,0 +1,208 @@
use serde::{Deserialize, Serialize};
use proxmox_schema::{api, ApiStringFormat, ApiType, ArraySchema, Schema, StringSchema, Updater};
use super::{REALM_ID_SCHEMA, SINGLE_LINE_COMMENT_SCHEMA};
#[api()]
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
/// LDAP connection type
pub enum LdapMode {
/// Plaintext LDAP connection
#[serde(rename = "ldap")]
#[default]
Ldap,
/// Secure STARTTLS connection
#[serde(rename = "ldap+starttls")]
StartTls,
/// Secure LDAPS connection
#[serde(rename = "ldaps")]
Ldaps,
}
#[api(
properties: {
"realm": {
schema: REALM_ID_SCHEMA,
},
"comment": {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
"verify": {
optional: true,
default: false,
},
"sync-defaults-options": {
schema: SYNC_DEFAULTS_STRING_SCHEMA,
optional: true,
},
"sync-attributes": {
schema: SYNC_ATTRIBUTES_SCHEMA,
optional: true,
},
"user-classes" : {
optional: true,
schema: USER_CLASSES_SCHEMA,
},
"base-dn" : {
schema: LDAP_DOMAIN_SCHEMA,
},
"bind-dn" : {
schema: LDAP_DOMAIN_SCHEMA,
optional: true,
}
},
)]
#[derive(Serialize, Deserialize, Updater, Clone)]
#[serde(rename_all = "kebab-case")]
/// LDAP configuration properties.
pub struct LdapRealmConfig {
#[updater(skip)]
pub realm: String,
/// LDAP server address
pub server1: String,
/// Fallback LDAP server address
#[serde(skip_serializing_if = "Option::is_none")]
pub server2: Option<String>,
/// Port
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
/// Base domain name. Users are searched under this domain using a `subtree search`.
pub base_dn: String,
/// Username attribute. Used to map a ``userid`` to LDAP to an LDAP ``dn``.
pub user_attr: String,
/// Comment
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
/// Connection security
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<LdapMode>,
/// Verify server certificate
#[serde(skip_serializing_if = "Option::is_none")]
pub verify: Option<bool>,
/// CA certificate to use for the server. The path can point to
/// either a file, or a directory. If it points to a file,
/// the PEM-formatted X.509 certificate stored at the path
/// will be added as a trusted certificate.
/// If the path points to a directory,
/// the directory replaces the system's default certificate
/// store at `/etc/ssl/certs` - Every file in the directory
/// will be loaded as a trusted certificate.
#[serde(skip_serializing_if = "Option::is_none")]
pub capath: Option<String>,
/// Bind domain to use for looking up users
#[serde(skip_serializing_if = "Option::is_none")]
pub bind_dn: Option<String>,
/// Custom LDAP search filter for user sync
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<String>,
/// Default options for LDAP sync
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_defaults_options: Option<String>,
/// List of attributes to sync from LDAP to user config
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_attributes: Option<String>,
/// User ``objectClass`` classes to sync
#[serde(skip_serializing_if = "Option::is_none")]
pub user_classes: Option<String>,
}
#[api(
properties: {
"remove-vanished": {
optional: true,
schema: REMOVE_VANISHED_SCHEMA,
},
},
)]
#[derive(Serialize, Deserialize, Updater, Default, Debug)]
#[serde(rename_all = "kebab-case")]
/// Default options for LDAP synchronization runs
pub struct SyncDefaultsOptions {
/// How to handle vanished properties/users
pub remove_vanished: Option<String>,
/// Enable new users after sync
pub enable_new: Option<bool>,
}
#[api()]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
/// remove-vanished options
pub enum RemoveVanished {
/// Delete ACLs for vanished users
Acl,
/// Remove vanished users
Entry,
/// Remove vanished properties from users (e.g. email)
Properties,
}
pub const LDAP_DOMAIN_SCHEMA: Schema = StringSchema::new("LDAP Domain").schema();
pub const SYNC_DEFAULTS_STRING_SCHEMA: Schema = StringSchema::new("sync defaults options")
.format(&ApiStringFormat::PropertyString(
&SyncDefaultsOptions::API_SCHEMA,
))
.schema();
const REMOVE_VANISHED_DESCRIPTION: &str =
"A semicolon-separated list of things to remove when they or the user \
vanishes during user synchronization. The following values are possible: ``entry`` removes the \
user when not returned from the sync; ``properties`` removes any \
properties on existing user that do not appear in the source. \
``acl`` removes ACLs when the user is not returned from the sync.";
pub const REMOVE_VANISHED_SCHEMA: Schema = StringSchema::new(REMOVE_VANISHED_DESCRIPTION)
.format(&ApiStringFormat::PropertyString(&REMOVE_VANISHED_ARRAY))
.schema();
pub const REMOVE_VANISHED_ARRAY: Schema = ArraySchema::new(
"Array of remove-vanished options",
&RemoveVanished::API_SCHEMA,
)
.min_length(1)
.schema();
#[api()]
#[derive(Serialize, Deserialize, Updater, Default, Debug)]
#[serde(rename_all = "kebab-case")]
/// Determine which LDAP attributes should be synced to which user attributes
pub struct SyncAttributes {
/// Name of the LDAP attribute containing the user's email address
pub email: Option<String>,
/// Name of the LDAP attribute containing the user's first name
pub firstname: Option<String>,
/// Name of the LDAP attribute containing the user's last name
pub lastname: Option<String>,
}
const SYNC_ATTRIBUTES_TEXT: &str = "Comma-separated list of key=value pairs for specifying \
which LDAP attributes map to which PBS user field. For example, \
to map the LDAP attribute ``mail`` to PBS's ``email``, write \
``email=mail``.";
pub const SYNC_ATTRIBUTES_SCHEMA: Schema = StringSchema::new(SYNC_ATTRIBUTES_TEXT)
.format(&ApiStringFormat::PropertyString(
&SyncAttributes::API_SCHEMA,
))
.schema();
pub const USER_CLASSES_ARRAY: Schema = ArraySchema::new(
"Array of user classes",
&StringSchema::new("user class").schema(),
)
.min_length(1)
.schema();
const USER_CLASSES_TEXT: &str = "Comma-separated list of allowed objectClass values for \
user synchronization. For instance, if ``user-classes`` is set to ``person,user``, \
then user synchronization will consider all LDAP entities \
where ``objectClass: person`` `or` ``objectClass: user``.";
pub const USER_CLASSES_SCHEMA: Schema = StringSchema::new(USER_CLASSES_TEXT)
.format(&ApiStringFormat::PropertyString(&USER_CLASSES_ARRAY))
.default("inetorgperson,posixaccount,person,user")
.schema();

373
pbs-api-types/src/lib.rs Normal file
View File

@ -0,0 +1,373 @@
//! Basic API types used by most of the PBS code.
use const_format::concatcp;
use serde::{Deserialize, Serialize};
pub mod percent_encoding;
use proxmox_schema::{
api, const_regex, ApiStringFormat, ApiType, ArraySchema, ReturnType, Schema, StringSchema,
};
use proxmox_time::parse_daily_duration;
use proxmox_auth_api::types::{APITOKEN_ID_REGEX_STR, USER_ID_REGEX_STR};
pub use proxmox_schema::api_types::SAFE_ID_FORMAT as PROXMOX_SAFE_ID_FORMAT;
pub use proxmox_schema::api_types::SAFE_ID_REGEX as PROXMOX_SAFE_ID_REGEX;
pub use proxmox_schema::api_types::SAFE_ID_REGEX_STR as PROXMOX_SAFE_ID_REGEX_STR;
pub use proxmox_schema::api_types::{
BLOCKDEVICE_DISK_AND_PARTITION_NAME_REGEX, BLOCKDEVICE_NAME_REGEX,
};
pub use proxmox_schema::api_types::{DNS_ALIAS_REGEX, DNS_NAME_OR_IP_REGEX, DNS_NAME_REGEX};
pub use proxmox_schema::api_types::{FINGERPRINT_SHA256_REGEX, SHA256_HEX_REGEX};
pub use proxmox_schema::api_types::{
GENERIC_URI_REGEX, HOSTNAME_REGEX, HOST_PORT_REGEX, HTTP_URL_REGEX,
};
pub use proxmox_schema::api_types::{MULTI_LINE_COMMENT_REGEX, SINGLE_LINE_COMMENT_REGEX};
pub use proxmox_schema::api_types::{PASSWORD_REGEX, SYSTEMD_DATETIME_REGEX, UUID_REGEX};
pub use proxmox_schema::api_types::{CIDR_FORMAT, CIDR_REGEX};
pub use proxmox_schema::api_types::{CIDR_V4_FORMAT, CIDR_V4_REGEX};
pub use proxmox_schema::api_types::{CIDR_V6_FORMAT, CIDR_V6_REGEX};
pub use proxmox_schema::api_types::{IPRE_STR, IP_FORMAT, IP_REGEX};
pub use proxmox_schema::api_types::{IPV4RE_STR, IP_V4_FORMAT, IP_V4_REGEX};
pub use proxmox_schema::api_types::{IPV6RE_STR, IP_V6_FORMAT, IP_V6_REGEX};
pub use proxmox_schema::api_types::COMMENT_SCHEMA as SINGLE_LINE_COMMENT_SCHEMA;
pub use proxmox_schema::api_types::HOSTNAME_SCHEMA;
pub use proxmox_schema::api_types::HOST_PORT_SCHEMA;
pub use proxmox_schema::api_types::HTTP_URL_SCHEMA;
pub use proxmox_schema::api_types::MULTI_LINE_COMMENT_SCHEMA;
pub use proxmox_schema::api_types::NODE_SCHEMA;
pub use proxmox_schema::api_types::SINGLE_LINE_COMMENT_FORMAT;
pub use proxmox_schema::api_types::{
BLOCKDEVICE_DISK_AND_PARTITION_NAME_SCHEMA, BLOCKDEVICE_NAME_SCHEMA,
};
pub use proxmox_schema::api_types::{CERT_FINGERPRINT_SHA256_SCHEMA, FINGERPRINT_SHA256_FORMAT};
pub use proxmox_schema::api_types::{DISK_ARRAY_SCHEMA, DISK_LIST_SCHEMA};
pub use proxmox_schema::api_types::{DNS_ALIAS_FORMAT, DNS_NAME_FORMAT, DNS_NAME_OR_IP_SCHEMA};
pub use proxmox_schema::api_types::{PASSWORD_FORMAT, PASSWORD_SCHEMA};
pub use proxmox_schema::api_types::{SERVICE_ID_SCHEMA, UUID_FORMAT};
pub use proxmox_schema::api_types::{SYSTEMD_DATETIME_FORMAT, TIME_ZONE_SCHEMA};
use proxmox_schema::api_types::{DNS_NAME_STR, IPRE_BRACKET_STR};
// re-export APT API types
pub use proxmox_apt_api_types::{
APTChangeRepositoryOptions, APTGetChangelogOptions, APTRepositoriesResult, APTRepositoryFile,
APTRepositoryFileError, APTRepositoryHandle, APTRepositoryInfo, APTStandardRepository,
APTUpdateInfo, APTUpdateOptions,
};
#[rustfmt::skip]
pub const BACKUP_ID_RE: &str = r"[A-Za-z0-9_][A-Za-z0-9._\-]*";
#[rustfmt::skip]
pub const BACKUP_TYPE_RE: &str = r"(?:host|vm|ct)";
#[rustfmt::skip]
pub const BACKUP_TIME_RE: &str = r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z";
#[rustfmt::skip]
pub const BACKUP_NS_RE: &str =
concatcp!("(?:",
"(?:", PROXMOX_SAFE_ID_REGEX_STR, r"/){0,7}", PROXMOX_SAFE_ID_REGEX_STR,
")?");
#[rustfmt::skip]
pub const BACKUP_NS_PATH_RE: &str =
concatcp!(r"(?:ns/", PROXMOX_SAFE_ID_REGEX_STR, r"/){0,7}ns/", PROXMOX_SAFE_ID_REGEX_STR, r"/");
#[rustfmt::skip]
pub const SNAPSHOT_PATH_REGEX_STR: &str =
concatcp!(
r"(", BACKUP_TYPE_RE, ")/(", BACKUP_ID_RE, ")/(", BACKUP_TIME_RE, r")",
);
#[rustfmt::skip]
pub const GROUP_OR_SNAPSHOT_PATH_REGEX_STR: &str =
concatcp!(
r"(", BACKUP_TYPE_RE, ")/(", BACKUP_ID_RE, ")(?:/(", BACKUP_TIME_RE, r"))?",
);
mod acl;
pub use acl::*;
mod datastore;
pub use datastore::*;
mod jobs;
pub use jobs::*;
mod key_derivation;
pub use key_derivation::{Kdf, KeyInfo};
mod maintenance;
pub use maintenance::*;
mod network;
pub use network::*;
mod node;
pub use node::*;
pub use proxmox_auth_api::types as userid;
pub use proxmox_auth_api::types::{Authid, Userid};
pub use proxmox_auth_api::types::{Realm, RealmRef};
pub use proxmox_auth_api::types::{Tokenname, TokennameRef};
pub use proxmox_auth_api::types::{Username, UsernameRef};
pub use proxmox_auth_api::types::{
PROXMOX_GROUP_ID_SCHEMA, PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN_NAME_SCHEMA,
};
#[macro_use]
mod user;
pub use user::*;
pub use proxmox_schema::upid::*;
mod crypto;
pub use crypto::{bytes_as_fingerprint, CryptMode, Fingerprint};
pub mod file_restore;
mod openid;
pub use openid::*;
mod ldap;
pub use ldap::*;
mod ad;
pub use ad::*;
mod remote;
pub use remote::*;
mod pathpatterns;
pub use pathpatterns::*;
mod tape;
pub use tape::*;
mod traffic_control;
pub use traffic_control::*;
mod zfs;
pub use zfs::*;
mod metrics;
pub use metrics::*;
mod version;
pub use version::*;
const_regex! {
// just a rough check - dummy acceptor is used before persisting
pub OPENSSL_CIPHERS_REGEX = r"^[0-9A-Za-z_:, +!\-@=.]+$";
pub BACKUP_REPO_URL_REGEX = concatcp!(
r"^^(?:(?:(",
USER_ID_REGEX_STR, "|", APITOKEN_ID_REGEX_STR,
")@)?(",
DNS_NAME_STR, "|", IPRE_BRACKET_STR,
"):)?(?:([0-9]{1,5}):)?(", PROXMOX_SAFE_ID_REGEX_STR, r")$"
);
pub SUBSCRIPTION_KEY_REGEX = concat!(r"^pbs(?:[cbsp])-[0-9a-f]{10}$");
}
pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
pub const SUBSCRIPTION_KEY_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&SUBSCRIPTION_KEY_REGEX);
pub const OPENSSL_CIPHERS_TLS_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&OPENSSL_CIPHERS_REGEX);
pub const DAILY_DURATION_FORMAT: ApiStringFormat =
ApiStringFormat::VerifyFn(|s| parse_daily_duration(s).map(drop));
pub const SEARCH_DOMAIN_SCHEMA: Schema =
StringSchema::new("Search domain for host-name lookup.").schema();
pub const FIRST_DNS_SERVER_SCHEMA: Schema = StringSchema::new("First name server IP address.")
.format(&IP_FORMAT)
.schema();
pub const SECOND_DNS_SERVER_SCHEMA: Schema = StringSchema::new("Second name server IP address.")
.format(&IP_FORMAT)
.schema();
pub const THIRD_DNS_SERVER_SCHEMA: Schema = StringSchema::new("Third name server IP address.")
.format(&IP_FORMAT)
.schema();
pub const OPENSSL_CIPHERS_TLS_1_2_SCHEMA: Schema =
StringSchema::new("OpenSSL cipher list used by the proxy for TLS <= 1.2")
.format(&OPENSSL_CIPHERS_TLS_FORMAT)
.schema();
pub const OPENSSL_CIPHERS_TLS_1_3_SCHEMA: Schema =
StringSchema::new("OpenSSL ciphersuites list used by the proxy for TLS 1.3")
.format(&OPENSSL_CIPHERS_TLS_FORMAT)
.schema();
pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
.format(&PASSWORD_FORMAT)
.min_length(8)
.max_length(64)
.schema();
pub const REALM_ID_SCHEMA: Schema = StringSchema::new("Realm name.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(2)
.max_length(32)
.schema();
pub const SUBSCRIPTION_KEY_SCHEMA: Schema =
StringSchema::new("Proxmox Backup Server subscription key.")
.format(&SUBSCRIPTION_KEY_FORMAT)
.min_length(15)
.max_length(16)
.schema();
pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(
"Prevent changes if current configuration file has different \
SHA256 digest. This can be used to prevent concurrent \
modifications.",
)
.format(&PVE_CONFIG_DIGEST_FORMAT)
.schema();
/// API schema format definition for repository URLs
pub const BACKUP_REPO_URL: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_REPO_URL_REGEX);
// Complex type definitions
#[api()]
#[derive(Default, Serialize, Deserialize)]
/// Storage space usage information.
pub struct StorageStatus {
/// Total space (bytes).
pub total: u64,
/// Used space (bytes).
pub used: u64,
/// Available space (bytes).
pub avail: u64,
}
pub const PASSWORD_HINT_SCHEMA: Schema = StringSchema::new("Password hint.")
.format(&SINGLE_LINE_COMMENT_FORMAT)
.min_length(1)
.max_length(64)
.schema();
#[api()]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// Node Power command type.
pub enum NodePowerCommand {
/// Restart the server
Reboot,
/// Shutdown the server
Shutdown,
}
#[api()]
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// The state (result) of a finished worker task.
pub enum TaskStateType {
/// Ok
OK,
/// Warning
Warning,
/// Error
Error,
/// Unknown
Unknown,
}
#[api(
properties: {
upid: { schema: UPID::API_SCHEMA },
},
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
/// Task properties.
pub struct TaskListItem {
pub upid: String,
/// The node name where the task is running on.
pub node: String,
/// The Unix PID
pub pid: i64,
/// The task start time (Epoch)
pub pstart: u64,
/// The task start time (Epoch)
pub starttime: i64,
/// Worker type (arbitrary ASCII string)
pub worker_type: String,
/// Worker ID (arbitrary ASCII string)
pub worker_id: Option<String>,
/// The authenticated entity who started the task
pub user: String,
/// The task end time (Epoch)
#[serde(skip_serializing_if = "Option::is_none")]
pub endtime: Option<i64>,
/// Task end status
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
}
pub const NODE_TASKS_LIST_TASKS_RETURN_TYPE: ReturnType = ReturnType {
optional: false,
schema: &ArraySchema::new("A list of tasks.", &TaskListItem::API_SCHEMA).schema(),
};
#[api]
#[derive(Deserialize, Serialize, Copy, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
/// type of the realm
pub enum RealmType {
/// The PAM realm
Pam,
/// The PBS realm
Pbs,
/// An OpenID Connect realm
OpenId,
/// An LDAP realm
Ldap,
/// An Active Directory (AD) realm
Ad,
}
serde_plain::derive_display_from_serialize!(RealmType);
serde_plain::derive_fromstr_from_deserialize!(RealmType);
#[api(
properties: {
realm: {
schema: REALM_ID_SCHEMA,
},
"type": {
type: RealmType,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
},
)]
#[derive(Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Basic Information about a realm
pub struct BasicRealmInfo {
pub realm: String,
#[serde(rename = "type")]
pub ty: RealmType,
/// True if it is the default realm
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}

View File

@ -0,0 +1,110 @@
use anyhow::{bail, Error};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use proxmox_schema::{api, const_regex, ApiStringFormat, Schema, StringSchema};
const_regex! {
pub MAINTENANCE_MESSAGE_REGEX = r"^[[:^cntrl:]]*$";
}
pub const MAINTENANCE_MESSAGE_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&MAINTENANCE_MESSAGE_REGEX);
pub const MAINTENANCE_MESSAGE_SCHEMA: Schema =
StringSchema::new("Message describing the reason for the maintenance.")
.format(&MAINTENANCE_MESSAGE_FORMAT)
.max_length(64)
.schema();
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
/// Operation requirements, used when checking for maintenance mode.
pub enum Operation {
/// for any read operation like backup restore or RRD metric collection
Read,
/// for any write/delete operation, like backup create or GC
Write,
/// for any purely logical operation on the in-memory state of the datastore, e.g., to check if
/// some mutex could be locked (e.g., GC already running?)
///
/// NOTE: one must *not* do any IO operations when only helding this Op state
Lookup,
// GarbageCollect or Delete?
}
#[api]
#[derive(Copy, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
/// Maintenance type.
pub enum MaintenanceType {
// TODO:
// - Add "GarbageCollection" or "DeleteOnly" as type and track GC (or all deletes) as separate
// operation, so that one can enable a mode where nothing new can be added but stuff can be
// cleaned
/// Only read operations are allowed on the datastore.
ReadOnly,
/// Neither read nor write operations are allowed on the datastore.
Offline,
/// The datastore is being deleted.
Delete,
/// The (removable) datastore is being unmounted.
Unmount,
}
serde_plain::derive_display_from_serialize!(MaintenanceType);
serde_plain::derive_fromstr_from_deserialize!(MaintenanceType);
#[api(
properties: {
type: {
type: MaintenanceType,
},
message: {
optional: true,
schema: MAINTENANCE_MESSAGE_SCHEMA,
}
},
default_key: "type",
)]
#[derive(Deserialize, Serialize)]
/// Maintenance mode
pub struct MaintenanceMode {
/// Type of maintenance ("read-only" or "offline").
#[serde(rename = "type")]
pub ty: MaintenanceType,
/// Reason for maintenance.
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl MaintenanceMode {
/// Used for deciding whether the datastore is cleared from the internal cache
pub fn clear_from_cache(&self) -> bool {
self.ty == MaintenanceType::Offline
|| self.ty == MaintenanceType::Delete
|| self.ty == MaintenanceType::Unmount
}
pub fn check(&self, operation: Option<Operation>) -> Result<(), Error> {
if self.ty == MaintenanceType::Delete {
bail!("datastore is being deleted");
}
let message = percent_encoding::percent_decode_str(self.message.as_deref().unwrap_or(""))
.decode_utf8()
.unwrap_or(Cow::Borrowed(""));
if let Some(Operation::Lookup) = operation {
return Ok(());
} else if self.ty == MaintenanceType::Unmount {
bail!("datastore is being unmounted");
} else if self.ty == MaintenanceType::Offline {
bail!("offline maintenance mode: {}", message);
} else if self.ty == MaintenanceType::ReadOnly {
if let Some(Operation::Write) = operation {
bail!("read-only maintenance mode: {}", message);
}
}
Ok(())
}
}

View File

@ -0,0 +1,255 @@
use serde::{Deserialize, Serialize};
use crate::{
HOST_PORT_SCHEMA, HTTP_URL_SCHEMA, PROXMOX_SAFE_ID_FORMAT, SINGLE_LINE_COMMENT_SCHEMA,
};
use proxmox_schema::{api, Schema, StringSchema, Updater};
pub const METRIC_SERVER_ID_SCHEMA: Schema = StringSchema::new("Metrics Server ID.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(3)
.max_length(32)
.schema();
pub const INFLUXDB_BUCKET_SCHEMA: Schema = StringSchema::new("InfluxDB Bucket.")
.min_length(1)
.max_length(32)
.default("proxmox")
.schema();
pub const INFLUXDB_ORGANIZATION_SCHEMA: Schema = StringSchema::new("InfluxDB Organization.")
.min_length(1)
.max_length(32)
.default("proxmox")
.schema();
fn return_true() -> bool {
true
}
fn is_true(b: &bool) -> bool {
*b
}
#[api(
properties: {
name: {
schema: METRIC_SERVER_ID_SCHEMA,
},
enable: {
type: bool,
optional: true,
default: true,
},
host: {
schema: HOST_PORT_SCHEMA,
},
mtu: {
type: u16,
optional: true,
default: 1500,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
},
)]
#[derive(Serialize, Deserialize, Updater)]
#[serde(rename_all = "kebab-case")]
/// InfluxDB Server (UDP)
pub struct InfluxDbUdp {
#[updater(skip)]
pub name: String,
#[serde(default = "return_true", skip_serializing_if = "is_true")]
#[updater(serde(skip_serializing_if = "Option::is_none"))]
/// Enables or disables the metrics server
pub enable: bool,
/// the host + port
pub host: String,
#[serde(skip_serializing_if = "Option::is_none")]
/// The MTU
pub mtu: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
#[api(
properties: {
name: {
schema: METRIC_SERVER_ID_SCHEMA,
},
enable: {
type: bool,
optional: true,
default: true,
},
url: {
schema: HTTP_URL_SCHEMA,
},
token: {
type: String,
optional: true,
},
bucket: {
schema: INFLUXDB_BUCKET_SCHEMA,
optional: true,
},
organization: {
schema: INFLUXDB_ORGANIZATION_SCHEMA,
optional: true,
},
"max-body-size": {
type: usize,
optional: true,
default: 25_000_000,
},
"verify-tls": {
type: bool,
optional: true,
default: true,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
},
)]
#[derive(Serialize, Deserialize, Updater)]
#[serde(rename_all = "kebab-case")]
/// InfluxDB Server (HTTP(s))
pub struct InfluxDbHttp {
#[updater(skip)]
pub name: String,
#[serde(default = "return_true", skip_serializing_if = "is_true")]
#[updater(serde(skip_serializing_if = "Option::is_none"))]
/// Enables or disables the metrics server
pub enable: bool,
/// The base url of the influxdb server
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
/// The (optional) API token
pub token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// Named location where time series data is stored
pub bucket: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// Workspace for a group of users
pub organization: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// The (optional) maximum body size
pub max_body_size: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
/// If true, the certificate will be validated.
pub verify_tls: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
#[api]
#[derive(Copy, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
/// Type of the metric server
pub enum MetricServerType {
/// InfluxDB HTTP
#[serde(rename = "influxdb-http")]
InfluxDbHttp,
/// InfluxDB UDP
#[serde(rename = "influxdb-udp")]
InfluxDbUdp,
}
#[api(
properties: {
name: {
schema: METRIC_SERVER_ID_SCHEMA,
},
"type": {
type: MetricServerType,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
},
)]
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "kebab-case")]
/// Basic information about a metric server that's available for all types
pub struct MetricServerInfo {
pub name: String,
#[serde(rename = "type")]
pub ty: MetricServerType,
/// Enables or disables the metrics server
#[serde(skip_serializing_if = "Option::is_none")]
pub enable: Option<bool>,
/// The target server
pub server: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[api(
properties: {
data: {
type: Array,
items: {
type: MetricDataPoint,
}
}
}
)]
/// Return type for the metric API endpoint
pub struct Metrics {
/// List of metric data points, sorted by timestamp
pub data: Vec<MetricDataPoint>,
}
#[api(
properties: {
id: {
type: String,
},
metric: {
type: String,
},
timestamp: {
type: Integer,
},
},
)]
/// Metric data point
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MetricDataPoint {
/// Unique identifier for this metric object, for instance `node/<nodename>`
/// or `qemu/<vmid>`.
pub id: String,
/// Name of the metric.
pub metric: String,
/// Time at which this metric was observed
pub timestamp: i64,
#[serde(rename = "type")]
pub ty: MetricDataType,
/// Metric value.
pub value: f64,
}
#[api]
/// Type of the metric.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum MetricDataType {
/// gauge.
Gauge,
/// counter.
Counter,
/// derive.
Derive,
}
serde_plain::derive_display_from_serialize!(MetricDataType);
serde_plain::derive_fromstr_from_deserialize!(MetricDataType);

View File

@ -0,0 +1,345 @@
use std::fmt;
use serde::{Deserialize, Serialize};
use proxmox_schema::*;
use crate::{
CIDR_FORMAT, CIDR_V4_FORMAT, CIDR_V6_FORMAT, IP_FORMAT, IP_V4_FORMAT, IP_V6_FORMAT,
PROXMOX_SAFE_ID_REGEX,
};
pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
pub const IP_V4_SCHEMA: Schema = StringSchema::new("IPv4 address.")
.format(&IP_V4_FORMAT)
.max_length(15)
.schema();
pub const IP_V6_SCHEMA: Schema = StringSchema::new("IPv6 address.")
.format(&IP_V6_FORMAT)
.max_length(39)
.schema();
pub const IP_SCHEMA: Schema = StringSchema::new("IP (IPv4 or IPv6) address.")
.format(&IP_FORMAT)
.max_length(39)
.schema();
pub const CIDR_V4_SCHEMA: Schema = StringSchema::new("IPv4 address with netmask (CIDR notation).")
.format(&CIDR_V4_FORMAT)
.max_length(18)
.schema();
pub const CIDR_V6_SCHEMA: Schema = StringSchema::new("IPv6 address with netmask (CIDR notation).")
.format(&CIDR_V6_FORMAT)
.max_length(43)
.schema();
pub const CIDR_SCHEMA: Schema =
StringSchema::new("IP address (IPv4 or IPv6) with netmask (CIDR notation).")
.format(&CIDR_FORMAT)
.max_length(43)
.schema();
#[api()]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// Interface configuration method
pub enum NetworkConfigMethod {
/// Configuration is done manually using other tools
Manual,
/// Define interfaces with statically allocated addresses.
Static,
/// Obtain an address via DHCP
DHCP,
/// Define the loopback interface.
Loopback,
}
#[api()]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[repr(u8)]
/// Linux Bond Mode
pub enum LinuxBondMode {
/// Round-robin policy
BalanceRr = 0,
/// Active-backup policy
ActiveBackup = 1,
/// XOR policy
BalanceXor = 2,
/// Broadcast policy
Broadcast = 3,
/// IEEE 802.3ad Dynamic link aggregation
#[serde(rename = "802.3ad")]
Ieee802_3ad = 4,
/// Adaptive transmit load balancing
BalanceTlb = 5,
/// Adaptive load balancing
BalanceAlb = 6,
}
impl fmt::Display for LinuxBondMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
LinuxBondMode::BalanceRr => "balance-rr",
LinuxBondMode::ActiveBackup => "active-backup",
LinuxBondMode::BalanceXor => "balance-xor",
LinuxBondMode::Broadcast => "broadcast",
LinuxBondMode::Ieee802_3ad => "802.3ad",
LinuxBondMode::BalanceTlb => "balance-tlb",
LinuxBondMode::BalanceAlb => "balance-alb",
})
}
}
#[api()]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[repr(u8)]
/// Bond Transmit Hash Policy for LACP (802.3ad)
pub enum BondXmitHashPolicy {
/// Layer 2
Layer2 = 0,
/// Layer 2+3
#[serde(rename = "layer2+3")]
Layer2_3 = 1,
/// Layer 3+4
#[serde(rename = "layer3+4")]
Layer3_4 = 2,
}
impl fmt::Display for BondXmitHashPolicy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
BondXmitHashPolicy::Layer2 => "layer2",
BondXmitHashPolicy::Layer2_3 => "layer2+3",
BondXmitHashPolicy::Layer3_4 => "layer3+4",
})
}
}
#[api()]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// Network interface type
pub enum NetworkInterfaceType {
/// Loopback
Loopback,
/// Physical Ethernet device
Eth,
/// Linux Bridge
Bridge,
/// Linux Bond
Bond,
/// Linux VLAN (eth.10)
Vlan,
/// Interface Alias (eth:1)
Alias,
/// Unknown interface type
Unknown,
}
pub const NETWORK_INTERFACE_NAME_SCHEMA: Schema = StringSchema::new("Network interface name.")
.format(&NETWORK_INTERFACE_FORMAT)
.min_length(1)
.max_length(15) // libc::IFNAMSIZ-1
.schema();
pub const NETWORK_INTERFACE_ARRAY_SCHEMA: Schema =
ArraySchema::new("Network interface list.", &NETWORK_INTERFACE_NAME_SCHEMA).schema();
pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema =
StringSchema::new("A list of network devices, comma separated.")
.format(&ApiStringFormat::PropertyString(
&NETWORK_INTERFACE_ARRAY_SCHEMA,
))
.schema();
#[api(
properties: {
name: {
schema: NETWORK_INTERFACE_NAME_SCHEMA,
},
"type": {
type: NetworkInterfaceType,
},
method: {
type: NetworkConfigMethod,
optional: true,
},
method6: {
type: NetworkConfigMethod,
optional: true,
},
cidr: {
schema: CIDR_V4_SCHEMA,
optional: true,
},
cidr6: {
schema: CIDR_V6_SCHEMA,
optional: true,
},
gateway: {
schema: IP_V4_SCHEMA,
optional: true,
},
gateway6: {
schema: IP_V6_SCHEMA,
optional: true,
},
options: {
description: "Option list (inet)",
type: Array,
items: {
description: "Optional attribute line.",
type: String,
},
},
options6: {
description: "Option list (inet6)",
type: Array,
items: {
description: "Optional attribute line.",
type: String,
},
},
comments: {
description: "Comments (inet, may span multiple lines)",
type: String,
optional: true,
},
comments6: {
description: "Comments (inet6, may span multiple lines)",
type: String,
optional: true,
},
bridge_ports: {
schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
optional: true,
},
slaves: {
schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
optional: true,
},
"vlan-id": {
description: "VLAN ID.",
type: u16,
optional: true,
},
"vlan-raw-device": {
schema: NETWORK_INTERFACE_NAME_SCHEMA,
optional: true,
},
bond_mode: {
type: LinuxBondMode,
optional: true,
},
"bond-primary": {
schema: NETWORK_INTERFACE_NAME_SCHEMA,
optional: true,
},
bond_xmit_hash_policy: {
type: BondXmitHashPolicy,
optional: true,
},
}
)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
/// Network Interface configuration
pub struct Interface {
/// Autostart interface
#[serde(rename = "autostart")]
pub autostart: bool,
/// Interface is active (UP)
pub active: bool,
/// Interface name
pub name: String,
/// Interface type
#[serde(rename = "type")]
pub interface_type: NetworkInterfaceType,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<NetworkConfigMethod>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method6: Option<NetworkConfigMethod>,
#[serde(skip_serializing_if = "Option::is_none")]
/// IPv4 address with netmask
pub cidr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// IPv4 gateway
pub gateway: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// IPv6 address with netmask
pub cidr6: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// IPv6 gateway
pub gateway6: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub options: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub options6: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comments: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comments6: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// Maximum Transmission Unit
pub mtu: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bridge_ports: Option<Vec<String>>,
/// Enable bridge vlan support.
#[serde(skip_serializing_if = "Option::is_none")]
pub bridge_vlan_aware: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "vlan-id")]
pub vlan_id: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "vlan-raw-device")]
pub vlan_raw_device: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slaves: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bond_mode: Option<LinuxBondMode>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "bond-primary")]
pub bond_primary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
}
impl Interface {
pub fn new(name: String) -> Self {
Self {
name,
interface_type: NetworkInterfaceType::Unknown,
autostart: false,
active: false,
method: None,
method6: None,
cidr: None,
gateway: None,
cidr6: None,
gateway6: None,
options: Vec::new(),
options6: Vec::new(),
comments: None,
comments6: None,
mtu: None,
bridge_ports: None,
bridge_vlan_aware: None,
vlan_id: None,
vlan_raw_device: None,
slaves: None,
bond_mode: None,
bond_primary: None,
bond_xmit_hash_policy: None,
}
}
}

162
pbs-api-types/src/node.rs Normal file
View File

@ -0,0 +1,162 @@
use std::ffi::OsStr;
use proxmox_schema::*;
use serde::{Deserialize, Serialize};
use crate::StorageStatus;
#[api]
#[derive(Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
/// Node memory usage counters
pub struct NodeMemoryCounters {
/// Total memory
pub total: u64,
/// Used memory
pub used: u64,
/// Free memory
pub free: u64,
}
#[api]
#[derive(Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
/// Node swap usage counters
pub struct NodeSwapCounters {
/// Total swap
pub total: u64,
/// Used swap
pub used: u64,
/// Free swap
pub free: u64,
}
#[api]
#[derive(Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
/// Contains general node information such as the fingerprint`
pub struct NodeInformation {
/// The SSL Fingerprint
pub fingerprint: String,
}
#[api]
#[derive(Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
/// The current kernel version (output of `uname`)
pub struct KernelVersionInformation {
/// The systemname/nodename
pub sysname: String,
/// The kernel release number
pub release: String,
/// The kernel version
pub version: String,
/// The machine architecture
pub machine: String,
}
impl KernelVersionInformation {
pub fn from_uname_parts(
sysname: &OsStr,
release: &OsStr,
version: &OsStr,
machine: &OsStr,
) -> Self {
KernelVersionInformation {
sysname: sysname.to_str().map(String::from).unwrap_or_default(),
release: release.to_str().map(String::from).unwrap_or_default(),
version: version.to_str().map(String::from).unwrap_or_default(),
machine: machine.to_str().map(String::from).unwrap_or_default(),
}
}
pub fn get_legacy(&self) -> String {
format!("{} {} {}", self.sysname, self.release, self.version)
}
}
#[api]
#[derive(Serialize, Deserialize, Copy, Clone)]
#[serde(rename_all = "kebab-case")]
/// The possible BootModes
pub enum BootMode {
/// The BootMode is EFI/UEFI
Efi,
/// The BootMode is Legacy BIOS
LegacyBios,
}
#[api]
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
/// Holds the Bootmodes
pub struct BootModeInformation {
/// The BootMode, either Efi or Bios
pub mode: BootMode,
/// SecureBoot status
pub secureboot: bool,
}
#[api]
#[derive(Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
/// Information about the CPU
pub struct NodeCpuInformation {
/// The CPU model
pub model: String,
/// The number of CPU sockets
pub sockets: usize,
/// The number of CPU cores (incl. threads)
pub cpus: usize,
}
#[api(
properties: {
memory: {
type: NodeMemoryCounters,
},
root: {
type: StorageStatus,
},
swap: {
type: NodeSwapCounters,
},
loadavg: {
type: Array,
items: {
type: Number,
description: "the load",
}
},
cpuinfo: {
type: NodeCpuInformation,
},
info: {
type: NodeInformation,
}
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// The Node status
pub struct NodeStatus {
pub memory: NodeMemoryCounters,
pub root: StorageStatus,
pub swap: NodeSwapCounters,
/// The current uptime of the server.
pub uptime: u64,
/// Load for 1, 5 and 15 minutes.
pub loadavg: [f64; 3],
/// The current kernel version (NEW struct type).
pub current_kernel: KernelVersionInformation,
/// The current kernel version (LEGACY string type).
pub kversion: String,
/// Total CPU usage since last query.
pub cpu: f64,
/// Total IO wait since last query.
pub wait: f64,
pub cpuinfo: NodeCpuInformation,
pub info: NodeInformation,
/// Current boot mode
pub boot_info: BootModeInformation,
}

120
pbs-api-types/src/openid.rs Normal file
View File

@ -0,0 +1,120 @@
use serde::{Deserialize, Serialize};
use proxmox_schema::{api, ApiStringFormat, ArraySchema, Schema, StringSchema, Updater};
use super::{
GENERIC_URI_REGEX, PROXMOX_SAFE_ID_FORMAT, PROXMOX_SAFE_ID_REGEX, REALM_ID_SCHEMA,
SINGLE_LINE_COMMENT_SCHEMA,
};
pub const OPENID_SCOPE_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
pub const OPENID_SCOPE_SCHEMA: Schema = StringSchema::new("OpenID Scope Name.")
.format(&OPENID_SCOPE_FORMAT)
.schema();
pub const OPENID_SCOPE_ARRAY_SCHEMA: Schema =
ArraySchema::new("Array of OpenId Scopes.", &OPENID_SCOPE_SCHEMA).schema();
pub const OPENID_SCOPE_LIST_FORMAT: ApiStringFormat =
ApiStringFormat::PropertyString(&OPENID_SCOPE_ARRAY_SCHEMA);
pub const OPENID_DEFAILT_SCOPE_LIST: &str = "email profile";
pub const OPENID_SCOPE_LIST_SCHEMA: Schema = StringSchema::new("OpenID Scope List")
.format(&OPENID_SCOPE_LIST_FORMAT)
.default(OPENID_DEFAILT_SCOPE_LIST)
.schema();
pub const OPENID_ACR_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&GENERIC_URI_REGEX);
pub const OPENID_ACR_SCHEMA: Schema =
StringSchema::new("OpenID Authentication Context Class Reference.")
.format(&OPENID_ACR_FORMAT)
.schema();
pub const OPENID_ACR_ARRAY_SCHEMA: Schema =
ArraySchema::new("Array of OpenId ACRs.", &OPENID_ACR_SCHEMA).schema();
pub const OPENID_ACR_LIST_FORMAT: ApiStringFormat =
ApiStringFormat::PropertyString(&OPENID_ACR_ARRAY_SCHEMA);
pub const OPENID_ACR_LIST_SCHEMA: Schema = StringSchema::new("OpenID ACR List")
.format(&OPENID_ACR_LIST_FORMAT)
.schema();
pub const OPENID_USERNAME_CLAIM_SCHEMA: Schema = StringSchema::new(
"Use the value of this attribute/claim as unique user name. It \
is up to the identity provider to guarantee the uniqueness. The \
OpenID specification only guarantees that Subject ('sub') is \
unique. Also make sure that the user is not allowed to change that \
attribute by himself!",
)
.max_length(64)
.min_length(1)
.format(&PROXMOX_SAFE_ID_FORMAT)
.schema();
#[api(
properties: {
realm: {
schema: REALM_ID_SCHEMA,
},
"client-key": {
optional: true,
},
"scopes": {
schema: OPENID_SCOPE_LIST_SCHEMA,
optional: true,
},
"acr-values": {
schema: OPENID_ACR_LIST_SCHEMA,
optional: true,
},
prompt: {
description: "OpenID Prompt",
type: String,
format: &PROXMOX_SAFE_ID_FORMAT,
optional: true,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
autocreate: {
optional: true,
default: false,
},
"username-claim": {
schema: OPENID_USERNAME_CLAIM_SCHEMA,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize, Updater)]
#[serde(rename_all = "kebab-case")]
/// OpenID configuration properties.
pub struct OpenIdRealmConfig {
#[updater(skip)]
pub realm: String,
/// OpenID Issuer Url
pub issuer_url: String,
/// OpenID Client ID
pub client_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub scopes: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub acr_values: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt: Option<String>,
/// OpenID Client Key
#[serde(skip_serializing_if = "Option::is_none")]
pub client_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
/// Automatically create users if they do not exist.
#[serde(skip_serializing_if = "Option::is_none")]
pub autocreate: Option<bool>,
#[updater(skip)]
#[serde(skip_serializing_if = "Option::is_none")]
pub username_claim: Option<String>,
}

View File

@ -0,0 +1,30 @@
use proxmox_schema::{const_regex, ApiStringFormat, ApiType, Schema, StringSchema};
use serde::{Deserialize, Serialize};
const_regex! {
pub PATH_PATTERN_REGEX = concat!(r"^.+[^\\]$");
}
pub const PATH_PATTERN_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PATH_PATTERN_REGEX);
pub const PATH_PATTERN_SCHEMA: Schema =
StringSchema::new("Path or match pattern for matching filenames.")
.format(&PATH_PATTERN_FORMAT)
.schema();
#[derive(Default, Deserialize, Serialize)]
/// Path or path pattern for filename matching
pub struct PathPattern {
pattern: String,
}
impl ApiType for PathPattern {
const API_SCHEMA: Schema = PATH_PATTERN_SCHEMA;
}
impl AsRef<[u8]> for PathPattern {
fn as_ref(&self) -> &[u8] {
self.pattern.as_bytes()
}
}

View File

@ -0,0 +1,22 @@
use percent_encoding::{utf8_percent_encode, AsciiSet};
/// This used to be: `SIMPLE_ENCODE_SET` plus space, `"`, `#`, `<`, `>`, backtick, `?`, `{`, `}`
pub const DEFAULT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS // 0..1f and 7e
// The SIMPLE_ENCODE_SET adds space and anything >= 0x7e (7e itself is already included above)
.add(0x20)
.add(0x7f)
// the DEFAULT_ENCODE_SET added:
.add(b' ')
.add(b'"')
.add(b'#')
.add(b'<')
.add(b'>')
.add(b'`')
.add(b'?')
.add(b'{')
.add(b'}');
/// percent encode a url component
pub fn percent_encode_component(comp: &str) -> String {
utf8_percent_encode(comp, percent_encoding::NON_ALPHANUMERIC).to_string()
}

106
pbs-api-types/src/remote.rs Normal file
View File

@ -0,0 +1,106 @@
use serde::{Deserialize, Serialize};
use super::*;
use proxmox_schema::*;
pub const REMOTE_PASSWORD_SCHEMA: Schema =
StringSchema::new("Password or auth token for remote host.")
.format(&PASSWORD_FORMAT)
.min_length(1)
.max_length(1024)
.schema();
pub const REMOTE_PASSWORD_BASE64_SCHEMA: Schema =
StringSchema::new("Password or auth token for remote host (stored as base64 string).")
.format(&PASSWORD_FORMAT)
.min_length(1)
.max_length(1024)
.schema();
pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(3)
.max_length(32)
.schema();
#[api(
properties: {
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
host: {
schema: DNS_NAME_OR_IP_SCHEMA,
},
port: {
optional: true,
description: "The (optional) port",
type: u16,
},
"auth-id": {
type: Authid,
},
fingerprint: {
optional: true,
schema: CERT_FINGERPRINT_SHA256_SCHEMA,
},
},
)]
#[derive(Serialize, Deserialize, Updater, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Remote configuration properties.
pub struct RemoteConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
pub host: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
pub auth_id: Authid,
#[serde(skip_serializing_if = "Option::is_none")]
pub fingerprint: Option<String>,
}
#[api(
properties: {
name: {
schema: REMOTE_ID_SCHEMA,
},
config: {
type: RemoteConfig,
},
password: {
schema: REMOTE_PASSWORD_BASE64_SCHEMA,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Remote properties.
pub struct Remote {
pub name: String,
// Note: The stored password is base64 encoded
#[serde(default, skip_serializing_if = "String::is_empty")]
#[serde(with = "proxmox_serde::string_as_base64")]
pub password: String,
#[serde(flatten)]
pub config: RemoteConfig,
}
#[api(
properties: {
name: {
schema: REMOTE_ID_SCHEMA,
},
config: {
type: RemoteConfig,
},
},
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Remote properties.
pub struct RemoteWithoutPassword {
pub name: String,
#[serde(flatten)]
pub config: RemoteConfig,
}

View File

@ -0,0 +1,134 @@
//! Types for tape changer API
use serde::{Deserialize, Serialize};
use proxmox_schema::{
api, ApiStringFormat, ArraySchema, IntegerSchema, Schema, StringSchema, Updater,
};
use crate::{OptionalDeviceIdentification, PROXMOX_SAFE_ID_FORMAT};
pub const CHANGER_NAME_SCHEMA: Schema = StringSchema::new("Tape Changer Identifier.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(3)
.max_length(32)
.schema();
pub const SCSI_CHANGER_PATH_SCHEMA: Schema =
StringSchema::new("Path to Linux generic SCSI device (e.g. '/dev/sg4')").schema();
pub const MEDIA_LABEL_SCHEMA: Schema = StringSchema::new("Media Label/Barcode.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(2)
.max_length(32)
.schema();
pub const SLOT_ARRAY_SCHEMA: Schema = ArraySchema::new(
"Slot list.",
&IntegerSchema::new("Slot number").minimum(1).schema(),
)
.schema();
pub const EXPORT_SLOT_LIST_SCHEMA: Schema = StringSchema::new(
"\
A list of slot numbers, comma separated. Those slots are reserved for
Import/Export, i.e. any media in those slots are considered to be
'offline'.
",
)
.format(&ApiStringFormat::PropertyString(&SLOT_ARRAY_SCHEMA))
.schema();
#[api(
properties: {
name: {
schema: CHANGER_NAME_SCHEMA,
},
path: {
schema: SCSI_CHANGER_PATH_SCHEMA,
},
"export-slots": {
schema: EXPORT_SLOT_LIST_SCHEMA,
optional: true,
},
"eject-before-unload": {
optional: true,
default: false,
}
},
)]
#[derive(Serialize, Deserialize, Updater)]
#[serde(rename_all = "kebab-case")]
/// SCSI tape changer
pub struct ScsiTapeChanger {
#[updater(skip)]
pub name: String,
pub path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub export_slots: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// if set to true, tapes are ejected manually before unloading
pub eject_before_unload: Option<bool>,
}
#[api(
properties: {
config: {
type: ScsiTapeChanger,
},
info: {
type: OptionalDeviceIdentification,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Changer config with optional device identification attributes
pub struct ChangerListEntry {
#[serde(flatten)]
pub config: ScsiTapeChanger,
#[serde(flatten)]
pub info: OptionalDeviceIdentification,
}
#[api()]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Mtx Entry Kind
pub enum MtxEntryKind {
/// Drive
Drive,
/// Slot
Slot,
/// Import/Export Slot
ImportExport,
}
#[api(
properties: {
"entry-kind": {
type: MtxEntryKind,
},
"label-text": {
schema: MEDIA_LABEL_SCHEMA,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Mtx Status Entry
pub struct MtxStatusEntry {
pub entry_kind: MtxEntryKind,
/// The ID of the slot or drive
pub entry_id: u64,
/// The media label (volume tag) if the slot/drive is full
#[serde(skip_serializing_if = "Option::is_none")]
pub label_text: Option<String>,
/// The slot the drive was loaded from
#[serde(skip_serializing_if = "Option::is_none")]
pub loaded_slot: Option<u64>,
/// The current state of the drive
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
}

View File

@ -0,0 +1,55 @@
use ::serde::{Deserialize, Serialize};
use proxmox_schema::api;
#[api()]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Optional Device Identification Attributes
pub struct OptionalDeviceIdentification {
/// Vendor (autodetected)
#[serde(skip_serializing_if = "Option::is_none")]
pub vendor: Option<String>,
/// Model (autodetected)
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
/// Serial number (autodetected)
#[serde(skip_serializing_if = "Option::is_none")]
pub serial: Option<String>,
}
#[api()]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Kind of device
pub enum DeviceKind {
/// Tape changer (Autoloader, Robot)
Changer,
/// Normal SCSI tape device
Tape,
}
#[api(
properties: {
kind: {
type: DeviceKind,
},
},
)]
#[derive(Debug, Serialize, Deserialize)]
/// Tape device information
pub struct TapeDeviceInfo {
pub kind: DeviceKind,
/// Path to the linux device node
pub path: String,
/// Serial number (autodetected)
pub serial: String,
/// Vendor (autodetected)
pub vendor: String,
/// Model (autodetected)
pub model: String,
/// Device major number
pub major: u32,
/// Device minor number
pub minor: u32,
}

View File

@ -0,0 +1,350 @@
//! Types for tape drive API
use anyhow::{bail, Error};
use serde::{Deserialize, Serialize};
use proxmox_schema::{api, IntegerSchema, Schema, StringSchema, Updater};
use crate::{OptionalDeviceIdentification, CHANGER_NAME_SCHEMA, PROXMOX_SAFE_ID_FORMAT};
pub const DRIVE_NAME_SCHEMA: Schema = StringSchema::new("Drive Identifier.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(3)
.max_length(32)
.schema();
pub const LTO_DRIVE_PATH_SCHEMA: Schema =
StringSchema::new("The path to a LTO SCSI-generic tape device (i.e. '/dev/sg0')").schema();
pub const CHANGER_DRIVENUM_SCHEMA: Schema =
IntegerSchema::new("Associated changer drive number (requires option changer)")
.minimum(0)
.maximum(255)
.default(0)
.schema();
#[api(
properties: {
name: {
schema: DRIVE_NAME_SCHEMA,
}
}
)]
#[derive(Serialize, Deserialize)]
/// Simulate tape drives (only for test and debug)
#[serde(rename_all = "kebab-case")]
pub struct VirtualTapeDrive {
pub name: String,
/// Path to directory
pub path: String,
/// Virtual tape size
#[serde(skip_serializing_if = "Option::is_none")]
pub max_size: Option<usize>,
}
#[api(
properties: {
name: {
schema: DRIVE_NAME_SCHEMA,
},
path: {
schema: LTO_DRIVE_PATH_SCHEMA,
},
changer: {
schema: CHANGER_NAME_SCHEMA,
optional: true,
},
"changer-drivenum": {
schema: CHANGER_DRIVENUM_SCHEMA,
optional: true,
},
}
)]
#[derive(Serialize, Deserialize, Updater, Clone)]
#[serde(rename_all = "kebab-case")]
/// Lto SCSI tape driver
pub struct LtoTapeDrive {
#[updater(skip)]
pub name: String,
pub path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub changer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub changer_drivenum: Option<u64>,
}
#[api(
properties: {
config: {
type: LtoTapeDrive,
},
info: {
type: OptionalDeviceIdentification,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Drive list entry
pub struct DriveListEntry {
#[serde(flatten)]
pub config: LtoTapeDrive,
#[serde(flatten)]
pub info: OptionalDeviceIdentification,
/// the state of the drive if locked
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
/// Current device activity
#[serde(skip_serializing_if = "Option::is_none")]
pub activity: Option<DeviceActivity>,
}
#[api()]
#[derive(Serialize, Deserialize)]
/// Medium auxiliary memory attributes (MAM)
pub struct MamAttribute {
/// Attribute id
pub id: u16,
/// Attribute name
pub name: String,
/// Attribute value
pub value: String,
}
#[api()]
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq)]
/// The density of a tape medium, derived from the LTO version.
pub enum TapeDensity {
/// Unknown (no media loaded)
Unknown,
/// LTO1
LTO1,
/// LTO2
LTO2,
/// LTO3
LTO3,
/// LTO4
LTO4,
/// LTO5
LTO5,
/// LTO6
LTO6,
/// LTO7
LTO7,
/// LTO7M8
LTO7M8,
/// LTO8
LTO8,
/// LTO9
LTO9,
}
impl TryFrom<u8> for TapeDensity {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
let density = match value {
0x00 => TapeDensity::Unknown,
0x40 => TapeDensity::LTO1,
0x42 => TapeDensity::LTO2,
0x44 => TapeDensity::LTO3,
0x46 => TapeDensity::LTO4,
0x58 => TapeDensity::LTO5,
0x5a => TapeDensity::LTO6,
0x5c => TapeDensity::LTO7,
0x5d => TapeDensity::LTO7M8,
0x5e => TapeDensity::LTO8,
0x60 => TapeDensity::LTO9,
_ => bail!("unknown tape density code 0x{:02x}", value),
};
Ok(density)
}
}
#[api(
properties: {
density: {
type: TapeDensity,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Drive/Media status for Lto SCSI drives.
///
/// Media related data is optional - only set if there is a medium
/// loaded.
pub struct LtoDriveAndMediaStatus {
/// Vendor
pub vendor: String,
/// Product
pub product: String,
/// Revision
pub revision: String,
/// Block size (0 is variable size)
pub blocksize: u32,
/// Compression enabled
pub compression: bool,
/// Drive buffer mode
pub buffer_mode: u8,
/// Tape density
pub density: TapeDensity,
/// Media is write protected
#[serde(skip_serializing_if = "Option::is_none")]
pub write_protect: Option<bool>,
/// Tape Alert Flags
#[serde(skip_serializing_if = "Option::is_none")]
pub alert_flags: Option<String>,
/// Current file number
#[serde(skip_serializing_if = "Option::is_none")]
pub file_number: Option<u64>,
/// Current block number
#[serde(skip_serializing_if = "Option::is_none")]
pub block_number: Option<u64>,
/// Medium Manufacture Date (epoch)
#[serde(skip_serializing_if = "Option::is_none")]
pub manufactured: Option<i64>,
/// Total Bytes Read in Medium Life
#[serde(skip_serializing_if = "Option::is_none")]
pub bytes_read: Option<u64>,
/// Total Bytes Written in Medium Life
#[serde(skip_serializing_if = "Option::is_none")]
pub bytes_written: Option<u64>,
/// Number of mounts for the current volume (i.e., Thread Count)
#[serde(skip_serializing_if = "Option::is_none")]
pub volume_mounts: Option<u64>,
/// Count of the total number of times the medium has passed over
/// the head.
#[serde(skip_serializing_if = "Option::is_none")]
pub medium_passes: Option<u64>,
/// Estimated tape wearout factor (assuming max. 16000 end-to-end passes)
#[serde(skip_serializing_if = "Option::is_none")]
pub medium_wearout: Option<f64>,
/// Current device activity
#[serde(skip_serializing_if = "Option::is_none")]
pub drive_activity: Option<DeviceActivity>,
}
#[api()]
/// Volume statistics from SCSI log page 17h
#[derive(Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Lp17VolumeStatistics {
/// Volume mounts (thread count)
pub volume_mounts: u64,
/// Total data sets written
pub volume_datasets_written: u64,
/// Write retries
pub volume_recovered_write_data_errors: u64,
/// Total unrecovered write errors
pub volume_unrecovered_write_data_errors: u64,
/// Total suspended writes
pub volume_write_servo_errors: u64,
/// Total fatal suspended writes
pub volume_unrecovered_write_servo_errors: u64,
/// Total datasets read
pub volume_datasets_read: u64,
/// Total read retries
pub volume_recovered_read_errors: u64,
/// Total unrecovered read errors
pub volume_unrecovered_read_errors: u64,
/// Last mount unrecovered write errors
pub last_mount_unrecovered_write_errors: u64,
/// Last mount unrecovered read errors
pub last_mount_unrecovered_read_errors: u64,
/// Last mount bytes written
pub last_mount_bytes_written: u64,
/// Last mount bytes read
pub last_mount_bytes_read: u64,
/// Lifetime bytes written
pub lifetime_bytes_written: u64,
/// Lifetime bytes read
pub lifetime_bytes_read: u64,
/// Last load write compression ratio
pub last_load_write_compression_ratio: u64,
/// Last load read compression ratio
pub last_load_read_compression_ratio: u64,
/// Medium mount time
pub medium_mount_time: u64,
/// Medium ready time
pub medium_ready_time: u64,
/// Total native capacity
pub total_native_capacity: u64,
/// Total used native capacity
pub total_used_native_capacity: u64,
/// Write protect
pub write_protect: bool,
/// Volume is WORM
pub worm: bool,
/// Beginning of medium passes
pub beginning_of_medium_passes: u64,
/// Middle of medium passes
pub middle_of_tape_passes: u64,
/// Volume serial number
pub serial: String,
}
/// The DT Device Activity from DT Device Status LP page
#[api]
#[derive(Copy, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum DeviceActivity {
/// No activity
NoActivity,
/// Cleaning
Cleaning,
/// Loading
Loading,
/// Unloading
Unloading,
/// Other unspecified activity
Other,
/// Reading
Reading,
/// Writing
Writing,
/// Locating
Locating,
/// Rewinding
Rewinding,
/// Erasing
Erasing,
/// Formatting
Formatting,
/// Calibrating
Calibrating,
/// Other (DT)
OtherDT,
/// Updating microcode
MicrocodeUpdate,
/// Reading encrypted data
ReadingEncrypted,
/// Writing encrypted data
WritingEncrypted,
}
impl TryFrom<u8> for DeviceActivity {
type Error = Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0x00 => DeviceActivity::NoActivity,
0x01 => DeviceActivity::Cleaning,
0x02 => DeviceActivity::Loading,
0x03 => DeviceActivity::Unloading,
0x04 => DeviceActivity::Other,
0x05 => DeviceActivity::Reading,
0x06 => DeviceActivity::Writing,
0x07 => DeviceActivity::Locating,
0x08 => DeviceActivity::Rewinding,
0x09 => DeviceActivity::Erasing,
0x0A => DeviceActivity::Formatting,
0x0B => DeviceActivity::Calibrating,
0x0C => DeviceActivity::OtherDT,
0x0D => DeviceActivity::MicrocodeUpdate,
0x0E => DeviceActivity::ReadingEncrypted,
0x0F => DeviceActivity::WritingEncrypted,
other => bail!("invalid DT device activity value: {:x}", other),
})
}
}

View File

@ -0,0 +1,179 @@
use ::serde::{Deserialize, Serialize};
use proxmox_schema::*;
use proxmox_uuid::Uuid;
use crate::{MediaLocation, MediaStatus, UUID_FORMAT};
pub const MEDIA_SET_UUID_SCHEMA: Schema = StringSchema::new(
"MediaSet Uuid (We use the all-zero Uuid to reseve an empty media for a specific pool).",
)
.format(&UUID_FORMAT)
.schema();
pub const MEDIA_UUID_SCHEMA: Schema = StringSchema::new("Media Uuid.")
.format(&UUID_FORMAT)
.schema();
#[api(
properties: {
"media-set-uuid": {
schema: MEDIA_SET_UUID_SCHEMA,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Media Set list entry
pub struct MediaSetListEntry {
/// Media set name
pub media_set_name: String,
pub media_set_uuid: Uuid,
/// MediaSet creation time stamp
pub media_set_ctime: i64,
/// Media Pool
pub pool: String,
}
#[api(
properties: {
location: {
type: MediaLocation,
},
status: {
type: MediaStatus,
},
uuid: {
schema: MEDIA_UUID_SCHEMA,
},
"media-set-uuid": {
schema: MEDIA_SET_UUID_SCHEMA,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Media list entry
pub struct MediaListEntry {
/// Media label text (or Barcode)
pub label_text: String,
pub uuid: Uuid,
/// Creation time stamp
pub ctime: i64,
pub location: MediaLocation,
pub status: MediaStatus,
/// Expired flag
pub expired: bool,
/// Catalog status OK
pub catalog: bool,
/// Media set name
#[serde(skip_serializing_if = "Option::is_none")]
pub media_set_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub media_set_uuid: Option<Uuid>,
/// Media set seq_nr
#[serde(skip_serializing_if = "Option::is_none")]
pub seq_nr: Option<u64>,
/// MediaSet creation time stamp
#[serde(skip_serializing_if = "Option::is_none")]
pub media_set_ctime: Option<i64>,
/// Media Pool
#[serde(skip_serializing_if = "Option::is_none")]
pub pool: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// Bytes currently used
pub bytes_used: Option<u64>,
}
#[api(
properties: {
uuid: {
schema: MEDIA_UUID_SCHEMA,
},
"media-set-uuid": {
schema: MEDIA_SET_UUID_SCHEMA,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Media label info
pub struct MediaIdFlat {
/// Unique ID
pub uuid: Uuid,
/// Media label text (or Barcode)
pub label_text: String,
/// Creation time stamp
pub ctime: i64,
// All MediaSet properties are optional here
/// MediaSet Pool
#[serde(skip_serializing_if = "Option::is_none")]
pub pool: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub media_set_uuid: Option<Uuid>,
/// MediaSet media sequence number
#[serde(skip_serializing_if = "Option::is_none")]
pub seq_nr: Option<u64>,
/// MediaSet Creation time stamp
#[serde(skip_serializing_if = "Option::is_none")]
pub media_set_ctime: Option<i64>,
/// Encryption key fingerprint
#[serde(skip_serializing_if = "Option::is_none")]
pub encryption_key_fingerprint: Option<String>,
}
#[api(
properties: {
uuid: {
schema: MEDIA_UUID_SCHEMA,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Label with optional Uuid
pub struct LabelUuidMap {
/// Changer label text (or Barcode)
pub label_text: String,
/// Associated Uuid (if any)
pub uuid: Option<Uuid>,
}
#[api(
properties: {
uuid: {
schema: MEDIA_UUID_SCHEMA,
},
"media-set-uuid": {
schema: MEDIA_SET_UUID_SCHEMA,
},
},
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Media content list entry
pub struct MediaContentEntry {
/// Media label text (or Barcode)
pub label_text: String,
/// Media Uuid
pub uuid: Uuid,
/// Media set name
pub media_set_name: String,
/// Media set uuid
pub media_set_uuid: Uuid,
/// MediaSet Creation time stamp
pub media_set_ctime: i64,
/// Media set seq_nr
pub seq_nr: u64,
/// Media Pool
pub pool: String,
/// Datastore Name
pub store: String,
/// Backup snapshot
pub snapshot: String,
/// Snapshot creation time (epoch)
pub backup_time: i64,
}

View File

@ -0,0 +1,80 @@
use anyhow::{bail, Error};
use proxmox_schema::{ApiStringFormat, Schema, StringSchema};
use crate::{CHANGER_NAME_SCHEMA, PROXMOX_SAFE_ID_FORMAT};
pub const VAULT_NAME_SCHEMA: Schema = StringSchema::new("Vault name.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(3)
.max_length(32)
.schema();
#[derive(Debug, PartialEq, Eq, Clone)]
/// Media location
pub enum MediaLocation {
/// Ready for use (inside tape library)
Online(String),
/// Local available, but need to be mounted (insert into tape
/// drive)
Offline,
/// Media is inside a Vault
Vault(String),
}
proxmox_serde::forward_deserialize_to_from_str!(MediaLocation);
proxmox_serde::forward_serialize_to_display!(MediaLocation);
impl proxmox_schema::ApiType for MediaLocation {
const API_SCHEMA: Schema = StringSchema::new(
"Media location (e.g. 'offline', 'online-<changer_name>', 'vault-<vault_name>')",
)
.format(&ApiStringFormat::VerifyFn(|text| {
let location: MediaLocation = text.parse()?;
match location {
MediaLocation::Online(ref changer) => {
CHANGER_NAME_SCHEMA.parse_simple_value(changer)?;
}
MediaLocation::Vault(ref vault) => {
VAULT_NAME_SCHEMA.parse_simple_value(vault)?;
}
MediaLocation::Offline => { /* OK */ }
}
Ok(())
}))
.schema();
}
impl std::fmt::Display for MediaLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MediaLocation::Offline => {
write!(f, "offline")
}
MediaLocation::Online(changer) => {
write!(f, "online-{}", changer)
}
MediaLocation::Vault(vault) => {
write!(f, "vault-{}", vault)
}
}
}
}
impl std::str::FromStr for MediaLocation {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "offline" {
return Ok(MediaLocation::Offline);
}
if let Some(changer) = s.strip_prefix("online-") {
return Ok(MediaLocation::Online(changer.to_string()));
}
if let Some(vault) = s.strip_prefix("vault-") {
return Ok(MediaLocation::Vault(vault.to_string()));
}
bail!("MediaLocation parse error");
}
}

View File

@ -0,0 +1,161 @@
//! Types for tape media pool API
//!
//! Note: Both MediaSetPolicy and RetentionPolicy are complex enums,
//! so we cannot use them directly for the API. Instead, we represent
//! them as String.
use std::str::FromStr;
use anyhow::Error;
use serde::{Deserialize, Serialize};
use proxmox_schema::{api, ApiStringFormat, Schema, StringSchema, Updater};
use proxmox_time::{CalendarEvent, TimeSpan};
use crate::{
PROXMOX_SAFE_ID_FORMAT, SINGLE_LINE_COMMENT_FORMAT, SINGLE_LINE_COMMENT_SCHEMA,
TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
};
pub const MEDIA_POOL_NAME_SCHEMA: Schema = StringSchema::new("Media pool name.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(2)
.max_length(32)
.schema();
pub const MEDIA_SET_NAMING_TEMPLATE_SCHEMA: Schema = StringSchema::new(
"Media set naming template (may contain strftime() time format specifications).",
)
.format(&SINGLE_LINE_COMMENT_FORMAT)
.min_length(2)
.max_length(64)
.schema();
pub const MEDIA_SET_ALLOCATION_POLICY_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|s| {
MediaSetPolicy::from_str(s)?;
Ok(())
});
pub const MEDIA_SET_ALLOCATION_POLICY_SCHEMA: Schema =
StringSchema::new("Media set allocation policy ('continue', 'always', or a calendar event).")
.format(&MEDIA_SET_ALLOCATION_POLICY_FORMAT)
.schema();
/// Media set allocation policy
pub enum MediaSetPolicy {
/// Try to use the current media set
ContinueCurrent,
/// Each backup job creates a new media set
AlwaysCreate,
/// Create a new set when the specified CalendarEvent triggers
CreateAt(CalendarEvent),
}
impl std::str::FromStr for MediaSetPolicy {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "continue" {
return Ok(MediaSetPolicy::ContinueCurrent);
}
if s == "always" {
return Ok(MediaSetPolicy::AlwaysCreate);
}
let event = s.parse()?;
Ok(MediaSetPolicy::CreateAt(event))
}
}
pub const MEDIA_RETENTION_POLICY_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|s| {
RetentionPolicy::from_str(s)?;
Ok(())
});
pub const MEDIA_RETENTION_POLICY_SCHEMA: Schema =
StringSchema::new("Media retention policy ('overwrite', 'keep', or time span).")
.format(&MEDIA_RETENTION_POLICY_FORMAT)
.schema();
/// Media retention Policy
pub enum RetentionPolicy {
/// Always overwrite media
OverwriteAlways,
/// Protect data for the timespan specified
ProtectFor(TimeSpan),
/// Never overwrite data
KeepForever,
}
impl std::str::FromStr for RetentionPolicy {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "overwrite" {
return Ok(RetentionPolicy::OverwriteAlways);
}
if s == "keep" {
return Ok(RetentionPolicy::KeepForever);
}
let time_span = s.parse()?;
Ok(RetentionPolicy::ProtectFor(time_span))
}
}
#[api(
properties: {
name: {
schema: MEDIA_POOL_NAME_SCHEMA,
},
allocation: {
schema: MEDIA_SET_ALLOCATION_POLICY_SCHEMA,
optional: true,
},
retention: {
schema: MEDIA_RETENTION_POLICY_SCHEMA,
optional: true,
},
template: {
schema: MEDIA_SET_NAMING_TEMPLATE_SCHEMA,
optional: true,
},
encrypt: {
schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
optional: true,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
},
)]
#[derive(Serialize, Deserialize, Updater)]
/// Media pool configuration
pub struct MediaPoolConfig {
/// The pool name
#[updater(skip)]
pub name: String,
/// Media Set allocation policy
#[serde(skip_serializing_if = "Option::is_none")]
pub allocation: Option<String>,
/// Media retention policy
#[serde(skip_serializing_if = "Option::is_none")]
pub retention: Option<String>,
/// Media set naming template (default "%c")
///
/// The template is UTF8 text, and can include strftime time
/// format specifications.
#[serde(skip_serializing_if = "Option::is_none")]
pub template: Option<String>,
/// Encryption key fingerprint
///
/// If set, encrypt all data using the specified key.
#[serde(skip_serializing_if = "Option::is_none")]
pub encrypt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}

View File

@ -0,0 +1,21 @@
use serde::{Deserialize, Serialize};
use proxmox_schema::api;
#[api()]
/// Media status
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// Media Status
pub enum MediaStatus {
/// Media is ready to be written
Writable,
/// Media is full (contains data)
Full,
/// Media is marked as unknown, needs rescan
Unknown,
/// Media is marked as damaged
Damaged,
/// Media is marked as retired
Retired,
}

View File

@ -0,0 +1,92 @@
//! Types for tape backup API
mod device;
pub use device::*;
mod changer;
pub use changer::*;
mod drive;
pub use drive::*;
mod media_pool;
pub use media_pool::*;
mod media_status;
pub use media_status::*;
mod media_location;
pub use media_location::*;
mod media;
pub use media::*;
use const_format::concatcp;
use serde::{Deserialize, Serialize};
use proxmox_schema::{api, const_regex, ApiStringFormat, Schema, StringSchema};
use proxmox_uuid::Uuid;
use crate::{
BackupType, BACKUP_ID_SCHEMA, BACKUP_NS_PATH_RE, FINGERPRINT_SHA256_FORMAT,
PROXMOX_SAFE_ID_REGEX_STR, SNAPSHOT_PATH_REGEX_STR,
};
const_regex! {
pub TAPE_RESTORE_SNAPSHOT_REGEX = concatcp!(r"^", PROXMOX_SAFE_ID_REGEX_STR, r":(?:", BACKUP_NS_PATH_RE,")?", SNAPSHOT_PATH_REGEX_STR, r"$");
}
pub const TAPE_RESTORE_SNAPSHOT_FORMAT: ApiStringFormat =
ApiStringFormat::Pattern(&TAPE_RESTORE_SNAPSHOT_REGEX);
pub const TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA: Schema =
StringSchema::new("Tape encryption key fingerprint (sha256).")
.format(&FINGERPRINT_SHA256_FORMAT)
.schema();
pub const TAPE_RESTORE_SNAPSHOT_SCHEMA: Schema =
StringSchema::new("A snapshot in the format: 'store:[ns/namespace/...]type/id/time")
.format(&TAPE_RESTORE_SNAPSHOT_FORMAT)
.type_text("store:[ns/namespace/...]type/id/time")
.schema();
#[api(
properties: {
pool: {
schema: MEDIA_POOL_NAME_SCHEMA,
optional: true,
},
"label-text": {
schema: MEDIA_LABEL_SCHEMA,
optional: true,
},
"media": {
schema: MEDIA_UUID_SCHEMA,
optional: true,
},
"media-set": {
schema: MEDIA_SET_UUID_SCHEMA,
optional: true,
},
"backup-type": {
type: BackupType,
optional: true,
},
"backup-id": {
schema: BACKUP_ID_SCHEMA,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Content list filter parameters
pub struct MediaContentListFilter {
pub pool: Option<String>,
pub label_text: Option<String>,
pub media: Option<Uuid>,
pub media_set: Option<Uuid>,
pub backup_type: Option<BackupType>,
pub backup_id: Option<String>,
}

View File

@ -0,0 +1,168 @@
use serde::{Deserialize, Serialize};
use proxmox_human_byte::HumanByte;
use proxmox_schema::{api, ApiType, Schema, StringSchema, Updater};
use crate::{
CIDR_SCHEMA, DAILY_DURATION_FORMAT, PROXMOX_SAFE_ID_FORMAT, SINGLE_LINE_COMMENT_SCHEMA,
};
pub const TRAFFIC_CONTROL_TIMEFRAME_SCHEMA: Schema =
StringSchema::new("Timeframe to specify when the rule is active.")
.format(&DAILY_DURATION_FORMAT)
.schema();
pub const TRAFFIC_CONTROL_ID_SCHEMA: Schema = StringSchema::new("Rule ID.")
.format(&PROXMOX_SAFE_ID_FORMAT)
.min_length(3)
.max_length(32)
.schema();
#[api(
properties: {
"rate-in": {
type: HumanByte,
optional: true,
},
"burst-in": {
type: HumanByte,
optional: true,
},
"rate-out": {
type: HumanByte,
optional: true,
},
"burst-out": {
type: HumanByte,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize, Default, Clone, Updater, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Rate Limit Configuration
pub struct RateLimitConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub rate_in: Option<HumanByte>,
#[serde(skip_serializing_if = "Option::is_none")]
pub burst_in: Option<HumanByte>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rate_out: Option<HumanByte>,
#[serde(skip_serializing_if = "Option::is_none")]
pub burst_out: Option<HumanByte>,
}
impl RateLimitConfig {
pub fn with_same_inout(rate: Option<HumanByte>, burst: Option<HumanByte>) -> Self {
Self {
rate_in: rate,
burst_in: burst,
rate_out: rate,
burst_out: burst,
}
}
/// Create a [RateLimitConfig] from a [ClientRateLimitConfig]
pub fn from_client_config(limit: ClientRateLimitConfig) -> Self {
Self::with_same_inout(limit.rate, limit.burst)
}
}
const CLIENT_RATE_LIMIT_SCHEMA: Schema = HumanByte::API_SCHEMA
.unwrap_string_schema_cloned()
.description("Rate limit (for Token bucket filter) in bytes/s with optional unit (B, KB (base 10), MB, GB, ..., KiB (base 2), MiB, Gib, ...).")
.schema();
const CLIENT_BURST_SCHEMA: Schema = HumanByte::API_SCHEMA
.unwrap_string_schema_cloned()
.description("Size of the token bucket (for Token bucket filter) in bytes with optional unit (B, KB (base 10), MB, GB, ..., KiB (base 2), MiB, Gib, ...).")
.schema();
#[api(
properties: {
rate: {
schema: CLIENT_RATE_LIMIT_SCHEMA,
optional: true,
},
burst: {
schema: CLIENT_BURST_SCHEMA,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize, Default, Clone)]
#[serde(rename_all = "kebab-case")]
/// Client Rate Limit Configuration
pub struct ClientRateLimitConfig {
#[serde(skip_serializing_if = "Option::is_none")]
rate: Option<HumanByte>,
#[serde(skip_serializing_if = "Option::is_none")]
burst: Option<HumanByte>,
}
#[api(
properties: {
name: {
schema: TRAFFIC_CONTROL_ID_SCHEMA,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
limit: {
type: RateLimitConfig,
},
network: {
type: Array,
items: {
schema: CIDR_SCHEMA,
},
},
timeframe: {
type: Array,
items: {
schema: TRAFFIC_CONTROL_TIMEFRAME_SCHEMA,
},
optional: true,
},
},
)]
#[derive(Clone, Serialize, Deserialize, PartialEq, Updater)]
#[serde(rename_all = "kebab-case")]
/// Traffic control rule
pub struct TrafficControlRule {
#[updater(skip)]
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
/// Rule applies to Source IPs within this networks
pub network: Vec<String>,
#[serde(flatten)]
pub limit: RateLimitConfig,
// fixme: expose this?
// /// Bandwidth is shared across all connections
// #[serde(skip_serializing_if="Option::is_none")]
// pub shared: Option<bool>,
/// Enable the rule at specific times
#[serde(skip_serializing_if = "Option::is_none")]
pub timeframe: Option<Vec<String>>,
}
#[api(
properties: {
config: {
type: TrafficControlRule,
},
},
)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// Traffic control rule config with current rates
pub struct TrafficControlCurrentRate {
#[serde(flatten)]
pub config: TrafficControlRule,
/// Current ingress rate in bytes/second
pub cur_rate_in: u64,
/// Current egress rate in bytes/second
pub cur_rate_out: u64,
}

226
pbs-api-types/src/user.rs Normal file
View File

@ -0,0 +1,226 @@
use serde::{Deserialize, Serialize};
use proxmox_schema::{api, BooleanSchema, IntegerSchema, Schema, StringSchema, Updater};
use super::userid::{Authid, Userid, PROXMOX_TOKEN_ID_SCHEMA};
use super::{SINGLE_LINE_COMMENT_FORMAT, SINGLE_LINE_COMMENT_SCHEMA};
pub const ENABLE_USER_SCHEMA: Schema = BooleanSchema::new(
"Enable the account (default). You can set this to '0' to disable the account.",
)
.default(true)
.schema();
pub const EXPIRE_USER_SCHEMA: Schema = IntegerSchema::new(
"Account expiration date (seconds since epoch). '0' means no expiration date.",
)
.default(0)
.minimum(0)
.schema();
pub const FIRST_NAME_SCHEMA: Schema = StringSchema::new("First name.")
.format(&SINGLE_LINE_COMMENT_FORMAT)
.min_length(2)
.max_length(64)
.schema();
pub const LAST_NAME_SCHEMA: Schema = StringSchema::new("Last name.")
.format(&SINGLE_LINE_COMMENT_FORMAT)
.min_length(2)
.max_length(64)
.schema();
pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
.format(&SINGLE_LINE_COMMENT_FORMAT)
.min_length(2)
.max_length(64)
.schema();
#[api(
properties: {
userid: {
type: Userid,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
enable: {
optional: true,
schema: ENABLE_USER_SCHEMA,
},
expire: {
optional: true,
schema: EXPIRE_USER_SCHEMA,
},
firstname: {
optional: true,
schema: FIRST_NAME_SCHEMA,
},
lastname: {
schema: LAST_NAME_SCHEMA,
optional: true,
},
email: {
schema: EMAIL_SCHEMA,
optional: true,
},
tokens: {
type: Array,
optional: true,
description: "List of user's API tokens.",
items: {
type: ApiToken
},
},
"totp-locked": {
type: bool,
optional: true,
default: false,
description: "True if the user is currently locked out of TOTP factors",
},
"tfa-locked-until": {
optional: true,
description: "Contains a timestamp until when a user is locked out of 2nd factors",
},
}
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// User properties with added list of ApiTokens
pub struct UserWithTokens {
pub userid: Userid,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub firstname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lastname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub tokens: Vec<ApiToken>,
#[serde(skip_serializing_if = "bool_is_false", default)]
pub totp_locked: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub tfa_locked_until: Option<i64>,
}
fn bool_is_false(b: &bool) -> bool {
!b
}
#[api(
properties: {
tokenid: {
schema: PROXMOX_TOKEN_ID_SCHEMA,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
enable: {
optional: true,
schema: ENABLE_USER_SCHEMA,
},
expire: {
optional: true,
schema: EXPIRE_USER_SCHEMA,
},
}
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
/// ApiToken properties.
pub struct ApiToken {
pub tokenid: Authid,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire: Option<i64>,
}
impl ApiToken {
pub fn is_active(&self) -> bool {
if !self.enable.unwrap_or(true) {
return false;
}
if let Some(expire) = self.expire {
let now = proxmox_time::epoch_i64();
if expire > 0 && expire <= now {
return false;
}
}
true
}
}
#[api(
properties: {
userid: {
type: Userid,
},
comment: {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
enable: {
optional: true,
schema: ENABLE_USER_SCHEMA,
},
expire: {
optional: true,
schema: EXPIRE_USER_SCHEMA,
},
firstname: {
optional: true,
schema: FIRST_NAME_SCHEMA,
},
lastname: {
schema: LAST_NAME_SCHEMA,
optional: true,
},
email: {
schema: EMAIL_SCHEMA,
optional: true,
},
}
)]
#[derive(Serialize, Deserialize, Updater, PartialEq, Eq)]
/// User properties.
pub struct User {
#[updater(skip)]
pub userid: Userid,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub firstname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lastname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}
impl User {
pub fn is_active(&self) -> bool {
if !self.enable.unwrap_or(true) {
return false;
}
if let Some(expire) = self.expire {
let now = proxmox_time::epoch_i64();
if expire > 0 && expire <= now {
return false;
}
}
true
}
}

View File

@ -0,0 +1,190 @@
//! Defines the types for the api version info endpoint
use std::cmp::Ordering;
use std::convert::TryFrom;
use anyhow::{format_err, Context};
use proxmox_schema::api;
#[api(
description: "Api version information",
properties: {
"version": {
description: "Version 'major.minor'",
type: String,
},
"release": {
description: "Version release",
type: String,
},
"repoid": {
description: "Version repository id",
type: String,
},
}
)]
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ApiVersionInfo {
pub version: String,
pub release: String,
pub repoid: String,
}
pub type ApiVersionMajor = u64;
pub type ApiVersionMinor = u64;
pub type ApiVersionRelease = u64;
#[derive(PartialEq, Eq)]
pub struct ApiVersion {
pub major: ApiVersionMajor,
pub minor: ApiVersionMinor,
pub release: ApiVersionRelease,
}
impl TryFrom<ApiVersionInfo> for ApiVersion {
type Error = anyhow::Error;
fn try_from(value: ApiVersionInfo) -> Result<Self, Self::Error> {
let (major, minor) = value
.version
.split_once('.')
.ok_or_else(|| format_err!("malformed API version {}", value.version))?;
let major: ApiVersionMajor = major
.parse()
.with_context(|| "failed to parse major version")?;
let minor: ApiVersionMinor = minor
.parse()
.with_context(|| "failed to parse minor version")?;
let release: ApiVersionRelease = value
.release
.parse()
.with_context(|| "failed to parse release version")?;
Ok(Self {
major,
minor,
release,
})
}
}
impl PartialOrd for ApiVersion {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let ordering = match (
self.major.cmp(&other.major),
self.minor.cmp(&other.minor),
self.release.cmp(&other.release),
) {
(Ordering::Equal, Ordering::Equal, ordering) => ordering,
(Ordering::Equal, ordering, _) => ordering,
(ordering, _, _) => ordering,
};
Some(ordering)
}
}
impl ApiVersion {
pub fn new(major: ApiVersionMajor, minor: ApiVersionMinor, release: ApiVersionRelease) -> Self {
Self {
major,
minor,
release,
}
}
}
#[test]
fn same_level_version_comarison() {
let major_base = ApiVersion::new(2, 0, 0);
let major_less = ApiVersion::new(1, 0, 0);
let major_greater = ApiVersion::new(3, 0, 0);
let minor_base = ApiVersion::new(2, 2, 0);
let minor_less = ApiVersion::new(2, 1, 0);
let minor_greater = ApiVersion::new(2, 3, 0);
let release_base = ApiVersion::new(2, 2, 2);
let release_less = ApiVersion::new(2, 2, 1);
let release_greater = ApiVersion::new(2, 2, 3);
assert!(major_base == major_base);
assert!(minor_base == minor_base);
assert!(release_base == release_base);
assert!(major_base > major_less);
assert!(major_base >= major_less);
assert!(major_base != major_less);
assert!(major_base < major_greater);
assert!(major_base <= major_greater);
assert!(major_base != major_greater);
assert!(minor_base > minor_less);
assert!(minor_base >= minor_less);
assert!(minor_base != minor_less);
assert!(minor_base < minor_greater);
assert!(minor_base <= minor_greater);
assert!(minor_base != minor_greater);
assert!(release_base > release_less);
assert!(release_base >= release_less);
assert!(release_base != release_less);
assert!(release_base < release_greater);
assert!(release_base <= release_greater);
assert!(release_base != release_greater);
}
#[test]
fn mixed_level_version_comarison() {
let major_base = ApiVersion::new(2, 0, 0);
let major_less = ApiVersion::new(1, 0, 0);
let major_greater = ApiVersion::new(3, 0, 0);
let minor_base = ApiVersion::new(2, 2, 0);
let minor_less = ApiVersion::new(2, 1, 0);
let minor_greater = ApiVersion::new(2, 3, 0);
let release_base = ApiVersion::new(2, 2, 2);
let release_less = ApiVersion::new(2, 2, 1);
let release_greater = ApiVersion::new(2, 2, 3);
assert!(major_base < minor_base);
assert!(major_base < minor_less);
assert!(major_base < minor_greater);
assert!(major_base < release_base);
assert!(major_base < release_less);
assert!(major_base < release_greater);
assert!(major_less < minor_base);
assert!(major_less < minor_less);
assert!(major_less < minor_greater);
assert!(major_less < release_base);
assert!(major_less < release_less);
assert!(major_less < release_greater);
assert!(major_greater > minor_base);
assert!(major_greater > minor_less);
assert!(major_greater > minor_greater);
assert!(major_greater > release_base);
assert!(major_greater > release_less);
assert!(major_greater > release_greater);
assert!(minor_base < release_base);
assert!(minor_base < release_less);
assert!(minor_base < release_greater);
assert!(minor_greater > release_base);
assert!(minor_greater > release_less);
assert!(minor_greater > release_greater);
assert!(minor_less < release_base);
assert!(minor_less < release_less);
assert!(minor_less < release_greater);
}

78
pbs-api-types/src/zfs.rs Normal file
View File

@ -0,0 +1,78 @@
use serde::{Deserialize, Serialize};
use proxmox_schema::*;
const_regex! {
pub ZPOOL_NAME_REGEX = r"^[a-zA-Z][a-z0-9A-Z\-_.:]+$";
}
pub const ZFS_ASHIFT_SCHEMA: Schema = IntegerSchema::new("Pool sector size exponent.")
.minimum(9)
.maximum(16)
.default(12)
.schema();
pub const ZPOOL_NAME_SCHEMA: Schema = StringSchema::new("ZFS Pool Name")
.format(&ApiStringFormat::Pattern(&ZPOOL_NAME_REGEX))
.schema();
#[api(default: "On")]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// The ZFS compression algorithm to use.
pub enum ZfsCompressionType {
/// Gnu Zip
Gzip,
/// LZ4
Lz4,
/// LZJB
Lzjb,
/// ZLE
Zle,
/// ZStd
ZStd,
/// Enable compression using the default algorithm.
On,
/// Disable compression.
Off,
}
#[api()]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
/// The ZFS RAID level to use.
pub enum ZfsRaidLevel {
/// Single Disk
Single,
/// Mirror
Mirror,
/// Raid10
Raid10,
/// RaidZ
RaidZ,
/// RaidZ2
RaidZ2,
/// RaidZ3
RaidZ3,
}
#[api()]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// zpool list item
pub struct ZpoolListItem {
/// zpool name
pub name: String,
/// Health
pub health: String,
/// Total size
pub size: u64,
/// Used size
pub alloc: u64,
/// Free space
pub free: u64,
/// ZFS fragnentation level
pub frag: u64,
/// ZFS deduplication ratio
pub dedup: f64,
}

View File

@ -0,0 +1,76 @@
use pbs_api_types::{BackupGroup, BackupType, GroupFilter};
use std::str::FromStr;
#[test]
fn test_no_filters() {
let group_filters = vec![];
let do_backup = [
"vm/101", "vm/102", "vm/103", "vm/104", "vm/105", "vm/106", "vm/107", "vm/108", "vm/109",
];
for id in do_backup {
assert!(BackupGroup::new(BackupType::Vm, id).apply_filters(&group_filters));
}
}
#[test]
fn test_include_filters() {
let group_filters = vec![GroupFilter::from_str("regex:.*10[2-8]").unwrap()];
let do_backup = [
"vm/102", "vm/103", "vm/104", "vm/105", "vm/106", "vm/107", "vm/108",
];
let dont_backup = ["vm/101", "vm/109"];
for id in do_backup {
assert!(BackupGroup::new(BackupType::Vm, id).apply_filters(&group_filters));
}
for id in dont_backup {
assert!(!BackupGroup::new(BackupType::Vm, id).apply_filters(&group_filters));
}
}
#[test]
fn test_exclude_filters() {
let group_filters = [
GroupFilter::from_str("exclude:regex:.*10[1-3]").unwrap(),
GroupFilter::from_str("exclude:regex:.*10[5-7]").unwrap(),
];
let do_backup = ["vm/104", "vm/108", "vm/109"];
let dont_backup = ["vm/101", "vm/102", "vm/103", "vm/105", "vm/106", "vm/107"];
for id in do_backup {
assert!(BackupGroup::new(BackupType::Vm, id).apply_filters(&group_filters));
}
for id in dont_backup {
assert!(!BackupGroup::new(BackupType::Vm, id).apply_filters(&group_filters));
}
}
#[test]
fn test_include_and_exclude_filters() {
let group_filters = [
GroupFilter::from_str("exclude:regex:.*10[1-3]").unwrap(),
GroupFilter::from_str("regex:.*10[2-8]").unwrap(),
GroupFilter::from_str("exclude:regex:.*10[5-7]").unwrap(),
];
let do_backup = ["vm/104", "vm/108"];
let dont_backup = [
"vm/101", "vm/102", "vm/103", "vm/105", "vm/106", "vm/107", "vm/109",
];
for id in do_backup {
assert!(BackupGroup::new(BackupType::Vm, id).apply_filters(&group_filters));
}
for id in dont_backup {
assert!(!BackupGroup::new(BackupType::Vm, id).apply_filters(&group_filters));
}
}

View File

@ -0,0 +1,43 @@
[package]
name = "proxmox-access-control"
description = "A collection of utilities to implement access control management."
version = "0.2.4"
authors.workspace = true
edition.workspace = true
exclude.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[dependencies]
anyhow.workspace = true
nix = { workspace = true, optional = true }
openssl = { workspace = true, optional = true }
serde.workspace = true
serde_json = { workspace = true, optional = true }
proxmox-auth-api = { workspace = true, features = [ "api-types" ] }
proxmox-config-digest = { workspace = true, optional = true, features = [ "openssl" ] }
proxmox-product-config = { workspace = true, optional = true }
proxmox-router = { workspace = true, optional = true }
proxmox-schema.workspace = true
proxmox-section-config = { workspace = true, optional = true }
proxmox-shared-memory = { workspace = true, optional = true }
proxmox-sys = { workspace = true, features = [ "crypt" ], optional = true }
proxmox-time = { workspace = true }
[features]
default = []
impl = [
"dep:nix",
"dep:openssl",
"dep:proxmox-config-digest",
"dep:proxmox-product-config",
"dep:proxmox-router",
"dep:proxmox-section-config",
"dep:proxmox-shared-memory",
"dep:proxmox-sys",
"dep:serde_json",
]

View File

@ -0,0 +1,48 @@
rust-proxmox-access-control (0.2.4-1) bookworm; urgency=medium
* rebuild with proxmox-schema 4.0
-- Proxmox Support Team <support@proxmox.com> Wed, 15 Jan 2025 12:47:56 +0100
rust-proxmox-access-control (0.2.3-1) bookworm; urgency=medium
* upgrade to current proxmox-router
* doc fixup
-- Proxmox Support Team <support@proxmox.com> Thu, 05 Sep 2024 14:25:02 +0200
rust-proxmox-access-control (0.2.2-1) bookworm; urgency=medium
* add init_user_config() to AccessControlConfig with a default
implementation so downstream can ensure default users such as 'root@pam'
are enabled if so desired
-- Proxmox Support Team <support@proxmox.com> Mon, 22 Jul 2024 09:04:59 +0200
rust-proxmox-access-control (0.2.1-1) bookworm; urgency=medium
* rebuild with proxmox-sys 6.0
-- Proxmox Support Team <support@proxmox.com> Thu, 11 Jul 2024 14:46:29 +0200
rust-proxmox-access-control (0.2.0-1) bookworm; urgency=medium
* change acl::config() and user::config() to return a ConfigDigest instead
of raw digest bytes
* various clippy fixes
-- Proxmox Support Team <support@proxmox.com> Thu, 04 Jul 2024 14:32:54 +0200
rust-proxmox-access-control (0.1.1-1) bookworm; urgency=medium
* upgrade proxmox-time to 2.0
-- Proxmox Support Team <support@proxmox.com> Thu, 20 Jun 2024 13:47:21 +0200
rust-proxmox-access-control (0.1.0-1) bookworm; urgency=medium
* initial packaging
-- Proxmox Support Team <support@proxmox.com> Wed, 19 Jun 2024 14:44:26 +0200

View File

@ -0,0 +1,70 @@
Source: rust-proxmox-access-control
Section: rust
Priority: optional
Build-Depends: debhelper-compat (= 13),
dh-sequence-cargo,
cargo:native <!nocheck>,
rustc:native (>= 1.80) <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-proxmox-auth-api-0.4+api-types-dev <!nocheck>,
librust-proxmox-auth-api-0.4+default-dev <!nocheck>,
librust-proxmox-schema-4+default-dev <!nocheck>,
librust-proxmox-time-2+default-dev <!nocheck>,
librust-serde-1+default-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.7.0
Vcs-Git: git://git.proxmox.com/git/proxmox.git
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
Homepage: https://proxmox.com
X-Cargo-Crate: proxmox-access-control
Rules-Requires-Root: no
Package: librust-proxmox-access-control-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
librust-proxmox-auth-api-0.4+api-types-dev,
librust-proxmox-auth-api-0.4+default-dev,
librust-proxmox-schema-4+default-dev,
librust-proxmox-time-2+default-dev,
librust-serde-1+default-dev
Suggests:
librust-proxmox-access-control+impl-dev (= ${binary:Version})
Provides:
librust-proxmox-access-control+default-dev (= ${binary:Version}),
librust-proxmox-access-control-0-dev (= ${binary:Version}),
librust-proxmox-access-control-0+default-dev (= ${binary:Version}),
librust-proxmox-access-control-0.2-dev (= ${binary:Version}),
librust-proxmox-access-control-0.2+default-dev (= ${binary:Version}),
librust-proxmox-access-control-0.2.4-dev (= ${binary:Version}),
librust-proxmox-access-control-0.2.4+default-dev (= ${binary:Version})
Description: Collection of utilities to implement access control management - Rust source code
Source code for Debianized Rust crate "proxmox-access-control"
Package: librust-proxmox-access-control+impl-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-access-control-dev (= ${binary:Version}),
librust-nix-0.26+default-dev (>= 0.26.1-~~),
librust-openssl-0.10+default-dev,
librust-proxmox-config-digest-0.1+default-dev,
librust-proxmox-config-digest-0.1+openssl-dev,
librust-proxmox-product-config-0.2+default-dev,
librust-proxmox-router-3+default-dev,
librust-proxmox-section-config-2+default-dev (>= 2.1.0-~~),
librust-proxmox-shared-memory-0.3+default-dev,
librust-proxmox-sys-0.6+crypt-dev (>= 0.6.5-~~),
librust-proxmox-sys-0.6+default-dev (>= 0.6.5-~~),
librust-serde-json-1+default-dev
Provides:
librust-proxmox-access-control-0+impl-dev (= ${binary:Version}),
librust-proxmox-access-control-0.2+impl-dev (= ${binary:Version}),
librust-proxmox-access-control-0.2.4+impl-dev (= ${binary:Version})
Description: Collection of utilities to implement access control management - feature "impl"
This metapackage enables feature "impl" for the Rust proxmox-access-control
crate, by pulling in any additional dependencies needed by that feature.

View File

@ -0,0 +1,18 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files:
*
Copyright: 2024 Proxmox Server Solutions GmbH <support@proxmox.com>
License: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -0,0 +1,7 @@
overlay = "."
crate_src_path = ".."
maintainer = "Proxmox Support Team <support@proxmox.com>"
[source]
vcs_git = "git://git.proxmox.com/git/proxmox.git"
vcs_browser = "https://git.proxmox.com/?p=proxmox.git"

View File

@ -0,0 +1 @@
3.0 (native)

View File

@ -0,0 +1,992 @@
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::io::Write;
use std::path::Path;
use std::sync::{Arc, OnceLock, RwLock};
use anyhow::{bail, Error};
use proxmox_auth_api::types::{Authid, Userid};
use proxmox_config_digest::ConfigDigest;
use proxmox_product_config::{open_api_lockfile, replace_privileged_config, ApiLockGuard};
use crate::init::{access_conf, acl_config, acl_config_lock};
pub fn split_acl_path(path: &str) -> Vec<&str> {
let items = path.split('/');
let mut components = vec![];
for name in items {
if name.is_empty() {
continue;
}
components.push(name);
}
components
}
/// Tree representing a parsed acl.cfg
#[derive(Default)]
pub struct AclTree {
/// Root node of the tree.
///
/// The rest of the tree is available via [find_node()](AclTree::find_node()) or an
/// [`AclTreeNode`]'s [children](AclTreeNode::children) member.
pub root: AclTreeNode,
}
/// Node representing ACLs for a certain ACL path.
#[derive(Default)]
pub struct AclTreeNode {
/// `User` or `Token` ACLs for this node.
pub users: HashMap<Authid, HashMap<String, bool>>,
/// `Group` ACLs for this node (not yet implemented)
pub groups: HashMap<String, HashMap<String, bool>>,
/// `AclTreeNodes` representing ACL paths directly below the current one.
pub children: BTreeMap<String, AclTreeNode>,
}
impl AclTreeNode {
/// Creates a new, empty AclTreeNode.
pub fn new() -> Self {
Self {
users: HashMap::new(),
groups: HashMap::new(),
children: BTreeMap::new(),
}
}
/// Returns applicable role and their propagation status for a given [Authid].
///
/// If the `Authid` is a `User` that has no specific `Roles` configured on this node,
/// applicable `Group` roles will be returned instead.
///
/// If `leaf` is `false`, only those roles where the propagate flag in the ACL is set to `true`
/// are returned. Otherwise, all roles will be returned.
pub fn extract_roles(&self, auth_id: &Authid, leaf: bool) -> HashMap<String, bool> {
let user_roles = self.extract_user_roles(auth_id, leaf);
if !user_roles.is_empty() || auth_id.is_token() {
// user privs always override group privs
return user_roles;
};
self.extract_group_roles(auth_id.user(), leaf)
}
fn extract_user_roles(&self, auth_id: &Authid, leaf: bool) -> HashMap<String, bool> {
let mut map = HashMap::new();
let roles = match self.users.get(auth_id) {
Some(m) => m,
None => return map,
};
for (role, propagate) in roles {
if *propagate || leaf {
if access_conf().role_no_access() == Some(role) {
// return a map with a single role 'NoAccess'
let mut map = HashMap::new();
map.insert(role.to_string(), false);
return map;
}
map.insert(role.to_string(), *propagate);
}
}
map
}
fn extract_group_roles(&self, _user: &Userid, leaf: bool) -> HashMap<String, bool> {
let mut map = HashMap::new();
for roles in self.groups.values() {
let is_member = false; // fixme: check if user is member of the group
if !is_member {
continue;
}
for (role, propagate) in roles {
if *propagate || leaf {
if access_conf().role_no_access() == Some(role) {
// return a map with a single role 'NoAccess'
let mut map = HashMap::new();
map.insert(role.to_string(), false);
return map;
}
map.insert(role.to_string(), *propagate);
}
}
}
map
}
fn delete_group_role(&mut self, group: &str, role: &str) {
let roles = match self.groups.get_mut(group) {
Some(r) => r,
None => return,
};
roles.remove(role);
}
fn delete_user_role(&mut self, auth_id: &Authid, role: &str) {
let roles = match self.users.get_mut(auth_id) {
Some(r) => r,
None => return,
};
roles.remove(role);
}
fn delete_authid(&mut self, auth_id: &Authid) {
for node in self.children.values_mut() {
node.delete_authid(auth_id);
}
self.users.remove(auth_id);
}
fn insert_group_role(&mut self, group: String, role: String, propagate: bool) {
let map = self.groups.entry(group).or_default();
if let Some(no_access) = access_conf().role_no_access() {
if role == no_access {
map.clear();
} else {
map.remove(no_access);
}
}
map.insert(role, propagate);
}
fn insert_user_role(&mut self, auth_id: Authid, role: String, propagate: bool) {
let map = self.users.entry(auth_id).or_default();
if let Some(no_access) = access_conf().role_no_access() {
if role == no_access {
map.clear();
} else {
map.remove(no_access);
}
}
map.insert(role, propagate);
}
fn get_child_paths(
&self,
path: String,
auth_id: &Authid,
paths: &mut Vec<String>,
) -> Result<(), Error> {
for (sub_comp, child_node) in &self.children {
let roles = child_node.extract_roles(auth_id, true);
let child_path = format!("{path}/{sub_comp}");
if !roles.is_empty() {
paths.push(child_path.clone());
}
child_node.get_child_paths(child_path, auth_id, paths)?;
}
Ok(())
}
}
impl AclTree {
/// Create a new, empty ACL tree with a single, empty root [node](AclTreeNode)
pub fn new() -> Self {
Self {
root: AclTreeNode::new(),
}
}
/// Iterates over the tree looking for a node matching `path`.
pub fn find_node(&mut self, path: &str) -> Option<&mut AclTreeNode> {
let path = split_acl_path(path);
self.get_node_mut(&path)
}
fn get_node(&self, path: &[&str]) -> Option<&AclTreeNode> {
let mut node = &self.root;
for outer in path {
for comp in outer.split('/') {
node = node.children.get(comp)?;
}
}
Some(node)
}
fn get_node_mut(&mut self, path: &[&str]) -> Option<&mut AclTreeNode> {
let mut node = &mut self.root;
for outer in path {
for comp in outer.split('/') {
node = node.children.get_mut(comp)?;
}
}
Some(node)
}
fn get_or_insert_node(&mut self, path: &[&str]) -> &mut AclTreeNode {
let mut node = &mut self.root;
for outer in path {
for comp in outer.split('/') {
node = node.children.entry(String::from(comp)).or_default();
}
}
node
}
/// Deletes the specified `role` from the `group`'s ACL on `path`.
///
/// Never fails, even if the `path` has no ACLs configured, or the `group`/`role` combination
/// does not exist on `path`.
pub fn delete_group_role(&mut self, path: &str, group: &str, role: &str) {
let path = split_acl_path(path);
let node = match self.get_node_mut(&path) {
Some(n) => n,
None => return,
};
node.delete_group_role(group, role);
}
/// Deletes the specified `role` from the `user`'s ACL on `path`.
///
/// Never fails, even if the `path` has no ACLs configured, or the `user`/`role` combination
/// does not exist on `path`.
pub fn delete_user_role(&mut self, path: &str, auth_id: &Authid, role: &str) {
let path = split_acl_path(path);
let node = match self.get_node_mut(&path) {
Some(n) => n,
None => return,
};
node.delete_user_role(auth_id, role);
}
/// Deletes the [`AclTreeNode`] at the specified patth
///
/// Never fails, deletes a node iff the specified path exists.
pub fn delete_node(&mut self, path: &str) {
let mut path = split_acl_path(path);
let last = path.pop();
let parent = match self.get_node_mut(&path) {
Some(n) => n,
None => return,
};
if let Some(name) = last {
parent.children.remove(name);
}
}
/// Deletes a user or token from the ACL-tree
///
/// Traverses the tree in-order and removes the given user/token by their Authid
/// from every node in the tree.
pub fn delete_authid(&mut self, auth_id: &Authid) {
self.root.delete_authid(auth_id);
}
/// Inserts the specified `role` into the `group` ACL on `path`.
///
/// The [`AclTreeNode`] representing `path` will be created and inserted into the tree if
/// necessary.
pub fn insert_group_role(&mut self, path: &str, group: &str, role: &str, propagate: bool) {
let path = split_acl_path(path);
let node = self.get_or_insert_node(&path);
node.insert_group_role(group.to_string(), role.to_string(), propagate);
}
/// Inserts the specified `role` into the `user` ACL on `path`.
///
/// The [`AclTreeNode`] representing `path` will be created and inserted into the tree if
/// necessary.
pub fn insert_user_role(&mut self, path: &str, auth_id: &Authid, role: &str, propagate: bool) {
let path = split_acl_path(path);
let node = self.get_or_insert_node(&path);
node.insert_user_role(auth_id.to_owned(), role.to_string(), propagate);
}
fn write_node_config(node: &AclTreeNode, path: &str, w: &mut dyn Write) -> Result<(), Error> {
let mut role_ug_map0: HashMap<_, BTreeSet<_>> = HashMap::new();
let mut role_ug_map1: HashMap<_, BTreeSet<_>> = HashMap::new();
for (auth_id, roles) in &node.users {
// no need to save, because root is always 'Administrator'
if !auth_id.is_token() && auth_id.user() == "root@pam" {
continue;
}
for (role, propagate) in roles {
let role = role.as_str();
let auth_id = auth_id.to_string();
if *propagate {
role_ug_map1.entry(role).or_default().insert(auth_id);
} else {
role_ug_map0.entry(role).or_default().insert(auth_id);
}
}
}
for (group, roles) in &node.groups {
for (role, propagate) in roles {
let group = format!("@{}", group);
if *propagate {
role_ug_map1.entry(role).or_default().insert(group);
} else {
role_ug_map0.entry(role).or_default().insert(group);
}
}
}
fn group_by_property_list(
item_property_map: &HashMap<&str, BTreeSet<String>>,
) -> BTreeMap<String, BTreeSet<String>> {
let mut result_map: BTreeMap<_, BTreeSet<_>> = BTreeMap::new();
for (item, property_map) in item_property_map {
let item_list = property_map.iter().fold(String::new(), |mut acc, v| {
if !acc.is_empty() {
acc.push(',');
}
acc.push_str(v);
acc
});
result_map
.entry(item_list)
.or_default()
.insert(item.to_string());
}
result_map
}
let uglist_role_map0 = group_by_property_list(&role_ug_map0);
let uglist_role_map1 = group_by_property_list(&role_ug_map1);
fn role_list(roles: &BTreeSet<String>) -> String {
if let Some(no_access) = access_conf().role_no_access() {
if roles.contains(no_access) {
return String::from(no_access);
}
}
roles.iter().fold(String::new(), |mut acc, v| {
if !acc.is_empty() {
acc.push(',');
}
acc.push_str(v);
acc
})
}
for (uglist, roles) in &uglist_role_map0 {
let role_list = role_list(roles);
writeln!(
w,
"acl:0:{}:{}:{}",
if path.is_empty() { "/" } else { path },
uglist,
role_list
)?;
}
for (uglist, roles) in &uglist_role_map1 {
let role_list = role_list(roles);
writeln!(
w,
"acl:1:{}:{}:{}",
if path.is_empty() { "/" } else { path },
uglist,
role_list
)?;
}
for (name, child) in node.children.iter() {
let child_path = format!("{}/{}", path, name);
Self::write_node_config(child, &child_path, w)?;
}
Ok(())
}
fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> {
Self::write_node_config(&self.root, "", w)
}
fn parse_acl_line(&mut self, line: &str) -> Result<(), Error> {
let items: Vec<&str> = line.split(':').collect();
if items.len() != 5 {
bail!("wrong number of items.");
}
if items[0] != "acl" {
bail!("line does not start with 'acl'.");
}
let propagate = if items[1] == "0" {
false
} else if items[1] == "1" {
true
} else {
bail!("expected '0' or '1' for propagate flag.");
};
let path_str = items[2];
let path = split_acl_path(path_str);
let node = self.get_or_insert_node(&path);
let uglist: Vec<&str> = items[3].split(',').map(|v| v.trim()).collect();
let rolelist: Vec<&str> = items[4].split(',').map(|v| v.trim()).collect();
for user_or_group in &uglist {
for role in &rolelist {
if !access_conf().roles().contains_key(role) {
bail!("unknown role '{}'", role);
}
if let Some(group) = user_or_group.strip_prefix('@') {
node.insert_group_role(group.to_string(), role.to_string(), propagate);
} else {
node.insert_user_role(user_or_group.parse()?, role.to_string(), propagate);
}
}
}
Ok(())
}
fn load(filename: &Path) -> Result<(Self, ConfigDigest), Error> {
let mut tree = Self::new();
let raw = match std::fs::read_to_string(filename) {
Ok(v) => v,
Err(err) => {
if err.kind() == std::io::ErrorKind::NotFound {
String::new()
} else {
bail!("unable to read acl config {:?} - {}", filename, err);
}
}
};
let digest = ConfigDigest::from_slice(raw.as_bytes());
for (linenr, line) in raw.lines().enumerate() {
let line = line.trim();
if line.is_empty() {
continue;
}
if let Err(err) = tree.parse_acl_line(line) {
bail!(
"unable to parse acl config {:?}, line {} - {}",
filename,
linenr + 1,
err
);
}
}
Ok((tree, digest))
}
/// This is used for testing
pub fn from_raw(raw: &str) -> Result<Self, Error> {
let mut tree = Self::new();
for (linenr, line) in raw.lines().enumerate() {
let line = line.trim();
if line.is_empty() {
continue;
}
if let Err(err) = tree.parse_acl_line(line) {
bail!(
"unable to parse acl config data, line {} - {}",
linenr + 1,
err
);
}
}
Ok(tree)
}
/// Returns a map of role name and propagation status for a given `auth_id` and `path`.
///
/// This will collect role mappings according to the following algorithm:
/// - iterate over all intermediate nodes along `path` and collect roles with `propagate` set
/// - get all (propagating and non-propagating) roles for last component of path
/// - more specific role maps replace less specific role maps
/// -- user/token is more specific than group at each level
/// -- roles lower in the tree are more specific than those higher up along the path
pub fn roles(&self, auth_id: &Authid, path: &[&str]) -> HashMap<String, bool> {
let mut node = &self.root;
let mut role_map = node.extract_roles(auth_id, path.is_empty());
let mut comp_iter = path.iter().peekable();
while let Some(comp) = comp_iter.next() {
let last_comp = comp_iter.peek().is_none();
let mut sub_comp_iter = comp.split('/').peekable();
while let Some(sub_comp) = sub_comp_iter.next() {
let last_sub_comp = last_comp && sub_comp_iter.peek().is_none();
node = match node.children.get(sub_comp) {
Some(n) => n,
None => return role_map, // path not found
};
let new_map = node.extract_roles(auth_id, last_sub_comp);
if !new_map.is_empty() {
// overwrite previous mappings
role_map = new_map;
}
}
}
role_map
}
pub fn get_child_paths(&self, auth_id: &Authid, path: &[&str]) -> Result<Vec<String>, Error> {
let mut res = Vec::new();
if let Some(node) = self.get_node(path) {
let path = path.join("/");
node.get_child_paths(path, auth_id, &mut res)?;
}
Ok(res)
}
}
/// Get exclusive lock
pub fn lock_config() -> Result<ApiLockGuard, Error> {
open_api_lockfile(acl_config_lock(), None, true)
}
/// Reads the [`AclTree`] from `acl.cfg` in the configuration directory.
pub fn config() -> Result<(AclTree, ConfigDigest), Error> {
let path = acl_config();
AclTree::load(&path)
}
/// Returns a cached [`AclTree`] or a fresh copy read directly from `acl.cfg` in the configuration
/// directory.
///
/// Since the AclTree is used for every API request's permission check, this caching mechanism
/// allows to skip reading and parsing the file again if it is unchanged.
pub fn cached_config() -> Result<Arc<AclTree>, Error> {
struct ConfigCache {
data: Option<Arc<AclTree>>,
last_mtime: i64,
last_mtime_nsec: i64,
}
static CACHED_CONFIG: OnceLock<RwLock<ConfigCache>> = OnceLock::new();
let cached_conf = CACHED_CONFIG.get_or_init(|| {
RwLock::new(ConfigCache {
data: None,
last_mtime: 0,
last_mtime_nsec: 0,
})
});
let conf = acl_config();
let stat = match nix::sys::stat::stat(&conf) {
Ok(stat) => Some(stat),
Err(nix::errno::Errno::ENOENT) => None,
Err(err) => bail!("unable to stat '{}' - {err}", conf.display()),
};
{
// limit scope
let cache = cached_conf.read().unwrap();
if let Some(ref config) = cache.data {
if let Some(stat) = stat {
if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec
{
return Ok(config.clone());
}
} else if cache.last_mtime == 0 && cache.last_mtime_nsec == 0 {
return Ok(config.clone());
}
}
}
let (config, _digest) = config()?;
let config = Arc::new(config);
let mut cache = cached_conf.write().unwrap();
if let Some(stat) = stat {
cache.last_mtime = stat.st_mtime;
cache.last_mtime_nsec = stat.st_mtime_nsec;
}
cache.data = Some(config.clone());
Ok(config)
}
/// Saves an [`AclTree`] to `acl.cfg` in the configuration directory, ensuring proper ownership and
/// file permissions.
pub fn save_config(acl: &AclTree) -> Result<(), Error> {
let mut raw: Vec<u8> = Vec::new();
acl.write_config(&mut raw)?;
let conf = acl_config();
replace_privileged_config(conf, &raw)?;
// increase cache generation so we reload it next time we access it
access_conf().increment_cache_generation()?;
Ok(())
}
#[cfg(test)]
mod test {
use std::{collections::HashMap, sync::OnceLock};
use crate::init::{init_access_config, AccessControlConfig};
use super::AclTree;
use anyhow::Error;
use proxmox_auth_api::types::Authid;
#[derive(Debug)]
struct TestAcmConfig<'a> {
roles: HashMap<&'a str, u64>,
}
impl AccessControlConfig for TestAcmConfig<'_> {
fn roles(&self) -> &HashMap<&str, u64> {
&self.roles
}
fn privileges(&self) -> &HashMap<&str, u64> {
unreachable!("acl tests don't need privileges")
}
fn role_no_access(&self) -> Option<&'static str> {
Some("NoAccess")
}
fn role_admin(&self) -> Option<&'static str> {
Some("Admin")
}
}
fn setup_acl_tree_config() {
static ACL_CONFIG: OnceLock<TestAcmConfig> = OnceLock::new();
let config = ACL_CONFIG.get_or_init(|| {
let mut roles = HashMap::new();
roles.insert("NoAccess", 0);
roles.insert("Admin", u64::MAX);
roles.insert("DatastoreBackup", 4);
roles.insert("DatastoreReader", 8);
TestAcmConfig { roles }
});
// ignore errors here, we don't care if it's initialized already
let _ = init_access_config(config);
}
fn check_roles(tree: &AclTree, auth_id: &Authid, path: &str, expected_roles: &str) {
let path_vec = super::split_acl_path(path);
let mut roles = tree
.roles(auth_id, &path_vec)
.keys()
.cloned()
.collect::<Vec<String>>();
roles.sort();
let roles = roles.join(",");
assert_eq!(
roles, expected_roles,
"\nat check_roles for '{}' on '{}'",
auth_id, path
);
}
#[test]
fn test_acl_line_compression() {
setup_acl_tree_config();
let tree = AclTree::from_raw(
"\
acl:0:/store/store2:user1@pbs:Admin\n\
acl:0:/store/store2:user2@pbs:Admin\n\
acl:0:/store/store2:user1@pbs:DatastoreBackup\n\
acl:0:/store/store2:user2@pbs:DatastoreBackup\n\
",
)
.expect("failed to parse acl tree");
let mut raw: Vec<u8> = Vec::new();
tree.write_config(&mut raw)
.expect("failed to write acl tree");
let raw = std::str::from_utf8(&raw).expect("acl tree is not valid utf8");
assert_eq!(
raw,
"acl:0:/store/store2:user1@pbs,user2@pbs:Admin,DatastoreBackup\n"
);
}
#[test]
fn test_roles_1() -> Result<(), Error> {
setup_acl_tree_config();
let tree = AclTree::from_raw(
"\
acl:1:/storage:user1@pbs:Admin\n\
acl:1:/storage/store1:user1@pbs:DatastoreBackup\n\
acl:1:/storage/store2:user2@pbs:DatastoreBackup\n\
",
)?;
let user1: Authid = "user1@pbs".parse()?;
check_roles(&tree, &user1, "/", "");
check_roles(&tree, &user1, "/storage", "Admin");
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
check_roles(&tree, &user1, "/storage/store2", "Admin");
let user2: Authid = "user2@pbs".parse()?;
check_roles(&tree, &user2, "/", "");
check_roles(&tree, &user2, "/storage", "");
check_roles(&tree, &user2, "/storage/store1", "");
check_roles(&tree, &user2, "/storage/store2", "DatastoreBackup");
Ok(())
}
#[test]
fn test_role_no_access() -> Result<(), Error> {
setup_acl_tree_config();
let tree = AclTree::from_raw(
"\
acl:1:/:user1@pbs:Admin\n\
acl:1:/storage:user1@pbs:NoAccess\n\
acl:1:/storage/store1:user1@pbs:DatastoreBackup\n\
",
)?;
let user1: Authid = "user1@pbs".parse()?;
check_roles(&tree, &user1, "/", "Admin");
check_roles(&tree, &user1, "/storage", "NoAccess");
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
check_roles(&tree, &user1, "/storage/store2", "NoAccess");
check_roles(&tree, &user1, "/system", "Admin");
let tree = AclTree::from_raw(
"\
acl:1:/:user1@pbs:Admin\n\
acl:0:/storage:user1@pbs:NoAccess\n\
acl:1:/storage/store1:user1@pbs:DatastoreBackup\n\
",
)?;
check_roles(&tree, &user1, "/", "Admin");
check_roles(&tree, &user1, "/storage", "NoAccess");
check_roles(&tree, &user1, "/storage/store1", "DatastoreBackup");
check_roles(&tree, &user1, "/storage/store2", "Admin");
check_roles(&tree, &user1, "/system", "Admin");
Ok(())
}
#[test]
fn test_role_add_delete() -> Result<(), Error> {
setup_acl_tree_config();
let mut tree = AclTree::new();
let user1: Authid = "user1@pbs".parse()?;
tree.insert_user_role("/", &user1, "Admin", true);
tree.insert_user_role("/", &user1, "Audit", true);
check_roles(&tree, &user1, "/", "Admin,Audit");
tree.insert_user_role("/", &user1, "NoAccess", true);
check_roles(&tree, &user1, "/", "NoAccess");
let mut raw: Vec<u8> = Vec::new();
tree.write_config(&mut raw)?;
let raw = std::str::from_utf8(&raw)?;
assert_eq!(raw, "acl:1:/:user1@pbs:NoAccess\n");
Ok(())
}
#[test]
fn test_no_access_overwrite() -> Result<(), Error> {
setup_acl_tree_config();
let mut tree = AclTree::new();
let user1: Authid = "user1@pbs".parse()?;
tree.insert_user_role("/storage", &user1, "NoAccess", true);
check_roles(&tree, &user1, "/storage", "NoAccess");
tree.insert_user_role("/storage", &user1, "Admin", true);
tree.insert_user_role("/storage", &user1, "Audit", true);
check_roles(&tree, &user1, "/storage", "Admin,Audit");
tree.insert_user_role("/storage", &user1, "NoAccess", true);
check_roles(&tree, &user1, "/storage", "NoAccess");
Ok(())
}
#[test]
fn test_get_child_paths() -> Result<(), Error> {
setup_acl_tree_config();
let tree = AclTree::from_raw(
"\
acl:0:/store/store2:user1@pbs:Admin\n\
acl:1:/store/store2/store31/store4/store6:user2@pbs:DatastoreReader\n\
acl:0:/store/store2/store3:user1@pbs:Admin\n\
",
)
.expect("failed to parse acl tree");
let user1: Authid = "user1@pbs".parse()?;
let user2: Authid = "user2@pbs".parse()?;
// user1 has admin on "/store/store2/store3" -> return paths
let paths = tree.get_child_paths(&user1, &["store"])?;
assert!(
paths.len() == 2
&& paths.contains(&"store/store2".to_string())
&& paths.contains(&"store/store2/store3".to_string())
);
// user2 has no privileges under "/store/store2/store3" --> return empty
assert!(tree
.get_child_paths(&user2, &["store", "store2", "store3"],)?
.is_empty());
// user2 has DatastoreReader privileges under "/store/store2/store31" --> return paths
let paths = tree.get_child_paths(&user2, &["store/store2/store31"])?;
assert!(
paths.len() == 1 && paths.contains(&"store/store2/store31/store4/store6".to_string())
);
// user2 has no privileges under "/store/store2/foo/bar/baz"
assert!(tree
.get_child_paths(&user2, &["store", "store2", "foo/bar/baz"])?
.is_empty());
// user2 has DatastoreReader privileges on "/store/store2/store31/store4/store6", but not
// on any child paths --> return empty
assert!(tree
.get_child_paths(&user2, &["store/store2/store31/store4/store6"],)?
.is_empty());
Ok(())
}
#[test]
fn test_delete_node() -> Result<(), Error> {
setup_acl_tree_config();
let mut tree = AclTree::new();
let user1: Authid = "user1@pbs".parse()?;
tree.insert_user_role("/storage", &user1, "NoAccess", true);
tree.insert_user_role("/storage/a", &user1, "NoAccess", true);
tree.insert_user_role("/storage/b", &user1, "NoAccess", true);
tree.insert_user_role("/storage/b/a", &user1, "NoAccess", true);
tree.insert_user_role("/storage/b/b", &user1, "NoAccess", true);
tree.insert_user_role("/datastore/c", &user1, "NoAccess", true);
tree.insert_user_role("/datastore/d", &user1, "NoAccess", true);
assert!(tree.find_node("/storage/b/a").is_some());
tree.delete_node("/storage/b/a");
assert!(tree.find_node("/storage/b/a").is_none());
assert!(tree.find_node("/storage/b/b").is_some());
assert!(tree.find_node("/storage/b").is_some());
tree.delete_node("/storage/b");
assert!(tree.find_node("/storage/b/b").is_none());
assert!(tree.find_node("/storage/b").is_none());
assert!(tree.find_node("/storage").is_some());
assert!(tree.find_node("/storage/a").is_some());
tree.delete_node("/storage");
assert!(tree.find_node("/storage").is_none());
assert!(tree.find_node("/storage/a").is_none());
assert!(tree.find_node("/datastore/c").is_some());
tree.delete_node("/datastore/c");
assert!(tree.find_node("/datastore/c").is_none());
assert!(tree.find_node("/datastore/d").is_some());
tree.delete_node("/datastore/d");
assert!(tree.find_node("/datastore/d").is_none());
// '/' should not be deletable
assert!(tree.find_node("/").is_some());
tree.delete_node("/");
assert!(tree.find_node("/").is_some());
Ok(())
}
#[test]
fn test_delete_authid() -> Result<(), Error> {
setup_acl_tree_config();
let mut tree = AclTree::new();
let user1: Authid = "user1@pbs".parse()?;
let user2: Authid = "user2@pbs".parse()?;
let user1_paths = vec![
"/",
"/storage",
"/storage/a",
"/storage/a/b",
"/storage/b",
"/storage/b/a",
"/storage/b/b",
"/storage/a/a",
];
let user2_paths = vec!["/", "/storage", "/storage/a/b", "/storage/a/a"];
for path in &user1_paths {
tree.insert_user_role(path, &user1, "NoAccess", true);
}
for path in &user2_paths {
tree.insert_user_role(path, &user2, "NoAccess", true);
}
tree.delete_authid(&user1);
for path in &user1_paths {
let node = tree.find_node(path);
assert!(node.is_some());
if let Some(node) = node {
assert!(!node.users.contains_key(&user1));
}
}
for path in &user2_paths {
let node = tree.find_node(path);
assert!(node.is_some());
if let Some(node) = node {
assert!(node.users.contains_key(&user2));
}
}
tree.delete_authid(&user2);
for path in &user2_paths {
let node = tree.find_node(path);
assert!(node.is_some());
if let Some(node) = node {
assert!(!node.users.contains_key(&user2));
}
}
Ok(())
}
}

View File

@ -0,0 +1,246 @@
//! Cached user info for fast ACL permission checks
use std::sync::{Arc, OnceLock, RwLock};
use anyhow::{bail, Error};
use proxmox_auth_api::types::{Authid, Userid};
use proxmox_router::UserInformation;
use proxmox_section_config::SectionConfigData;
use proxmox_time::epoch_i64;
use crate::acl::AclTree;
use crate::init::access_conf;
use crate::types::{ApiToken, User};
/// Cache User/Group/Token/Acl configuration data for fast permission tests
pub struct CachedUserInfo {
user_cfg: Arc<SectionConfigData>,
acl_tree: Arc<AclTree>,
}
struct ConfigCache {
data: Option<Arc<CachedUserInfo>>,
last_update: i64,
last_user_cache_generation: usize,
}
impl CachedUserInfo {
/// Returns a cached instance (up to 5 seconds old).
pub fn new() -> Result<Arc<Self>, Error> {
let now = epoch_i64();
let cache_generation = access_conf().cache_generation();
static CACHED_CONFIG: OnceLock<RwLock<ConfigCache>> = OnceLock::new();
let cached_config = CACHED_CONFIG.get_or_init(|| {
RwLock::new(ConfigCache {
data: None,
last_update: 0,
last_user_cache_generation: 0,
})
});
{
// limit scope
let cache = cached_config.read().unwrap();
if let Some(current_generation) = cache_generation {
if (current_generation == cache.last_user_cache_generation)
&& ((now - cache.last_update) < 5)
{
if let Some(ref config) = cache.data {
return Ok(config.clone());
}
}
}
}
let config = Arc::new(CachedUserInfo {
user_cfg: crate::user::cached_config()?,
acl_tree: crate::acl::cached_config()?,
});
let mut cache = cached_config.write().unwrap();
if let Some(current_generation) = cache_generation {
cache.last_user_cache_generation = current_generation;
}
cache.last_update = now;
cache.data = Some(config.clone());
Ok(config)
}
pub fn is_superuser(&self, auth_id: &Authid) -> bool {
access_conf().is_superuser(auth_id)
}
pub fn is_group_member(&self, user_id: &Userid, group: &str) -> bool {
access_conf().is_group_member(user_id, group)
}
/// Test if a user_id is enabled and not expired
pub fn is_active_user_id(&self, userid: &Userid) -> bool {
if let Ok(info) = self.user_cfg.lookup::<User>("user", userid.as_str()) {
info.is_active()
} else {
false
}
}
/// Test if a authentication id is enabled and not expired
pub fn is_active_auth_id(&self, auth_id: &Authid) -> bool {
let userid = auth_id.user();
if !self.is_active_user_id(userid) {
return false;
}
if auth_id.is_token() {
if let Ok(info) = self
.user_cfg
.lookup::<ApiToken>("token", &auth_id.to_string())
{
return info.is_active();
} else {
return false;
}
}
true
}
pub fn check_privs(
&self,
auth_id: &Authid,
path: &[&str],
required_privs: u64,
partial: bool,
) -> Result<(), Error> {
let privs = self.lookup_privs(auth_id, path);
let allowed = if partial {
(privs & required_privs) != 0
} else {
(privs & required_privs) == required_privs
};
if !allowed {
// printing the path doesn't leak any information as long as we
// always check privilege before resource existence
let priv_names = privs_to_priv_names(required_privs);
let priv_names = if partial {
priv_names.join("|")
} else {
priv_names.join("&")
};
bail!(
"missing permissions '{priv_names}' on '/{}'",
path.join("/")
);
}
Ok(())
}
pub fn lookup_privs(&self, auth_id: &Authid, path: &[&str]) -> u64 {
let (privs, _) = self.lookup_privs_details(auth_id, path);
privs
}
pub fn lookup_privs_details(&self, auth_id: &Authid, path: &[&str]) -> (u64, u64) {
if self.is_superuser(auth_id) {
let acm_config = access_conf();
if let Some(admin) = acm_config.role_admin() {
if let Some(admin) = acm_config.roles().get(admin) {
return (*admin, *admin);
}
}
}
let roles = self.acl_tree.roles(auth_id, path);
let mut privs: u64 = 0;
let mut propagated_privs: u64 = 0;
for (role, propagate) in roles {
if let Some(role_privs) = access_conf().roles().get(role.as_str()) {
if propagate {
propagated_privs |= role_privs;
}
privs |= role_privs;
}
}
if auth_id.is_token() {
// limit privs to that of owning user
let user_auth_id = Authid::from(auth_id.user().clone());
let (owner_privs, owner_propagated_privs) =
self.lookup_privs_details(&user_auth_id, path);
privs &= owner_privs;
propagated_privs &= owner_propagated_privs;
}
(privs, propagated_privs)
}
/// Checks whether the `auth_id` has any of the privileges `privs` on any object below `path`.
pub fn any_privs_below(
&self,
auth_id: &Authid,
path: &[&str],
privs: u64,
) -> Result<bool, Error> {
// if the anchor path itself has matching propagated privs, we skip checking children
let (_privs, propagated_privs) = self.lookup_privs_details(auth_id, path);
if propagated_privs & privs != 0 {
return Ok(true);
}
// get all sub-paths with roles defined for `auth_id`
let paths = self.acl_tree.get_child_paths(auth_id, path)?;
for path in paths.iter() {
// early return if any sub-path has any of the privs we are looking for
if privs & self.lookup_privs(auth_id, &[path.as_str()]) != 0 {
return Ok(true);
}
}
// no paths or no matching paths
Ok(false)
}
}
impl UserInformation for CachedUserInfo {
fn is_superuser(&self, userid: &str) -> bool {
if let Ok(authid) = userid.parse() {
return self.is_superuser(&authid);
}
false
}
fn is_group_member(&self, userid: &str, group: &str) -> bool {
if let Ok(userid) = userid.parse() {
return self.is_group_member(&userid, group);
}
false
}
fn lookup_privs(&self, auth_id: &str, path: &[&str]) -> u64 {
match auth_id.parse::<Authid>() {
Ok(auth_id) => Self::lookup_privs(self, &auth_id, path),
Err(_) => 0,
}
}
}
pub fn privs_to_priv_names(privs: u64) -> Vec<&'static str> {
access_conf()
.privileges()
.iter()
.fold(Vec::new(), |mut priv_names, (name, value)| {
if value & privs != 0 {
priv_names.push(name);
}
priv_names
})
}

View File

@ -0,0 +1,131 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use anyhow::{format_err, Error};
use proxmox_auth_api::types::{Authid, Userid};
use proxmox_section_config::SectionConfigData;
static ACCESS_CONF: OnceLock<&'static dyn AccessControlConfig> = OnceLock::new();
static ACCESS_CONF_DIR: OnceLock<PathBuf> = OnceLock::new();
/// This trait specifies the functions a product needs to implement to get ACL tree based access
/// control management from this plugin.
pub trait AccessControlConfig: Send + Sync {
/// Returns a mapping of all recognized privileges and their corresponding `u64` value.
fn privileges(&self) -> &HashMap<&str, u64>;
/// Returns a mapping of all recognized roles and their corresponding `u64` value.
fn roles(&self) -> &HashMap<&str, u64>;
/// Checks whether an `Authid` has super user privileges or not.
///
/// Default: Always returns `false`.
fn is_superuser(&self, _auth_id: &Authid) -> bool {
false
}
/// Checks whether a user is part of a group.
///
/// Default: Always returns `false`.
fn is_group_member(&self, _user_id: &Userid, _group: &str) -> bool {
false
}
/// Returns the current cache generation of the user and acl configs. If the generation was
/// incremented since the last time the cache was queried, the configs are loaded again from
/// disk.
///
/// Returning `None` will always reload the cache.
///
/// Default: Always returns `None`.
fn cache_generation(&self) -> Option<usize> {
None
}
/// Increment the cache generation of user and acl configs. This indicates that they were
/// changed on disk.
///
/// Default: Does nothing.
fn increment_cache_generation(&self) -> Result<(), Error> {
Ok(())
}
/// Optionally returns a role that has no access to any resource.
///
/// Default: Returns `None`.
fn role_no_access(&self) -> Option<&str> {
None
}
/// Optionally returns a role that is allowed to access all resources.
///
/// Default: Returns `None`.
fn role_admin(&self) -> Option<&str> {
None
}
/// Called after the user configuration is loaded to potentially re-add fixed users, such as a
/// `root@pam` user.
fn init_user_config(&self, config: &mut SectionConfigData) -> Result<(), Error> {
let _ = config;
Ok(())
}
}
pub fn init<P: AsRef<Path>>(
acm_config: &'static dyn AccessControlConfig,
config_dir: P,
) -> Result<(), Error> {
init_access_config(acm_config)?;
init_access_config_dir(config_dir)
}
pub(crate) fn init_access_config_dir<P: AsRef<Path>>(config_dir: P) -> Result<(), Error> {
ACCESS_CONF_DIR
.set(config_dir.as_ref().to_owned())
.map_err(|_e| format_err!("cannot initialize acl tree config twice!"))
}
pub(crate) fn init_access_config(config: &'static dyn AccessControlConfig) -> Result<(), Error> {
ACCESS_CONF
.set(config)
.map_err(|_e| format_err!("cannot initialize acl tree config twice!"))
}
pub(crate) fn access_conf() -> &'static dyn AccessControlConfig {
*ACCESS_CONF
.get()
.expect("please initialize the acm config before using it!")
}
fn conf_dir() -> &'static PathBuf {
ACCESS_CONF_DIR
.get()
.expect("please initialize acm config dir before using it!")
}
pub(crate) fn acl_config() -> PathBuf {
conf_dir().join("acl.cfg")
}
pub(crate) fn acl_config_lock() -> PathBuf {
conf_dir().join(".acl.lck")
}
pub(crate) fn user_config() -> PathBuf {
conf_dir().join("user.cfg")
}
pub(crate) fn user_config_lock() -> PathBuf {
conf_dir().join(".user.lck")
}
pub(crate) fn token_shadow() -> PathBuf {
conf_dir().join("token.shadow")
}
pub(crate) fn token_shadow_lock() -> PathBuf {
conf_dir().join("token.shadow.lock")
}

View File

@ -0,0 +1,20 @@
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
pub mod types;
#[cfg(feature = "impl")]
pub mod acl;
#[cfg(feature = "impl")]
pub mod init;
#[cfg(feature = "impl")]
pub mod token_shadow;
#[cfg(feature = "impl")]
pub mod user;
#[cfg(feature = "impl")]
mod cached_user_info;
#[cfg(feature = "impl")]
pub use cached_user_info::CachedUserInfo;

View File

@ -0,0 +1,84 @@
use std::collections::HashMap;
use anyhow::{bail, format_err, Error};
use serde::{Deserialize, Serialize};
use serde_json::{from_value, Value};
use proxmox_auth_api::types::Authid;
use proxmox_product_config::{open_api_lockfile, replace_config, ApiLockGuard};
use crate::init::{token_shadow, token_shadow_lock};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// ApiToken id / secret pair
pub struct ApiTokenSecret {
pub tokenid: Authid,
pub secret: String,
}
// Get exclusive lock
fn lock_config() -> Result<ApiLockGuard, Error> {
open_api_lockfile(token_shadow_lock(), None, true)
}
fn read_file() -> Result<HashMap<Authid, String>, Error> {
let json = proxmox_sys::fs::file_get_json(token_shadow(), Some(Value::Null))?;
if json == Value::Null {
Ok(HashMap::new())
} else {
// swallow serde error which might contain sensitive data
from_value(json)
.map_err(|_err| format_err!("unable to parse '{}'", token_shadow().display()))
}
}
fn write_file(data: HashMap<Authid, String>) -> Result<(), Error> {
let json = serde_json::to_vec(&data)?;
replace_config(token_shadow(), &json)
}
/// Verifies that an entry for given tokenid / API token secret exists
pub fn verify_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> {
if !tokenid.is_token() {
bail!("not an API token ID");
}
let data = read_file()?;
match data.get(tokenid) {
Some(hashed_secret) => proxmox_sys::crypt::verify_crypt_pw(secret, hashed_secret),
None => bail!("invalid API token"),
}
}
/// Adds a new entry for the given tokenid / API token secret. The secret is stored as salted hash.
pub fn set_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> {
if !tokenid.is_token() {
bail!("not an API token ID");
}
let _guard = lock_config()?;
let mut data = read_file()?;
let hashed_secret = proxmox_sys::crypt::encrypt_pw(secret)?;
data.insert(tokenid.clone(), hashed_secret);
write_file(data)?;
Ok(())
}
/// Deletes the entry for the given tokenid.
pub fn delete_secret(tokenid: &Authid) -> Result<(), Error> {
if !tokenid.is_token() {
bail!("not an API token ID");
}
let _guard = lock_config()?;
let mut data = read_file()?;
data.remove(tokenid);
write_file(data)?;
Ok(())
}

View File

@ -0,0 +1,194 @@
use serde::{Deserialize, Serialize};
use proxmox_auth_api::types::{Authid, Userid, PROXMOX_TOKEN_ID_SCHEMA};
use proxmox_schema::{
api,
api_types::{COMMENT_SCHEMA, SINGLE_LINE_COMMENT_FORMAT},
BooleanSchema, IntegerSchema, Schema, StringSchema, Updater,
};
pub const ENABLE_USER_SCHEMA: Schema = BooleanSchema::new(
"Enable the account (default). You can set this to '0' to disable the account.",
)
.default(true)
.schema();
pub const EXPIRE_USER_SCHEMA: Schema = IntegerSchema::new(
"Account expiration date (seconds since epoch). '0' means no expiration date.",
)
.default(0)
.minimum(0)
.schema();
pub const FIRST_NAME_SCHEMA: Schema = StringSchema::new("First name.")
.format(&SINGLE_LINE_COMMENT_FORMAT)
.min_length(2)
.max_length(64)
.schema();
pub const LAST_NAME_SCHEMA: Schema = StringSchema::new("Last name.")
.format(&SINGLE_LINE_COMMENT_FORMAT)
.min_length(2)
.max_length(64)
.schema();
pub const EMAIL_SCHEMA: Schema = StringSchema::new("E-Mail Address.")
.format(&SINGLE_LINE_COMMENT_FORMAT)
.min_length(2)
.max_length(64)
.schema();
#[api(
properties: {
user: {
type: User,
flatten: true,
},
tokens: {
type: Array,
optional: true,
description: "List of user's API tokens.",
items: {
type: ApiToken
},
},
"totp-locked": {
type: bool,
optional: true,
default: false,
description: "True if the user is currently locked out of TOTP factors",
},
"tfa-locked-until": {
optional: true,
description: "Contains a timestamp until when a user is locked out of 2nd factors",
},
}
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "kebab-case")]
/// User properties with added list of ApiTokens
pub struct UserWithTokens {
#[serde(flatten)]
pub user: User,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub tokens: Vec<ApiToken>,
#[serde(skip_serializing_if = "bool_is_false", default)]
pub totp_locked: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub tfa_locked_until: Option<i64>,
}
fn bool_is_false(b: &bool) -> bool {
!b
}
#[api(
properties: {
tokenid: {
schema: PROXMOX_TOKEN_ID_SCHEMA,
},
comment: {
optional: true,
schema: COMMENT_SCHEMA,
},
enable: {
optional: true,
schema: ENABLE_USER_SCHEMA,
},
expire: {
optional: true,
schema: EXPIRE_USER_SCHEMA,
},
}
)]
#[derive(Serialize, Deserialize, Clone, PartialEq)]
/// ApiToken properties.
pub struct ApiToken {
pub tokenid: Authid,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire: Option<i64>,
}
impl ApiToken {
pub fn is_active(&self) -> bool {
if !self.enable.unwrap_or(true) {
return false;
}
if let Some(expire) = self.expire {
let now = proxmox_time::epoch_i64();
if expire > 0 && expire <= now {
return false;
}
}
true
}
}
#[api(
properties: {
userid: {
type: Userid,
},
comment: {
optional: true,
schema: COMMENT_SCHEMA,
},
enable: {
optional: true,
schema: ENABLE_USER_SCHEMA,
},
expire: {
optional: true,
schema: EXPIRE_USER_SCHEMA,
},
firstname: {
optional: true,
schema: FIRST_NAME_SCHEMA,
},
lastname: {
schema: LAST_NAME_SCHEMA,
optional: true,
},
email: {
schema: EMAIL_SCHEMA,
optional: true,
},
}
)]
#[derive(Serialize, Deserialize, Updater, PartialEq, Eq, Clone)]
/// User properties.
pub struct User {
#[updater(skip)]
pub userid: Userid,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub firstname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lastname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}
impl User {
pub fn is_active(&self) -> bool {
if !self.enable.unwrap_or(true) {
return false;
}
if let Some(expire) = self.expire {
let now = proxmox_time::epoch_i64();
if expire > 0 && expire <= now {
return false;
}
}
true
}
}

View File

@ -0,0 +1,183 @@
use std::collections::HashMap;
use std::sync::{Arc, OnceLock, RwLock};
use anyhow::{bail, Error};
use proxmox_auth_api::types::Authid;
use proxmox_config_digest::ConfigDigest;
use proxmox_product_config::{open_api_lockfile, replace_privileged_config, ApiLockGuard};
use proxmox_schema::*;
use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin};
use crate::init::{access_conf, user_config, user_config_lock};
use crate::types::{ApiToken, User};
fn get_or_init_config() -> &'static SectionConfig {
static CONFIG: OnceLock<SectionConfig> = OnceLock::new();
CONFIG.get_or_init(|| {
let mut config = SectionConfig::new(&Authid::API_SCHEMA);
let user_schema = match User::API_SCHEMA {
Schema::Object(ref user_schema) => user_schema,
_ => unreachable!(),
};
let user_plugin =
SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), user_schema);
config.register_plugin(user_plugin);
let token_schema = match ApiToken::API_SCHEMA {
Schema::Object(ref token_schema) => token_schema,
_ => unreachable!(),
};
let token_plugin = SectionConfigPlugin::new(
"token".to_string(),
Some("tokenid".to_string()),
token_schema,
);
config.register_plugin(token_plugin);
config
})
}
/// Get exclusive lock
pub fn lock_config() -> Result<ApiLockGuard, Error> {
open_api_lockfile(user_config_lock(), None, true)
}
pub fn config() -> Result<(SectionConfigData, ConfigDigest), Error> {
let content = proxmox_sys::fs::file_read_optional_string(user_config())?.unwrap_or_default();
let digest = ConfigDigest::from_slice(content.as_bytes());
let mut data = get_or_init_config().parse(user_config(), &content)?;
access_conf().init_user_config(&mut data)?;
Ok((data, digest))
}
pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> {
struct ConfigCache {
data: Option<Arc<SectionConfigData>>,
last_mtime: i64,
last_mtime_nsec: i64,
}
static CACHED_CONFIG: OnceLock<RwLock<ConfigCache>> = OnceLock::new();
let cached_config = CACHED_CONFIG.get_or_init(|| {
RwLock::new(ConfigCache {
data: None,
last_mtime: 0,
last_mtime_nsec: 0,
})
});
let stat = match nix::sys::stat::stat(&user_config()) {
Ok(stat) => Some(stat),
Err(nix::errno::Errno::ENOENT) => None,
Err(err) => bail!("unable to stat '{}' - {err}", user_config().display()),
};
{
// limit scope
let cache = cached_config.read().unwrap();
if let Some(ref config) = cache.data {
if let Some(stat) = stat {
if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec
{
return Ok(config.clone());
}
} else if cache.last_mtime == 0 && cache.last_mtime_nsec == 0 {
return Ok(config.clone());
}
}
}
let (config, _digest) = config()?;
let config = Arc::new(config);
let mut cache = cached_config.write().unwrap();
if let Some(stat) = stat {
cache.last_mtime = stat.st_mtime;
cache.last_mtime_nsec = stat.st_mtime_nsec;
}
cache.data = Some(config.clone());
Ok(config)
}
pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
let config_file = user_config();
let raw = get_or_init_config().write(&config_file, config)?;
replace_privileged_config(config_file, raw.as_bytes())?;
// increase cache generation so we reload it next time we access it
access_conf().increment_cache_generation()?;
Ok(())
}
/// Only exposed for testing
#[doc(hidden)]
pub fn test_cfg_from_str(raw: &str) -> Result<(SectionConfigData, [u8; 32]), Error> {
let cfg = get_or_init_config();
let parsed = cfg.parse("test_user_cfg", raw)?;
Ok((parsed, [0; 32]))
}
// shell completion helper
pub fn complete_userid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
match config() {
Ok((data, _digest)) => data
.sections
.iter()
.filter_map(|(id, (section_type, _))| {
if section_type == "user" {
Some(id.to_string())
} else {
None
}
})
.collect(),
Err(_) => Vec::new(),
}
}
// shell completion helper
pub fn complete_authid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
match config() {
Ok((data, _digest)) => data.sections.keys().map(|id| id.to_string()).collect(),
Err(_) => vec![],
}
}
// shell completion helper
pub fn complete_token_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
let data = match config() {
Ok((data, _digest)) => data,
Err(_) => return Vec::new(),
};
match param.get("userid") {
Some(userid) => {
let user = data.lookup::<User>("user", userid);
let tokens = data.convert_to_typed_array("token");
match (user, tokens) {
(Ok(_), Ok(tokens)) => tokens
.into_iter()
.filter_map(|token: ApiToken| {
let tokenid = token.tokenid;
if tokenid.is_token() && tokenid.user() == userid {
Some(tokenid.tokenname().unwrap().as_str().to_string())
} else {
None
}
})
.collect(),
_ => vec![],
}
}
None => vec![],
}
}

View File

@ -0,0 +1,68 @@
[package]
name = "proxmox-acme-api"
description = "ACME API implementation"
version = "0.1.7"
authors.workspace = true
edition.workspace = true
exclude.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[dependencies]
anyhow.workspace = true
base64 = { workspace = true, optional = true }
futures = { workspace = true, optional = true }
hex = { workspace = true, optional = true }
http = { workspace = true, optional = true }
hyper = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tokio = { workspace = true, optional = true, features = ["fs"] }
foreign-types = { workspace = true, optional = true }
libc = { workspace = true, optional = true }
openssl = { workspace = true, optional = true }
proxmox-acme = { workspace = true, features = ["api-types"] }
proxmox-config-digest = { workspace = true, optional = true }
proxmox-log = { workspace = true, optional = true }
proxmox-product-config = { workspace = true, optional = true }
proxmox-rest-server = { workspace = true, optional = true }
proxmox-router = { workspace = true, optional = true }
proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] }
proxmox-section-config = { workspace = true, optional = true }
proxmox-serde.workspace = true
proxmox-sys = { workspace = true, optional = true }
proxmox-time = { workspace = true, optional = true }
proxmox-uuid = { workspace = true, optional = true }
[features]
default = []
impl = [
"dep:base64",
"dep:foreign-types",
"dep:futures",
"dep:hex",
"dep:http",
"dep:hyper",
"dep:libc",
"dep:openssl",
"dep:tokio",
"dep:proxmox-config-digest",
"dep:proxmox-log",
"dep:proxmox-product-config",
"dep:proxmox-rest-server",
"dep:proxmox-router",
"dep:proxmox-section-config",
"dep:proxmox-sys",
"dep:proxmox-time",
"dep:proxmox-uuid",
"proxmox-acme/async-client",
"proxmox-acme/impl",
"proxmox-config-digest?/openssl",
]

View File

@ -0,0 +1,59 @@
rust-proxmox-acme-api (0.1.7-1) bookworm; urgency=medium
* rebuild with proxmox-schema 4.0
* use inner mutability for ACME_ACME_CONFIG
-- Proxmox Support Team <support@proxmox.com> Wed, 15 Jan 2025 12:45:05 +0100
rust-proxmox-acme-api (0.1.6-1) bookworm; urgency=medium
* rebuild with new rest-server and router dependencies
-- Proxmox Support Team <support@proxmox.com> Wed, 04 Sep 2024 15:42:27 +0200
rust-proxmox-acme-api (0.1.5-1) bookworm; urgency=medium
* replace lazy_static with std's LazyLock and drop the dependency
* remove unused dependencies
-- Proxmox Support Team <support@proxmox.com> Wed, 14 Aug 2024 11:36:01 +0200
rust-proxmox-acme-api (0.1.4-1) bookworm; urgency=medium
* rebuild with proxmox-log 0.2 and proxmox-rest-server 0.7
-- Proxmox Support Team <support@proxmox.com> Wed, 24 Jul 2024 14:33:06 +0200
rust-proxmox-acme-api (0.1.3-1) bookworm; urgency=medium
* adapt to tracing log infrastructure
* various clippy fixes
* upgrade proxmox-sys to 6.0
-- Proxmox Support Team <support@proxmox.com> Thu, 11 Jul 2024 14:50:02 +0200
rust-proxmox-acme-api (0.1.2-1) bookworm; urgency=medium
* upgrade proxmox-time to 2.0
-- Proxmox Support Team <support@proxmox.com> Thu, 20 Jun 2024 13:57:36 +0200
rust-proxmox-acme-api (0.1.1-1) bookworm; urgency=medium
* add function to create certificate revocation
* add function to create self signed certificates
* add function to get info from PEM formatted certificates
-- Proxmox Support Team <support@proxmox.com> Wed, 19 Jun 2024 12:06:49 +0200
rust-proxmox-acme-api (0.1.0-1) bookworm; urgency=medium
* initial package
-- Proxmox Support Team <support@proxmox.com> Wed, 05 Jun 2024 12:12:42 +0200

View File

@ -0,0 +1,90 @@
Source: rust-proxmox-acme-api
Section: rust
Priority: optional
Build-Depends: debhelper-compat (= 13),
dh-sequence-cargo,
cargo:native <!nocheck>,
rustc:native (>= 1.80) <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-proxmox-acme-0.5+api-types-dev (>= 0.5.3-~~) <!nocheck>,
librust-proxmox-schema-4+api-macro-dev <!nocheck>,
librust-proxmox-schema-4+api-types-dev <!nocheck>,
librust-proxmox-schema-4+default-dev <!nocheck>,
librust-proxmox-serde-0.1+default-dev (>= 0.1.1-~~) <!nocheck>,
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~) <!nocheck>,
librust-serde-1+default-dev <!nocheck>,
librust-serde-1+derive-dev <!nocheck>,
librust-serde-json-1+default-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.7.0
Vcs-Git:
Vcs-Browser:
Homepage: https://proxmox.com
X-Cargo-Crate: proxmox-acme-api
Rules-Requires-Root: no
Package: librust-proxmox-acme-api-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-anyhow-1+default-dev,
librust-proxmox-acme-0.5+api-types-dev (>= 0.5.3-~~),
librust-proxmox-schema-4+api-macro-dev,
librust-proxmox-schema-4+api-types-dev,
librust-proxmox-schema-4+default-dev,
librust-proxmox-serde-0.1+default-dev (>= 0.1.1-~~),
librust-proxmox-serde-0.1+serde-json-dev (>= 0.1.1-~~),
librust-serde-1+default-dev,
librust-serde-1+derive-dev,
librust-serde-json-1+default-dev
Suggests:
librust-proxmox-acme-api+impl-dev (= ${binary:Version})
Provides:
librust-proxmox-acme-api+default-dev (= ${binary:Version}),
librust-proxmox-acme-api-0-dev (= ${binary:Version}),
librust-proxmox-acme-api-0+default-dev (= ${binary:Version}),
librust-proxmox-acme-api-0.1-dev (= ${binary:Version}),
librust-proxmox-acme-api-0.1+default-dev (= ${binary:Version}),
librust-proxmox-acme-api-0.1.7-dev (= ${binary:Version}),
librust-proxmox-acme-api-0.1.7+default-dev (= ${binary:Version})
Description: ACME API implementation - Rust source code
Source code for Debianized Rust crate "proxmox-acme-api"
Package: librust-proxmox-acme-api+impl-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-acme-api-dev (= ${binary:Version}),
librust-base64-0.13+default-dev,
librust-foreign-types-0.3+default-dev,
librust-futures-0.3+default-dev,
librust-hex-0.4+default-dev,
librust-http-0.2+default-dev,
librust-hyper-0.14+default-dev (>= 0.14.5-~~),
librust-libc-0.2+default-dev (>= 0.2.107-~~),
librust-openssl-0.10+default-dev,
librust-proxmox-acme-0.5+api-types-dev (>= 0.5.3-~~),
librust-proxmox-acme-0.5+async-client-dev (>= 0.5.3-~~),
librust-proxmox-acme-0.5+impl-dev (>= 0.5.3-~~),
librust-proxmox-config-digest-0.1+default-dev,
librust-proxmox-config-digest-0.1+openssl-dev,
librust-proxmox-log-0.2+default-dev (>= 0.2.5-~~),
librust-proxmox-product-config-0.2+default-dev,
librust-proxmox-rest-server-0.8+default-dev,
librust-proxmox-router-3+default-dev,
librust-proxmox-section-config-2+default-dev (>= 2.1.0-~~),
librust-proxmox-sys-0.6+default-dev (>= 0.6.5-~~),
librust-proxmox-time-2+default-dev,
librust-proxmox-uuid-1+default-dev (>= 1.0.1-~~),
librust-tokio-1+default-dev (>= 1.6-~~),
librust-tokio-1+fs-dev (>= 1.6-~~)
Provides:
librust-proxmox-acme-api-0+impl-dev (= ${binary:Version}),
librust-proxmox-acme-api-0.1+impl-dev (= ${binary:Version}),
librust-proxmox-acme-api-0.1.7+impl-dev (= ${binary:Version})
Description: ACME API implementation - feature "impl"
This metapackage enables feature "impl" for the Rust proxmox-acme-api crate, by
pulling in any additional dependencies needed by that feature.

View File

@ -0,0 +1,16 @@
Copyright (C) 2020-2024 Proxmox Server Solutions GmbH
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,8 @@
overlay = "."
crate_src_path = ".."
maintainer = "Proxmox Support Team <support@proxmox.com>"
[source]
# TODO: update once public
vcs_git = ""
vcs_browser = ""

View File

@ -0,0 +1,118 @@
//! ACME account configuration API implementation
use std::ops::ControlFlow;
use anyhow::Error;
use serde_json::json;
use proxmox_acme::async_client::AcmeClient;
use proxmox_acme::types::AccountData as AcmeAccountData;
use proxmox_log::warn;
use crate::account_config::AccountData;
use crate::config::DEFAULT_ACME_DIRECTORY_ENTRY;
use crate::types::{AccountEntry, AccountInfo, AcmeAccountName};
fn account_contact_from_string(s: &str) -> Vec<String> {
s.split(&[' ', ';', ',', '\0'][..])
.map(|s| format!("mailto:{}", s))
.collect()
}
pub fn list_accounts() -> Result<Vec<AccountEntry>, Error> {
let mut entries = Vec::new();
super::account_config::foreach_acme_account(|name| {
entries.push(AccountEntry { name });
ControlFlow::Continue(())
})?;
Ok(entries)
}
pub async fn get_account(account_name: AcmeAccountName) -> Result<AccountInfo, Error> {
let account_data = super::account_config::load_account_config(&account_name).await?;
Ok(AccountInfo {
location: account_data.location.clone(),
tos: account_data.tos.clone(),
directory: account_data.directory_url.clone(),
account: AcmeAccountData {
only_return_existing: false, // don't actually write this out in case it's set
..account_data.account.clone()
},
})
}
pub async fn get_tos(directory: Option<String>) -> Result<Option<String>, Error> {
let directory = directory.unwrap_or_else(|| DEFAULT_ACME_DIRECTORY_ENTRY.url.to_string());
Ok(AcmeClient::new(directory)
.terms_of_service_url()
.await?
.map(str::to_owned))
}
pub async fn register_account(
name: &AcmeAccountName,
contact: String,
tos_url: Option<String>,
directory_url: Option<String>,
eab_creds: Option<(String, String)>,
) -> Result<String, Error> {
let directory_url =
directory_url.unwrap_or_else(|| DEFAULT_ACME_DIRECTORY_ENTRY.url.to_string());
let mut client = AcmeClient::new(directory_url.clone());
let contact = account_contact_from_string(&contact);
let account = client
.new_account(tos_url.is_some(), contact, None, eab_creds)
.await?;
let account = AccountData::from_account_dir_tos(account, directory_url, tos_url);
super::account_config::create_account_config(name, &account)?;
Ok(account.location)
}
pub async fn deactivate_account(name: &AcmeAccountName, force: bool) -> Result<(), Error> {
let mut account_data = super::account_config::load_account_config(name).await?;
let mut client = account_data.client();
match client
.update_account(&json!({"status": "deactivated"}))
.await
{
Ok(account) => {
account_data.account = account.data.clone();
super::account_config::save_account_config(name, &account_data)?;
}
Err(err) if !force => return Err(err),
Err(err) => {
warn!(
"error deactivating account {}, proceedeing anyway - {}",
name, err,
);
}
}
super::account_config::mark_account_deactivated(name)?;
Ok(())
}
pub async fn update_account(name: &AcmeAccountName, contact: Option<String>) -> Result<(), Error> {
let mut account_data = super::account_config::load_account_config(name).await?;
let mut client = account_data.client();
let data = match contact {
Some(contact) => json!({
"contact": account_contact_from_string(&contact),
}),
None => json!({}),
};
let account = client.update_account(&data).await?;
account_data.account = account.data.clone();
super::account_config::save_account_config(name, &account_data)?;
Ok(())
}

View File

@ -0,0 +1,212 @@
//! ACME account configuration helpers (load/save config)
use std::fs::OpenOptions;
use std::ops::ControlFlow;
use std::os::unix::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use anyhow::{bail, format_err, Error};
use serde::{Deserialize, Serialize};
use proxmox_product_config::replace_secret_config;
use proxmox_sys::error::SysError;
use proxmox_schema::api_types::SAFE_ID_REGEX;
use proxmox_acme::async_client::AcmeClient;
use proxmox_acme::types::AccountData as AcmeAccountData;
use proxmox_acme::Account;
use crate::acme_account_dir;
use crate::types::AcmeAccountName;
#[inline]
fn is_false(b: &bool) -> bool {
!*b
}
// Our on-disk format inherited from PVE's proxmox-acme code.
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountData {
/// The account's location URL.
pub location: String,
/// The account data.
pub account: AcmeAccountData,
/// The private key as PEM formatted string.
pub key: String,
/// ToS URL the user agreed to.
#[serde(skip_serializing_if = "Option::is_none")]
pub tos: Option<String>,
#[serde(skip_serializing_if = "is_false", default)]
pub debug: bool,
/// The directory's URL.
pub directory_url: String,
}
impl AccountData {
pub fn from_account_dir_tos(
account: &Account,
directory_url: String,
tos: Option<String>,
) -> Self {
AccountData {
location: account.location.clone(),
key: account.private_key.clone(),
account: AcmeAccountData {
only_return_existing: false, // don't actually write this out in case it's set
..account.data.clone()
},
debug: false,
tos,
directory_url,
}
}
pub fn client(&self) -> AcmeClient {
let mut client = AcmeClient::new(self.directory_url.clone());
client.set_account(Account {
location: self.location.clone(),
private_key: self.key.clone(),
data: self.account.clone(),
});
client
}
}
/// Returns the path to the account configuration file (`$config_dir/accounts/$name`).
pub fn account_config_filename(name: &str) -> PathBuf {
acme_account_dir().join(name)
}
pub(crate) fn foreach_acme_account<F>(mut func: F) -> Result<(), Error>
where
F: FnMut(AcmeAccountName) -> ControlFlow<Result<(), Error>>,
{
match proxmox_sys::fs::scan_subdir(-1, acme_account_dir(), &SAFE_ID_REGEX) {
Ok(files) => {
for file in files {
let file = file?;
let file_name = unsafe { file.file_name_utf8_unchecked() };
if file_name.starts_with('_') {
continue;
}
let account_name = match AcmeAccountName::from_string(file_name.to_owned()) {
Ok(account_name) => account_name,
Err(_) => continue,
};
if let ControlFlow::Break(result) = func(account_name) {
return result;
}
}
Ok(())
}
Err(err) if err.not_found() => Ok(()),
Err(err) => Err(err.into()),
}
}
// Mark account as deactivated
pub(crate) fn mark_account_deactivated(account_name: &str) -> Result<(), Error> {
let from = account_config_filename(account_name);
for i in 0..100 {
let to = account_config_filename(&format!("_deactivated_{}_{}", account_name, i));
if !Path::new(&to).exists() {
return std::fs::rename(&from, &to).map_err(|err| {
format_err!(
"failed to move account path {:?} to {:?} - {}",
from,
to,
err
)
});
}
}
bail!(
"No free slot to rename deactivated account {:?}, please cleanup {:?}",
from,
acme_account_dir()
);
}
// Load an existing ACME account by name.
pub(crate) async fn load_account_config(account_name: &str) -> Result<AccountData, Error> {
let account_config_filename = account_config_filename(account_name);
let data = match tokio::fs::read(&account_config_filename).await {
Ok(data) => data,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
bail!("acme account '{}' does not exist", account_name)
}
Err(err) => bail!(
"failed to load acme account from {:?} - {}",
account_config_filename,
err
),
};
let data: AccountData = serde_json::from_slice(&data).map_err(|err| {
format_err!(
"failed to parse acme account from {:?} - {}",
account_config_filename,
err
)
})?;
Ok(data)
}
// Save an new ACME account (fails if the file already exists).
pub(crate) fn create_account_config(
account_name: &AcmeAccountName,
account: &AccountData,
) -> Result<(), Error> {
let account_config_filename = account_config_filename(account_name.as_ref());
let file = OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(&account_config_filename)
.map_err(|err| {
format_err!(
"failed to open {:?} for writing: {}",
account_config_filename,
err
)
})?;
serde_json::to_writer_pretty(file, account).map_err(|err| {
format_err!(
"failed to write acme account to {:?}: {}",
account_config_filename,
err
)
})?;
Ok(())
}
// Save ACME account data (overtwrite existing data).
pub(crate) fn save_account_config(
account_name: &AcmeAccountName,
account: &AccountData,
) -> Result<(), Error> {
let account_config_filename = account_config_filename(account_name.as_ref());
let mut data = Vec::<u8>::new();
serde_json::to_writer_pretty(&mut data, account).map_err(|err| {
format_err!(
"failed to serialize acme account to {:?}: {}",
account_config_filename,
err
)
})?;
replace_secret_config(account_config_filename, &data)
}

View File

@ -0,0 +1,315 @@
//! Plugin type definitions.
use std::future::Future;
use std::pin::Pin;
use std::process::Stdio;
use std::sync::Arc;
use std::time::Duration;
use anyhow::{bail, format_err, Error};
use hyper::{Body, Request, Response};
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWriteExt, BufReader};
use tokio::process::Command;
use proxmox_acme::async_client::AcmeClient;
use proxmox_acme::{Authorization, Challenge};
use proxmox_rest_server::WorkerTask;
use crate::plugin_config::PluginData;
use crate::types::{AcmeDomain, DnsPlugin};
const PROXMOX_ACME_SH_PATH: &str = "/usr/share/proxmox-acme/proxmox-acme";
pub(crate) fn get_acme_plugin(
plugin_data: &PluginData,
name: &str,
) -> Result<Option<Box<dyn AcmePlugin + Send + Sync + 'static>>, Error> {
let (ty, data) = match plugin_data.get(name) {
Some(plugin) => plugin,
None => return Ok(None),
};
Ok(Some(match ty.as_str() {
"dns" => {
let plugin: DnsPlugin = serde::Deserialize::deserialize(data)?;
Box::new(plugin)
}
"standalone" => {
// this one has no config
Box::<StandaloneServer>::default()
}
other => bail!("missing implementation for plugin type '{}'", other),
}))
}
pub(crate) trait AcmePlugin {
/// Setup everything required to trigger the validation and return the corresponding validation
/// URL.
fn setup<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
&'a mut self,
client: &'b mut AcmeClient,
authorization: &'c Authorization,
domain: &'d AcmeDomain,
task: Arc<WorkerTask>,
) -> Pin<Box<dyn Future<Output = Result<&'c str, Error>> + Send + 'fut>>;
fn teardown<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
&'a mut self,
client: &'b mut AcmeClient,
authorization: &'c Authorization,
domain: &'d AcmeDomain,
task: Arc<WorkerTask>,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'fut>>;
}
fn extract_challenge<'a>(
authorization: &'a Authorization,
ty: &str,
) -> Result<&'a Challenge, Error> {
authorization
.challenges
.iter()
.find(|ch| ch.ty == ty)
.ok_or_else(|| format_err!("no supported challenge type ({}) found", ty))
}
async fn pipe_to_tasklog<T: AsyncRead + Unpin>(
pipe: T,
task: Arc<WorkerTask>,
) -> Result<(), std::io::Error> {
let mut pipe = BufReader::new(pipe);
let mut line = String::new();
loop {
line.clear();
match pipe.read_line(&mut line).await {
Ok(0) => return Ok(()),
Ok(_) => task.log_message(line.as_str()),
Err(err) => return Err(err),
}
}
}
impl DnsPlugin {
async fn action<'a>(
&self,
client: &mut AcmeClient,
authorization: &'a Authorization,
domain: &AcmeDomain,
task: Arc<WorkerTask>,
action: &str,
) -> Result<&'a str, Error> {
let challenge = extract_challenge(authorization, "dns-01")?;
let mut stdin_data = client
.dns_01_txt_value(
challenge
.token()
.ok_or_else(|| format_err!("missing token in challenge"))?,
)?
.into_bytes();
stdin_data.push(b'\n');
stdin_data.extend(self.data.as_bytes());
if stdin_data.last() != Some(&b'\n') {
stdin_data.push(b'\n');
}
let mut command = Command::new("/usr/bin/setpriv");
#[rustfmt::skip]
command.args([
"--reuid", "nobody",
"--regid", "nogroup",
"--clear-groups",
"--reset-env",
"--",
"/bin/bash",
PROXMOX_ACME_SH_PATH,
action,
&self.core.api,
domain.alias.as_deref().unwrap_or(&domain.domain),
]);
// We could use 1 socketpair, but tokio wraps them all in `File` internally causing `close`
// to be called separately on all of them without exception, so we need 3 pipes :-(
let mut child = command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let mut stdin = child.stdin.take().expect("Stdio::piped()");
let stdout = child.stdout.take().expect("Stdio::piped() failed?");
let stdout = pipe_to_tasklog(stdout, Arc::clone(&task));
let stderr = child.stderr.take().expect("Stdio::piped() failed?");
let stderr = pipe_to_tasklog(stderr, Arc::clone(&task));
let stdin = async move {
stdin.write_all(&stdin_data).await?;
stdin.flush().await?;
Ok::<_, std::io::Error>(())
};
match futures::try_join!(stdin, stdout, stderr) {
Ok(((), (), ())) => (),
Err(err) => {
if let Err(err) = child.kill().await {
task.log_message(format!(
"failed to kill '{} {}' command: {}",
PROXMOX_ACME_SH_PATH, action, err
));
}
bail!("'{}' failed: {}", PROXMOX_ACME_SH_PATH, err);
}
}
let status = child.wait().await?;
if !status.success() {
bail!(
"'{} {}' exited with error ({})",
PROXMOX_ACME_SH_PATH,
action,
status.code().unwrap_or(-1)
);
}
Ok(&challenge.url)
}
}
impl AcmePlugin for DnsPlugin {
fn setup<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
&'a mut self,
client: &'b mut AcmeClient,
authorization: &'c Authorization,
domain: &'d AcmeDomain,
task: Arc<WorkerTask>,
) -> Pin<Box<dyn Future<Output = Result<&'c str, Error>> + Send + 'fut>> {
Box::pin(async move {
let result = self
.action(client, authorization, domain, task.clone(), "setup")
.await;
let validation_delay = self.core.validation_delay.unwrap_or(30) as u64;
if validation_delay > 0 {
task.log_message(format!(
"Sleeping {} seconds to wait for TXT record propagation",
validation_delay
));
tokio::time::sleep(Duration::from_secs(validation_delay)).await;
}
result
})
}
fn teardown<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
&'a mut self,
client: &'b mut AcmeClient,
authorization: &'c Authorization,
domain: &'d AcmeDomain,
task: Arc<WorkerTask>,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'fut>> {
Box::pin(async move {
self.action(client, authorization, domain, task, "teardown")
.await
.map(drop)
})
}
}
#[derive(Default)]
struct StandaloneServer {
abort_handle: Option<futures::future::AbortHandle>,
}
// In case the "order_certificates" future gets dropped between setup & teardown, let's also cancel
// the HTTP listener on Drop:
impl Drop for StandaloneServer {
fn drop(&mut self) {
self.stop();
}
}
impl StandaloneServer {
fn stop(&mut self) {
if let Some(abort) = self.abort_handle.take() {
abort.abort();
}
}
}
async fn standalone_respond(
req: Request<Body>,
path: Arc<String>,
key_auth: Arc<String>,
) -> Result<Response<Body>, hyper::Error> {
if req.method() == hyper::Method::GET && req.uri().path() == path.as_str() {
Ok(Response::builder()
.status(http::StatusCode::OK)
.body(key_auth.as_bytes().to_vec().into())
.unwrap())
} else {
Ok(Response::builder()
.status(http::StatusCode::NOT_FOUND)
.body("Not found.".into())
.unwrap())
}
}
impl AcmePlugin for StandaloneServer {
fn setup<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
&'a mut self,
client: &'b mut AcmeClient,
authorization: &'c Authorization,
_domain: &'d AcmeDomain,
_task: Arc<WorkerTask>,
) -> Pin<Box<dyn Future<Output = Result<&'c str, Error>> + Send + 'fut>> {
use hyper::server::conn::AddrIncoming;
use hyper::service::{make_service_fn, service_fn};
Box::pin(async move {
self.stop();
let challenge = extract_challenge(authorization, "http-01")?;
let token = challenge
.token()
.ok_or_else(|| format_err!("missing token in challenge"))?;
let key_auth = Arc::new(client.key_authorization(token)?);
let path = Arc::new(format!("/.well-known/acme-challenge/{}", token));
let service = make_service_fn(move |_| {
let path = Arc::clone(&path);
let key_auth = Arc::clone(&key_auth);
async move {
Ok::<_, hyper::Error>(service_fn(move |request| {
standalone_respond(request, Arc::clone(&path), Arc::clone(&key_auth))
}))
}
});
// `[::]:80` first, then `*:80`
let incoming = AddrIncoming::bind(&(([0u16; 8], 80).into()))
.or_else(|_| AddrIncoming::bind(&(([0u8; 4], 80).into())))?;
let server = hyper::Server::builder(incoming).serve(service);
let (future, abort) = futures::future::abortable(server);
self.abort_handle = Some(abort);
tokio::spawn(future);
Ok(challenge.url.as_str())
})
}
fn teardown<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
&'a mut self,
_client: &'b mut AcmeClient,
_authorization: &'c Authorization,
_domain: &'d AcmeDomain,
_task: Arc<WorkerTask>,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'fut>> {
Box::pin(async move {
if let Some(abort) = self.abort_handle.take() {
abort.abort();
}
Ok(())
})
}
}

View File

@ -0,0 +1,400 @@
use std::mem::MaybeUninit;
use std::sync::Arc;
use std::time::Duration;
use foreign_types::ForeignTypeRef;
use anyhow::{bail, format_err, Error};
use openssl::pkey::{PKey, Private};
use openssl::rsa::Rsa;
use openssl::x509::{X509Builder, X509};
use proxmox_acme::async_client::AcmeClient;
use proxmox_log::{info, warn};
use proxmox_rest_server::WorkerTask;
use crate::types::{AcmeConfig, AcmeDomain};
use crate::CertificateInfo;
pub async fn revoke_certificate(acme_config: &AcmeConfig, certificate: &[u8]) -> Result<(), Error> {
let mut acme = super::account_config::load_account_config(&acme_config.account)
.await?
.client();
acme.revoke_certificate(certificate, None).await?;
Ok(())
}
pub struct OrderedCertificate {
pub certificate: Vec<u8>,
pub private_key_pem: Vec<u8>,
}
pub async fn order_certificate(
worker: Arc<WorkerTask>,
acme_config: &AcmeConfig,
domains: &[AcmeDomain],
) -> Result<Option<OrderedCertificate>, Error> {
use proxmox_acme::authorization::Status;
use proxmox_acme::order::Identifier;
let get_domain_config = |domain: &str| {
domains
.iter()
.find(|d| d.domain == domain)
.ok_or_else(|| format_err!("no config for domain '{}'", domain))
};
if domains.is_empty() {
info!("No domains configured to be ordered from an ACME server.");
return Ok(None);
}
let mut acme = super::account_config::load_account_config(&acme_config.account)
.await?
.client();
let (plugins, _) = super::plugin_config::plugin_config()?;
info!("Placing ACME order");
let order = acme
.new_order(domains.iter().map(|d| d.domain.to_ascii_lowercase()))
.await?;
info!("Order URL: {}", order.location);
let identifiers: Vec<String> = order
.data
.identifiers
.iter()
.map(|identifier| match identifier {
Identifier::Dns(domain) => domain.clone(),
})
.collect();
for auth_url in &order.data.authorizations {
info!("Getting authorization details from '{}'", auth_url);
let mut auth = acme.get_authorization(auth_url).await?;
let domain = match &mut auth.identifier {
Identifier::Dns(domain) => domain.to_ascii_lowercase(),
};
if auth.status == Status::Valid {
info!("{} is already validated!", domain);
continue;
}
info!("The validation for {} is pending", domain);
let domain_config: &AcmeDomain = get_domain_config(&domain)?;
let plugin_id = domain_config.plugin.as_deref().unwrap_or("standalone");
let mut plugin_cfg =
crate::acme_plugin::get_acme_plugin(&plugins, plugin_id)?.ok_or_else(|| {
format_err!("plugin '{}' for domain '{}' not found!", plugin_id, domain)
})?;
info!("Setting up validation plugin");
let validation_url = plugin_cfg
.setup(&mut acme, &auth, domain_config, Arc::clone(&worker))
.await?;
let result = request_validation(&mut acme, auth_url, validation_url).await;
if let Err(err) = plugin_cfg
.teardown(&mut acme, &auth, domain_config, Arc::clone(&worker))
.await
{
warn!(
"Failed to teardown plugin '{}' for domain '{}' - {}",
plugin_id, domain, err
);
}
result?;
}
info!("All domains validated");
info!("Creating CSR");
let csr = proxmox_acme::util::Csr::generate(&identifiers, &Default::default())?;
let mut finalize_error_cnt = 0u8;
let order_url = &order.location;
let mut order;
loop {
use proxmox_acme::order::Status;
order = acme.get_order(order_url).await?;
match order.status {
Status::Pending => {
info!("still pending, trying to finalize anyway");
let finalize = order
.finalize
.as_deref()
.ok_or_else(|| format_err!("missing 'finalize' URL in order"))?;
if let Err(err) = acme.finalize(finalize, &csr.data).await {
if finalize_error_cnt >= 5 {
return Err(err);
}
finalize_error_cnt += 1;
}
tokio::time::sleep(Duration::from_secs(5)).await;
}
Status::Ready => {
info!("order is ready, finalizing");
let finalize = order
.finalize
.as_deref()
.ok_or_else(|| format_err!("missing 'finalize' URL in order"))?;
acme.finalize(finalize, &csr.data).await?;
tokio::time::sleep(Duration::from_secs(5)).await;
}
Status::Processing => {
info!("still processing, trying again in 30 seconds");
tokio::time::sleep(Duration::from_secs(30)).await;
}
Status::Valid => {
info!("valid");
break;
}
other => bail!("order status: {:?}", other),
}
}
info!("Downloading certificate");
let certificate = acme
.get_certificate(
order
.certificate
.as_deref()
.ok_or_else(|| format_err!("missing certificate url in finalized order"))?,
)
.await?;
Ok(Some(OrderedCertificate {
certificate: certificate.to_vec(),
private_key_pem: csr.private_key_pem,
}))
}
async fn request_validation(
acme: &mut AcmeClient,
auth_url: &str,
validation_url: &str,
) -> Result<(), Error> {
info!("Triggering validation");
acme.request_challenge_validation(validation_url).await?;
info!("Sleeping for 5 seconds");
tokio::time::sleep(Duration::from_secs(5)).await;
loop {
use proxmox_acme::authorization::Status;
let auth = acme.get_authorization(auth_url).await?;
match auth.status {
Status::Pending => {
info!("Status is still 'pending', trying again in 10 seconds");
tokio::time::sleep(Duration::from_secs(10)).await;
}
Status::Valid => return Ok(()),
other => bail!(
"validating challenge '{}' failed - status: {:?}",
validation_url,
other
),
}
}
}
pub fn create_self_signed_cert(
product_name: &str,
nodename: &str,
domain: Option<&str>,
) -> Result<(PKey<Private>, X509), Error> {
let rsa = Rsa::generate(4096).unwrap();
let mut x509 = X509Builder::new()?;
x509.set_version(2)?;
let today = openssl::asn1::Asn1Time::days_from_now(0)?;
x509.set_not_before(&today)?;
let expire = openssl::asn1::Asn1Time::days_from_now(365 * 1000)?;
x509.set_not_after(&expire)?;
let mut fqdn = nodename.to_owned();
if let Some(domain) = domain {
fqdn.push('.');
fqdn.push_str(domain);
}
// we try to generate an unique 'subject' to avoid browser problems
//(reused serial numbers, ..)
let uuid = proxmox_uuid::Uuid::generate();
let mut subject_name = openssl::x509::X509NameBuilder::new()?;
subject_name.append_entry_by_text("O", product_name)?;
subject_name.append_entry_by_text("OU", &format!("{:X}", uuid))?;
subject_name.append_entry_by_text("CN", &fqdn)?;
let subject_name = subject_name.build();
x509.set_subject_name(&subject_name)?;
x509.set_issuer_name(&subject_name)?;
let bc = openssl::x509::extension::BasicConstraints::new(); // CA = false
let bc = bc.build()?;
x509.append_extension(bc)?;
let usage = openssl::x509::extension::ExtendedKeyUsage::new()
.server_auth()
.build()?;
x509.append_extension(usage)?;
let context = x509.x509v3_context(None, None);
let mut alt_names = openssl::x509::extension::SubjectAlternativeName::new();
alt_names.ip("127.0.0.1");
alt_names.ip("::1");
alt_names.dns("localhost");
if nodename != "localhost" {
alt_names.dns(nodename);
}
if nodename != fqdn {
alt_names.dns(&fqdn);
}
let alt_names = alt_names.build(&context)?;
x509.append_extension(alt_names)?;
let pub_pem = rsa.public_key_to_pem()?;
let pubkey = PKey::public_key_from_pem(&pub_pem)?;
x509.set_pubkey(&pubkey)?;
let context = x509.x509v3_context(None, None);
let ext = openssl::x509::extension::SubjectKeyIdentifier::new().build(&context)?;
x509.append_extension(ext)?;
let context = x509.x509v3_context(None, None);
let ext = openssl::x509::extension::AuthorityKeyIdentifier::new()
.keyid(true)
.build(&context)?;
x509.append_extension(ext)?;
let privkey = PKey::from_rsa(rsa)?;
x509.sign(&privkey, openssl::hash::MessageDigest::sha256())?;
Ok((privkey, x509.build()))
}
impl CertificateInfo {
pub fn from_pem(filename: &str, cert_pem: &[u8]) -> Result<Self, Error> {
let x509 = openssl::x509::X509::from_pem(cert_pem)?;
let cert_pem = String::from_utf8(cert_pem.to_vec())
.map_err(|_| format_err!("certificate in {:?} is not a valid PEM file", filename))?;
let pubkey = x509.public_key()?;
let subject = x509name_to_string(x509.subject_name())?;
let issuer = x509name_to_string(x509.issuer_name())?;
let fingerprint = x509.digest(openssl::hash::MessageDigest::sha256())?;
let fingerprint = hex::encode(fingerprint)
.as_bytes()
.chunks(2)
.map(|v| std::str::from_utf8(v).unwrap())
.collect::<Vec<&str>>()
.join(":");
let public_key_type = openssl::nid::Nid::from_raw(pubkey.id().as_raw())
.long_name()
.unwrap_or("<unsupported key type>")
.to_owned();
let san = x509
.subject_alt_names()
.map(|san| {
san.into_iter()
.filter_map(|name| {
// this is not actually a map and we don't want to break the pattern
#[allow(clippy::manual_map)]
if let Some(name) = name.dnsname() {
Some(format!("DNS: {name}"))
} else if let Some(ip) = name.ipaddress() {
Some(format!("IP: {ip:?}"))
} else if let Some(email) = name.email() {
Some(format!("EMAIL: {email}"))
} else if let Some(uri) = name.uri() {
Some(format!("URI: {uri}"))
} else {
None
}
})
.collect()
})
.unwrap_or_default();
Ok(CertificateInfo {
filename: filename.to_string(),
pem: Some(cert_pem),
subject,
issuer,
fingerprint: Some(fingerprint),
public_key_bits: Some(pubkey.bits()),
notbefore: asn1_time_to_unix(x509.not_before()).ok(),
notafter: asn1_time_to_unix(x509.not_after()).ok(),
public_key_type,
san,
})
}
/// Check if the certificate is expired at or after a specific unix epoch.
pub fn is_expired_after_epoch(&self, epoch: i64) -> Result<bool, Error> {
if let Some(notafter) = self.notafter {
Ok(notafter < epoch)
} else {
Ok(false)
}
}
}
fn x509name_to_string(name: &openssl::x509::X509NameRef) -> Result<String, Error> {
let mut parts = Vec::new();
for entry in name.entries() {
parts.push(format!(
"{} = {}",
entry.object().nid().short_name()?,
entry.data().as_utf8()?
));
}
Ok(parts.join(", "))
}
// C type:
#[allow(non_camel_case_types)]
type ASN1_TIME = <openssl::asn1::Asn1TimeRef as ForeignTypeRef>::CType;
unsafe extern "C" {
fn ASN1_TIME_to_tm(s: *const ASN1_TIME, tm: *mut libc::tm) -> libc::c_int;
}
fn asn1_time_to_unix(time: &openssl::asn1::Asn1TimeRef) -> Result<i64, Error> {
let mut c_tm = MaybeUninit::<libc::tm>::uninit();
let rc = unsafe { ASN1_TIME_to_tm(time.as_ptr(), c_tm.as_mut_ptr()) };
if rc != 1 {
bail!("failed to parse ASN1 time");
}
let mut c_tm = unsafe { c_tm.assume_init() };
proxmox_time::timegm(&mut c_tm)
}

View File

@ -0,0 +1,71 @@
//! Read DNS Challenge schemas.
//!
//! Those schemas are provided by debian package "libproxmox-acme-plugins".
use std::sync::{Arc, LazyLock, Mutex};
use std::time::SystemTime;
use anyhow::Error;
use serde::Serialize;
use serde_json::Value;
use proxmox_sys::fs::file_read_string;
use crate::types::AcmeChallengeSchema;
const ACME_DNS_SCHEMA_FN: &str = "/usr/share/proxmox-acme/dns-challenge-schema.json";
/// Wrapper for efficient Arc use when returning the ACME challenge-plugin schema for serializing.
pub struct ChallengeSchemaWrapper {
inner: Arc<Vec<AcmeChallengeSchema>>,
}
impl Serialize for ChallengeSchemaWrapper {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.inner.serialize(serializer)
}
}
fn load_dns_challenge_schema() -> Result<Vec<AcmeChallengeSchema>, Error> {
let raw = file_read_string(ACME_DNS_SCHEMA_FN)?;
let schemas: serde_json::Map<String, Value> = serde_json::from_str(&raw)?;
Ok(schemas
.iter()
.map(|(id, schema)| AcmeChallengeSchema {
id: id.to_owned(),
name: schema
.get("name")
.and_then(Value::as_str)
.unwrap_or(id)
.to_owned(),
ty: "dns".into(),
schema: schema.to_owned(),
})
.collect())
}
pub fn get_cached_challenge_schemas() -> Result<ChallengeSchemaWrapper, Error> {
#[allow(clippy::type_complexity)]
static CACHE: LazyLock<Mutex<Option<(Arc<Vec<AcmeChallengeSchema>>, SystemTime)>>> =
LazyLock::new(|| Mutex::new(None));
// the actual loading code
let mut last = CACHE.lock().unwrap();
let actual_mtime = std::fs::metadata(ACME_DNS_SCHEMA_FN)?.modified()?;
let schema = match &*last {
Some((schema, cached_mtime)) if *cached_mtime >= actual_mtime => schema.clone(),
_ => {
let new_schema = Arc::new(load_dns_challenge_schema()?);
*last = Some((Arc::clone(&new_schema), actual_mtime));
new_schema
}
};
Ok(ChallengeSchemaWrapper { inner: schema })
}

View File

@ -0,0 +1,18 @@
use std::borrow::Cow;
use crate::types::KnownAcmeDirectory;
/// List of known ACME directorties.
pub const KNOWN_ACME_DIRECTORIES: &[KnownAcmeDirectory] = &[
KnownAcmeDirectory {
name: Cow::Borrowed("Let's Encrypt V2"),
url: Cow::Borrowed("https://acme-v02.api.letsencrypt.org/directory"),
},
KnownAcmeDirectory {
name: Cow::Borrowed("Let's Encrypt V2 Staging"),
url: Cow::Borrowed("https://acme-staging-v02.api.letsencrypt.org/directory"),
},
];
/// Default ACME directorties.
pub const DEFAULT_ACME_DIRECTORY_ENTRY: &KnownAcmeDirectory = &KNOWN_ACME_DIRECTORIES[0];

View File

@ -0,0 +1,55 @@
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use anyhow::Error;
use proxmox_product_config::create_secret_dir;
#[derive(Debug)]
struct AcmeApiConfig {
acme_config_dir: PathBuf,
acme_account_dir: PathBuf,
}
static ACME_ACME_CONFIG: OnceLock<AcmeApiConfig> = OnceLock::new();
/// Initialize the global product configuration.
pub fn init<P: AsRef<Path>>(acme_config_dir: P, create_subdirs: bool) -> Result<(), Error> {
let acme_config_dir = acme_config_dir.as_ref().to_owned();
ACME_ACME_CONFIG
.set(AcmeApiConfig {
acme_account_dir: acme_config_dir.join("accounts"),
acme_config_dir,
})
.expect("cannot set acme configuration twice");
if create_subdirs {
create_secret_dir(self::acme_config_dir(), false)?;
create_secret_dir(acme_account_dir(), false)?;
}
Ok(())
}
fn acme_api_config() -> &'static AcmeApiConfig {
ACME_ACME_CONFIG
.get()
.expect("ProxmoxProductConfig is not initialized!")
}
fn acme_config_dir() -> &'static Path {
acme_api_config().acme_config_dir.as_path()
}
pub(crate) fn acme_account_dir() -> &'static Path {
acme_api_config().acme_account_dir.as_path()
}
pub(crate) fn plugin_cfg_filename() -> PathBuf {
acme_config_dir().join("plugins.cfg")
}
pub(crate) fn plugin_cfg_lockfile() -> PathBuf {
acme_config_dir().join("plugins.lck")
}

View File

@ -0,0 +1,48 @@
//! ACME API crate (API types and API implementation)
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
mod types;
pub use types::*;
#[cfg(feature = "impl")]
mod init;
#[cfg(feature = "impl")]
pub use init::*;
#[cfg(feature = "impl")]
mod config;
#[cfg(feature = "impl")]
pub use config::{DEFAULT_ACME_DIRECTORY_ENTRY, KNOWN_ACME_DIRECTORIES};
#[cfg(feature = "impl")]
mod challenge_schemas;
#[cfg(feature = "impl")]
pub use challenge_schemas::{get_cached_challenge_schemas, ChallengeSchemaWrapper};
#[cfg(feature = "impl")]
mod account_config;
#[cfg(feature = "impl")]
pub use account_config::account_config_filename;
#[cfg(feature = "impl")]
mod plugin_config;
#[cfg(feature = "impl")]
mod account_api_impl;
#[cfg(feature = "impl")]
pub use account_api_impl::{
deactivate_account, get_account, get_tos, list_accounts, register_account, update_account,
};
#[cfg(feature = "impl")]
mod plugin_api_impl;
#[cfg(feature = "impl")]
pub use plugin_api_impl::{add_plugin, delete_plugin, get_plugin, list_plugins, update_plugin};
#[cfg(feature = "impl")]
pub(crate) mod acme_plugin;
#[cfg(feature = "impl")]
mod certificate_helpers;
#[cfg(feature = "impl")]
pub use certificate_helpers::{create_self_signed_cert, order_certificate, revoke_certificate};

View File

@ -0,0 +1,169 @@
//! ACME plugin configuration API implementation
use anyhow::{bail, format_err, Error};
use serde::Deserialize;
use serde_json::Value;
use proxmox_config_digest::ConfigDigest;
use proxmox_schema::param_bail;
use crate::types::{
DeletablePluginProperty, DnsPlugin, DnsPluginCore, DnsPluginCoreUpdater, PluginConfig,
};
use proxmox_router::{http_bail, RpcEnvironment};
pub fn list_plugins(rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<PluginConfig>, Error> {
let (plugins, digest) = super::plugin_config::plugin_config()?;
rpcenv["digest"] = digest.to_hex().into();
Ok(plugins
.iter()
.map(|(id, (ty, data))| modify_cfg_for_api(id, ty, data))
.collect())
}
pub fn get_plugin(id: String, rpcenv: &mut dyn RpcEnvironment) -> Result<PluginConfig, Error> {
let (plugins, digest) = super::plugin_config::plugin_config()?;
rpcenv["digest"] = digest.to_hex().into();
match plugins.get(&id) {
Some((ty, data)) => Ok(modify_cfg_for_api(&id, ty, data)),
None => http_bail!(NOT_FOUND, "no such plugin"),
}
}
pub fn add_plugin(r#type: String, core: DnsPluginCore, data: String) -> Result<(), Error> {
// Currently we only support DNS plugins and the standalone plugin is "fixed":
if r#type != "dns" {
param_bail!("type", "invalid ACME plugin type: {:?}", r#type);
}
let data = String::from_utf8(base64::decode(data)?)
.map_err(|_| format_err!("data must be valid UTF-8"))?;
let id = core.id.clone();
let _lock = super::plugin_config::lock_plugin_config()?;
let (mut plugins, _digest) = super::plugin_config::plugin_config()?;
if plugins.contains_key(&id) {
param_bail!("id", "ACME plugin ID {:?} already exists", id);
}
let plugin = serde_json::to_value(DnsPlugin { core, data })?;
plugins.insert(id, r#type, plugin);
super::plugin_config::save_plugin_config(&plugins)?;
Ok(())
}
pub fn update_plugin(
id: String,
update: DnsPluginCoreUpdater,
data: Option<String>,
delete: Option<Vec<DeletablePluginProperty>>,
digest: Option<ConfigDigest>,
) -> Result<(), Error> {
let data = data
.as_deref()
.map(base64::decode)
.transpose()?
.map(String::from_utf8)
.transpose()
.map_err(|_| format_err!("data must be valid UTF-8"))?;
let _lock = super::plugin_config::lock_plugin_config()?;
let (mut plugins, expected_digest) = super::plugin_config::plugin_config()?;
expected_digest.detect_modification(digest.as_ref())?;
match plugins.get_mut(&id) {
Some((ty, ref mut entry)) => {
if ty != "dns" {
bail!("cannot update plugin of type {:?}", ty);
}
let mut plugin = DnsPlugin::deserialize(&*entry)?;
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletablePluginProperty::ValidationDelay => {
plugin.core.validation_delay = None;
}
DeletablePluginProperty::Disable => {
plugin.core.disable = None;
}
}
}
}
if let Some(data) = data {
plugin.data = data;
}
if let Some(api) = update.api {
plugin.core.api = api;
}
if update.validation_delay.is_some() {
plugin.core.validation_delay = update.validation_delay;
}
if update.disable.is_some() {
plugin.core.disable = update.disable;
}
*entry = serde_json::to_value(plugin)?;
}
None => http_bail!(NOT_FOUND, "no such plugin"),
}
super::plugin_config::save_plugin_config(&plugins)?;
Ok(())
}
pub fn delete_plugin(id: String) -> Result<(), Error> {
let _lock = super::plugin_config::lock_plugin_config()?;
let (mut plugins, _digest) = super::plugin_config::plugin_config()?;
if plugins.remove(&id).is_none() {
http_bail!(NOT_FOUND, "no such plugin");
}
super::plugin_config::save_plugin_config(&plugins)?;
Ok(())
}
// See PMG/PVE's $modify_cfg_for_api sub
fn modify_cfg_for_api(id: &str, ty: &str, data: &Value) -> PluginConfig {
let mut entry = data.clone();
let obj = entry.as_object_mut().unwrap();
obj.remove("id");
obj.insert("plugin".to_string(), Value::String(id.to_owned()));
obj.insert("type".to_string(), Value::String(ty.to_owned()));
// FIXME: This needs to go once the `Updater` is fixed.
// None of these should be able to fail unless the user changed the files by hand, in which
// case we leave the unmodified string in the Value for now. This will be handled with an error
// later.
if let Some(Value::String(ref mut data)) = obj.get_mut("data") {
if let Ok(new) = base64::decode_config(&data, base64::URL_SAFE_NO_PAD) {
if let Ok(utf8) = String::from_utf8(new) {
*data = utf8;
}
}
}
// PVE/PMG do this explicitly for ACME plugins...
// obj.insert("digest".to_string(), Value::String(digest.clone()));
serde_json::from_value(entry).unwrap_or_else(|_| PluginConfig {
plugin: "*Error*".to_string(),
ty: "*Error*".to_string(),
..Default::default()
})
}

View File

@ -0,0 +1,114 @@
//! ACME plugin configuration helpers (SectionConfig implementation)
use std::sync::LazyLock;
use anyhow::Error;
use serde_json::Value;
use proxmox_config_digest::ConfigDigest;
use proxmox_product_config::{open_api_lockfile, replace_secret_config, ApiLockGuard};
use proxmox_schema::{ApiType, Schema};
use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin};
use crate::types::{DnsPlugin, StandalonePlugin, PLUGIN_ID_SCHEMA};
static CONFIG: LazyLock<SectionConfig> = LazyLock::new(init);
impl DnsPlugin {
pub fn decode_data(&self, output: &mut Vec<u8>) -> Result<(), Error> {
Ok(base64::decode_config_buf(
&self.data,
base64::URL_SAFE_NO_PAD,
output,
)?)
}
}
fn init() -> SectionConfig {
let mut config = SectionConfig::new(&PLUGIN_ID_SCHEMA);
let standalone_schema = match &StandalonePlugin::API_SCHEMA {
Schema::Object(schema) => schema,
_ => unreachable!(),
};
let standalone_plugin = SectionConfigPlugin::new(
"standalone".to_string(),
Some("id".to_string()),
standalone_schema,
);
config.register_plugin(standalone_plugin);
let dns_challenge_schema = match DnsPlugin::API_SCHEMA {
Schema::AllOf(ref schema) => schema,
_ => unreachable!(),
};
let dns_challenge_plugin = SectionConfigPlugin::new(
"dns".to_string(),
Some("id".to_string()),
dns_challenge_schema,
);
config.register_plugin(dns_challenge_plugin);
config
}
pub(crate) fn lock_plugin_config() -> Result<ApiLockGuard, Error> {
let plugin_cfg_lockfile = crate::plugin_cfg_lockfile();
open_api_lockfile(plugin_cfg_lockfile, None, true)
}
pub(crate) fn plugin_config() -> Result<(PluginData, ConfigDigest), Error> {
let plugin_cfg_filename = crate::plugin_cfg_filename();
let content =
proxmox_sys::fs::file_read_optional_string(&plugin_cfg_filename)?.unwrap_or_default();
let digest = ConfigDigest::from_slice(content.as_bytes());
let mut data = CONFIG.parse(plugin_cfg_filename, &content)?;
if !data.sections.contains_key("standalone") {
let standalone = StandalonePlugin::default();
data.set_data("standalone", "standalone", &standalone)
.unwrap();
}
Ok((PluginData { data }, digest))
}
pub(crate) fn save_plugin_config(config: &PluginData) -> Result<(), Error> {
let plugin_cfg_filename = crate::plugin_cfg_filename();
let raw = CONFIG.write(&plugin_cfg_filename, &config.data)?;
replace_secret_config(plugin_cfg_filename, raw.as_bytes())
}
pub(crate) struct PluginData {
data: SectionConfigData,
}
// And some convenience helpers.
impl PluginData {
pub fn remove(&mut self, name: &str) -> Option<(String, Value)> {
self.data.sections.remove(name)
}
pub fn contains_key(&mut self, name: &str) -> bool {
self.data.sections.contains_key(name)
}
pub fn get(&self, name: &str) -> Option<&(String, Value)> {
self.data.sections.get(name)
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut (String, Value)> {
self.data.sections.get_mut(name)
}
pub fn insert(&mut self, id: String, ty: String, plugin: Value) {
self.data.sections.insert(id, (ty, plugin));
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &(String, Value))> + Send {
self.data.sections.iter()
}
}

View File

@ -0,0 +1,355 @@
//! ACME API type definitions.
use std::borrow::Cow;
use anyhow::Error;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use proxmox_schema::api_types::{DNS_ALIAS_FORMAT, DNS_NAME_FORMAT, SAFE_ID_FORMAT};
use proxmox_schema::{api, ApiStringFormat, ApiType, Schema, StringSchema, Updater};
use proxmox_acme::types::AccountData as AcmeAccountData;
#[api(
properties: {
san: {
type: Array,
items: {
description: "A SubjectAlternateName entry.",
type: String,
},
},
},
)]
/// Certificate information.
#[derive(PartialEq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct CertificateInfo {
/// Certificate file name.
pub filename: String,
/// Certificate subject name.
pub subject: String,
/// List of certificate's SubjectAlternativeName entries.
pub san: Vec<String>,
/// Certificate issuer name.
pub issuer: String,
/// Certificate's notBefore timestamp (UNIX epoch).
#[serde(skip_serializing_if = "Option::is_none")]
pub notbefore: Option<i64>,
/// Certificate's notAfter timestamp (UNIX epoch).
#[serde(skip_serializing_if = "Option::is_none")]
pub notafter: Option<i64>,
/// Certificate in PEM format.
#[serde(skip_serializing_if = "Option::is_none")]
pub pem: Option<String>,
/// Certificate's public key algorithm.
pub public_key_type: String,
/// Certificate's public key size if available.
#[serde(skip_serializing_if = "Option::is_none")]
pub public_key_bits: Option<u32>,
/// The SSL Fingerprint.
#[serde(skip_serializing_if = "Option::is_none")]
pub fingerprint: Option<String>,
}
proxmox_schema::api_string_type! {
#[api(format: &SAFE_ID_FORMAT)]
/// ACME account name.
#[derive(Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[serde(transparent)]
pub struct AcmeAccountName(String);
}
#[api(
properties: {
name: { type: String },
url: { type: String },
},
)]
/// An ACME directory endpoint with a name and URL.
#[derive(Clone, Deserialize, Serialize, PartialEq)]
pub struct KnownAcmeDirectory {
/// The ACME directory's name.
pub name: Cow<'static, str>,
/// The ACME directory's endpoint URL.
pub url: Cow<'static, str>,
}
#[api(
properties: {
schema: {
type: Object,
additional_properties: true,
properties: {},
},
type: {
type: String,
},
},
)]
#[derive(Clone, Deserialize, Serialize, PartialEq)]
/// Schema for an ACME challenge plugin.
pub struct AcmeChallengeSchema {
/// Plugin ID.
pub id: String,
/// Human readable name, falls back to id.
pub name: String,
/// Plugin Type.
#[serde(rename = "type")]
pub ty: String,
/// The plugin's parameter schema.
pub schema: Value,
}
#[api(
properties: {
"domain": { format: &DNS_NAME_FORMAT },
"alias": {
optional: true,
format: &DNS_ALIAS_FORMAT,
},
"plugin": {
optional: true,
format: &SAFE_ID_FORMAT,
},
},
default_key: "domain",
)]
#[derive(Clone, PartialEq, Deserialize, Serialize)]
/// A domain entry for an ACME certificate.
pub struct AcmeDomain {
/// The domain to certify for.
pub domain: String,
/// The domain to use for challenges instead of the default acme challenge domain.
///
/// This is useful if you use CNAME entries to redirect `_acme-challenge.*` domains to a
/// different DNS server.
#[serde(skip_serializing_if = "Option::is_none")]
pub alias: Option<String>,
/// The plugin to use to validate this domain.
///
/// Empty means standalone HTTP validation is used.
#[serde(skip_serializing_if = "Option::is_none")]
pub plugin: Option<String>,
}
/// ACME domain configuration string [Schema].
pub const ACME_DOMAIN_PROPERTY_SCHEMA: Schema =
StringSchema::new("ACME domain configuration string")
.format(&ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA))
.schema();
/// Parse [AcmeDomain] from property string.
pub fn parse_acme_domain_string(value_str: &str) -> Result<AcmeDomain, Error> {
let value = AcmeDomain::API_SCHEMA.parse_property_string(value_str)?;
let value: AcmeDomain = serde_json::from_value(value)?;
Ok(value)
}
/// Format [AcmeDomain] as property string.
pub fn create_acme_domain_string(config: &AcmeDomain) -> String {
proxmox_schema::property_string::print::<AcmeDomain>(config).unwrap()
}
#[api()]
#[derive(Clone, PartialEq, Deserialize, Serialize)]
/// ACME Account information.
///
/// This is what we return via the API.
pub struct AccountInfo {
/// Raw account data.
pub account: AcmeAccountData,
/// The ACME directory URL the account was created at.
pub directory: String,
/// The account's own URL within the ACME directory.
pub location: String,
/// The ToS URL, if the user agreed to one.
#[serde(skip_serializing_if = "Option::is_none")]
pub tos: Option<String>,
}
/// An ACME Account entry.
///
/// Currently only contains a 'name' property.
#[api()]
#[derive(Clone, PartialEq, Deserialize, Serialize)]
pub struct AcmeAccountEntry {
pub name: AcmeAccountName,
}
#[api()]
#[derive(Clone, PartialEq, Deserialize, Serialize)]
/// The ACME configuration.
///
/// Currently only contains the name of the account use.
pub struct AcmeConfig {
/// Account to use to acquire ACME certificates.
pub account: String,
}
/// Parse [AcmeConfig] from property string.
pub fn parse_acme_config_string(value_str: &str) -> Result<AcmeConfig, Error> {
let value = AcmeConfig::API_SCHEMA.parse_property_string(value_str)?;
let value: AcmeConfig = serde_json::from_value(value)?;
Ok(value)
}
/// Format [AcmeConfig] as property string.
pub fn create_acme_config_string(config: &AcmeConfig) -> String {
proxmox_schema::property_string::print::<AcmeConfig>(config).unwrap()
}
/// [Schema] for ACME Challenge Plugin ID.
pub const PLUGIN_ID_SCHEMA: Schema = StringSchema::new("ACME Challenge Plugin ID.")
.format(&SAFE_ID_FORMAT)
.min_length(1)
.max_length(32)
.schema();
#[api]
#[derive(Clone, Default, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
/// ACME plugin config. The API's format is inherited from PVE/PMG:
pub struct PluginConfig {
/// Plugin ID.
pub plugin: String,
/// Plugin type.
#[serde(rename = "type")]
pub ty: String,
/// DNS Api name.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub api: Option<String>,
/// Plugin configuration data.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub data: Option<String>,
/// Extra delay in seconds to wait before requesting validation.
///
/// Allows to cope with long TTL of DNS records.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub validation_delay: Option<u32>,
/// Flag to disable the config.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub disable: Option<bool>,
}
#[api(
properties: {
id: { schema: PLUGIN_ID_SCHEMA },
},
)]
#[derive(Deserialize, Serialize)]
/// Standalone ACME Plugin for the http-1 challenge.
pub struct StandalonePlugin {
/// Plugin ID.
id: String,
}
impl Default for StandalonePlugin {
fn default() -> Self {
Self {
id: "standalone".to_string(),
}
}
}
#[api(
properties: {
id: { schema: PLUGIN_ID_SCHEMA },
disable: {
optional: true,
default: false,
},
"validation-delay": {
default: 30,
optional: true,
minimum: 0,
maximum: 2 * 24 * 60 * 60,
},
},
)]
/// DNS ACME Challenge Plugin core data.
#[derive(Deserialize, Serialize, Updater)]
#[serde(rename_all = "kebab-case")]
pub struct DnsPluginCore {
/// Plugin ID.
#[updater(skip)]
pub id: String,
/// DNS API Plugin Id.
pub api: String,
/// Extra delay in seconds to wait before requesting validation.
///
/// Allows to cope with long TTL of DNS records.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub validation_delay: Option<u32>,
/// Flag to disable the config.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub disable: Option<bool>,
}
#[api(
properties: {
core: { type: DnsPluginCore },
},
)]
/// DNS ACME Challenge Plugin.
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct DnsPlugin {
#[serde(flatten)]
pub core: DnsPluginCore,
// We handle this property separately in the API calls.
/// DNS plugin data (base64url encoded without padding).
#[serde(with = "proxmox_serde::string_as_base64url_nopad")]
pub data: String,
}
#[api()]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
/// Deletable plugin property names.
pub enum DeletablePluginProperty {
/// Delete the disable property
Disable,
/// Delete the validation-delay property
ValidationDelay,
}
#[api(
properties: {
name: { type: AcmeAccountName },
},
)]
/// An ACME Account entry.
///
/// Currently only contains a 'name' property.
#[derive(Clone, PartialEq, Deserialize, Serialize)]
pub struct AccountEntry {
pub name: AcmeAccountName,
}

45
proxmox-acme/Cargo.toml Normal file
View File

@ -0,0 +1,45 @@
[package]
name = "proxmox-acme"
description = "ACME client library"
version = "0.5.4"
exclude = [ "debian" ]
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
base64.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
# For the ACME implementation
openssl = { workspace = true, optional = true }
# For the client
native-tls = { workspace = true, optional = true }
proxmox-schema = { workspace = true, optional = true, features = [ "api-macro" ] }
proxmox-http = { workspace = true, optional = true, features = [ "client" ] }
anyhow = { workspace = true, optional = true }
bytes = { workspace = true, optional = true }
hyper = { workspace = true, optional = true }
[dependencies.ureq]
optional = true
version = "2.4"
default-features = false
features = [ "native-tls", "gzip" ]
[features]
default = [ "impl" ]
api-types = [ "dep:proxmox-schema" ]
impl = [ "api-types", "dep:openssl" ]
client = [ "impl", "dep:ureq", "dep:native-tls"]
async-client = [ "impl", "dep:hyper", "dep:proxmox-http", "dep:anyhow", "dep:bytes" ]
[dev-dependencies]
anyhow.workspace = true

View File

@ -0,0 +1,127 @@
rust-proxmox-acme (0.5.4-1) bookworm; urgency=medium
* rebuild with proxmox-schema 4.0
-- Proxmox Support Team <support@proxmox.com> Wed, 15 Jan 2025 12:27:29 +0100
rust-proxmox-acme (0.5.3) bookworm; urgency=medium
* detect base64 vs base64url encoded eab hmac key
-- Proxmox Support Team <support@proxmox.com> Thu, 03 Oct 2024 09:52:20 +0200
rust-proxmox-acme (0.5.2) bookworm; urgency=medium
* allow to compile/use api types separately.
* add async-client feature
-- Proxmox Support Team <support@proxmox.com> Thu, 16 May 2024 11:31:43 +0200
rust-proxmox-acme (0.5.1) bookworm; urgency=medium
* add api-types feature to provide schemas for api types
* derive PartialEq for api types for integration in rust based ui code
-- Proxmox Support Team <support@proxmox.com> Thu, 07 Mar 2024 13:27:08 +0100
rust-proxmox-acme (0.5.0) bookworm; urgency=medium
* add external account binding support
* add a few more standard fields to Meta
* update deprecated openssl calls
* documentation fixups
* general code improvements and cleanups
-- Proxmox Support Team <support@proxmox.com> Mon, 04 Dec 2023 11:46:26 +0100
rust-proxmox-acme-rs (0.4.0) pve; urgency=medium
* switch from curl to ureq with native-tls
* bump edition to 2021
-- Proxmox Support Team <support@proxmox.com> Tue, 01 Feb 2022 10:19:29 +0100
rust-proxmox-acme-rs (0.3.2) pve; urgency=medium
* rebuild with base64 0.13
-- Proxmox Support Team <support@proxmox.com> Thu, 18 Nov 2021 12:49:25 +0100
rust-proxmox-acme-rs (0.3.1) pve; urgency=medium
* add proxy support
-- Proxmox Support Team <support@proxmox.com> Thu, 18 Nov 2021 09:46:34 +0100
rust-proxmox-acme-rs (0.3.0) pve; urgency=medium
* directory: make metadata optional
-- Proxmox Support Team <support@proxmox.com> Thu, 21 Oct 2021 13:10:27 +0200
rust-proxmox-acme-rs (0.2.2-1) pve; urgency=medium
* improve crate documentation
* mark `Error` as 'must_use'
* make status types `Copy`
* add Client::directory_url() to get the URL without querying the whole
directory
-- Proxmox Support Team <support@proxmox.com> Fri, 07 May 2021 13:53:08 +0200
rust-proxmox-acme-rs (0.2.1-1) pve; urgency=medium
* make revocation workflow accessible without client
-- Proxmox Support Team <support@proxmox.com> Wed, 14 Apr 2021 14:56:49 +0200
rust-proxmox-acme-rs (0.2.0-1) pve; urgency=medium
* add 'status' and 'url' as fixed members to `Challenge`
* expose some workflow helpers in a more consistentw ay
* add `util::Csr` for CSR generation
-- Proxmox Support Team <support@proxmox.com> Mon, 12 Apr 2021 13:06:19 +0200
rust-proxmox-acme-rs (0.1.4-1) pve; urgency=medium
* collect extra account fields (such as 'created' from let's encrypt)
in the AccountData struct
-- Proxmox Support Team <support@proxmox.com> Wed, 17 Mar 2021 15:28:09 +0100
rust-proxmox-acme-rs (0.1.3-1) pve; urgency=medium
* fix padding in ecdsa signatures
-- Proxmox Support Team <support@proxmox.com> Wed, 17 Mar 2021 13:34:10 +0100
rust-proxmox-acme-rs (0.1.2-1) pve; urgency=medium
* include Content-length header in requests
-- Proxmox Support Team <support@proxmox.com> Fri, 12 Mar 2021 15:43:01 +0100
rust-proxmox-acme-rs (0.1.1-1) pve; urgency=medium
* make AccountData fields public
-- Proxmox Support Team <support@proxmox.com> Tue, 09 Mar 2021 13:22:55 +0100
rust-proxmox-acme-rs (0.1.0-1) pve; urgency=medium
* initial release
-- Proxmox Support Team <support@proxmox.com> Tue, 09 Mar 2021 13:01:56 +0100

120
proxmox-acme/debian/control Normal file
View File

@ -0,0 +1,120 @@
Source: rust-proxmox-acme
Section: rust
Priority: optional
Build-Depends: debhelper-compat (= 13),
dh-sequence-cargo,
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-base64-0.13+default-dev <!nocheck>,
librust-openssl-0.10+default-dev <!nocheck>,
librust-proxmox-schema-4+api-macro-dev <!nocheck>,
librust-proxmox-schema-4+default-dev <!nocheck>,
librust-serde-1+default-dev <!nocheck>,
librust-serde-1+derive-dev <!nocheck>,
librust-serde-json-1+default-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.7.0
Vcs-Git:
Vcs-Browser:
Homepage: https://proxmox.com
X-Cargo-Crate: proxmox-acme
Rules-Requires-Root: no
Package: librust-proxmox-acme-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-base64-0.13+default-dev,
librust-serde-1+default-dev,
librust-serde-1+derive-dev,
librust-serde-json-1+default-dev
Recommends:
librust-proxmox-acme+impl-dev (= ${binary:Version})
Suggests:
librust-proxmox-acme+api-types-dev (= ${binary:Version}),
librust-proxmox-acme+async-client-dev (= ${binary:Version}),
librust-proxmox-acme+client-dev (= ${binary:Version})
Provides:
librust-proxmox-acme-0-dev (= ${binary:Version}),
librust-proxmox-acme-0.5-dev (= ${binary:Version}),
librust-proxmox-acme-0.5.4-dev (= ${binary:Version})
Description: ACME client library - Rust source code
Source code for Debianized Rust crate "proxmox-acme"
Package: librust-proxmox-acme+api-types-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-acme-dev (= ${binary:Version}),
librust-proxmox-schema-4+api-macro-dev,
librust-proxmox-schema-4+default-dev
Provides:
librust-proxmox-acme-0+api-types-dev (= ${binary:Version}),
librust-proxmox-acme-0.5+api-types-dev (= ${binary:Version}),
librust-proxmox-acme-0.5.4+api-types-dev (= ${binary:Version})
Description: ACME client library - feature "api-types"
This metapackage enables feature "api-types" for the Rust proxmox-acme crate,
by pulling in any additional dependencies needed by that feature.
Package: librust-proxmox-acme+async-client-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-acme-dev (= ${binary:Version}),
librust-proxmox-acme+impl-dev (= ${binary:Version}),
librust-anyhow-1+default-dev,
librust-bytes-1+default-dev,
librust-hyper-0.14+default-dev (>= 0.14.5-~~),
librust-proxmox-http-0.9+client-dev (>= 0.9.4-~~),
librust-proxmox-http-0.9+default-dev (>= 0.9.4-~~)
Provides:
librust-proxmox-acme-0+async-client-dev (= ${binary:Version}),
librust-proxmox-acme-0.5+async-client-dev (= ${binary:Version}),
librust-proxmox-acme-0.5.4+async-client-dev (= ${binary:Version})
Description: ACME client library - feature "async-client"
This metapackage enables feature "async-client" for the Rust proxmox-acme
crate, by pulling in any additional dependencies needed by that feature.
Package: librust-proxmox-acme+client-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-acme-dev (= ${binary:Version}),
librust-proxmox-acme+impl-dev (= ${binary:Version}),
librust-native-tls-0.2+default-dev,
librust-ureq-2+gzip-dev (>= 2.4-~~),
librust-ureq-2+native-tls-dev (>= 2.4-~~)
Provides:
librust-proxmox-acme-0+client-dev (= ${binary:Version}),
librust-proxmox-acme-0.5+client-dev (= ${binary:Version}),
librust-proxmox-acme-0.5.4+client-dev (= ${binary:Version})
Description: ACME client library - feature "client"
This metapackage enables feature "client" for the Rust proxmox-acme crate, by
pulling in any additional dependencies needed by that feature.
Package: librust-proxmox-acme+impl-dev
Architecture: any
Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-acme-dev (= ${binary:Version}),
librust-proxmox-acme+api-types-dev (= ${binary:Version}),
librust-openssl-0.10+default-dev
Provides:
librust-proxmox-acme+default-dev (= ${binary:Version}),
librust-proxmox-acme-0+impl-dev (= ${binary:Version}),
librust-proxmox-acme-0+default-dev (= ${binary:Version}),
librust-proxmox-acme-0.5+impl-dev (= ${binary:Version}),
librust-proxmox-acme-0.5+default-dev (= ${binary:Version}),
librust-proxmox-acme-0.5.4+impl-dev (= ${binary:Version}),
librust-proxmox-acme-0.5.4+default-dev (= ${binary:Version})
Description: ACME client library - feature "impl" and 1 more
This metapackage enables feature "impl" for the Rust proxmox-acme crate, by
pulling in any additional dependencies needed by that feature.
.
Additionally, this package also provides the "default" feature.

View File

@ -0,0 +1,16 @@
Copyright (C) 2020-2021 Proxmox Server Solutions GmbH
This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,8 @@
overlay = "."
crate_src_path = ".."
maintainer = "Proxmox Support Team <support@proxmox.com>"
[source]
# TODO: update once public
vcs_git = ""
vcs_browser = ""

View File

@ -0,0 +1 @@
3.0 (native)

425
proxmox-acme/src/account.rs Normal file
View File

@ -0,0 +1,425 @@
//! ACME Account management and creation. The [`Account`] type also contains most of the ACME API
//! entry point helpers.
use std::collections::HashMap;
use std::convert::TryFrom;
use openssl::pkey::{PKey, Private};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::authorization::{Authorization, GetAuthorization};
use crate::b64u;
use crate::directory::Directory;
use crate::jws::Jws;
use crate::key::{Jwk, PublicKey};
use crate::order::{NewOrder, Order, OrderData};
use crate::request::Request;
use crate::types::{AccountData, AccountStatus, ExternalAccountBinding};
use crate::Error;
/// An ACME Account.
///
/// This contains the location URL, the account data and the private key for an account.
/// This can directly be serialized via serde to persist the account.
///
/// In order to register a new account with an ACME provider, see the [`Account::creator`] method.
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Account {
/// Account location URL.
pub location: String,
/// Acme account data.
pub data: AccountData,
/// base64url encoded PEM formatted private key.
pub private_key: String,
}
impl Account {
/// Rebuild an account from its components.
pub fn from_parts(location: String, private_key: String, data: AccountData) -> Self {
Self {
location,
data,
private_key,
}
}
/// Builds an [`AccountCreator`]. This handles creation of the private key and account data as
/// well as handling the response sent by the server for the registration request.
pub fn creator() -> AccountCreator {
AccountCreator::default()
}
/// Place a new order. This will build a [`NewOrder`] representing an in flight order creation
/// request.
///
/// The returned `NewOrder`'s `request` option is *guaranteed* to be `Some(Request)`.
pub fn new_order(
&self,
order: &OrderData,
directory: &Directory,
nonce: &str,
) -> Result<NewOrder, Error> {
let key = PKey::private_key_from_pem(self.private_key.as_bytes())?;
if order.identifiers.is_empty() {
return Err(Error::EmptyOrder);
}
let url = directory.new_order_url();
let body = serde_json::to_string(&Jws::new(
&key,
Some(self.location.clone()),
url.to_owned(),
nonce.to_owned(),
order,
)?)?;
let request = Request {
url: url.to_owned(),
method: "POST",
content_type: crate::request::JSON_CONTENT_TYPE,
body,
expected: crate::request::CREATED,
};
Ok(NewOrder::new(request))
}
/// Prepare a "POST-as-GET" request to fetch data. Low level helper.
pub fn get_request(&self, url: &str, nonce: &str) -> Result<Request, Error> {
let key = PKey::private_key_from_pem(self.private_key.as_bytes())?;
let body = serde_json::to_string(&Jws::new_full(
&key,
Some(self.location.clone()),
url.to_owned(),
nonce.to_owned(),
String::new(),
)?)?;
Ok(Request {
url: url.to_owned(),
method: "POST",
content_type: crate::request::JSON_CONTENT_TYPE,
body,
expected: 200,
})
}
/// Prepare a JSON POST request. Low level helper.
pub fn post_request<T: Serialize>(
&self,
url: &str,
nonce: &str,
data: &T,
) -> Result<Request, Error> {
let key = PKey::private_key_from_pem(self.private_key.as_bytes())?;
let body = serde_json::to_string(&Jws::new(
&key,
Some(self.location.clone()),
url.to_owned(),
nonce.to_owned(),
data,
)?)?;
Ok(Request {
url: url.to_owned(),
method: "POST",
content_type: crate::request::JSON_CONTENT_TYPE,
body,
expected: 200,
})
}
/// Prepare a JSON POST request.
fn post_request_raw_payload(
&self,
url: &str,
nonce: &str,
payload: String,
) -> Result<Request, Error> {
let key = PKey::private_key_from_pem(self.private_key.as_bytes())?;
let body = serde_json::to_string(&Jws::new_full(
&key,
Some(self.location.clone()),
url.to_owned(),
nonce.to_owned(),
payload,
)?)?;
Ok(Request {
url: url.to_owned(),
method: "POST",
content_type: crate::request::JSON_CONTENT_TYPE,
body,
expected: 200,
})
}
/// Get the "key authorization" for a token.
pub fn key_authorization(&self, token: &str) -> Result<String, Error> {
let key = PKey::private_key_from_pem(self.private_key.as_bytes())?;
let thumbprint = PublicKey::try_from(&*key)?.thumbprint()?;
Ok(format!("{}.{}", token, thumbprint))
}
/// Get the TXT field value for a dns-01 token. This is the base64url encoded sha256 digest of
/// the key authorization value.
pub fn dns_01_txt_value(&self, token: &str) -> Result<String, Error> {
let key_authorization = self.key_authorization(token)?;
let digest = openssl::sha::sha256(key_authorization.as_bytes());
Ok(b64u::encode(&digest))
}
/// Prepare a request to update account data.
///
/// This is a rather low level interface. You should know what you're doing.
pub fn update_account_request<T: Serialize>(
&self,
nonce: &str,
data: &T,
) -> Result<Request, Error> {
self.post_request(&self.location, nonce, data)
}
/// Prepare a request to deactivate this account.
pub fn deactivate_account_request<T: Serialize>(&self, nonce: &str) -> Result<Request, Error> {
self.post_request_raw_payload(
&self.location,
nonce,
r#"{"status":"deactivated"}"#.to_string(),
)
}
/// Prepare a request to query an Authorization for an Order.
///
/// Returns `Ok(None)` if `auth_index` is out of out of range. You can query the number of
/// authorizations from via [`Order::authorization_len`] or by manually inspecting its
/// `.data.authorization` vector.
pub fn get_authorization(
&self,
order: &Order,
auth_index: usize,
nonce: &str,
) -> Result<Option<GetAuthorization>, Error> {
match order.authorization(auth_index) {
None => Ok(None),
Some(url) => Ok(Some(GetAuthorization::new(self.get_request(url, nonce)?))),
}
}
/// Prepare a request to validate a Challenge from an Authorization.
///
/// Returns `Ok(None)` if `challenge_index` is out of out of range. The challenge count is
/// available by inspecting the [`Authorization::challenges`] vector.
///
/// This returns a raw `Request` since validation takes some time and the `Authorization`
/// object has to be re-queried and its `status` inspected.
pub fn validate_challenge(
&self,
authorization: &Authorization,
challenge_index: usize,
nonce: &str,
) -> Result<Option<Request>, Error> {
match authorization.challenges.get(challenge_index) {
None => Ok(None),
Some(challenge) => self
.post_request_raw_payload(&challenge.url, nonce, "{}".to_string())
.map(Some),
}
}
/// Prepare a request to revoke a certificate.
///
/// The certificate can be either PEM or DER formatted.
///
/// Note that this uses the account's key for authorization.
///
/// Revocation using a certificate's private key is not yet implemented.
pub fn revoke_certificate(
&self,
certificate: &[u8],
reason: Option<u32>,
) -> Result<CertificateRevocation, Error> {
let cert = if certificate.starts_with(b"-----BEGIN CERTIFICATE-----") {
b64u::encode(&openssl::x509::X509::from_pem(certificate)?.to_der()?)
} else {
b64u::encode(certificate)
};
let data = match reason {
Some(reason) => serde_json::json!({ "certificate": cert, "reason": reason }),
None => serde_json::json!({ "certificate": cert }),
};
Ok(CertificateRevocation {
account: self,
data,
})
}
}
/// Certificate revocation involves converting the certificate to base64url encoded DER and then
/// embedding it in a json structure. Since we also need a nonce and possibly retry the request if
/// a `BadNonce` error happens, this caches the converted data for efficiency.
pub struct CertificateRevocation<'a> {
account: &'a Account,
data: Value,
}
impl CertificateRevocation<'_> {
/// Create the revocation request using the specified nonce for the given directory.
pub fn request(&self, directory: &Directory, nonce: &str) -> Result<Request, Error> {
self.account
.post_request(&directory.data.revoke_cert, nonce, &self.data)
}
}
/// Helper to create an account.
///
/// This is used to generate a private key and set the contact info for the account. Afterwards the
/// creation request can be created via the [`request`](AccountCreator::request()) method, giving
/// it a nonce and a directory. This can be repeated, if necessary, like when the nonce fails.
///
/// When the server sends a successful response, it should be passed to the
/// [`response`](AccountCreator::response()) method to finish the creation of an [`Account`] which
/// can then be persisted.
#[derive(Default)]
#[must_use = "when creating an account you must pass the response to AccountCreator::response()!"]
pub struct AccountCreator {
contact: Vec<String>,
terms_of_service_agreed: bool,
key: Option<PKey<Private>>,
eab_credentials: Option<(String, PKey<Private>)>,
}
impl AccountCreator {
/// Replace the contact info with the provided ACME compatible data.
pub fn set_contacts(mut self, contact: Vec<String>) -> Self {
self.contact = contact;
self
}
/// Append a contact string.
pub fn contact(mut self, contact: String) -> Self {
self.contact.push(contact);
self
}
/// Append an email address to the contact list.
pub fn email(self, email: String) -> Self {
self.contact(format!("mailto:{}", email))
}
/// Change whether the account agrees to the terms of service. Use the directory's or client's
/// `terms_of_service_url()` method to present the user with the Terms of Service.
pub fn agree_to_tos(mut self, agree: bool) -> Self {
self.terms_of_service_agreed = agree;
self
}
/// Set the EAB credentials for the account registration
pub fn set_eab_credentials(mut self, kid: String, hmac_key: String) -> Result<Self, Error> {
let hmac_key = if hmac_key.contains('+') || hmac_key.contains('/') {
base64::decode(hmac_key)?
} else {
b64u::decode(&hmac_key)?
};
let hmac_key = PKey::hmac(&hmac_key)?;
self.eab_credentials = Some((kid, hmac_key));
Ok(self)
}
/// Generate a new RSA key of the specified key size.
pub fn generate_rsa_key(self, bits: u32) -> Result<Self, Error> {
let key = openssl::rsa::Rsa::generate(bits)?;
Ok(self.with_key(PKey::from_rsa(key)?))
}
/// Generate a new P-256 EC key.
pub fn generate_ec_key(self) -> Result<Self, Error> {
let key = openssl::ec::EcKey::generate(
openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1)?.as_ref(),
)?;
Ok(self.with_key(PKey::from_ec_key(key)?))
}
/// Use an existing key. Note that only RSA and EC keys using the `P-256` curve are currently
/// supported, however, this will not be checked at this point.
pub fn with_key(mut self, key: PKey<Private>) -> Self {
self.key = Some(key);
self
}
/// Prepare a HTTP request to create this account.
///
/// Changes to the user data made after this will have no effect on the account generated with
/// the resulting request.
/// Changing the private key between using the request and passing the response to
/// [`response`](AccountCreator::response()) will render the account unusable!
pub fn request(&self, directory: &Directory, nonce: &str) -> Result<Request, Error> {
let key = self.key.as_deref().ok_or(Error::MissingKey)?;
let url = directory.new_account_url();
let external_account_binding = self
.eab_credentials
.as_ref()
.map(|cred| {
ExternalAccountBinding::new(&cred.0, &cred.1, Jwk::try_from(key)?, url.to_string())
})
.transpose()?;
let data = AccountData {
orders: None,
status: AccountStatus::New,
contact: self.contact.clone(),
terms_of_service_agreed: if self.terms_of_service_agreed {
Some(true)
} else {
None
},
external_account_binding,
only_return_existing: false,
extra: HashMap::new(),
};
let body = serde_json::to_string(&Jws::new(
key,
None,
url.to_owned(),
nonce.to_owned(),
&data,
)?)?;
Ok(Request {
url: url.to_owned(),
method: "POST",
content_type: crate::request::JSON_CONTENT_TYPE,
body,
expected: crate::request::CREATED,
})
}
/// After issuing the request from [`request()`](AccountCreator::request()), the response's
/// `Location` header and body must be passed to this for verification and to create an account
/// which is to be persisted!
pub fn response(self, location_header: String, response_body: &[u8]) -> Result<Account, Error> {
let private_key = self
.key
.ok_or(Error::MissingKey)?
.private_key_to_pem_pkcs8()?;
let private_key = String::from_utf8(private_key).map_err(|_| {
Error::Custom("PEM key contained illegal non-utf-8 characters".to_string())
})?;
Ok(Account {
location: location_header,
data: serde_json::from_slice(response_body)
.map_err(|err| Error::BadAccountData(err.to_string()))?,
private_key,
})
}
}

View File

@ -0,0 +1,587 @@
//! Async HTTP Client implementation for the ACME protocol.
use anyhow::format_err;
use bytes::Bytes;
use hyper::{Body, Request};
use serde::{Deserialize, Serialize};
use proxmox_http::client::Client;
use crate::account::AccountCreator;
use crate::order::{Order, OrderData};
use crate::Request as AcmeRequest;
use crate::{Account, Authorization, Challenge, Directory, Error, ErrorResponse};
/// A non-blocking Acme client using tokio/hyper.
pub struct AcmeClient {
directory_url: String,
account: Option<Account>,
directory: Option<Directory>,
nonce: Option<String>,
http_client: Client,
}
impl AcmeClient {
/// Create a new ACME client for a given ACME directory URL.
pub fn new(directory_url: String) -> Self {
const USER_AGENT_STRING: &str = "proxmox-acme-client/1.0";
const TCP_KEEPALIVE_TIME: u32 = 120;
let options = proxmox_http::HttpOptions {
proxy_config: None, // fixme???
user_agent: Some(USER_AGENT_STRING.to_string()),
tcp_keepalive: Some(TCP_KEEPALIVE_TIME),
};
let http_client = Client::with_options(options);
Self {
directory_url,
account: None,
directory: None,
nonce: None,
http_client,
}
}
/// Get the current account, if there is one.
pub fn account(&self) -> Option<&Account> {
self.account.as_ref()
}
/// Set the account this client should use.
pub fn set_account(&mut self, account: Account) {
self.account = Some(account);
}
/// Convenience method to create a new account with a list of ACME compatible contact strings
/// (eg. `mailto:someone@example.com`).
///
/// Please remember to persist the returned `Account` structure somewhere to not lose access to
/// the account!
///
/// If an RSA key size is provided, an RSA key will be generated. Otherwise an EC key using the
/// P-256 curve will be generated.
pub async fn new_account(
&mut self,
tos_agreed: bool,
contact: Vec<String>,
rsa_bits: Option<u32>,
eab_creds: Option<(String, String)>,
) -> Result<&Account, anyhow::Error> {
let mut account = Account::creator()
.set_contacts(contact)
.agree_to_tos(tos_agreed);
if let Some((eab_kid, eab_hmac_key)) = eab_creds {
account = account.set_eab_credentials(eab_kid, eab_hmac_key)?;
}
let account = if let Some(bits) = rsa_bits {
account.generate_rsa_key(bits)?
} else {
account.generate_ec_key()?
};
let _ = self.register_account(account).await?;
// unwrap: Setting `self.account` is literally this function's job, we just can't keep
// the borrow from from `self.register_account()` active due to clashes.
Ok(self.account.as_ref().unwrap())
}
/// Shortcut to `account().ok_or_else(...).key_authorization()`.
pub fn key_authorization(&self, token: &str) -> Result<String, anyhow::Error> {
Ok(Self::need_account(&self.account)?.key_authorization(token)?)
}
/// Shortcut to `account().ok_or_else(...).dns_01_txt_value()`.
/// the key authorization value.
pub fn dns_01_txt_value(&self, token: &str) -> Result<String, anyhow::Error> {
Ok(Self::need_account(&self.account)?.dns_01_txt_value(token)?)
}
async fn register_account(
&mut self,
account: AccountCreator,
) -> Result<&Account, anyhow::Error> {
let mut retry = retry();
let mut response = loop {
retry.tick()?;
let (directory, nonce) = Self::get_dir_nonce(
&mut self.http_client,
&self.directory_url,
&mut self.directory,
&mut self.nonce,
)
.await?;
let request = account.request(directory, nonce)?;
match self.run_request(request).await {
Ok(response) => break response,
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err.into()),
}
};
let account = account.response(response.location_required()?, &response.body)?;
self.account = Some(account);
Ok(self.account.as_ref().unwrap())
}
/// Update account data.
///
/// Low-level version: we allow arbitrary data to be passed to the remote here, it's up to the
/// user to know what to do for now.
pub async fn update_account<T: Serialize>(
&mut self,
data: &T,
) -> Result<&Account, anyhow::Error> {
let account = Self::need_account(&self.account)?;
let mut retry = retry();
let response = loop {
retry.tick()?;
let (_directory, nonce) = Self::get_dir_nonce(
&mut self.http_client,
&self.directory_url,
&mut self.directory,
&mut self.nonce,
)
.await?;
let request = account.post_request(&account.location, nonce, data)?;
match Self::execute(&mut self.http_client, request, &mut self.nonce).await {
Ok(response) => break response,
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err.into()),
}
};
// unwrap: we've been keeping an immutable reference to it from the top of the method
let _ = account;
self.account.as_mut().unwrap().data = response.json()?;
// fixme: self.save()?;
Ok(self.account.as_ref().unwrap())
}
/// Method to create a new order for a set of domains.
///
/// Please remember to persist the order somewhere (ideally along with the account data) in
/// order to finish & query it later on.
pub async fn new_order<I>(&mut self, domains: I) -> Result<Order, anyhow::Error>
where
I: IntoIterator<Item = String>,
{
let account = Self::need_account(&self.account)?;
let order = domains
.into_iter()
.fold(OrderData::new(), |order, domain| order.domain(domain));
let mut retry = retry();
loop {
retry.tick()?;
let (directory, nonce) = Self::get_dir_nonce(
&mut self.http_client,
&self.directory_url,
&mut self.directory,
&mut self.nonce,
)
.await?;
let mut new_order = account.new_order(&order, directory, nonce)?;
let mut response = match Self::execute(
&mut self.http_client,
new_order.request.take().unwrap(),
&mut self.nonce,
)
.await
{
Ok(response) => response,
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err.into()),
};
return Ok(
new_order.response(response.location_required()?, response.bytes().as_ref())?
);
}
}
/// Low level "POST-as-GET" request.
async fn post_as_get(&mut self, url: &str) -> Result<AcmeResponse, anyhow::Error> {
let account = Self::need_account(&self.account)?;
let mut retry = retry();
loop {
retry.tick()?;
let (_directory, nonce) = Self::get_dir_nonce(
&mut self.http_client,
&self.directory_url,
&mut self.directory,
&mut self.nonce,
)
.await?;
let request = account.get_request(url, nonce)?;
match Self::execute(&mut self.http_client, request, &mut self.nonce).await {
Ok(response) => return Ok(response),
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err.into()),
}
}
}
/// Low level POST request.
async fn post<T: Serialize>(
&mut self,
url: &str,
data: &T,
) -> Result<AcmeResponse, anyhow::Error> {
let account = Self::need_account(&self.account)?;
let mut retry = retry();
loop {
retry.tick()?;
let (_directory, nonce) = Self::get_dir_nonce(
&mut self.http_client,
&self.directory_url,
&mut self.directory,
&mut self.nonce,
)
.await?;
let request = account.post_request(url, nonce, data)?;
match Self::execute(&mut self.http_client, request, &mut self.nonce).await {
Ok(response) => return Ok(response),
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err.into()),
}
}
}
/// Request challenge validation. Afterwards, the challenge should be polled.
pub async fn request_challenge_validation(
&mut self,
url: &str,
) -> Result<Challenge, anyhow::Error> {
Ok(self
.post(url, &serde_json::Value::Object(Default::default()))
.await?
.json()?)
}
/// Assuming the provided URL is an 'Authorization' URL, get and deserialize it.
pub async fn get_authorization(&mut self, url: &str) -> Result<Authorization, anyhow::Error> {
Ok(self.post_as_get(url).await?.json()?)
}
/// Assuming the provided URL is an 'Order' URL, get and deserialize it.
pub async fn get_order(&mut self, url: &str) -> Result<OrderData, anyhow::Error> {
Ok(self.post_as_get(url).await?.json()?)
}
/// Finalize an Order via its `finalize` URL property and the DER encoded CSR.
pub async fn finalize(&mut self, url: &str, csr: &[u8]) -> Result<(), anyhow::Error> {
let csr = base64::encode_config(csr, base64::URL_SAFE_NO_PAD);
let data = serde_json::json!({ "csr": csr });
self.post(url, &data).await?;
Ok(())
}
/// Download a certificate via its 'certificate' URL property.
///
/// The certificate will be a PEM certificate chain.
pub async fn get_certificate(&mut self, url: &str) -> Result<Bytes, anyhow::Error> {
Ok(self.post_as_get(url).await?.body)
}
/// Revoke an existing certificate (PEM or DER formatted).
pub async fn revoke_certificate(
&mut self,
certificate: &[u8],
reason: Option<u32>,
) -> Result<(), anyhow::Error> {
// TODO: This can also work without an account.
let account = Self::need_account(&self.account)?;
let revocation = account.revoke_certificate(certificate, reason)?;
let mut retry = retry();
loop {
retry.tick()?;
let (directory, nonce) = Self::get_dir_nonce(
&mut self.http_client,
&self.directory_url,
&mut self.directory,
&mut self.nonce,
)
.await?;
let request = revocation.request(directory, nonce)?;
match Self::execute(&mut self.http_client, request, &mut self.nonce).await {
Ok(_response) => return Ok(()),
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err.into()),
}
}
}
fn need_account(account: &Option<Account>) -> Result<&Account, anyhow::Error> {
account
.as_ref()
.ok_or_else(|| format_err!("cannot use client without an account"))
}
/// Get the directory URL without querying the `Directory` structure.
///
/// The difference to [`directory`](AcmeClient::directory()) is that this does not
/// attempt to fetch the directory data from the ACME server.
pub fn directory_url(&self) -> &str {
&self.directory_url
}
}
struct AcmeResponse {
body: Bytes,
location: Option<String>,
got_nonce: bool,
}
impl AcmeResponse {
/// Convenience helper to assert that a location header was part of the response.
fn location_required(&mut self) -> Result<String, anyhow::Error> {
self.location
.take()
.ok_or_else(|| format_err!("missing Location header"))
}
/// Convenience shortcut to perform json deserialization of the returned body.
fn json<T: for<'a> Deserialize<'a>>(&self) -> Result<T, Error> {
Ok(serde_json::from_slice(&self.body)?)
}
/// Convenience shortcut to get the body as bytes.
fn bytes(&self) -> &[u8] {
&self.body
}
}
impl AcmeClient {
/// Non-self-borrowing run_request version for borrow workarounds.
async fn execute(
http_client: &mut Client,
request: AcmeRequest,
nonce: &mut Option<String>,
) -> Result<AcmeResponse, Error> {
let req_builder = Request::builder().method(request.method).uri(&request.url);
let http_request = if !request.content_type.is_empty() {
req_builder
.header("Content-Type", request.content_type)
.header("Content-Length", request.body.len())
.body(request.body.into())
} else {
req_builder.body(Body::empty())
}
.map_err(|err| Error::Custom(format!("failed to create http request: {}", err)))?;
let response = http_client
.request(http_request)
.await
.map_err(|err| Error::Custom(err.to_string()))?;
let (parts, body) = response.into_parts();
let status = parts.status.as_u16();
let body = hyper::body::to_bytes(body)
.await
.map_err(|err| Error::Custom(format!("failed to retrieve response body: {}", err)))?;
let got_nonce = if let Some(new_nonce) = parts.headers.get(crate::REPLAY_NONCE) {
let new_nonce = new_nonce.to_str().map_err(|err| {
Error::Client(format!(
"received invalid replay-nonce header from ACME server: {}",
err
))
})?;
*nonce = Some(new_nonce.to_owned());
true
} else {
false
};
if parts.status.is_success() {
if status != request.expected {
return Err(Error::InvalidApi(format!(
"ACME server responded with unexpected status code: {:?}",
parts.status
)));
}
let location = parts
.headers
.get("Location")
.map(|header| {
header.to_str().map(str::to_owned).map_err(|err| {
Error::Client(format!(
"received invalid location header from ACME server: {}",
err
))
})
})
.transpose()?;
return Ok(AcmeResponse {
body,
location,
got_nonce,
});
}
let error: ErrorResponse = serde_json::from_slice(&body).map_err(|err| {
Error::Client(format!(
"error status with improper error ACME response: {}",
err
))
})?;
if error.ty == crate::error::BAD_NONCE {
if !got_nonce {
return Err(Error::InvalidApi(
"badNonce without a new Replay-Nonce header".to_string(),
));
}
return Err(Error::BadNonce);
}
Err(Error::Api(error))
}
/// Low-level API to run an n API request. This automatically updates the current nonce!
async fn run_request(&mut self, request: AcmeRequest) -> Result<AcmeResponse, Error> {
Self::execute(&mut self.http_client, request, &mut self.nonce).await
}
/// Get the Directory information.
pub async fn directory(&mut self) -> Result<&Directory, Error> {
Ok(Self::get_directory(
&mut self.http_client,
&self.directory_url,
&mut self.directory,
&mut self.nonce,
)
.await?
.0)
}
async fn get_directory<'a, 'b>(
http_client: &mut Client,
directory_url: &str,
directory: &'a mut Option<Directory>,
nonce: &'b mut Option<String>,
) -> Result<(&'a Directory, Option<&'b str>), Error> {
if let Some(d) = directory {
return Ok((d, nonce.as_deref()));
}
let response = Self::execute(
http_client,
AcmeRequest {
url: directory_url.to_string(),
method: "GET",
content_type: "",
body: String::new(),
expected: 200,
},
nonce,
)
.await?;
*directory = Some(Directory::from_parts(
directory_url.to_string(),
response.json()?,
));
Ok((directory.as_ref().unwrap(), nonce.as_deref()))
}
/// Like `get_directory`, but if the directory provides no nonce, also performs a `HEAD`
/// request on the new nonce URL.
async fn get_dir_nonce<'a, 'b>(
http_client: &mut Client,
directory_url: &str,
directory: &'a mut Option<Directory>,
nonce: &'b mut Option<String>,
) -> Result<(&'a Directory, &'b str), Error> {
// this let construct is a lifetime workaround:
let _ = Self::get_directory(http_client, directory_url, directory, nonce).await?;
let dir = directory.as_ref().unwrap(); // the above fails if it couldn't fill this option
if nonce.is_none() {
// this is also a lifetime issue...
let _ = Self::get_nonce(http_client, nonce, dir.new_nonce_url()).await?;
};
Ok((dir, nonce.as_deref().unwrap()))
}
/// Convenience method to get the ToS URL from the contained `Directory`.
///
/// This requires mutable self as the directory information may be lazily loaded, which can
/// fail.
pub async fn terms_of_service_url(&mut self) -> Result<Option<&str>, Error> {
Ok(self.directory().await?.terms_of_service_url())
}
async fn get_nonce<'a>(
http_client: &mut Client,
nonce: &'a mut Option<String>,
new_nonce_url: &str,
) -> Result<&'a str, Error> {
let response = Self::execute(
http_client,
AcmeRequest {
url: new_nonce_url.to_owned(),
method: "HEAD",
content_type: "",
body: String::new(),
expected: 200,
},
nonce,
)
.await?;
if !response.got_nonce {
return Err(Error::InvalidApi(
"no new nonce received from new nonce URL".to_string(),
));
}
nonce
.as_deref()
.ok_or_else(|| Error::Client("failed to update nonce".to_string()))
}
}
/// bad nonce retry count helper
struct Retry(usize);
const fn retry() -> Retry {
Retry(0)
}
impl Retry {
fn tick(&mut self) -> Result<(), Error> {
if self.0 >= 3 {
Err(Error::Client("kept getting a badNonce error!".to_string()))
} else {
self.0 += 1;
Ok(())
}
}
}

View File

@ -0,0 +1,162 @@
//! Authorization and Challenge data.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::order::Identifier;
use crate::request::Request;
use crate::Error;
/// Status of an [`Authorization`].
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Status {
/// The authorization was deactivated by the client.
Deactivated,
/// The authorization expired.
Expired,
/// The authorization failed and is now invalid.
Invalid,
/// Validation is pending.
Pending,
/// The authorization was revoked by the server.
Revoked,
/// The identifier is authorized.
Valid,
}
impl Status {
/// Convenience method to check if the status is 'pending'.
#[inline]
pub fn is_pending(self) -> bool {
self == Status::Pending
}
/// Convenience method to check if the status is 'valid'.
#[inline]
pub fn is_valid(self) -> bool {
self == Status::Valid
}
}
/// Represents an authorization state for an order. The user is expected to pick a challenge,
/// execute it, and the request validation for it.
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Authorization {
/// The identifier (usually domain name) this authorization is for.
pub identifier: Identifier,
/// The current status of this authorization entry.
pub status: Status,
/// Expiration date for the authorization.
#[serde(skip_serializing_if = "Option::is_none")]
pub expires: Option<String>,
/// List of challenges which can be used to complete this authorization.
pub challenges: Vec<Challenge>,
/// The authorization is for a wildcard domain.
#[serde(default, skip_serializing_if = "is_false")]
pub wildcard: bool,
}
/// The state of a challenge.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ChallengeStatus {
/// The challenge is pending and has not been validated yet.
Pending,
/// The validation is in progress.
Processing,
/// The challenge was successfully validated.
Valid,
/// Validation of this challenge failed.
Invalid,
}
impl ChallengeStatus {
/// Convenience method to check if the status is 'pending'.
#[inline]
pub fn is_pending(self) -> bool {
self == ChallengeStatus::Pending
}
/// Convenience method to check if the status is 'valid'.
#[inline]
pub fn is_valid(self) -> bool {
self == ChallengeStatus::Valid
}
}
/// A challenge object contains information on how to complete an authorization for an order.
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Challenge {
/// The challenge type (such as `"dns-01"`).
#[serde(rename = "type")]
pub ty: String,
/// The current challenge status.
pub status: ChallengeStatus,
/// The URL used to post to in order to begin the validation for this challenge.
pub url: String,
/// Contains the remaining fields of the Challenge object, such as the `token`.
#[serde(flatten)]
pub data: HashMap<String, Value>,
}
impl Challenge {
/// Most challenges have a `token` used for key authorizations. This is a convenience helper to
/// access it.
pub fn token(&self) -> Option<&str> {
self.data.get("token").and_then(Value::as_str)
}
}
/// Serde helper
#[inline]
fn is_false(b: &bool) -> bool {
!*b
}
/// Represents an in-flight query for an authorization.
///
/// This is created via [`Account::get_authorization`](crate::Account::get_authorization()).
pub struct GetAuthorization {
//order: OrderData,
/// The request to send to the ACME provider. This is wrapped in an option in order to allow
/// moving it out instead of copying the contents.
///
/// When generated via [`Account::get_authorization`](crate::Account::get_authorization()),
/// this is guaranteed to be `Some`.
///
/// The response should be passed to the the [`response`](GetAuthorization::response()) method.
pub request: Option<Request>,
}
impl GetAuthorization {
pub(crate) fn new(request: Request) -> Self {
Self {
request: Some(request),
}
}
/// Deal with the response we got from the server.
pub fn response(self, response_body: &[u8]) -> Result<Authorization, Error> {
Ok(serde_json::from_slice(response_body)?)
}
}

43
proxmox-acme/src/b64u.rs Normal file
View File

@ -0,0 +1,43 @@
fn config() -> base64::Config {
base64::Config::new(base64::CharacterSet::UrlSafe, false)
}
/// Encode bytes as base64url into a `String`.
pub fn encode(data: &[u8]) -> String {
base64::encode_config(data, config())
}
/// Decode a base64url encoded string.
pub fn decode<T: AsRef<[u8]>>(data: &T) -> Result<Vec<u8>, crate::Error> {
Ok(base64::decode_config(data.as_ref(), config())?)
}
// curiously currently unused as we don't deserialize any of that
// /// Decode bytes from a base64url string.
// pub fn decode(data: &str) -> Result<Vec<u8>, base64::DecodeError> {
// base64::decode_config(data, config())
// }
/// Our serde module for encoding bytes as base64url encoded strings.
pub mod bytes {
use serde::{Serialize, Serializer};
//use serde::{Deserialize, Deserializer};
pub fn serialize<S>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
super::encode(data).serialize(serializer)
}
// curiously currently unused as we don't deserialize any of that
// pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
// where
// D: Deserializer<'de>,
// {
// use serde::de::Error;
// Ok(super::decode(&String::deserialize(deserializer)?)
// .map_err(|e| D::Error::custom(e.to_string()))?)
// }
}

614
proxmox-acme/src/client.rs Normal file
View File

@ -0,0 +1,614 @@
//! A blocking higher-level ACME client implementation using 'curl'.
use std::io::Read;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::b64u;
use crate::error;
use crate::order::OrderData;
use crate::request::ErrorResponse;
use crate::{Account, Authorization, Challenge, Directory, Error, Order, Request};
macro_rules! format_err {
($($fmt:tt)*) => { Error::Client(format!($($fmt)*)) };
}
macro_rules! bail {
($($fmt:tt)*) => {{ return Err(format_err!($($fmt)*)); }}
}
/// Low level HTTP response structure.
pub struct HttpResponse {
/// The raw HTTP response body as a byte vector.
pub body: Vec<u8>,
/// The http status code.
pub status: u16,
/// The headers relevant to the ACME protocol.
pub headers: Headers,
}
impl HttpResponse {
/// Check the HTTP status code for a success code (200..299).
pub fn is_success(&self) -> bool {
self.status >= 200 && self.status < 300
}
/// Convenience shortcut to perform json deserialization of the returned body.
pub fn json<T: for<'a> Deserialize<'a>>(&self) -> Result<T, Error> {
Ok(serde_json::from_slice(&self.body)?)
}
/// Access the raw body as bytes.
pub fn bytes(&self) -> &[u8] {
&self.body
}
/// Get the returned location header. Borrowing shortcut to `self.headers.location`.
pub fn location(&self) -> Option<&str> {
self.headers.location.as_deref()
}
/// Convenience helper to assert that a location header was part of the response.
pub fn location_required(&mut self) -> Result<String, Error> {
self.headers
.location
.take()
.ok_or_else(|| format_err!("missing Location header"))
}
}
/// Contains headers from the HTTP response which are relevant parts of the Acme API.
///
/// Note that access to the `nonce` header is internal to this crate only, since a nonce will
/// always be moved out of the response into the `Client` whenever a new nonce is received.
#[derive(Default)]
pub struct Headers {
/// The 'Location' header usually encodes the URL where an account or order can be queried from
/// after they were created.
pub location: Option<String>,
nonce: Option<String>,
}
struct Inner {
agent: Option<ureq::Agent>,
nonce: Option<String>,
proxy: Option<String>,
}
impl Inner {
fn agent(&mut self) -> Result<&mut ureq::Agent, Error> {
if self.agent.is_none() {
let connector = Arc::new(
native_tls::TlsConnector::new()
.map_err(|err| format_err!("failed to create tls connector: {}", err))?,
);
let mut builder = ureq::AgentBuilder::new().tls_connector(connector);
if let Some(proxy) = self.proxy.as_deref() {
builder = builder.proxy(
ureq::Proxy::new(proxy)
.map_err(|err| format_err!("failed to set proxy: {}", err))?,
);
}
self.agent = Some(builder.build());
}
Ok(self.agent.as_mut().unwrap())
}
fn new() -> Self {
Self {
agent: None,
nonce: None,
proxy: None,
}
}
fn execute(
&mut self,
method: &[u8],
url: &str,
request_body: Option<(&str, &[u8])>, // content-type and body
) -> Result<HttpResponse, Error> {
let agent = self.agent()?;
let req = match method {
b"POST" => agent.post(url),
b"GET" => agent.get(url),
b"HEAD" => agent.head(url),
other => bail!("invalid http method: {:?}", other),
};
let response = if let Some((content_type, body)) = request_body {
req.set("Content-Type", content_type)
.set("Content-Length", &body.len().to_string())
.send_bytes(body)
} else {
req.call()
}
.map_err(|err| format_err!("http request failed: {}", err))?;
let mut headers = Headers::default();
if let Some(value) = response.header(crate::LOCATION) {
headers.location = Some(value.to_owned());
}
if let Some(value) = response.header(crate::REPLAY_NONCE) {
headers.nonce = Some(value.to_owned());
}
let status = response.status();
let mut body = Vec::new();
response
.into_reader()
.take(16 * 1024 * 1024) // arbitrary limit
.read_to_end(&mut body)
.map_err(|err| format_err!("failed to read response body: {}", err))?;
Ok(HttpResponse {
status,
headers,
body,
})
}
pub fn set_proxy(&mut self, proxy: String) {
self.proxy = Some(proxy);
self.agent = None;
}
/// Low-level API to run an API request. This automatically updates the current nonce!
fn run_request(&mut self, request: Request) -> Result<HttpResponse, Error> {
let body = if request.body.is_empty() {
None
} else {
Some((request.content_type, request.body.as_bytes()))
};
let mut response = self
.execute(request.method.as_bytes(), &request.url, body)
.map_err({
// borrow fixup:
let method = &request.method;
let url = &request.url;
move |err| format_err!("failed to execute {} request to {}: {}", method, url, err)
})?;
let got_nonce = self.update_nonce(&mut response)?;
if response.is_success() {
if response.status != request.expected {
return Err(Error::InvalidApi(format!(
"API server responded with unexpected status code: {:?}",
response.status
)));
}
return Ok(response);
}
let error: ErrorResponse = response.json().map_err(|err| {
format_err!("error status with improper error ACME response: {}", err)
})?;
if error.ty == error::BAD_NONCE {
if !got_nonce {
return Err(Error::InvalidApi(
"badNonce without a new Replay-Nonce header".to_string(),
));
}
return Err(Error::BadNonce);
}
Err(Error::Api(error))
}
/// If the response contained a nonce, update our nonce and return `true`, otherwise return
/// `false`.
fn update_nonce(&mut self, response: &mut HttpResponse) -> Result<bool, Error> {
match response.headers.nonce.take() {
Some(nonce) => {
self.nonce = Some(nonce);
Ok(true)
}
None => Ok(false),
}
}
/// Update the nonce, if there isn't one it is an error.
fn must_update_nonce(&mut self, response: &mut HttpResponse) -> Result<(), Error> {
if !self.update_nonce(response)? {
bail!("newNonce URL did not return a nonce");
}
Ok(())
}
/// Update the Nonce.
fn new_nonce(&mut self, new_nonce_url: &str) -> Result<(), Error> {
let mut response = self.execute(b"HEAD", new_nonce_url, None).map_err(|err| {
Error::InvalidApi(format!("failed to get HEAD of newNonce URL: {}", err))
})?;
if !response.is_success() {
bail!("HEAD on newNonce URL returned error");
}
self.must_update_nonce(&mut response)?;
Ok(())
}
/// Make sure a nonce is available without forcing renewal.
fn nonce(&mut self, new_nonce_url: &str) -> Result<&str, Error> {
if self.nonce.is_none() {
self.new_nonce(new_nonce_url)?;
}
self.nonce
.as_deref()
.ok_or_else(|| format_err!("failed to get nonce"))
}
}
/// A blocking Acme client using curl's `Easy` interface.
pub struct Client {
inner: Inner,
directory: Option<Directory>,
account: Option<Account>,
directory_url: String,
}
impl Client {
/// Create a new Client. This has no account associated with it yet, so the next step is to
/// either attach an existing `Account` or create a new one.
pub fn new(directory_url: String) -> Self {
Self {
inner: Inner::new(),
directory: None,
account: None,
directory_url,
}
}
/// Get the directory URL without querying the `Directory` structure.
///
/// The difference to [`directory`](Client::directory()) is that this does not
/// attempt to fetch the directory data from the ACME server.
pub fn directory_url(&self) -> &str {
&self.directory_url
}
/// Set the account this client should use.
pub fn set_account(&mut self, account: Account) {
self.account = Some(account);
}
/// Get the Directory information.
pub fn directory(&mut self) -> Result<&Directory, Error> {
Self::get_directory(&mut self.inner, &mut self.directory, &self.directory_url)
}
/// Get the Directory information.
fn get_directory<'a>(
inner: &'_ mut Inner,
directory: &'a mut Option<Directory>,
directory_url: &str,
) -> Result<&'a Directory, Error> {
if let Some(d) = directory {
return Ok(d);
}
let response = inner
.execute(b"GET", directory_url, None)
.map_err(|err| Error::InvalidApi(format!("failed to get directory info: {}", err)))?;
if !response.is_success() {
bail!(
"GET on the directory URL returned error status ({})",
response.status
);
}
*directory = Some(Directory::from_parts(
directory_url.to_string(),
response.json()?,
));
Ok(directory.as_ref().unwrap())
}
/// Get the current account, if there is one.
pub fn account(&self) -> Option<&Account> {
self.account.as_ref()
}
/// Convenience method to get the ToS URL from the contained `Directory`.
///
/// This requires mutable self as the directory information may be lazily loaded, which can
/// fail.
pub fn terms_of_service_url(&mut self) -> Result<Option<&str>, Error> {
Ok(self.directory()?.terms_of_service_url())
}
/// Get a fresh nonce (this should normally not be required as nonces are updated
/// automatically, even when a `badNonce` error occurs, which according to the ACME API
/// specification should include a new valid nonce in its headers anyway).
pub fn new_nonce(&mut self) -> Result<(), Error> {
let was_none = self.inner.nonce.is_none();
let directory =
Self::get_directory(&mut self.inner, &mut self.directory, &self.directory_url)?;
if was_none && self.inner.nonce.is_some() {
// this was the first call and we already got a nonce from querying the directory
return Ok(());
}
// otherwise actually call up to get a new nonce
self.inner.new_nonce(directory.new_nonce_url())
}
/// borrow helper
fn nonce<'a>(inner: &'a mut Inner, directory: &'_ Directory) -> Result<&'a str, Error> {
inner.nonce(directory.new_nonce_url())
}
/// Convenience method to create a new account with a list of ACME compatible contact strings
/// (eg. `mailto:someone@example.com`).
///
/// Please remember to persist the returned `Account` structure somewhere to not lose access to
/// the account!
///
/// If an RSA key size is provided, an RSA key will be generated. Otherwise an EC key using the
/// P-256 curve will be generated.
pub fn new_account(
&mut self,
contact: Vec<String>,
tos_agreed: bool,
rsa_bits: Option<u32>,
eab_creds: Option<(String, String)>,
) -> Result<&Account, Error> {
let mut account = Account::creator()
.set_contacts(contact)
.agree_to_tos(tos_agreed);
if let Some((eab_kid, eab_hmac_key)) = eab_creds {
account = account.set_eab_credentials(eab_kid, eab_hmac_key)?;
}
let account = if let Some(bits) = rsa_bits {
account.generate_rsa_key(bits)?
} else {
account.generate_ec_key()?
};
self.register_account(account)
}
/// Register an ACME account.
///
/// This uses an [`AccountCreator`](crate::account::AccountCreator) since it may need to build
/// the request multiple times in case the we get a `BadNonce` error.
pub fn register_account(
&mut self,
account: crate::account::AccountCreator,
) -> Result<&Account, Error> {
let mut retry = retry();
let mut response = loop {
retry.tick()?;
let directory =
Self::get_directory(&mut self.inner, &mut self.directory, &self.directory_url)?;
let nonce = Self::nonce(&mut self.inner, directory)?;
let request = account.request(directory, nonce)?;
match self.run_request(request) {
Ok(response) => break response,
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err),
}
};
let account = account.response(response.location_required()?, response.bytes().as_ref())?;
self.account = Some(account);
Ok(self.account.as_ref().unwrap())
}
fn need_account(account: &Option<Account>) -> Result<&Account, Error> {
account
.as_ref()
.ok_or_else(|| format_err!("cannot use client without an account"))
}
/// Update account data.
///
/// Low-level version: we allow arbitrary data to be passed to the remote here, it's up to the
/// user to know what to do for now.
pub fn update_account<T: Serialize>(&mut self, data: &T) -> Result<&Account, Error> {
let account = Self::need_account(&self.account)?;
let mut retry = retry();
let response = loop {
retry.tick()?;
let directory =
Self::get_directory(&mut self.inner, &mut self.directory, &self.directory_url)?;
let nonce = Self::nonce(&mut self.inner, directory)?;
let request = account.post_request(&account.location, nonce, data)?;
let response = match self.inner.run_request(request) {
Ok(response) => response,
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err),
};
break response;
};
// unwrap: we asserted we have an account at the top of the method!
let account = self.account.as_mut().unwrap();
account.data = response.json()?;
Ok(account)
}
/// Method to create a new order for a set of domains.
///
/// Please remember to persist the order somewhere (ideally along with the account data) in
/// order to finish & query it later on.
pub fn new_order(&mut self, domains: Vec<String>) -> Result<Order, Error> {
let account = Self::need_account(&self.account)?;
let order = domains
.into_iter()
.fold(OrderData::new(), |order, domain| order.domain(domain));
let mut retry = retry();
loop {
retry.tick()?;
let directory =
Self::get_directory(&mut self.inner, &mut self.directory, &self.directory_url)?;
let nonce = Self::nonce(&mut self.inner, directory)?;
let mut new_order = account.new_order(&order, directory, nonce)?;
let mut response = match self.inner.run_request(new_order.request.take().unwrap()) {
Ok(response) => response,
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err),
};
return new_order.response(response.location_required()?, response.bytes().as_ref());
}
}
/// Assuming the provided URL is an 'Authorization' URL, get and deserialize it.
pub fn get_authorization(&mut self, url: &str) -> Result<Authorization, Error> {
self.post_as_get(url)?.json()
}
/// Assuming the provided URL is an 'Order' URL, get and deserialize it.
pub fn get_order(&mut self, url: &str) -> Result<OrderData, Error> {
self.post_as_get(url)?.json()
}
/// Low level "POST-as-GET" request.
pub fn post_as_get(&mut self, url: &str) -> Result<HttpResponse, Error> {
let account = Self::need_account(&self.account)?;
let mut retry = retry();
loop {
retry.tick()?;
let directory =
Self::get_directory(&mut self.inner, &mut self.directory, &self.directory_url)?;
let nonce = Self::nonce(&mut self.inner, directory)?;
let request = account.get_request(url, nonce)?;
match self.inner.run_request(request) {
Ok(response) => return Ok(response),
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err),
}
}
}
/// Low level POST request.
pub fn post<T: Serialize>(&mut self, url: &str, data: &T) -> Result<HttpResponse, Error> {
let account = Self::need_account(&self.account)?;
let mut retry = retry();
loop {
retry.tick()?;
let directory =
Self::get_directory(&mut self.inner, &mut self.directory, &self.directory_url)?;
let nonce = Self::nonce(&mut self.inner, directory)?;
let request = account.post_request(url, nonce, data)?;
match self.inner.run_request(request) {
Ok(response) => return Ok(response),
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err),
}
}
}
/// Request challenge validation. Afterwards, the challenge should be polled.
pub fn request_challenge_validation(&mut self, url: &str) -> Result<Challenge, Error> {
self.post(url, &serde_json::json!({}))?.json()
}
/// Shortcut to `account().ok_or_else(...).key_authorization()`.
pub fn key_authorization(&self, token: &str) -> Result<String, Error> {
Self::need_account(&self.account)?.key_authorization(token)
}
/// Shortcut to `account().ok_or_else(...).dns_01_txt_value()`.
/// the key authorization value.
pub fn dns_01_txt_value(&self, token: &str) -> Result<String, Error> {
Self::need_account(&self.account)?.dns_01_txt_value(token)
}
/// Low-level API to run an n API request. This automatically updates the current nonce!
pub fn run_request(&mut self, request: Request) -> Result<HttpResponse, Error> {
self.inner.run_request(request)
}
/// Finalize an Order via its `finalize` URL property and the DER encoded CSR.
pub fn finalize(&mut self, url: &str, csr: &[u8]) -> Result<(), Error> {
let csr = b64u::encode(csr);
let data = serde_json::json!({ "csr": csr });
self.post(url, &data)?;
Ok(())
}
/// Download a certificate via its 'certificate' URL property.
///
/// The certificate will be a PEM certificate chain.
pub fn get_certificate(&mut self, url: &str) -> Result<Vec<u8>, Error> {
Ok(self.post_as_get(url)?.body)
}
/// Revoke an existing certificate (PEM or DER formatted).
pub fn revoke_certificate(
&mut self,
certificate: &[u8],
reason: Option<u32>,
) -> Result<(), Error> {
// TODO: This can also work without an account.
let account = Self::need_account(&self.account)?;
let revocation = account.revoke_certificate(certificate, reason)?;
let mut retry = retry();
loop {
retry.tick()?;
let directory =
Self::get_directory(&mut self.inner, &mut self.directory, &self.directory_url)?;
let nonce = Self::nonce(&mut self.inner, directory)?;
let request = revocation.request(directory, nonce)?;
match self.inner.run_request(request) {
Ok(_response) => return Ok(()),
Err(err) if err.is_bad_nonce() => continue,
Err(err) => return Err(err),
}
}
}
/// Set a proxy
pub fn set_proxy(&mut self, proxy: String) {
self.inner.set_proxy(proxy)
}
}
/// bad nonce retry count helper
struct Retry(usize);
const fn retry() -> Retry {
Retry(0)
}
impl Retry {
fn tick(&mut self) -> Result<(), Error> {
if self.0 >= 3 {
bail!("kept getting a badNonce error!");
}
self.0 += 1;
Ok(())
}
}

View File

@ -0,0 +1,107 @@
//! ACME Directory information.
use serde::{Deserialize, Serialize};
/// An ACME Directory. This contains the base URL and the directory data as received via a `GET`
/// request to the URL.
pub struct Directory {
/// The main entry point URL to the ACME directory.
pub url: String,
/// The json structure received via a `GET` request to the directory URL. This contains the
/// URLs for various API entry points.
pub data: DirectoryData,
}
/// The ACME Directory object structure.
///
/// The data in here is typically not relevant to the user of this crate.
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DirectoryData {
/// The entry point to create a new account.
pub new_account: String,
/// The entry point to retrieve a new nonce, should be used with a `HEAD` request.
pub new_nonce: String,
/// URL to post new orders to.
pub new_order: String,
/// URL to use for certificate revocation.
pub revoke_cert: String,
/// Account key rollover URL.
pub key_change: String,
/// Metadata object, for additional information which aren't directly part of the API
/// itself, such as the terms of service.
#[serde(skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
/// The directory's "meta" object.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Meta {
/// The terms of service. This is typically in the form of an URL.
#[serde(skip_serializing_if = "Option::is_none")]
pub terms_of_service: Option<String>,
/// Flag indicating if EAB is required, None is equivalent to false
#[serde(skip_serializing_if = "Option::is_none")]
pub external_account_required: Option<bool>,
/// Website with information about the ACME Server
#[serde(skip_serializing_if = "Option::is_none")]
pub website: Option<String>,
/// List of hostnames used by the CA, intended for the use with caa dns records
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub caa_identities: Vec<String>,
}
impl Directory {
/// Create a `Directory` given the parsed `DirectoryData` of a `GET` request to the directory
/// URL.
pub fn from_parts(url: String, data: DirectoryData) -> Self {
Self { url, data }
}
/// Get the ToS URL.
pub fn terms_of_service_url(&self) -> Option<&str> {
match &self.data.meta {
Some(meta) => meta.terms_of_service.as_deref(),
None => None,
}
}
/// Get if external account binding is required
pub fn external_account_binding_required(&self) -> bool {
matches!(
&self.data.meta,
Some(Meta {
external_account_required: Some(true),
..
})
)
}
/// Get the "newNonce" URL. Use `HEAD` requests on this to get a new nonce.
pub fn new_nonce_url(&self) -> &str {
&self.data.new_nonce
}
pub(crate) fn new_account_url(&self) -> &str {
&self.data.new_account
}
pub(crate) fn new_order_url(&self) -> &str {
&self.data.new_order
}
/// Access to the in the Acme spec defined metadata structure.
pub fn meta(&self) -> Option<&Meta> {
self.data.meta.as_ref()
}
}

61
proxmox-acme/src/eab.rs Normal file
View File

@ -0,0 +1,61 @@
use openssl::hash::MessageDigest;
use openssl::pkey::{HasPrivate, PKeyRef};
use openssl::sign::Signer;
use serde::Serialize;
use crate::key::Jwk;
use crate::types::ExternalAccountBinding;
use crate::{b64u, Error};
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct Protected {
alg: &'static str,
url: String,
kid: String,
}
impl ExternalAccountBinding {
/// Create a new instance
pub fn new<P>(
eab_kid: &str,
eab_hmac_key: &PKeyRef<P>,
jwk: Jwk,
url: String,
) -> Result<Self, Error>
where
P: HasPrivate,
{
let protected = Protected {
alg: "HS256",
kid: eab_kid.to_string(),
url,
};
let payload = b64u::encode(serde_json::to_string(&jwk)?.as_bytes());
let protected_data = b64u::encode(serde_json::to_string(&protected)?.as_bytes());
let signature = {
let protected = protected_data.as_bytes();
let payload = payload.as_bytes();
Self::sign_hmac(eab_hmac_key, protected, payload)?
};
let signature = b64u::encode(&signature);
Ok(ExternalAccountBinding {
protected: protected_data,
payload,
signature,
})
}
fn sign_hmac<P>(key: &PKeyRef<P>, protected: &[u8], payload: &[u8]) -> Result<Vec<u8>, Error>
where
P: HasPrivate,
{
let mut signer = Signer::new(MessageDigest::sha256(), key)?;
signer.update(protected)?;
signer.update(b".")?;
signer.update(payload)?;
Ok(signer.sign_to_vec()?)
}
}

154
proxmox-acme/src/error.rs Normal file
View File

@ -0,0 +1,154 @@
//! The `Error` type and some ACME error constants for reference.
use std::fmt;
use openssl::error::ErrorStack as SslErrorStack;
/// The ACME error string for a "bad nonce" error.
pub const BAD_NONCE: &str = "urn:ietf:params:acme:error:badNonce";
/// The ACME error string for a "user action required" error.
pub const USER_ACTION_REQUIRED: &str = "urn:ietf:params:acme:error:userActionRequired";
/// Error types returned by this crate.
#[derive(Debug)]
#[must_use = "unused errors have no effect"]
pub enum Error {
/// A `badNonce` API response. The request should be retried with the new nonce received along
/// with this response.
BadNonce,
/// A `userActionRequired` API response. Typically this means there was a change to the ToS and
/// the user has to agree to the new terms.
UserActionRequired(String),
/// Other error responses from the Acme API not handled specially.
Api(crate::request::ErrorResponse),
/// The Acme API behaved unexpectedly.
InvalidApi(String),
/// Tried to use an `Account` or `AccountCreator` without a private key.
MissingKey,
/// Tried to create an `Account` without providing a single contact info.
MissingContactInfo,
/// Tried to use an empty `Order`.
EmptyOrder,
/// A raw `openssl::PKey` containing an unsupported key was passed.
UnsupportedKeyType,
/// A raw `openssl::PKey` or `openssl::EcKey` with an unsupported curve was passed.
UnsupportedGroup,
/// Failed to parse the account data returned by the API upon account creation.
BadAccountData(String),
/// Failed to parse the order data returned by the API from a new-order request.
BadOrderData(String),
/// An openssl error occurred during a crypto operation.
RawSsl(SslErrorStack),
/// An openssl error occurred during a crypto operation.
/// With some textual context.
Ssl(&'static str, SslErrorStack),
/// An otherwise uncaught serde error happened.
Json(serde_json::Error),
/// Failed to parse
BadBase64(base64::DecodeError),
/// Can be used by the user for textual error messages without having to downcast to regular
/// acme errors.
Custom(String),
/// If built with the `client` feature, this is where general ureq/network errors end up.
/// This is usually a `ureq::Error`, however in order to provide an API which is not
/// feature-dependent, this variant is always present and contains a boxed `dyn Error`.
HttpClient(Box<dyn std::error::Error + Send + Sync + 'static>),
/// If built with the `client` feature, this is where client specific errors which are not from
/// errors forwarded from `ureq` end up.
Client(String),
/// A non-openssl error occurred while building data for the CSR.
Csr(String),
}
impl Error {
/// Create an `Error` from a custom text.
pub fn custom<T: std::fmt::Display>(s: T) -> Self {
Error::Custom(s.to_string())
}
/// Convenience method to check if this error represents a bad nonce error in which case the
/// request needs to be re-created using a new nonce.
pub fn is_bad_nonce(&self) -> bool {
matches!(self, Error::BadNonce)
}
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Api(err) => match err.detail.as_deref() {
Some(detail) => write!(f, "{}: {}", err.ty, detail),
None => fmt::Display::fmt(&err.ty, f),
},
Error::InvalidApi(err) => write!(f, "Acme Server API misbehaved: {}", err),
Error::BadNonce => f.write_str("bad nonce, please retry with a new nonce"),
Error::UserActionRequired(err) => write!(f, "user action required: {}", err),
Error::MissingKey => f.write_str("cannot build an account without a key"),
Error::MissingContactInfo => f.write_str("account requires contact info"),
Error::EmptyOrder => f.write_str("cannot make an empty order"),
Error::UnsupportedKeyType => f.write_str("unsupported key type"),
Error::UnsupportedGroup => f.write_str("unsupported EC group"),
Error::BadAccountData(err) => {
write!(f, "bad response to account query or creation: {}", err)
}
Error::BadOrderData(err) => {
write!(f, "bad response to new-order query or creation: {}", err)
}
Error::RawSsl(err) => fmt::Display::fmt(err, f),
Error::Ssl(context, err) => {
write!(f, "{}: {}", context, err)
}
Error::Json(err) => fmt::Display::fmt(err, f),
Error::Custom(err) => fmt::Display::fmt(err, f),
Error::HttpClient(err) => fmt::Display::fmt(err, f),
Error::Client(err) => fmt::Display::fmt(err, f),
Error::Csr(err) => fmt::Display::fmt(err, f),
Error::BadBase64(err) => fmt::Display::fmt(err, f),
}
}
}
impl From<SslErrorStack> for Error {
fn from(e: SslErrorStack) -> Self {
Error::RawSsl(e)
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::Json(e)
}
}
impl From<crate::request::ErrorResponse> for Error {
fn from(e: crate::request::ErrorResponse) -> Self {
Error::Api(e)
}
}
impl From<base64::DecodeError> for Error {
fn from(e: base64::DecodeError) -> Self {
Error::BadBase64(e)
}
}

43
proxmox-acme/src/json.rs Normal file
View File

@ -0,0 +1,43 @@
use openssl::hash::Hasher;
use serde_json::Value;
use crate::Error;
pub fn to_hash_canonical(value: &Value, output: &mut Hasher) -> Result<(), Error> {
match value {
Value::Null | Value::String(_) | Value::Number(_) | Value::Bool(_) => {
serde_json::to_writer(output, &value)?;
}
Value::Array(list) => {
output.update(b"[")?;
let mut iter = list.iter();
if let Some(item) = iter.next() {
to_hash_canonical(item, output)?;
for item in iter {
output.update(b",")?;
to_hash_canonical(item, output)?;
}
}
output.update(b"]")?;
}
Value::Object(map) => {
output.update(b"{")?;
let mut keys: Vec<&str> = map.keys().map(String::as_str).collect();
keys.sort_unstable();
let mut iter = keys.into_iter();
if let Some(key) = iter.next() {
serde_json::to_writer(&mut *output, &key)?;
output.update(b":")?;
to_hash_canonical(&map[key], output)?;
for key in iter {
output.update(b",")?;
serde_json::to_writer(&mut *output, &key)?;
output.update(b":")?;
to_hash_canonical(&map[key], output)?;
}
}
output.update(b"}")?;
}
}
Ok(())
}

168
proxmox-acme/src/jws.rs Normal file
View File

@ -0,0 +1,168 @@
use std::convert::TryFrom;
use openssl::hash::{Hasher, MessageDigest};
use openssl::pkey::{HasPrivate, PKeyRef};
use openssl::sign::Signer;
use serde::Serialize;
use crate::b64u;
use crate::key::{Jwk, PublicKey};
use crate::Error;
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Protected {
alg: &'static str,
nonce: String,
url: String,
#[serde(flatten)]
key: KeyId,
}
/// Acme requires to the use of *either* `jwk` *or* `kid` depending on the action taken.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum KeyId {
/// This is the actual JWK structure.
Jwk(Jwk),
/// This should be the account location.
Kid(String),
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Jws {
protected: String,
payload: String,
signature: String,
}
impl Jws {
pub fn new<P, T>(
key: &PKeyRef<P>,
location: Option<String>,
url: String,
nonce: String,
payload: &T,
) -> Result<Self, Error>
where
P: HasPrivate,
T: Serialize,
{
Self::new_full(
key,
location,
url,
nonce,
b64u::encode(serde_json::to_string(payload)?.as_bytes()),
)
}
pub fn new_full<P: HasPrivate>(
key: &PKeyRef<P>,
location: Option<String>,
url: String,
nonce: String,
payload: String,
) -> Result<Self, Error> {
let jwk = Jwk::try_from(key)?;
let pubkey = jwk.key.clone();
let mut protected = Protected {
alg: "",
nonce,
url,
key: match location {
Some(location) => KeyId::Kid(location),
None => KeyId::Jwk(jwk),
},
};
let (digest, ec_order_bytes): (MessageDigest, usize) = match &pubkey {
PublicKey::Rsa(_) => (Self::prepare_rsa(key, &mut protected), 0),
PublicKey::Ec(_) => Self::prepare_ec(key, &mut protected),
};
let protected_data = b64u::encode(serde_json::to_string(&protected)?.as_bytes());
let signature = {
let prot = protected_data.as_bytes();
let payload = payload.as_bytes();
match &pubkey {
PublicKey::Rsa(_) => Self::sign_rsa(key, digest, prot, payload),
PublicKey::Ec(_) => Self::sign_ec(key, digest, ec_order_bytes, prot, payload),
}?
};
let signature = b64u::encode(&signature);
Ok(Jws {
protected: protected_data,
payload,
signature,
})
}
fn prepare_rsa<P>(_key: &PKeyRef<P>, protected: &mut Protected) -> MessageDigest
where
P: HasPrivate,
{
protected.alg = "RS256";
MessageDigest::sha256()
}
/// Returns the digest and the size of the two signature components 'r' and 's'.
fn prepare_ec<P>(_key: &PKeyRef<P>, protected: &mut Protected) -> (MessageDigest, usize)
where
P: HasPrivate,
{
// Note: if we support >256 bit keys we'll want to also support using ES512 here probably
protected.alg = "ES256";
// 'r' and 's' are each 256 bit numbers:
(MessageDigest::sha256(), 32)
}
fn sign_rsa<P>(
key: &PKeyRef<P>,
digest: MessageDigest,
protected: &[u8],
payload: &[u8],
) -> Result<Vec<u8>, Error>
where
P: HasPrivate,
{
let mut signer = Signer::new(digest, key)?;
signer.set_rsa_padding(openssl::rsa::Padding::PKCS1)?;
signer.update(protected)?;
signer.update(b".")?;
signer.update(payload)?;
Ok(signer.sign_to_vec()?)
}
fn sign_ec<P>(
key: &PKeyRef<P>,
digest: MessageDigest,
ec_order_bytes: usize,
protected: &[u8],
payload: &[u8],
) -> Result<Vec<u8>, Error>
where
P: HasPrivate,
{
let mut hasher = Hasher::new(digest)?;
hasher.update(protected)?;
hasher.update(b".")?;
hasher.update(payload)?;
let sig =
openssl::ecdsa::EcdsaSig::sign(hasher.finish()?.as_ref(), key.ec_key()?.as_ref())?;
let r = sig.r().to_vec();
let s = sig.s().to_vec();
let mut out = Vec::with_capacity(ec_order_bytes * 2);
out.extend(std::iter::repeat(0u8).take(ec_order_bytes - r.len()));
out.extend(r);
out.extend(std::iter::repeat(0u8).take(ec_order_bytes - s.len()));
out.extend(s);
Ok(out)
}
}

129
proxmox-acme/src/key.rs Normal file
View File

@ -0,0 +1,129 @@
use std::convert::{TryFrom, TryInto};
use openssl::hash::{Hasher, MessageDigest};
use openssl::pkey::{HasPublic, Id, PKeyRef};
use serde::Serialize;
use crate::b64u;
use crate::Error;
/// An RSA public key.
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RsaPublicKey {
#[serde(with = "b64u::bytes")]
e: Vec<u8>,
#[serde(with = "b64u::bytes")]
n: Vec<u8>,
}
/// An EC public key.
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EcPublicKey {
crv: &'static str,
#[serde(with = "b64u::bytes")]
x: Vec<u8>,
#[serde(with = "b64u::bytes")]
y: Vec<u8>,
}
/// A public key.
///
/// Internally tagged, so this already contains the 'kty' member.
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "kty")]
pub enum PublicKey {
#[serde(rename = "RSA")]
Rsa(RsaPublicKey),
#[serde(rename = "EC")]
Ec(EcPublicKey),
}
impl PublicKey {
/// The thumbprint is the b64u encoded sha256sum of the *canonical* json representation.
pub fn thumbprint(&self) -> Result<String, Error> {
let mut hasher = Hasher::new(MessageDigest::sha256())?;
crate::json::to_hash_canonical(&serde_json::to_value(self)?, &mut hasher)?;
Ok(b64u::encode(hasher.finish()?.as_ref()))
}
}
#[derive(Clone, Debug, Serialize)]
pub struct Jwk {
#[serde(rename = "use", skip_serializing_if = "Option::is_none")]
pub usage: Option<String>,
/// The key data is internally tagged, we can just flatten it.
#[serde(flatten)]
pub key: PublicKey,
}
impl<P: HasPublic> TryFrom<&PKeyRef<P>> for Jwk {
type Error = Error;
fn try_from(key: &PKeyRef<P>) -> Result<Self, Self::Error> {
Ok(Self {
key: key.try_into()?,
usage: None,
})
}
}
impl<P: HasPublic> TryFrom<&PKeyRef<P>> for PublicKey {
type Error = Error;
fn try_from(key: &PKeyRef<P>) -> Result<Self, Self::Error> {
match key.id() {
Id::RSA => Ok(PublicKey::Rsa(RsaPublicKey::try_from(&key.rsa()?)?)),
Id::EC => Ok(PublicKey::Ec(EcPublicKey::try_from(&key.ec_key()?)?)),
_ => Err(Error::UnsupportedKeyType),
}
}
}
impl<P: HasPublic> TryFrom<&openssl::rsa::Rsa<P>> for RsaPublicKey {
type Error = Error;
fn try_from(key: &openssl::rsa::Rsa<P>) -> Result<Self, Self::Error> {
Ok(RsaPublicKey {
e: key.e().to_vec(),
n: key.n().to_vec(),
})
}
}
impl<P: HasPublic> TryFrom<&openssl::ec::EcKey<P>> for EcPublicKey {
type Error = Error;
fn try_from(key: &openssl::ec::EcKey<P>) -> Result<Self, Self::Error> {
let group = key.group();
if group.curve_name() != Some(openssl::nid::Nid::X9_62_PRIME256V1) {
return Err(Error::UnsupportedGroup);
}
let mut ctx = openssl::bn::BigNumContext::new()?;
let mut x = openssl::bn::BigNum::new()?;
let mut y = openssl::bn::BigNum::new()?;
key.public_key()
.affine_coordinates(group, &mut x, &mut y, &mut ctx)?;
Ok(EcPublicKey {
crv: "P-256",
x: x.to_vec(),
y: y.to_vec(),
})
}
}
#[test]
fn test_key_conversion() -> Result<(), Error> {
let key = openssl::ec::EcKey::generate(
openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1)?.as_ref(),
)?;
let _ = EcPublicKey::try_from(&key).expect("failed to jsonify ec key");
Ok(())
}

91
proxmox-acme/src/lib.rs Normal file
View File

@ -0,0 +1,91 @@
//! ACME protocol helper.
//!
//! This is supposed to implement the low level parts of the ACME protocol, providing an [`Account`]
//! and some other helper types which allow interacting with an ACME server by implementing methods
//! which create [`Request`]s the user can then combine with a nonce and send to the the ACME
//! server using whatever http client they choose.
//!
//! This is a rather low level crate, and while it provides an optional synchronous client using
//! curl (for simplicity), users should have basic understanding of the ACME API in order to
//! implement a client using this.
//!
//! The [`Account`] helper supports RSA and ECC keys and provides most of the API methods.
#![deny(missing_docs)]
#![deny(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#[cfg(feature = "api-types")]
pub mod types;
#[cfg(feature = "impl")]
mod b64u;
#[cfg(feature = "impl")]
mod eab;
#[cfg(feature = "impl")]
mod json;
#[cfg(feature = "impl")]
mod jws;
#[cfg(feature = "impl")]
mod key;
#[cfg(feature = "impl")]
mod request;
#[cfg(feature = "impl")]
pub mod account;
#[cfg(feature = "impl")]
pub mod authorization;
#[cfg(feature = "impl")]
pub mod directory;
#[cfg(feature = "impl")]
pub mod error;
#[cfg(feature = "impl")]
pub mod order;
#[cfg(feature = "impl")]
pub mod util;
#[cfg(feature = "impl")]
#[doc(inline)]
pub use account::Account;
#[cfg(feature = "impl")]
#[doc(inline)]
pub use authorization::{Authorization, Challenge};
#[cfg(feature = "impl")]
#[doc(inline)]
pub use directory::Directory;
#[cfg(feature = "impl")]
#[doc(inline)]
pub use error::Error;
#[cfg(feature = "impl")]
#[doc(inline)]
pub use order::Order;
#[cfg(feature = "impl")]
#[doc(inline)]
pub use request::Request;
// we don't inline these:
#[cfg(feature = "impl")]
pub use order::NewOrder;
#[cfg(feature = "impl")]
pub use request::ErrorResponse;
/// Header name for nonces.
pub const REPLAY_NONCE: &str = "Replay-Nonce";
/// Header name for locations.
pub const LOCATION: &str = "Location";
#[cfg(feature = "client")]
pub mod client;
#[cfg(feature = "client")]
pub use client::Client;
#[cfg(feature = "async-client")]
pub mod async_client;

175
proxmox-acme/src/order.rs Normal file
View File

@ -0,0 +1,175 @@
//! ACME Orders data and identifiers.
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::request::Request;
use crate::Error;
/// Status of an [`Order`].
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum Status {
/// Invalid, used as a place holder for when sending objects as contrary to account creation,
/// the Acme RFC does not require the server to ignore unknown parts of the `Order` object.
#[default]
New,
/// Authorization failed and it is now invalid.
Invalid,
/// The authorization is pending and the user should look through its challenges.
///
/// This is the initial state of a new authorization.
Pending,
/// The ACME provider is processing an authorization validation.
Processing,
/// The requirements for the order have been met and it may be finalized.
Ready,
/// The certificate has been issued and can be downloaded from the URL provided in the
/// [`Order`]'s `certificate` field.
Valid,
}
impl Status {
/// Serde helper
fn is_new(&self) -> bool {
*self == Status::New
}
/// Convenience method to check if the status is 'pending'.
#[inline]
pub fn is_pending(self) -> bool {
self == Status::Pending
}
/// Convenience method to check if the status is 'valid'.
#[inline]
pub fn is_valid(self) -> bool {
self == Status::Valid
}
}
/// An identifier used for a certificate request.
///
/// Currently only supports DNS name identifiers.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(tag = "type", content = "value", rename_all = "lowercase")]
pub enum Identifier {
/// A DNS identifier is used to request a domain name to be added to a certificate.
Dns(String),
}
/// This contains the order data sent to and received from the ACME server.
///
/// This is typically filled with a set of domains and then issued as a new-order request via [`Account::new_order`](crate::Account::new_order).
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderData {
/// The order status.
#[serde(skip_serializing_if = "Status::is_new", default)]
pub status: Status,
/// This order's expiration date as RFC3339 formatted time string.
#[serde(skip_serializing_if = "Option::is_none")]
pub expires: Option<String>,
/// List of identifiers to order for the certificate.
pub identifiers: Vec<Identifier>,
/// An RFC3339 formatted time string. It is up to the user to choose a dev dependency for this
/// shit.
#[serde(skip_serializing_if = "Option::is_none")]
pub not_before: Option<String>,
/// An RFC3339 formatted time string. It is up to the user to choose a dev dependency for this
/// shit.
#[serde(skip_serializing_if = "Option::is_none")]
pub not_after: Option<String>,
/// Possible errors in this order.
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<Value>,
/// List of URL's to authorizations the client needs to complete.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub authorizations: Vec<String>,
/// URL the final CSR needs to be POSTed to in order to complete the order, once all
/// authorizations have been performed.
#[serde(skip_serializing_if = "Option::is_none")]
pub finalize: Option<String>,
/// URL at which the issued certificate can be fetched once it is available.
#[serde(skip_serializing_if = "Option::is_none")]
pub certificate: Option<String>,
}
impl OrderData {
/// Initialize an empty order object.
pub fn new() -> Self {
Default::default()
}
/// Builder-style method to add a domain identifier to the data.
pub fn domain(mut self, domain: String) -> Self {
self.identifiers.push(Identifier::Dns(domain));
self
}
}
/// Represents an order for a new certificate. This combines the order's own location (URL) with
/// the [`OrderData`] received from the ACME server.
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Order {
/// Order location URL.
pub location: String,
/// The order's data object.
pub data: OrderData,
}
impl Order {
/// Get an authorization URL (or `None` if the index is out of range).
pub fn authorization(&self, index: usize) -> Option<&str> {
Some(self.data.authorizations.get(index)?)
}
/// Get the number of authorizations in this object.
pub fn authorization_len(&self) -> usize {
self.data.authorizations.len()
}
}
/// Represents a new in-flight order creation.
///
/// This is created via [`Account::new_order`](crate::Account::new_order()).
pub struct NewOrder {
//order: OrderData,
/// The request to execute to place the order. When creating a [`NewOrder`] via
/// [`Account::new_order`](crate::Account::new_order) this is guaranteed to be `Some`.
pub request: Option<Request>,
}
impl NewOrder {
pub(crate) fn new(request: Request) -> Self {
Self {
//order,
request: Some(request),
}
}
/// Deal with the response we got from the server.
pub fn response(self, location_header: String, response_body: &[u8]) -> Result<Order, Error> {
Ok(Order {
location: location_header,
data: serde_json::from_slice(response_body)
.map_err(|err| Error::BadOrderData(err.to_string()))?,
})
}
}

View File

@ -0,0 +1,42 @@
use serde::Deserialize;
pub(crate) const JSON_CONTENT_TYPE: &str = "application/jose+json";
pub(crate) const CREATED: u16 = 201;
/// A request which should be performed on the ACME provider.
pub struct Request {
/// The complete URL to send the request to.
pub url: String,
/// The HTTP method name to use.
pub method: &'static str,
/// The `Content-Type` header to pass along.
pub content_type: &'static str,
/// The body to pass along with request, or an empty string.
pub body: String,
/// The expected status code a compliant ACME provider will return on success.
pub expected: u16,
}
/// An ACME error response contains a specially formatted type string, and can optionally
/// contain textual details and a set of sub problems.
#[derive(Clone, Debug, Deserialize)]
pub struct ErrorResponse {
/// The ACME error type string.
///
/// Most of the time we're only interested in the "bad nonce" or "user action required"
/// errors. When an [`Error`](crate::Error) is built from this error response, it will map
/// to the corresponding enum values (eg. [`Error::BadNonce`](crate::Error::BadNonce)).
#[serde(rename = "type")]
pub ty: String,
/// A textual detail string optionally provided by the ACME provider to inform the user more
/// verbosely about why the error occurred.
pub detail: Option<String>,
/// Additional json data containing information as to why the error occurred.
pub subproblems: Option<serde_json::Value>,
}

126
proxmox-acme/src/types.rs Normal file
View File

@ -0,0 +1,126 @@
//! Define types which are exposed with the proxmox API
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[cfg_attr(feature = "api-types", proxmox_schema::api())]
/// External Account Bindings
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExternalAccountBinding {
/// JOSE Header (see RFC 7515)
pub protected: String,
/// Payload
pub payload: String,
/// HMAC signature
pub signature: String,
}
/// Status of an ACME account.
#[cfg_attr(feature = "api-types", proxmox_schema::api())]
#[derive(Clone, Copy, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum AccountStatus {
/// This is not part of the ACME API, but a temporary marker for us until the ACME provider
/// tells us the account's real status.
#[serde(rename = "<invalid>")]
New,
/// Means the account is valid and can be used.
Valid,
/// The account has been deactivated by its user and cannot be used anymore.
Deactivated,
/// The account has been revoked by the server and cannot be used anymore.
Revoked,
}
impl Default for AccountStatus {
fn default() -> Self {
Self::new()
}
}
impl AccountStatus {
/// Create a new instance with state New.
#[inline]
pub fn new() -> Self {
AccountStatus::New
}
/// Return true if state is New
#[inline]
pub fn is_new(&self) -> bool {
*self == AccountStatus::New
}
}
#[inline]
fn default_true() -> bool {
true
}
#[inline]
fn is_false(b: &bool) -> bool {
!*b
}
#[cfg_attr(feature="api-types", proxmox_schema::api(
properties: {
extra: {
type: Object,
properties: {},
additional_properties: true,
},
contact: {
type: Array,
items: {
type: String,
description: "Contact Info.",
},
},
}
))]
/// ACME Account data. This is the part of the account returned from and possibly sent to the ACME
/// provider. Some fields may be uptdated by the user via a request to the account location, others
/// may not be changed.
#[derive(Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountData {
/// The current account status.
#[serde(
skip_serializing_if = "AccountStatus::is_new",
default = "AccountStatus::new"
)]
pub status: AccountStatus,
/// URLs to currently pending orders.
#[serde(skip_serializing_if = "Option::is_none")]
pub orders: Option<String>,
/// The account's contact info.
///
/// This usually contains a `"mailto:<email address>"` entry but may also contain some other
/// data if the server accepts it.
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub contact: Vec<String>,
/// Indicated whether the user agreed to the ACME provider's terms of service.
#[serde(skip_serializing_if = "Option::is_none")]
pub terms_of_service_agreed: Option<bool>,
/// External account information.
#[serde(skip_serializing_if = "Option::is_none")]
pub external_account_binding: Option<ExternalAccountBinding>,
/// This is only used by the client when querying an account.
#[serde(default = "default_true", skip_serializing_if = "is_false")]
pub only_return_existing: bool,
/// Stores unknown fields if there are any.
#[serde(flatten, default, skip_serializing_if = "HashMap::is_empty")]
pub extra: HashMap<String, Value>,
}

85
proxmox-acme/src/util.rs Normal file
View File

@ -0,0 +1,85 @@
//! Certificate utility methods for convenience (such as CSR generation).
use std::collections::HashMap;
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
use openssl::x509::{self, X509Name, X509Req};
use crate::Error;
/// A certificate signing request.
pub struct Csr {
/// DER encoded certificate request.
pub data: Vec<u8>,
/// PEM formatted PKCS#8 private key.
pub private_key_pem: Vec<u8>,
}
impl Csr {
/// Generate a CSR in DER format with a PEM formatted PKCS8 private key.
///
/// The `identifiers` should be a list of domains. The `attributes` should have standard names
/// recognized by openssl.
pub fn generate(
identifiers: &[impl AsRef<str>],
attributes: &HashMap<String, &str>,
) -> Result<Self, Error> {
if identifiers.is_empty() {
return Err(Error::Csr("cannot generate empty CSR".to_string()));
}
let private_key = Rsa::generate(4096)
.and_then(PKey::from_rsa)
.map_err(|err| Error::Ssl("failed to generate RSA key: {}", err))?;
let private_key_pem = private_key
.private_key_to_pem_pkcs8()
.map_err(|err| Error::Ssl("failed to format private key as PEM pkcs8: {}", err))?;
let mut name = X509Name::builder()?;
if !attributes.contains_key("CN") {
name.append_entry_by_nid(Nid::COMMONNAME, identifiers[0].as_ref())?;
}
for (key, value) in attributes {
name.append_entry_by_text(key, value)?;
}
let name = name.build();
let mut csr = X509Req::builder()?;
csr.set_subject_name(&name)?;
csr.set_pubkey(&private_key)?;
let context = csr.x509v3_context(None);
let mut ext = openssl::stack::Stack::new()?;
ext.push(x509::extension::BasicConstraints::new().build()?)?;
ext.push(
x509::extension::KeyUsage::new()
.digital_signature()
.key_encipherment()
.build()?,
)?;
ext.push(
x509::extension::ExtendedKeyUsage::new()
.server_auth()
.client_auth()
.build()?,
)?;
let mut san = x509::extension::SubjectAlternativeName::new();
for dns in identifiers {
san.dns(dns.as_ref());
}
ext.push({ san }.build(&context)?)?;
csr.add_extensions(&ext)?;
csr.sign(&private_key, MessageDigest::sha256())?;
Ok(Self {
data: csr.build().to_der()?,
private_key_pem,
})
}
}

View File

@ -1,13 +1,14 @@
[package]
name = "proxmox-api-macro"
edition.workspace = true
version = "1.0.4"
description = "Proxmox API macro"
version = "1.3.2"
authors.workspace = true
edition.workspace = true
exclude.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
description = "Proxmox API macro"
exclude.workspace = true
[lib]
proc-macro = true
@ -22,6 +23,7 @@ syn = { workspace = true , features = [ "extra-traits" ] }
futures.workspace = true
serde = { workspace = true, features = [ "derive" ] }
serde_json.workspace = true
proxmox-section-config.workspace = true
[dev-dependencies.proxmox-schema]
workspace = true

View File

@ -1,3 +1,89 @@
rust-proxmox-api-macro (1.3.2-1) bookworm; urgency=medium
* mark parameter defaults as `#[allow(dead_code)]`
* sort variants when using `#[api]` on an `enum` to generate a OneOfSchema
-- Proxmox Support Team <support@proxmox.com> Wed, 19 Feb 2025 12:55:02 +0100
rust-proxmox-api-macro (1.3.1-1) bookworm; urgency=medium
* rebuild with proxmox-schema 4.0
-- Proxmox Support Team <support@proxmox.com> Wed, 15 Jan 2025 12:36:26 +0100
rust-proxmox-api-macro (1.3.0-1) bookworm; urgency=medium
* A missing/empty description for enums is now an error.
* Add experimental json_schema!() macro to create a `Schema` in json
notation.
-- Proxmox Support Team <support@proxmox.com> Thu, 09 Jan 2025 14:20:54 +0100
rust-proxmox-api-macro (1.2.1-1) bookworm; urgency=medium
* allow declaring a field meant to collect the 'additional_properties'
-- Proxmox Support Team <support@proxmox.com> Thu, 26 Sep 2024 14:52:43 +0200
rust-proxmox-api-macro (1.2.0-1) bookworm; urgency=medium
* deprecate old "streaming" method attribute
* add "serializing" method attribute to replace the old "streaming" one
* add "stream" method attribute for the *new* streaming API
* fix warnings in tests
-- Proxmox Support Team <support@proxmox.com> Wed, 04 Sep 2024 15:36:05 +0200
rust-proxmox-api-macro (1.1.0-1) stable; urgency=medium
* fix handling of renames when deriving an Updater for structs
* experimental support for newtype-only enums for SectionConfig support
* use const blocks in thread_local calls
* documentation and typo fixe
* code cleanups, warning and clippy fixes
-- Proxmox Support Team <support@proxmox.com> Tue, 06 Aug 2024 14:15:49 +0200
rust-proxmox-api-macro (1.0.8-1) stable; urgency=medium
* update to proxmox-schema 3
* make #[serde(skip_serializing_if)] without #[serde(default)] an error
-- Proxmox Support Team <support@proxmox.com> Fri, 02 Feb 2024 13:44:40 +0100
rust-proxmox-api-macro (1.0.7-1) stable; urgency=medium
* make serde(skip_serializing_if) without serde(default) for non-Option
types an error
* split field and variant attribute parsing
-- Proxmox Support Team <support@proxmox.com> Wed, 06 Dec 2023 16:02:11 +0100
rust-proxmox-api-macro (1.0.6-1) stable; urgency=medium
* clippy fix: this (Default) `impl` can be derived
* update to syn 2, rework attribute parsing
-- Proxmox Support Team <support@proxmox.com> Mon, 02 Oct 2023 09:27:12 +0200
rust-proxmox-api-macro (1.0.5-1) bookworm; urgency=medium
* support non-idents in serde rename attributes on enum variants
-- Proxmox Support Team <support@proxmox.com> Thu, 03 Aug 2023 08:23:42 +0200
rust-proxmox-api-macro (1.0.4-1) stable; urgency=medium
* support #[default] attribute for types which derive Default

View File

@ -1,22 +1,23 @@
Source: rust-proxmox-api-macro
Section: rust
Priority: optional
Build-Depends: debhelper (>= 12),
dh-cargo (>= 25),
Build-Depends: debhelper-compat (= 13),
dh-sequence-cargo,
cargo:native <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-anyhow-1+default-dev <!nocheck>,
librust-proc-macro2-1+default-dev <!nocheck>,
librust-quote-1+default-dev <!nocheck>,
librust-syn-1+default-dev <!nocheck>,
librust-syn-1+extra-traits-dev <!nocheck>,
librust-syn-1+full-dev <!nocheck>,
librust-syn-1+visit-mut-dev <!nocheck>
librust-syn-2+default-dev <!nocheck>,
librust-syn-2+extra-traits-dev <!nocheck>,
librust-syn-2+full-dev <!nocheck>,
librust-syn-2+visit-mut-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
Standards-Version: 4.6.1
Standards-Version: 4.7.0
Vcs-Git: git://git.proxmox.com/git/proxmox.git
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
Homepage: https://proxmox.com
X-Cargo-Crate: proxmox-api-macro
Rules-Requires-Root: no
@ -28,18 +29,17 @@ Depends:
librust-anyhow-1+default-dev,
librust-proc-macro2-1+default-dev,
librust-quote-1+default-dev,
librust-syn-1+default-dev,
librust-syn-1+extra-traits-dev,
librust-syn-1+full-dev,
librust-syn-1+visit-mut-dev
librust-syn-2+default-dev,
librust-syn-2+extra-traits-dev,
librust-syn-2+full-dev,
librust-syn-2+visit-mut-dev
Provides:
librust-proxmox-api-macro+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-1-dev (= ${binary:Version}),
librust-proxmox-api-macro-1+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0.4-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.0.4+default-dev (= ${binary:Version})
librust-proxmox-api-macro-1.3-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.3+default-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.3.2-dev (= ${binary:Version}),
librust-proxmox-api-macro-1.3.2+default-dev (= ${binary:Version})
Description: Proxmox API macro - Rust source code
This package contains the source for the Rust proxmox-api-macro crate, packaged
by debcargo for use with cargo and dh-cargo.
Source code for Debianized Rust crate "proxmox-api-macro"

View File

@ -1,8 +1,7 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{Meta, NestedMeta};
use syn::meta::ParseNestedMeta;
use crate::util::{self, default_false, parse_str_value_to_option, set_bool};
use crate::util;
#[derive(Default)]
pub struct UpdaterFieldAttributes {
@ -20,46 +19,42 @@ impl UpdaterFieldAttributes {
pub fn from_attributes(input: &mut Vec<syn::Attribute>) -> Self {
let mut this = Self::default();
util::extract_attributes(input, "updater", |attr, meta| this.parse(attr, meta));
for attr in std::mem::take(input) {
if attr.style != syn::AttrStyle::Outer || !attr.path().is_ident("updater") {
input.push(attr);
continue;
}
match attr.parse_nested_meta(|meta| this.parse(meta)) {
Ok(()) => (),
Err(err) => crate::add_error(err),
}
}
this
}
fn parse(&mut self, attr: &syn::Attribute, input: NestedMeta) -> Result<(), syn::Error> {
match input {
NestedMeta::Lit(lit) => bail!(lit => "unexpected literal"),
NestedMeta::Meta(meta) => self.parse_meta(attr, meta),
}
}
fn parse(&mut self, meta: ParseNestedMeta<'_>) -> Result<(), syn::Error> {
let path = &meta.path;
fn parse_meta(&mut self, attr: &syn::Attribute, meta: Meta) -> Result<(), syn::Error> {
match meta {
Meta::Path(ref path) if path.is_ident("skip") => {
set_bool(&mut self.skip, path, true);
if path.is_ident("skip") {
if !meta.input.is_empty() {
return Err(meta.error("'skip' attribute does not take any data"));
}
Meta::NameValue(ref nv) if nv.path.is_ident("type") => {
parse_str_value_to_option(&mut self.ty, nv)
}
Meta::NameValue(m) => bail!(&m => "invalid updater attribute: {:?}", m.path),
Meta::List(m) if m.path.is_ident("serde") => {
let mut tokens = TokenStream::new();
m.paren_token
.surround(&mut tokens, |tokens| m.nested.to_tokens(tokens));
self.serde.push(syn::Attribute {
path: m.path,
tokens,
..attr.clone()
});
}
Meta::List(m) => bail!(&m => "invalid updater attribute: {:?}", m.path),
Meta::Path(m) => bail!(&m => "invalid updater attribute: {:?}", m),
util::set_bool(&mut self.skip, path, true);
} else if path.is_ident("type") {
util::parse_str_value_to_option(&mut self.ty, path, meta.value()?);
} else if path.is_ident("serde") {
let content: TokenStream = meta.input.parse()?;
self.serde.push(syn::parse_quote! { # [ #path #content ] });
} else {
return Err(meta.error(format!("invalid updater attribute: {path:?}")));
}
Ok(())
}
pub fn skip(&self) -> bool {
default_false(self.skip.as_ref())
util::default_false(self.skip.as_ref())
}
pub fn ty(&self) -> Option<&syn::TypePath> {
@ -68,8 +63,50 @@ impl UpdaterFieldAttributes {
pub fn replace_serde_attributes(&self, attrs: &mut Vec<syn::Attribute>) {
if !self.serde.is_empty() {
attrs.retain(|attr| !attr.path.is_ident("serde"));
attrs.retain(|attr| !attr.path().is_ident("serde"));
attrs.extend(self.serde.iter().cloned())
}
}
}
#[derive(Default)]
pub struct EnumFieldAttributes {
/// Change the "type-key" for this entry type..
type_key: Option<syn::LitStr>,
}
impl EnumFieldAttributes {
pub fn from_attributes(input: &mut Vec<syn::Attribute>) -> Self {
let mut this = Self::default();
for attr in std::mem::take(input) {
if attr.style != syn::AttrStyle::Outer || !attr.path().is_ident("api") {
input.push(attr);
continue;
}
match attr.parse_nested_meta(|meta| this.parse(meta)) {
Ok(()) => (),
Err(err) => crate::add_error(err),
}
}
this
}
fn parse(&mut self, meta: ParseNestedMeta<'_>) -> Result<(), syn::Error> {
let path = &meta.path;
if path.is_ident("type_key") {
util::duplicate(&self.type_key, path);
self.type_key = Some(meta.value()?.parse()?);
} else {
return Err(meta.error(format!("invalid api attribute: {path:?}")));
}
Ok(())
}
pub fn type_key(&self) -> Option<&syn::LitStr> {
self.type_key.as_ref()
}
}

View File

@ -4,13 +4,55 @@ use anyhow::Error;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned;
use syn::spanned::Spanned;
use super::attributes::EnumFieldAttributes;
use super::Schema;
use crate::serde;
use crate::util::{self, FieldName, JSONObject, JSONValue, Maybe};
/// Enums, provided they're simple enums, simply get an enum string schema attached to them.
pub fn handle_enum(
pub fn handle_enum(attribs: JSONObject, enum_ty: syn::ItemEnum) -> Result<TokenStream, Error> {
let mut first_unit = None;
let mut first_unnamed = None;
let mut first_named = None;
for variant in &enum_ty.variants {
match &variant.fields {
syn::Fields::Unit => first_unit = Some(variant.fields.span()),
syn::Fields::Unnamed(_) => first_unnamed = Some(variant.fields.span()),
syn::Fields::Named(_) => first_named = Some(variant.fields.span()),
}
}
if first_unit.is_some() {
if let Some(conflict) = first_unnamed.or(first_named) {
bail!(
conflict,
"enums must be either with only unit types or only newtypes"
);
}
return handle_string_enum(attribs, enum_ty);
}
if first_unnamed.is_some() {
if let Some(conflict) = first_unit.or(first_named) {
bail!(
conflict,
"enums must be either with only unit types or only newtypes"
);
}
return handle_section_config_enum(attribs, enum_ty);
}
if let Some(bad) = first_named {
bail!(bad, "api type enums with named fields are not allowed");
}
bail!(enum_ty => "api type enums must not be empty");
}
/// Enums, provided they're simple enums, simply get an enum string schema attached to them.
fn handle_string_enum(
mut attribs: JSONObject,
mut enum_ty: syn::ItemEnum,
) -> Result<TokenStream, Error> {
@ -32,6 +74,12 @@ pub fn handle_enum(
if schema.description.is_none() {
let (comment, span) = util::get_doc_comments(&enum_ty.attrs)?;
if comment.is_empty() {
error!(
Span::call_site(),
"missing doc comment on enum for api-schema description"
);
}
schema.description = Maybe::Derived(syn::LitStr::new(comment.trim(), span));
}
@ -57,7 +105,7 @@ pub fn handle_enum(
comment = "<missing description>".to_string();
}
let attrs = serde::SerdeAttrib::try_from(&variant.attrs[..])?;
let attrs = serde::VariantAttrib::try_from(&variant.attrs[..])?;
let variant_string = if let Some(renamed) = attrs.rename {
renamed
} else if let Some(rename_all) = container_attrs.rename_all {
@ -69,7 +117,7 @@ pub fn handle_enum(
};
if derives_default {
if let Some(attr) = variant.attrs.iter().find(|a| a.path.is_ident("default")) {
if let Some(attr) = variant.attrs.iter().find(|a| a.path().is_ident("default")) {
if let Some(default_value) = &default_value {
error!(attr => "multiple default values defined");
error!(default_value => "default previously defined here");
@ -114,3 +162,153 @@ pub fn handle_enum(
}
})
}
fn handle_section_config_enum(
mut attribs: JSONObject,
mut enum_ty: syn::ItemEnum,
) -> Result<TokenStream, Error> {
let name = &enum_ty.ident;
let description: syn::LitStr = match attribs.remove("description") {
Some(desc) => desc.try_into()?,
None => {
let (comment, span) = util::get_doc_comments(&enum_ty.attrs)?;
syn::LitStr::new(comment.trim(), span)
}
};
let id_schema = {
let schema: Schema = match attribs.remove("id-schema") {
Some(schema) => schema.try_into()?,
None => {
bail!(name => "missing 'id-schema' property for SectionConfig style enum")
}
};
let mut ts = TokenStream::new();
schema.to_typed_schema(&mut ts)?;
ts
};
let id_property: syn::LitStr = match attribs.remove("id-property") {
Some(name) => name.try_into()?,
None => bail!(name => "missing 'id-property' property for SectionConfig style enum"),
};
let with_type_key: TokenStream = match attribs.remove("type-key") {
Some(value) => {
let value: syn::LitStr = value.try_into()?;
quote_spanned!(value.span() => .with_type_key(#value))
}
None => TokenStream::new(),
};
let container_attrs = serde::ContainerAttrib::try_from(&enum_ty.attrs[..])?;
let Some(tag) = container_attrs.tag.as_ref() else {
bail!(name => r#"SectionConfig enum needs a `#[serde(tag = "...")]` container attribute"#);
};
let mut variants = Vec::new();
let mut register_sections = TokenStream::new();
let mut to_type = TokenStream::new();
for variant in &mut enum_ty.variants {
let field = match &variant.fields {
syn::Fields::Unnamed(field) if field.unnamed.len() == 1 => &field.unnamed[0],
_ => bail!(variant => "SectionConfig style enum can only have newtype variants"),
};
let attrs = serde::VariantAttrib::try_from(&variant.attrs[..])?;
let variant_string = if let Some(renamed) = attrs.rename {
renamed
} else if let Some(rename_all) = container_attrs.rename_all {
let name = rename_all.apply_to_variant(&variant.ident.to_string());
syn::LitStr::new(&name, variant.ident.span())
} else {
let name = &variant.ident;
syn::LitStr::new(&name.to_string(), name.span())
};
let field_attrs = EnumFieldAttributes::from_attributes(&mut variant.attrs);
let with_type_key = if let Some(key) = field_attrs.type_key() {
quote_spanned!(key.span() => .with_type_key(#key))
} else {
TokenStream::new()
};
let variant_ident = &variant.ident;
let ty = &field.ty;
variants.push((
variant_string.value(),
quote_spanned! { variant.ident.span() =>
(
#variant_string,
&<#ty as ::proxmox_schema::ApiType>::API_SCHEMA,
),
},
));
register_sections.extend(quote_spanned! { variant.ident.span() =>
this.register_plugin(
::proxmox_section_config::SectionConfigPlugin::new(
#variant_string.to_string(),
Some(#id_property.to_string()),
const {
match &<#ty as ::proxmox_schema::ApiType>::API_SCHEMA {
::proxmox_schema::Schema::Object(schema) => schema,
::proxmox_schema::Schema::OneOf(schema) => schema,
_ => panic!("enum requires an object schema"),
}
}
)
#with_type_key
);
});
to_type.extend(quote_spanned! { variant.ident.span() =>
Self::#variant_ident(_) => #variant_string,
});
}
variants.sort_by(|a, b| a.0.cmp(&b.0));
let variants = variants
.into_iter()
.map(|(_name, def)| def)
.collect::<TokenStream>();
Ok(quote_spanned! { name.span() =>
#enum_ty
impl ::proxmox_schema::ApiType for #name {
const API_SCHEMA: ::proxmox_schema::Schema =
::proxmox_schema::OneOfSchema::new(
#description,
&(#tag, false, &#id_schema.schema()),
&[#variants],
)
.schema();
}
impl ::proxmox_section_config::typed::ApiSectionDataEntry for #name {
const INTERNALLY_TAGGED: Option<&'static str> = Some(#tag);
fn section_config() -> &'static ::proxmox_section_config::SectionConfig {
static CONFIG: ::std::sync::OnceLock<::proxmox_section_config::SectionConfig> =
::std::sync::OnceLock::new();
CONFIG.get_or_init(|| {
let id_schema = const {
<Self as ::proxmox_schema::ApiType>::API_SCHEMA
.unwrap_one_of_schema()
.type_property_entry
.2
};
let mut this = ::proxmox_section_config::SectionConfig::new(id_schema)
#with_type_key;
#register_sections
this
})
}
fn section_type(&self) -> &'static str {
match self {
#to_type
}
}
}
})
}

View File

@ -109,11 +109,28 @@ impl TryFrom<JSONObject> for ReturnSchema {
}
}
#[derive(Clone, Copy)]
enum MethodFlavor {
Normal,
Serializing,
Streaming,
}
struct MethodInfo {
input_schema: Schema,
return_type: Option<ReturnType>,
func: syn::ItemFn,
wrapper_ts: TokenStream,
default_consts: TokenStream,
flavor: MethodFlavor,
is_async: bool,
}
/// Parse `input`, `returns` and `protected` attributes out of an function annotated
/// with an `#[api]` attribute and produce a `const ApiMethod` named after the function.
///
/// See the top level macro documentation for a complete example.
pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<TokenStream, Error> {
pub fn handle_method(mut attribs: JSONObject, func: syn::ItemFn) -> Result<TokenStream, Error> {
let input_schema: Schema = match attribs.remove("input") {
Some(input) => input.into_object("input schema definition")?.try_into()?,
None => Schema {
@ -124,7 +141,7 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
},
};
let mut input_schema = if input_schema.as_object().is_some() {
let input_schema = if input_schema.as_object().is_some() {
input_schema
} else {
error!(
@ -137,11 +154,70 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
schema
};
let mut return_type: Option<ReturnType> = attribs
let return_type: Option<ReturnType> = attribs
.remove("returns")
.map(|ret| ret.try_into())
.transpose()?;
/* FIXME: Once the deprecation period is over:
if let Some(streaming) = attribs.remove("streaming") {
error!(
streaming.span(),
"streaming attribute was renamed to 'serializing', as it did not actually stream"
);
}
*/
let streaming: Option<syn::LitBool> = attribs
.remove("streaming")
.map(TryFrom::try_from)
.transpose()?;
let serializing: Option<syn::LitBool> = attribs
.remove("serializing")
.map(TryFrom::try_from)
.transpose()?;
let deprecation_warning = if let Some(streaming) = streaming.clone() {
let deprecation_name = Ident::new(
&format!("attribute_in_{}", func.sig.ident),
streaming.span(),
);
quote! {
mod #deprecation_name {
#[deprecated = "'streaming' attribute is being renamed to 'serializing'"]
fn streaming() {}
fn trigger_deprecation_warning() { streaming() }
}
}
} else {
TokenStream::new()
};
let serializing = streaming
.or(serializing)
.unwrap_or(syn::LitBool::new(false, Span::call_site()));
let streaming: syn::LitBool = attribs
.remove("stream")
.map(TryFrom::try_from)
.transpose()?
.unwrap_or(syn::LitBool::new(false, Span::call_site()));
let mut method_info = MethodInfo {
input_schema,
return_type,
wrapper_ts: TokenStream::new(),
default_consts: TokenStream::new(),
is_async: func.sig.asyncness.is_some(),
flavor: match (serializing.value(), streaming.value()) {
(false, false) => MethodFlavor::Normal,
(true, false) => MethodFlavor::Serializing,
(false, true) => MethodFlavor::Streaming,
(true, true) => {
error!(serializing => "'stream' and 'serializing' attributes are in conflict");
MethodFlavor::Normal
}
},
func,
};
let access_setter = match attribs.remove("access") {
Some(access) => {
let access = Access::try_from(access.into_object("access rules")?)?;
@ -169,12 +245,6 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
.transpose()?
.unwrap_or(false);
let streaming: bool = attribs
.remove("streaming")
.map(TryFrom::try_from)
.transpose()?
.unwrap_or(false);
if !attribs.is_empty() {
error!(
attribs.span(),
@ -183,29 +253,32 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
);
}
let (doc_comment, doc_span) = util::get_doc_comments(&func.attrs)?;
let (doc_comment, doc_span) = util::get_doc_comments(&method_info.func.attrs)?;
util::derive_descriptions(
&mut input_schema,
return_type.as_mut().and_then(ReturnType::as_mut_schema),
&mut method_info.input_schema,
method_info
.return_type
.as_mut()
.and_then(ReturnType::as_mut_schema),
&doc_comment,
doc_span,
)?;
let mut wrapper_ts = TokenStream::new();
let mut default_consts = TokenStream::new();
let is_async = func.sig.asyncness.is_some();
let api_func_name = handle_function_signature(
&mut input_schema,
&mut return_type,
&mut func,
&mut wrapper_ts,
&mut default_consts,
streaming,
)?;
let api_func_name = handle_function_signature(&mut method_info)?;
// input schema is done, let's give the method body a chance to extract default parameters:
DefaultParameters(&input_schema).visit_item_fn_mut(&mut func);
DefaultParameters(&method_info.input_schema).visit_item_fn_mut(&mut method_info.func);
let MethodInfo {
input_schema,
func,
wrapper_ts,
default_consts,
return_type,
flavor,
is_async,
..
} = method_info;
let vis = &func.vis;
let func_name = &func.sig.ident;
@ -224,11 +297,25 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
returns_schema_setter = quote! { .returns(#inner) };
}
let api_handler = match (streaming, is_async) {
(true, true) => quote! { ::proxmox_router::ApiHandler::StreamingAsync(&#api_func_name) },
(true, false) => quote! { ::proxmox_router::ApiHandler::StreamingSync(&#api_func_name) },
(false, true) => quote! { ::proxmox_router::ApiHandler::Async(&#api_func_name) },
(false, false) => quote! { ::proxmox_router::ApiHandler::Sync(&#api_func_name) },
let api_handler = match (flavor, is_async) {
(MethodFlavor::Normal, true) => {
quote! { ::proxmox_router::ApiHandler::Async(&#api_func_name) }
}
(MethodFlavor::Normal, false) => {
quote! { ::proxmox_router::ApiHandler::Sync(&#api_func_name) }
}
(MethodFlavor::Serializing, true) => {
quote! { ::proxmox_router::ApiHandler::SerializingAsync(&#api_func_name) }
}
(MethodFlavor::Serializing, false) => {
quote! { ::proxmox_router::ApiHandler::SerializingSync(&#api_func_name) }
}
(MethodFlavor::Streaming, true) => {
quote! { ::proxmox_router::ApiHandler::StreamAsync(&#api_func_name) }
}
(MethodFlavor::Streaming, false) => {
quote! { ::proxmox_router::ApiHandler::StreamSync(&#api_func_name) }
}
};
Ok(quote_spanned! { func.sig.span() =>
@ -249,20 +336,22 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
#wrapper_ts
#func
#deprecation_warning
})
//Ok(quote::quote!(#func))
}
enum ParameterType<'a> {
enum ParameterType {
Value,
ApiMethod,
RpcEnv,
Normal(NormalParameter<'a>),
Normal(NormalParameter),
}
struct NormalParameter<'a> {
ty: &'a syn::Type,
entry: &'a ObjectEntry,
struct NormalParameter {
ty: syn::Type,
entry: ObjectEntry,
}
fn check_input_type(input: &syn::FnArg) -> Result<(&syn::PatType, &syn::PatIdent), syn::Error> {
@ -281,16 +370,8 @@ fn check_input_type(input: &syn::FnArg) -> Result<(&syn::PatType, &syn::PatIdent
Ok((pat_type, pat))
}
fn handle_function_signature(
input_schema: &mut Schema,
_return_type: &mut Option<ReturnType>,
func: &mut syn::ItemFn,
wrapper_ts: &mut TokenStream,
default_consts: &mut TokenStream,
streaming: bool,
) -> Result<Ident, Error> {
let sig = &func.sig;
let is_async = sig.asyncness.is_some();
fn handle_function_signature(method_info: &mut MethodInfo) -> Result<Ident, Error> {
let sig = &method_info.func.sig;
let mut api_method_param = None;
let mut rpc_env_param = None;
@ -308,7 +389,10 @@ fn handle_function_signature(
};
// For any named type which exists on the function signature...
if let Some(entry) = input_schema.find_obj_property_by_ident_mut(&pat.ident.to_string()) {
if let Some(entry) = method_info
.input_schema
.find_obj_property_by_ident_mut(&pat.ident.to_string())
{
// try to infer the type in the schema if it is not specified explicitly:
let is_option = util::infer_type(&mut entry.schema, &pat_type.ty)?;
let has_default = entry.schema.find_schema_property("default").is_some();
@ -356,75 +440,49 @@ fn handle_function_signature(
// bail out with an error.
let pat_ident = pat.ident.unraw();
let mut param_name: FieldName = pat_ident.clone().into();
let param_type =
if let Some(entry) = input_schema.find_obj_property_by_ident(&pat_ident.to_string()) {
if let SchemaItem::Inferred(span) = &entry.schema.item {
bail!(*span, "failed to infer type");
}
param_name = entry.name.clone();
// Found an explicit parameter: extract it:
ParameterType::Normal(NormalParameter {
ty: &pat_type.ty,
entry,
})
} else if is_api_method_type(&pat_type.ty) {
if api_method_param.is_some() {
error!(pat_type => "multiple ApiMethod parameters found");
continue;
}
api_method_param = Some(param_list.len());
ParameterType::ApiMethod
} else if is_rpc_env_type(&pat_type.ty) {
if rpc_env_param.is_some() {
error!(pat_type => "multiple RpcEnvironment parameters found");
continue;
}
rpc_env_param = Some(param_list.len());
ParameterType::RpcEnv
} else if is_value_type(&pat_type.ty) {
if value_param.is_some() {
error!(pat_type => "multiple additional Value parameters found");
continue;
}
value_param = Some(param_list.len());
ParameterType::Value
} else {
error!(&pat_ident => "unexpected parameter {:?}", pat_ident.to_string());
let param_type = if let Some(entry) = method_info
.input_schema
.find_obj_property_by_ident(&pat_ident.to_string())
{
if let SchemaItem::Inferred(span) = &entry.schema.item {
bail!(*span, "failed to infer type");
}
param_name = entry.name.clone();
// Found an explicit parameter: extract it:
ParameterType::Normal(NormalParameter {
ty: (*pat_type.ty).clone(),
entry: entry.clone(),
})
} else if is_api_method_type(&pat_type.ty) {
if api_method_param.is_some() {
error!(pat_type => "multiple ApiMethod parameters found");
continue;
};
}
api_method_param = Some(param_list.len());
ParameterType::ApiMethod
} else if is_rpc_env_type(&pat_type.ty) {
if rpc_env_param.is_some() {
error!(pat_type => "multiple RpcEnvironment parameters found");
continue;
}
rpc_env_param = Some(param_list.len());
ParameterType::RpcEnv
} else if is_value_type(&pat_type.ty) {
if value_param.is_some() {
error!(pat_type => "multiple additional Value parameters found");
continue;
}
value_param = Some(param_list.len());
ParameterType::Value
} else {
error!(&pat_ident => "unexpected parameter {:?}", pat_ident.to_string());
continue;
};
param_list.push((param_name, param_type));
}
/*
* Doing this is actually unreliable, since we cannot support aliased Result types, or all
* poassible combinations of paths like `result::Result<>` or `std::result::Result<>` or
* `ApiResult`.
// Secondly, take a look at the return type, and then decide what to do:
// If our function has the correct signature we may not even need a wrapper.
if is_default_return_type(&sig.output)
&& (
param_list.len(),
value_param,
api_method_param,
rpc_env_param,
) == (3, Some(0), Some(1), Some(2))
{
return Ok(sig.ident.clone());
}
*/
create_wrapper_function(
//input_schema,
//return_type,
param_list,
func,
wrapper_ts,
default_consts,
is_async,
streaming,
)
create_wrapper_function(method_info, param_list)
}
fn is_api_method_type(ty: &syn::Type) -> bool {
@ -474,24 +532,18 @@ fn is_value_type(ty: &syn::Type) -> bool {
}
fn create_wrapper_function(
//_input_schema: &Schema,
//_returns_schema: &Option<ReturnType>,
method_info: &mut MethodInfo,
param_list: Vec<(FieldName, ParameterType)>,
func: &syn::ItemFn,
wrapper_ts: &mut TokenStream,
default_consts: &mut TokenStream,
is_async: bool,
streaming: bool,
) -> Result<Ident, Error> {
let api_func_name = Ident::new(
&format!("api_function_{}", &func.sig.ident),
func.sig.ident.span(),
&format!("api_function_{}", &method_info.func.sig.ident),
method_info.func.sig.ident.span(),
);
let mut body = TokenStream::new();
let mut args = TokenStream::new();
let func_uc = func.sig.ident.to_string().to_uppercase();
let func_uc = method_info.func.sig.ident.to_string().to_uppercase();
for (name, param) in param_list {
let span = name.span();
@ -507,69 +559,71 @@ fn create_wrapper_function(
&func_uc,
name,
span,
default_consts,
&mut method_info.default_consts,
)?;
}
}
}
// build the wrapping function:
let func_name = &func.sig.ident;
let func_name = &method_info.func.sig.ident;
let await_keyword = if is_async { Some(quote!(.await)) } else { None };
let await_keyword = if method_info.is_async {
Some(quote!(.await))
} else {
None
};
let question_mark = match func.sig.output {
let question_mark = match method_info.func.sig.output {
syn::ReturnType::Default => None,
_ => Some(quote!(?)),
};
let body = if streaming {
quote! {
if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
#body
let res = #func_name(#args) #await_keyword #question_mark;
let res: ::std::boxed::Box<dyn ::proxmox_router::SerializableReturn + Send> = ::std::boxed::Box::new(res);
Ok(res)
} else {
::anyhow::bail!("api function wrapper called with a non-object json value");
let body = match method_info.flavor {
MethodFlavor::Normal => {
quote! {
if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
#body
Ok(::serde_json::to_value(#func_name(#args) #await_keyword #question_mark)?)
} else {
::anyhow::bail!("api function wrapper called with a non-object json value");
}
}
}
} else {
quote! {
if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
#body
Ok(::serde_json::to_value(#func_name(#args) #await_keyword #question_mark)?)
MethodFlavor::Serializing => {
quote! {
if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
#body
let res = #func_name(#args) #await_keyword #question_mark;
let res: ::std::boxed::Box<dyn ::proxmox_router::SerializableReturn + Send> = ::std::boxed::Box::new(res);
Ok(res)
} else {
::anyhow::bail!("api function wrapper called with a non-object json value");
}
}
}
MethodFlavor::Streaming => {
let ty = if method_info.is_async {
quote! { ::proxmox_router::Stream }
} else {
::anyhow::bail!("api function wrapper called with a non-object json value");
quote! { ::proxmox_router::SyncStream }
};
quote! {
if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
#body
let res = #func_name(#args) #await_keyword #question_mark;
let res = #ty::from(res);
Ok(res)
} else {
::anyhow::bail!("api function wrapper called with a non-object json value");
}
}
}
};
match (streaming, is_async) {
(true, true) => {
wrapper_ts.extend(quote! {
fn #api_func_name<'a>(
mut input_params: ::serde_json::Value,
api_method_param: &'static ::proxmox_router::ApiMethod,
rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
) -> ::proxmox_router::StreamingApiFuture<'a> {
::std::boxed::Box::pin(async move { #body })
}
});
}
(true, false) => {
wrapper_ts.extend(quote! {
fn #api_func_name(
mut input_params: ::serde_json::Value,
api_method_param: &::proxmox_router::ApiMethod,
rpc_env_param: &mut dyn ::proxmox_router::RpcEnvironment,
) -> ::std::result::Result<::std::boxed::Box<dyn ::proxmox_router::SerializableReturn + Send>, ::anyhow::Error> {
#body
}
});
}
(false, true) => {
wrapper_ts.extend(quote! {
match (method_info.flavor, method_info.is_async) {
(MethodFlavor::Normal, true) => {
method_info.wrapper_ts.extend(quote! {
fn #api_func_name<'a>(
mut input_params: ::serde_json::Value,
api_method_param: &'static ::proxmox_router::ApiMethod,
@ -589,8 +643,8 @@ fn create_wrapper_function(
}
});
}
(false, false) => {
wrapper_ts.extend(quote! {
(MethodFlavor::Normal, false) => {
method_info.wrapper_ts.extend(quote! {
fn #api_func_name(
mut input_params: ::serde_json::Value,
api_method_param: &::proxmox_router::ApiMethod,
@ -600,6 +654,50 @@ fn create_wrapper_function(
}
});
}
(MethodFlavor::Serializing, true) => {
method_info.wrapper_ts.extend(quote! {
fn #api_func_name<'a>(
mut input_params: ::serde_json::Value,
api_method_param: &'static ::proxmox_router::ApiMethod,
rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
) -> ::proxmox_router::SerializingApiFuture<'a> {
::std::boxed::Box::pin(async move { #body })
}
});
}
(MethodFlavor::Serializing, false) => {
method_info.wrapper_ts.extend(quote! {
fn #api_func_name(
mut input_params: ::serde_json::Value,
api_method_param: &::proxmox_router::ApiMethod,
rpc_env_param: &mut dyn ::proxmox_router::RpcEnvironment,
) -> ::std::result::Result<::std::boxed::Box<dyn ::proxmox_router::SerializableReturn + Send>, ::anyhow::Error> {
#body
}
});
}
(MethodFlavor::Streaming, true) => {
method_info.wrapper_ts.extend(quote! {
fn #api_func_name<'a>(
mut input_params: ::serde_json::Value,
api_method_param: &'static ::proxmox_router::ApiMethod,
rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
) -> ::proxmox_router::StreamApiFuture<'a> {
::std::boxed::Box::pin(async move { #body })
}
});
}
(MethodFlavor::Streaming, false) => {
method_info.wrapper_ts.extend(quote! {
fn #api_func_name(
mut input_params: ::serde_json::Value,
api_method_param: &::proxmox_router::ApiMethod,
rpc_env_param: &mut dyn ::proxmox_router::RpcEnvironment,
) -> ::std::result::Result<::proxmox_router::SyncStream, ::anyhow::Error> {
#body
}
});
}
}
Ok(api_func_name)
@ -648,7 +746,7 @@ fn extract_normal_parameter(
});
}
let no_option_type = util::is_option_type(param.ty).is_none();
let no_option_type = util::is_option_type(&param.ty).is_none();
if let Some(def) = &default_value {
let name_uc = name.as_ident().to_string().to_uppercase();
@ -658,8 +756,9 @@ fn extract_normal_parameter(
);
// strip possible Option<> from this type:
let ty = util::is_option_type(param.ty).unwrap_or(param.ty);
let ty = util::is_option_type(&param.ty).unwrap_or(&param.ty);
default_consts.extend(quote_spanned! { span =>
#[allow(dead_code)]
pub const #name: #ty = #def;
});
@ -683,7 +782,7 @@ fn extract_normal_parameter(
body.extend(quote_spanned! { span => ; });
}
Some(flatten_span) => {
// Flattened parameter, we need ot use our special partial-object deserializer.
// Flattened parameter, we need to use our special partial-object deserializer.
// Also note that we do not support simply nesting schemas. We need a referenced type.
// Otherwise the expanded code here gets ugly and we'd need to make sure we pull out
// nested schemas into named variables first... No thanks.
@ -837,7 +936,7 @@ fn serialize_input_schema(
struct DefaultParameters<'a>(&'a Schema);
impl<'a> VisitMut for DefaultParameters<'a> {
impl VisitMut for DefaultParameters<'_> {
fn visit_expr_mut(&mut self, i: &mut syn::Expr) {
if let syn::Expr::Macro(exprmac) = i {
if exprmac.mac.path.is_ident("api_get_default") {
@ -857,7 +956,7 @@ impl<'a> VisitMut for DefaultParameters<'a> {
}
}
impl<'a> DefaultParameters<'a> {
impl DefaultParameters<'_> {
fn get_default(&self, param_tokens: TokenStream) -> Result<syn::Expr, syn::Error> {
let param_name: syn::LitStr = syn::parse2(param_tokens)?;
match self.0.find_obj_property_by_ident(&param_name.value()) {

Some files were not shown because too many files have changed in this diff Show More