From fe1e4c995701bd7fe6818283e246f9021bcaeee3 Mon Sep 17 00:00:00 2001 From: Daniel Grunberger Date: Thu, 14 Dec 2023 10:26:20 +0200 Subject: [PATCH] merge ports Signed-off-by: Daniel Grunberger --- .../networkpolicy/networkpolicy.go | 141 +++++++ .../networkpolicy/networkpolicy_test.go | 389 ++++++++++++++++-- 2 files changed, 491 insertions(+), 39 deletions(-) diff --git a/pkg/apis/softwarecomposition/networkpolicy/networkpolicy.go b/pkg/apis/softwarecomposition/networkpolicy/networkpolicy.go index 46d180d74..6e8308e9d 100644 --- a/pkg/apis/softwarecomposition/networkpolicy/networkpolicy.go +++ b/pkg/apis/softwarecomposition/networkpolicy/networkpolicy.go @@ -2,6 +2,7 @@ package networkpolicy import ( "net" + "sort" "strings" "golang.org/x/exp/maps" @@ -81,11 +82,151 @@ func GenerateNetworkPolicy(networkNeighbors softwarecomposition.NetworkNeighbors } + networkPolicy.Spec.Egress = mergeEgressRulesByPorts(networkPolicy.Spec.Egress) + + networkPolicy.Spec.Ingress = mergeIngressRulesByPorts(networkPolicy.Spec.Ingress) + generatedNetworkPolicy.Spec = networkPolicy return generatedNetworkPolicy, nil } +func mergeIngressRulesByPorts(rules []softwarecomposition.NetworkPolicyIngressRule) []softwarecomposition.NetworkPolicyIngressRule { + type PortProtocolKey struct { + Port int32 + Protocol v1.Protocol + } + + merged := make(map[PortProtocolKey][]softwarecomposition.NetworkPolicyPeer) + var keys []PortProtocolKey + var nonMergedRules []softwarecomposition.NetworkPolicyIngressRule + + for _, rule := range rules { + hasSelector := false + for _, peer := range rule.From { + if peer.PodSelector != nil || peer.NamespaceSelector != nil { + hasSelector = true + break + } + } + + if hasSelector { + nonMergedRules = append(nonMergedRules, rule) + continue + } + + for _, port := range rule.Ports { + key := PortProtocolKey{Port: *port.Port, Protocol: *port.Protocol} + if _, exists := merged[key]; !exists { + keys = append(keys, key) + } + for _, peer := range rule.From { + if peer.IPBlock != nil { + merged[key] = append(merged[key], peer) + } + } + } + } + + // Sort the keys + sort.Slice(keys, func(i, j int) bool { + if keys[i].Port != keys[j].Port { + return keys[i].Port < keys[j].Port + } + return keys[i].Protocol < keys[j].Protocol + }) + + // Construct merged rules using sorted keys + var mergedRules []softwarecomposition.NetworkPolicyIngressRule + for i := range keys { + peers := merged[keys[i]] + sort.Slice(peers, func(i, j int) bool { + if peers[i].IPBlock != nil && peers[j].IPBlock != nil { + return peers[i].IPBlock.CIDR < peers[j].IPBlock.CIDR + } + return false // Keep the order as is if IPBlock is nil + }) + + mergedRules = append(mergedRules, softwarecomposition.NetworkPolicyIngressRule{ + Ports: []softwarecomposition.NetworkPolicyPort{{Protocol: &keys[i].Protocol, Port: &keys[i].Port}}, + From: peers, + }) + } + + // Combine merged and non-merged rules + mergedRules = append(mergedRules, nonMergedRules...) + + return mergedRules +} + +func mergeEgressRulesByPorts(rules []softwarecomposition.NetworkPolicyEgressRule) []softwarecomposition.NetworkPolicyEgressRule { + type PortProtocolKey struct { + Port int32 + Protocol v1.Protocol + } + + merged := make(map[PortProtocolKey][]softwarecomposition.NetworkPolicyPeer) + var keys []PortProtocolKey + var nonMergedRules []softwarecomposition.NetworkPolicyEgressRule + + for _, rule := range rules { + hasSelector := false + for _, peer := range rule.To { + if peer.PodSelector != nil || peer.NamespaceSelector != nil { + hasSelector = true + break + } + } + + if hasSelector { + nonMergedRules = append(nonMergedRules, rule) + continue + } + + for _, port := range rule.Ports { + key := PortProtocolKey{Port: *port.Port, Protocol: *port.Protocol} + if _, exists := merged[key]; !exists { + keys = append(keys, key) + } + for _, peer := range rule.To { + if peer.IPBlock != nil { + merged[key] = append(merged[key], peer) + } + } + } + } + + // Sort the keys + sort.Slice(keys, func(i, j int) bool { + if keys[i].Port != keys[j].Port { + return keys[i].Port < keys[j].Port + } + return keys[i].Protocol < keys[j].Protocol + }) + + // Construct merged rules using sorted keys + var mergedRules []softwarecomposition.NetworkPolicyEgressRule + for i := range keys { + peers := merged[keys[i]] + sort.Slice(peers, func(i, j int) bool { + if peers[i].IPBlock != nil && peers[j].IPBlock != nil { + return peers[i].IPBlock.CIDR < peers[j].IPBlock.CIDR + } + return false // Keep the order as is if IPBlock is nil + }) + + mergedRules = append(mergedRules, softwarecomposition.NetworkPolicyEgressRule{ + Ports: []softwarecomposition.NetworkPolicyPort{{Protocol: &keys[i].Protocol, Port: &keys[i].Port}}, + To: peers, + }) + } + + // Combine merged and non-merged rules + mergedRules = append(mergedRules, nonMergedRules...) + + return mergedRules +} + func generateEgressRule(neighbor softwarecomposition.NetworkNeighbor, KnownServer []softwarecomposition.KnownServer) (softwarecomposition.NetworkPolicyEgressRule, []softwarecomposition.PolicyRef) { egressRule := softwarecomposition.NetworkPolicyEgressRule{} policyRefs := []softwarecomposition.PolicyRef{} diff --git a/pkg/apis/softwarecomposition/networkpolicy/networkpolicy_test.go b/pkg/apis/softwarecomposition/networkpolicy/networkpolicy_test.go index 7fb51cf7f..3b3bf7c4a 100644 --- a/pkg/apis/softwarecomposition/networkpolicy/networkpolicy_test.go +++ b/pkg/apis/softwarecomposition/networkpolicy/networkpolicy_test.go @@ -925,21 +925,6 @@ func TestGenerateNetworkPolicy(t *testing.T) { softwarecomposition.PolicyTypeIngress, }, Ingress: []softwarecomposition.NetworkPolicyIngressRule{ - { - Ports: []softwarecomposition.NetworkPolicyPort{ - { - Port: pointer.Int32(80), - Protocol: &protocolTCP, - }, - }, - From: []softwarecomposition.NetworkPolicyPeer{ - { - IPBlock: &softwarecomposition.IPBlock{ - CIDR: "172.17.0.0/16", - }, - }, - }, - }, { Ports: []softwarecomposition.NetworkPolicyPort{ { @@ -968,6 +953,11 @@ func TestGenerateNetworkPolicy(t *testing.T) { CIDR: "156.43.0.2/32", }, }, + { + IPBlock: &softwarecomposition.IPBlock{ + CIDR: "172.17.0.0/16", + }, + }, }, }, }, @@ -1019,7 +1009,7 @@ func TestGenerateNetworkPolicy(t *testing.T) { DNS: "stripe.com", Ports: []softwarecomposition.NetworkPort{ { - Port: pointer.Int32(80), + Port: pointer.Int32(90), Protocol: softwarecomposition.ProtocolTCP, Name: "TCP-80", }, @@ -1076,7 +1066,7 @@ func TestGenerateNetworkPolicy(t *testing.T) { { Ports: []softwarecomposition.NetworkPolicyPort{ { - Port: pointer.Int32(80), + Port: pointer.Int32(90), Protocol: &protocolTCP, }, }, @@ -1135,7 +1125,7 @@ func TestGenerateNetworkPolicy(t *testing.T) { DNS: "stripe.com", Ports: []softwarecomposition.NetworkPort{ { - Port: pointer.Int32(80), + Port: pointer.Int32(90), Protocol: softwarecomposition.ProtocolTCP, Name: "TCP-80", }, @@ -1202,7 +1192,7 @@ func TestGenerateNetworkPolicy(t *testing.T) { { Ports: []softwarecomposition.NetworkPolicyPort{ { - Port: pointer.Int32(80), + Port: pointer.Int32(90), Protocol: &protocolTCP, }, }, @@ -1325,16 +1315,6 @@ func TestGenerateNetworkPolicy(t *testing.T) { CIDR: "172.17.0.0/16", }, }, - }, - }, - { - Ports: []softwarecomposition.NetworkPolicyPort{ - { - Port: pointer.Int32(80), - Protocol: &protocolTCP, - }, - }, - To: []softwarecomposition.NetworkPolicyPeer{ { IPBlock: &softwarecomposition.IPBlock{ CIDR: "198.17.0.2/32", @@ -1459,16 +1439,6 @@ func TestGenerateNetworkPolicy(t *testing.T) { CIDR: "172.17.0.0/16", }, }, - }, - }, - { - Ports: []softwarecomposition.NetworkPolicyPort{ - { - Port: pointer.Int32(80), - Protocol: &protocolTCP, - }, - }, - To: []softwarecomposition.NetworkPolicyPeer{ { IPBlock: &softwarecomposition.IPBlock{ CIDR: "198.17.0.0/16", @@ -1497,6 +1467,347 @@ func TestGenerateNetworkPolicy(t *testing.T) { }, }, }, + { + name: "same ports with different addresses - addresses are merged", + networkNeighbors: softwarecomposition.NetworkNeighbors{ + ObjectMeta: v1.ObjectMeta{ + Name: "deployment-nginx", + Namespace: "kubescape", + }, + Spec: softwarecomposition.NetworkNeighborsSpec{ + LabelSelector: v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + Egress: []softwarecomposition.NetworkNeighbor{ + { + IPAddress: "172.17.0.2", + Ports: []softwarecomposition.NetworkPort{ + { + Port: pointer.Int32(80), + Protocol: softwarecomposition.ProtocolTCP, + Name: "TCP-80", + }, + }, + }, + { + IPAddress: "196.17.0.2", + Ports: []softwarecomposition.NetworkPort{ + { + Port: pointer.Int32(80), + Protocol: softwarecomposition.ProtocolTCP, + Name: "TCP-80", + }, + }, + }, + }, + }, + }, + expectedNetworkPolicy: softwarecomposition.GeneratedNetworkPolicy{ + ObjectMeta: v1.ObjectMeta{ + Name: "deployment-nginx", + Namespace: "kubescape", + CreationTimestamp: timeProvider, + }, + TypeMeta: v1.TypeMeta{ + Kind: "GeneratedNetworkPolicy", + APIVersion: "spdx.softwarecomposition.kubescape.io/v1beta1", + }, + PoliciesRef: []softwarecomposition.PolicyRef{}, + Spec: softwarecomposition.NetworkPolicy{ + Kind: "NetworkPolicy", + APIVersion: "networking.k8s.io/v1", + ObjectMeta: v1.ObjectMeta{ + Name: "deployment-nginx", + Namespace: "kubescape", + Annotations: map[string]string{ + "generated-by": "kubescape", + }, + }, + Spec: softwarecomposition.NetworkPolicySpec{ + PodSelector: v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + PolicyTypes: []softwarecomposition.PolicyType{ + softwarecomposition.PolicyTypeEgress, + }, + Egress: []softwarecomposition.NetworkPolicyEgressRule{ + { + Ports: []softwarecomposition.NetworkPolicyPort{ + { + Port: pointer.Int32(80), + Protocol: &protocolTCP, + }, + }, + To: []softwarecomposition.NetworkPolicyPeer{ + { + IPBlock: &softwarecomposition.IPBlock{ + CIDR: "172.17.0.2/32", + }, + }, + { + IPBlock: &softwarecomposition.IPBlock{ + CIDR: "196.17.0.2/32", + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "same ports for pod traffic", + networkNeighbors: softwarecomposition.NetworkNeighbors{ + ObjectMeta: v1.ObjectMeta{ + Name: "deployment-nginx", + Namespace: "kubescape", + }, + Spec: softwarecomposition.NetworkNeighborsSpec{ + LabelSelector: v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + Egress: []softwarecomposition.NetworkNeighbor{ + { + PodSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + Ports: []softwarecomposition.NetworkPort{ + { + Port: pointer.Int32(80), + Protocol: softwarecomposition.ProtocolTCP, + Name: "TCP-80", + }, + }, + }, + { + PodSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "redis", + }, + }, + Ports: []softwarecomposition.NetworkPort{ + { + Port: pointer.Int32(80), + Protocol: softwarecomposition.ProtocolTCP, + Name: "TCP-80", + }, + }, + }, + }, + }, + }, + expectedNetworkPolicy: softwarecomposition.GeneratedNetworkPolicy{ + ObjectMeta: v1.ObjectMeta{ + Name: "deployment-nginx", + Namespace: "kubescape", + CreationTimestamp: timeProvider, + }, + TypeMeta: v1.TypeMeta{ + Kind: "GeneratedNetworkPolicy", + APIVersion: "spdx.softwarecomposition.kubescape.io/v1beta1", + }, + PoliciesRef: []softwarecomposition.PolicyRef{}, + Spec: softwarecomposition.NetworkPolicy{ + Kind: "NetworkPolicy", + APIVersion: "networking.k8s.io/v1", + ObjectMeta: v1.ObjectMeta{ + Name: "deployment-nginx", + Namespace: "kubescape", + Annotations: map[string]string{ + "generated-by": "kubescape", + }, + }, + Spec: softwarecomposition.NetworkPolicySpec{ + PodSelector: v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + PolicyTypes: []softwarecomposition.PolicyType{ + softwarecomposition.PolicyTypeEgress, + }, + Egress: []softwarecomposition.NetworkPolicyEgressRule{ + { + Ports: []softwarecomposition.NetworkPolicyPort{ + { + Port: pointer.Int32(80), + Protocol: &protocolTCP, + }, + }, + To: []softwarecomposition.NetworkPolicyPeer{ + { + PodSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + }, + }, + }, + { + Ports: []softwarecomposition.NetworkPolicyPort{ + { + Port: pointer.Int32(80), + Protocol: &protocolTCP, + }, + }, + To: []softwarecomposition.NetworkPolicyPeer{ + { + PodSelector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "redis", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "same ports for multiple IPs - addresses are merged correctly", + networkNeighbors: softwarecomposition.NetworkNeighbors{ + ObjectMeta: v1.ObjectMeta{ + Name: "deployment-nginx", + Namespace: "kubescape", + }, + Spec: softwarecomposition.NetworkNeighborsSpec{ + LabelSelector: v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + Egress: []softwarecomposition.NetworkNeighbor{ + { + IPAddress: "172.17.0.2", + Ports: []softwarecomposition.NetworkPort{ + { + Port: pointer.Int32(80), + Protocol: softwarecomposition.ProtocolTCP, + Name: "TCP-80", + }, + }, + }, + { + IPAddress: "172.17.0.2", + Ports: []softwarecomposition.NetworkPort{ + { + Port: pointer.Int32(443), + Protocol: softwarecomposition.ProtocolTCP, + Name: "TCP-80", + }, + }, + }, + { + IPAddress: "196.17.0.2", + Ports: []softwarecomposition.NetworkPort{ + { + Port: pointer.Int32(80), + Protocol: softwarecomposition.ProtocolTCP, + Name: "TCP-80", + }, + }, + }, + { + IPAddress: "196.17.0.2", + Ports: []softwarecomposition.NetworkPort{ + { + Port: pointer.Int32(443), + Protocol: softwarecomposition.ProtocolTCP, + Name: "TCP-80", + }, + }, + }, + }, + }, + }, + expectedNetworkPolicy: softwarecomposition.GeneratedNetworkPolicy{ + ObjectMeta: v1.ObjectMeta{ + Name: "deployment-nginx", + Namespace: "kubescape", + CreationTimestamp: timeProvider, + }, + TypeMeta: v1.TypeMeta{ + Kind: "GeneratedNetworkPolicy", + APIVersion: "spdx.softwarecomposition.kubescape.io/v1beta1", + }, + PoliciesRef: []softwarecomposition.PolicyRef{}, + Spec: softwarecomposition.NetworkPolicy{ + Kind: "NetworkPolicy", + APIVersion: "networking.k8s.io/v1", + ObjectMeta: v1.ObjectMeta{ + Name: "deployment-nginx", + Namespace: "kubescape", + Annotations: map[string]string{ + "generated-by": "kubescape", + }, + }, + Spec: softwarecomposition.NetworkPolicySpec{ + PodSelector: v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + PolicyTypes: []softwarecomposition.PolicyType{ + softwarecomposition.PolicyTypeEgress, + }, + Egress: []softwarecomposition.NetworkPolicyEgressRule{ + { + Ports: []softwarecomposition.NetworkPolicyPort{ + { + Port: pointer.Int32(80), + Protocol: &protocolTCP, + }, + }, + To: []softwarecomposition.NetworkPolicyPeer{ + { + IPBlock: &softwarecomposition.IPBlock{ + CIDR: "172.17.0.2/32", + }, + }, + { + IPBlock: &softwarecomposition.IPBlock{ + CIDR: "196.17.0.2/32", + }, + }, + }, + }, + { + Ports: []softwarecomposition.NetworkPolicyPort{ + { + Port: pointer.Int32(443), + Protocol: &protocolTCP, + }, + }, + To: []softwarecomposition.NetworkPolicyPeer{ + { + IPBlock: &softwarecomposition.IPBlock{ + CIDR: "172.17.0.2/32", + }, + }, + { + IPBlock: &softwarecomposition.IPBlock{ + CIDR: "196.17.0.2/32", + }, + }, + }, + }, + }, + }, + }, + }, + }, } for _, test := range tests {