[pve-devel] [PATCH installer v4 02/12] fetch-answer: move http-related code to gated module in installer-common
Christoph Heiss
c.heiss at proxmox.com
Mon Nov 11 14:14:58 CET 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:
* 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
More information about the pve-devel
mailing list