src/client/http_client.rs: new prefix options
This commit is contained in:
parent
5a74756c15
commit
5030b7cea4
@ -166,6 +166,7 @@ fn complete_repository(_arg: &str, _param: &HashMap<String, String>) -> Vec<Stri
|
|||||||
fn connect(server: &str, userid: &str) -> Result<HttpClient, Error> {
|
fn connect(server: &str, userid: &str) -> Result<HttpClient, Error> {
|
||||||
|
|
||||||
let options = HttpClientOptions::new()
|
let options = HttpClientOptions::new()
|
||||||
|
.prefix(Some("proxmox-backup".to_string()))
|
||||||
.interactive(true)
|
.interactive(true)
|
||||||
.fingerprint_cache(true)
|
.fingerprint_cache(true)
|
||||||
.ticket_cache(true);
|
.ticket_cache(true);
|
||||||
@ -536,7 +537,7 @@ fn api_logout(param: Value) -> Result<Value, Error> {
|
|||||||
|
|
||||||
let repo = extract_repository_from_value(¶m)?;
|
let repo = extract_repository_from_value(¶m)?;
|
||||||
|
|
||||||
delete_ticket_info(repo.host(), repo.user())?;
|
delete_ticket_info("proxmox-backup", repo.host(), repo.user())?;
|
||||||
|
|
||||||
Ok(Value::Null)
|
Ok(Value::Null)
|
||||||
}
|
}
|
||||||
@ -1474,6 +1475,7 @@ async fn status(param: Value) -> Result<Value, Error> {
|
|||||||
async fn try_get(repo: &BackupRepository, url: &str) -> Value {
|
async fn try_get(repo: &BackupRepository, url: &str) -> Value {
|
||||||
|
|
||||||
let options = HttpClientOptions::new()
|
let options = HttpClientOptions::new()
|
||||||
|
.prefix(Some("proxmox-backup".to_string()))
|
||||||
.interactive(false)
|
.interactive(false)
|
||||||
.fingerprint_cache(true)
|
.fingerprint_cache(true)
|
||||||
.ticket_cache(true);
|
.ticket_cache(true);
|
||||||
|
@ -35,6 +35,7 @@ fn connect() -> Result<HttpClient, Error> {
|
|||||||
let uid = nix::unistd::Uid::current();
|
let uid = nix::unistd::Uid::current();
|
||||||
|
|
||||||
let mut options = HttpClientOptions::new()
|
let mut options = HttpClientOptions::new()
|
||||||
|
.prefix(Some("proxmox-backup".to_string()))
|
||||||
.verify_cert(false); // not required for connection to localhost
|
.verify_cert(false); // not required for connection to localhost
|
||||||
|
|
||||||
let client = if uid.is_root() {
|
let client = if uid.is_root() {
|
||||||
|
@ -31,6 +31,7 @@ pub struct AuthInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct HttpClientOptions {
|
pub struct HttpClientOptions {
|
||||||
|
prefix: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
fingerprint: Option<String>,
|
fingerprint: Option<String>,
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
@ -43,6 +44,7 @@ impl HttpClientOptions {
|
|||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
prefix: None,
|
||||||
password: None,
|
password: None,
|
||||||
fingerprint: None,
|
fingerprint: None,
|
||||||
interactive: false,
|
interactive: false,
|
||||||
@ -52,6 +54,11 @@ impl HttpClientOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prefix(mut self, prefix: Option<String>) -> Self {
|
||||||
|
self.prefix = prefix;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn password(mut self, password: Option<String>) -> Self {
|
pub fn password(mut self, password: Option<String>) -> Self {
|
||||||
self.password = password;
|
self.password = password;
|
||||||
self
|
self
|
||||||
@ -93,9 +100,9 @@ pub struct HttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Delete stored ticket data (logout)
|
/// Delete stored ticket data (logout)
|
||||||
pub fn delete_ticket_info(server: &str, username: &str) -> Result<(), Error> {
|
pub fn delete_ticket_info(prefix: &str, server: &str, username: &str) -> Result<(), Error> {
|
||||||
|
|
||||||
let base = BaseDirectories::with_prefix("proxmox-backup")?;
|
let base = BaseDirectories::with_prefix(prefix)?;
|
||||||
|
|
||||||
// usually /run/user/<uid>/...
|
// usually /run/user/<uid>/...
|
||||||
let path = base.place_runtime_file("tickets")?;
|
let path = base.place_runtime_file("tickets")?;
|
||||||
@ -113,11 +120,11 @@ pub fn delete_ticket_info(server: &str, username: &str) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_fingerprint(server: &str, fingerprint: &str) -> Result<(), Error> {
|
fn store_fingerprint(prefix: &str, server: &str, fingerprint: &str) -> Result<(), Error> {
|
||||||
|
|
||||||
let base = BaseDirectories::with_prefix("proxmox-backup")?;
|
let base = BaseDirectories::with_prefix(prefix)?;
|
||||||
|
|
||||||
// usually ~/.config/proxmox-backup/fingerprints
|
// usually ~/.config/<prefix>/fingerprints
|
||||||
let path = base.place_config_file("fingerprints")?;
|
let path = base.place_config_file("fingerprints")?;
|
||||||
|
|
||||||
let raw = match std::fs::read_to_string(&path) {
|
let raw = match std::fs::read_to_string(&path) {
|
||||||
@ -155,11 +162,11 @@ fn store_fingerprint(server: &str, fingerprint: &str) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_fingerprint(server: &str) -> Option<String> {
|
fn load_fingerprint(prefix: &str, server: &str) -> Option<String> {
|
||||||
|
|
||||||
let base = BaseDirectories::with_prefix("proxmox-backup").ok()?;
|
let base = BaseDirectories::with_prefix(prefix).ok()?;
|
||||||
|
|
||||||
// usually ~/.config/proxmox-backup/fingerprints
|
// usually ~/.config/<prefix>/fingerprints
|
||||||
let path = base.place_config_file("fingerprints").ok()?;
|
let path = base.place_config_file("fingerprints").ok()?;
|
||||||
|
|
||||||
let raw = std::fs::read_to_string(&path).ok()?;
|
let raw = std::fs::read_to_string(&path).ok()?;
|
||||||
@ -176,9 +183,9 @@ fn load_fingerprint(server: &str) -> Option<String> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_ticket_info(server: &str, username: &str, ticket: &str, token: &str) -> Result<(), Error> {
|
fn store_ticket_info(prefix: &str, server: &str, username: &str, ticket: &str, token: &str) -> Result<(), Error> {
|
||||||
|
|
||||||
let base = BaseDirectories::with_prefix("proxmox-backup")?;
|
let base = BaseDirectories::with_prefix(prefix)?;
|
||||||
|
|
||||||
// usually /run/user/<uid>/...
|
// usually /run/user/<uid>/...
|
||||||
let path = base.place_runtime_file("tickets")?;
|
let path = base.place_runtime_file("tickets")?;
|
||||||
@ -212,8 +219,8 @@ fn store_ticket_info(server: &str, username: &str, ticket: &str, token: &str) ->
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_ticket_info(server: &str, username: &str) -> Option<(String, String)> {
|
fn load_ticket_info(prefix: &str, server: &str, username: &str) -> Option<(String, String)> {
|
||||||
let base = BaseDirectories::with_prefix("proxmox-backup").ok()?;
|
let base = BaseDirectories::with_prefix(prefix).ok()?;
|
||||||
|
|
||||||
// usually /run/user/<uid>/...
|
// usually /run/user/<uid>/...
|
||||||
let path = base.place_runtime_file("tickets").ok()?;
|
let path = base.place_runtime_file("tickets").ok()?;
|
||||||
@ -240,27 +247,58 @@ impl HttpClient {
|
|||||||
let verified_fingerprint = Arc::new(Mutex::new(None));
|
let verified_fingerprint = Arc::new(Mutex::new(None));
|
||||||
|
|
||||||
let mut fingerprint = options.fingerprint.take();
|
let mut fingerprint = options.fingerprint.take();
|
||||||
if options.fingerprint_cache && fingerprint.is_none() {
|
if options.fingerprint_cache && fingerprint.is_none() && options.prefix.is_some() {
|
||||||
fingerprint = load_fingerprint(server);
|
fingerprint = load_fingerprint(options.prefix.as_ref().unwrap(), server);
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = Self::build_client(
|
let mut ssl_connector_builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||||
server.to_string(),
|
|
||||||
fingerprint,
|
if options.verify_cert {
|
||||||
options.interactive,
|
let server = server.to_string();
|
||||||
verified_fingerprint.clone(),
|
let verified_fingerprint = verified_fingerprint.clone();
|
||||||
options.verify_cert,
|
let interactive = options.interactive;
|
||||||
options.fingerprint_cache,
|
let fingerprint_cache = options.fingerprint_cache;
|
||||||
);
|
let prefix = options.prefix.clone();
|
||||||
|
ssl_connector_builder.set_verify_callback(openssl::ssl::SslVerifyMode::PEER, move |valid, ctx| {
|
||||||
|
let (valid, fingerprint) = Self::verify_callback(valid, ctx, fingerprint.clone(), interactive);
|
||||||
|
if valid {
|
||||||
|
if let Some(fingerprint) = fingerprint {
|
||||||
|
if fingerprint_cache && prefix.is_some() {
|
||||||
|
if let Err(err) = store_fingerprint(
|
||||||
|
prefix.as_ref().unwrap(), &server, &fingerprint) {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*verified_fingerprint.lock().unwrap() = Some(fingerprint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valid
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut httpc = hyper::client::HttpConnector::new();
|
||||||
|
httpc.set_nodelay(true); // important for h2 download performance!
|
||||||
|
httpc.set_recv_buffer_size(Some(1024*1024)); //important for h2 download performance!
|
||||||
|
httpc.enforce_http(false); // we want https...
|
||||||
|
|
||||||
|
let https = HttpsConnector::with_connector(httpc, ssl_connector_builder.build());
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
//.http2_initial_stream_window_size( (1 << 31) - 2)
|
||||||
|
//.http2_initial_connection_window_size( (1 << 31) - 2)
|
||||||
|
.build::<_, Body>(https);
|
||||||
|
|
||||||
let password = options.password.take();
|
let password = options.password.take();
|
||||||
|
let use_ticket_cache = options.ticket_cache && options.prefix.is_some();
|
||||||
|
|
||||||
let password = if let Some(password) = password {
|
let password = if let Some(password) = password {
|
||||||
password
|
password
|
||||||
} else {
|
} else {
|
||||||
let mut ticket_info = None;
|
let mut ticket_info = None;
|
||||||
if options.ticket_cache {
|
if use_ticket_cache {
|
||||||
ticket_info = load_ticket_info(server, username);
|
ticket_info = load_ticket_info(options.prefix.as_ref().unwrap(), server, username);
|
||||||
}
|
}
|
||||||
if let Some((ticket, _token)) = ticket_info {
|
if let Some((ticket, _token)) = ticket_info {
|
||||||
ticket
|
ticket
|
||||||
@ -274,8 +312,18 @@ impl HttpClient {
|
|||||||
server.to_owned(),
|
server.to_owned(),
|
||||||
username.to_owned(),
|
username.to_owned(),
|
||||||
password,
|
password,
|
||||||
options.ticket_cache,
|
).map_ok({
|
||||||
);
|
let server = server.to_string();
|
||||||
|
let prefix = options.prefix.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
auth
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client,
|
client,
|
||||||
@ -320,25 +368,22 @@ impl HttpClient {
|
|||||||
fn verify_callback(
|
fn verify_callback(
|
||||||
valid: bool, ctx:
|
valid: bool, ctx:
|
||||||
&mut X509StoreContextRef,
|
&mut X509StoreContextRef,
|
||||||
server: String,
|
|
||||||
expected_fingerprint: Option<String>,
|
expected_fingerprint: Option<String>,
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
verified_fingerprint: Arc<Mutex<Option<String>>>,
|
) -> (bool, Option<String>) {
|
||||||
fingerprint_cache: bool,
|
if valid { return (true, None); }
|
||||||
) -> bool {
|
|
||||||
if valid { return true; }
|
|
||||||
|
|
||||||
let cert = match ctx.current_cert() {
|
let cert = match ctx.current_cert() {
|
||||||
Some(cert) => cert,
|
Some(cert) => cert,
|
||||||
None => return false,
|
None => return (false, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let depth = ctx.error_depth();
|
let depth = ctx.error_depth();
|
||||||
if depth != 0 { return false; }
|
if depth != 0 { return (false, None); }
|
||||||
|
|
||||||
let fp = match cert.digest(openssl::hash::MessageDigest::sha256()) {
|
let fp = match cert.digest(openssl::hash::MessageDigest::sha256()) {
|
||||||
Ok(fp) => fp,
|
Ok(fp) => fp,
|
||||||
Err(_) => return false, // should not happen
|
Err(_) => return (false, None), // should not happen
|
||||||
};
|
};
|
||||||
let fp_string = proxmox::tools::digest_to_hex(&fp);
|
let fp_string = proxmox::tools::digest_to_hex(&fp);
|
||||||
let fp_string = fp_string.as_bytes().chunks(2).map(|v| std::str::from_utf8(v).unwrap())
|
let fp_string = fp_string.as_bytes().chunks(2).map(|v| std::str::from_utf8(v).unwrap())
|
||||||
@ -346,10 +391,9 @@ impl HttpClient {
|
|||||||
|
|
||||||
if let Some(expected_fingerprint) = expected_fingerprint {
|
if let Some(expected_fingerprint) = expected_fingerprint {
|
||||||
if expected_fingerprint == fp_string {
|
if expected_fingerprint == fp_string {
|
||||||
*verified_fingerprint.lock().unwrap() = Some(fp_string);
|
return (true, Some(fp_string));
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return (false, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,58 +408,18 @@ impl HttpClient {
|
|||||||
match std::io::stdin().read_exact(&mut buf) {
|
match std::io::stdin().read_exact(&mut buf) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
if buf[0] == b'y' || buf[0] == b'Y' {
|
if buf[0] == b'y' || buf[0] == b'Y' {
|
||||||
if fingerprint_cache {
|
return (true, Some(fp_string));
|
||||||
if let Err(err) = store_fingerprint(&server, &fp_string) {
|
|
||||||
eprintln!("{}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*verified_fingerprint.lock().unwrap() = Some(fp_string);
|
|
||||||
return true;
|
|
||||||
} else if buf[0] == b'n' || buf[0] == b'N' {
|
} else if buf[0] == b'n' || buf[0] == b'N' {
|
||||||
return false;
|
return (false, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return false;
|
return (false, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
(false, None)
|
||||||
}
|
|
||||||
|
|
||||||
fn build_client(
|
|
||||||
server: String,
|
|
||||||
fingerprint: Option<String>,
|
|
||||||
interactive: bool,
|
|
||||||
verified_fingerprint: Arc<Mutex<Option<String>>>,
|
|
||||||
verify_cert: bool,
|
|
||||||
fingerprint_cache: bool,
|
|
||||||
) -> Client<HttpsConnector> {
|
|
||||||
|
|
||||||
let mut ssl_connector_builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
|
||||||
|
|
||||||
if verify_cert {
|
|
||||||
ssl_connector_builder.set_verify_callback(openssl::ssl::SslVerifyMode::PEER, move |valid, ctx| {
|
|
||||||
Self::verify_callback(
|
|
||||||
valid, ctx, server.clone(), fingerprint.clone(), interactive,
|
|
||||||
verified_fingerprint.clone(), fingerprint_cache)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut httpc = hyper::client::HttpConnector::new();
|
|
||||||
httpc.set_nodelay(true); // important for h2 download performance!
|
|
||||||
httpc.set_recv_buffer_size(Some(1024*1024)); //important for h2 download performance!
|
|
||||||
httpc.enforce_http(false); // we want https...
|
|
||||||
|
|
||||||
let https = HttpsConnector::with_connector(httpc, ssl_connector_builder.build());
|
|
||||||
|
|
||||||
Client::builder()
|
|
||||||
//.http2_initial_stream_window_size( (1 << 31) - 2)
|
|
||||||
//.http2_initial_connection_window_size( (1 << 31) - 2)
|
|
||||||
.build::<_, Body>(https)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request(&self, mut req: Request<Body>) -> Result<Value, Error> {
|
pub async fn request(&self, mut req: Request<Body>) -> Result<Value, Error> {
|
||||||
@ -576,7 +580,6 @@ impl HttpClient {
|
|||||||
server: String,
|
server: String,
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
use_ticket_cache: bool,
|
|
||||||
) -> Result<AuthInfo, Error> {
|
) -> Result<AuthInfo, Error> {
|
||||||
let data = json!({ "username": username, "password": password });
|
let data = json!({ "username": username, "password": password });
|
||||||
let req = Self::request_builder(&server, "POST", "/api2/json/access/ticket", Some(data)).unwrap();
|
let req = Self::request_builder(&server, "POST", "/api2/json/access/ticket", Some(data)).unwrap();
|
||||||
@ -587,10 +590,6 @@ impl HttpClient {
|
|||||||
token: cred["data"]["CSRFPreventionToken"].as_str().unwrap().to_owned(),
|
token: cred["data"]["CSRFPreventionToken"].as_str().unwrap().to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if use_ticket_cache {
|
|
||||||
let _ = store_ticket_info(&server, &auth.username, &auth.ticket, &auth.token);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(auth)
|
Ok(auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user