public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH installer v4 02/12] fetch-answer: move http-related code to gated module in installer-common
Date: Mon, 11 Nov 2024 14:14:58 +0100	[thread overview]
Message-ID: <20241111131519.867887-3-c.heiss@proxmox.com> (raw)
In-Reply-To: <20241111131519.867887-1-c.heiss@proxmox.com>

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@proxmox.com>
---
Changes v2 -> v3:
  * rebased on latest master

Changes v2 -> v3:
  * no changes

Changes v1 -> v2:
  * no changes

 Cargo.toml                                    |   4 +
 proxmox-fetch-answer/Cargo.toml               |  15 +--
 .../src/fetch_plugins/http.rs                 | 100 +-----------------
 proxmox-installer-common/Cargo.toml           |  18 ++++
 proxmox-installer-common/src/http.rs          |  94 ++++++++++++++++
 proxmox-installer-common/src/lib.rs           |   3 +
 6 files changed, 125 insertions(+), 109 deletions(-)
 create mode 100644 proxmox-installer-common/src/http.rs

diff --git a/Cargo.toml b/Cargo.toml
index ec1deaf..e20db33 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,3 +11,7 @@ members = [
 
 [workspace.dependencies]
 anyhow = "1.0"
+log = "0.4.20"
+toml = "0.8"
+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 9149176..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.workspace = true
-hex = "0.4"
-log = "0.4.20"
-native-tls = "0.2"
-proxmox-auto-installer = { path = "../proxmox-auto-installer" }
-rustls = { version = "0.21", features = [ "dangerous_configuration" ] }
-rustls-native-certs = "0.6"
-sha2 = "0.10"
-toml = "0.8"
-ureq = { version = "2.6", features = [ "native-certs", "native-tls" ] }
+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 fa99c57..007e6f4 100644
--- a/proxmox-installer-common/Cargo.toml
+++ b/proxmox-installer-common/Cargo.toml
@@ -13,3 +13,21 @@ regex = "1.7"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 serde_plain = "1.0"
+
+# `http` feature
+hex = { version = "0.4", optional = true }
+native-tls = { version = "0.2", optional = true }
+rustls = { version = "0.21", 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: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 028b43c..85fc399 100644
--- a/proxmox-installer-common/src/lib.rs
+++ b/proxmox-installer-common/src/lib.rs
@@ -3,6 +3,9 @@ pub mod options;
 pub mod setup;
 pub mod utils;
 
+#[cfg(feature = "http")]
+pub mod http;
+
 pub const RUNTIME_DIR: &str = "/run/proxmox-installer";
 
 /// Default placeholder value for the administrator email address.
-- 
2.47.0



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


  parent reply	other threads:[~2024-11-11 13:16 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-11-11 13:14 [pve-devel] [PATCH installer v4 00/12] fix #5536: implement post-(auto-)installation notification mechanism Christoph Heiss
2024-11-11 13:14 ` [pve-devel] [PATCH installer v4 01/12] debian: strip unused library dependencies Christoph Heiss
2024-11-11 13:14 ` Christoph Heiss [this message]
2024-11-11 13:14 ` [pve-devel] [PATCH installer v4 03/12] tree-wide: convert some more crates to use workspace dependencies Christoph Heiss
2024-11-11 13:15 ` [pve-devel] [PATCH installer v4 04/12] auto-install-assistant: replace `PathBuf` parameters with `AsRef<Path>` Christoph Heiss
2024-11-11 13:15 ` [pve-devel] [PATCH installer v4 05/12] auto-installer: tests: simplify empty disks check Christoph Heiss
2024-11-11 13:15 ` [pve-devel] [PATCH installer v4 06/12] auto-installer: tests: replace `PathBuf` parameters with `AsRef<Path>` Christoph Heiss
2024-11-11 13:15 ` [pve-devel] [PATCH installer v4 07/12] auto-installer: move `SystemDMI` struct to common crate Christoph Heiss
2024-11-11 13:15 ` [pve-devel] [PATCH installer v4 08/12] auto-installer: answer: factor out answer file reading into function Christoph Heiss
2024-11-11 13:15 ` [pve-devel] [PATCH installer v4 09/12] auto-installer: udevinfo: introduce type alias for udev properties Christoph Heiss
2024-11-11 13:15 ` [pve-devel] [PATCH installer v4 10/12] fix #5536: auto-installer: answer: add `posthook` section Christoph Heiss
2024-11-11 13:15 ` [pve-devel] [PATCH installer v4 11/12] fix #5536: post-hook: add utility for sending notifications after auto-install Christoph Heiss
2024-11-11 13:15 ` [pve-devel] [PATCH installer v4 12/12] unconfigured.sh: run proxmox-post-hook after successful auto-install Christoph Heiss
2024-11-11 17:41 ` [pve-devel] applied: [PATCH installer v4 00/12] fix #5536: implement post-(auto-)installation notification mechanism Thomas Lamprecht
2024-11-12 10:33   ` Christoph Heiss

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=20241111131519.867887-3-c.heiss@proxmox.com \
    --to=c.heiss@proxmox.com \
    --cc=pve-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