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 7362D61BB6 for ; Tue, 15 Sep 2020 11:15:03 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 701E518125 for ; Tue, 15 Sep 2020 11:15:03 +0200 (CEST) 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 A53381811C for ; Tue, 15 Sep 2020 11:15:02 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 70C7F44C22 for ; Tue, 15 Sep 2020 11:15:02 +0200 (CEST) From: Dominik Csapak To: pbs-devel@lists.proxmox.com Date: Tue, 15 Sep 2020 11:15:00 +0200 Message-Id: <20200915091501.28979-1-d.csapak@proxmox.com> X-Mailer: git-send-email 2.20.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.178 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods NO_DNS_FOR_FROM 0.379 Envelope sender has no MX or A DNS records 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_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pbs-devel] [PATCH proxmox-backup 1/2] fix #2870: renew tickets in HttpClient 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: Tue, 15 Sep 2020 09:15:03 -0000 by packing the auth into a RwLock and starting a background future that renews the ticket every 15 minutes we still use the BroadcastFuture for the first ticket and only if that is finished we start the scheduled future we have to store an abort handle for the renewal future and abort it when the http client is dropped, so we do not request new tickets forever Signed-off-by: Dominik Csapak --- src/client/http_client.rs | 67 +++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/src/client/http_client.rs b/src/client/http_client.rs index 3fd9fb7f..692b2c3e 100644 --- a/src/client/http_client.rs +++ b/src/client/http_client.rs @@ -1,6 +1,7 @@ use std::io::Write; use std::task::{Context, Poll}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; +use std::time::Duration; use anyhow::{bail, format_err, Error}; use futures::*; @@ -29,7 +30,7 @@ use crate::tools::{self, BroadcastFuture, DEFAULT_ENCODE_SET}; #[derive(Clone)] pub struct AuthInfo { - pub username: String, + pub userid: Userid, pub ticket: String, pub token: String, } @@ -99,7 +100,9 @@ pub struct HttpClient { client: Client, server: String, fingerprint: Arc>>, - auth: BroadcastFuture, + first_auth: BroadcastFuture<()>, + auth: Arc>, + ticket_abort: futures::future::AbortHandle, _options: HttpClientOptions, } @@ -317,6 +320,41 @@ impl HttpClient { } }; + let auth = Arc::new(RwLock::new(AuthInfo { + userid: userid.clone(), + ticket: password.clone(), + token: "".to_string(), + })); + + let server2 = server.to_string(); + let client2 = client.clone(); + let auth2 = auth.clone(); + let prefix2 = options.prefix.clone(); + + let renewal_future = async move { + loop { + tokio::time::delay_for(Duration::new(60*15, 0)).await; // 15 minutes + let (userid, ticket) = { + let authinfo = auth2.read().unwrap().clone(); + (authinfo.userid, authinfo.ticket) + }; + match Self::credentials(client2.clone(), server2.clone(), userid, ticket).await { + Ok(auth) => { + if use_ticket_cache & &prefix2.is_some() { + let _ = store_ticket_info(prefix2.as_ref().unwrap(), &server2, &auth.userid.to_string(), &auth.ticket, &auth.token); + } + *auth2.write().unwrap() = auth; + }, + Err(err) => { + eprintln!("re-authentication failed: {}", err); + return; + } + } + } + }; + + let (renewal_future, ticket_abort) = futures::future::abortable(renewal_future); + let login_future = Self::credentials( client.clone(), server.to_owned(), @@ -325,13 +363,14 @@ impl HttpClient { ).map_ok({ let server = server.to_string(); let prefix = options.prefix.clone(); + let authinfo = auth.clone(); move |auth| { if use_ticket_cache & &prefix.is_some() { - let _ = store_ticket_info(prefix.as_ref().unwrap(), &server, &auth.username, &auth.ticket, &auth.token); + let _ = store_ticket_info(prefix.as_ref().unwrap(), &server, &auth.userid.to_string(), &auth.ticket, &auth.token); } - - auth + *authinfo.write().unwrap() = auth; + tokio::spawn(renewal_future); } }); @@ -339,7 +378,9 @@ impl HttpClient { client, server: String::from(server), fingerprint: verified_fingerprint, - auth: BroadcastFuture::new(Box::new(login_future)), + auth, + ticket_abort, + first_auth: BroadcastFuture::new(Box::new(login_future)), _options: options, }) } @@ -349,7 +390,9 @@ impl HttpClient { /// Login is done on demand, so this is only required if you need /// access to authentication data in 'AuthInfo'. pub async fn login(&self) -> Result { - self.auth.listen().await + self.first_auth.listen().await?; + let authinfo = self.auth.read().unwrap(); + Ok(authinfo.clone()) } /// Returns the optional fingerprint passed to the new() constructor. @@ -588,7 +631,7 @@ impl HttpClient { let req = Self::request_builder(&server, "POST", "/api2/json/access/ticket", Some(data)).unwrap(); let cred = Self::api_request(client, req).await?; let auth = AuthInfo { - username: cred["data"]["username"].as_str().unwrap().to_owned(), + userid: cred["data"]["username"].as_str().unwrap().parse()?, ticket: cred["data"]["ticket"].as_str().unwrap().to_owned(), token: cred["data"]["CSRFPreventionToken"].as_str().unwrap().to_owned(), }; @@ -666,6 +709,12 @@ impl HttpClient { } } +impl Drop for HttpClient { + fn drop(&mut self) { + self.ticket_abort.abort(); + } +} + #[derive(Clone)] pub struct H2Client { -- 2.20.1