public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Shannon Sterz <s.sterz@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox-backup 3/3] client: adapt pbs client to also handle http-only flows correctly
Date: Thu, 10 Jul 2025 15:50:10 +0200	[thread overview]
Message-ID: <20250710135010.305861-5-s.sterz@proxmox.com> (raw)
In-Reply-To: <20250710135010.305861-1-s.sterz@proxmox.com>

if we decide to make the http-only flow opt-out or remove the previous
authentication flow entirely, prepare the client to speak to servers
that only support http-only cookie tickets

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 pbs-client/src/http_client.rs | 70 ++++++++++++++++++++++++++++++++---
 1 file changed, 65 insertions(+), 5 deletions(-)

diff --git a/pbs-client/src/http_client.rs b/pbs-client/src/http_client.rs
index 0b415cac..f6aedda1 100644
--- a/pbs-client/src/http_client.rs
+++ b/pbs-client/src/http_client.rs
@@ -7,6 +7,7 @@ use bytes::Bytes;
 use futures::*;
 use http_body_util::{BodyDataStream, BodyExt};
 use hyper::body::Incoming;
+use hyper::header::SET_COOKIE;
 use hyper::http::header::HeaderValue;
 use hyper::http::Uri;
 use hyper::http::{Request, Response};
@@ -111,11 +112,16 @@ mod resolver {
 /// certain error conditions. Keep it generous, to avoid false-positive under high load.
 const HTTP_TIMEOUT: Duration = Duration::from_secs(2 * 60);
 
+const PROXMOX_BACKUP_AUTH_COOKIE: &str = "PBSAuthCookie";
+const PROXMOX_BACKUP_PREFIXED_AUTH_COOKIE: &str = "__Host-PBSAuthCookie";
+
 #[derive(Clone)]
 pub struct AuthInfo {
     pub auth_id: Authid,
     pub ticket: String,
     pub token: String,
+    // Whether the server uses HttpOnly cookies for authentication
+    pub http_only: bool,
 }
 
 pub struct HttpClientOptions {
@@ -504,6 +510,7 @@ impl HttpClient {
             auth_id: auth_id.clone(),
             ticket: password.clone(),
             token: "".to_string(),
+            http_only: false,
         }));
 
         let server2 = server.to_string();
@@ -725,10 +732,18 @@ impl HttpClient {
                 HeaderValue::from_str(&enc_api_token).unwrap(),
             );
         } else {
+            let cookie_name = if auth.http_only {
+                // server has switched to http only flow, provide ticket in properly prefixed cookie
+                PROXMOX_BACKUP_PREFIXED_AUTH_COOKIE
+            } else {
+                PROXMOX_BACKUP_AUTH_COOKIE
+            };
+
             let enc_ticket = format!(
-                "PBSAuthCookie={}",
+                "{cookie_name}={}",
                 percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET)
             );
+
             req.headers_mut()
                 .insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap());
             req.headers_mut().insert(
@@ -767,10 +782,18 @@ impl HttpClient {
 
         let auth = self.login().await?;
 
+        let cookie_name = if auth.http_only {
+            // server has switched to http only flow, provide ticket in properly prefixed cookie
+            PROXMOX_BACKUP_PREFIXED_AUTH_COOKIE
+        } else {
+            PROXMOX_BACKUP_AUTH_COOKIE
+        };
+
         let enc_ticket = format!(
-            "PBSAuthCookie={}",
+            "{cookie_name}={}",
             percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET)
         );
+
         req.headers_mut()
             .insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap());
 
@@ -836,10 +859,18 @@ impl HttpClient {
                 HeaderValue::from_str(&enc_api_token).unwrap(),
             );
         } else {
+            let cookie_name = if auth.http_only {
+                // server has switched to http only flow, provide ticket in properly prefixed cookie
+                PROXMOX_BACKUP_PREFIXED_AUTH_COOKIE
+            } else {
+                PROXMOX_BACKUP_AUTH_COOKIE
+            };
+
             let enc_ticket = format!(
-                "PBSAuthCookie={}",
+                "{cookie_name}={}",
                 percent_encode(auth.ticket.as_bytes(), DEFAULT_ENCODE_SET)
             );
+
             req.headers_mut()
                 .insert("Cookie", HeaderValue::from_str(&enc_ticket).unwrap());
             req.headers_mut().insert(
@@ -905,14 +936,43 @@ impl HttpClient {
             "/api2/json/access/ticket",
             Some(data),
         )?;
-        let cred = Self::api_request(client, req).await?;
+
+        let res = tokio::time::timeout(HTTP_TIMEOUT, client.request(req))
+            .await
+            .map_err(|_| format_err!("http request timed out"))??;
+
+        // check if the headers contain a newer http only cookie
+        let http_only_ticket = res
+            .headers()
+            .get_all(SET_COOKIE)
+            .iter()
+            .filter_map(|c| c.to_str().ok())
+            .filter_map(|c| match (c.find('='), c.find(';')) {
+                (Some(begin), Some(end))
+                    if begin < end && &c[..begin] == PROXMOX_BACKUP_PREFIXED_AUTH_COOKIE =>
+                {
+                    Some(c[begin + 1..end].to_string())
+                }
+                _ => None,
+            })
+            .next();
+
+        // if the headers contained a new http only cookie, the server switched to providing these
+        // by default. this means that older cookies may no longer be supported, so switch to using
+        // the new cookie name.
+        let http_only = http_only_ticket.is_some();
+
+        let cred = Self::api_response(res).await?;
         let auth = AuthInfo {
             auth_id: cred["data"]["username"].as_str().unwrap().parse()?,
-            ticket: cred["data"]["ticket"].as_str().unwrap().to_owned(),
+            ticket: http_only_ticket
+                .or(cred["data"]["ticket"].as_str().map(|t| t.to_string()))
+                .unwrap(),
             token: cred["data"]["CSRFPreventionToken"]
                 .as_str()
                 .unwrap()
                 .to_owned(),
+            http_only,
         };
 
         Ok(auth)
-- 
2.39.5



_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel


  parent reply	other threads:[~2025-07-10 13:50 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-07-10 13:50 [pbs-devel] [PATCH proxmox{, -backup} 0/4] http only cookie based tickets for pbs Shannon Sterz
2025-07-10 13:50 ` [pbs-devel] [PATCH proxmox 1/1] auth-api: include meta information required by extjs in api endpoints Shannon Sterz
2025-07-15 22:40   ` Thomas Lamprecht
     [not found]     ` <DBDBXGTI71WP.3V2J3DEMNK1DL@proxmox.com>
2025-07-22 20:21       ` Thomas Lamprecht
2025-07-23 15:18         ` Shannon Sterz
2025-07-10 13:50 ` [pbs-devel] [PATCH proxmox-backup 1/3] api: access: add opt-in http only ticket authentication flow Shannon Sterz
2025-07-23 12:57   ` Mira Limbeck
2025-07-23 13:58   ` Maximiliano Sandoval
2025-07-10 13:50 ` [pbs-devel] [PATCH proxmox-backup 2/3] ui: opt into the new http-only " Shannon Sterz
2025-07-10 13:50 ` Shannon Sterz [this message]
2025-07-23 12:56 ` [pbs-devel] [PATCH proxmox{, -backup} 0/4] http only cookie based tickets for pbs Mira Limbeck
2025-07-23 14:05 ` Maximiliano Sandoval
2025-07-23 15:15 ` Shannon Sterz

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=20250710135010.305861-5-s.sterz@proxmox.com \
    --to=s.sterz@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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal