Skip to content

Commit

Permalink
Merge pull request #51 from wa5i/utils
Browse files Browse the repository at this point in the history
Added the  implementation of the salt and the CIDR tool.
  • Loading branch information
InfoHunter authored May 9, 2024
2 parents b823ef1 + e5a516c commit 5afb067
Show file tree
Hide file tree
Showing 8 changed files with 870 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ ureq = "2.9"
glob = "0.3"
serde_asn1_der = "0.8"
base64 = "0.22"
ipnetwork = "0.20"

[features]
storage_mysql = ["diesel", "r2d2", "r2d2-diesel"]
Expand Down
11 changes: 11 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,17 @@ pub enum RvError {
#[error("RwLock was poisoned (writing)")]
ErrRwLockWritePoison,

#[error("Some net addr parse error happened, {:?}", .source)]
AddrParseError {
#[from]
source: std::net::AddrParseError,
},
#[error("Some ipnetwork error happened, {:?}", .source)]
IpNetworkError {
#[from]
source: ipnetwork::IpNetworkError,
},

/// Database Errors Begin
///
#[error("Database type is not support now. Please try postgressql or mysql again.")]
Expand Down
309 changes: 309 additions & 0 deletions src/utils/cidr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
//! This module is a Rust replica of
//! https://github.com/hashicorp/vault/blob/main/sdk/helper/cidrutil/cidr.go
use std::{
str::FromStr,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
collections::HashSet,
};

use ipnetwork::IpNetwork;

use super::{
sock_addr::{new_sock_addr, SockAddrType, SockAddr},
};

use crate::errors::RvError;

pub fn is_ip_addr(addr: &dyn SockAddr) -> bool {
(addr.sock_addr_type() as u8 & SockAddrType::IP as u8) != 0
}

pub fn remote_addr_is_ok(remote_addr: &str, bound_cidrs: &[Box<dyn SockAddr>]) -> bool {
if bound_cidrs.len() == 0 {
return true;
}

if let Ok(addr) = new_sock_addr(remote_addr) {
for cidr in bound_cidrs.iter() {
if is_ip_addr(cidr.as_ref()) && cidr.contains(addr.as_ref()) {
return true;
}
}
}

false
}

pub fn ip_belongs_to_cidr(ip_addr: &str, cidr: &str) -> Result<bool, RvError> {
if ip_addr == "" {
return Err(RvError::ErrResponse("missing IP address".to_string()));
}

let ip = IpAddr::from_str(ip_addr)?;
let ipnet = IpNetwork::from_str(cidr)?;

Ok(ipnet.contains(ip))
}

pub fn ip_belongs_to_cidrs(ip_addr: &str, cidrs: &[&str]) -> Result<bool, RvError> {
if ip_addr == "" {
return Err(RvError::ErrResponse("missing IP address".to_string()));
}

if cidrs.len() == 0 {
return Err(RvError::ErrResponse("missing CIDR blocks to be checked against".to_string()));
}

for cidr in cidrs.iter() {
if ip_belongs_to_cidr(ip_addr, cidr)? {
return Ok(true);
}
}

Ok(false)
}

pub fn validate_cidr_string(cidr_list: &str, separator: &str) -> Result<bool, RvError> {
if cidr_list == "" {
return Err(RvError::ErrResponse("missing CIDR list that needs validation".to_string()));
}

if separator == "" {
return Err(RvError::ErrResponse("missing separator".to_string()));
}

let cidrs_set: HashSet<&str> = cidr_list.split(separator)
.map(|cidr| cidr.trim())
.filter(|cidr| !cidr.is_empty())
.collect();

let cidrs: Vec<&str> = cidrs_set.into_iter().collect();

validate_cidrs(&cidrs)
}

pub fn validate_cidrs(cidrs: &[&str]) -> Result<bool, RvError> {
if cidrs.len() == 0 {
return Err(RvError::ErrResponse("missing CIDR blocks that needs validation".to_string()));
}

for cidr in cidrs.iter() {
let _ = IpNetwork::from_str(cidr)?;
}

Ok(true)
}

pub fn subset(cidr1: &str, cidr2: &str) -> Result<bool, RvError> {
if cidr1 == "" {
return Err(RvError::ErrResponse("missing CIDR to be checked against".to_string()));
}

if cidr2 == "" {
return Err(RvError::ErrResponse("missing CIDR that needs to be checked".to_string()));
}

let ipnet1 = IpNetwork::from_str(cidr1)?;
let mask_len1 = ipnet1.prefix();

if !is_ip_addr_zero(&ipnet1.ip()) && mask_len1 == 0 {
return Err(RvError::ErrResponse("CIDR to be checked against is not in its canonical form".to_string()));
}

let ipnet2 = IpNetwork::from_str(cidr2)?;
let mask_len2 = ipnet2.prefix();

if !is_ip_addr_zero(&ipnet2.ip()) && mask_len2 == 0 {
return Err(RvError::ErrResponse("CIDR that needs to be checked is not in its canonical form".to_string()));
}

/*
* If the mask length of the CIDR that needs to be checked is smaller
* then the mask length of the CIDR to be checked against, then the
* former will encompass more IPs than the latter, and hence can't be a
* subset of the latter.
*/
if mask_len2 < mask_len1 {
return Ok(false);
}

Ok(ipnet1.contains(ipnet2.ip()))
}

/*
* subset_blocks checks if each CIDR block of a given set of CIDR blocks, is a
* subset of at least one CIDR block belonging to another set of CIDR blocks.
* First parameter is the set of CIDR blocks to check against and the second
* parameter is the set of CIDR blocks that needs to be checked.
*/
pub fn subset_blocks(cidr_blocks1: &[&str], cidr_blocks2: &[&str]) -> Result<bool, RvError> {
if cidr_blocks1.len() == 0 {
return Err(RvError::ErrResponse("missing CIDR blocks to be checked against".to_string()));
}

if cidr_blocks2.len() == 0 {
return Err(RvError::ErrResponse("missing CIDR blocks that needs to be checked".to_string()));
}

// Check if all the elements of cidr_blocks2 is a subset of at least one
// element of cidr_blocks1
for cidr_block2 in cidr_blocks2.iter() {
let mut is_subset = false;
for cidr_block1 in cidr_blocks1.iter() {
if subset(cidr_block1, cidr_block2)? {
is_subset = true;
break;
}
}

if !is_subset {
return Ok(false);
}
}

Ok(true)
}

fn is_ip_addr_zero(ip_addr: &IpAddr) -> bool {
match *ip_addr {
IpAddr::V4(addr) => addr == Ipv4Addr::UNSPECIFIED,
IpAddr::V6(addr) => addr == Ipv6Addr::UNSPECIFIED,
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_cidr_ip_belongs_to_cidr() {
let ip = "192.168.25.30";
let cidr = "192.168.25.30/16";
let belongs = ip_belongs_to_cidr(ip, cidr);
assert!(belongs.is_ok());
assert!(belongs.unwrap());

let ip = "10.197.192.6";
let cidr = "10.197.192.0/18";
let belongs = ip_belongs_to_cidr(ip, cidr);
assert!(belongs.is_ok());
assert!(belongs.unwrap());

let ip = "192.168.25.30";
let cidr = "192.168.26.30/24";
let belongs = ip_belongs_to_cidr(ip, cidr);
assert!(belongs.is_ok());
assert!(!belongs.unwrap());

let ip = "192.168.25.30.100";
let cidr = "192.168.26.30/24";
let belongs = ip_belongs_to_cidr(ip, cidr);
assert!(belongs.is_err());
}

#[test]
fn test_cidr_ip_belongs_to_cidrs() {
let ip = "192.168.27.29";
let cidrs = vec!["172.169.100.200/18", "192.168.0.0/16", "10.10.20.20/24"];
let belongs = ip_belongs_to_cidrs(ip, &cidrs);
assert!(belongs.is_ok());
assert!(belongs.unwrap());

let ip = "192.168.27.29";
let cidrs = vec!["172.169.100.200/18", "192.168.0.0.0/16", "10.10.20.20/24"];
let belongs = ip_belongs_to_cidrs(ip, &cidrs);
assert!(belongs.is_err());

let ip = "30.40.50.60";
let cidrs = vec!["172.169.100.200/18", "192.168.0.0/16", "10.10.20.20/24"];
let belongs = ip_belongs_to_cidrs(ip, &cidrs);
assert!(belongs.is_ok());
assert!(!belongs.unwrap());
}

#[test]
fn test_cidr_validate_cidr_string() {
let cidr = "172.169.100.200/18,192.168.0.0/16,10.10.20.20/24";
let valid = validate_cidr_string(cidr, ",");
assert!(valid.is_ok());
assert!(valid.unwrap());

let cidr = "172.169.100.200,192.168.0.0/16,10.10.20.20/24";
let valid = validate_cidr_string(cidr, ",");
assert!(valid.is_ok());
assert!(valid.unwrap());

let cidr = "172.169.100.200/18,192.168.0.0.0/16,10.10.20.20/24";
let valid = validate_cidr_string(cidr, ",");
assert!(valid.is_err());
}

#[test]
fn test_cidr_validate_cidrs() {
let cidrs = vec!["172.169.100.200/18", "192.168.0.0/16", "10.10.20.20/24"];
let valid = validate_cidrs(&cidrs);
assert!(valid.is_ok());
assert!(valid.unwrap());

let cidrs = vec!["172.169.100.200", "192.168.0.0/16", "10.10.20.20/24"];
let valid = validate_cidrs(&cidrs);
assert!(valid.is_ok());
assert!(valid.unwrap());

let cidrs = vec!["172.169.100.200/18", "192.168.0.0.0/16", "10.10.20.20/24"];
let valid = validate_cidrs(&cidrs);
assert!(valid.is_err());
}

#[test]
fn test_cidr_subset() {
let cidr1 = "192.168.27.29/24";
let cidr2 = "192.168.27.29/24";
let ret = subset(cidr1, cidr2);
assert!(ret.is_ok());
assert!(ret.unwrap());

let cidr1 = "192.168.27.29/16";
let cidr2 = "192.168.27.29/24";
let ret = subset(cidr1, cidr2);
assert!(ret.is_ok());
assert!(ret.unwrap());
let ret = subset(cidr2, cidr1);
assert!(ret.is_ok());
assert!(!ret.unwrap());

let cidr1 = "192.168.0.128/25";
let cidr2 = "192.168.0.0/24";
let ret = subset(cidr1, cidr2);
assert!(ret.is_ok());
assert!(!ret.unwrap());
let ret = subset(cidr2, cidr1);
assert!(ret.is_ok());
assert!(ret.unwrap());
}

#[test]
fn test_cidr_subset_blocks() {
let cidrs1 = vec!["192.168.27.29/16", "172.245.30.40/24", "10.20.30.40/30"];
let cidrs2 = vec!["192.168.27.29/20", "172.245.30.40/25", "10.20.30.40/32"];
let ret = subset_blocks(&cidrs1, &cidrs2);
assert!(ret.is_ok());
assert!(ret.unwrap());

let cidrs1 = vec!["192.168.27.29/16", "172.245.30.40/25", "10.20.30.40/30"];
let cidrs2 = vec!["192.168.27.29/20", "172.245.30.40/24", "10.20.30.40/32"];
let ret = subset_blocks(&cidrs1, &cidrs2);
assert!(ret.is_ok());
assert!(!ret.unwrap());
}

#[test]
fn test_cidr_remote_addr_is_ok() {
let addr = new_sock_addr("127.0.0.1/8");
assert!(addr.is_ok());
let bound_cidrs = vec![addr.unwrap()];
assert!(!remote_addr_is_ok("123.0.0.1", &bound_cidrs));
assert!(remote_addr_is_ok("127.0.0.1", &bound_cidrs));
}
}
Loading

0 comments on commit 5afb067

Please sign in to comment.