Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ http = []
glob = ["dep:globset"]
graph = []
jsonschema = ["dep:jsonschema"]
net = []
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
Expand All @@ -49,6 +50,7 @@ full-opa = [
"hex",
"http",
"jsonschema",
"net",
"opa-runtime",
"regex",
"semver",
Expand Down
5 changes: 5 additions & 0 deletions src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ mod glob;
mod graph;
#[cfg(feature = "http")]
mod http;
#[cfg(feature = "net")]
mod net;

pub mod numbers;
mod objects;
#[cfg(feature = "opa-runtime")]
Expand Down Expand Up @@ -80,6 +83,8 @@ lazy_static! {
//graphql::register(&mut m);
#[cfg(feature = "http")]
http::register(&mut m);
#[cfg(feature = "net")]
net::register(&mut m);
//net::register(&mut m);
#[cfg(feature = "uuid")]
uuid::register(&mut m);
Expand Down
91 changes: 91 additions & 0 deletions src/builtins/net.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use core::net::IpAddr;
use std::format;
use std::sync::Arc;

use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::ensure_args_count;
use crate::lexer::Span;
use crate::value::Value;

use anyhow::{anyhow, bail, Result};

use super::utils::ensure_string;

pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("net.cidr_is_valid", (cidr_is_valid, 1));
}

/// Checks if a CIDR string is valid or invalid. Uses the
/// `net::IpAddr` type to determine if the string is a valid IP,
/// and checks to ensure that the mask is in bounds for the parsed
/// IP address type (v4 or v6).
pub fn cidr_is_valid(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
ensure_args_count(span, "cidr_is_valid", params, args, 1)?;
let cidr = ensure_string("cidr_is_valid", &params[0], &args[0])?;

is_valid_cidr(cidr).map_err(|e| span.error(&format!("{}", e)))
Comment thread Fixed
}

fn is_valid_cidr(cidr: Arc<str>) -> Result<Value> {
let Some((ip_addr, prefix_len)) = cidr.split_once("/") else {
bail!("invalid CIDR")
};
match ip_addr.parse::<IpAddr>() {
Ok(addr) => {
let mask = prefix_len
.parse::<i16>()
.map_err(|_| anyhow!("failed to parse prefix length"))?;

match addr {
IpAddr::V4(_) => {
if !(0..=32).contains(&mask) {
bail!("invalid CIDR: {} is not a valid IPv4 CIDR mask", &mask)
}
}
IpAddr::V6(_) => {
if !(0..=128).contains(&mask) {
bail!("invalid CIDR: {} is not a valid IPv6 CIDR mask", &mask)
}
}
}
Ok(Value::Bool(true))
}
Err(_) => bail!(
"Invalid CIDR: {} could not be parsed as a IPv4 or IPv6 address",
ip_addr
),
}
}

#[cfg(test)]
mod net_tests {
use super::*;
use std::format;
use std::vec::Vec;

#[test]
fn test_cidr_is_valid() {
let valids = Vec::from(["127.0.0.1/32", "10.0.0.0/8", "0.1.2.3/32", "::1/128"]);
let invalids = Vec::from(["256.0.0.0/8", "127.0.0.1/33", "::1/129"]);

for cidr in valids {
assert_eq!(
is_valid_cidr(Arc::from(cidr)).unwrap(),
Value::Bool(true),
"Valid CIDR {} deemed invalid",
cidr
);
}

for cidr in invalids {
is_valid_cidr(Arc::from(cidr))
.expect_err(format!("Invalid CIDR {} deemed valid", cidr).as_str());
}
}
}
Loading