[pve-devel] [PATCH installer v3 10/20] fetch-answer: move http-related code to gated module in installer-common

Christoph Heiss c.heiss at proxmox.com
Wed Aug 21 11:40:12 CEST 2024


This enable reusage of this code in other crates. Needed esp. by the
upcoming post-installation notification hook functionality.

No functional changes overall.

Signed-off-by: Christoph Heiss <c.heiss at proxmox.com>
---
Changes v2 -> v3:
  * no changes

Changes v1 -> v2:
  * no changes

 Cargo.toml                                    |   6 ++
 proxmox-fetch-answer/Cargo.toml               |  17 +--
 .../src/fetch_plugins/http.rs                 | 100 +-----------------
 proxmox-installer-common/Cargo.toml           |  20 ++++
 proxmox-installer-common/src/http.rs          |  94 ++++++++++++++++
 proxmox-installer-common/src/lib.rs           |   3 +
 6 files changed, 130 insertions(+), 110 deletions(-)
 create mode 100644 proxmox-installer-common/src/http.rs

diff --git a/Cargo.toml b/Cargo.toml
index 1e730ce..1dcf669 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,3 +9,9 @@ members = [
     "proxmox-tui-installer",
 ]
 
+[workspace.dependencies]
+anyhow = "1.0"
+log = "0.4.20"
+toml = "0.7"
+proxmox-auto-installer.path = "./proxmox-auto-installer"
+proxmox-installer-common.path = "./proxmox-installer-common"
diff --git a/proxmox-fetch-answer/Cargo.toml b/proxmox-fetch-answer/Cargo.toml
index 964682a..50f3da3 100644
--- a/proxmox-fetch-answer/Cargo.toml
+++ b/proxmox-fetch-answer/Cargo.toml
@@ -10,16 +10,9 @@ license = "AGPL-3"
 exclude = [ "build", "debian" ]
 homepage = "https://www.proxmox.com"
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
 [dependencies]
-anyhow = "1.0"
-hex = "0.4"
-log = "0.4.20"
-native-tls = "0.2"
-proxmox-auto-installer = { path = "../proxmox-auto-installer" }
-rustls = { version = "0.20", features = [ "dangerous_configuration" ] }
-rustls-native-certs = "0.6"
-sha2 = "0.10"
-toml = "0.7"
-ureq = { version = "2.6", features = [ "native-certs", "native-tls" ] }
+anyhow.workspace = true
+log.workspace = true
+proxmox-auto-installer.workspace = true
+proxmox-installer-common = { workspace = true, features = ["http"] }
+toml.workspace = true
diff --git a/proxmox-fetch-answer/src/fetch_plugins/http.rs b/proxmox-fetch-answer/src/fetch_plugins/http.rs
index 5e10f6a..4317430 100644
--- a/proxmox-fetch-answer/src/fetch_plugins/http.rs
+++ b/proxmox-fetch-answer/src/fetch_plugins/http.rs
@@ -67,7 +67,8 @@ impl FetchFromHTTP {
         info!("Gathering system information.");
         let payload = SysInfo::as_json()?;
         info!("Sending POST request to '{answer_url}'.");
-        let answer = http_post::call(&answer_url, fingerprint.as_deref(), payload)?;
+        let answer =
+            proxmox_installer_common::http::post(&answer_url, fingerprint.as_deref(), payload)?;
         Ok(answer)
     }
 
@@ -179,100 +180,3 @@ impl FetchFromHTTP {
         value.map(|value| String::from(&value[1..value.len() - 2]))
     }
 }
-
-mod http_post {
-    use anyhow::Result;
-    use rustls::ClientConfig;
-    use sha2::{Digest, Sha256};
-    use std::sync::Arc;
-    use ureq::{Agent, AgentBuilder};
-
-    /// Issues a POST request with the payload (JSON). Optionally a SHA256 fingerprint can be used to
-    /// check the cert against it, instead of the regular cert validation.
-    /// To gather the sha256 fingerprint you can use the following command:
-    /// ```no_compile
-    /// openssl s_client -connect <host>:443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -sha256  -noout -in /dev/stdin
-    /// ```
-    ///
-    /// # Arguments
-    /// * `url` - URL to call
-    /// * `fingerprint` - SHA256 cert fingerprint if certificate pinning should be used. Optional.
-    /// * `payload` - The payload to send to the server. Expected to be a JSON formatted string.
-    pub fn call(url: &str, fingerprint: Option<&str>, payload: String) -> Result<String> {
-        let answer;
-
-        if let Some(fingerprint) = fingerprint {
-            let tls_config = ClientConfig::builder()
-                .with_safe_defaults()
-                .with_custom_certificate_verifier(VerifyCertFingerprint::new(fingerprint)?)
-                .with_no_client_auth();
-
-            let agent: Agent = AgentBuilder::new().tls_config(Arc::new(tls_config)).build();
-
-            answer = agent
-                .post(url)
-                .set("Content-type", "application/json; charset=utf-8")
-                .send_string(&payload)?
-                .into_string()?;
-        } else {
-            let mut roots = rustls::RootCertStore::empty();
-            for cert in rustls_native_certs::load_native_certs()? {
-                roots.add(&rustls::Certificate(cert.0)).unwrap();
-            }
-
-            let tls_config = rustls::ClientConfig::builder()
-                .with_safe_defaults()
-                .with_root_certificates(roots)
-                .with_no_client_auth();
-
-            let agent = AgentBuilder::new()
-                .tls_connector(Arc::new(native_tls::TlsConnector::new()?))
-                .tls_config(Arc::new(tls_config))
-                .build();
-            answer = agent
-                .post(url)
-                .set("Content-type", "application/json; charset=utf-8")
-                .timeout(std::time::Duration::from_secs(60))
-                .send_string(&payload)?
-                .into_string()?;
-        }
-        Ok(answer)
-    }
-
-    struct VerifyCertFingerprint {
-        cert_fingerprint: Vec<u8>,
-    }
-
-    impl VerifyCertFingerprint {
-        fn new<S: AsRef<str>>(cert_fingerprint: S) -> Result<std::sync::Arc<Self>> {
-            let cert_fingerprint = cert_fingerprint.as_ref();
-            let sanitized = cert_fingerprint.replace(':', "");
-            let decoded = hex::decode(sanitized)?;
-            Ok(std::sync::Arc::new(Self {
-                cert_fingerprint: decoded,
-            }))
-        }
-    }
-
-    impl rustls::client::ServerCertVerifier for VerifyCertFingerprint {
-        fn verify_server_cert(
-            &self,
-            end_entity: &rustls::Certificate,
-            _intermediates: &[rustls::Certificate],
-            _server_name: &rustls::ServerName,
-            _scts: &mut dyn Iterator<Item = &[u8]>,
-            _ocsp_response: &[u8],
-            _now: std::time::SystemTime,
-        ) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
-            let mut hasher = Sha256::new();
-            hasher.update(end_entity);
-            let result = hasher.finalize();
-
-            if result.as_slice() == self.cert_fingerprint {
-                Ok(rustls::client::ServerCertVerified::assertion())
-            } else {
-                Err(rustls::Error::General("Fingerprint did not match!".into()))
-            }
-        }
-    }
-}
diff --git a/proxmox-installer-common/Cargo.toml b/proxmox-installer-common/Cargo.toml
index 4b72041..d8e9e06 100644
--- a/proxmox-installer-common/Cargo.toml
+++ b/proxmox-installer-common/Cargo.toml
@@ -12,3 +12,23 @@ regex = "1.7"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 serde_plain = "1.0"
+
+# `http` feature
+anyhow = { workspace = true, optional = true }
+hex = { version = "0.4", optional = true }
+native-tls = { version = "0.2", optional = true }
+rustls = { version = "0.20", features = [ "dangerous_configuration" ], optional = true }
+rustls-native-certs = { version = "0.6", optional = true }
+sha2 = { version = "0.10", optional = true }
+ureq = { version = "2.6", features = [ "native-certs", "native-tls" ], optional = true }
+
+[features]
+http = [
+    "dep:anyhow",
+    "dep:hex",
+    "dep:native-tls",
+    "dep:rustls",
+    "dep:rustls-native-certs",
+    "dep:sha2",
+    "dep:ureq"
+]
diff --git a/proxmox-installer-common/src/http.rs b/proxmox-installer-common/src/http.rs
new file mode 100644
index 0000000..b754ed8
--- /dev/null
+++ b/proxmox-installer-common/src/http.rs
@@ -0,0 +1,94 @@
+use anyhow::Result;
+use rustls::ClientConfig;
+use sha2::{Digest, Sha256};
+use std::sync::Arc;
+use ureq::{Agent, AgentBuilder};
+
+/// Issues a POST request with the payload (JSON). Optionally a SHA256 fingerprint can be used to
+/// check the cert against it, instead of the regular cert validation.
+/// To gather the sha256 fingerprint you can use the following command:
+/// ```no_compile
+/// openssl s_client -connect <host>:443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -sha256  -noout -in /dev/stdin
+/// ```
+///
+/// # Arguments
+/// * `url` - URL to call
+/// * `fingerprint` - SHA256 cert fingerprint if certificate pinning should be used. Optional.
+/// * `payload` - The payload to send to the server. Expected to be a JSON formatted string.
+pub fn post(url: &str, fingerprint: Option<&str>, payload: String) -> Result<String> {
+    let answer;
+
+    if let Some(fingerprint) = fingerprint {
+        let tls_config = ClientConfig::builder()
+            .with_safe_defaults()
+            .with_custom_certificate_verifier(VerifyCertFingerprint::new(fingerprint)?)
+            .with_no_client_auth();
+
+        let agent: Agent = AgentBuilder::new().tls_config(Arc::new(tls_config)).build();
+
+        answer = agent
+            .post(url)
+            .set("Content-Type", "application/json; charset=utf-8")
+            .send_string(&payload)?
+            .into_string()?;
+    } else {
+        let mut roots = rustls::RootCertStore::empty();
+        for cert in rustls_native_certs::load_native_certs()? {
+            roots.add(&rustls::Certificate(cert.0)).unwrap();
+        }
+
+        let tls_config = rustls::ClientConfig::builder()
+            .with_safe_defaults()
+            .with_root_certificates(roots)
+            .with_no_client_auth();
+
+        let agent = AgentBuilder::new()
+            .tls_connector(Arc::new(native_tls::TlsConnector::new()?))
+            .tls_config(Arc::new(tls_config))
+            .build();
+        answer = agent
+            .post(url)
+            .set("Content-Type", "application/json; charset=utf-8")
+            .timeout(std::time::Duration::from_secs(60))
+            .send_string(&payload)?
+            .into_string()?;
+    }
+    Ok(answer)
+}
+
+struct VerifyCertFingerprint {
+    cert_fingerprint: Vec<u8>,
+}
+
+impl VerifyCertFingerprint {
+    fn new<S: AsRef<str>>(cert_fingerprint: S) -> Result<std::sync::Arc<Self>> {
+        let cert_fingerprint = cert_fingerprint.as_ref();
+        let sanitized = cert_fingerprint.replace(':', "");
+        let decoded = hex::decode(sanitized)?;
+        Ok(std::sync::Arc::new(Self {
+            cert_fingerprint: decoded,
+        }))
+    }
+}
+
+impl rustls::client::ServerCertVerifier for VerifyCertFingerprint {
+    fn verify_server_cert(
+        &self,
+        end_entity: &rustls::Certificate,
+        _intermediates: &[rustls::Certificate],
+        _server_name: &rustls::ServerName,
+        _scts: &mut dyn Iterator<Item = &[u8]>,
+        _ocsp_response: &[u8],
+        _now: std::time::SystemTime,
+    ) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
+        let mut hasher = Sha256::new();
+        hasher.update(end_entity);
+        let result = hasher.finalize();
+
+        if result.as_slice() == self.cert_fingerprint {
+            Ok(rustls::client::ServerCertVerified::assertion())
+        } else {
+            Err(rustls::Error::General("Fingerprint did not match!".into()))
+        }
+    }
+}
diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs
index 850e825..ee9f2a8 100644
--- a/proxmox-installer-common/src/lib.rs
+++ b/proxmox-installer-common/src/lib.rs
@@ -3,4 +3,7 @@ pub mod options;
 pub mod setup;
 pub mod utils;
 
+#[cfg(feature = "http")]
+pub mod http;
+
 pub const RUNTIME_DIR: &str = "/run/proxmox-installer";
-- 
2.45.2





More information about the pve-devel mailing list