From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 09CA11FF13E for ; Fri, 03 Apr 2026 18:57:10 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id C7D258BC5; Fri, 3 Apr 2026 18:57:40 +0200 (CEST) From: Christoph Heiss To: pdm-devel@lists.proxmox.com Subject: [PATCH installer v3 26/38] common: http: allow passing custom headers to post() Date: Fri, 3 Apr 2026 18:53:58 +0200 Message-ID: <20260403165437.2166551-27-c.heiss@proxmox.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260403165437.2166551-1-c.heiss@proxmox.com> References: <20260403165437.2166551-1-c.heiss@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1775235360227 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.067 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: ZUAEOQLKHXCICHSTEH4HDU6B3FNXMDGK X-Message-ID-Hash: ZUAEOQLKHXCICHSTEH4HDU6B3FNXMDGK X-MailFrom: c.heiss@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add an additional parameter to allow passing in additional headers. No functional changes. Signed-off-by: Christoph Heiss --- Changes v2 -> v3: * new patch .../src/fetch_plugins/http.rs | 12 ++++-- proxmox-installer-common/src/http.rs | 40 ++++++++++++++++--- proxmox-post-hook/src/main.rs | 22 ++++------ 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/proxmox-fetch-answer/src/fetch_plugins/http.rs b/proxmox-fetch-answer/src/fetch_plugins/http.rs index e2fd633..b958a35 100644 --- a/proxmox-fetch-answer/src/fetch_plugins/http.rs +++ b/proxmox-fetch-answer/src/fetch_plugins/http.rs @@ -7,6 +7,7 @@ use std::{ }; use proxmox_auto_installer::{sysinfo::SysInfo, utils::HttpOptions}; +use proxmox_installer_common::http::{self, header::HeaderMap}; static ANSWER_URL_SUBDOMAIN: &str = "proxmox-auto-installer"; static ANSWER_CERT_FP_SUBDOMAIN: &str = "proxmox-auto-installer-cert-fingerprint"; @@ -130,9 +131,14 @@ impl FetchFromHTTP { let payload = HttpFetchPayload::as_json()?; info!("Sending POST request to '{answer_url}'."); - let answer = - proxmox_installer_common::http::post(&answer_url, fingerprint.as_deref(), payload)?; - Ok(answer) + + Ok(http::post( + &answer_url, + fingerprint.as_deref(), + HeaderMap::new(), + payload, + )? + .0) } /// Fetches search domain from resolv.conf file diff --git a/proxmox-installer-common/src/http.rs b/proxmox-installer-common/src/http.rs index 7662673..f04552a 100644 --- a/proxmox-installer-common/src/http.rs +++ b/proxmox-installer-common/src/http.rs @@ -13,6 +13,9 @@ use ureq::unversioned::transport::{ Transport, TransportAdapter, }; +// Re-export for conviencence when using post() +pub use ureq::http::header; + /// Builds an [`Agent`] with TLS suitable set up, depending whether a custom fingerprint was /// supplied or not. If a fingerprint was supplied, only matching certificates will be accepted. /// Otherwise, the system certificate store is loaded. @@ -95,18 +98,43 @@ pub fn get_as_bytes(url: &str, fingerprint: Option<&str>, max_size: usize) -> Re /// openssl s_client -connect :443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -sha256 -noout -in /dev/stdin /// ``` /// +/// The `Content-Type` header is automatically set to `application/json`. +/// /// # Arguments /// * `url` - URL to call /// * `fingerprint` - SHA256 cert fingerprint if certificate pinning should be used. Optional. +/// * `headers` - Additional headers to add to the request. /// * `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 { +/// +/// # Returns +/// +/// A tuple containing +/// * The body contents, as returned by the server +/// * The content type of the response, if set in the response headers +pub fn post( + url: &str, + fingerprint: Option<&str>, + headers: header::HeaderMap, + payload: String, +) -> Result<(String, Option)> { // TODO: read_to_string limits the size to 10 MB, should be increase that? - Ok(build_agent(fingerprint)? + + let mut request = build_agent(fingerprint)? .post(url) - .header("Content-Type", "application/json; charset=utf-8") - .send(&payload)? - .body_mut() - .read_to_string()?) + .header("Content-Type", "application/json; charset=utf-8"); + + for (name, value) in headers.iter() { + request = request.header(name, value); + } + + let mut response = request.send(&payload)?; + let content_type = response + .headers() + .get(header::CONTENT_TYPE) + .and_then(|h| h.to_str().ok()) + .map(|s| s.to_owned()); + + Ok((response.body_mut().read_to_string()?, content_type)) } #[derive(Debug)] diff --git a/proxmox-post-hook/src/main.rs b/proxmox-post-hook/src/main.rs index a792b6d..2ee0231 100644 --- a/proxmox-post-hook/src/main.rs +++ b/proxmox-post-hook/src/main.rs @@ -9,6 +9,8 @@ //! Relies on `proxmox-chroot` as an external dependency to (bind-)mount the //! previously installed system. +use anyhow::{Context, Result, anyhow, bail}; +use serde::Serialize; use std::{ collections::HashSet, ffi::CStr, @@ -19,7 +21,6 @@ use std::{ process::{Command, ExitCode}, }; -use anyhow::{Context, Result, anyhow, bail}; use proxmox_auto_installer::{ answer::{ Answer, FqdnConfig, FqdnExtendedConfig, FqdnSourceMode, PostNotificationHookInfo, @@ -27,6 +28,7 @@ use proxmox_auto_installer::{ }, udevinfo::{UdevInfo, UdevProperties}, }; +use proxmox_installer_common::http::{self, header::HeaderMap}; use proxmox_installer_common::{ options::{Disk, FsType, NetworkOptions}, setup::{ @@ -36,7 +38,6 @@ use proxmox_installer_common::{ sysinfo::SystemDMI, utils::CidrAddress, }; -use serde::Serialize; /// Information about the system boot status. #[derive(Serialize)] @@ -536,11 +537,7 @@ impl PostHookInfo { .map(|v| { // /proc/version: "Linux version 6.17.2-1-pve (...) #1 SMP ..." // extract everything after the second space - v.splitn(3, ' ') - .nth(2) - .unwrap_or("") - .trim() - .to_owned() + v.splitn(3, ' ').nth(2).unwrap_or("").trim().to_owned() }) .unwrap_or_default(); @@ -640,16 +637,12 @@ impl PostHookInfo { sockets.insert(value); } // x86: "flags", ARM64: "Features" - Some((key, value)) - if key.trim() == "flags" - || key.trim() == "Features" => - { + Some((key, value)) if key.trim() == "flags" || key.trim() == "Features" => { value.trim().clone_into(&mut result.flags); } // x86: "model name", ARM64: "CPU implementer" Some((key, value)) - if key.trim() == "model name" - || key.trim() == "CPU implementer" => + if key.trim() == "model name" || key.trim() == "CPU implementer" => { if result.model.is_empty() { value.trim().clone_into(&mut result.model); @@ -727,9 +720,10 @@ fn do_main() -> Result<()> { ); } - proxmox_installer_common::http::post( + http::post( url, cert_fingerprint.as_deref(), + HeaderMap::new(), serde_json::to_string(&info)?, )?; } else { -- 2.53.0