Release v1.1.0
This commit is contained in:
1 parent
34227efd2b
commit
edc710cbd2
46 files changed
+5324
-190
No files matched your search
@@ -43,6 +43,26 @@ pub fn generate_keypair() -> (WireGuardPrivateKey, WireGuardPublicKey) {
|
||||
)
|
||||
}
|
||||
|
||||
/// Derive a WireGuard public key (x25519) from a base64-encoded private key.
|
||||
pub fn derive_public_key(private_key_b64: &str) -> Result<WireGuardPublicKey> {
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
let key_bytes = STANDARD
|
||||
.decode(private_key_b64.trim())
|
||||
.map_err(|e| Nx9Error::Validation(format!("invalid base64 private key: {e}")))?;
|
||||
if key_bytes.len() != 32 {
|
||||
return Err(Nx9Error::Validation(
|
||||
"private key must be exactly 32 bytes (256 bits)".to_string(),
|
||||
));
|
||||
}
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes.copy_from_slice(&key_bytes);
|
||||
let secret = StaticSecret::from(bytes);
|
||||
let public = PublicKey::from(&secret);
|
||||
Ok(WireGuardPublicKey::new(STANDARD.encode(public.as_bytes())))
|
||||
}
|
||||
|
||||
/// Generate a WireGuard preshared key (32 random bytes, base64).
|
||||
pub fn generate_preshared_key() -> WireGuardPresharedKey {
|
||||
use base64::Engine;
|
||||
@@ -106,6 +126,15 @@ mod tests {
|
||||
let (priv_key, pub_key) = generate_keypair();
|
||||
assert!(!priv_key.as_str().is_empty());
|
||||
assert!(!pub_key.as_str().is_empty());
|
||||
|
||||
let derived_pub = derive_public_key(priv_key.as_str()).unwrap();
|
||||
assert_eq!(derived_pub.as_str(), pub_key.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derive_public_key_invalid() {
|
||||
assert!(derive_public_key("not-base64!").is_err());
|
||||
assert!(derive_public_key("dG9vLXNob3J0").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -170,13 +170,52 @@ impl FromStr for PeerProfile {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum InterfaceRole {
|
||||
#[default]
|
||||
Overlay,
|
||||
Upstream,
|
||||
}
|
||||
|
||||
impl InterfaceRole {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Overlay => "overlay",
|
||||
Self::Upstream => "upstream",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InterfaceRole {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for InterfaceRole {
|
||||
type Err = Nx9Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"overlay" => Ok(Self::Overlay),
|
||||
"upstream" => Ok(Self::Upstream),
|
||||
_ => Err(Nx9Error::Validation(format!(
|
||||
"invalid InterfaceRole: {}",
|
||||
s
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Interface {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub role: InterfaceRole,
|
||||
pub private_key: WireGuardPrivateKey,
|
||||
pub public_key: WireGuardPublicKey,
|
||||
pub listen_port: u16,
|
||||
pub listen_port: Option<u16>,
|
||||
pub address_v4: IpNet,
|
||||
pub address_v6: Option<IpNet>,
|
||||
pub mtu: Option<u16>,
|
||||
@@ -285,6 +324,39 @@ impl Peer {
|
||||
|
||||
String::new()
|
||||
}
|
||||
|
||||
/// Returns the effective server-side WireGuard AllowedIPs string for this peer,
|
||||
/// scoped by the containing interface's role.
|
||||
///
|
||||
/// For `InterfaceRole::Upstream`:
|
||||
/// Preserves the provider's configured AllowedIPs (including `0.0.0.0/0` and `::/0`)
|
||||
/// for Generic Netlink cryptokey routing on the upstream interface.
|
||||
///
|
||||
/// For `InterfaceRole::Overlay`:
|
||||
/// Delegates strictly to `server_wireguard_allowed_ips()`, guaranteeing that
|
||||
/// RoadWarrior overlay client AllowedIPs are strictly derived from assigned tunnel IPs
|
||||
/// and full-tunnel routes are never installed as server-side overlay peer AllowedIPs.
|
||||
pub fn server_wireguard_allowed_ips_for_role(&self, role: InterfaceRole) -> String {
|
||||
if role == InterfaceRole::Upstream {
|
||||
let src = self
|
||||
.server_allowed_ips
|
||||
.as_deref()
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.unwrap_or(&self.allowed_ips);
|
||||
let mut valid = Vec::new();
|
||||
for item in src.split(',') {
|
||||
let trimmed = item.trim();
|
||||
if let Ok(net) = trimmed.parse::<IpNet>() {
|
||||
valid.push(net.to_string());
|
||||
}
|
||||
}
|
||||
if !valid.is_empty() {
|
||||
return valid.join(", ");
|
||||
}
|
||||
}
|
||||
|
||||
self.server_wireguard_allowed_ips()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user