• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

noironetworks / aci-containers / 12009

29 May 2026 06:44AM UTC coverage: 63.944% (+0.2%) from 63.735%
12009

push

travis-pro

web-flow
Merge pull request #1738 from noironetworks/fix-hpp-opt-mmr

[mmr-6.1.1] Resolve named port conflicts across sibling NPs in shared HPP

136 of 151 new or added lines in 1 file covered. (90.07%)

3 existing lines in 2 files now uncovered.

13677 of 21389 relevant lines covered (63.94%)

0.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.25
/pkg/controller/network_policy.go
1
// Copyright 2017 Cisco Systems, Inc.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
// Handlers for network policy updates.  Generate ACI security groups
16
// based on Kubernetes network policies.
17

18
package controller
19

20
import (
21
        "bytes"
22
        "context"
23
        "fmt"
24
        "maps"
25
        "net"
26
        "os"
27
        "reflect"
28
        "slices"
29
        "sort"
30
        "strconv"
31
        "strings"
32

33
        "github.com/sirupsen/logrus"
34
        "github.com/yl2chen/cidranger"
35

36
        v1 "k8s.io/api/core/v1"
37
        v1net "k8s.io/api/networking/v1"
38
        "k8s.io/apimachinery/pkg/api/errors"
39
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
40
        "k8s.io/apimachinery/pkg/fields"
41
        "k8s.io/apimachinery/pkg/labels"
42
        "k8s.io/apimachinery/pkg/util/intstr"
43
        "k8s.io/client-go/kubernetes"
44
        "k8s.io/client-go/tools/cache"
45
        k8util "k8s.io/kubectl/pkg/util"
46

47
        "github.com/noironetworks/aci-containers/pkg/apicapi"
48
        hppv1 "github.com/noironetworks/aci-containers/pkg/hpp/apis/aci.hpp/v1"
49
        hppclset "github.com/noironetworks/aci-containers/pkg/hpp/clientset/versioned"
50
        "github.com/noironetworks/aci-containers/pkg/index"
51
        "github.com/noironetworks/aci-containers/pkg/ipam"
52
        "github.com/noironetworks/aci-containers/pkg/util"
53
        discovery "k8s.io/api/discovery/v1"
54
)
55

56
func (cont *AciController) initNetworkPolicyInformerFromClient(
57
        kubeClient kubernetes.Interface) {
×
58
        cont.initNetworkPolicyInformerBase(
×
59
                cache.NewListWatchFromClient(
×
60
                        kubeClient.NetworkingV1().RESTClient(), "networkpolicies",
×
61
                        metav1.NamespaceAll, fields.Everything()))
×
62
}
×
63

64
func (cont *AciController) initNetworkPolicyInformerBase(listWatch *cache.ListWatch) {
1✔
65
        cont.networkPolicyIndexer, cont.networkPolicyInformer =
1✔
66
                cache.NewIndexerInformer(
1✔
67
                        listWatch, &v1net.NetworkPolicy{}, 0,
1✔
68
                        cache.ResourceEventHandlerFuncs{
1✔
69
                                AddFunc: func(obj interface{}) {
2✔
70
                                        cont.networkPolicyAdded(obj)
1✔
71
                                },
1✔
72
                                UpdateFunc: func(oldobj interface{}, newobj interface{}) {
×
73
                                        cont.networkPolicyChanged(oldobj, newobj)
×
74
                                },
×
75
                                DeleteFunc: func(obj interface{}) {
1✔
76
                                        cont.networkPolicyDeleted(obj)
1✔
77
                                },
1✔
78
                        },
79
                        cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
80
                )
81
}
82

83
func (cont *AciController) peerPodSelector(np *v1net.NetworkPolicy,
84
        peers []v1net.NetworkPolicyPeer) []index.PodSelector {
1✔
85
        var ret []index.PodSelector
1✔
86
        for _, peer := range peers {
2✔
87
                podselector, err :=
1✔
88
                        metav1.LabelSelectorAsSelector(peer.PodSelector)
1✔
89
                if err != nil {
1✔
90
                        networkPolicyLogger(cont.log, np).
×
91
                                Error("Could not create selector: ", err)
×
92
                        continue
×
93
                }
94
                nsselector, err := metav1.
1✔
95
                        LabelSelectorAsSelector(peer.NamespaceSelector)
1✔
96
                if err != nil {
1✔
97
                        networkPolicyLogger(cont.log, np).
×
98
                                Error("Could not create selector: ", err)
×
99
                        continue
×
100
                }
101

102
                switch {
1✔
103
                case peer.PodSelector != nil && peer.NamespaceSelector != nil:
1✔
104
                        ret = append(ret, index.PodSelector{
1✔
105
                                NsSelector:  nsselector,
1✔
106
                                PodSelector: podselector,
1✔
107
                        })
1✔
108
                case peer.PodSelector != nil:
1✔
109
                        ret = append(ret, index.PodSelector{
1✔
110
                                Namespace:   &np.ObjectMeta.Namespace,
1✔
111
                                PodSelector: podselector,
1✔
112
                        })
1✔
113
                case peer.NamespaceSelector != nil:
1✔
114
                        ret = append(ret, index.PodSelector{
1✔
115
                                NsSelector:  nsselector,
1✔
116
                                PodSelector: labels.Everything(),
1✔
117
                        })
1✔
118
                }
119
        }
120
        return ret
1✔
121
}
122

123
func (cont *AciController) egressPodSelector(np *v1net.NetworkPolicy) []index.PodSelector {
1✔
124
        var ret []index.PodSelector
1✔
125

1✔
126
        for _, egress := range np.Spec.Egress {
2✔
127
                ret = append(ret, cont.peerPodSelector(np, egress.To)...)
1✔
128
        }
1✔
129

130
        return ret
1✔
131
}
132

133
func (cont *AciController) ingressPodSelector(np *v1net.NetworkPolicy) []index.PodSelector {
1✔
134
        var ret []index.PodSelector
1✔
135

1✔
136
        for _, ingress := range np.Spec.Ingress {
2✔
137
                ret = append(ret, cont.peerPodSelector(np, ingress.From)...)
1✔
138
        }
1✔
139

140
        return ret
1✔
141
}
142

143
func (cont *AciController) initNetPolPodIndex() {
1✔
144
        cont.netPolPods = index.NewPodSelectorIndex(
1✔
145
                cont.log,
1✔
146
                cont.podIndexer, cont.namespaceIndexer, cont.networkPolicyIndexer,
1✔
147
                cache.MetaNamespaceKeyFunc,
1✔
148
                func(obj interface{}) []index.PodSelector {
2✔
149
                        np := obj.(*v1net.NetworkPolicy)
1✔
150
                        return index.PodSelectorFromNsAndSelector(np.ObjectMeta.Namespace,
1✔
151
                                &np.Spec.PodSelector)
1✔
152
                },
1✔
153
        )
154
        cont.netPolPods.SetPodUpdateCallback(func(podkey string) {
2✔
155
                podobj, exists, err := cont.podIndexer.GetByKey(podkey)
1✔
156
                if exists && err == nil {
2✔
157
                        cont.queuePodUpdate(podobj.(*v1.Pod))
1✔
158
                }
1✔
159
        })
160

161
        cont.netPolIngressPods = index.NewPodSelectorIndex(
1✔
162
                cont.log,
1✔
163
                cont.podIndexer, cont.namespaceIndexer, cont.networkPolicyIndexer,
1✔
164
                cache.MetaNamespaceKeyFunc,
1✔
165
                func(obj interface{}) []index.PodSelector {
2✔
166
                        return cont.ingressPodSelector(obj.(*v1net.NetworkPolicy))
1✔
167
                },
1✔
168
        )
169
        cont.netPolEgressPods = index.NewPodSelectorIndex(
1✔
170
                cont.log,
1✔
171
                cont.podIndexer, cont.namespaceIndexer, cont.networkPolicyIndexer,
1✔
172
                cache.MetaNamespaceKeyFunc,
1✔
173
                func(obj interface{}) []index.PodSelector {
2✔
174
                        return cont.egressPodSelector(obj.(*v1net.NetworkPolicy))
1✔
175
                },
1✔
176
        )
177
        npupdate := func(npkey string) {
2✔
178
                npobj, exists, err := cont.networkPolicyIndexer.GetByKey(npkey)
1✔
179
                if exists && err == nil {
2✔
180
                        cont.queueNetPolUpdate(npobj.(*v1net.NetworkPolicy))
1✔
181
                }
1✔
182
        }
183
        nphash := func(pod *v1.Pod) string {
2✔
184
                return pod.Status.PodIP
1✔
185
        }
1✔
186

187
        remipupdate := func(pod *v1.Pod, deleted bool) {
2✔
188
                cont.queueRemoteIpConUpdate(pod, deleted)
1✔
189
        }
1✔
190

191
        cont.netPolIngressPods.SetObjUpdateCallback(npupdate)
1✔
192
        cont.netPolIngressPods.SetRemIpUpdateCallback(remipupdate)
1✔
193
        cont.netPolIngressPods.SetPodHashFunc(nphash)
1✔
194
        cont.netPolEgressPods.SetObjUpdateCallback(npupdate)
1✔
195
        cont.netPolEgressPods.SetRemIpUpdateCallback(remipupdate)
1✔
196
        cont.netPolEgressPods.SetPodHashFunc(nphash)
1✔
197
}
198

199
func (cont *AciController) staticNetPolObjs() apicapi.ApicSlice {
1✔
200
        hppIngress :=
1✔
201
                apicapi.NewHostprotPol(cont.config.AciPolicyTenant,
1✔
202
                        cont.aciNameForKey("np", "static-ingress"))
1✔
203
        {
2✔
204
                ingressSubj := apicapi.NewHostprotSubj(hppIngress.GetDn(), "ingress")
1✔
205
                if !cont.configuredPodNetworkIps.V6.Empty() {
2✔
206
                        outbound := apicapi.NewHostprotRule(ingressSubj.GetDn(),
1✔
207
                                "allow-all-reflexive-v6")
1✔
208
                        outbound.SetAttr("direction", "ingress")
1✔
209
                        outbound.SetAttr("ethertype", "ipv6")
1✔
210
                        ingressSubj.AddChild(outbound)
1✔
211
                }
1✔
212
                if !cont.configuredPodNetworkIps.V4.Empty() {
2✔
213
                        outbound := apicapi.NewHostprotRule(ingressSubj.GetDn(),
1✔
214
                                "allow-all-reflexive")
1✔
215
                        outbound.SetAttr("direction", "ingress")
1✔
216
                        outbound.SetAttr("ethertype", "ipv4")
1✔
217
                        ingressSubj.AddChild(outbound)
1✔
218
                }
1✔
219
                hppIngress.AddChild(ingressSubj)
1✔
220
        }
221

222
        hppEgress :=
1✔
223
                apicapi.NewHostprotPol(cont.config.AciPolicyTenant,
1✔
224
                        cont.aciNameForKey("np", "static-egress"))
1✔
225
        {
2✔
226
                egressSubj := apicapi.NewHostprotSubj(hppEgress.GetDn(), "egress")
1✔
227
                if !cont.configuredPodNetworkIps.V6.Empty() {
2✔
228
                        outbound := apicapi.NewHostprotRule(egressSubj.GetDn(),
1✔
229
                                "allow-all-reflexive-v6")
1✔
230
                        outbound.SetAttr("direction", "egress")
1✔
231
                        outbound.SetAttr("ethertype", "ipv6")
1✔
232
                        egressSubj.AddChild(outbound)
1✔
233
                }
1✔
234
                if !cont.configuredPodNetworkIps.V4.Empty() {
2✔
235
                        outbound := apicapi.NewHostprotRule(egressSubj.GetDn(),
1✔
236
                                "allow-all-reflexive")
1✔
237
                        outbound.SetAttr("direction", "egress")
1✔
238
                        outbound.SetAttr("ethertype", "ipv4")
1✔
239
                        egressSubj.AddChild(outbound)
1✔
240
                }
1✔
241
                hppEgress.AddChild(egressSubj)
1✔
242
        }
243

244
        hppDiscovery :=
1✔
245
                apicapi.NewHostprotPol(cont.config.AciPolicyTenant,
1✔
246
                        cont.aciNameForKey("np", "static-discovery"))
1✔
247
        {
2✔
248
                discSubj := apicapi.NewHostprotSubj(hppDiscovery.GetDn(), "discovery")
1✔
249
                discDn := discSubj.GetDn()
1✔
250
                {
2✔
251
                        arpin := apicapi.NewHostprotRule(discDn, "arp-ingress")
1✔
252
                        arpin.SetAttr("direction", "ingress")
1✔
253
                        arpin.SetAttr("ethertype", "arp")
1✔
254
                        arpin.SetAttr("connTrack", "normal")
1✔
255
                        discSubj.AddChild(arpin)
1✔
256
                }
1✔
257
                {
1✔
258
                        arpout := apicapi.NewHostprotRule(discDn, "arp-egress")
1✔
259
                        arpout.SetAttr("direction", "egress")
1✔
260
                        arpout.SetAttr("ethertype", "arp")
1✔
261
                        arpout.SetAttr("connTrack", "normal")
1✔
262
                        discSubj.AddChild(arpout)
1✔
263
                }
1✔
264
                if !cont.configuredPodNetworkIps.V4.Empty() {
2✔
265
                        icmpin := apicapi.NewHostprotRule(discDn, "icmp-ingress")
1✔
266
                        icmpin.SetAttr("direction", "ingress")
1✔
267
                        icmpin.SetAttr("ethertype", "ipv4")
1✔
268
                        icmpin.SetAttr("protocol", "icmp")
1✔
269
                        icmpin.SetAttr("connTrack", "normal")
1✔
270
                        discSubj.AddChild(icmpin)
1✔
271
                }
1✔
272

273
                if !cont.configuredPodNetworkIps.V6.Empty() {
2✔
274
                        icmpin := apicapi.NewHostprotRule(discDn, "icmpv6-ingress")
1✔
275
                        icmpin.SetAttr("direction", "ingress")
1✔
276
                        icmpin.SetAttr("ethertype", "ipv6")
1✔
277
                        icmpin.SetAttr("protocol", "icmpv6")
1✔
278
                        icmpin.SetAttr("connTrack", "normal")
1✔
279
                        discSubj.AddChild(icmpin)
1✔
280
                }
1✔
281
                if !cont.configuredPodNetworkIps.V4.Empty() {
2✔
282
                        icmpout := apicapi.NewHostprotRule(discDn, "icmp-egress")
1✔
283
                        icmpout.SetAttr("direction", "egress")
1✔
284
                        icmpout.SetAttr("ethertype", "ipv4")
1✔
285
                        icmpout.SetAttr("protocol", "icmp")
1✔
286
                        icmpout.SetAttr("connTrack", "normal")
1✔
287
                        discSubj.AddChild(icmpout)
1✔
288
                }
1✔
289

290
                if !cont.configuredPodNetworkIps.V6.Empty() {
2✔
291
                        icmpout := apicapi.NewHostprotRule(discDn, "icmpv6-egress")
1✔
292
                        icmpout.SetAttr("direction", "egress")
1✔
293
                        icmpout.SetAttr("ethertype", "ipv6")
1✔
294
                        icmpout.SetAttr("protocol", "icmpv6")
1✔
295
                        icmpout.SetAttr("connTrack", "normal")
1✔
296
                        discSubj.AddChild(icmpout)
1✔
297
                }
1✔
298

299
                hppDiscovery.AddChild(discSubj)
1✔
300
        }
301

302
        return apicapi.ApicSlice{hppIngress, hppEgress, hppDiscovery}
1✔
303
}
304

305
func (cont *AciController) getHppClient() (hppclset.Interface, bool) {
1✔
306
        env := cont.env.(*K8sEnvironment)
1✔
307
        hppcl := env.hppClient
1✔
308
        if hppcl == nil {
2✔
309
                cont.log.Error("hpp client not found")
1✔
310
                return nil, false
1✔
311
        }
1✔
312
        return hppcl, true
1✔
313
}
314

315
func (cont *AciController) validateHppCr(hpp *hppv1.HostprotPol) bool {
1✔
316
        allowedProtocols := map[string]bool{
1✔
317
                "tcp":         true,
1✔
318
                "udp":         true,
1✔
319
                "icmp":        true,
1✔
320
                "icmpv6":      true,
1✔
321
                "unspecified": true,
1✔
322
        }
1✔
323

1✔
324
        for _, subj := range hpp.Spec.HostprotSubj {
2✔
325
                for _, rule := range subj.HostprotRule {
2✔
326
                        if rule.Protocol != "" {
2✔
327
                                if !allowedProtocols[rule.Protocol] {
1✔
328
                                        cont.log.Error("unknown protocol value: ", rule.Protocol, ", hostprotPol CR: ", hpp)
×
329
                                        return false
×
330
                                }
×
331
                        }
332
                }
333
        }
334
        return true
1✔
335
}
336

337
func (cont *AciController) createHostprotPol(hpp *hppv1.HostprotPol, ns string) bool {
1✔
338
        if !cont.validateHppCr(hpp) {
1✔
339
                return false
×
340
        }
×
341
        hppcl, ok := cont.getHppClient()
1✔
342
        if !ok {
2✔
343
                return false
1✔
344
        }
1✔
345

346
        cont.log.Debug("Creating HPP CR: ", hpp)
1✔
347
        _, err := hppcl.AciV1().HostprotPols(ns).Create(context.TODO(), hpp, metav1.CreateOptions{})
1✔
348
        if err != nil {
1✔
349
                cont.log.Error("Error creating HPP CR: ", err)
×
350
                return false
×
351
        }
×
352

353
        return true
1✔
354
}
355

356
func (cont *AciController) updateHostprotPol(hpp *hppv1.HostprotPol, ns string) bool {
1✔
357
        if !cont.validateHppCr(hpp) {
1✔
358
                cont.deleteHostprotPol(hpp.Name, hpp.Namespace)
×
359
                return false
×
360
        }
×
361
        hppcl, ok := cont.getHppClient()
1✔
362
        if !ok {
2✔
363
                return false
1✔
364
        }
1✔
365

366
        cont.log.Debug("Updating HPP CR: ", hpp)
1✔
367
        _, err := hppcl.AciV1().HostprotPols(ns).Update(context.TODO(), hpp, metav1.UpdateOptions{})
1✔
368
        if err != nil {
1✔
369
                cont.log.Error("Error updating HPP CR: ", err)
×
370
                return false
×
371
        }
×
372

373
        return true
1✔
374
}
375

376
func (cont *AciController) deleteAllHostprotPol() error {
1✔
377
        sysNs := os.Getenv("SYSTEM_NAMESPACE")
1✔
378
        hppcl, ok := cont.getHppClient()
1✔
379
        if !ok {
1✔
380
                cont.log.Error("Failed to delete HostprotPol CRs")
×
381
                return fmt.Errorf("HppClient not initialized")
×
382
        }
×
383

384
        cont.log.Debug("Deleting all HostprotPol CRs")
1✔
385
        err := hppcl.AciV1().HostprotPols(sysNs).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
1✔
386
        if err != nil {
1✔
387
                cont.log.Error("Failed to delete HostprotPol CRs: ", err)
×
388
        }
×
389
        return err
1✔
390
}
391

392
func (cont *AciController) deleteHostprotPol(hppName string, ns string) bool {
1✔
393
        hppcl, ok := cont.getHppClient()
1✔
394
        if !ok {
2✔
395
                return false
1✔
396
        }
1✔
397

398
        cont.log.Debug("Deleting HPP CR: ", hppName)
1✔
399
        err := hppcl.AciV1().HostprotPols(ns).Delete(context.TODO(), hppName, metav1.DeleteOptions{})
1✔
400
        if err != nil {
1✔
401
                cont.log.Error("Error deleting HPP CR: ", err)
×
402
                return false
×
403
        }
×
404

405
        return true
1✔
406
}
407

408
func (cont *AciController) getHostprotPol(hppName string, ns string) (*hppv1.HostprotPol, error) {
1✔
409
        hppcl, ok := cont.getHppClient()
1✔
410
        if !ok {
2✔
411
                return nil, fmt.Errorf("hpp client not found")
1✔
412
        }
1✔
413

414
        hpp, err := hppcl.AciV1().HostprotPols(ns).Get(context.TODO(), hppName, metav1.GetOptions{})
1✔
415
        if err != nil {
2✔
416
                return nil, err
1✔
417
        }
1✔
418
        cont.log.Debug("HPP CR found: ", hpp)
1✔
419
        return hpp, nil
1✔
420
}
421

422
func (cont *AciController) getHostprotRemoteIpContainer(name, ns string) (*hppv1.HostprotRemoteIpContainer, error) {
1✔
423
        hppcl, ok := cont.getHppClient()
1✔
424
        if !ok {
2✔
425
                return nil, fmt.Errorf("hpp client not found")
1✔
426
        }
1✔
427

428
        hpp, err := hppcl.AciV1().HostprotRemoteIpContainers(ns).Get(context.TODO(), name, metav1.GetOptions{})
1✔
429
        if err != nil {
2✔
430
                cont.log.Error("Error getting HostprotRemoteIpContainers CR: ", err)
1✔
431
                return nil, err
1✔
432
        }
1✔
433
        cont.log.Debug("HostprotRemoteIpContainers CR found: ", hpp)
1✔
434
        return hpp, nil
1✔
435
}
436

437
func (cont *AciController) createHostprotRemoteIpContainer(hppIpCont *hppv1.HostprotRemoteIpContainer, ns string) bool {
1✔
438
        hppcl, ok := cont.getHppClient()
1✔
439
        if !ok {
2✔
440
                return false
1✔
441
        }
1✔
442

443
        cont.log.Debug("Creating HostprotRemoteIpContainer CR: ", hppIpCont)
1✔
444
        _, err := hppcl.AciV1().HostprotRemoteIpContainers(ns).Create(context.TODO(), hppIpCont, metav1.CreateOptions{})
1✔
445
        if err != nil {
1✔
446
                cont.log.Error("Error creating HostprotRemoteIpContainer CR: ", err)
×
447
                return false
×
448
        }
×
449

450
        return true
1✔
451
}
452

453
func (cont *AciController) updateHostprotRemoteIpContainer(hppIpCont *hppv1.HostprotRemoteIpContainer, ns string) bool {
1✔
454
        hppcl, ok := cont.getHppClient()
1✔
455
        if !ok {
2✔
456
                return false
1✔
457
        }
1✔
458

459
        cont.log.Debug("Updating HostprotRemoteIpContainer CR: ", hppIpCont)
1✔
460
        _, err := hppcl.AciV1().HostprotRemoteIpContainers(ns).Update(context.TODO(), hppIpCont, metav1.UpdateOptions{})
1✔
461
        if err != nil {
1✔
462
                cont.log.Error("Error updating HostprotRemoteIpContainer CR: ", err)
×
463
                return false
×
464
        }
×
465

466
        return true
1✔
467
}
468

469
func (cont *AciController) deleteAllHostprotRemoteIpContainers() error {
1✔
470
        sysNs := os.Getenv("SYSTEM_NAMESPACE")
1✔
471
        hppcl, ok := cont.getHppClient()
1✔
472
        if !ok {
1✔
473
                cont.log.Error("Failed to delete HostprotRemoteIpContainer CRs")
×
474
                return fmt.Errorf("HppClient not initialized")
×
475
        }
×
476

477
        cont.log.Debug("Deleting all HostprotRemoteIpContainer CRs")
1✔
478
        err := hppcl.AciV1().HostprotRemoteIpContainers(sysNs).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
1✔
479
        if err != nil {
1✔
480
                cont.log.Error("Failed to delete HostprotRemoteIpContainer CRs: ", err)
×
481
        }
×
482
        return err
1✔
483
}
484

485
func (cont *AciController) deleteHostprotRemoteIpContainer(hppIpContName string, ns string) bool {
1✔
486
        hppcl, ok := cont.getHppClient()
1✔
487
        if !ok {
2✔
488
                return false
1✔
489
        }
1✔
490

491
        cont.log.Debug("Deleting HostprotRemoteIpContainer CR: ", hppIpContName)
1✔
492
        err := hppcl.AciV1().HostprotRemoteIpContainers(ns).Delete(context.TODO(), hppIpContName, metav1.DeleteOptions{})
1✔
493
        if err != nil {
1✔
494
                cont.log.Error("Error deleting HostprotRemoteIpContainer CR: ", err)
×
495
                return false
×
496
        }
×
497

498
        return true
1✔
499
}
500

501
func (cont *AciController) listHostprotPol(ns string) (*hppv1.HostprotPolList, error) {
1✔
502
        hppcl, ok := cont.getHppClient()
1✔
503
        if !ok {
1✔
504
                return nil, fmt.Errorf("hpp client not found")
×
505
        }
×
506

507
        hpps, err := hppcl.AciV1().HostprotPols(ns).List(context.TODO(), metav1.ListOptions{})
1✔
508
        if err != nil {
2✔
509
                cont.log.Error("Error listing HPP CR: ", err)
1✔
510
                return nil, err
1✔
511
        }
1✔
512
        return hpps, nil
×
513
}
514

515
func (cont *AciController) listHostprotRemoteIpContainers(ns string) (*hppv1.HostprotRemoteIpContainerList, error) {
1✔
516
        hppcl, ok := cont.getHppClient()
1✔
517
        if !ok {
1✔
518
                return nil, fmt.Errorf("hpp client not found")
×
519
        }
×
520

521
        hpRemoteIpConts, err := hppcl.AciV1().HostprotRemoteIpContainers(ns).List(context.TODO(), metav1.ListOptions{})
1✔
522
        if err != nil {
2✔
523
                cont.log.Error("Error getting HostprotRemoteIpContainers CRs: ", err)
1✔
524
                return nil, err
1✔
525
        }
1✔
526
        return hpRemoteIpConts, nil
×
527
}
528

529
func (cont *AciController) createStaticNetPolCrs() bool {
1✔
530
        ns := os.Getenv("SYSTEM_NAMESPACE")
1✔
531

1✔
532
        createPol := func(labelKey, subjName, direction string, rules []hppv1.HostprotRule) bool {
2✔
533
                hppName := strings.ReplaceAll(labelKey, "_", "-")
1✔
534
                if _, err := cont.getHostprotPol(hppName, ns); errors.IsNotFound(err) {
2✔
535
                        hpp := &hppv1.HostprotPol{
1✔
536
                                ObjectMeta: metav1.ObjectMeta{
1✔
537
                                        Name:      hppName,
1✔
538
                                        Namespace: ns,
1✔
539
                                },
1✔
540
                                Spec: hppv1.HostprotPolSpec{
1✔
541
                                        Name:            labelKey,
1✔
542
                                        NetworkPolicies: []string{labelKey},
1✔
543
                                        HostprotSubj: []hppv1.HostprotSubj{
1✔
544
                                                {
1✔
545
                                                        Name:         subjName,
1✔
546
                                                        HostprotRule: rules,
1✔
547
                                                },
1✔
548
                                        },
1✔
549
                                },
1✔
550
                        }
1✔
551
                        if !cont.createHostprotPol(hpp, ns) {
1✔
552
                                return false
×
553
                        }
×
554
                }
555
                return true
1✔
556
        }
557

558
        if !createPol(cont.aciNameForKey("np", "static-ingress"), "ingress", "ingress", cont.getHostprotRules("ingress")) {
1✔
559
                return false
×
560
        }
×
561
        if !createPol(cont.aciNameForKey("np", "static-egress"), "egress", "egress", cont.getHostprotRules("egress")) {
1✔
562
                return false
×
563
        }
×
564
        if !createPol(cont.aciNameForKey("np", "static-discovery"), "discovery", "discovery", cont.getDiscoveryRules()) {
1✔
565
                return false
×
566
        }
×
567

568
        return true
1✔
569
}
570

571
func (cont *AciController) getHostprotRules(direction string) []hppv1.HostprotRule {
1✔
572
        var rules []hppv1.HostprotRule
1✔
573
        outbound := hppv1.HostprotRule{
1✔
574
                ConnTrack: "reflexive",
1✔
575
                Protocol:  "unspecified",
1✔
576
                FromPort:  "unspecified",
1✔
577
                ToPort:    "unspecified",
1✔
578
                Direction: direction,
1✔
579
        }
1✔
580

1✔
581
        if !cont.configuredPodNetworkIps.V6.Empty() {
1✔
582
                outbound.Name = "allow-all-reflexive-v6"
×
583
                outbound.Ethertype = "ipv6"
×
584
                rules = append(rules, outbound)
×
585
        }
×
586
        if !cont.configuredPodNetworkIps.V4.Empty() {
2✔
587
                outbound.Name = "allow-all-reflexive"
1✔
588
                outbound.Ethertype = "ipv4"
1✔
589
                rules = append(rules, outbound)
1✔
590
        }
1✔
591

592
        return rules
1✔
593
}
594

595
func (cont *AciController) getDiscoveryRules() []hppv1.HostprotRule {
1✔
596
        rules := []hppv1.HostprotRule{
1✔
597
                {
1✔
598
                        Name:      "arp-ingress",
1✔
599
                        Direction: "ingress",
1✔
600
                        Ethertype: "arp",
1✔
601
                        ConnTrack: "normal",
1✔
602
                },
1✔
603
                {
1✔
604
                        Name:      "arp-egress",
1✔
605
                        Direction: "egress",
1✔
606
                        Ethertype: "arp",
1✔
607
                        ConnTrack: "normal",
1✔
608
                },
1✔
609
        }
1✔
610

1✔
611
        if !cont.configuredPodNetworkIps.V4.Empty() {
2✔
612
                rules = append(rules,
1✔
613
                        hppv1.HostprotRule{
1✔
614
                                Name:      "icmp-ingress",
1✔
615
                                Direction: "ingress",
1✔
616
                                Ethertype: "ipv4",
1✔
617
                                Protocol:  "icmp",
1✔
618
                                ConnTrack: "normal",
1✔
619
                        },
1✔
620
                        hppv1.HostprotRule{
1✔
621
                                Name:      "icmp-egress",
1✔
622
                                Direction: "egress",
1✔
623
                                Ethertype: "ipv4",
1✔
624
                                Protocol:  "icmp",
1✔
625
                                ConnTrack: "normal",
1✔
626
                        },
1✔
627
                )
1✔
628
        }
1✔
629

630
        if !cont.configuredPodNetworkIps.V6.Empty() {
1✔
631
                rules = append(rules,
×
632
                        hppv1.HostprotRule{
×
633
                                Name:      "icmpv6-ingress",
×
634
                                Direction: "ingress",
×
635
                                Ethertype: "ipv6",
×
636
                                Protocol:  "icmpv6",
×
637
                                ConnTrack: "normal",
×
638
                        },
×
639
                        hppv1.HostprotRule{
×
640
                                Name:      "icmpv6-egress",
×
641
                                Direction: "egress",
×
642
                                Ethertype: "ipv6",
×
643
                                Protocol:  "icmpv6",
×
644
                                ConnTrack: "normal",
×
645
                        },
×
646
                )
×
647
        }
×
648

649
        return rules
1✔
650
}
651

652
func (cont *AciController) cleanStaleHppCrs() {
1✔
653
        sysNs := os.Getenv("SYSTEM_NAMESPACE")
1✔
654
        npNames := make(map[string]struct{})
1✔
655

1✔
656
        namespaces, err := cont.listNamespaces()
1✔
657
        if err != nil {
1✔
658
                cont.log.Error("Error listing namespaces: ", err)
×
659
                return
×
660
        }
×
661

662
        for _, ns := range namespaces.Items {
1✔
663
                netpols, err := cont.listNetworkPolicies(ns.Name)
×
664
                if err != nil {
×
665
                        cont.log.Error("Error listing network policies in namespace ", ns.Name, ": ", err)
×
666
                        continue
×
667
                }
668
                for _, np := range netpols.Items {
×
669
                        nsName := np.ObjectMeta.Namespace + "/" + np.ObjectMeta.Name
×
670
                        npNames[nsName] = struct{}{}
×
671
                }
×
672
        }
673

674
        hpps, err := cont.listHostprotPol(sysNs)
1✔
675
        if err != nil {
2✔
676
                cont.log.Error("Error listing HostprotPols: ", err)
1✔
677
                return
1✔
678
        }
1✔
679

680
        for _, hpp := range hpps.Items {
×
681
                for _, npName := range hpp.Spec.NetworkPolicies {
×
682
                        if _, exists := npNames[npName]; !exists {
×
683
                                if !cont.deleteHostprotPol(hpp.ObjectMeta.Name, sysNs) {
×
684
                                        cont.log.Error("Error deleting stale HostprotPol: ", hpp.ObjectMeta.Name)
×
685
                                }
×
686
                        }
687
                }
688
        }
689
}
690

691
func (cont *AciController) cleanStaleHostprotRemoteIpContainers() {
1✔
692
        sysNs := os.Getenv("SYSTEM_NAMESPACE")
1✔
693
        nsNames := make(map[string]struct{})
1✔
694

1✔
695
        namespaces, err := cont.listNamespaces()
1✔
696
        if err != nil {
1✔
697
                cont.log.Error("Error listing namespaces: ", err)
×
698
                return
×
699
        }
×
700

701
        for _, ns := range namespaces.Items {
1✔
702
                nsNames[ns.Name] = struct{}{}
×
703
        }
×
704

705
        hpRemIpConts, err := cont.listHostprotRemoteIpContainers(sysNs)
1✔
706
        if err != nil {
2✔
707
                cont.log.Error("Error listing HostprotRemoteIpContainers: ", err)
1✔
708
                return
1✔
709
        }
1✔
710

711
        for _, hpRemIpCont := range hpRemIpConts.Items {
×
712
                if _, exists := nsNames[hpRemIpCont.ObjectMeta.Name]; !exists {
×
713
                        if !cont.deleteHostprotRemoteIpContainer(hpRemIpCont.ObjectMeta.Name, sysNs) {
×
714
                                cont.log.Error("Error deleting stale HostprotRemoteIpContainer: ", hpRemIpCont.ObjectMeta.Name)
×
715
                        }
×
716
                }
717
        }
718
}
719

720
func (cont *AciController) initStaticNetPolObjs() {
1✔
721
        if cont.config.EnableHppDirect {
2✔
722
                cont.cleanStaleHostprotRemoteIpContainers()
1✔
723
                cont.cleanStaleHppCrs()
1✔
724

1✔
725
                if !cont.createStaticNetPolCrs() {
1✔
726
                        cont.log.Error("Error creating static HPP CRs")
×
727
                }
×
728
                return
1✔
729
        } else {
1✔
730
                cont.deleteAllHostprotPol()
1✔
731
                cont.deleteAllHostprotRemoteIpContainers()
1✔
732
        }
1✔
733

734
        cont.apicConn.WriteApicObjects(cont.config.AciPrefix+"_np_static", cont.staticNetPolObjs())
1✔
735
}
736

737
func networkPolicyLogger(log *logrus.Logger,
738
        np *v1net.NetworkPolicy) *logrus.Entry {
1✔
739
        return log.WithFields(logrus.Fields{
1✔
740
                "namespace": np.ObjectMeta.Namespace,
1✔
741
                "name":      np.ObjectMeta.Name,
1✔
742
        })
1✔
743
}
1✔
744

745
func (cont *AciController) queueNetPolUpdateByKey(key string) {
1✔
746
        cont.netPolQueue.Add(key)
1✔
747
}
1✔
748

749
func (cont *AciController) queueRemoteIpConUpdate(pod *v1.Pod, deleted bool) {
1✔
750
        cont.hppMutex.Lock()
1✔
751
        update := cont.updateNsRemoteIpCont(pod, deleted)
1✔
752
        if update {
2✔
753
                podns := pod.ObjectMeta.Namespace
1✔
754
                cont.remIpContQueue.Add(podns)
1✔
755
        }
1✔
756
        cont.hppMutex.Unlock()
1✔
757
}
758

759
func (cont *AciController) queueNetPolUpdate(netpol *v1net.NetworkPolicy) {
1✔
760
        key, err := cache.MetaNamespaceKeyFunc(netpol)
1✔
761
        if err != nil {
1✔
762
                networkPolicyLogger(cont.log, netpol).
×
763
                        Error("Could not create network policy key: ", err)
×
764
                return
×
765
        }
×
766
        cont.netPolQueue.Add(key)
1✔
767
}
768

769
func (cont *AciController) peerMatchesPod(npNs string,
770
        peer *v1net.NetworkPolicyPeer, pod *v1.Pod, podNs *v1.Namespace) bool {
1✔
771
        if peer.PodSelector != nil && npNs == pod.ObjectMeta.Namespace {
2✔
772
                selector, err :=
1✔
773
                        metav1.LabelSelectorAsSelector(peer.PodSelector)
1✔
774
                if err != nil {
1✔
775
                        cont.log.Error("Could not parse pod selector: ", err)
×
776
                } else {
1✔
777
                        return selector.Matches(labels.Set(pod.ObjectMeta.Labels))
1✔
778
                }
1✔
779
        }
780
        if peer.NamespaceSelector != nil {
2✔
781
                selector, err :=
1✔
782
                        metav1.LabelSelectorAsSelector(peer.NamespaceSelector)
1✔
783
                if err != nil {
1✔
784
                        cont.log.Error("Could not parse namespace selector: ", err)
×
785
                } else {
1✔
786
                        match := selector.Matches(labels.Set(podNs.ObjectMeta.Labels))
1✔
787
                        if match && peer.PodSelector != nil {
2✔
788
                                podSelector, err :=
1✔
789
                                        metav1.LabelSelectorAsSelector(peer.PodSelector)
1✔
790
                                if err != nil {
1✔
791
                                        cont.log.Error("Could not parse pod selector: ", err)
×
792
                                } else {
1✔
793
                                        return podSelector.Matches(labels.Set(pod.ObjectMeta.Labels))
1✔
794
                                }
1✔
795
                        }
796
                        return match
1✔
797
                }
798
        }
799
        return false
×
800
}
801

802
func ipsForPod(pod *v1.Pod) []string {
1✔
803
        var ips []string
1✔
804
        podIPsField := reflect.ValueOf(pod.Status).FieldByName("PodIPs")
1✔
805
        if podIPsField.IsValid() {
2✔
806
                if len(pod.Status.PodIPs) > 0 {
2✔
807
                        for _, ip := range pod.Status.PodIPs {
2✔
808
                                ips = append(ips, ip.IP)
1✔
809
                        }
1✔
810
                        return ips
1✔
811
                }
812
        }
813
        if pod.Status.PodIP != "" {
2✔
814
                return []string{pod.Status.PodIP}
1✔
815
        }
1✔
816
        return nil
1✔
817
}
818

819
func ipBlockToSubnets(ipblock *v1net.IPBlock) ([]string, error) {
1✔
820
        _, nw, err := net.ParseCIDR(ipblock.CIDR)
1✔
821
        if err != nil {
1✔
822
                return nil, err
×
823
        }
×
824
        ips := ipam.New()
1✔
825
        ips.AddSubnet(nw)
1✔
826
        for _, except := range ipblock.Except {
2✔
827
                _, nw, err = net.ParseCIDR(except)
1✔
828
                if err != nil {
1✔
829
                        return nil, err
×
830
                }
×
831
                ips.RemoveSubnet(nw)
1✔
832
        }
833
        var subnets []string
1✔
834
        for _, r := range ips.FreeList {
2✔
835
                ipnets := ipam.Range2Cidr(r.Start, r.End)
1✔
836
                for _, n := range ipnets {
2✔
837
                        subnets = append(subnets, n.String())
1✔
838
                }
1✔
839
        }
840
        return subnets, nil
1✔
841
}
842

843
func parseCIDR(sub string) *net.IPNet {
1✔
844
        _, netw, err := net.ParseCIDR(sub)
1✔
845
        if err == nil {
2✔
846
                return netw
1✔
847
        }
1✔
848
        ip := net.ParseIP(sub)
1✔
849
        if ip == nil {
1✔
850
                return nil
×
851
        }
×
852
        var mask net.IPMask
1✔
853
        if ip.To4() != nil {
2✔
854
                mask = net.CIDRMask(32, 32)
1✔
855
        } else if ip.To16() != nil {
3✔
856
                mask = net.CIDRMask(128, 128)
1✔
857
        } else {
1✔
858
                return nil
×
859
        }
×
860
        return &net.IPNet{
1✔
861
                IP:   ip,
1✔
862
                Mask: mask,
1✔
863
        }
1✔
864
}
865

866
func netEqual(a, b net.IPNet) bool {
1✔
867
        return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask)
1✔
868
}
1✔
869

870
func (cont *AciController) updateIpIndexEntry(index cidranger.Ranger,
871
        subnetStr string, key string, add bool) bool {
1✔
872
        cidr := parseCIDR(subnetStr)
1✔
873
        if cidr == nil {
1✔
874
                cont.log.WithFields(logrus.Fields{
×
875
                        "subnet": subnetStr,
×
876
                        "netpol": key,
×
877
                }).Warning("Invalid subnet or IP")
×
878
                return false
×
879
        }
×
880

881
        entries, err := index.CoveredNetworks(*cidr)
1✔
882
        if err != nil {
1✔
883
                cont.log.Error("Corrupted subnet index: ", err)
×
884
                return false
×
885
        }
×
886
        if add {
2✔
887
                for _, entryObj := range entries {
2✔
888
                        if netEqual(entryObj.Network(), *cidr) {
2✔
889
                                entry := entryObj.(*ipIndexEntry)
1✔
890
                                existing := entry.keys[key]
1✔
891
                                entry.keys[key] = true
1✔
892
                                return !existing
1✔
893
                        }
1✔
894
                }
895

896
                entry := &ipIndexEntry{
1✔
897
                        ipNet: *cidr,
1✔
898
                        keys: map[string]bool{
1✔
899
                                key: true,
1✔
900
                        },
1✔
901
                }
1✔
902
                index.Insert(entry)
1✔
903
                return true
1✔
904
        } else {
1✔
905
                var existing bool
1✔
906
                for _, entryObj := range entries {
2✔
907
                        entry := entryObj.(*ipIndexEntry)
1✔
908
                        if entry.keys[key] {
2✔
909
                                existing = true
1✔
910
                                delete(entry.keys, key)
1✔
911
                        }
1✔
912
                        if len(entry.keys) == 0 {
2✔
913
                                index.Remove(entry.Network())
1✔
914
                        }
1✔
915
                }
916
                return existing
1✔
917
        }
918
}
919

920
// resolvedPeerPorts holds the fully-resolved peer+port data for one NP
921
// ingress/egress rule. Peer IP resolution and named port resolution are
922
// performed together so that each resolvedPortEntry carries the exact set of
923
// remote IPs to which the port applies.
924
type resolvedPeerPorts struct {
925
        // entries is the list of port-scoped IP groups. Each entry becomes one
926
        // or more APIC/HPP rules (split by ethertype).
927
        entries []resolvedPortEntry
928

929
        // subnetMap is the set of all matched peer IPs + IPBlock subnets.
930
        subnetMap map[string]bool
931
        // ipBlockSubs is the sorted list of IPBlock-derived subnets only.
932
        ipBlockSubs []string
933

934
        // HPP-Direct metadata
935
        peerNsList   []string
936
        podSelectors []*metav1.LabelSelector
937

938
        // noPeers is true when there are no peer selectors (egress no-To / ingress no-From).
939
        noPeers bool
940
        // addPodSubnetAsRemIp is true when the rule allows all namespaces.
941
        addPodSubnetAsRemIp bool
942
        // hasNamedPort is true when the rule contains at least one named port.
943
        hasNamedPort bool
944
}
945

946
// resolvedPortEntry is a single port (or port range) with its associated remote IPs.
947
type resolvedPortEntry struct {
948
        proto    string
949
        fromPort string
950
        toPort   string
951
        // ips contains the remote IPs for this entry. For non-port-scoped entries
952
        // this is the full set of matched peer IPs (pod IPs + IPBlock CIDRs). For
953
        // port-scoped entries (portScoped=true) this is the subset of pod IPs that
954
        // define the named port at a specific number.
955
        ips []string
956
        // portScoped is true when ips contains only the pod IPs that define a named
957
        // port at a specific number, rather than the full peer IP set. When true,
958
        // consumption sites must also include ipBlockSubs alongside ips.
959
        portScoped bool
960
}
961

962
func (cont *AciController) updateIpIndex(index cidranger.Ranger,
963
        oldSubnets map[string]bool, newSubnets map[string]bool, key string) {
1✔
964
        for subStr := range oldSubnets {
2✔
965
                if newSubnets[subStr] {
1✔
966
                        continue
×
967
                }
968
                cont.updateIpIndexEntry(index, subStr, key, false)
1✔
969
        }
970
        for subStr := range newSubnets {
2✔
971
                if oldSubnets[subStr] {
1✔
972
                        continue
×
973
                }
974
                cont.updateIpIndexEntry(index, subStr, key, true)
1✔
975
        }
976
}
977

978
func (cont *AciController) updateTargetPortIndex(service bool, key string,
979
        oldPorts map[string]targetPort, newPorts map[string]targetPort) {
1✔
980
        for portkey := range oldPorts {
2✔
981
                if _, ok := newPorts[portkey]; ok {
1✔
982
                        continue
×
983
                }
984

985
                entry, ok := cont.targetPortIndex[portkey]
1✔
986
                if !ok {
1✔
987
                        continue
×
988
                }
989

990
                if service {
1✔
991
                        entry.removeServiceKey(key)
×
992
                } else {
1✔
993
                        delete(entry.networkPolicyKeys, key)
1✔
994
                }
1✔
995
                if !entry.hasServiceKeys() && len(entry.networkPolicyKeys) == 0 {
1✔
996
                        delete(cont.targetPortIndex, portkey)
×
997
                }
×
998
        }
999
        for portkey, port := range newPorts {
2✔
1000
                if _, ok := oldPorts[portkey]; ok {
1✔
1001
                        continue
×
1002
                }
1003
                entry := cont.targetPortIndex[portkey]
1✔
1004
                if entry == nil {
2✔
1005
                        entry = &portIndexEntry{
1✔
1006
                                portMapping:       port,
1✔
1007
                                networkPolicyKeys: make(map[string]bool),
1✔
1008
                        }
1✔
1009
                        cont.targetPortIndex[portkey] = entry
1✔
1010
                } else {
2✔
1011
                        for p, svcKeys := range port.portServiceMap {
2✔
1012
                                if entry.portMapping.portServiceMap[p] == nil {
2✔
1013
                                        entry.portMapping.portServiceMap[p] = svcKeys
1✔
1014
                                } else if svcKeys != nil {
3✔
1015
                                        for sk := range svcKeys {
2✔
1016
                                                entry.portMapping.portServiceMap[p][sk] = true
1✔
1017
                                        }
1✔
1018
                                }
1019
                        }
1020
                }
1021

1022
                if !service {
2✔
1023
                        entry.networkPolicyKeys[key] = true
1✔
1024
                }
1✔
1025
        }
1026
}
1027

1028
func (cont *AciController) getPortNumsFromPortName(podKeys []string, portName string) map[int]bool {
1✔
1029
        ports := make(map[int]bool)
1✔
1030
        for _, podkey := range podKeys {
2✔
1031
                podobj, exists, err := cont.podIndexer.GetByKey(podkey)
1✔
1032
                if exists && err == nil {
2✔
1033
                        pod := podobj.(*v1.Pod)
1✔
1034
                        port, err := k8util.LookupContainerPortNumberByName(*pod, portName)
1✔
1035
                        if err != nil {
1✔
1036
                                continue
×
1037
                        }
1038
                        ports[int(port)] = true
1✔
1039
                }
1040
        }
1041
        if len(ports) == 0 {
2✔
1042
                cont.log.Infof("No matching portnumbers for portname %s: ", portName)
1✔
1043
        }
1✔
1044
        cont.log.Debug("PortName: ", portName, "Mapping port numbers: ", ports)
1✔
1045
        return ports
1✔
1046
}
1047

1048
// get a map of target ports for egress rules that have no "To" clause
1049
func (cont *AciController) getNetPolTargetPorts(np *v1net.NetworkPolicy) map[string]targetPort {
1✔
1050
        ports := make(map[string]targetPort)
1✔
1051
        for _, egress := range np.Spec.Egress {
2✔
1052
                if len(egress.To) != 0 && !isNamedPortPresenInNp(np) {
2✔
1053
                        continue
1✔
1054
                }
1055
                for _, port := range egress.Ports {
2✔
1056
                        if port.Port == nil {
1✔
1057
                                continue
×
1058
                        }
1059
                        proto := v1.ProtocolTCP
1✔
1060
                        if port.Protocol != nil {
2✔
1061
                                proto = *port.Protocol
1✔
1062
                        }
1✔
1063
                        npKey, _ := cache.MetaNamespaceKeyFunc(np)
1✔
1064
                        var key string
1✔
1065
                        portnums := make(map[int]map[string]bool)
1✔
1066
                        if port.Port.Type == intstr.Int {
2✔
1067
                                key = portProto(&proto) + "-num-" + port.Port.String()
1✔
1068
                                portnums[port.Port.IntValue()] = nil
1✔
1069
                        } else {
2✔
1070
                                if len(egress.To) != 0 {
2✔
1071
                                        // TODO optimize this code instead going through all matching pods every time
1✔
1072
                                        podKeys := cont.netPolEgressPods.GetPodForObj(npKey)
1✔
1073
                                        numericPorts := cont.getPortNumsFromPortName(podKeys, port.Port.String())
1✔
1074
                                        for p := range numericPorts {
2✔
1075
                                                portnums[p] = nil
1✔
1076
                                        }
1✔
1077
                                } else {
1✔
1078
                                        ctrNmpEntry, ok := cont.ctrPortNameCache[port.Port.String()]
1✔
1079
                                        if ok {
2✔
1080
                                                for key := range ctrNmpEntry.ctrNmpToPods {
2✔
1081
                                                        val := strings.Split(key, "-")
1✔
1082
                                                        if len(val) != 2 {
1✔
1083
                                                                continue
×
1084
                                                        }
1085
                                                        if val[0] == portProto(&proto) {
2✔
1086
                                                                port, _ := strconv.Atoi(val[1])
1✔
1087
                                                                portnums[port] = nil
1✔
1088
                                                        }
1✔
1089
                                                }
1090
                                        }
1091
                                }
1092
                                if len(portnums) == 0 {
2✔
1093
                                        continue
1✔
1094
                                }
1095
                                key = portProto(&proto) + "-name-" + port.Port.String()
1✔
1096
                        }
1097
                        ports[key] = targetPort{
1✔
1098
                                proto:          proto,
1✔
1099
                                portServiceMap: portnums,
1✔
1100
                        }
1✔
1101
                }
1102
        }
1103
        return ports
1✔
1104
}
1105

1106
// resolveNetPolPeersAndPorts resolves NP peers and ports together in a single
1107
// pass over matched pods. For egress named ports, port resolution is performed
1108
// during pod iteration so that each resolvedPortEntry carries the exact IPs
1109
// that define the named port at that number. This avoids repeated pod lookups.
1110
func (cont *AciController) resolveNetPolPeersAndPorts(
1111
        direction string,
1112
        peers []v1net.NetworkPolicyPeer,
1113
        ports []v1net.NetworkPolicyPort,
1114
        peerPods []*v1.Pod,
1115
        peerNs map[string]*v1.Namespace,
1116
        np *v1net.NetworkPolicy,
1117
        logger *logrus.Entry,
1118
) *resolvedPeerPorts {
1✔
1119
        namespace := np.Namespace
1✔
1120
        result := &resolvedPeerPorts{
1✔
1121
                subnetMap:           make(map[string]bool),
1✔
1122
                addPodSubnetAsRemIp: isAllowAllForAllNamespaces(peers),
1✔
1123
                noPeers:             len(peers) == 0,
1✔
1124
        }
1✔
1125

1✔
1126
        // Collect HPP-Direct pod selectors from peers (independent of pod iteration).
1✔
1127
        if cont.config.EnableHppDirect {
2✔
1128
                for i := range peers {
2✔
1129
                        if peers[i].PodSelector != nil &&
1✔
1130
                                !cont.isPodSelectorPresent(result.podSelectors, peers[i].PodSelector) {
2✔
1131
                                result.podSelectors = append(result.podSelectors, peers[i].PodSelector)
1✔
1132
                        }
1✔
1133
                }
1134
        }
1135

1136
        // --- Phase 1: Resolve peers to IPs ---
1137
        var namedPortIps map[string]map[int][]string
1✔
1138
        var allRemoteIps []string
1✔
1139
        if result.addPodSubnetAsRemIp {
1✔
1140
                // Allow-all: peers match all pods in all namespaces. Skip per-pod
×
1141
                // iteration — individual IPs are not needed (pod subnets are used
×
1142
                // instead) and namespace list can be derived from peerNs directly.
×
1143
                result.subnetMap["0.0.0.0/0"] = true
×
1144
                for nsName := range peerNs {
×
1145
                        result.peerNsList = append(result.peerNsList, nsName)
×
1146
                }
×
1147
        } else {
1✔
1148
                // For egress with specific peers, track named port → pod IP
1✔
1149
                // mappings during peer iteration to avoid repeated pod lookups later.
1✔
1150
                if direction == "egress" && len(peers) > 0 {
2✔
1151
                        for _, p := range ports {
2✔
1152
                                if p.Port != nil && p.Port.Type == intstr.String {
2✔
1153
                                        if namedPortIps == nil {
2✔
1154
                                                namedPortIps = make(map[string]map[int][]string)
1✔
1155
                                        }
1✔
1156
                                        namedPortIps[p.Port.String()] = make(map[int][]string)
1✔
1157
                                }
1158
                        }
1159
                }
1160
                for _, pod := range peerPods {
2✔
1161
                        podNs, ok := peerNs[pod.ObjectMeta.Namespace]
1✔
1162
                        if !ok {
1✔
1163
                                continue
×
1164
                        }
1165
                        for peerIx := range peers {
2✔
1166
                                if !cont.peerMatchesPod(namespace, &peers[peerIx], pod, podNs) {
2✔
1167
                                        continue
1✔
1168
                                }
1169
                                podIps := ipsForPod(pod)
1✔
1170
                                if len(podIps) == 0 || result.subnetMap[podIps[0]] {
2✔
1171
                                        break // pod already processed or has no IPs
1✔
1172
                                }
1173
                                for _, ip := range podIps {
2✔
1174
                                        result.subnetMap[ip] = true
1✔
1175
                                }
1✔
1176
                                allRemoteIps = append(allRemoteIps, podIps...)
1✔
1177
                                if !slices.Contains(result.peerNsList, pod.ObjectMeta.Namespace) {
2✔
1178
                                        result.peerNsList = append(result.peerNsList, pod.ObjectMeta.Namespace)
1✔
1179
                                }
1✔
1180
                                // Resolve named ports on this pod — port number is pod-level,
1181
                                // so look it up once and associate all of the pod's IPs.
1182
                                for portName, portMap := range namedPortIps {
2✔
1183
                                        if portNum, err := k8util.LookupContainerPortNumberByName(*pod, portName); err == nil {
2✔
1184
                                                portMap[int(portNum)] = append(portMap[int(portNum)], podIps...)
1✔
1185
                                        }
1✔
1186
                                }
1187
                                break // pod matched this peer; no need to check remaining peers
1✔
1188
                        }
1189
                }
1190
        }
1191

1192
        // IPBlock peers.
1193
        for i := range peers {
2✔
1194
                if peers[i].IPBlock == nil {
2✔
1195
                        continue
1✔
1196
                }
1197
                subs, err := ipBlockToSubnets(peers[i].IPBlock)
1✔
1198
                if err != nil {
1✔
1199
                        logger.Warning("Invalid IPBlock in network policy rule: ", err)
×
1200
                        continue
×
1201
                }
1202
                for _, subnet := range subs {
2✔
1203
                        result.subnetMap[subnet] = true
1✔
1204
                }
1✔
1205
                allRemoteIps = append(allRemoteIps, subs...)
1✔
1206
                result.ipBlockSubs = append(result.ipBlockSubs, subs...)
1✔
1207
        }
1208
        sort.Strings(allRemoteIps)
1✔
1209

1✔
1210
        // --- Phase 2: Build port entries ---
1✔
1211
        if len(ports) == 0 {
2✔
1212
                result.entries = []resolvedPortEntry{{ips: allRemoteIps}}
1✔
1213
                return result
1✔
1214
        }
1✔
1215

1216
        for j := range ports {
2✔
1217
                proto := portProto(ports[j].Protocol)
1✔
1218

1✔
1219
                if ports[j].Port == nil {
2✔
1220
                        result.entries = append(result.entries, resolvedPortEntry{proto: proto, ips: allRemoteIps})
1✔
1221
                        continue
1✔
1222
                }
1223

1224
                if ports[j].Port.Type == intstr.Int {
2✔
1225
                        entry := resolvedPortEntry{
1✔
1226
                                proto:    proto,
1✔
1227
                                fromPort: ports[j].Port.String(),
1✔
1228
                                ips:      allRemoteIps,
1✔
1229
                        }
1✔
1230
                        if ports[j].EndPort != nil {
2✔
1231
                                entry.toPort = strconv.Itoa(int(*ports[j].EndPort))
1✔
1232
                        }
1✔
1233
                        result.entries = append(result.entries, entry)
1✔
1234
                        continue
1✔
1235
                }
1236

1237
                // Named port resolution.
1238
                portName := ports[j].Port.String()
1✔
1239
                result.hasNamedPort = true
1✔
1240

1✔
1241
                if direction == "ingress" {
2✔
1242
                        var portMap map[int]bool
1✔
1243
                        if reflect.DeepEqual(np.Spec.PodSelector, metav1.LabelSelector{}) {
2✔
1244
                                // Empty PodSelector = all pods in namespace. Use ctrPortNameCache
1✔
1245
                                // filtered to the NP namespace to find port numbers efficiently.
1✔
1246
                                portMap = cont.getNamedPortNumsForNs(portName, namespace)
1✔
1247
                        } else {
2✔
1248
                                npKey := np.Namespace + "/" + np.Name
1✔
1249
                                podKeys := cont.netPolPods.GetPodForObj(npKey)
1✔
1250
                                portMap = cont.getPortNumsFromPortName(podKeys, portName)
1✔
1251
                        }
1✔
1252
                        if len(portMap) > 1 {
1✔
1253
                                resolved := make([]int, 0, len(portMap))
×
1254
                                for p := range portMap {
×
1255
                                        resolved = append(resolved, p)
×
1256
                                }
×
1257
                                logger.WithFields(logrus.Fields{
×
1258
                                        "namedPort":     portName,
×
1259
                                        "resolvedPorts": resolved,
×
1260
                                }).Warning("Ingress named port resolves to multiple port numbers " +
×
1261
                                        "across subject pods; rules will be over-permissive. " +
×
1262
                                        "Use numeric ports in this NetworkPolicy to avoid ambiguity.")
×
1263
                        }
1264
                        for portNum := range portMap {
2✔
1265
                                result.entries = append(result.entries, resolvedPortEntry{
1✔
1266
                                        proto:    proto,
1✔
1267
                                        fromPort: strconv.Itoa(portNum),
1✔
1268
                                        ips:      allRemoteIps,
1✔
1269
                                })
1✔
1270
                        }
1✔
1271
                        continue
1✔
1272
                }
1273

1274
                // Egress named port: resolve portMap and determine scope.
1275
                // Three cases unified: with-peers (namedPortIps), allow-all, no-To (global cache).
1276
                var portMap map[int][]string
1✔
1277
                portScoped := true
1✔
1278
                switch {
1✔
1279
                case namedPortIps != nil:
1✔
1280
                        portMap = namedPortIps[portName]
1✔
1281
                case result.addPodSubnetAsRemIp:
×
1282
                        portScoped = false
×
1283
                        portNums := cont.getPortNums(&ports[j])
×
1284
                        portMap = make(map[int][]string, len(portNums))
×
1285
                        for num := range portNums {
×
1286
                                portMap[num] = allRemoteIps
×
1287
                        }
×
1288
                default:
1✔
1289
                        portMap = cont.getNamedPortIPMap(portName)
1✔
1290
                }
1291
                for portNum, ips := range portMap {
2✔
1292
                        if portScoped {
2✔
1293
                                sort.Strings(ips)
1✔
1294
                        }
1✔
1295
                        result.entries = append(result.entries, resolvedPortEntry{
1✔
1296
                                proto:      proto,
1✔
1297
                                fromPort:   strconv.Itoa(portNum),
1✔
1298
                                ips:        ips,
1✔
1299
                                portScoped: portScoped,
1✔
1300
                        })
1✔
1301
                }
1302
        }
1303

1304
        // Warn if egress has IPBlock CIDRs alongside port-scoped (named port) entries.
1305
        if direction == "egress" && len(result.ipBlockSubs) > 0 {
1✔
1306
                for _, e := range result.entries {
×
1307
                        if e.portScoped {
×
1308
                                logger.WithFields(logrus.Fields{
×
1309
                                        "ipBlocks":  result.ipBlockSubs,
×
1310
                                        "namedPort": e.fromPort,
×
1311
                                }).Warning("Egress rule has IPBlock peers with named ports; " +
×
1312
                                        "named ports cannot be resolved for IPBlock destinations. " +
×
1313
                                        "Traffic to IPBlock CIDRs will be blocked by this policy rule.")
×
1314
                                break
×
1315
                        }
1316
                }
1317
        }
1318

1319
        return result
1✔
1320
}
1321

1322
func (cont *AciController) ipInPodSubnet(ip net.IP) bool {
×
1323
        for _, podsubnet := range cont.config.PodSubnet {
×
1324
                _, subnet, err := net.ParseCIDR(podsubnet)
×
1325
                if err == nil && subnet != nil {
×
1326
                        if subnet.Contains(ip) {
×
1327
                                return true
×
1328
                        }
×
1329
                }
1330
        }
1331
        return false
×
1332
}
1333

1334
func (cont *AciController) buildNetPolSubjRule(subj apicapi.ApicObject, ruleName,
1335
        direction, ethertype, proto, port string, endPort string, remoteSubnets []string,
1336
        addPodSubnetAsRemIp bool) {
1✔
1337
        rule := apicapi.NewHostprotRule(subj.GetDn(), ruleName)
1✔
1338
        rule.SetAttr("direction", direction)
1✔
1339
        rule.SetAttr("ethertype", ethertype)
1✔
1340
        if proto != "" {
2✔
1341
                rule.SetAttr("protocol", proto)
1✔
1342
        }
1✔
1343

1344
        if addPodSubnetAsRemIp {
1✔
1345
                for _, podsubnet := range cont.config.PodSubnet {
×
1346
                        _, subnet, err := net.ParseCIDR(podsubnet)
×
1347
                        if err == nil && subnet != nil {
×
1348
                                if (ethertype == "ipv4" && subnet.IP.To4() != nil) || (ethertype == "ipv6" && subnet.IP.To4() == nil) {
×
1349
                                        rule.AddChild(apicapi.NewHostprotRemoteIp(rule.GetDn(), podsubnet))
×
1350
                                }
×
1351
                        }
1352
                }
1353
        }
1354
        for _, subnetStr := range remoteSubnets {
2✔
1355
                _, subnet, err := net.ParseCIDR(subnetStr)
1✔
1356
                if err == nil && subnet != nil {
2✔
1357
                        // subnetStr is a valid CIDR notation, check its IP version and add the subnet to the rule
1✔
1358
                        if (ethertype == "ipv4" && subnet.IP.To4() != nil) || (ethertype == "ipv6" && subnet.IP.To4() == nil) {
2✔
1359
                                rule.AddChild(apicapi.NewHostprotRemoteIp(rule.GetDn(), subnetStr))
1✔
1360
                        }
1✔
1361
                } else if ip := net.ParseIP(subnetStr); ip != nil {
2✔
1362
                        if addPodSubnetAsRemIp && cont.ipInPodSubnet(ip) {
1✔
1363
                                continue
×
1364
                        }
1365
                        if ethertype == "ipv6" && (ip.To16() != nil && ip.To4() == nil) || ethertype == "ipv4" && ip.To4() != nil {
2✔
1366
                                rule.AddChild(apicapi.NewHostprotRemoteIp(rule.GetDn(), subnetStr))
1✔
1367
                        }
1✔
1368
                }
1369
        }
1370
        if port != "" {
2✔
1371
                rule.SetAttr("fromPort", port)
1✔
1372
                if endPort != "" {
2✔
1373
                        rule.SetAttr("toPort", endPort)
1✔
1374
                }
1✔
1375
        }
1376

1377
        subj.AddChild(rule)
1✔
1378
}
1379

1380
func (cont *AciController) isPodSelectorPresent(podSelectors []*metav1.LabelSelector,
1381
        podSelector *metav1.LabelSelector) bool {
1✔
1382

1✔
1383
        present := false
1✔
1384
        for _, selector := range podSelectors {
1✔
1385
                if reflect.DeepEqual(selector, podSelector) {
×
1386
                        present = true
×
1387
                        break
×
1388
                }
1389
        }
1390
        return present
1✔
1391
}
1392

1393
func (cont *AciController) buildLocalNetPolSubjRule(subj *hppv1.HostprotSubj, ruleName,
1394
        direction, ethertype, proto, port, endPort string, remoteNs []string,
1395
        podSelectors []*metav1.LabelSelector, remoteSubnets []string) {
1✔
1396
        rule := hppv1.HostprotRule{
1✔
1397
                ConnTrack: "reflexive",
1✔
1398
                Direction: "ingress",
1✔
1399
                Ethertype: "undefined",
1✔
1400
                Protocol:  "unspecified",
1✔
1401
                FromPort:  "unspecified",
1✔
1402
                ToPort:    "unspecified",
1✔
1403
        }
1✔
1404
        rule.Direction = direction
1✔
1405
        rule.Ethertype = ethertype
1✔
1406
        if proto != "" {
2✔
1407
                rule.Protocol = proto
1✔
1408
        }
1✔
1409
        rule.Name = ruleName
1✔
1410

1✔
1411
        rule.RsRemoteIpContainer = remoteNs
1✔
1412
        var remoteSubnetsCidr []hppv1.HostprotRemoteIp
1✔
1413
        for _, subnetStr := range remoteSubnets {
2✔
1414
                _, subnet, err := net.ParseCIDR(subnetStr)
1✔
1415
                if err == nil && subnet != nil {
2✔
1416
                        if (ethertype == "ipv4" && subnet.IP.To4() != nil) || (ethertype == "ipv6" && subnet.IP.To4() == nil) {
2✔
1417
                                remIpObj := hppv1.HostprotRemoteIp{
1✔
1418
                                        Addr: subnetStr,
1✔
1419
                                }
1✔
1420
                                remoteSubnetsCidr = append(remoteSubnetsCidr, remIpObj)
1✔
1421
                        }
1✔
1422
                }
1423
        }
1424
        if len(remoteSubnetsCidr) > 0 {
2✔
1425
                rule.HostprotRemoteIp = remoteSubnetsCidr
1✔
1426
        }
1✔
1427

1428
        var filterContainers []hppv1.HostprotFilterContainer
1✔
1429
        for _, podSelector := range podSelectors {
2✔
1430
                filterContainer := hppv1.HostprotFilterContainer{}
1✔
1431
                for key, val := range podSelector.MatchLabels {
2✔
1432
                        filter := hppv1.HostprotFilter{
1✔
1433
                                Key: key,
1✔
1434
                        }
1✔
1435
                        filter.Values = append(filter.Values, val)
1✔
1436
                        filter.Operator = "Equals"
1✔
1437
                        filterContainer.HostprotFilter = append(filterContainer.HostprotFilter, filter)
1✔
1438
                }
1✔
1439
                for _, expressions := range podSelector.MatchExpressions {
2✔
1440
                        filter := hppv1.HostprotFilter{
1✔
1441
                                Key:      expressions.Key,
1✔
1442
                                Values:   expressions.Values,
1✔
1443
                                Operator: string(expressions.Operator),
1✔
1444
                        }
1✔
1445
                        filterContainer.HostprotFilter = append(filterContainer.HostprotFilter, filter)
1✔
1446
                }
1✔
1447
                filterContainers = append(filterContainers, filterContainer)
1✔
1448
        }
1449

1450
        if len(filterContainers) > 0 {
2✔
1451
                rule.HostprotFilterContainer = filterContainers
1✔
1452
        }
1✔
1453

1454
        if port != "" {
2✔
1455
                rule.FromPort = port
1✔
1456
                if endPort != "" {
1✔
1457
                        rule.ToPort = endPort
×
1458
                }
×
1459
        }
1460

1461
        cont.log.Debug(direction)
1✔
1462
        if len(remoteSubnets) != 0 && direction == "egress" {
2✔
1463
                cont.log.Debug("HostprotServiceRemoteIps")
1✔
1464
                rule.HostprotServiceRemoteIps = remoteSubnets
1✔
1465
        }
1✔
1466

1467
        subj.HostprotRule = append(subj.HostprotRule, rule)
1✔
1468
}
1469

1470
func (cont *AciController) buildNetPolSubjRules(ruleName string,
1471
        subj apicapi.ApicObject, direction string,
1472
        resolved *resolvedPeerPorts, np *v1net.NetworkPolicy) {
1✔
1473
        if !resolved.noPeers && len(resolved.subnetMap) == 0 {
2✔
1474
                // Peers specified but match nothing; don't create rules.
1✔
1475
                return
1✔
1476
        }
1✔
1477
        hasV4 := !cont.configuredPodNetworkIps.V4.Empty()
1✔
1478
        hasV6 := !cont.configuredPodNetworkIps.V6.Empty()
1✔
1479
        ruleCounter := 0
1✔
1480
        for _, entry := range resolved.entries {
2✔
1481
                addPodSubnet := resolved.addPodSubnetAsRemIp
1✔
1482
                if entry.portScoped {
2✔
1483
                        // Port-scoped: only pod IPs that define the named port.
1✔
1484
                        addPodSubnet = false
1✔
1485
                }
1✔
1486
                // In HPP-opt mode the HPP is shared across sibling NPs with the same
1487
                // spec hash. Using np.Name in rule names would cause gratuitous renames
1488
                // whenever a different sibling triggers the rebuild. Use proto-port
1489
                // instead to keep names stable and unique per port entry.
1490
                entryName := np.Name
1✔
1491
                if cont.config.HppOptimization {
2✔
1492
                        if entry.proto != "" {
2✔
1493
                                entryName = protoPortKey(entry.proto, entry.fromPort)
1✔
1494
                        } else {
2✔
1495
                                entryName = "unspecified"
1✔
1496
                        }
1✔
1497
                }
1498

1499
                if entry.proto == "" && entry.fromPort == "" {
2✔
1500
                        if hasV4 {
2✔
1501
                                policyRuleName := util.AciNameForKey(ruleName+"-ipv4", "", entryName)
1✔
1502
                                cont.buildNetPolSubjRule(subj, policyRuleName, direction,
1✔
1503
                                        "ipv4", "", "", "", entry.ips, addPodSubnet)
1✔
1504
                        }
1✔
1505
                        if hasV6 {
2✔
1506
                                policyRuleName := util.AciNameForKey(ruleName+"-ipv6", "", entryName)
1✔
1507
                                cont.buildNetPolSubjRule(subj, policyRuleName, direction,
1✔
1508
                                        "ipv6", "", "", "", entry.ips, addPodSubnet)
1✔
1509
                        }
1✔
1510
                } else {
1✔
1511
                        if hasV4 {
2✔
1512
                                var prefix string
1✔
1513
                                if cont.config.HppOptimization {
2✔
1514
                                        prefix = ruleName + "-ipv4"
1✔
1515
                                } else {
2✔
1516
                                        prefix = fmt.Sprintf("%s_%d-ipv4", ruleName, ruleCounter)
1✔
1517
                                }
1✔
1518
                                policyRuleName := util.AciNameForKey(prefix, "", entryName)
1✔
1519
                                cont.buildNetPolSubjRule(subj, policyRuleName, direction,
1✔
1520
                                        "ipv4", entry.proto, entry.fromPort, entry.toPort, entry.ips, addPodSubnet)
1✔
1521
                        }
1522
                        if hasV6 {
2✔
1523
                                var prefix string
1✔
1524
                                if cont.config.HppOptimization {
2✔
1525
                                        prefix = ruleName + "-ipv6"
1✔
1526
                                } else {
2✔
1527
                                        prefix = fmt.Sprintf("%s_%d-ipv6", ruleName, ruleCounter)
1✔
1528
                                }
1✔
1529
                                policyRuleName := util.AciNameForKey(prefix, "", entryName)
1✔
1530
                                cont.buildNetPolSubjRule(subj, policyRuleName, direction,
1✔
1531
                                        "ipv6", entry.proto, entry.fromPort, entry.toPort, entry.ips, addPodSubnet)
1✔
1532
                        }
1533
                        ruleCounter++
1✔
1534
                }
1535
        }
1536
}
1537

1538
func (cont *AciController) buildLocalNetPolSubjRules(ruleName string,
1539
        subj *hppv1.HostprotSubj, direction string,
1540
        resolved *resolvedPeerPorts) {
1✔
1541
        hasV4 := !cont.configuredPodNetworkIps.V4.Empty()
1✔
1542
        hasV6 := !cont.configuredPodNetworkIps.V6.Empty()
1✔
1543
        ruleCounter := 0
1✔
1544
        peerNsList := resolved.peerNsList
1✔
1545
        for _, entry := range resolved.entries {
2✔
1546
                peerIpBlock := resolved.ipBlockSubs
1✔
1547
                if entry.portScoped {
2✔
1548
                        // Port-scoped: IPBlock CIDRs excluded for named ports.
1✔
1549
                        peerIpBlock = nil
1✔
1550
                }
1✔
1551
                // HPP-Direct always shares the HPP CRD across sibling NPs
1552
                // (spec-hash based key). Use proto-port in rule names to
1553
                // keep names stable and unique per port entry.
1554
                entryName := "unspecified"
1✔
1555
                if entry.proto != "" {
2✔
1556
                        entryName = protoPortKey(entry.proto, entry.fromPort)
1✔
1557
                }
1✔
1558

1559
                if entry.proto == "" && entry.fromPort == "" {
2✔
1560
                        if hasV4 {
2✔
1561
                                policyRuleName := util.AciNameForKey(ruleName+"-ipv4", "", entryName)
1✔
1562
                                cont.buildLocalNetPolSubjRule(subj, policyRuleName, direction,
1✔
1563
                                        "ipv4", "", "", "", peerNsList, resolved.podSelectors, peerIpBlock)
1✔
1564
                        }
1✔
1565
                        if hasV6 {
1✔
NEW
1566
                                policyRuleName := util.AciNameForKey(ruleName+"-ipv6", "", entryName)
×
1567
                                cont.buildLocalNetPolSubjRule(subj, policyRuleName, direction,
×
1568
                                        "ipv6", "", "", "", peerNsList, resolved.podSelectors, peerIpBlock)
×
1569
                        }
×
1570
                } else {
1✔
1571
                        if hasV4 {
2✔
1572
                                prefix := ruleName + "-ipv4"
1✔
1573
                                policyRuleName := util.AciNameForKey(prefix, "", entryName)
1✔
1574
                                cont.buildLocalNetPolSubjRule(subj, policyRuleName, direction,
1✔
1575
                                        "ipv4", entry.proto, entry.fromPort, entry.toPort, peerNsList, resolved.podSelectors, peerIpBlock)
1✔
1576
                        }
1✔
1577
                        if hasV6 {
1✔
NEW
1578
                                prefix := ruleName + "-ipv6"
×
NEW
1579
                                policyRuleName := util.AciNameForKey(prefix, "", entryName)
×
1580
                                cont.buildLocalNetPolSubjRule(subj, policyRuleName, direction,
×
1581
                                        "ipv6", entry.proto, entry.fromPort, entry.toPort, peerNsList, resolved.podSelectors, peerIpBlock)
×
1582
                        }
×
1583
                        ruleCounter++
1✔
1584
                }
1585
        }
1586
}
1587

1588
func (cont *AciController) getPortNums(port *v1net.NetworkPolicyPort) map[int]map[string]bool {
1✔
1589
        portkey := portKey(port)
1✔
1590
        cont.indexMutex.Lock()
1✔
1591
        defer cont.indexMutex.Unlock()
1✔
1592
        cont.log.Debug("PortKey1: ", portkey)
1✔
1593
        entry := cont.targetPortIndex[portkey]
1✔
1594
        if entry == nil || len(entry.portMapping.portServiceMap) == 0 {
2✔
1595
                return nil
1✔
1596
        }
1✔
1597
        result := make(map[int]map[string]bool, len(entry.portMapping.portServiceMap))
1✔
1598
        for p, svcKeys := range entry.portMapping.portServiceMap {
2✔
1599
                result[p] = maps.Clone(svcKeys)
1✔
1600
        }
1✔
1601
        return result
1✔
1602
}
1603

1604
// getNamedPortIPMap resolves a named port to a map of portNumber -> []podIPs.
1605
// Each pod that defines the named port contributes its IP to the list for the
1606
// specific port number that the named port resolves to on that pod. This allows
1607
// creating per-destination-IP scoped egress rules so that traffic is only
1608
// allowed to a port number on pods that actually define the named port as that
1609
// number.
1610
func (cont *AciController) getNamedPortIPMap(portName string) map[int][]string {
1✔
1611
        result := make(map[int][]string)
1✔
1612
        cont.indexMutex.Lock()
1✔
1613
        ctrNmpEntry, ok := cont.ctrPortNameCache[portName]
1✔
1614
        if !ok {
2✔
1615
                cont.indexMutex.Unlock()
1✔
1616
                return result
1✔
1617
        }
1✔
1618
        // ctrNmpToPods maps "proto-portnum" -> set of pod keys
1619
        for key, podkeys := range ctrNmpEntry.ctrNmpToPods {
2✔
1620
                val := strings.Split(key, "-")
1✔
1621
                if len(val) != 2 {
1✔
1622
                        continue
×
1623
                }
1624
                portNum, err := strconv.Atoi(val[1])
1✔
1625
                if err != nil {
1✔
1626
                        continue
×
1627
                }
1628
                for podkey := range podkeys {
2✔
1629
                        podobj, exists, err := cont.podIndexer.GetByKey(podkey)
1✔
1630
                        if !exists || err != nil {
1✔
1631
                                continue
×
1632
                        }
1633
                        pod := podobj.(*v1.Pod)
1✔
1634
                        for _, ip := range ipsForPod(pod) {
2✔
1635
                                result[portNum] = append(result[portNum], ip)
1✔
1636
                        }
1✔
1637
                }
1638
        }
1639
        cont.indexMutex.Unlock()
1✔
1640
        return result
1✔
1641
}
1642

1643
// getNamedPortNumsForNs returns the set of numeric port numbers that a named
1644
// port resolves to on pods within a specific namespace. It uses ctrPortNameCache
1645
// rather than iterating all pods in the namespace.
1646
func (cont *AciController) getNamedPortNumsForNs(portName, namespace string) map[int]bool {
1✔
1647
        result := make(map[int]bool)
1✔
1648
        nsPrefix := namespace + "/"
1✔
1649
        cont.indexMutex.Lock()
1✔
1650
        ctrNmpEntry, ok := cont.ctrPortNameCache[portName]
1✔
1651
        if !ok {
2✔
1652
                cont.indexMutex.Unlock()
1✔
1653
                return result
1✔
1654
        }
1✔
1655
        for key, podkeys := range ctrNmpEntry.ctrNmpToPods {
2✔
1656
                val := strings.Split(key, "-")
1✔
1657
                if len(val) != 2 {
1✔
1658
                        continue
×
1659
                }
1660
                portNum, err := strconv.Atoi(val[1])
1✔
1661
                if err != nil {
1✔
1662
                        continue
×
1663
                }
1664
                for podkey := range podkeys {
2✔
1665
                        if strings.HasPrefix(podkey, nsPrefix) {
2✔
1666
                                result[portNum] = true
1✔
1667
                                break
1✔
1668
                        }
1669
                }
1670
        }
1671
        cont.indexMutex.Unlock()
1✔
1672
        return result
1✔
1673
}
1674

1675
func portProto(protocol *v1.Protocol) string {
1✔
1676
        proto := "tcp"
1✔
1677
        if protocol != nil && *protocol == v1.ProtocolUDP {
2✔
1678
                proto = "udp"
1✔
1679
        } else if protocol != nil && *protocol == v1.ProtocolSCTP {
3✔
1680
                proto = "sctp"
1✔
1681
        }
1✔
1682
        return proto
1✔
1683
}
1684

1685
func portKey(p *v1net.NetworkPolicyPort) string {
1✔
1686
        portType := ""
1✔
1687
        port := ""
1✔
1688
        if p != nil && p.Port != nil {
2✔
1689
                if p.Port.Type == intstr.Int {
2✔
1690
                        portType = "num"
1✔
1691
                } else {
2✔
1692
                        portType = "name"
1✔
1693
                }
1✔
1694
                port = p.Port.String()
1✔
1695
                return portProto(p.Protocol) + "-" + portType + "-" + port
1✔
1696
        }
1697
        return ""
1✔
1698
}
1699

1700
func checkEndpointslices(subnetIndex cidranger.Ranger,
1701
        addresses []string) bool {
1✔
1702
        for _, addr := range addresses {
2✔
1703
                ip := net.ParseIP(addr)
1✔
1704
                if ip == nil {
1✔
1705
                        return false
×
1706
                }
×
1707
                contains, err := subnetIndex.Contains(ip)
1✔
1708
                if err != nil || !contains {
2✔
1709
                        return false
1✔
1710
                }
1✔
1711
        }
1712
        return true
1✔
1713
}
1714

1715
type portRemoteSubnet struct {
1716
        port           *v1net.NetworkPolicyPort
1717
        subnetMap      map[string]bool
1718
        hasNamedTarget bool
1719
}
1720

1721
func updatePortRemoteSubnets(portRemoteSubs map[string]*portRemoteSubnet,
1722
        portkey string, port *v1net.NetworkPolicyPort, subnetMap map[string]bool,
1723
        hasNamedTarget bool) {
1✔
1724
        if prs, ok := portRemoteSubs[portkey]; ok {
1✔
1725
                for s := range subnetMap {
×
1726
                        prs.subnetMap[s] = true
×
1727
                }
×
1728
                prs.hasNamedTarget = hasNamedTarget || prs.hasNamedTarget
×
1729
        } else {
1✔
1730
                portRemoteSubs[portkey] = &portRemoteSubnet{
1✔
1731
                        port:           port,
1✔
1732
                        subnetMap:      subnetMap,
1✔
1733
                        hasNamedTarget: hasNamedTarget,
1✔
1734
                }
1✔
1735
        }
1✔
1736
}
1737

1738
func protoPortKey(proto, port string) string {
1✔
1739
        return proto + "-" + port
1✔
1740
}
1✔
1741

1742
type portServiceAugment struct {
1743
        proto string
1744
        port  string
1745
        ipMap map[string]bool
1746
}
1747

1748
func updateServiceAugment(portAugments map[string]*portServiceAugment, proto, port, ip string) {
1✔
1749
        key := protoPortKey(proto, port)
1✔
1750
        if psa, ok := portAugments[key]; ok {
1✔
1751
                psa.ipMap[ip] = true
×
1752
        } else {
1✔
1753
                portAugments[key] = &portServiceAugment{
1✔
1754
                        proto: proto,
1✔
1755
                        port:  port,
1✔
1756
                        ipMap: map[string]bool{ip: true},
1✔
1757
                }
1✔
1758
        }
1✔
1759
}
1760

1761
func updateServiceAugmentForService(portAugments map[string]*portServiceAugment,
1762
        proto, port string, service *v1.Service) {
1✔
1763
        if service.Spec.ClusterIP != "" {
2✔
1764
                updateServiceAugment(portAugments,
1✔
1765
                        proto, port, service.Spec.ClusterIP)
1✔
1766
        }
1✔
1767
        for _, ig := range service.Status.LoadBalancer.Ingress {
1✔
1768
                if ig.IP == "" {
×
1769
                        continue
×
1770
                }
1771
                updateServiceAugment(portAugments,
×
1772
                        proto, port, ig.IP)
×
1773
        }
1774
}
1775

1776
// build service augment by matching peers against the endpoints ip
1777
// index
1778
func (cont *AciController) getServiceAugmentBySubnet(
1779
        prs *portRemoteSubnet, portAugments map[string]*portServiceAugment,
1780
        logger *logrus.Entry) {
1✔
1781
        matchedServices := make(map[string]bool)
1✔
1782
        subnetIndex := cidranger.NewPCTrieRanger()
1✔
1783

1✔
1784
        // find candidate service endpoints objects that include
1✔
1785
        // endpoints selected by the egress rule
1✔
1786
        cont.indexMutex.Lock()
1✔
1787
        for sub := range prs.subnetMap {
2✔
1788
                cidr := parseCIDR(sub)
1✔
1789
                if cidr == nil {
1✔
1790
                        continue
×
1791
                }
1792
                subnetIndex.Insert(cidranger.NewBasicRangerEntry(*cidr))
1✔
1793

1✔
1794
                entries, err := cont.endpointsIpIndex.CoveredNetworks(*cidr)
1✔
1795
                if err != nil {
1✔
1796
                        logger.Error("endpointsIpIndex corrupted: ", err)
×
1797
                        continue
×
1798
                }
1799
                for _, entry := range entries {
2✔
1800
                        e := entry.(*ipIndexEntry)
1✔
1801
                        for servicekey := range e.keys {
2✔
1802
                                matchedServices[servicekey] = true
1✔
1803
                        }
1✔
1804
                }
1805
        }
1806
        cont.indexMutex.Unlock()
1✔
1807

1✔
1808
        // if all endpoints are selected by egress rule, allow egress
1✔
1809
        // to the service cluster IP as well as to the endpoints
1✔
1810
        // themselves
1✔
1811
        for servicekey := range matchedServices {
2✔
1812
                serviceobj, _, err := cont.serviceIndexer.GetByKey(servicekey)
1✔
1813
                if err != nil {
1✔
1814
                        logger.Error("Could not lookup service for "+
×
1815
                                servicekey+": ", err.Error())
×
1816
                        continue
×
1817
                }
1818
                if serviceobj == nil {
1✔
1819
                        continue
×
1820
                }
1821
                service := serviceobj.(*v1.Service)
1✔
1822
                cont.serviceEndPoints.SetNpServiceAugmentForService(servicekey, service,
1✔
1823
                        prs, portAugments, subnetIndex, logger)
1✔
1824
        }
1825
}
1826

1827
// getServiceAugmentByPort builds service augment by matching against
1828
// services with a given target port.
1829
func (cont *AciController) getServiceAugmentByPort(
1830
        prs *portRemoteSubnet, portAugments map[string]*portServiceAugment,
1831
        logger *logrus.Entry) {
1✔
1832
        // nil port means it matches against all ports.  If we're here, it
1✔
1833
        // means this is a rule that matches all ports with all
1✔
1834
        // destinations, so there's no need to augment anything.
1✔
1835
        if prs.port == nil ||
1✔
1836
                prs.port.Port == nil {
2✔
1837
                return
1✔
1838
        }
1✔
1839

1840
        portkey := portKey(prs.port)
1✔
1841
        cont.indexMutex.Lock()
1✔
1842
        defer cont.indexMutex.Unlock()
1✔
1843
        entries := make(map[string]map[string]bool)
1✔
1844
        entry := cont.targetPortIndex[portkey]
1✔
1845
        if entry == nil {
2✔
1846
                return
1✔
1847
        }
1✔
1848
        if prs.port.Port.Type == intstr.String {
2✔
1849
                // Named port in netpol
1✔
1850
                for port, svcKeys := range entry.portMapping.portServiceMap {
2✔
1851
                        if len(svcKeys) > 0 {
2✔
1852
                                portstring := strconv.Itoa(port)
1✔
1853
                                entries[portstring] = svcKeys
1✔
1854
                        }
1✔
1855
                }
1856
        } else if prs.port.EndPort != nil {
2✔
1857
                // Port Range in netpol
1✔
1858
                startPort := prs.port.Port.IntValue()
1✔
1859
                endPort := int(*prs.port.EndPort)
1✔
1860
                rangeSize := endPort - startPort + 1
1✔
1861
                proto := portProto(prs.port.Protocol)
1✔
1862
                if rangeSize < len(cont.targetPortIndex) {
2✔
1863
                        for port := startPort; port <= endPort; port++ {
2✔
1864
                                portstring := strconv.Itoa(port)
1✔
1865
                                key := proto + "-num-" + portstring
1✔
1866
                                portEntry := cont.targetPortIndex[key]
1✔
1867
                                if portEntry != nil {
2✔
1868
                                        entries[portstring] = portEntry.portMapping.portServiceMap[port]
1✔
1869
                                }
1✔
1870
                        }
1871
                } else {
1✔
1872
                        protoPrefix := proto + "-num-"
1✔
1873
                        for portkey, portEntry := range cont.targetPortIndex {
2✔
1874
                                if !strings.HasPrefix(portkey, protoPrefix) {
2✔
1875
                                        continue
1✔
1876
                                }
1877
                                portNumStr := strings.TrimPrefix(portkey, protoPrefix)
1✔
1878
                                portNum, err := strconv.Atoi(portNumStr)
1✔
1879
                                if err != nil {
1✔
1880
                                        continue
×
1881
                                }
1882
                                if portNum >= startPort && portNum <= endPort {
2✔
1883
                                        portstring := strconv.Itoa(portNum)
1✔
1884
                                        entries[portstring] = portEntry.portMapping.portServiceMap[portNum]
1✔
1885
                                }
1✔
1886
                        }
1887
                }
1888
                // Look through services with named target ports as well.
1889
                for serviceKey, namedSvcEntry := range cont.namedPortServiceIndex {
2✔
1890
                        for _, svcPortEntry := range *namedSvcEntry {
2✔
1891
                                // Named ports that resolve to a single port number are
1✔
1892
                                // already handled above via the -num- entries in
1✔
1893
                                // targetPortIndex.
1✔
1894
                                if len(svcPortEntry.resolvedPorts) <= 1 {
1✔
1895
                                        continue
×
1896
                                }
1897
                                // Check if ALL resolved ports are within the range (all-or-nothing semantics)
1898
                                allInRange := true
1✔
1899
                                for resolvedPort := range svcPortEntry.resolvedPorts {
2✔
1900
                                        if resolvedPort < startPort || resolvedPort > endPort {
1✔
1901
                                                allInRange = false
×
1902
                                                break
×
1903
                                        }
1904
                                }
1905
                                if allInRange {
2✔
1906
                                        portstring := svcPortEntry.targetPortName
1✔
1907
                                        if _, ok := entries[portstring]; !ok {
2✔
1908
                                                entries[portstring] = map[string]bool{serviceKey: true}
1✔
1909
                                        } else {
1✔
1910
                                                entries[portstring][serviceKey] = true
×
1911
                                        }
×
1912
                                }
1913
                        }
1914
                }
1915
        } else if len(entry.portMapping.portServiceMap) > 0 {
2✔
1916
                // Single numeric portNum in netpol
1✔
1917
                portNum := prs.port.Port.IntValue()
1✔
1918
                entries[prs.port.Port.String()] = entry.portMapping.portServiceMap[portNum]
1✔
1919
        }
1✔
1920
        for key, servicekeys := range entries {
2✔
1921
                for servicekey := range servicekeys {
2✔
1922
                        serviceobj, _, err := cont.serviceIndexer.GetByKey(servicekey)
1✔
1923
                        if err != nil {
1✔
1924
                                logger.Error("Could not lookup service for "+
×
1925
                                        servicekey+": ", err.Error())
×
1926
                                continue
×
1927
                        }
1928
                        if serviceobj == nil {
1✔
1929
                                continue
×
1930
                        }
1931
                        service := serviceobj.(*v1.Service)
1✔
1932

1✔
1933
                        for _, svcPort := range service.Spec.Ports {
2✔
1934
                                if svcPort.Protocol != *prs.port.Protocol {
1✔
1935
                                        continue
×
1936
                                }
1937
                                // Handle the case where the NP specifies a numeric port
1938
                                // but the service has a named targetPort. The key in
1939
                                // entries is the resolved numeric port, so it won't match
1940
                                // svcPort.TargetPort.String() directly. We check if the
1941
                                // named targetPort resolves to exactly one numeric port
1942
                                // (all-or-nothing) and whether that port matches the key.
1943
                                match := false
1✔
1944
                                if indexEntry, ok := cont.namedPortServiceIndex[servicekey]; ok {
2✔
1945
                                        if svcPortIdxEntry, ok := (*indexEntry)[svcPort.Name]; ok && len(svcPortIdxEntry.resolvedPorts) == 1 {
2✔
1946
                                                intKey, error := strconv.Atoi(key)
1✔
1947
                                                if error == nil && svcPortIdxEntry.resolvedPorts[intKey] {
2✔
1948
                                                        match = true
1✔
1949
                                                }
1✔
1950
                                        }
1951
                                }
1952
                                svcTargetPort := svcPort.TargetPort.String()
1✔
1953
                                if !match && svcTargetPort != key && svcTargetPort != prs.port.Port.String() {
2✔
1954
                                        continue
1✔
1955
                                }
1956
                                proto := portProto(&svcPort.Protocol)
1✔
1957
                                port := strconv.Itoa(int(svcPort.Port))
1✔
1958

1✔
1959
                                updateServiceAugmentForService(portAugments,
1✔
1960
                                        proto, port, service)
1✔
1961

1✔
1962
                                logger.WithFields(logrus.Fields{
1✔
1963
                                        "proto":   proto,
1✔
1964
                                        "port":    port,
1✔
1965
                                        "service": servicekey,
1✔
1966
                                }).Debug("Allowing egress for service by port")
1✔
1967
                        }
1968
                }
1969
        }
1970
}
1971

1972
// The egress NetworkPolicy API were designed with the iptables
1973
// implementation in mind and don't contemplate that the layer 4 load
1974
// balancer could happen separately from the policy.  In particular,
1975
// it expects load balancer operations to be applied before the policy
1976
// is applied in both directions, so network policies would apply only
1977
// to pods and not to service IPs. This presents a problem for egress
1978
// policies on ACI since the security groups are applied before load
1979
// balancer operations when egressing, and after when ingressing.
1980
//
1981
// To solve this problem, we use some indexes to discover situations
1982
// when an egress policy covers all the endpoints associated with a
1983
// particular service, and automatically add a rule that allows egress
1984
// to the corresponding service cluster IP and ports.
1985
//
1986
// Note that this differs slightly from the behavior you'd see if you
1987
// applied the load balancer rule first: If the egress policy allows
1988
// access to a subset of the allowed IPs you'd see random failures
1989
// depending on which destination is chosen, while with this approach
1990
// it's all or nothing.  This should not impact any correctly-written
1991
// network policies.
1992
//
1993
// To do this, we work first from the set of pods and subnets matches
1994
// by the egress policy.  We use this to find using the
1995
// endpointsIpIndex all services that contain at least one of the
1996
// matched pods or subnets.  For each of these candidate services, we
1997
// find service ports for which _all_ referenced endpoints are allowed
1998
// by the egress policy.  Note that a service will have the service
1999
// port and the target port; the NetworkPolicy (confusingly) refers to
2000
// the target port.
2001
//
2002
// Once confirmed matches are found, we augment the egress policy with
2003
// extra rules to allow egress to the service IPs and service ports.
2004
//
2005
// As a special case, for rules that match everything, we also have a
2006
// backup index that works through ports which should allow more
2007
// efficient matching when allowing egress to all.
2008
func (cont *AciController) buildServiceAugment(subj apicapi.ApicObject,
2009
        localsubj *hppv1.HostprotSubj,
2010
        portRemoteSubs map[string]*portRemoteSubnet, logger *logrus.Entry) {
1✔
2011
        portAugments := make(map[string]*portServiceAugment)
1✔
2012
        for _, prs := range portRemoteSubs {
2✔
2013
                // TODO ipv6
1✔
2014
                if prs.subnetMap["0.0.0.0/0"] {
2✔
2015
                        cont.getServiceAugmentByPort(prs, portAugments, logger)
1✔
2016
                } else {
2✔
2017
                        cont.getServiceAugmentBySubnet(prs, portAugments, logger)
1✔
2018
                }
1✔
2019
        }
2020
        for _, augment := range portAugments {
2✔
2021
                var remoteIpsv4 []string
1✔
2022
                var remoteIpsv6 []string
1✔
2023
                for ipstr := range augment.ipMap {
2✔
2024
                        ip := net.ParseIP(ipstr)
1✔
2025
                        if ip == nil {
1✔
2026
                                continue
×
2027
                        } else if ip.To4() != nil {
2✔
2028
                                remoteIpsv4 = append(remoteIpsv4, ipstr)
1✔
2029
                        } else if ip.To16() != nil {
3✔
2030
                                remoteIpsv6 = append(remoteIpsv6, ipstr)
1✔
2031
                        }
1✔
2032
                }
2033
                cont.log.Debug("Service Augment: ", augment)
1✔
2034
                if !cont.config.EnableHppDirect && subj != nil {
2✔
2035
                        if len(remoteIpsv4) > 0 {
2✔
2036
                                serviceName := fmt.Sprintf("service_%s_%s-ipv4", augment.proto, augment.port)
1✔
2037
                                cont.buildNetPolSubjRule(subj,
1✔
2038
                                        serviceName,
1✔
2039
                                        "egress", "ipv4", augment.proto, augment.port, "", remoteIpsv4, false)
1✔
2040
                        }
1✔
2041
                        if len(remoteIpsv6) > 0 {
2✔
2042
                                serviceName := fmt.Sprintf("service_%s_%s-ipv6", augment.proto, augment.port)
1✔
2043
                                cont.buildNetPolSubjRule(subj,
1✔
2044
                                        serviceName,
1✔
2045
                                        "egress", "ipv6", augment.proto, augment.port, "", remoteIpsv6, false)
1✔
2046
                        }
1✔
2047
                } else if cont.config.EnableHppDirect && localsubj != nil {
2✔
2048
                        if len(remoteIpsv4) > 0 {
2✔
2049
                                cont.buildLocalNetPolSubjRule(localsubj,
1✔
2050
                                        "service_"+augment.proto+"_"+augment.port,
1✔
2051
                                        "egress", "ipv4", augment.proto, augment.port, "", nil, nil, remoteIpsv4)
1✔
2052
                        }
1✔
2053
                        if len(remoteIpsv6) > 0 {
1✔
2054
                                cont.buildLocalNetPolSubjRule(localsubj,
×
2055
                                        "service_"+augment.proto+"_"+augment.port,
×
2056
                                        "egress", "ipv6", augment.proto, augment.port, "", nil, nil, remoteIpsv6)
×
2057
                        }
×
2058
                }
2059
        }
2060
}
2061

2062
func isAllowAllForAllNamespaces(peers []v1net.NetworkPolicyPeer) bool {
1✔
2063
        addPodSubnetAsRemIp := false
1✔
2064
        if peers != nil && len(peers) > 0 {
2✔
2065
                var emptyPodSel, emptyNsSel bool
1✔
2066
                emptyPodSel = true
1✔
2067
                for _, peer := range peers {
2✔
2068
                        // namespaceSelector: {}
1✔
2069
                        if peer.NamespaceSelector != nil && peer.NamespaceSelector.MatchLabels == nil && peer.NamespaceSelector.MatchExpressions == nil {
1✔
2070
                                emptyNsSel = true
×
2071
                        }
×
2072
                        // podSelector has some fields
2073
                        if peer.PodSelector != nil && (peer.PodSelector.MatchLabels != nil || peer.PodSelector.MatchExpressions != nil) {
2✔
2074
                                emptyPodSel = false
1✔
2075
                        }
1✔
2076
                }
2077
                if emptyNsSel && emptyPodSel {
1✔
2078
                        addPodSubnetAsRemIp = true
×
2079
                }
×
2080
        }
2081
        return addPodSubnetAsRemIp
1✔
2082
}
2083

2084
func (cont *AciController) handleRemIpContUpdate(ns string) bool {
1✔
2085
        cont.hppMutex.Lock()
1✔
2086
        defer cont.hppMutex.Unlock()
1✔
2087

1✔
2088
        sysNs := os.Getenv("SYSTEM_NAMESPACE")
1✔
2089
        aobj, err := cont.getHostprotRemoteIpContainer(ns, sysNs)
1✔
2090
        isUpdate := err == nil
1✔
2091

1✔
2092
        if err != nil && !errors.IsNotFound(err) {
1✔
2093
                cont.log.Error("Error getting HostprotRemoteIpContainers CR: ", err)
×
2094
                return true
×
2095
        }
×
2096

2097
        if !isUpdate {
2✔
2098
                aobj = &hppv1.HostprotRemoteIpContainer{
1✔
2099
                        ObjectMeta: metav1.ObjectMeta{
1✔
2100
                                Name:      ns,
1✔
2101
                                Namespace: sysNs,
1✔
2102
                        },
1✔
2103
                        Spec: hppv1.HostprotRemoteIpContainerSpec{
1✔
2104
                                Name:             ns,
1✔
2105
                                HostprotRemoteIp: []hppv1.HostprotRemoteIp{},
1✔
2106
                        },
1✔
2107
                }
1✔
2108
        } else {
1✔
2109
                cont.log.Debug("HostprotRemoteIpContainers CR already exists: ", aobj)
×
2110
        }
×
2111

2112
        remIpCont, exists := cont.nsRemoteIpCont[ns]
1✔
2113
        if !exists {
2✔
2114
                if isUpdate {
1✔
2115
                        if !cont.deleteHostprotRemoteIpContainer(ns, sysNs) {
×
2116
                                return true
×
2117
                        }
×
2118
                } else {
1✔
2119
                        cont.log.Error("Couldn't find the ns in nsRemoteIpCont cache: ", ns)
1✔
2120
                        return false
1✔
2121
                }
1✔
2122
                return false
×
2123
        }
2124

2125
        aobj.Spec.HostprotRemoteIp = buildHostprotRemoteIpList(remIpCont)
1✔
2126

1✔
2127
        if isUpdate {
1✔
2128
                if !cont.updateHostprotRemoteIpContainer(aobj, sysNs) {
×
2129
                        return true
×
2130
                }
×
2131
        } else {
1✔
2132
                if !cont.createHostprotRemoteIpContainer(aobj, sysNs) {
1✔
2133
                        return true
×
2134
                }
×
2135
        }
2136

2137
        return false
1✔
2138
}
2139

2140
func buildHostprotRemoteIpList(remIpConts map[string]remoteIpCont) []hppv1.HostprotRemoteIp {
1✔
2141
        hostprotRemoteIpList := []hppv1.HostprotRemoteIp{}
1✔
2142

1✔
2143
        for _, remIpCont := range remIpConts {
2✔
2144
                for ip, labels := range remIpCont {
2✔
2145
                        remIpObj := hppv1.HostprotRemoteIp{
1✔
2146
                                Addr: ip,
1✔
2147
                        }
1✔
2148
                        for key, val := range labels {
2✔
2149
                                remIpObj.HppEpLabel = append(remIpObj.HppEpLabel, hppv1.HppEpLabel{
1✔
2150
                                        Key:   key,
1✔
2151
                                        Value: val,
1✔
2152
                                })
1✔
2153
                        }
1✔
2154
                        hostprotRemoteIpList = append(hostprotRemoteIpList, remIpObj)
1✔
2155
                }
2156
        }
2157

2158
        return hostprotRemoteIpList
1✔
2159
}
2160

2161
func (cont *AciController) deleteHppCr(np *v1net.NetworkPolicy) bool {
1✔
2162
        key, err := cache.MetaNamespaceKeyFunc(np)
1✔
2163
        logger := networkPolicyLogger(cont.log, np)
1✔
2164
        if err != nil {
1✔
2165
                logger.Error("Could not create network policy key: ", err)
×
2166
                return false
×
2167
        }
×
2168
        hash, err := util.CreateHashFromNetPol(np)
1✔
2169
        if err != nil {
1✔
2170
                logger.Error("Could not create hash from network policy: ", err)
×
2171
                return false
×
2172
        }
×
2173
        labelKey := cont.aciNameForKey("np", hash)
1✔
2174
        ns := os.Getenv("SYSTEM_NAMESPACE")
1✔
2175
        hppName := strings.ReplaceAll(labelKey, "_", "-")
1✔
2176
        hpp, _ := cont.getHostprotPol(hppName, ns)
1✔
2177
        if hpp == nil {
2✔
2178
                logger.Error("Could not find hostprotPol: ", hppName)
1✔
2179
                return false
1✔
2180
        }
1✔
2181
        netPols := hpp.Spec.NetworkPolicies
1✔
2182
        newNetPols := make([]string, 0)
1✔
2183
        for _, npName := range netPols {
2✔
2184
                if npName != key {
2✔
2185
                        newNetPols = append(newNetPols, npName)
1✔
2186
                }
1✔
2187
        }
2188

2189
        hpp.Spec.NetworkPolicies = newNetPols
1✔
2190

1✔
2191
        if len(newNetPols) > 0 {
2✔
2192
                return cont.updateHostprotPol(hpp, ns)
1✔
2193
        } else {
2✔
2194
                return cont.deleteHostprotPol(hppName, ns)
1✔
2195
        }
1✔
2196
}
2197

2198
func (cont *AciController) updateNodeIpsHostprotRemoteIpContainer(nodeIps map[string]bool) {
1✔
2199
        ns := os.Getenv("SYSTEM_NAMESPACE")
1✔
2200
        name := "nodeips"
1✔
2201

1✔
2202
        aobj, err := cont.getHostprotRemoteIpContainer(name, ns)
1✔
2203
        isUpdate := err == nil
1✔
2204

1✔
2205
        if err != nil && !errors.IsNotFound(err) {
1✔
2206
                cont.log.Error("Error getting HostprotRemoteIpContainers CR: ", err)
×
2207
                return
×
2208
        }
×
2209

2210
        if !isUpdate {
2✔
2211
                aobj = &hppv1.HostprotRemoteIpContainer{
1✔
2212
                        ObjectMeta: metav1.ObjectMeta{
1✔
2213
                                Name:      name,
1✔
2214
                                Namespace: ns,
1✔
2215
                        },
1✔
2216
                        Spec: hppv1.HostprotRemoteIpContainerSpec{
1✔
2217
                                Name:             name,
1✔
2218
                                HostprotRemoteIp: []hppv1.HostprotRemoteIp{},
1✔
2219
                        },
1✔
2220
                }
1✔
2221
        } else {
2✔
2222
                cont.log.Debug("HostprotRemoteIpContainers CR already exists: ", aobj)
1✔
2223
        }
1✔
2224

2225
        existingIps := make(map[string]bool)
1✔
2226
        for _, ip := range aobj.Spec.HostprotRemoteIp {
2✔
2227
                existingIps[ip.Addr] = true
1✔
2228
        }
1✔
2229

2230
        for ip := range nodeIps {
2✔
2231
                if !existingIps[ip] {
2✔
2232
                        aobj.Spec.HostprotRemoteIp = append(aobj.Spec.HostprotRemoteIp, hppv1.HostprotRemoteIp{Addr: ip})
1✔
2233
                }
1✔
2234
        }
2235

2236
        if isUpdate {
2✔
2237
                cont.updateHostprotRemoteIpContainer(aobj, ns)
1✔
2238
        } else {
2✔
2239
                cont.createHostprotRemoteIpContainer(aobj, ns)
1✔
2240
        }
1✔
2241
}
2242

2243
func (cont *AciController) deleteNodeIpsHostprotRemoteIpContainer(nodeIps map[string]bool) {
1✔
2244
        ns := os.Getenv("SYSTEM_NAMESPACE")
1✔
2245
        name := "nodeips"
1✔
2246

1✔
2247
        aobj, _ := cont.getHostprotRemoteIpContainer(name, ns)
1✔
2248
        if aobj == nil {
1✔
2249
                return
×
2250
        }
×
2251

2252
        newHostprotRemoteIps := aobj.Spec.HostprotRemoteIp[:0]
1✔
2253
        for _, hostprotRemoteIp := range aobj.Spec.HostprotRemoteIp {
2✔
2254
                if len(nodeIps) > 0 && !nodeIps[hostprotRemoteIp.Addr] {
2✔
2255
                        newHostprotRemoteIps = append(newHostprotRemoteIps, hostprotRemoteIp)
1✔
2256
                }
1✔
2257
        }
2258

2259
        aobj.Spec.HostprotRemoteIp = newHostprotRemoteIps
1✔
2260

1✔
2261
        if len(newHostprotRemoteIps) > 0 {
2✔
2262
                cont.updateHostprotRemoteIpContainer(aobj, ns)
1✔
2263
        } else {
2✔
2264
                cont.deleteHostprotRemoteIpContainer(name, ns)
1✔
2265
        }
1✔
2266
}
2267

2268
func (cont *AciController) updateNodeHostprotRemoteIpContainer(name string, nodeIps map[string]bool) {
1✔
2269
        ns := os.Getenv("SYSTEM_NAMESPACE")
1✔
2270

1✔
2271
        aobj, err := cont.getHostprotRemoteIpContainer(name, ns)
1✔
2272
        isUpdate := err == nil
1✔
2273

1✔
2274
        if err != nil && !errors.IsNotFound(err) {
1✔
2275
                cont.log.Error("Error getting HostprotRemoteIpContainers CR: ", err)
×
2276
                return
×
2277
        }
×
2278

2279
        if !isUpdate {
2✔
2280
                aobj = &hppv1.HostprotRemoteIpContainer{
1✔
2281
                        ObjectMeta: metav1.ObjectMeta{
1✔
2282
                                Name:      name,
1✔
2283
                                Namespace: ns,
1✔
2284
                        },
1✔
2285
                        Spec: hppv1.HostprotRemoteIpContainerSpec{
1✔
2286
                                Name:             name,
1✔
2287
                                HostprotRemoteIp: []hppv1.HostprotRemoteIp{},
1✔
2288
                        },
1✔
2289
                }
1✔
2290
        } else {
2✔
2291
                cont.log.Debug("HostprotRemoteIpContainers CR already exists: ", aobj)
1✔
2292
        }
1✔
2293

2294
        aobj.Spec.HostprotRemoteIp = make([]hppv1.HostprotRemoteIp, 0, len(nodeIps))
1✔
2295
        for ip := range nodeIps {
2✔
2296
                aobj.Spec.HostprotRemoteIp = append(aobj.Spec.HostprotRemoteIp, hppv1.HostprotRemoteIp{Addr: ip})
1✔
2297
        }
1✔
2298

2299
        if isUpdate {
2✔
2300
                cont.updateHostprotRemoteIpContainer(aobj, ns)
1✔
2301
        } else {
2✔
2302
                cont.createHostprotRemoteIpContainer(aobj, ns)
1✔
2303
        }
1✔
2304
}
2305

2306
func (cont *AciController) deleteNodeHostprotRemoteIpContainer(name string) {
1✔
2307
        ns := os.Getenv("SYSTEM_NAMESPACE")
1✔
2308

1✔
2309
        if _, err := cont.getHostprotRemoteIpContainer(name, ns); err == nil {
2✔
2310
                cont.deleteHostprotRemoteIpContainer(name, ns)
1✔
2311
        }
1✔
2312
}
2313

2314
func (cont *AciController) createNodeHostProtPol(name, nodeName string, nodeIps map[string]bool) {
1✔
2315
        ns := os.Getenv("SYSTEM_NAMESPACE")
1✔
2316
        hppName := strings.ReplaceAll(name, "_", "-")
1✔
2317

1✔
2318
        hpp, err := cont.getHostprotPol(hppName, ns)
1✔
2319
        isUpdate := hpp != nil && err == nil
1✔
2320

1✔
2321
        if err != nil && !errors.IsNotFound(err) {
1✔
2322
                cont.log.Error("Error getting HPP CR: ", err)
×
2323
                return
×
2324
        }
×
2325

2326
        if !isUpdate {
1✔
2327
                hpp = &hppv1.HostprotPol{
×
2328
                        ObjectMeta: metav1.ObjectMeta{
×
2329
                                Name:      hppName,
×
2330
                                Namespace: ns,
×
2331
                        },
×
2332
                        Spec: hppv1.HostprotPolSpec{
×
2333
                                Name:            name,
×
2334
                                NetworkPolicies: []string{name},
×
2335
                                HostprotSubj:    []hppv1.HostprotSubj{},
×
2336
                        },
×
2337
                }
×
2338
        } else {
1✔
2339
                cont.log.Debug("HPP CR already exists: ", hpp)
1✔
2340
                hpp.Spec.HostprotSubj = []hppv1.HostprotSubj{}
1✔
2341
        }
1✔
2342

2343
        if len(nodeIps) > 0 {
2✔
2344
                cont.updateNodeHostprotRemoteIpContainer(nodeName, nodeIps)
1✔
2345
                cont.updateNodeIpsHostprotRemoteIpContainer(nodeIps)
1✔
2346

1✔
2347
                hostprotSubj := hppv1.HostprotSubj{
1✔
2348
                        Name: "local-node",
1✔
2349
                        HostprotRule: []hppv1.HostprotRule{
1✔
2350
                                {
1✔
2351
                                        Name:                "allow-all-egress",
1✔
2352
                                        Direction:           "egress",
1✔
2353
                                        Ethertype:           "ipv4",
1✔
2354
                                        ConnTrack:           "normal",
1✔
2355
                                        RsRemoteIpContainer: []string{nodeName},
1✔
2356
                                },
1✔
2357
                                {
1✔
2358
                                        Name:                "allow-all-ingress",
1✔
2359
                                        Direction:           "ingress",
1✔
2360
                                        Ethertype:           "ipv4",
1✔
2361
                                        ConnTrack:           "normal",
1✔
2362
                                        RsRemoteIpContainer: []string{nodeName},
1✔
2363
                                },
1✔
2364
                        },
1✔
2365
                }
1✔
2366

1✔
2367
                hpp.Spec.HostprotSubj = append(hpp.Spec.HostprotSubj, hostprotSubj)
1✔
2368
        } else {
2✔
2369
                cont.deleteNodeHostprotRemoteIpContainer(nodeName)
1✔
2370
                cont.deleteNodeIpsHostprotRemoteIpContainer(nodeIps)
1✔
2371
        }
1✔
2372

2373
        if isUpdate {
2✔
2374
                cont.updateHostprotPol(hpp, ns)
1✔
2375
        } else {
1✔
2376
                cont.createHostprotPol(hpp, ns)
×
2377
        }
×
2378
}
2379

2380
func (cont *AciController) handleNetPolUpdate(np *v1net.NetworkPolicy) bool {
1✔
2381
        if cont.isCNOEnabled() {
1✔
2382
                return false
×
2383
        }
×
2384
        key, err := cache.MetaNamespaceKeyFunc(np)
1✔
2385
        logger := networkPolicyLogger(cont.log, np)
1✔
2386
        if err != nil {
1✔
2387
                logger.Error("Could not create network policy key: ", err)
×
2388
                return false
×
2389
        }
×
2390

2391
        peerPodKeys := cont.netPolIngressPods.GetPodForObj(key)
1✔
2392
        peerPodKeys =
1✔
2393
                append(peerPodKeys, cont.netPolEgressPods.GetPodForObj(key)...)
1✔
2394
        var peerPods []*v1.Pod
1✔
2395
        peerNs := make(map[string]*v1.Namespace)
1✔
2396
        for _, podkey := range peerPodKeys {
2✔
2397
                podobj, exists, err := cont.podIndexer.GetByKey(podkey)
1✔
2398
                if exists && err == nil {
2✔
2399
                        pod := podobj.(*v1.Pod)
1✔
2400
                        if _, nsok := peerNs[pod.ObjectMeta.Namespace]; !nsok {
2✔
2401
                                nsobj, exists, err :=
1✔
2402
                                        cont.namespaceIndexer.GetByKey(pod.ObjectMeta.Namespace)
1✔
2403
                                if !exists || err != nil {
1✔
2404
                                        continue
×
2405
                                }
2406
                                peerNs[pod.ObjectMeta.Namespace] = nsobj.(*v1.Namespace)
1✔
2407
                        }
2408
                        peerPods = append(peerPods, pod)
1✔
2409
                }
2410
        }
2411
        ptypeset := make(map[v1net.PolicyType]bool)
1✔
2412
        for _, t := range np.Spec.PolicyTypes {
2✔
2413
                ptypeset[t] = true
1✔
2414
        }
1✔
2415
        var labelKey string
1✔
2416

1✔
2417
        if !cont.config.EnableHppDirect {
2✔
2418
                if cont.config.HppOptimization {
2✔
2419
                        hash, err := util.CreateHashFromNetPol(np)
1✔
2420
                        if err != nil {
1✔
2421
                                logger.Error("Could not create hash from network policy: ", err)
×
2422
                                return false
×
2423
                        }
×
2424
                        labelKey = cont.aciNameForKey("np", hash)
1✔
2425
                } else {
1✔
2426
                        labelKey = cont.aciNameForKey("np", key)
1✔
2427
                }
1✔
2428
                hpp := apicapi.NewHostprotPol(cont.config.AciPolicyTenant, labelKey)
1✔
2429

1✔
2430
                var hasNamedPorts bool
1✔
2431

1✔
2432
                // Generate ingress policies
1✔
2433
                if np.Spec.PolicyTypes == nil || ptypeset[v1net.PolicyTypeIngress] {
2✔
2434
                        subjIngress :=
1✔
2435
                                apicapi.NewHostprotSubj(hpp.GetDn(), "networkpolicy-ingress")
1✔
2436

1✔
2437
                        for i, ingress := range np.Spec.Ingress {
2✔
2438
                                resolved := cont.resolveNetPolPeersAndPorts("ingress",
1✔
2439
                                        ingress.From, ingress.Ports, peerPods, peerNs, np, logger)
1✔
2440
                                cont.buildNetPolSubjRules(strconv.Itoa(i), subjIngress, "ingress", resolved, np)
1✔
2441
                                if resolved.hasNamedPort {
2✔
2442
                                        hasNamedPorts = true
1✔
2443
                                }
1✔
2444
                        }
2445

2446
                        // Merge sibling NPs' named port resolutions into this subject.
2447
                        // Rule names encode the ingress-rule index + proto-port so siblings
2448
                        // produce identically-named rules. Cache full rule names and pull
2449
                        // missing ones from the previously-written HPP object.
2450
                        if cont.config.HppOptimization && hasNamedPorts {
2✔
2451
                                ruleNames := make(map[string]bool)
1✔
2452
                                for _, body := range subjIngress {
2✔
2453
                                        for _, rule := range body.Children {
2✔
2454
                                                name := rule.GetAttrStr("name")
1✔
2455
                                                if name != "" {
2✔
2456
                                                        ruleNames[name] = true
1✔
2457
                                                }
1✔
2458
                                        }
2459
                                }
2460
                                cont.cacheNpIngressRules(labelKey, key, ruleNames)
1✔
2461
                                cont.mergeHppIngressRules(labelKey, key, ruleNames, subjIngress)
1✔
2462
                        }
2463
                        hpp.AddChild(subjIngress)
1✔
2464
                }
2465
                // Generate egress policies
2466
                if np.Spec.PolicyTypes == nil || ptypeset[v1net.PolicyTypeEgress] {
2✔
2467
                        subjEgress :=
1✔
2468
                                apicapi.NewHostprotSubj(hpp.GetDn(), "networkpolicy-egress")
1✔
2469

1✔
2470
                        portRemoteSubs := make(map[string]*portRemoteSubnet)
1✔
2471

1✔
2472
                        for i, egress := range np.Spec.Egress {
2✔
2473
                                resolved := cont.resolveNetPolPeersAndPorts("egress",
1✔
2474
                                        egress.To, egress.Ports, peerPods, peerNs, np, logger)
1✔
2475
                                cont.buildNetPolSubjRules(strconv.Itoa(i), subjEgress, "egress", resolved, np)
1✔
2476

1✔
2477
                                subnetMap := resolved.subnetMap
1✔
2478
                                if len(egress.To) == 0 {
2✔
2479
                                        subnetMap = map[string]bool{
1✔
2480
                                                "0.0.0.0/0": true,
1✔
2481
                                        }
1✔
2482
                                }
1✔
2483
                                for idx := range egress.Ports {
2✔
2484
                                        port := egress.Ports[idx]
1✔
2485
                                        portkey := portKey(&port)
1✔
2486
                                        updatePortRemoteSubnets(portRemoteSubs, portkey, &port, subnetMap,
1✔
2487
                                                port.Port != nil && port.Port.Type == intstr.Int)
1✔
2488
                                }
1✔
2489
                                if len(egress.Ports) == 0 {
2✔
2490
                                        updatePortRemoteSubnets(portRemoteSubs, "", nil, subnetMap,
1✔
2491
                                                false)
1✔
2492
                                }
1✔
2493
                        }
2494

2495
                        cont.buildServiceAugment(subjEgress, nil, portRemoteSubs, logger)
1✔
2496
                        hpp.AddChild(subjEgress)
1✔
2497
                }
2498
                if cont.config.HppOptimization {
2✔
2499
                        cont.addToHppCache(labelKey, key, apicapi.ApicSlice{hpp}, &hppv1.HostprotPol{})
1✔
2500
                }
1✔
2501
                cont.apicConn.WriteApicObjects(labelKey, apicapi.ApicSlice{hpp})
1✔
2502
        } else {
1✔
2503
                hash, err := util.CreateHashFromNetPol(np)
1✔
2504
                if err != nil {
1✔
2505
                        logger.Error("Could not create hash from network policy: ", err)
×
2506
                        return false
×
2507
                }
×
2508
                labelKey = cont.aciNameForKey("np", hash)
1✔
2509
                ns := os.Getenv("SYSTEM_NAMESPACE")
1✔
2510
                hppName := strings.ReplaceAll(labelKey, "_", "-")
1✔
2511
                hpp, err := cont.getHostprotPol(hppName, ns)
1✔
2512
                isUpdate := err == nil
1✔
2513

1✔
2514
                if err != nil && !errors.IsNotFound(err) {
1✔
2515
                        logger.Error("Error getting HPP CR: ", err)
×
2516
                        return false
×
2517
                }
×
2518

2519
                if isUpdate {
2✔
2520
                        logger.Debug("HPP CR already exists: ", hpp)
1✔
2521
                        if !slices.Contains(hpp.Spec.NetworkPolicies, key) {
2✔
2522
                                hpp.Spec.NetworkPolicies = append(hpp.Spec.NetworkPolicies, key)
1✔
2523
                        }
1✔
2524
                        hpp.Spec.HostprotSubj = nil
1✔
2525
                } else {
1✔
2526
                        hpp = &hppv1.HostprotPol{
1✔
2527
                                ObjectMeta: metav1.ObjectMeta{
1✔
2528
                                        Name:      hppName,
1✔
2529
                                        Namespace: ns,
1✔
2530
                                },
1✔
2531
                                Spec: hppv1.HostprotPolSpec{
1✔
2532
                                        Name:            labelKey,
1✔
2533
                                        NetworkPolicies: []string{key},
1✔
2534
                                        HostprotSubj:    nil,
1✔
2535
                                },
1✔
2536
                        }
1✔
2537
                }
1✔
2538

2539
                var hasNamedPorts bool
1✔
2540

1✔
2541
                // Generate ingress policies
1✔
2542
                if np.Spec.PolicyTypes == nil || ptypeset[v1net.PolicyTypeIngress] {
2✔
2543
                        subjIngress := &hppv1.HostprotSubj{
1✔
2544
                                Name:         "networkpolicy-ingress",
1✔
2545
                                HostprotRule: []hppv1.HostprotRule{},
1✔
2546
                        }
1✔
2547

1✔
2548
                        for i, ingress := range np.Spec.Ingress {
2✔
2549
                                resolved := cont.resolveNetPolPeersAndPorts("ingress",
1✔
2550
                                        ingress.From, ingress.Ports, peerPods, peerNs, np, logger)
1✔
2551
                                if resolved.hasNamedPort {
2✔
2552
                                        hasNamedPorts = true
1✔
2553
                                }
1✔
2554
                                if isAllowAllForAllNamespaces(ingress.From) {
1✔
2555
                                        if !slices.Contains(resolved.peerNsList, "nodeips") {
×
2556
                                                resolved.peerNsList = append(resolved.peerNsList, "nodeips")
×
2557
                                        }
×
2558
                                }
2559
                                if !(!resolved.noPeers && len(resolved.subnetMap) == 0) {
2✔
2560
                                        cont.buildLocalNetPolSubjRules(strconv.Itoa(i), subjIngress, "ingress", resolved)
1✔
2561
                                }
1✔
2562
                        }
2563

2564
                        // Merge sibling NPs' named port resolutions into this subject.
2565
                        // Cache full rule names and pull missing ones from the HPP CR.
2566
                        if hasNamedPorts {
2✔
2567
                                ruleNames := make(map[string]bool)
1✔
2568
                                for _, rule := range subjIngress.HostprotRule {
2✔
2569
                                        if rule.Name != "" {
2✔
2570
                                                ruleNames[rule.Name] = true
1✔
2571
                                        }
1✔
2572
                                }
2573
                                cont.cacheNpIngressRules(labelKey, key, ruleNames)
1✔
2574
                                cont.mergeHppDirectIngressRules(labelKey, key, ruleNames, subjIngress)
1✔
2575
                        }
2576
                        hpp.Spec.HostprotSubj = append(hpp.Spec.HostprotSubj, *subjIngress)
1✔
2577
                }
2578

2579
                if np.Spec.PolicyTypes == nil || ptypeset[v1net.PolicyTypeEgress] {
2✔
2580
                        subjEgress := &hppv1.HostprotSubj{
1✔
2581
                                Name:         "networkpolicy-egress",
1✔
2582
                                HostprotRule: []hppv1.HostprotRule{},
1✔
2583
                        }
1✔
2584

1✔
2585
                        portRemoteSubs := make(map[string]*portRemoteSubnet)
1✔
2586

1✔
2587
                        for i, egress := range np.Spec.Egress {
2✔
2588
                                resolved := cont.resolveNetPolPeersAndPorts("egress",
1✔
2589
                                        egress.To, egress.Ports, peerPods, peerNs, np, logger)
1✔
2590
                                if isAllowAllForAllNamespaces(egress.To) {
1✔
2591
                                        if !slices.Contains(resolved.peerNsList, "nodeips") {
×
2592
                                                resolved.peerNsList = append(resolved.peerNsList, "nodeips")
×
2593
                                        }
×
2594
                                }
2595
                                if !(!resolved.noPeers && len(resolved.subnetMap) == 0) {
2✔
2596
                                        cont.buildLocalNetPolSubjRules(strconv.Itoa(i), subjEgress, "egress", resolved)
1✔
2597
                                }
1✔
2598

2599
                                subnetMap := resolved.subnetMap
1✔
2600
                                if len(egress.To) == 0 {
2✔
2601
                                        subnetMap = map[string]bool{"0.0.0.0/0": true}
1✔
2602
                                }
1✔
2603
                                for idx := range egress.Ports {
2✔
2604
                                        port := egress.Ports[idx]
1✔
2605
                                        portkey := portKey(&port)
1✔
2606
                                        updatePortRemoteSubnets(portRemoteSubs, portkey, &port, subnetMap,
1✔
2607
                                                port.Port != nil && port.Port.Type == intstr.Int)
1✔
2608
                                }
1✔
2609
                                if len(egress.Ports) == 0 {
1✔
2610
                                        updatePortRemoteSubnets(portRemoteSubs, "", nil, subnetMap,
×
2611
                                                false)
×
2612
                                }
×
2613
                        }
2614

2615
                        cont.buildServiceAugment(nil, subjEgress, portRemoteSubs, logger)
1✔
2616
                        hpp.Spec.HostprotSubj = append(hpp.Spec.HostprotSubj, *subjEgress)
1✔
2617
                }
2618

2619
                cont.addToHppCache(labelKey, key, apicapi.ApicSlice{}, hpp)
1✔
2620

1✔
2621
                if isUpdate {
2✔
2622
                        cont.updateHostprotPol(hpp, ns)
1✔
2623
                } else {
2✔
2624
                        cont.createHostprotPol(hpp, ns)
1✔
2625
                }
1✔
2626
        }
2627
        return false
1✔
2628
}
2629

2630
func (cont *AciController) updateNsRemoteIpCont(pod *v1.Pod, deleted bool) bool {
1✔
2631
        podips := ipsForPod(pod)
1✔
2632
        podns := pod.ObjectMeta.Namespace
1✔
2633
        podname := pod.ObjectMeta.Name
1✔
2634
        podlabels := pod.ObjectMeta.Labels
1✔
2635
        remipconts, ok := cont.nsRemoteIpCont[podns]
1✔
2636

1✔
2637
        if deleted {
2✔
2638
                if !ok {
2✔
2639
                        return true
1✔
2640
                }
1✔
2641

2642
                present := false
1✔
2643
                if remipcont, remipcontok := remipconts[podname]; remipcontok {
1✔
2644
                        for _, ip := range podips {
×
2645
                                if _, ipok := remipcont[ip]; ipok {
×
2646
                                        delete(remipcont, ip)
×
2647
                                        present = true
×
2648
                                }
×
2649
                        }
2650
                        if len(remipcont) < 1 {
×
2651
                                delete(remipconts, podname)
×
2652
                        }
×
2653
                }
2654

2655
                if len(remipconts) < 1 {
1✔
2656
                        delete(cont.nsRemoteIpCont, podns)
×
2657
                        cont.apicConn.ClearApicObjects(cont.aciNameForKey("hostprot-ns-", podns))
×
2658
                        return false
×
2659
                }
×
2660

2661
                if !present {
2✔
2662
                        return false
1✔
2663
                }
1✔
2664
        } else {
1✔
2665
                if !ok {
2✔
2666
                        remipconts = make(remoteIpConts)
1✔
2667
                        cont.nsRemoteIpCont[podns] = remipconts
1✔
2668
                }
1✔
2669

2670
                remipcont, remipcontok := remipconts[podname]
1✔
2671
                if !remipcontok {
2✔
2672
                        remipcont = make(remoteIpCont)
1✔
2673
                }
1✔
2674
                for _, ip := range podips {
2✔
2675
                        remipcont[ip] = podlabels
1✔
2676
                }
1✔
2677
                remipconts[podname] = remipcont
1✔
2678
        }
2679

2680
        return true
1✔
2681
}
2682

2683
// mergeHppIngressRules merges missing ingress rules from sibling NPs into
2684
// the current subject. It compares full rule names (which encode the ingress
2685
// rule index + proto-port) to avoid false positives from overlapping ports
2686
// in different ingress rules. Rules present in the HPP object but missing
2687
// from the current NP are added if claimed by a sibling's cached rule names.
2688
func (cont *AciController) mergeHppIngressRules(labelKey, key string,
2689
        ruleNames map[string]bool, subjIngress apicapi.ApicObject) {
1✔
2690
        cont.indexMutex.Lock()
1✔
2691
        defer cont.indexMutex.Unlock()
1✔
2692

1✔
2693
        hppRef, ok := cont.hppRef[labelKey]
1✔
2694
        if !ok {
1✔
NEW
2695
                return
×
NEW
2696
        }
×
2697

2698
        // Collect all rule names claimed by siblings but not in current NP.
2699
        siblingNames := make(map[string]bool)
1✔
2700
        for _, npKey := range hppRef.Npkeys {
2✔
2701
                if npKey == key {
2✔
2702
                        continue
1✔
2703
                }
2704
                for name := range hppRef.NpIngressRules[npKey] {
2✔
2705
                        if !ruleNames[name] {
2✔
2706
                                siblingNames[name] = true
1✔
2707
                        }
1✔
2708
                }
2709
        }
2710
        if len(siblingNames) == 0 {
2✔
2711
                return
1✔
2712
        }
1✔
2713

2714
        // Find rules in the HPP object whose names are claimed by siblings.
2715
        for _, hppObj := range hppRef.HppObj {
2✔
2716
                hppBody, ok := hppObj["hostprotPol"]
1✔
2717
                if !ok || hppBody == nil {
1✔
NEW
2718
                        continue
×
2719
                }
2720
                for _, child := range hppBody.Children {
2✔
2721
                        subj, ok := child["hostprotSubj"]
1✔
2722
                        if !ok || subj == nil || subj.Attributes["name"] != "networkpolicy-ingress" {
2✔
2723
                                continue
1✔
2724
                        }
2725
                        for _, ruleChild := range subj.Children {
2✔
2726
                                rule, ok := ruleChild["hostprotRule"]
1✔
2727
                                if !ok || rule == nil {
2✔
2728
                                        continue
1✔
2729
                                }
2730
                                name, _ := rule.Attributes["name"].(string)
1✔
2731
                                if siblingNames[name] {
2✔
2732
                                        subjIngress.AddChild(ruleChild)
1✔
2733
                                        delete(siblingNames, name)
1✔
2734
                                }
1✔
2735
                        }
2736
                }
2737
        }
2738
}
2739

2740
// mergeHppDirectIngressRules merges missing ingress rules from sibling NPs
2741
// into the current HPP-Direct subject. Same approach as mergeHppIngressRules
2742
// but operates on the HPP CR struct instead of the APIC object tree.
2743
func (cont *AciController) mergeHppDirectIngressRules(labelKey, key string,
2744
        ruleNames map[string]bool, subjIngress *hppv1.HostprotSubj) {
1✔
2745
        cont.indexMutex.Lock()
1✔
2746
        defer cont.indexMutex.Unlock()
1✔
2747

1✔
2748
        hppRef, ok := cont.hppRef[labelKey]
1✔
2749
        if !ok {
1✔
NEW
2750
                return
×
NEW
2751
        }
×
2752

2753
        // Collect all rule names claimed by siblings but not in current NP.
2754
        siblingNames := make(map[string]bool)
1✔
2755
        for _, npKey := range hppRef.Npkeys {
2✔
2756
                if npKey == key {
1✔
NEW
2757
                        continue
×
2758
                }
2759
                for name := range hppRef.NpIngressRules[npKey] {
2✔
2760
                        if !ruleNames[name] {
2✔
2761
                                siblingNames[name] = true
1✔
2762
                        }
1✔
2763
                }
2764
        }
2765
        if len(siblingNames) == 0 {
2✔
2766
                return
1✔
2767
        }
1✔
2768

2769
        // Find rules in the HPP CR whose names are claimed by siblings.
2770
        for i := range hppRef.HppCr.Spec.HostprotSubj {
2✔
2771
                subj := &hppRef.HppCr.Spec.HostprotSubj[i]
1✔
2772
                if subj.Name != "networkpolicy-ingress" {
2✔
2773
                        continue
1✔
2774
                }
2775
                for _, rule := range subj.HostprotRule {
2✔
2776
                        if siblingNames[rule.Name] {
2✔
2777
                                subjIngress.HostprotRule = append(subjIngress.HostprotRule, rule)
1✔
2778
                                delete(siblingNames, rule.Name)
1✔
2779
                        }
1✔
2780
                }
2781
        }
2782
}
2783

2784
// cacheNpIngressRules caches the set of ingress rule names produced by a
2785
// single NP within a shared HPP. Sibling NPs use these names to identify
2786
// which rules in the HPP object belong to which NP and should be preserved.
2787
func (cont *AciController) cacheNpIngressRules(labelKey, npKey string, names map[string]bool) {
1✔
2788
        cont.indexMutex.Lock()
1✔
2789
        defer cont.indexMutex.Unlock()
1✔
2790

1✔
2791
        hppRef := cont.hppRef[labelKey]
1✔
2792
        if hppRef.NpIngressRules == nil {
2✔
2793
                hppRef.NpIngressRules = make(map[string]map[string]bool)
1✔
2794
        }
1✔
2795
        hppRef.NpIngressRules[npKey] = names
1✔
2796
        cont.hppRef[labelKey] = hppRef
1✔
2797
}
2798

2799
func (cont *AciController) addToHppCache(labelKey, key string, hpp apicapi.ApicSlice, hppcr *hppv1.HostprotPol) {
1✔
2800
        cont.indexMutex.Lock()
1✔
2801
        hppRef, ok := cont.hppRef[labelKey]
1✔
2802
        if ok {
2✔
2803
                var found bool
1✔
2804
                for _, npkey := range hppRef.Npkeys {
2✔
2805
                        if npkey == key {
2✔
2806
                                found = true
1✔
2807
                                break
1✔
2808
                        }
2809
                }
2810
                if !found {
2✔
2811
                        hppRef.RefCount++
1✔
2812
                        hppRef.Npkeys = append(hppRef.Npkeys, key)
1✔
2813
                }
1✔
2814
                hppRef.HppObj = hpp
1✔
2815
                hppRef.HppCr = *hppcr
1✔
2816
                cont.hppRef[labelKey] = hppRef
1✔
2817
        } else {
1✔
2818
                var newHppRef hppReference
1✔
2819
                newHppRef.RefCount++
1✔
2820
                newHppRef.HppObj = hpp
1✔
2821
                newHppRef.HppCr = *hppcr
1✔
2822
                newHppRef.Npkeys = append(newHppRef.Npkeys, key)
1✔
2823
                cont.hppRef[labelKey] = newHppRef
1✔
2824
        }
1✔
2825
        cont.indexMutex.Unlock()
1✔
2826
}
2827

2828
func (cont *AciController) removeFromHppCache(np *v1net.NetworkPolicy, key string) (string, bool) {
1✔
2829
        var labelKey string
1✔
2830
        var noRef bool
1✔
2831
        hash, err := util.CreateHashFromNetPol(np)
1✔
2832
        if err != nil {
1✔
2833
                cont.log.Error("Could not create hash from network policy: ", err)
×
2834
                cont.log.Error("Failed to remove np from hpp cache")
×
2835
                return labelKey, noRef
×
2836
        }
×
2837
        labelKey = cont.aciNameForKey("np", hash)
1✔
2838
        cont.indexMutex.Lock()
1✔
2839
        hppRef, ok := cont.hppRef[labelKey]
1✔
2840
        if ok {
2✔
2841
                for i, npkey := range hppRef.Npkeys {
2✔
2842
                        if npkey == key {
2✔
2843
                                hppRef.Npkeys = append(hppRef.Npkeys[:i], hppRef.Npkeys[i+1:]...)
1✔
2844
                                hppRef.RefCount--
1✔
2845
                                break
1✔
2846
                        }
2847
                }
2848
                _, hadRuleCache := hppRef.NpIngressRules[key]
1✔
2849
                delete(hppRef.NpIngressRules, key)
1✔
2850
                if hppRef.RefCount > 0 {
1✔
2851
                        cont.hppRef[labelKey] = hppRef
×
NEW
2852
                        // Requeue one sibling so the HPP is rewritten without
×
NEW
2853
                        // the departed NP's cached ingress rules.
×
NEW
2854
                        if hadRuleCache {
×
NEW
2855
                                cont.queueNetPolUpdateByKey(hppRef.Npkeys[0])
×
NEW
2856
                        }
×
2857
                } else {
1✔
2858
                        delete(cont.hppRef, labelKey)
1✔
2859
                        noRef = true
1✔
2860
                }
1✔
2861
        }
2862
        cont.indexMutex.Unlock()
1✔
2863
        return labelKey, noRef
1✔
2864
}
2865

2866
func getNetworkPolicyEgressIpBlocks(np *v1net.NetworkPolicy) map[string]bool {
1✔
2867
        subnets := make(map[string]bool)
1✔
2868
        for _, egress := range np.Spec.Egress {
2✔
2869
                for _, to := range egress.To {
2✔
2870
                        if to.IPBlock != nil && to.IPBlock.CIDR != "" {
2✔
2871
                                subnets[to.IPBlock.CIDR] = true
1✔
2872
                        }
1✔
2873
                }
2874
        }
2875
        return subnets
1✔
2876
}
2877

2878
func (cont *AciController) networkPolicyAdded(obj interface{}) {
1✔
2879
        np := obj.(*v1net.NetworkPolicy)
1✔
2880
        npkey, err := cache.MetaNamespaceKeyFunc(np)
1✔
2881
        if err != nil {
1✔
2882
                networkPolicyLogger(cont.log, np).
×
2883
                        Error("Could not create network policy key: ", err)
×
2884
                return
×
2885
        }
×
2886
        if cont.isCNOEnabled() {
1✔
2887
                return
×
2888
        }
×
2889
        cont.netPolPods.UpdateSelectorObj(obj)
1✔
2890
        cont.netPolIngressPods.UpdateSelectorObj(obj)
1✔
2891
        cont.netPolEgressPods.UpdateSelectorObj(obj)
1✔
2892
        cont.indexMutex.Lock()
1✔
2893
        subnets := getNetworkPolicyEgressIpBlocks(np)
1✔
2894
        cont.updateIpIndex(cont.netPolSubnetIndex, nil, subnets, npkey)
1✔
2895

1✔
2896
        ports := cont.getNetPolTargetPorts(np)
1✔
2897
        cont.updateTargetPortIndex(false, npkey, nil, ports)
1✔
2898
        if isNamedPortPresenInNp(np) {
2✔
2899
                cont.nmPortNp[npkey] = true
1✔
2900
        }
1✔
2901
        cont.indexMutex.Unlock()
1✔
2902
        cont.queueNetPolUpdateByKey(npkey)
1✔
2903
}
2904

2905
func (cont *AciController) networkPolicyChanged(oldobj interface{},
2906
        newobj interface{}) {
1✔
2907
        oldnp := oldobj.(*v1net.NetworkPolicy)
1✔
2908
        newnp := newobj.(*v1net.NetworkPolicy)
1✔
2909
        npkey, err := cache.MetaNamespaceKeyFunc(newnp)
1✔
2910
        if err != nil {
1✔
2911
                networkPolicyLogger(cont.log, newnp).
×
2912
                        Error("Could not create network policy key: ", err)
×
2913
                return
×
2914
        }
×
2915

2916
        if cont.config.HppOptimization || cont.config.EnableHppDirect {
2✔
2917
                if !reflect.DeepEqual(oldnp.Spec, newnp.Spec) {
2✔
2918
                        labelKey, noHppRef := cont.removeFromHppCache(oldnp, npkey)
1✔
2919
                        if noHppRef && labelKey != "" {
2✔
2920
                                cont.apicConn.ClearApicObjects(labelKey)
1✔
2921
                        }
1✔
2922
                }
2923
        }
2924

2925
        cont.indexMutex.Lock()
1✔
2926
        oldSubnets := getNetworkPolicyEgressIpBlocks(oldnp)
1✔
2927
        newSubnets := getNetworkPolicyEgressIpBlocks(newnp)
1✔
2928
        cont.updateIpIndex(cont.netPolSubnetIndex, oldSubnets, newSubnets, npkey)
1✔
2929

1✔
2930
        oldPorts := cont.getNetPolTargetPorts(oldnp)
1✔
2931
        newPorts := cont.getNetPolTargetPorts(newnp)
1✔
2932
        cont.updateTargetPortIndex(false, npkey, oldPorts, newPorts)
1✔
2933
        cont.indexMutex.Unlock()
1✔
2934

1✔
2935
        if !reflect.DeepEqual(oldnp.Spec.PodSelector, newnp.Spec.PodSelector) {
1✔
2936
                cont.netPolPods.UpdateSelectorObjNoCallback(newobj)
×
2937
        }
×
2938
        if !reflect.DeepEqual(oldnp.Spec.PolicyTypes, newnp.Spec.PolicyTypes) {
2✔
2939
                peerPodKeys := cont.netPolPods.GetPodForObj(npkey)
1✔
2940
                for _, podkey := range peerPodKeys {
1✔
2941
                        cont.podQueue.Add(podkey)
×
2942
                }
×
2943
        }
2944
        var queue bool
1✔
2945
        if !reflect.DeepEqual(oldnp.Spec.Ingress, newnp.Spec.Ingress) {
2✔
2946
                cont.netPolIngressPods.UpdateSelectorObjNoCallback(newobj)
1✔
2947
                queue = true
1✔
2948
        }
1✔
2949
        if !reflect.DeepEqual(oldnp.Spec.Egress, newnp.Spec.Egress) {
1✔
2950
                cont.netPolEgressPods.UpdateSelectorObjNoCallback(newobj)
×
2951
                queue = true
×
2952
        }
×
2953
        if cont.config.EnableHppDirect && !reflect.DeepEqual(oldnp.Spec, newnp.Spec) {
2✔
2954
                cont.deleteHppCr(oldnp)
1✔
2955
                queue = true
1✔
2956
        }
1✔
2957
        if queue {
2✔
2958
                cont.queueNetPolUpdateByKey(npkey)
1✔
2959
        }
1✔
2960
}
2961

2962
func (cont *AciController) networkPolicyDeleted(obj interface{}) {
1✔
2963
        np, isNetworkpolicy := obj.(*v1net.NetworkPolicy)
1✔
2964
        if !isNetworkpolicy {
1✔
2965
                deletedState, ok := obj.(cache.DeletedFinalStateUnknown)
×
2966
                if !ok {
×
2967
                        networkPolicyLogger(cont.log, np).
×
2968
                                Error("Received unexpected object: ", obj)
×
2969
                        return
×
2970
                }
×
2971
                np, ok = deletedState.Obj.(*v1net.NetworkPolicy)
×
2972
                if !ok {
×
NEW
2973
                        cont.log.Error("DeletedFinalStateUnknown contained non-Networkpolicy object: ", deletedState.Obj)
×
2974
                        return
×
2975
                }
×
2976
        }
2977
        npkey, err := cache.MetaNamespaceKeyFunc(np)
1✔
2978
        if err != nil {
1✔
2979
                networkPolicyLogger(cont.log, np).
×
2980
                        Error("Could not create network policy key: ", err)
×
2981
                return
×
2982
        }
×
2983

2984
        var labelKey string
1✔
2985
        var noHppRef bool
1✔
2986
        if cont.config.HppOptimization || cont.config.EnableHppDirect {
1✔
2987
                labelKey, noHppRef = cont.removeFromHppCache(np, npkey)
×
2988
        } else {
1✔
2989
                labelKey = cont.aciNameForKey("np", npkey)
1✔
2990
                noHppRef = true
1✔
2991
        }
1✔
2992

2993
        cont.indexMutex.Lock()
1✔
2994
        subnets := getNetworkPolicyEgressIpBlocks(np)
1✔
2995
        cont.updateIpIndex(cont.netPolSubnetIndex, subnets, nil, npkey)
1✔
2996

1✔
2997
        ports := cont.getNetPolTargetPorts(np)
1✔
2998
        cont.updateTargetPortIndex(false, npkey, ports, nil)
1✔
2999
        if isNamedPortPresenInNp(np) {
2✔
3000
                delete(cont.nmPortNp, npkey)
1✔
3001
        }
1✔
3002
        cont.indexMutex.Unlock()
1✔
3003

1✔
3004
        cont.netPolPods.DeleteSelectorObj(obj)
1✔
3005
        cont.netPolIngressPods.DeleteSelectorObj(obj)
1✔
3006
        cont.netPolEgressPods.DeleteSelectorObj(obj)
1✔
3007
        if noHppRef && labelKey != "" {
2✔
3008
                cont.apicConn.ClearApicObjects(labelKey)
1✔
3009
        }
1✔
3010
        if cont.config.EnableHppDirect {
1✔
3011
                cont.deleteHppCr(np)
×
3012
        }
×
3013
}
3014

3015
func (seps *serviceEndpointSlice) SetNpServiceAugmentForService(servicekey string, service *v1.Service,
3016
        prs *portRemoteSubnet, portAugments map[string]*portServiceAugment,
3017
        subnetIndex cidranger.Ranger, logger *logrus.Entry) {
1✔
3018
        cont := seps.cont
1✔
3019
        npTargetPortsMap := cont.getPortNums(prs.port)
1✔
3020

1✔
3021
        // Helper function to check if a numeric port matches the NetworkPolicy port spec
1✔
3022
        checkNumericPortMatchesNetpol := func(port int) bool {
2✔
3023
                if prs.port.EndPort != nil {
2✔
3024
                        // Port range matching: port must be within [Port, EndPort]
1✔
3025
                        return port >= prs.port.Port.IntValue() && port <= int(*prs.port.EndPort)
1✔
3026
                }
1✔
3027
                // Single port matching: check if port is in the target ports map
3028
                // and was registered by this service
3029
                svcKeys, ok := npTargetPortsMap[port]
1✔
3030
                if !ok {
2✔
3031
                        return false
1✔
3032
                }
1✔
3033
                return svcKeys == nil || svcKeys[servicekey]
1✔
3034
        }
3035

3036
        label := map[string]string{discovery.LabelServiceName: service.ObjectMeta.Name}
1✔
3037
        selector := labels.SelectorFromSet(label)
1✔
3038

1✔
3039
        endpointSliceList, err := cont.endpointSliceIndexer.ByIndex("namespace", service.ObjectMeta.Namespace)
1✔
3040
        if err != nil {
1✔
3041
                logger.Error("Could not list endpoint slices: ", err)
×
3042
                return
×
3043
        }
×
3044
        for _, svcPort := range service.Spec.Ports {
2✔
3045
                incomplete := false
1✔
3046
                hasValidatedSlice := false
1✔
3047
                if prs.port != nil &&
1✔
3048
                        (svcPort.Protocol != *prs.port.Protocol) {
1✔
3049
                        // egress rule does not match service target port
×
3050
                        continue
×
3051
                }
3052
                // Match any port if no port is specified in the np
3053
                portMatched := prs.port == nil || prs.port.Port == nil
1✔
3054

1✔
3055
                if !portMatched {
2✔
3056
                        if svcPort.TargetPort.Type == intstr.String {
2✔
3057
                                if prs.port.Port.Type == intstr.String {
2✔
3058
                                        if prs.port.Port.String() != svcPort.TargetPort.String() {
1✔
3059
                                                continue
×
3060
                                        }
3061
                                        portMatched = true
1✔
3062
                                }
3063
                        } else {
1✔
3064
                                if !checkNumericPortMatchesNetpol(svcPort.TargetPort.IntValue()) {
2✔
3065
                                        continue
1✔
3066
                                }
3067
                                portMatched = true
1✔
3068
                        }
3069
                }
3070

3071
                for _, endpointSliceobj := range endpointSliceList {
2✔
3072
                        endpointSlices := endpointSliceobj.(*discovery.EndpointSlice)
1✔
3073
                        if !selector.Matches(labels.Set(endpointSlices.Labels)) {
2✔
3074
                                continue
1✔
3075
                        }
3076

3077
                        var foundEpPort *discovery.EndpointPort
1✔
3078
                        for ix := range endpointSlices.Ports {
2✔
3079
                                if endpointSlices.Ports[ix].Name != nil && *endpointSlices.Ports[ix].Name == svcPort.Name ||
1✔
3080
                                        (len(service.Spec.Ports) == 1 &&
1✔
3081
                                                endpointSlices.Ports[ix].Name != nil && *endpointSlices.Ports[ix].Name == "") {
2✔
3082
                                        foundEpPort = &endpointSlices.Ports[ix]
1✔
3083
                                        cont.log.Debug("Found EpPort: ", foundEpPort)
1✔
3084
                                        break
1✔
3085
                                }
3086
                        }
3087

3088
                        if foundEpPort == nil {
1✔
3089
                                continue
×
3090
                        }
3091
                        if !portMatched && (foundEpPort.Port == nil || !checkNumericPortMatchesNetpol(int(*foundEpPort.Port))) {
1✔
3092
                                incomplete = true
×
3093
                                break
×
3094
                        }
3095
                        // @FIXME for non ready address
3096
                        for _, endpoint := range endpointSlices.Endpoints {
2✔
3097
                                incomplete = incomplete || !checkEndpointslices(subnetIndex, endpoint.Addresses)
1✔
3098
                        }
1✔
3099
                        if incomplete {
2✔
3100
                                break
1✔
3101
                        }
3102
                        hasValidatedSlice = true
1✔
3103
                }
3104
                if !incomplete && hasValidatedSlice {
2✔
3105
                        proto := portProto(&svcPort.Protocol)
1✔
3106
                        port := strconv.Itoa(int(svcPort.Port))
1✔
3107
                        cont.log.Debug("updateServiceAugmentForService: ", service)
1✔
3108
                        updateServiceAugmentForService(portAugments,
1✔
3109
                                proto, port, service)
1✔
3110
                        logger.WithFields(logrus.Fields{
1✔
3111
                                "proto":   proto,
1✔
3112
                                "port":    port,
1✔
3113
                                "service": servicekey,
1✔
3114
                        }).Debug("Allowing egress for service by subnet match")
1✔
3115
                }
1✔
3116
        }
3117
}
3118

3119
func isNamedPortPresenInNp(np *v1net.NetworkPolicy) bool {
1✔
3120
        for _, egress := range np.Spec.Egress {
2✔
3121
                for _, p := range egress.Ports {
2✔
3122
                        if p.Port.Type == intstr.String {
2✔
3123
                                return true
1✔
3124
                        }
1✔
3125
                }
3126
        }
3127
        return false
1✔
3128
}
3129

3130
func (cont *AciController) checkPodNmpMatchesNp(npkey, podkey string) bool {
1✔
3131
        podobj, exists, err := cont.podIndexer.GetByKey(podkey)
1✔
3132
        if err != nil {
1✔
3133
                return false
×
3134
        }
×
3135
        if !exists || podobj == nil {
2✔
3136
                return false
1✔
3137
        }
1✔
3138
        pod := podobj.(*v1.Pod)
1✔
3139
        npobj, npexists, nperr := cont.networkPolicyIndexer.GetByKey(npkey)
1✔
3140
        if npexists && nperr == nil && npobj != nil {
2✔
3141
                np := npobj.(*v1net.NetworkPolicy)
1✔
3142
                for _, egress := range np.Spec.Egress {
2✔
3143
                        for _, p := range egress.Ports {
2✔
3144
                                if p.Port.Type == intstr.String {
2✔
3145
                                        _, err := k8util.LookupContainerPortNumberByName(*pod, p.Port.String())
1✔
3146
                                        if err == nil {
2✔
3147
                                                return true
1✔
3148
                                        }
1✔
3149
                                }
3150
                        }
3151
                }
3152
        }
3153
        return false
1✔
3154
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc