1 use std
::collections
::HashMap
;
3 use anyhow
::{bail, format_err, Error}
;
4 use serde
::{Serialize, Deserialize}
;
5 use serde_json
::{from_value, Value}
;
7 use proxmox
::tools
::fs
::CreateOptions
;
9 use crate::api2
::types
::Authid
;
11 use pbs_config
::open_backup_lockfile
;
13 const LOCK_FILE
: &str = pbs_buildcfg
::configdir
!("/token.shadow.lock");
14 const CONF_FILE
: &str = pbs_buildcfg
::configdir
!("/token.shadow");
16 #[derive(Serialize, Deserialize)]
17 #[serde(rename_all="kebab-case")]
18 /// ApiToken id / secret pair
19 pub struct ApiTokenSecret
{
24 fn read_file() -> Result
<HashMap
<Authid
, String
>, Error
> {
25 let json
= proxmox
::tools
::fs
::file_get_json(CONF_FILE
, Some(Value
::Null
))?
;
27 if json
== Value
::Null
{
30 // swallow serde error which might contain sensitive data
31 from_value(json
).map_err(|_err
| format_err
!("unable to parse '{}'", CONF_FILE
))
35 fn write_file(data
: HashMap
<Authid
, String
>) -> Result
<(), Error
> {
36 let backup_user
= pbs_config
::backup_user()?
;
37 let options
= CreateOptions
::new()
38 .perm(nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640))
39 .owner(backup_user
.uid
)
40 .group(backup_user
.gid
);
42 let json
= serde_json
::to_vec(&data
)?
;
43 proxmox
::tools
::fs
::replace_file(CONF_FILE
, &json
, options
)
46 /// Verifies that an entry for given tokenid / API token secret exists
47 pub fn verify_secret(tokenid
: &Authid
, secret
: &str) -> Result
<(), Error
> {
48 if !tokenid
.is_token() {
49 bail
!("not an API token ID");
52 let data
= read_file()?
;
53 match data
.get(tokenid
) {
54 Some(hashed_secret
) => {
55 auth
::verify_crypt_pw(secret
, &hashed_secret
)
57 None
=> bail
!("invalid API token"),
61 /// Adds a new entry for the given tokenid / API token secret. The secret is stored as salted hash.
62 pub fn set_secret(tokenid
: &Authid
, secret
: &str) -> Result
<(), Error
> {
63 if !tokenid
.is_token() {
64 bail
!("not an API token ID");
67 let _guard
= open_backup_lockfile(LOCK_FILE
, None
, true)?
;
69 let mut data
= read_file()?
;
70 let hashed_secret
= auth
::encrypt_pw(secret
)?
;
71 data
.insert(tokenid
.clone(), hashed_secret
);
77 /// Deletes the entry for the given tokenid.
78 pub fn delete_secret(tokenid
: &Authid
) -> Result
<(), Error
> {
79 if !tokenid
.is_token() {
80 bail
!("not an API token ID");
83 let _guard
= open_backup_lockfile(LOCK_FILE
, None
, true)?
;
85 let mut data
= read_file()?
;