feat: add support for custom CIDR ingress/egress rules
All checks were successful
Run Check Script / check (push) Successful in 1m47s
Run Check Script / check (pull_request) Successful in 1m48s

- Added `additional_allowed_cidr_ingress` and `additional_allowed_cidr_egress` fields to `TenantNetworkPolicy` to allow specifying custom CIDR blocks for network access.
- Updated K8sTenantManager to parse and apply these CIDR rules to NetworkPolicy ingress and egress rules.
- Added `cidr` dependency to `harmony_macros` and a custom proc macro `cidrv4` to easily parse CIDR strings.
- Updated TenantConfig to default inter tenant and internet egress to deny all and added default empty vectors for CIDR ingress and egress.
- Updated ResourceLimits to implement default.
This commit is contained in:
Jean-Gabriel Gill-Couture 2025-06-11 15:18:43 -04:00
parent ef5ec4a131
commit acfcc77040
6 changed files with 124 additions and 16 deletions

4
Cargo.lock generated
View File

@ -394,6 +394,9 @@ name = "cidr"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdf600c45bd958cf2945c445264471cca8b6c8e67bc87b71affd6d7e5682621"
dependencies = [
"serde",
]
[[package]]
name = "cipher"
@ -1476,6 +1479,7 @@ dependencies = [
name = "harmony_macros"
version = "0.1.0"
dependencies = [
"cidr",
"harmony_types",
"quote",
"serde",

View File

@ -24,7 +24,7 @@ env_logger = "0.11.5"
derive-new = "0.7.0"
async-trait = "0.1.82"
tokio = { version = "1.40.0", features = ["io-std", "fs", "macros", "rt-multi-thread"] }
cidr = "0.2.3"
cidr = { features = ["serde"], version = "0.2" }
russh = "0.45.0"
russh-keys = "0.45.0"
rand = "0.8.5"

View File

@ -8,7 +8,7 @@ use async_trait::async_trait;
use derive_new::new;
use k8s_openapi::api::{
core::v1::{Namespace, ResourceQuota},
networking::v1::NetworkPolicy,
networking::v1::{NetworkPolicy, NetworkPolicyEgressRule, NetworkPolicyIngressRule},
};
use kube::Resource;
use log::{debug, info, warn};
@ -191,12 +191,80 @@ impl K8sTenantManager {
}
});
serde_json::from_value(network_policy).map_err(|e| {
ExecutorError::ConfigurationError(format!(
"Could not build TenantManager NetworkPolicy. {}",
e
))
})
let mut network_policy: NetworkPolicy =
serde_json::from_value(network_policy).map_err(|e| {
ExecutorError::ConfigurationError(format!(
"Could not build TenantManager NetworkPolicy. {}",
e
))
})?;
config
.network_policy
.additional_allowed_cidr_ingress
.iter()
.try_for_each(|c| -> Result<(), ExecutorError> {
let rule = serde_json::from_value::<NetworkPolicyIngressRule>(json!({
"from": [
{
"ipBlock": {
"cidr": c.to_string(),
}
}
]
}))
.map_err(|e| {
ExecutorError::ConfigurationError(format!(
"Could not build TenantManager NetworkPolicyIngressRule. {}",
e
))
})?;
network_policy
.spec
.as_mut()
.unwrap()
.ingress
.as_mut()
.unwrap()
.push(rule);
Ok(())
})?;
config
.network_policy
.additional_allowed_cidr_egress
.iter()
.try_for_each(|c| -> Result<(), ExecutorError> {
let rule = serde_json::from_value::<NetworkPolicyEgressRule>(json!({
"to": [
{
"ipBlock": {
"cidr": c.to_string(),
}
}
]
}))
.map_err(|e| {
ExecutorError::ConfigurationError(format!(
"Could not build TenantManager NetworkPolicyEgressRule. {}",
e
))
})?;
network_policy
.spec
.as_mut()
.unwrap()
.egress
.as_mut()
.unwrap()
.push(rule);
Ok(())
})?;
Ok(network_policy)
}
}

View File

@ -27,22 +27,18 @@ impl Default for TenantConfig {
Self {
name: format!("tenant_{id}"),
id,
resource_limits: ResourceLimits {
cpu_request_cores: 4.0,
cpu_limit_cores: 4.0,
memory_request_gb: 4.0,
memory_limit_gb: 4.0,
storage_total_gb: 20.0,
},
resource_limits: ResourceLimits::default(),
network_policy: TenantNetworkPolicy {
default_inter_tenant_ingress: InterTenantIngressPolicy::DenyAll,
default_internet_egress: InternetEgressPolicy::AllowAll,
additional_allowed_cidr_ingress: vec![],
additional_allowed_cidr_egress: vec![],
},
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceLimits {
/// Requested/guaranteed CPU cores (e.g., 2.0).
pub cpu_request_cores: f32,
@ -58,6 +54,18 @@ pub struct ResourceLimits {
pub storage_total_gb: f32,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
cpu_request_cores: 4.0,
cpu_limit_cores: 4.0,
memory_request_gb: 4.0,
memory_limit_gb: 4.0,
storage_total_gb: 20.0,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TenantNetworkPolicy {
/// Policy for ingress traffic originating from other tenants within the same Harmony-managed environment.
@ -65,6 +73,20 @@ pub struct TenantNetworkPolicy {
/// Policy for egress traffic destined for the public internet.
pub default_internet_egress: InternetEgressPolicy,
pub additional_allowed_cidr_ingress: Vec<cidr::Ipv4Cidr>,
pub additional_allowed_cidr_egress: Vec<cidr::Ipv4Cidr>,
}
impl Default for TenantNetworkPolicy {
fn default() -> Self {
TenantNetworkPolicy {
default_inter_tenant_ingress: InterTenantIngressPolicy::DenyAll,
default_internet_egress: InternetEgressPolicy::DenyAll,
additional_allowed_cidr_ingress: vec![],
additional_allowed_cidr_egress: vec![],
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -14,6 +14,7 @@ quote = "1.0.37"
serde = "1.0.217"
serde_yaml = "0.9.34"
syn = "2.0.90"
cidr.workspace = true
[dev-dependencies]
serde = { version = "1.0.217", features = ["derive"] }

View File

@ -132,3 +132,16 @@ pub fn ingress_path(input: TokenStream) -> TokenStream {
false => panic!("Invalid ingress path"),
}
}
#[proc_macro]
pub fn cidrv4(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
let cidr_str = input.value();
if let Ok(_) = cidr_str.parse::<cidr::Ipv4Cidr>() {
let expanded = quote! { #cidr_str.parse::<cidr::Ipv4Cidr>().unwrap() };
return TokenStream::from(expanded);
}
panic!("Invalid IPv4 CIDR : {}", cidr_str);
}