Skip to content

Commit 362769b

Browse files
committed
fix: cross cluster contamination
1 parent 758569d commit 362769b

File tree

6 files changed

+519
-69
lines changed

6 files changed

+519
-69
lines changed

src-tauri/src/k8s/resources.rs

Lines changed: 196 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,24 @@ pub struct K8sListItem {
192192
#[serde(rename_all = "camelCase")]
193193
pub enum WatchEvent {
194194
/// A new resource was created or discovered
195-
Added(K8sListItem),
195+
Added {
196+
item: K8sListItem,
197+
cluster_context: String,
198+
},
196199
/// An existing resource was updated
197-
Modified(K8sListItem),
200+
Modified {
201+
item: K8sListItem,
202+
cluster_context: String,
203+
},
198204
/// A resource was deleted
199-
Deleted(K8sListItem),
205+
Deleted {
206+
item: K8sListItem,
207+
cluster_context: String,
208+
},
200209
/// Initial watch synchronization is complete (no more items)
201-
InitialSyncComplete,
210+
InitialSyncComplete {
211+
cluster_context: String,
212+
},
202213
}
203214

204215
/// Returns all supported Kubernetes resource types organized by category.
@@ -754,20 +765,199 @@ mod tests {
754765
subsets: None,
755766
};
756767

757-
let event = WatchEvent::Added(item);
768+
let event = WatchEvent::Added {
769+
item: item.clone(),
770+
cluster_context: "test-cluster".to_string(),
771+
};
758772
let json = serde_json::to_string(&event).unwrap();
759773
let deserialized: WatchEvent = serde_json::from_str(&json).unwrap();
760774

761775
match deserialized {
762-
WatchEvent::Added(item) => {
776+
WatchEvent::Added { item, cluster_context } => {
763777
assert_eq!(item.metadata.name, Some("test-pod".to_string()));
764778
assert_eq!(item.kind, "Pod");
765779
assert_eq!(item.pod_status.as_ref().unwrap().phase, Some("Running".to_string()));
780+
assert_eq!(cluster_context, "test-cluster");
766781
}
767782
_ => panic!("Expected Added event"),
768783
}
769784
}
770785

786+
#[tokio::test]
787+
async fn test_cross_cluster_event_isolation() {
788+
// Test that watch events include cluster context for proper isolation
789+
use k8s_openapi::api::core::v1::Pod;
790+
791+
let mut pod = Pod::default();
792+
pod.metadata.name = Some("test-pod".to_string());
793+
pod.metadata.namespace = Some("default".to_string());
794+
pod.metadata.uid = Some("test-uid".to_string());
795+
796+
let item = K8sListItem {
797+
metadata: pod.metadata.clone(),
798+
kind: "Pod".to_string(),
799+
api_version: "v1".to_string(),
800+
complete_object: None,
801+
pod_spec: None,
802+
service_spec: None,
803+
config_map_spec: None,
804+
secret_spec: None,
805+
namespace_spec: None,
806+
node_spec: None,
807+
persistent_volume_spec: None,
808+
persistent_volume_claim_spec: None,
809+
endpoints_spec: None,
810+
deployment_spec: None,
811+
replica_set_spec: None,
812+
stateful_set_spec: None,
813+
daemon_set_spec: None,
814+
job_spec: None,
815+
cron_job_spec: None,
816+
ingress_spec: None,
817+
network_policy_spec: None,
818+
endpoint_slice: None,
819+
storage_class_spec: None,
820+
role_spec: None,
821+
role_binding_spec: None,
822+
cluster_role_spec: None,
823+
cluster_role_binding_spec: None,
824+
service_account_spec: None,
825+
pod_disruption_budget_spec: None,
826+
horizontal_pod_autoscaler_spec: None,
827+
pod_status: None,
828+
service_status: None,
829+
namespace_status: None,
830+
node_status: None,
831+
persistent_volume_status: None,
832+
persistent_volume_claim_status: None,
833+
deployment_status: None,
834+
replica_set_status: None,
835+
stateful_set_status: None,
836+
daemon_set_status: None,
837+
job_status: None,
838+
cron_job_status: None,
839+
ingress_status: None,
840+
pod_disruption_budget_status: None,
841+
horizontal_pod_autoscaler_status: None,
842+
subsets: None,
843+
};
844+
845+
// Test events from different clusters
846+
let cluster_a_event = WatchEvent::Added {
847+
item: item.clone(),
848+
cluster_context: "cluster-production".to_string(),
849+
};
850+
851+
let cluster_b_event = WatchEvent::Added {
852+
item: item.clone(),
853+
cluster_context: "cluster-staging".to_string(),
854+
};
855+
856+
// Serialize and verify both events
857+
let json_a = serde_json::to_string(&cluster_a_event).unwrap();
858+
let json_b = serde_json::to_string(&cluster_b_event).unwrap();
859+
860+
// Events should be different due to cluster context
861+
assert_ne!(json_a, json_b, "Events from different clusters should serialize differently");
862+
863+
// Deserialize and verify cluster context is preserved
864+
let deserialized_a: WatchEvent = serde_json::from_str(&json_a).unwrap();
865+
let deserialized_b: WatchEvent = serde_json::from_str(&json_b).unwrap();
866+
867+
match (deserialized_a, deserialized_b) {
868+
(WatchEvent::Added { cluster_context: ctx_a, .. }, WatchEvent::Added { cluster_context: ctx_b, .. }) => {
869+
assert_eq!(ctx_a, "cluster-production");
870+
assert_eq!(ctx_b, "cluster-staging");
871+
assert_ne!(ctx_a, ctx_b, "Cluster contexts should be different");
872+
}
873+
_ => panic!("Both events should be Added events"),
874+
}
875+
}
876+
877+
#[tokio::test]
878+
async fn test_watch_event_cluster_context_required() {
879+
// Test that all WatchEvent variants require cluster context
880+
use k8s_openapi::api::core::v1::Node;
881+
882+
let mut node = Node::default();
883+
node.metadata.name = Some("worker-node-1".to_string());
884+
node.metadata.uid = Some("node-uid".to_string());
885+
886+
let item = K8sListItem {
887+
metadata: node.metadata.clone(),
888+
kind: "Node".to_string(),
889+
api_version: "v1".to_string(),
890+
complete_object: None,
891+
pod_spec: None,
892+
service_spec: None,
893+
config_map_spec: None,
894+
secret_spec: None,
895+
namespace_spec: None,
896+
node_spec: None,
897+
persistent_volume_spec: None,
898+
persistent_volume_claim_spec: None,
899+
endpoints_spec: None,
900+
deployment_spec: None,
901+
replica_set_spec: None,
902+
stateful_set_spec: None,
903+
daemon_set_spec: None,
904+
job_spec: None,
905+
cron_job_spec: None,
906+
ingress_spec: None,
907+
network_policy_spec: None,
908+
endpoint_slice: None,
909+
storage_class_spec: None,
910+
role_spec: None,
911+
role_binding_spec: None,
912+
cluster_role_spec: None,
913+
cluster_role_binding_spec: None,
914+
service_account_spec: None,
915+
pod_disruption_budget_spec: None,
916+
horizontal_pod_autoscaler_spec: None,
917+
pod_status: None,
918+
service_status: None,
919+
namespace_status: None,
920+
node_status: None,
921+
persistent_volume_status: None,
922+
persistent_volume_claim_status: None,
923+
deployment_status: None,
924+
replica_set_status: None,
925+
stateful_set_status: None,
926+
daemon_set_status: None,
927+
job_status: None,
928+
cron_job_status: None,
929+
ingress_status: None,
930+
pod_disruption_budget_status: None,
931+
horizontal_pod_autoscaler_status: None,
932+
subsets: None,
933+
};
934+
935+
let cluster_context = "test-cluster".to_string();
936+
937+
// Test all event types include cluster context
938+
let test_events = vec![
939+
WatchEvent::Added { item: item.clone(), cluster_context: cluster_context.clone() },
940+
WatchEvent::Modified { item: item.clone(), cluster_context: cluster_context.clone() },
941+
WatchEvent::Deleted { item, cluster_context: cluster_context.clone() },
942+
WatchEvent::InitialSyncComplete { cluster_context: cluster_context.clone() },
943+
];
944+
945+
for event in test_events {
946+
let json = serde_json::to_string(&event).unwrap();
947+
let deserialized: WatchEvent = serde_json::from_str(&json).unwrap();
948+
949+
// Verify cluster context is preserved in all event types
950+
let preserved_context = match deserialized {
951+
WatchEvent::Added { cluster_context, .. } => cluster_context,
952+
WatchEvent::Modified { cluster_context, .. } => cluster_context,
953+
WatchEvent::Deleted { cluster_context, .. } => cluster_context,
954+
WatchEvent::InitialSyncComplete { cluster_context } => cluster_context,
955+
};
956+
957+
assert_eq!(preserved_context, "test-cluster", "Cluster context must be preserved in all event types");
958+
}
959+
}
960+
771961
#[test]
772962
fn test_k8s_object_meta_with_labels() {
773963
let mut labels = std::collections::BTreeMap::new();

0 commit comments

Comments
 (0)