From 623a3f019bef06a4414c9ab7113ef44c16b0ac9e Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Thu, 14 Aug 2025 20:36:19 +0000 Subject: [PATCH] fix: apply different network policies based on current target (#97) Fixes #94 Co-authored-by: Ian Letourneau Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/97 Reviewed-by: johnride --- harmony/src/domain/topology/k8s_anywhere.rs | 21 ++- harmony/src/domain/topology/tenant/k8s.rs | 51 ++++---- harmony/src/domain/topology/tenant/mod.rs | 8 +- .../domain/topology/tenant/network_policy.rs | 120 ++++++++++++++++++ 4 files changed, 163 insertions(+), 37 deletions(-) create mode 100644 harmony/src/domain/topology/tenant/network_policy.rs diff --git a/harmony/src/domain/topology/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere.rs index b21f385..f81bef4 100644 --- a/harmony/src/domain/topology/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere.rs @@ -28,7 +28,13 @@ use super::{ PreparationOutcome, Topology, k8s::K8sClient, oberservability::monitoring::AlertReceiver, - tenant::{TenantConfig, TenantManager, k8s::K8sTenantManager}, + tenant::{ + TenantConfig, TenantManager, + k8s::K8sTenantManager, + network_policy::{ + K3dNetworkPolicyStrategy, NetworkPolicyStrategy, NoopNetworkPolicyStrategy, + }, + }, }; #[derive(Clone, Debug)] @@ -250,16 +256,21 @@ impl K8sAnywhereTopology { Ok(Some(state)) } - async fn ensure_k8s_tenant_manager(&self) -> Result<(), String> { + async fn ensure_k8s_tenant_manager(&self, k8s_state: &K8sState) -> Result<(), String> { if self.tenant_manager.get().is_some() { return Ok(()); } self.tenant_manager .get_or_try_init(async || -> Result { - // TOOD: checker si K8s ou K3d/s tenant manager (ref. issue https://git.nationtech.io/NationTech/harmony/issues/94) let k8s_client = self.k8s_client().await?; - Ok(K8sTenantManager::new(k8s_client)) + let network_policy_strategy: Box = match k8s_state.source + { + K8sSource::LocalK3d => Box::new(K3dNetworkPolicyStrategy::new()), + K8sSource::Kubeconfig => Box::new(NoopNetworkPolicyStrategy::new()), + }; + + Ok(K8sTenantManager::new(k8s_client, network_policy_strategy)) }) .await?; @@ -390,7 +401,7 @@ impl Topology for K8sAnywhereTopology { "no K8s client could be found or installed".to_string(), ))?; - self.ensure_k8s_tenant_manager() + self.ensure_k8s_tenant_manager(k8s_state) .await .map_err(PreparationError::new)?; diff --git a/harmony/src/domain/topology/tenant/k8s.rs b/harmony/src/domain/topology/tenant/k8s.rs index f2edb05..8085127 100644 --- a/harmony/src/domain/topology/tenant/k8s.rs +++ b/harmony/src/domain/topology/tenant/k8s.rs @@ -20,24 +20,27 @@ use serde::de::DeserializeOwned; use serde_json::json; use tokio::sync::OnceCell; -use super::{TenantConfig, TenantManager}; +use super::{TenantConfig, TenantManager, network_policy::NetworkPolicyStrategy}; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct K8sTenantManager { k8s_client: Arc, k8s_tenant_config: Arc>, + network_policy_strategy: Box, } impl K8sTenantManager { - pub fn new(client: Arc) -> Self { + pub fn new( + client: Arc, + network_policy_strategy: Box, + ) -> Self { Self { k8s_client: client, k8s_tenant_config: Arc::new(OnceCell::new()), + network_policy_strategy, } } -} -impl K8sTenantManager { fn get_namespace_name(&self, config: &TenantConfig) -> String { config.name.clone() } @@ -218,29 +221,6 @@ impl K8sTenantManager { } ] }, - { - "to": [ - { - "ipBlock": { - "cidr": "10.43.0.1/32", - } - } - ] - }, - { - "to": [ - { - //TODO this ip is from the docker network that k3d is running on - //since k3d does not deploy kube-api-server as a pod it needs to ahve the ip - //address opened up - //need to find a way to automatically detect the ip address from the docker - //network - "ipBlock": { - "cidr": "172.18.0.0/16", - } - } - ] - }, { "to": [ { @@ -410,12 +390,27 @@ impl K8sTenantManager { } } +impl Clone for K8sTenantManager { + fn clone(&self) -> Self { + Self { + k8s_client: self.k8s_client.clone(), + k8s_tenant_config: self.k8s_tenant_config.clone(), + network_policy_strategy: self.network_policy_strategy.clone_box(), + } + } +} + #[async_trait] impl TenantManager for K8sTenantManager { async fn provision_tenant(&self, config: &TenantConfig) -> Result<(), ExecutorError> { let namespace = self.build_namespace(config)?; let resource_quota = self.build_resource_quota(config)?; + let network_policy = self.build_network_policy(config)?; + let network_policy = self + .network_policy_strategy + .adjust_policy(network_policy, config); + let resource_limit_range = self.build_limit_range(config)?; self.ensure_constraints(&namespace)?; diff --git a/harmony/src/domain/topology/tenant/mod.rs b/harmony/src/domain/topology/tenant/mod.rs index e8d41dc..1978563 100644 --- a/harmony/src/domain/topology/tenant/mod.rs +++ b/harmony/src/domain/topology/tenant/mod.rs @@ -1,11 +1,11 @@ pub mod k8s; mod manager; -use std::str::FromStr; - -pub use manager::*; -use serde::{Deserialize, Serialize}; +pub mod network_policy; use crate::data::Id; +pub use manager::*; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // Assuming serde for Scores pub struct TenantConfig { diff --git a/harmony/src/domain/topology/tenant/network_policy.rs b/harmony/src/domain/topology/tenant/network_policy.rs new file mode 100644 index 0000000..5610b85 --- /dev/null +++ b/harmony/src/domain/topology/tenant/network_policy.rs @@ -0,0 +1,120 @@ +use k8s_openapi::api::networking::v1::{ + IPBlock, NetworkPolicy, NetworkPolicyEgressRule, NetworkPolicyPeer, NetworkPolicySpec, +}; + +use super::TenantConfig; + +pub trait NetworkPolicyStrategy: Send + Sync + std::fmt::Debug { + fn clone_box(&self) -> Box; + + fn adjust_policy(&self, policy: NetworkPolicy, config: &TenantConfig) -> NetworkPolicy; +} + +#[derive(Clone, Debug)] +pub struct NoopNetworkPolicyStrategy {} + +impl NoopNetworkPolicyStrategy { + pub fn new() -> Self { + Self {} + } +} + +impl Default for NoopNetworkPolicyStrategy { + fn default() -> Self { + Self::new() + } +} + +impl NetworkPolicyStrategy for NoopNetworkPolicyStrategy { + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + + fn adjust_policy(&self, policy: NetworkPolicy, _config: &TenantConfig) -> NetworkPolicy { + policy + } +} + +#[derive(Clone, Debug)] +pub struct K3dNetworkPolicyStrategy {} + +impl K3dNetworkPolicyStrategy { + pub fn new() -> Self { + Self {} + } +} + +impl Default for K3dNetworkPolicyStrategy { + fn default() -> Self { + Self::new() + } +} + +impl NetworkPolicyStrategy for K3dNetworkPolicyStrategy { + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + + fn adjust_policy(&self, policy: NetworkPolicy, _config: &TenantConfig) -> NetworkPolicy { + let mut egress = policy + .spec + .clone() + .unwrap_or_default() + .egress + .clone() + .unwrap_or_default(); + egress.push(NetworkPolicyEgressRule { + to: Some(vec![NetworkPolicyPeer { + ip_block: Some(IPBlock { + cidr: "172.18.0.0/16".into(), // TODO: query the IP range https://git.nationtech.io/NationTech/harmony/issues/108 + ..Default::default() + }), + ..Default::default() + }]), + ..Default::default() + }); + + NetworkPolicy { + spec: Some(NetworkPolicySpec { + egress: Some(egress), + ..policy.spec.unwrap_or_default() + }), + ..policy + } + } +} + +#[cfg(test)] +mod tests { + use k8s_openapi::api::networking::v1::{ + IPBlock, NetworkPolicy, NetworkPolicyEgressRule, NetworkPolicyPeer, NetworkPolicySpec, + }; + + use super::{K3dNetworkPolicyStrategy, NetworkPolicyStrategy}; + + #[test] + pub fn should_add_ip_block_for_k3d_harmony_server() { + let strategy = K3dNetworkPolicyStrategy::new(); + + let policy = + strategy.adjust_policy(NetworkPolicy::default(), &super::TenantConfig::default()); + + let expected_policy = NetworkPolicy { + spec: Some(NetworkPolicySpec { + egress: Some(vec![NetworkPolicyEgressRule { + to: Some(vec![NetworkPolicyPeer { + ip_block: Some(IPBlock { + cidr: "172.18.0.0/16".into(), + ..Default::default() + }), + ..Default::default() + }]), + ..Default::default() + }]), + ..Default::default() + }), + ..Default::default() + }; + assert_eq!(expected_policy, policy); + } +}