From 293c5921c5d539c4362236daa08a67a13986ba3a Mon Sep 17 00:00:00 2001 From: Anvesh J Date: Fri, 22 Nov 2024 17:57:06 +0530 Subject: [PATCH] networkpolicy pkg: add new methods to multi network policy --- pkg/networkpolicy/multinetegressrule.go | 134 ++++++++++++++++++ pkg/networkpolicy/multinetegressrule_test.go | 131 +++++++++++++++++ pkg/networkpolicy/multinetingressrule.go | 100 +++++++++++++ pkg/networkpolicy/multinetingressrule_test.go | 92 ++++++++++++ 4 files changed, 457 insertions(+) diff --git a/pkg/networkpolicy/multinetegressrule.go b/pkg/networkpolicy/multinetegressrule.go index a44c1cf4b..1b7d51fab 100644 --- a/pkg/networkpolicy/multinetegressrule.go +++ b/pkg/networkpolicy/multinetegressrule.go @@ -6,6 +6,7 @@ import ( "github.com/golang/glog" "github.com/k8snetworkplumbingwg/multi-networkpolicy/pkg/apis/k8s.cni.cncf.io/v1beta1" + "github.com/openshift-kni/eco-goinfra/pkg/msg" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -54,6 +55,52 @@ func (builder *EgressRuleBuilder) WithPortAndProtocol(port uint16, protocol core return builder } +// WithProtocol appends new item with only protocol to Ports list. +func (builder *EgressRuleBuilder) WithProtocol(protocol corev1.Protocol) *EgressRuleBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding protocol %s to EgressRule", protocol) + + if !(protocol == corev1.ProtocolTCP || protocol == corev1.ProtocolUDP || protocol == corev1.ProtocolSCTP) { + glog.V(100).Infof("invalid protocol argument. Allowed protocols: TCP, UDP & SCTP ") + + builder.errorMsg = "invalid protocol argument. Allowed protocols: TCP, UDP & SCTP" + + return builder + } + + builder.definition.Ports = append( + builder.definition.Ports, v1beta1.MultiNetworkPolicyPort{Protocol: &protocol}) + + return builder +} + +// WithPort appends new item with only port to Ports list. +func (builder *EgressRuleBuilder) WithPort(port uint16) *EgressRuleBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding port %d to EgressRule", port) + + if port == 0 { + glog.V(100).Infof("Cannot set port number to 0") + + builder.errorMsg = "port number cannot be 0" + + return builder + } + + formattedPort := intstr.FromInt(int(port)) + + builder.definition.Ports = append( + builder.definition.Ports, v1beta1.MultiNetworkPolicyPort{Port: &formattedPort}) + + return builder +} + // WithOptions adds generic options to Egress rule. func (builder *EgressRuleBuilder) WithOptions(options ...EgressAdditionalOptions) *EgressRuleBuilder { glog.V(100).Infof("Setting EgressRule additional options") @@ -88,6 +135,69 @@ func (builder *EgressRuleBuilder) WithPeerPodSelector(podSelector metav1.LabelSe return builder } +// WithPeerNamespaceSelector appends new item with only NamespaceSelector into To Peer list. +func (builder *EgressRuleBuilder) WithPeerNamespaceSelector(nsSelector metav1.LabelSelector) *EgressRuleBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding peer namespace selector %v to EgressRule", nsSelector) + + builder.definition.To = append(builder.definition.To, v1beta1.MultiNetworkPolicyPeer{NamespaceSelector: &nsSelector}) + + return builder +} + +// WithCIDR edits last item's IPBlock on Egress/To list or adds new item with only IPBlock into +// Egress/To list if the Egress/To list is empty. +func (builder *EgressRuleBuilder) WithCIDR(cidr string, except ...[]string) *EgressRuleBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding peer CIDR %s to Egress Rule", cidr) + + if len(except) != 0 { + glog.V(100).Infof("Adding CIDR except %v to Egress Rule", except[0]) + } + + _, _, err := net.ParseCIDR(cidr) + + if err != nil { + glog.V(100).Infof("Invalid CIDR %s", cidr) + + builder.errorMsg = fmt.Sprintf("invalid CIDR argument %s", cidr) + + return builder + } + + builder.definition.To = append(builder.definition.To, v1beta1.MultiNetworkPolicyPeer{}) + + // Append IPBlock config to the previously added Peer + builder.definition.To[len(builder.definition.To)-1].IPBlock = &v1beta1.IPBlock{CIDR: cidr} + + if len(except) > 0 { + builder.definition.To[len(builder.definition.To)-1].IPBlock.Except = except[0] + } + + return builder +} + +// WithPeerPodAndNamespaceSelector appends new item to Egress/To list with PodSelector and NamespaceSelector. +func (builder *EgressRuleBuilder) WithPeerPodAndNamespaceSelector( + podSelector, nsSelector metav1.LabelSelector) *EgressRuleBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding peer pod selector %v namespace selector %v to EgressRule", podSelector, nsSelector) + + builder.definition.To = append(builder.definition.To, v1beta1.MultiNetworkPolicyPeer{ + PodSelector: &podSelector, NamespaceSelector: &nsSelector}) + + return builder +} + // WithPeerPodSelectorAndCIDR adds pod selector and CIDR to Egress rule. func (builder *EgressRuleBuilder) WithPeerPodSelectorAndCIDR( podSelector metav1.LabelSelector, cidr string, except ...[]string) *EgressRuleBuilder { @@ -129,3 +239,27 @@ func (builder *EgressRuleBuilder) GetEgressRuleCfg() (*v1beta1.MultiNetworkPolic return builder.definition, nil } + +func (builder *EgressRuleBuilder) validate() (bool, error) { + objectName := "multiNetworkPolicyEgressRule" + + if builder == nil { + glog.V(100).Infof("The %s builder is uninitialized", objectName) + + return false, fmt.Errorf("error: received nil %s builder", objectName) + } + + if builder.definition == nil { + glog.V(100).Infof("The %s is undefined", objectName) + + builder.errorMsg = msg.UndefinedCrdObjectErrString(objectName) + } + + if builder.errorMsg != "" { + glog.V(100).Infof("The %s builder has error message: %s", objectName, builder.errorMsg) + + return false, fmt.Errorf(builder.errorMsg) + } + + return true, nil +} diff --git a/pkg/networkpolicy/multinetegressrule_test.go b/pkg/networkpolicy/multinetegressrule_test.go index 1edb1cd23..f1121bbd3 100644 --- a/pkg/networkpolicy/multinetegressrule_test.go +++ b/pkg/networkpolicy/multinetegressrule_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -28,6 +29,46 @@ func TestEgressWithPortAndProtocol(t *testing.T) { assert.Equal(t, builder.errorMsg, "port number can not be 0") } +func TestEgressWithProtocol(t *testing.T) { + testCases := []struct { + protocol v1.Protocol + expectedError string + }{ + {protocol: v1.ProtocolTCP, expectedError: ""}, + {protocol: v1.ProtocolUDP, expectedError: ""}, + {protocol: v1.ProtocolSCTP, expectedError: ""}, + {protocol: "dummy", expectedError: "invalid protocol argument. Allowed protocols: TCP, UDP & SCTP"}, + } + for _, testCase := range testCases { + builder := NewEgressRuleBuilder().WithProtocol(testCase.protocol) + assert.Equal(t, testCase.expectedError, builder.errorMsg) + + if testCase.expectedError == "" { + assert.Equal(t, testCase.protocol, *builder.definition.Ports[0].Protocol) + } + } +} + +func TestEgressWithPort(t *testing.T) { + testCases := []struct { + port uint16 + expectedError string + }{ + { + port: 5001, + expectedError: "", + }, + { + port: 0, + expectedError: "port number cannot be 0", + }, + } + for _, testCase := range testCases { + builder := NewEgressRuleBuilder().WithPort(testCase.port) + assert.Equal(t, testCase.expectedError, builder.errorMsg) + } +} + func TestEgressWithOptions(t *testing.T) { testCases := []struct { testOptions []EgressAdditionalOptions @@ -84,6 +125,72 @@ func TestEgressWithPeerPodSelector(t *testing.T) { assert.Len(t, builder.definition.To, 1) } +func TestEgressWithPeerNamespaceSelector(t *testing.T) { + testCases := []struct { + namespaceSelector metav1.LabelSelector + expectedError string + }{ + { + namespaceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, + expectedError: "", + }, + { + namespaceSelector: metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "app", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"nginx"}, + }, + }}, + }, + } + for _, testCase := range testCases { + builder := NewEgressRuleBuilder().WithPeerNamespaceSelector(testCase.namespaceSelector) + assert.Equal(t, testCase.expectedError, builder.errorMsg) + assert.Equal(t, &testCase.namespaceSelector, builder.definition.To[0].NamespaceSelector) + } +} + +func TestEgressWithCIDR(t *testing.T) { + testCases := []struct { + cidr string + except string + expectedLength int + }{ + { + cidr: "192.168.1.1/24", + expectedLength: 1, + }, + { + cidr: "192.168.1.1", + expectedLength: 0, + }, + { + cidr: "192.168.1.1/24", + except: "192.168.1.10/32", + expectedLength: 1, + }, + } + for _, testCase := range testCases { + if len(testCase.except) != 0 { + builder := NewEgressRuleBuilder().WithCIDR(testCase.cidr, []string{testCase.except}) + assert.Equal(t, testCase.expectedLength, len(builder.definition.To)) + assert.Equal(t, testCase.except, builder.definition.To[0].IPBlock.Except[0]) + + if len(builder.definition.To) != 0 { + assert.Equal(t, testCase.cidr, builder.definition.To[0].IPBlock.CIDR) + } + } else { + builder := NewEgressRuleBuilder().WithCIDR(testCase.cidr) + assert.Equal(t, testCase.expectedLength, len(builder.definition.To)) + + if len(builder.definition.To) != 0 { + assert.Equal(t, testCase.cidr, builder.definition.To[0].IPBlock.CIDR) + } + } + } +} + func TestEgressWithPeerPodSelectorAndCIDR(t *testing.T) { builder := NewEgressRuleBuilder() @@ -116,6 +223,30 @@ func TestEgressWithPeerPodSelectorAndCIDR(t *testing.T) { assert.Equal(t, builder.definition.To[0].IPBlock.Except[0], "192.168.1.1") } +func TestEgressWithPeerPodAndNamespaceSelector(t *testing.T) { + testCases := []struct { + podSelector metav1.LabelSelector + namespaceSelector metav1.LabelSelector + expectedError string + }{ + { + podSelector: metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, + namespaceSelector: metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "app", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"nginx"}, + }}}, + expectedError: "", + }, + } + for _, testCase := range testCases { + builder := NewEgressRuleBuilder().WithPeerPodAndNamespaceSelector(testCase.podSelector, testCase.namespaceSelector) + assert.Equal(t, testCase.expectedError, builder.errorMsg) + assert.Equal(t, &testCase.podSelector, builder.definition.To[0].PodSelector) + assert.Equal(t, &testCase.namespaceSelector, builder.definition.To[0].NamespaceSelector) + } +} + func TestEgressGetEgressRuleCfg(t *testing.T) { builder := NewEgressRuleBuilder() diff --git a/pkg/networkpolicy/multinetingressrule.go b/pkg/networkpolicy/multinetingressrule.go index 819662a71..630697364 100644 --- a/pkg/networkpolicy/multinetingressrule.go +++ b/pkg/networkpolicy/multinetingressrule.go @@ -6,6 +6,7 @@ import ( "github.com/golang/glog" "github.com/k8snetworkplumbingwg/multi-networkpolicy/pkg/apis/k8s.cni.cncf.io/v1beta1" + "github.com/openshift-kni/eco-goinfra/pkg/msg" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -53,6 +54,52 @@ func (builder *IngressRuleBuilder) WithPortAndProtocol(port uint16, protocol cor return builder } +// WithProtocol appends new item with only protocol to Ports list. +func (builder *IngressRuleBuilder) WithProtocol(protocol corev1.Protocol) *IngressRuleBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding protocol %s to IngressRule", protocol) + + if !(protocol == corev1.ProtocolTCP || protocol == corev1.ProtocolUDP || protocol == corev1.ProtocolSCTP) { + glog.V(100).Infof("invalid protocol argument") + + builder.errorMsg = "invalid protocol argument. Allowed protocols: TCP, UDP & SCTP" + + return builder + } + + builder.definition.Ports = append( + builder.definition.Ports, v1beta1.MultiNetworkPolicyPort{Protocol: &protocol}) + + return builder +} + +// WithPort appends new item with only port to Ports list. +func (builder *IngressRuleBuilder) WithPort(port uint16) *IngressRuleBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding port %d to IngressRule", port) + + if port == 0 { + glog.V(100).Infof("Cannot set port number to 0") + + builder.errorMsg = "port number cannot be 0" + + return builder + } + + formattedPort := intstr.FromInt(int(port)) + + builder.definition.Ports = append( + builder.definition.Ports, v1beta1.MultiNetworkPolicyPort{Port: &formattedPort}) + + return builder +} + // WithOptions adds generic options to Ingress rule. func (builder *IngressRuleBuilder) WithOptions(options ...IngressAdditionalOptions) *IngressRuleBuilder { glog.V(100).Infof("Setting IngressRule additional options") @@ -90,6 +137,20 @@ func (builder *IngressRuleBuilder) WithPeerPodSelector(podSelector metav1.LabelS return builder } +// WithPeerNamespaceSelector appends new item with only NamespaceSelector to From Peer list. +func (builder *IngressRuleBuilder) WithPeerNamespaceSelector(nsSelector metav1.LabelSelector) *IngressRuleBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding peer namespace selector %v to IngressRule", nsSelector) + + builder.definition.From = append(builder.definition.From, + v1beta1.MultiNetworkPolicyPeer{NamespaceSelector: &nsSelector}) + + return builder +} + // WithCIDR adds CIDR to Ingress rule. func (builder *IngressRuleBuilder) WithCIDR(cidr string, except ...[]string) *IngressRuleBuilder { glog.V(100).Infof("Adding peer CIDR %s to Ingress Rule", cidr) @@ -122,6 +183,21 @@ func (builder *IngressRuleBuilder) WithCIDR(cidr string, except ...[]string) *In return builder } +// WithPeerPodAndNamespaceSelector appends new item to Ingress/From list with PodSelector and NamespaceSelector. +func (builder *IngressRuleBuilder) WithPeerPodAndNamespaceSelector( + podSelector, nsSelector metav1.LabelSelector) *IngressRuleBuilder { + if valid, _ := builder.validate(); !valid { + return builder + } + + glog.V(100).Infof("Adding peer pod selector %v namespace selector %v to IngressRule", podSelector, nsSelector) + + builder.definition.From = append(builder.definition.From, v1beta1.MultiNetworkPolicyPeer{ + PodSelector: &podSelector, NamespaceSelector: &nsSelector}) + + return builder +} + // WithPeerPodSelectorAndCIDR adds port and protocol,CIDR to Ingress rule. func (builder *IngressRuleBuilder) WithPeerPodSelectorAndCIDR( podSelector metav1.LabelSelector, cidr string, except ...[]string) *IngressRuleBuilder { @@ -149,3 +225,27 @@ func (builder *IngressRuleBuilder) GetIngressRuleCfg() (*v1beta1.MultiNetworkPol return builder.definition, nil } + +func (builder *IngressRuleBuilder) validate() (bool, error) { + objectName := "multiNetworkPolicyIngressRule" + + if builder == nil { + glog.V(100).Infof("The %s builder is uninitialized", objectName) + + return false, fmt.Errorf("error: received nil %s builder", objectName) + } + + if builder.definition == nil { + glog.V(100).Infof("The %s is undefined", objectName) + + builder.errorMsg = msg.UndefinedCrdObjectErrString(objectName) + } + + if builder.errorMsg != "" { + glog.V(100).Infof("The %s builder has error message: %s", objectName, builder.errorMsg) + + return false, fmt.Errorf(builder.errorMsg) + } + + return true, nil +} diff --git a/pkg/networkpolicy/multinetingressrule_test.go b/pkg/networkpolicy/multinetingressrule_test.go index 5b48d0ab8..d59f07af1 100644 --- a/pkg/networkpolicy/multinetingressrule_test.go +++ b/pkg/networkpolicy/multinetingressrule_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -28,6 +29,46 @@ func TestIngressWithPortAndProtocol(t *testing.T) { assert.Equal(t, builder.errorMsg, "port number can not be 0") } +func TestIngressWithProtocol(t *testing.T) { + testCases := []struct { + protocol v1.Protocol + expectedError string + }{ + {protocol: v1.ProtocolTCP, expectedError: ""}, + {protocol: v1.ProtocolUDP, expectedError: ""}, + {protocol: v1.ProtocolSCTP, expectedError: ""}, + {protocol: "dummy", expectedError: "invalid protocol argument. Allowed protocols: TCP, UDP & SCTP"}, + } + for _, testCase := range testCases { + builder := NewIngressRuleBuilder().WithProtocol(testCase.protocol) + assert.Equal(t, testCase.expectedError, builder.errorMsg) + + if testCase.expectedError == "" { + assert.Equal(t, testCase.protocol, *builder.definition.Ports[0].Protocol) + } + } +} + +func TestIngressWithPort(t *testing.T) { + testCases := []struct { + port uint16 + expectedError string + }{ + { + port: 5001, + expectedError: "", + }, + { + port: 0, + expectedError: "port number cannot be 0", + }, + } + for _, testCase := range testCases { + builder := NewIngressRuleBuilder().WithPort(testCase.port) + assert.Equal(t, testCase.expectedError, builder.errorMsg) + } +} + func TestIngressWithOptions(t *testing.T) { testCases := []struct { testOptions []IngressAdditionalOptions @@ -85,6 +126,33 @@ func TestIngressWithPeerPodSelector(t *testing.T) { assert.Len(t, builder.definition.From, 0) } +func TestIngressWithPeerNamespaceSelector(t *testing.T) { + testCases := []struct { + namespaceSelector metav1.LabelSelector + expectedError string + }{ + { + namespaceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, + expectedError: "", + }, + { + namespaceSelector: metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "app", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"nginx"}, + }, + }}, + expectedError: "", + }, + } + for _, testCase := range testCases { + builder := NewIngressRuleBuilder().WithPeerNamespaceSelector(testCase.namespaceSelector) + assert.Equal(t, testCase.expectedError, builder.errorMsg) + assert.Equal(t, &testCase.namespaceSelector, builder.definition.From[0].NamespaceSelector) + } +} + func TestIngressWithCIDR(t *testing.T) { builder := NewIngressRuleBuilder() @@ -110,6 +178,30 @@ func TestIngressWithCIDR(t *testing.T) { assert.Equal(t, builder.definition.From[0].IPBlock.Except, []string{"192.168.1.1"}) } +func TestIngressWithPeerPodAndNamespaceSelector(t *testing.T) { + testCases := []struct { + podSelector metav1.LabelSelector + namespaceSelector metav1.LabelSelector + expectedError string + }{ + { + podSelector: metav1.LabelSelector{MatchLabels: map[string]string{"app": "nginx"}}, + namespaceSelector: metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "app", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"nginx"}, + }}}, + expectedError: "", + }, + } + for _, testCase := range testCases { + builder := NewIngressRuleBuilder().WithPeerPodAndNamespaceSelector(testCase.podSelector, testCase.namespaceSelector) + assert.Equal(t, testCase.expectedError, builder.errorMsg) + assert.Equal(t, &testCase.podSelector, builder.definition.From[0].PodSelector) + assert.Equal(t, &testCase.namespaceSelector, builder.definition.From[0].NamespaceSelector) + } +} + func TestIngressWithPeerPodSelectorAndCIDR(t *testing.T) { builder := NewIngressRuleBuilder()