Skip to content

Commit 9bcdb92

Browse files
committed
networkpolicy: provider-scoped policies for multi-network pods
- add ovn.kubernetes.io/policy-for parsing and provider filtering - gate Service ClusterIP inclusion to primary-only policies - add unit + e2e tests (skip if NAD CRD missing) Signed-off-by: akbarkn <akbarkusumanegaralth@gmail.com>
1 parent 98c4486 commit 9bcdb92

File tree

3 files changed

+487
-6
lines changed

3 files changed

+487
-6
lines changed

pkg/controller/network_policy.go

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package controller
22

33
import (
4+
"errors"
45
"fmt"
56
"reflect"
67
"slices"
@@ -28,6 +29,7 @@ import (
2829
const (
2930
NetworkPolicyEnforcementStandard = "standard"
3031
NetworkPolicyEnforcementLax = "lax"
32+
policyForAnnotation = "ovn.kubernetes.io/policy-for"
3133
)
3234

3335
func (c *Controller) enqueueAddNp(obj any) {
@@ -121,6 +123,11 @@ func (c *Controller) handleUpdateNp(key string) error {
121123
}
122124
logRate := parseACLLogRate(np.Annotations)
123125

126+
providers, includeSvc, err := parsePolicyFor(np)
127+
if err != nil {
128+
return err
129+
}
130+
124131
npName := np.Name
125132
nameArray := []rune(np.Name)
126133
if !unicode.IsLetter(nameArray[0]) {
@@ -142,7 +149,7 @@ func (c *Controller) handleUpdateNp(key string) error {
142149
}
143150

144151
namedPortMap := c.namedPort.GetNamedPortByNs(np.Namespace)
145-
ports, subnetNames, err := c.fetchSelectedPorts(np.Namespace, &np.Spec.PodSelector)
152+
ports, subnetNames, err := c.fetchSelectedPorts(np.Namespace, &np.Spec.PodSelector, providers)
146153
if err != nil {
147154
klog.Errorf("fetch ports belongs to np %s: %v", key, err)
148155
return err
@@ -212,7 +219,7 @@ func (c *Controller) handleUpdateNp(key string) error {
212219
} else {
213220
var allow, except []string
214221
for _, npp := range npr.From {
215-
if allow, except, err = c.fetchPolicySelectedAddresses(np.Namespace, protocol, npp); err != nil {
222+
if allow, except, err = c.fetchPolicySelectedAddresses(np.Namespace, protocol, npp, providers, includeSvc); err != nil {
216223
klog.Errorf("failed to fetch policy selected addresses, %v", err)
217224
return err
218225
}
@@ -359,7 +366,7 @@ func (c *Controller) handleUpdateNp(key string) error {
359366
} else {
360367
var allow, except []string
361368
for _, npp := range npr.To {
362-
if allow, except, err = c.fetchPolicySelectedAddresses(np.Namespace, protocol, npp); err != nil {
369+
if allow, except, err = c.fetchPolicySelectedAddresses(np.Namespace, protocol, npp, providers, includeSvc); err != nil {
363370
klog.Errorf("failed to fetch policy selected addresses, %v", err)
364371
return err
365372
}
@@ -531,7 +538,50 @@ func (c *Controller) handleDeleteNp(key string) error {
531538
return nil
532539
}
533540

534-
func (c *Controller) fetchSelectedPorts(namespace string, selector *metav1.LabelSelector) ([]string, []string, error) {
541+
func parsePolicyFor(np *netv1.NetworkPolicy) (map[string]struct{}, bool, error) {
542+
raw := strings.TrimSpace(np.Annotations[policyForAnnotation])
543+
if raw == "" {
544+
return nil, true, nil
545+
}
546+
547+
providers := map[string]struct{}{}
548+
includeSvc := false
549+
550+
for _, token := range strings.Split(raw, ",") {
551+
t := strings.TrimSpace(token)
552+
if t == "" {
553+
continue
554+
}
555+
556+
switch strings.ToLower(t) {
557+
case "primary":
558+
providers[util.OvnProvider] = struct{}{}
559+
includeSvc = true
560+
continue
561+
case "default":
562+
return nil, false, fmt.Errorf("invalid policy-for entry %q (use 'primary')", t)
563+
case "all":
564+
return nil, false, fmt.Errorf("invalid policy-for entry %q (omit annotation for all)", t)
565+
}
566+
if strings.Contains(t, "/") {
567+
parts := strings.SplitN(t, "/", 2)
568+
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
569+
return nil, false, fmt.Errorf("invalid policy-for entry %q", t)
570+
}
571+
provider := fmt.Sprintf("%s.%s.%s", parts[1], parts[0], util.OvnProvider)
572+
providers[provider] = struct{}{}
573+
continue
574+
}
575+
return nil, false, fmt.Errorf("invalid policy-for entry %q", t)
576+
}
577+
578+
if len(providers) == 0 {
579+
return nil, false, errors.New("policy-for annotation has no valid entries")
580+
}
581+
return providers, includeSvc, nil
582+
}
583+
584+
func (c *Controller) fetchSelectedPorts(namespace string, selector *metav1.LabelSelector, providers map[string]struct{}) ([]string, []string, error) {
535585
var subnets []string
536586
sel, err := metav1.LabelSelectorAsSelector(selector)
537587
if err != nil {
@@ -557,6 +607,12 @@ func (c *Controller) fetchSelectedPorts(namespace string, selector *metav1.Label
557607
if !isOvnSubnet(podNet.Subnet) {
558608
continue
559609
}
610+
provider := podNet.ProviderName
611+
if providers != nil {
612+
if _, ok := providers[provider]; !ok {
613+
continue
614+
}
615+
}
560616

561617
if pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)] == "true" {
562618
ports = append(ports, ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName))
@@ -587,7 +643,7 @@ func hasEgressRule(np *netv1.NetworkPolicy) bool {
587643
return np.Spec.Egress != nil
588644
}
589645

590-
func (c *Controller) fetchPolicySelectedAddresses(namespace, protocol string, npp netv1.NetworkPolicyPeer) ([]string, []string, error) {
646+
func (c *Controller) fetchPolicySelectedAddresses(namespace, protocol string, npp netv1.NetworkPolicyPeer, providers map[string]struct{}, includeSvc bool) ([]string, []string, error) {
591647
selectedAddresses := []string{}
592648
exceptAddresses := []string{}
593649

@@ -644,14 +700,21 @@ func (c *Controller) fetchPolicySelectedAddresses(namespace, protocol string, np
644700
return nil, nil, err
645701
}
646702
for _, podNet := range podNets {
703+
provider := podNet.ProviderName
704+
if providers != nil {
705+
if _, ok := providers[provider]; !ok {
706+
continue
707+
}
708+
}
709+
647710
podIPAnnotation := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]
648711
podIPs := strings.SplitSeq(podIPAnnotation, ",")
649712
for podIP := range podIPs {
650713
if podIP != "" && util.CheckProtocol(podIP) == protocol {
651714
selectedAddresses = append(selectedAddresses, podIP)
652715
}
653716
}
654-
if len(svcs) == 0 {
717+
if !includeSvc || len(svcs) == 0 {
655718
continue
656719
}
657720

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package controller
2+
3+
import (
4+
"testing"
5+
6+
netv1 "k8s.io/api/networking/v1"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/kubeovn/kube-ovn/pkg/util"
12+
)
13+
14+
func TestParsePolicyFor(t *testing.T) {
15+
t.Parallel()
16+
17+
tests := []struct {
18+
name string
19+
annotation *string
20+
wantProviders map[string]struct{}
21+
wantIncludeSvc bool
22+
wantErr bool
23+
}{
24+
{
25+
name: "annotation omitted",
26+
annotation: nil,
27+
wantProviders: nil,
28+
wantIncludeSvc: true,
29+
wantErr: false,
30+
},
31+
{
32+
name: "primary only",
33+
annotation: ptrString("primary"),
34+
wantProviders: map[string]struct{}{
35+
util.OvnProvider: {},
36+
},
37+
wantIncludeSvc: true,
38+
wantErr: false,
39+
},
40+
{
41+
name: "secondary only",
42+
annotation: ptrString("ns1/net1"),
43+
wantProviders: map[string]struct{}{
44+
"net1.ns1." + util.OvnProvider: {},
45+
},
46+
wantIncludeSvc: false,
47+
wantErr: false,
48+
},
49+
{
50+
name: "primary and secondary",
51+
annotation: ptrString(" primary , ns1/net1 "),
52+
wantProviders: map[string]struct{}{
53+
util.OvnProvider: {},
54+
"net1.ns1." + util.OvnProvider: {},
55+
},
56+
wantIncludeSvc: true,
57+
wantErr: false,
58+
},
59+
{
60+
name: "invalid all",
61+
annotation: ptrString("all"),
62+
wantErr: true,
63+
},
64+
{
65+
name: "invalid default",
66+
annotation: ptrString("default"),
67+
wantErr: true,
68+
},
69+
{
70+
name: "invalid no entries",
71+
annotation: ptrString(","),
72+
wantErr: true,
73+
},
74+
{
75+
name: "invalid token",
76+
annotation: ptrString("foo"),
77+
wantErr: true,
78+
},
79+
}
80+
81+
for _, tt := range tests {
82+
t.Run(tt.name, func(t *testing.T) {
83+
np := &netv1.NetworkPolicy{
84+
ObjectMeta: metav1.ObjectMeta{
85+
Name: "np",
86+
Namespace: "default",
87+
},
88+
}
89+
if tt.annotation != nil {
90+
np.Annotations = map[string]string{
91+
policyForAnnotation: *tt.annotation,
92+
}
93+
}
94+
95+
providers, includeSvc, err := parsePolicyFor(np)
96+
if tt.wantErr {
97+
require.Error(t, err)
98+
return
99+
}
100+
101+
require.NoError(t, err)
102+
require.Equal(t, tt.wantIncludeSvc, includeSvc)
103+
if tt.wantProviders == nil {
104+
require.Nil(t, providers)
105+
return
106+
}
107+
require.Equal(t, tt.wantProviders, providers)
108+
})
109+
}
110+
}
111+
112+
func ptrString(s string) *string {
113+
return &s
114+
}

0 commit comments

Comments
 (0)