diff --git a/Cargo.toml b/Cargo.toml index 5184720..6fba549 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ http = [] glob = ["dep:globset"] graph = [] jsonschema = ["dep:jsonschema"] +net = [] no_std = ["lazy_static/spin_no_std"] opa-runtime = [] regex = ["dep:regex"] @@ -50,6 +51,7 @@ full-opa = [ "hex", "http", "jsonschema", + "net", "opa-runtime", "regex", "semver", diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 73a8c4e..0a300c3 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -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")] @@ -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); diff --git a/src/builtins/net.rs b/src/builtins/net.rs new file mode 100644 index 0000000..874e281 --- /dev/null +++ b/src/builtins/net.rs @@ -0,0 +1,86 @@ +use core::net::IpAddr; +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::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], + args: &[Value], + _strict: bool, +) -> Result { + ensure_args_count(span, "cidr_is_valid", params, args, 1)?; + let cidr = ensure_string("cidr_is_valid", ¶ms[0], &args[0])?; + + Ok(Value::from(is_valid_cidr(cidr))) +} + +fn is_valid_cidr(cidr: Arc) -> bool { + let Some((ip_addr, prefix_len)) = cidr.split_once("/") else { + return false; + }; + match ip_addr.parse::() { + Ok(addr) => { + let Ok(mask) = prefix_len.parse::() else { + return false; + }; + + match addr { + IpAddr::V4(_) => { + if !(0..=32).contains(&mask) { + return false; + } + } + IpAddr::V6(_) => { + if !(0..=128).contains(&mask) { + return false; + } + } + } + true + } + Err(_) => false, + } +} + +#[cfg(test)] +mod net_tests { + use super::*; + 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!( + is_valid_cidr(Arc::from(cidr)), + "Valid CIDR {cidr} deemed invalid" + ); + } + + for cidr in invalids { + assert!( + !is_valid_cidr(Arc::from(cidr)), + "Invalid CIDR {cidr} deemed valid" + ); + } + } +} diff --git a/tests/opa.passing b/tests/opa.passing index 170c0a8..b81fb71 100644 --- a/tests/opa.passing +++ b/tests/opa.passing @@ -45,6 +45,7 @@ v0/jsonremoveidempotent v0/jsonschema v0/negation v0/nestedreferences +v0/netcidrisvalid v0/numbersrange v0/numbersrangestep v0/objectfilter @@ -149,6 +150,7 @@ v1/jsonremoveidempotent v1/jsonschema v1/negation v1/nestedreferences +v1/netcidrisvalid v1/numbersrange v1/numbersrangestep v1/objectfilter