From: "Fabian Grünbichler" <f.gruenbichler@proxmox.com>
To: Proxmox Backup Server development discussion
<pbs-devel@lists.proxmox.com>
Subject: [pbs-devel] NACK: [PATCH backup] check only user part of backup owner for comparisons
Date: Wed, 16 Oct 2024 09:03:00 +0200 [thread overview]
Message-ID: <1729061622.yvcb60ha1s.astroid@yuna.none> (raw)
In-Reply-To: <20241015133108.345076-1-m.sandoval@proxmox.com>
this is wrong, and I already told you that before you wrote the patch?
quoting myself:
> different tokens should be handed out to different systems, and system
> A should not be able to access backups made by system B unless it is
> highly privileged
the existing check is intentional and correct.
there are other issues surrounding ownership that we might want to
tackle:
- (optionally?) remove owner when removing last snapshot in a group[0]
- list groups without snapshots on the UI (requires restructuring the
whole datastore browser, which might be nice anyway for performance
reasons - e.g., we could have a streaming browser or a "browser
session" similar to the backup/reader ones)
- multiple owners
[0]: the two issues you linked
On October 15, 2024 3:31 pm, Maximiliano Sandoval wrote:
> Prevents the errors:
>
> INFO: Error: backup owner check failed (user@domain!first != user@domain!second)
>
> when making backups of a guest using different tokens. This can for
> example happen when the user has to generate a new token.
>
> Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
> ---
>
> I am not entirely sure if this proposed behavior is a desirable one. The docs
> [1] don't mention any token.
>
> I run into this issue multiple times a week and bugs like [2, 3] make it
> (changing the owner of a dozen backups) more cumbersome that it already is.
>
> [1] https://pbs.proxmox.com/docs/backup-client.html#changing-the-owner-of-a-backup-group
> [2] https://bugzilla.proxmox.com/show_bug.cgi?id=5621
> [3] https://bugzilla.proxmox.com/show_bug.cgi?id=3336
>
>
> pbs-datastore/src/datastore.rs | 62 +++++++++++++++++++++++++++++++---
> src/api2/backup/mod.rs | 4 +--
> src/api2/reader/mod.rs | 10 ++++--
> 3 files changed, 66 insertions(+), 10 deletions(-)
>
> diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs
> index d0f3c53ac..e84139eb5 100644
> --- a/pbs-datastore/src/datastore.rs
> +++ b/pbs-datastore/src/datastore.rs
> @@ -35,13 +35,15 @@ use crate::DataBlob;
> static DATASTORE_MAP: LazyLock<Mutex<HashMap<String, Arc<DataStoreImpl>>>> =
> LazyLock::new(|| Mutex::new(HashMap::new()));
>
> -/// checks if auth_id is owner, or, if owner is a token, if
> -/// auth_id is the user of the token
> +/// checks if auth_id's user matches the owner's user.
> pub fn check_backup_owner(owner: &Authid, auth_id: &Authid) -> Result<(), Error> {
> - let correct_owner =
> - owner == auth_id || (owner.is_token() && &Authid::from(owner.user().clone()) == auth_id);
> + let correct_owner = owner.user() == auth_id.user();
> if !correct_owner {
> - bail!("backup owner check failed ({} != {})", auth_id, owner);
> + bail!(
> + "backup owner check failed ({} != {})",
> + auth_id.user(),
> + owner.user()
> + );
> }
> Ok(())
> }
> @@ -1466,3 +1468,53 @@ impl DataStore {
> Ok(())
> }
> }
> +
> +#[cfg(test)]
> +mod test {
> + use std::str::FromStr;
> +
> + use pbs_api_types::{Tokenname, Userid};
> +
> + use super::*;
> +
> + #[test]
> + fn test_check_backup_owner() {
> + let user_a = Userid::from_str("user-a@some-realm").unwrap();
> + let user_b = Userid::from_str("user-b@some-realm").unwrap();
> + let token_a = Tokenname::try_from("token-a".to_string()).unwrap();
> + let token_b = Tokenname::try_from("token-b".to_string()).unwrap();
> +
> + // Same users.
> + assert!(check_backup_owner(
> + &Authid::from((user_a.clone(), Some(token_a.clone()))),
> + &Authid::from((user_a.clone(), Some(token_b.clone())))
> + )
> + .is_ok());
> + assert!(check_backup_owner(
> + &Authid::from((user_a.clone(), Some(token_a.clone()))),
> + &Authid::from((user_a.clone(), None))
> + )
> + .is_ok());
> + assert!(check_backup_owner(
> + &Authid::from((user_a.clone(), None)),
> + &Authid::from((user_a.clone(), None))
> + )
> + .is_ok());
> +
> + // Different users.
> + assert!(check_backup_owner(
> + &Authid::from((user_a.clone(), Some(token_a.clone()))),
> + &Authid::from((user_b.clone(), Some(token_b)))
> + )
> + .is_err());
> + assert!(check_backup_owner(
> + &Authid::from((user_a.clone(), Some(token_a))),
> + &Authid::from((user_b.clone(), None))
> + )
> + .is_err());
> + assert!(
> + check_backup_owner(&Authid::from((user_a, None)), &Authid::from((user_b, None)))
> + .is_err()
> + );
> + }
> +}
> diff --git a/src/api2/backup/mod.rs b/src/api2/backup/mod.rs
> index ea0d0292e..8e4cd297f 100644
> --- a/src/api2/backup/mod.rs
> +++ b/src/api2/backup/mod.rs
> @@ -149,10 +149,10 @@ fn upgrade_to_backup_protocol(
>
> // permission check
> let correct_owner =
> - owner == auth_id || (owner.is_token() && Authid::from(owner.user().clone()) == auth_id);
> + owner.user() == auth_id.user();
> if !correct_owner && worker_type != "benchmark" {
> // only the owner is allowed to create additional snapshots
> - bail!("backup owner check failed ({} != {})", auth_id, owner);
> + bail!("backup owner check failed ({} != {})", auth_id.user(), owner.user());
but this
> }
>
> let last_backup = {
> diff --git a/src/api2/reader/mod.rs b/src/api2/reader/mod.rs
> index 23051653e..2d192a979 100644
> --- a/src/api2/reader/mod.rs
> +++ b/src/api2/reader/mod.rs
> @@ -119,10 +119,14 @@ fn upgrade_to_backup_reader_protocol(
> let backup_dir = datastore.backup_dir(backup_ns, backup_dir)?;
> if !priv_read {
> let owner = backup_dir.get_owner()?;
> - let correct_owner = owner == auth_id
> - || (owner.is_token() && Authid::from(owner.user().clone()) == auth_id);
> + let correct_owner = owner.user() == auth_id.user();
> if !correct_owner {
> - bail!("backup owner check failed!");
> + // only the owner is allowed to create additional snapshots
> + bail!(
> + "backup owner check failed ({} != {})",
> + auth_id.user(),
> + owner.user()
> + );
and this could be replaced with a call to the helper, so that there's a
single place where that is implemented?
> }
> }
>
> --
> 2.39.5
>
>
>
> _______________________________________________
> pbs-devel mailing list
> pbs-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>
>
>
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
prev parent reply other threads:[~2024-10-16 7:02 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-10-15 13:31 [pbs-devel] " Maximiliano Sandoval
2024-10-16 7:03 ` Fabian Grünbichler [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1729061622.yvcb60ha1s.astroid@yuna.none \
--to=f.gruenbichler@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox