mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: Implement net.cidr_contains builtin (#471)
Major changes: - Implement the `net.cidr_contains` builtin - Enable the v0 and v1 test for `net.cidr_contains` - Add the `netip` crate to standardize CIDR searching and other operations Key Concept: - Allow users to leverage the `net.cidr_contains` builtin to check whether an IPv4 or IPv6 CIDR contains a specified IP address or subnet. Testing: - All tests passing. Signed-off-by: tjons <tylerschade99@gmail.com>
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -657,6 +657,12 @@ dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
@@ -1177,6 +1183,7 @@ dependencies = [
|
||||
"dashmap",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
"mimalloc",
|
||||
|
||||
@@ -33,7 +33,7 @@ glob = ["dep:globset"]
|
||||
graph = []
|
||||
jsonschema = ["dep:jsonschema"]
|
||||
mimalloc = ["dep:mimalloc"]
|
||||
net = []
|
||||
net = ["dep:ipnet"]
|
||||
no_std = ["lazy_static/spin_no_std"]
|
||||
opa-runtime = []
|
||||
regex = ["dep:regex"]
|
||||
@@ -108,6 +108,7 @@ uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-r
|
||||
jsonschema = { version = "0.30.0", default-features = false, optional = true }
|
||||
chrono = { version = "0.4.40", optional = true }
|
||||
chrono-tz = { version = "0.10.1", optional = true }
|
||||
ipnet = { version = "2.11.0", optional = true, default-features = false }
|
||||
|
||||
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
|
||||
# Specify thread_rng for in order to use random_range
|
||||
|
||||
@@ -303,11 +303,9 @@ The following test suites don't pass fully due to missing builtins:
|
||||
- `jwtverifyhs384`
|
||||
- `jwtverifyhs512`
|
||||
- `jwtverifyrsa`
|
||||
- `netcidrcontains`
|
||||
- `netcidrcontainsmatches`
|
||||
- `netcidrexpand`
|
||||
- `netcidrintersects`
|
||||
- `netcidrisvalid`
|
||||
- `netcidrmerge`
|
||||
- `netcidroverlap`
|
||||
- `netlookupipaddr`
|
||||
|
||||
7
bindings/ffi/Cargo.lock
generated
7
bindings/ffi/Cargo.lock
generated
@@ -546,6 +546,12 @@ dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
@@ -960,6 +966,7 @@ dependencies = [
|
||||
"dashmap",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
"mimalloc",
|
||||
|
||||
7
bindings/java/Cargo.lock
generated
7
bindings/java/Cargo.lock
generated
@@ -418,6 +418,12 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@@ -835,6 +841,7 @@ dependencies = [
|
||||
"chrono-tz",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
"mimalloc",
|
||||
|
||||
7
bindings/python/Cargo.lock
generated
7
bindings/python/Cargo.lock
generated
@@ -408,6 +408,12 @@ version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@@ -891,6 +897,7 @@ dependencies = [
|
||||
"chrono-tz",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
"mimalloc",
|
||||
|
||||
7
bindings/ruby/Cargo.lock
generated
7
bindings/ruby/Cargo.lock
generated
@@ -448,6 +448,12 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
@@ -937,6 +943,7 @@ dependencies = [
|
||||
"chrono-tz",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
"mimalloc",
|
||||
|
||||
7
bindings/wasm/Cargo.lock
generated
7
bindings/wasm/Cargo.lock
generated
@@ -411,6 +411,12 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@@ -816,6 +822,7 @@ dependencies = [
|
||||
"chrono-tz",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"ipnet",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
"mimalloc",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use core::net::IpAddr;
|
||||
use ipnet::IpNet;
|
||||
use std::format;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ast::{Expr, Ref};
|
||||
@@ -7,12 +9,13 @@ use crate::builtins::utils::ensure_args_count;
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
|
||||
use anyhow::Result;
|
||||
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));
|
||||
m.insert("net.cidr_contains", (cidr_contains, 2));
|
||||
}
|
||||
|
||||
/// Checks if a CIDR string is valid or invalid. Uses the
|
||||
@@ -59,6 +62,50 @@ fn is_valid_cidr(cidr: Arc<str>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cidr_contains(
|
||||
span: &Span,
|
||||
params: &[Ref<Expr>],
|
||||
args: &[Value],
|
||||
strict: bool,
|
||||
) -> Result<Value> {
|
||||
ensure_args_count(span, "cidr_contains", params, args, 2)?;
|
||||
let cidr = ensure_string("cidr_contains", ¶ms[0], &args[0])?;
|
||||
let cidr_or_ip = ensure_string("cidr_contains", ¶ms[1], &args[1])?;
|
||||
let contains = _cidr_contains(cidr, cidr_or_ip);
|
||||
|
||||
match contains {
|
||||
Ok(r) => Ok(Value::from(r)),
|
||||
// The rego implementation will retur an error in strict mode, see
|
||||
// https://github.com/open-policy-agent/opa/blob/main/v1/test/cases/testdata/v1/netcidrcontains/test-netcidrcontains-0100.yaml
|
||||
// as an example, so we will propagate the error if the builtin is
|
||||
// run in strict mode.
|
||||
Err(e) if strict => bail!(span.error(&format!("{e}"))),
|
||||
// If not in strict mode, an error will result in Undefined.
|
||||
_ => Ok(Value::Undefined),
|
||||
}
|
||||
}
|
||||
|
||||
fn _cidr_contains(cidr: Arc<str>, cidr_or_ip: Arc<str>) -> Result<bool> {
|
||||
let net = cidr
|
||||
.parse::<IpNet>()
|
||||
.map_err(|e| anyhow!("Error parsing {cidr}: {e}"))?;
|
||||
|
||||
if cidr_or_ip.contains("/") {
|
||||
let subnet = cidr_or_ip
|
||||
.parse::<IpNet>()
|
||||
.map_err(|e| anyhow!("Error parsing {cidr_or_ip} as CIDR: {e}"))?;
|
||||
|
||||
return Ok(net.contains(&subnet));
|
||||
}
|
||||
|
||||
// if the caller did not provide a CIDR string, try to parse
|
||||
// the input as an IP address.
|
||||
let subnet = cidr_or_ip
|
||||
.parse::<IpAddr>()
|
||||
.map_err(|e| anyhow!("Error parsing {cidr_or_ip} as IP address: {e}"))?;
|
||||
Ok(net.contains(&subnet))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod net_tests {
|
||||
use super::*;
|
||||
@@ -83,4 +130,58 @@ mod net_tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cidr_contains() {
|
||||
let test_cases: std::vec::IntoIter<(Arc<str>, Arc<str>, bool, bool)> = Vec::from([
|
||||
// Each case is a tuple of (cidr, cidr_or_ip, expected Ok(result), and expected error)
|
||||
(
|
||||
Arc::from("127.0.0.1/32"),
|
||||
Arc::from("127.0.0.1"),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(
|
||||
Arc::from("10.0.0.0/8"),
|
||||
Arc::from("10.10.10.10"),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(
|
||||
Arc::from("10.0.0.0/8"),
|
||||
Arc::from("10.10.10.0/24"),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
(Arc::from("fd00::/16"), Arc::from("fd00::/17"), true, false),
|
||||
(
|
||||
Arc::from("127.0.0.1/32"),
|
||||
Arc::from("127.0.0.2"),
|
||||
false,
|
||||
false,
|
||||
),
|
||||
(Arc::from("10.0.0.0/8"), Arc::from("11.0.0.1"), false, false),
|
||||
(Arc::from("fd00::/16"), Arc::from("fd00::/15"), false, false),
|
||||
(
|
||||
Arc::from("127.0.0.0/8"),
|
||||
Arc::from("not a cidr"),
|
||||
false,
|
||||
true,
|
||||
),
|
||||
])
|
||||
.into_iter();
|
||||
|
||||
for (cidr, sub, result, should_err) in test_cases {
|
||||
let got = _cidr_contains(cidr.clone(), sub.clone());
|
||||
match got {
|
||||
Err(_) if should_err => continue,
|
||||
Ok(res) if res == result => continue,
|
||||
_ => {
|
||||
panic!(
|
||||
"Expected `cidr_contains` for cidr {cidr} and subnet {sub} to be {result}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ v0/jsonschema
|
||||
v0/negation
|
||||
v0/nestedreferences
|
||||
v0/netcidrisvalid
|
||||
v0/netcidrcontains
|
||||
v0/numbersrange
|
||||
v0/numbersrangestep
|
||||
v0/objectfilter
|
||||
@@ -150,6 +151,7 @@ v1/jsonremoveidempotent
|
||||
v1/jsonschema
|
||||
v1/negation
|
||||
v1/nestedreferences
|
||||
v1/netcidrcontains
|
||||
v1/netcidrisvalid
|
||||
v1/numbersrange
|
||||
v1/numbersrangestep
|
||||
@@ -206,4 +208,4 @@ v1/uuid
|
||||
v1/varreferences
|
||||
v1/virtualdocs
|
||||
v1/walkbuiltin
|
||||
v1/withkeyword
|
||||
v1/withkeyword
|
||||
|
||||
Reference in New Issue
Block a user