From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 1BF816A27A for ; Wed, 24 Mar 2021 16:21:42 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id C2594C319 for ; Wed, 24 Mar 2021 16:21:10 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 8F957C204 for ; Wed, 24 Mar 2021 16:21:03 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 53A194647F for ; Wed, 24 Mar 2021 16:21:03 +0100 (CET) From: Stefan Reiter To: pbs-devel@lists.proxmox.com Date: Wed, 24 Mar 2021 16:18:15 +0100 Message-Id: <20210324151827.26200-9-s.reiter@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210324151827.26200-1-s.reiter@proxmox.com> References: <20210324151827.26200-1-s.reiter@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.022 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [auth.rs, rest.rs, server.rs] Subject: [pbs-devel] [PATCH v2 proxmox-backup 08/20] server/rest: extract auth to seperate module X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 24 Mar 2021 15:21:42 -0000 Signed-off-by: Stefan Reiter --- src/server.rs | 2 + src/server/auth.rs | 102 +++++++++++++++++++++++++++++++++++++++++++++ src/server/rest.rs | 93 +---------------------------------------- 3 files changed, 105 insertions(+), 92 deletions(-) create mode 100644 src/server/auth.rs diff --git a/src/server.rs b/src/server.rs index 7c159c23..b6a37b92 100644 --- a/src/server.rs +++ b/src/server.rs @@ -89,3 +89,5 @@ mod report; pub use report::*; pub mod ticket; + +pub mod auth; diff --git a/src/server/auth.rs b/src/server/auth.rs new file mode 100644 index 00000000..24151886 --- /dev/null +++ b/src/server/auth.rs @@ -0,0 +1,102 @@ +//! Provides authentication primitives for the HTTP server +use anyhow::{bail, format_err, Error}; + +use crate::tools::ticket::Ticket; +use crate::auth_helpers::*; +use crate::tools; +use crate::config::cached_user_info::CachedUserInfo; +use crate::api2::types::{Authid, Userid}; + +use hyper::header; +use percent_encoding::percent_decode_str; + +pub struct UserAuthData { + ticket: String, + csrf_token: Option, +} + +pub enum AuthData { + User(UserAuthData), + ApiToken(String), +} + +pub fn extract_auth_data(headers: &http::HeaderMap) -> Option { + if let Some(raw_cookie) = headers.get(header::COOKIE) { + if let Ok(cookie) = raw_cookie.to_str() { + if let Some(ticket) = tools::extract_cookie(cookie, "PBSAuthCookie") { + let csrf_token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) { + Some(Ok(v)) => Some(v.to_owned()), + _ => None, + }; + return Some(AuthData::User(UserAuthData { + ticket, + csrf_token, + })); + } + } + } + + match headers.get(header::AUTHORIZATION).map(|v| v.to_str()) { + Some(Ok(v)) => { + if v.starts_with("PBSAPIToken ") || v.starts_with("PBSAPIToken=") { + Some(AuthData::ApiToken(v["PBSAPIToken ".len()..].to_owned())) + } else { + None + } + }, + _ => None, + } +} + +pub fn check_auth( + method: &hyper::Method, + auth_data: &AuthData, + user_info: &CachedUserInfo, +) -> Result { + match auth_data { + AuthData::User(user_auth_data) => { + let ticket = user_auth_data.ticket.clone(); + let ticket_lifetime = tools::ticket::TICKET_LIFETIME; + + let userid: Userid = Ticket::::parse(&ticket)? + .verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)? + .require_full()?; + + let auth_id = Authid::from(userid.clone()); + if !user_info.is_active_auth_id(&auth_id) { + bail!("user account disabled or expired."); + } + + if method != hyper::Method::GET { + if let Some(csrf_token) = &user_auth_data.csrf_token { + verify_csrf_prevention_token(csrf_secret(), &userid, &csrf_token, -300, ticket_lifetime)?; + } else { + bail!("missing CSRF prevention token"); + } + } + + Ok(auth_id) + }, + AuthData::ApiToken(api_token) => { + let mut parts = api_token.splitn(2, ':'); + let tokenid = parts.next() + .ok_or_else(|| format_err!("failed to split API token header"))?; + let tokenid: Authid = tokenid.parse()?; + + if !user_info.is_active_auth_id(&tokenid) { + bail!("user account or token disabled or expired."); + } + + let tokensecret = parts.next() + .ok_or_else(|| format_err!("failed to split API token header"))?; + let tokensecret = percent_decode_str(tokensecret) + .decode_utf8() + .map_err(|_| format_err!("failed to decode API token header"))?; + + crate::config::token_shadow::verify_secret(&tokenid, &tokensecret)?; + + Ok(tokenid) + } + } +} + diff --git a/src/server/rest.rs b/src/server/rest.rs index 44e157fb..2169766a 100644 --- a/src/server/rest.rs +++ b/src/server/rest.rs @@ -17,7 +17,6 @@ use lazy_static::lazy_static; use serde_json::{json, Value}; use tokio::fs::File; use tokio::time::Instant; -use percent_encoding::percent_decode_str; use url::form_urlencoded; use regex::Regex; @@ -42,12 +41,12 @@ use proxmox::api::schema::{ use super::environment::RestEnvironment; use super::formatter::*; use super::ApiConfig; +use super::auth::{check_auth, extract_auth_data}; use crate::auth_helpers::*; use crate::api2::types::{Authid, Userid}; use crate::tools; use crate::tools::FileLogger; -use crate::tools::ticket::Ticket; use crate::config::cached_user_info::CachedUserInfo; extern "C" { fn tzset(); } @@ -574,96 +573,6 @@ fn extract_lang_header(headers: &http::HeaderMap) -> Option { None } -struct UserAuthData{ - ticket: String, - csrf_token: Option, -} - -enum AuthData { - User(UserAuthData), - ApiToken(String), -} - -fn extract_auth_data(headers: &http::HeaderMap) -> Option { - if let Some(raw_cookie) = headers.get(header::COOKIE) { - if let Ok(cookie) = raw_cookie.to_str() { - if let Some(ticket) = tools::extract_cookie(cookie, "PBSAuthCookie") { - let csrf_token = match headers.get("CSRFPreventionToken").map(|v| v.to_str()) { - Some(Ok(v)) => Some(v.to_owned()), - _ => None, - }; - return Some(AuthData::User(UserAuthData { - ticket, - csrf_token, - })); - } - } - } - - match headers.get(header::AUTHORIZATION).map(|v| v.to_str()) { - Some(Ok(v)) => { - if v.starts_with("PBSAPIToken ") || v.starts_with("PBSAPIToken=") { - Some(AuthData::ApiToken(v["PBSAPIToken ".len()..].to_owned())) - } else { - None - } - }, - _ => None, - } -} - -fn check_auth( - method: &hyper::Method, - auth_data: &AuthData, - user_info: &CachedUserInfo, -) -> Result { - match auth_data { - AuthData::User(user_auth_data) => { - let ticket = user_auth_data.ticket.clone(); - let ticket_lifetime = tools::ticket::TICKET_LIFETIME; - - let userid: Userid = Ticket::::parse(&ticket)? - .verify_with_time_frame(public_auth_key(), "PBS", None, -300..ticket_lifetime)? - .require_full()?; - - let auth_id = Authid::from(userid.clone()); - if !user_info.is_active_auth_id(&auth_id) { - bail!("user account disabled or expired."); - } - - if method != hyper::Method::GET { - if let Some(csrf_token) = &user_auth_data.csrf_token { - verify_csrf_prevention_token(csrf_secret(), &userid, &csrf_token, -300, ticket_lifetime)?; - } else { - bail!("missing CSRF prevention token"); - } - } - - Ok(auth_id) - }, - AuthData::ApiToken(api_token) => { - let mut parts = api_token.splitn(2, ':'); - let tokenid = parts.next() - .ok_or_else(|| format_err!("failed to split API token header"))?; - let tokenid: Authid = tokenid.parse()?; - - if !user_info.is_active_auth_id(&tokenid) { - bail!("user account or token disabled or expired."); - } - - let tokensecret = parts.next() - .ok_or_else(|| format_err!("failed to split API token header"))?; - let tokensecret = percent_decode_str(tokensecret) - .decode_utf8() - .map_err(|_| format_err!("failed to decode API token header"))?; - - crate::config::token_shadow::verify_secret(&tokenid, &tokensecret)?; - - Ok(tokenid) - } - } -} - async fn handle_request( api: Arc, req: Request, -- 2.20.1