Release v1.1.0

This commit is contained in:
thakares committed 2026-09-02 15:19:19 +05:30
1 parent 34227efd2b
commit edc710cbd2
46 files changed
+5324 -190

No files matched your search

+29
View File
@@ -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]
+73 -1
View File
@@ -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)]