From d97ff8ae2a2d92d45ac0e0cee5ed7e2348afc786 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Wed, 1 Feb 2023 16:01:12 +0100 Subject: [PATCH] use new auth api crate Signed-off-by: Wolfgang Bumiller Signed-off-by: Thomas Lamprecht --- Cargo.toml | 8 +- debian/control | 10 +- pbs-api-types/Cargo.toml | 1 + pbs-api-types/src/lib.rs | 18 +- pbs-client/Cargo.toml | 2 +- pbs-client/src/http_client.rs | 4 +- pbs-ticket/Cargo.toml | 14 -- pbs-ticket/src/lib.rs | 332 -------------------------------- src/api2/access/mod.rs | 223 +-------------------- src/api2/access/openid.rs | 7 +- src/api2/access/tfa.rs | 2 +- src/api2/access/user.rs | 2 +- src/api2/node/mod.rs | 23 ++- src/auth.rs | 240 +++++++++++++++-------- src/auth_helpers.rs | 2 + src/bin/proxmox-backup-api.rs | 2 + src/bin/proxmox-backup-proxy.rs | 3 +- src/client_helpers.rs | 8 +- src/config/tfa.rs | 55 ++---- src/lib.rs | 2 +- src/server/auth.rs | 110 +---------- src/server/mod.rs | 2 - src/server/ticket.rs | 68 ------- 23 files changed, 232 insertions(+), 906 deletions(-) delete mode 100644 pbs-ticket/Cargo.toml delete mode 100644 pbs-ticket/src/lib.rs delete mode 100644 src/server/ticket.rs diff --git a/Cargo.toml b/Cargo.toml index 4d016e3b..373b5018 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ members = [ "pbs-key-config", "pbs-pxar-fuse", "pbs-tape", - "pbs-ticket", "pbs-tools", "proxmox-backup-banner", @@ -56,6 +55,7 @@ path = "src/lib.rs" [workspace.dependencies] # proxmox workspace proxmox-async = "0.4" +proxmox-auth-api = "0.1" proxmox-borrow = "1" proxmox-compression = "0.1.1" proxmox-fuse = "0.1.3" @@ -75,7 +75,7 @@ proxmox-shared-memory = "0.2.3" proxmox-sortable-macro = "0.1.2" proxmox-subscription = { version = "0.3", features = [ "api-types" ] } proxmox-sys = "0.4.2" -proxmox-tfa = { version = "2.1", features = [ "api", "api-types" ] } +proxmox-tfa = { version = "3", features = [ "api", "api-types" ] } proxmox-time = "1.1.2" proxmox-uuid = "1" @@ -96,7 +96,6 @@ pbs-fuse-loop = { path = "pbs-fuse-loop" } pbs-key-config = { path = "pbs-key-config" } pbs-pxar-fuse = { path = "pbs-pxar-fuse" } pbs-tape = { path = "pbs-tape" } -pbs-ticket = { path = "pbs-ticket" } pbs-tools = { path = "pbs-tools" } proxmox-rrd = { path = "proxmox-rrd" } @@ -203,6 +202,7 @@ zstd.workspace = true #valgrind_request = { git = "https://github.com/edef1c/libvalgrind_request", version = "1.1.0", optional = true } proxmox-async.workspace = true +proxmox-auth-api = { workspace = true, features = [ "api", "pam-authenticator" ] } proxmox-compression.workspace = true proxmox-http = { workspace = true, features = [ "client-trait", "proxmox-async", "rate-limited-stream" ] } # pbs-client doesn't use these proxmox-io.workspace = true @@ -235,7 +235,6 @@ pbs-config.workspace = true pbs-datastore.workspace = true pbs-key-config.workspace = true pbs-tape.workspace = true -pbs-ticket.workspace = true pbs-tools.workspace = true proxmox-rrd.workspace = true @@ -244,6 +243,7 @@ proxmox-rrd.workspace = true [patch.crates-io] #proxmox-acme-rs = { path = "../proxmox-acme-rs" } #proxmox-async = { path = "../proxmox/proxmox-async" } +#proxmox-auth-api = { path = "../proxmox/proxmox-auth-api" } #proxmox-borrow = { path = "../proxmox/proxmox-borrow" } #proxmox-compression = { path = "../proxmox/proxmox-compression" } #proxmox-fuse = { path = "../proxmox-fuse" } diff --git a/debian/control b/debian/control index 461e2371..286922b5 100644 --- a/debian/control +++ b/debian/control @@ -44,6 +44,10 @@ Build-Depends: debhelper (>= 12), librust-proxmox-acme-rs-0.4+default-dev, librust-proxmox-apt-0.9+default-dev, librust-proxmox-async-0.4+default-dev, + librust-proxmox-auth-api-0.1+api-dev, + librust-proxmox-auth-api-0.1+api-types-dev, + librust-proxmox-auth-api-0.1+default-dev, + librust-proxmox-auth-api-0.1+pam-authenticator-dev, librust-proxmox-borrow-1+default-dev, librust-proxmox-compression-0.1+default-dev (>= 0.1.1-~~), librust-proxmox-fuse-0.1+default-dev (>= 0.1.3-~~), @@ -81,9 +85,9 @@ Build-Depends: debhelper (>= 12), librust-proxmox-sys-0.4+default-dev (>= 0.4.2-~~), librust-proxmox-sys-0.4+logrotate-dev (>= 0.4.2-~~), librust-proxmox-sys-0.4+timer-dev (>= 0.4.2-~~), - librust-proxmox-tfa-2+api-dev (>= 2.1-~~), - librust-proxmox-tfa-2+api-types-dev (>= 2.1-~~), - librust-proxmox-tfa-2+default-dev (>= 2.1-~~), + librust-proxmox-tfa-3+api-dev, + librust-proxmox-tfa-3+api-types-dev, + librust-proxmox-tfa-3+default-dev, librust-proxmox-time-1+default-dev (>= 1.1.2-~~), librust-proxmox-uuid-1+default-dev, librust-proxmox-uuid-1+serde-dev, diff --git a/pbs-api-types/Cargo.toml b/pbs-api-types/Cargo.toml index 4020fc20..b0092813 100644 --- a/pbs-api-types/Cargo.toml +++ b/pbs-api-types/Cargo.toml @@ -14,6 +14,7 @@ regex.workspace = true serde.workspace = true serde_plain.workspace = true +proxmox-auth-api = { workspace = true, features = [ "api-types" ] } proxmox-lang.workspace=true proxmox-schema = { workspace = true, features = [ "api-macro" ] } proxmox-serde.workspace = true diff --git a/pbs-api-types/src/lib.rs b/pbs-api-types/src/lib.rs index 0479b637..ec8b1f34 100644 --- a/pbs-api-types/src/lib.rs +++ b/pbs-api-types/src/lib.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; +use proxmox_auth_api::{APITOKEN_ID_REGEX_STR, USER_ID_REGEX_STR}; + pub mod common_regex; pub mod percent_encoding; @@ -85,14 +87,14 @@ pub use maintenance::*; mod network; pub use network::*; -#[macro_use] -mod userid; -pub use userid::Authid; -pub use userid::Userid; -pub use userid::{Realm, RealmRef}; -pub use userid::{Tokenname, TokennameRef}; -pub use userid::{Username, UsernameRef}; -pub use userid::{PROXMOX_GROUP_ID_SCHEMA, PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN_NAME_SCHEMA}; +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; diff --git a/pbs-client/Cargo.toml b/pbs-client/Cargo.toml index dd22e7a0..2b16150c 100644 --- a/pbs-client/Cargo.toml +++ b/pbs-client/Cargo.toml @@ -34,6 +34,7 @@ xdg.workspace = true pathpatterns.workspace = true proxmox-async.workspace = true +proxmox-auth-api.workspace = true proxmox-compression.workspace = true proxmox-http = { workspace = true, features = [ "rate-limiter" ] } proxmox-io = { workspace = true, features = [ "tokio" ] } @@ -48,5 +49,4 @@ pxar.workspace = true pbs-api-types.workspace = true pbs-buildcfg.workspace = true pbs-datastore.workspace = true -pbs-ticket.workspace = true pbs-tools.workspace = true diff --git a/pbs-client/src/http_client.rs b/pbs-client/src/http_client.rs index 4c1f7919..30af5966 100644 --- a/pbs-client/src/http_client.rs +++ b/pbs-client/src/http_client.rs @@ -249,7 +249,7 @@ fn store_ticket_info( let mut new_data = json!({}); - let ticket_lifetime = pbs_ticket::TICKET_LIFETIME - 60; + let ticket_lifetime = proxmox_auth_api::TICKET_LIFETIME - 60; let empty = serde_json::map::Map::new(); for (server, info) in data.as_object().unwrap_or(&empty) { @@ -280,7 +280,7 @@ fn load_ticket_info(prefix: &str, server: &str, userid: &Userid) -> Option<(Stri let path = base.place_runtime_file("tickets").ok()?; let data = file_get_json(&path, None).ok()?; let now = proxmox_time::epoch_i64(); - let ticket_lifetime = pbs_ticket::TICKET_LIFETIME - 60; + let ticket_lifetime = proxmox_auth_api::TICKET_LIFETIME - 60; let uinfo = data[server][userid.as_str()].as_object()?; let timestamp = uinfo["timestamp"].as_i64()?; let age = now - timestamp; diff --git a/pbs-ticket/Cargo.toml b/pbs-ticket/Cargo.toml deleted file mode 100644 index afe99b91..00000000 --- a/pbs-ticket/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "pbs-ticket" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -description = "pbs ticket handling" - -[dependencies] -anyhow.workspace = true -base64.workspace = true -openssl.workspace = true -percent-encoding.workspace = true - -proxmox-time.workspace = true diff --git a/pbs-ticket/src/lib.rs b/pbs-ticket/src/lib.rs deleted file mode 100644 index fcb88ff8..00000000 --- a/pbs-ticket/src/lib.rs +++ /dev/null @@ -1,332 +0,0 @@ -//! Generate and verify Authentication tickets - -use std::borrow::Cow; -use std::io; -use std::marker::PhantomData; - -use anyhow::{bail, format_err, Error}; -use openssl::hash::MessageDigest; -use openssl::pkey::{HasPublic, PKey, Private}; -use openssl::sign::{Signer, Verifier}; -use percent_encoding::{percent_decode_str, percent_encode, AsciiSet}; - -pub const TICKET_LIFETIME: i64 = 3600 * 2; // 2 hours - -pub const TERM_PREFIX: &str = "PBSTERM"; - -/// Stringified ticket data must not contain colons... -const TICKET_ASCIISET: &AsciiSet = &percent_encoding::CONTROLS.add(b':'); - -/// An empty type implementing [`ToString`] and [`FromStr`](std::str::FromStr), used for tickets -/// with no data. -pub struct Empty; - -impl ToString for Empty { - fn to_string(&self) -> String { - String::new() - } -} - -impl std::str::FromStr for Empty { - type Err = Error; - - fn from_str(s: &str) -> Result { - if !s.is_empty() { - bail!("unexpected ticket data, should be empty"); - } - Ok(Empty) - } -} - -/// An API ticket consists of a ticket type (prefix), type-dependent data, optional additional -/// authenticaztion data, a timestamp and a signature. We store these values in the form -/// `::::`. -/// -/// The signature is made over the string consisting of prefix, data, timestamp and aad joined -/// together by colons. If there is no additional authentication data it will be skipped together -/// with the colon separating it from the timestamp. -pub struct Ticket -where - T: ToString + std::str::FromStr, -{ - prefix: Cow<'static, str>, - data: String, - time: i64, - signature: Option>, - _type_marker: PhantomData T>, -} - -impl Ticket -where - T: ToString + std::str::FromStr, - ::Err: std::fmt::Debug, -{ - /// Prepare a new ticket for signing. - pub fn new(prefix: &'static str, data: &T) -> Result { - Ok(Self { - prefix: Cow::Borrowed(prefix), - data: data.to_string(), - time: proxmox_time::epoch_i64(), - signature: None, - _type_marker: PhantomData, - }) - } - - /// Get the ticket prefix. - pub fn prefix(&self) -> &str { - &self.prefix - } - - /// Get the ticket's time stamp in seconds since the unix epoch. - pub fn time(&self) -> i64 { - self.time - } - - /// Get the raw string data contained in the ticket. The `verify` method will call `parse()` - /// this in the end, so using this method directly is discouraged as it does not verify the - /// signature. - pub fn raw_data(&self) -> &str { - &self.data - } - - /// Serialize the ticket into a writer. - /// - /// This only writes a string. We use `io::write` instead of `fmt::Write` so we can reuse the - /// same function for openssl's `Verify`, which only implements `io::Write`. - fn write_data(&self, f: &mut dyn io::Write) -> Result<(), Error> { - write!( - f, - "{}:{}:{:08X}", - percent_encode(self.prefix.as_bytes(), TICKET_ASCIISET), - percent_encode(self.data.as_bytes(), TICKET_ASCIISET), - self.time, - ) - .map_err(Error::from) - } - - /// Write additional authentication data to the verifier. - fn write_aad(f: &mut dyn io::Write, aad: Option<&str>) -> Result<(), Error> { - if let Some(aad) = aad { - write!(f, ":{}", percent_encode(aad.as_bytes(), TICKET_ASCIISET))?; - } - Ok(()) - } - - /// Change the ticket's time, used mostly for testing. - #[cfg(test)] - fn change_time(&mut self, time: i64) -> &mut Self { - self.time = time; - self - } - - /// Sign the ticket. - pub fn sign(&mut self, keypair: &PKey, aad: Option<&str>) -> Result { - let mut output = Vec::::new(); - let mut signer = Signer::new(MessageDigest::sha256(), keypair) - .map_err(|err| format_err!("openssl error creating signer for ticket: {}", err))?; - - self.write_data(&mut output) - .map_err(|err| format_err!("error creating ticket: {}", err))?; - - signer - .update(&output) - .map_err(Error::from) - .and_then(|()| Self::write_aad(&mut signer, aad)) - .map_err(|err| format_err!("error signing ticket: {}", err))?; - - // See `Self::write_data` for why this is safe - let mut output = unsafe { String::from_utf8_unchecked(output) }; - - let signature = signer - .sign_to_vec() - .map_err(|err| format_err!("error finishing ticket signature: {}", err))?; - - use std::fmt::Write; - write!( - &mut output, - "::{}", - base64::encode_config(&signature, base64::STANDARD_NO_PAD), - )?; - - self.signature = Some(signature); - - Ok(output) - } - - /// `verify` with an additional time frame parameter, not usually required since we always use - /// the same time frame. - pub fn verify_with_time_frame( - &self, - keypair: &PKey

, - prefix: &str, - aad: Option<&str>, - time_frame: std::ops::Range, - ) -> Result { - if self.prefix != prefix { - bail!("ticket with invalid prefix"); - } - - let signature = match self.signature.as_ref() { - Some(sig) => sig, - None => bail!("invalid ticket without signature"), - }; - - let age = proxmox_time::epoch_i64() - self.time; - if age < time_frame.start { - bail!("invalid ticket - timestamp newer than expected"); - } - if age > time_frame.end { - bail!("invalid ticket - expired"); - } - - let mut verifier = Verifier::new(MessageDigest::sha256(), keypair)?; - - self.write_data(&mut verifier) - .and_then(|()| Self::write_aad(&mut verifier, aad)) - .map_err(|err| format_err!("error verifying ticket: {}", err))?; - - let is_valid: bool = verifier - .verify(signature) - .map_err(|err| format_err!("openssl error verifying ticket: {}", err))?; - - if !is_valid { - bail!("ticket with invalid signature"); - } - - self.data - .parse() - .map_err(|err| format_err!("failed to parse contained ticket data: {:?}", err)) - } - - /// Verify the ticket with the provided key pair. The additional authentication data needs to - /// match the one used when generating the ticket, and the ticket's age must fall into the time - /// frame. - pub fn verify( - &self, - keypair: &PKey

, - prefix: &str, - aad: Option<&str>, - ) -> Result { - self.verify_with_time_frame(keypair, prefix, aad, -300..TICKET_LIFETIME) - } - - /// Parse a ticket string. - pub fn parse(ticket: &str) -> Result { - let mut parts = ticket.splitn(4, ':'); - - let prefix = percent_decode_str( - parts - .next() - .ok_or_else(|| format_err!("ticket without prefix"))?, - ) - .decode_utf8() - .map_err(|err| format_err!("invalid ticket, error decoding prefix: {}", err))?; - - let data = percent_decode_str( - parts - .next() - .ok_or_else(|| format_err!("ticket without data"))?, - ) - .decode_utf8() - .map_err(|err| format_err!("invalid ticket, error decoding data: {}", err))?; - - let time = i64::from_str_radix( - parts - .next() - .ok_or_else(|| format_err!("ticket without timestamp"))?, - 16, - ) - .map_err(|err| format_err!("ticket with bad timestamp: {}", err))?; - - let remainder = parts - .next() - .ok_or_else(|| format_err!("ticket without signature"))?; - // ::