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 45F3B1FF141 for ; Fri, 13 Feb 2026 11:12:45 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7A93534F5C; Fri, 13 Feb 2026 11:13:32 +0100 (CET) Message-ID: <0c44a626-af78-4bb9-89a3-e5e5c6f6fd1b@proxmox.com> Date: Fri, 13 Feb 2026 11:13:28 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird From: Stefan Hanreich Subject: Re: [pve-devel] [PATCH proxmox 06/11] wireguard: init configuration support crate To: Proxmox VE development discussion , Christoph Heiss References: <20260116153317.1146323-1-c.heiss@proxmox.com> <20260116153317.1146323-7-c.heiss@proxmox.com> Content-Language: en-US In-Reply-To: <20260116153317.1146323-7-c.heiss@proxmox.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.720 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 WEIRD_PORT 0.001 Uses non-standard port number for HTTP Message-ID-Hash: GF6QLA565JXPBUPI4MPAIL5FWTCJJ42G X-Message-ID-Hash: GF6QLA565JXPBUPI4MPAIL5FWTCJJ42G X-MailFrom: s.hanreich@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 VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: comments inline On 1/16/26 4:33 PM, Christoph Heiss wrote: [snip] > +impl PrivateKey { > + /// Length of the raw private key data in bytes. > + pub const RAW_LENGTH: usize = ed25519_dalek::SECRET_KEY_LENGTH; > + > + /// Generates a new private key suitable for use with WireGuard. > + pub fn generate() -> Result { > + generate_key().map(Self) > + } > + > + /// Calculates the public key from the private key. > + pub fn public_key(&self) -> PublicKey { > + PublicKey( > + ed25519_dalek::SigningKey::from_bytes(&self.0) > + .verifying_key() > + .to_bytes(), > + ) > + } > + > + /// Returns the raw private key material. > + #[must_use] > + pub fn raw(&self) -> &ed25519_dalek::SecretKey { > + &self.0 > + } might be better to implement AsRef instead for ergonomics? > + /// Builds a new [`PrivateKey`] from raw key material. > + #[must_use] > + pub fn from_raw(data: ed25519_dalek::SecretKey) -> Self { > + // [`SigningKey`] takes care of correct key clamping. > + Self(SigningKey::from(&data).to_bytes()) > + } > +} > + > +impl From for PrivateKey { > + fn from(value: ed25519_dalek::SecretKey) -> Self { > + Self(value) > + } > +} > + > +/// Preshared key between two WireGuard peers. > +#[derive(Clone, Deserialize, Serialize)] > +#[serde(transparent)] > +pub struct PresharedKey( > + #[serde(with = "proxmox_serde::byte_array_as_base64")] ed25519_dalek::SecretKey, > +); > + > +impl fmt::Debug for PresharedKey { > + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { > + write!(f, "") > + } > +} > + > +impl PresharedKey { > + /// Length of the raw private key data in bytes. > + pub const RAW_LENGTH: usize = ed25519_dalek::SECRET_KEY_LENGTH; > + > + /// Generates a new preshared key suitable for use with WireGuard. > + pub fn generate() -> Result { > + generate_key().map(Self) > + } > + > + /// Returns the raw private key material. > + #[must_use] > + pub fn raw(&self) -> &ed25519_dalek::SecretKey { > + &self.0 > + } > + here too: might be better to implement AsRef instead for ergonomics? > + /// Builds a new [`PrivateKey`] from raw key material. > + #[must_use] > + pub fn from_raw(data: ed25519_dalek::SecretKey) -> Self { > + // [`SigningKey`] takes care of correct key clamping. > + Self(SigningKey::from(&data).to_bytes()) > + } > +} > + > +/// A single WireGuard peer. > +#[derive(Serialize, Debug)] > +#[serde(rename_all = "PascalCase")] > +pub struct WireGuardPeer { > + /// Public key, matching the private key of of the remote peer. > + pub public_key: PublicKey, > + /// Additional key preshared between two peers. Adds an additional layer of symmetric-key > + /// cryptography to be mixed into the already existing public-key cryptography, for > + /// post-quantum resistance. > + pub preshared_key: PresharedKey, > + /// List of IPv4/v6 CIDRs from which incoming traffic for this peer is allowed and to which > + /// outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may be specified for > + /// matching all IPv4 addresses, and ::/0 may be specified for matching all IPv6 addresses. > + #[serde(rename = "AllowedIPs")] > + pub allowed_ips: Vec, potentially missing skip_serializing_if = "Vec::is_empty"? [snip] > +#[cfg(test)] > +mod tests { > + use std::net::Ipv4Addr; > + > + use proxmox_network_types::ip_address::Cidr; > + > + use crate::{PresharedKey, PrivateKey, WireGuardConfig, WireGuardInterface, WireGuardPeer}; > + > + fn mock_private_key(v: u8) -> PrivateKey { > + let base = v * 32; > + PrivateKey((base..base + 32).collect::>().try_into().unwrap()) > + } > + > + fn mock_preshared_key(v: u8) -> PresharedKey { > + let base = v * 32; > + PresharedKey((base..base + 32).collect::>().try_into().unwrap()) > + } > + > + #[test] > + fn single_peer() { > + let config = WireGuardConfig { > + interface: WireGuardInterface { > + private_key: mock_private_key(0), > + listen_port: Some(51820), > + fw_mark: Some(127), > + }, > + peers: vec![WireGuardPeer { > + public_key: mock_private_key(1).public_key(), > + preshared_key: mock_preshared_key(1), > + allowed_ips: vec![Cidr::new_v4(Ipv4Addr::new(192, 168, 0, 0), 24).unwrap()], > + endpoint: Some("foo.example.com:51820".parse().unwrap()), > + persistent_keepalive: Some(25), > + }], > + }; > + > + pretty_assertions::assert_eq!( > + config.to_raw_config().unwrap(), > + "[Interface] > +PrivateKey = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8= > +ListenPort = 51820 > +FwMark = 127 > + > +[Peer] > +PublicKey = Kay64UG8yvCyLhqU000LxzYeUm0L/hLIl5S8kyKWbdc= > +PresharedKey = ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8= > +AllowedIPs = 192.168.0.0/24 > +Endpoint = foo.example.com:51820 > +PersistentKeepalive = 25 > +" > + ); > + } > + > + #[test] > + fn multiple_peers() { > + let config = WireGuardConfig { > + interface: WireGuardInterface { > + private_key: mock_private_key(0), > + listen_port: Some(51820), > + fw_mark: None, > + }, > + peers: vec![ > + WireGuardPeer { > + public_key: mock_private_key(1).public_key(), > + preshared_key: mock_preshared_key(1), > + allowed_ips: vec![Cidr::new_v4(Ipv4Addr::new(192, 168, 0, 0), 24).unwrap()], > + endpoint: Some("foo.example.com:51820".parse().unwrap()), > + persistent_keepalive: None, > + }, > + WireGuardPeer { > + public_key: mock_private_key(2).public_key(), > + preshared_key: mock_preshared_key(2), > + allowed_ips: vec![Cidr::new_v4(Ipv4Addr::new(192, 168, 1, 0), 24).unwrap()], > + endpoint: None, > + persistent_keepalive: Some(25), > + }, > + WireGuardPeer { > + public_key: mock_private_key(3).public_key(), > + preshared_key: mock_preshared_key(3), > + allowed_ips: vec![Cidr::new_v4(Ipv4Addr::new(192, 168, 2, 0), 24).unwrap()], > + endpoint: None, > + persistent_keepalive: None, > + }, > + ], > + }; > + > + pretty_assertions::assert_eq!( > + config.to_raw_config().unwrap(), > + "[Interface] > +PrivateKey = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8= > +ListenPort = 51820 > + > +[Peer] > +PublicKey = Kay64UG8yvCyLhqU000LxzYeUm0L/hLIl5S8kyKWbdc= > +PresharedKey = ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8= > +AllowedIPs = 192.168.0.0/24 > +Endpoint = foo.example.com:51820 > + > +[Peer] > +PublicKey = JUO5L/EJVRFHatyDadtt3JM2ZaEZeN2hQE7hBmypVZ0= > +PresharedKey = QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl8= > +AllowedIPs = 192.168.1.0/24 > +PersistentKeepalive = 25 > + > +[Peer] > +PublicKey = F0VTtFbd38aQjsqxwQH+arIeK6oGF3lbfUOmNIKZP9U= > +PresharedKey = YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8= > +AllowedIPs = 192.168.2.0/24 > +" > + ); > + } > +} maybe add some test cases for missing properties as well (or adapt the existing ones)?