Skip to content

Commit 8d16dd1

Browse files
committed
ClusterNetworkPolicy
1 parent f3ab703 commit 8d16dd1

File tree

3 files changed

+296
-7
lines changed

3 files changed

+296
-7
lines changed

cmd/main.go

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ import (
1616
"sigs.k8s.io/kube-network-policies/pkg/dns"
1717
"sigs.k8s.io/kube-network-policies/pkg/networkpolicy"
1818
"sigs.k8s.io/kube-network-policies/pkg/podinfo"
19+
20+
npav1alpha2 "sigs.k8s.io/network-policy-api/apis/v1alpha2"
1921
npaclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned"
2022
npainformers "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions"
21-
"sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha1"
23+
npainformersv1alpha1 "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha1"
24+
npainformersv1alpha2 "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha2"
2225

2326
"k8s.io/apimachinery/pkg/api/meta"
2427
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -38,6 +41,7 @@ var (
3841
failOpen bool
3942
adminNetworkPolicy bool // AdminNetworkPolicy is alpha so keep it feature gated behind a flag
4043
baselineAdminNetworkPolicy bool // BaselineAdminNetworkPolicy is alpha so keep it feature gated behind a flag
44+
clusterNetworkPolicy bool // ClusterNetworkPolicy is alpha so keep it feature gated behind a flag
4145
queueID int
4246
metricsBindAddress string
4347
hostnameOverride string
@@ -49,6 +53,7 @@ func init() {
4953
flag.BoolVar(&failOpen, "fail-open", false, "If set, don't drop packets if the controller is not running")
5054
flag.BoolVar(&adminNetworkPolicy, "admin-network-policy", false, "If set, enable Admin Network Policy API")
5155
flag.BoolVar(&baselineAdminNetworkPolicy, "baseline-admin-network-policy", false, "If set, enable Baseline Admin Network Policy API")
56+
flag.BoolVar(&clusterNetworkPolicy, "cluster-network-policy", false, "If set, enable Cluster-network-policy")
5257
flag.IntVar(&queueID, "nfqueue-id", 100, "Number of the nfqueue used")
5358
flag.StringVar(&metricsBindAddress, "metrics-bind-address", ":9080", "The IP address and port for the metrics server to serve on")
5459
flag.StringVar(&hostnameOverride, "hostname-override", "", "If non-empty, will be used as the name of the Node that kube-network-policies is running on. If unset, the node name is assumed to be the same as the node's hostname.")
@@ -131,7 +136,8 @@ func run() int {
131136
var npaClient *npaclient.Clientset
132137
var npaInformerFactory npainformers.SharedInformerFactory
133138
var nodeInformer coreinformers.NodeInformer
134-
if adminNetworkPolicy || baselineAdminNetworkPolicy {
139+
140+
if adminNetworkPolicy || baselineAdminNetworkPolicy || clusterNetworkPolicy {
135141
nodeInformer = informersFactory.Core().V1().Nodes()
136142
npaClient, err = npaclient.NewForConfig(npaConfig)
137143
if err != nil {
@@ -140,14 +146,18 @@ func run() int {
140146
npaInformerFactory = npainformers.NewSharedInformerFactory(npaClient, 0)
141147
}
142148

143-
var anpInformer v1alpha1.AdminNetworkPolicyInformer
149+
var anpInformer npainformersv1alpha1.AdminNetworkPolicyInformer
144150
if adminNetworkPolicy {
145151
anpInformer = npaInformerFactory.Policy().V1alpha1().AdminNetworkPolicies()
146152
}
147-
var banpInformer v1alpha1.BaselineAdminNetworkPolicyInformer
153+
var banpInformer npainformersv1alpha1.BaselineAdminNetworkPolicyInformer
148154
if baselineAdminNetworkPolicy {
149155
banpInformer = npaInformerFactory.Policy().V1alpha1().BaselineAdminNetworkPolicies()
150156
}
157+
var cnpInformer npainformersv1alpha2.ClusterNetworkPolicyInformer
158+
if clusterNetworkPolicy {
159+
cnpInformer = npaInformerFactory.Policy().V1alpha2().ClusterNetworkPolicies()
160+
}
151161

152162
nsInformer := informersFactory.Core().V1().Namespaces()
153163
networkPolicyInfomer := informersFactory.Networking().V1().NetworkPolicies()
@@ -203,25 +213,40 @@ func run() int {
203213
evaluators = append(evaluators, networkpolicy.NewLoggingPolicy())
204214
}
205215

206-
if adminNetworkPolicy {
216+
var domainResolver networkpolicy.DomainResolver
217+
// If AdminNetworkPolicy or ClusterNetworkPolicy are enabled, we need a domain resolver.
218+
if adminNetworkPolicy || clusterNetworkPolicy {
219+
klog.Infof("AdminNetworkPolicy or ClusterNetworkPolicy enabled, starting domain cache")
207220
// Admin Network Policy need to associate IP addresses to Domains
208221
// NewDomainCache implements the interface DomainResolver using
209222
// nftables to create a cache with the resolved IP addresses from the
210223
// Pod domain queries.
211-
domainResolver := dns.NewDomainCache(queueID + 1)
224+
domainCache := dns.NewDomainCache(queueID + 1)
212225
go func() {
213-
err := domainResolver.Run(ctx)
226+
err := domainCache.Run(ctx)
214227
if err != nil {
215228
klog.Infof("domain cache controller exited: %v", err)
216229
}
217230
}()
231+
domainResolver = domainCache
232+
233+
}
218234

235+
if adminNetworkPolicy {
219236
evaluators = append(evaluators, networkpolicy.NewAdminNetworkPolicy(
220237
anpInformer,
221238
domainResolver,
222239
))
223240
}
224241

242+
if clusterNetworkPolicy {
243+
evaluators = append(evaluators, networkpolicy.NewClusterNetworkPolicy(
244+
npav1alpha2.AdminTier,
245+
cnpInformer,
246+
domainResolver,
247+
))
248+
}
249+
225250
// Standard Network Policy goes after AdminNetworkPolicy and before BaselineAdminNetworkPolicy
226251
evaluators = append(evaluators, networkpolicy.NewStandardNetworkPolicy(
227252
networkPolicyInfomer,
@@ -233,6 +258,14 @@ func run() int {
233258
))
234259
}
235260

261+
if clusterNetworkPolicy {
262+
evaluators = append(evaluators, networkpolicy.NewClusterNetworkPolicy(
263+
npav1alpha2.BaselineTier,
264+
cnpInformer,
265+
nil,
266+
))
267+
}
268+
236269
http.Handle("/metrics", promhttp.Handler())
237270
go func() {
238271
err := http.ListenAndServe(metricsBindAddress, nil)
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
// SPDX-License-Identifier: APACHE-2.0
2+
3+
package networkpolicy
4+
5+
import (
6+
"cmp"
7+
"context"
8+
"net"
9+
"slices"
10+
11+
v1 "k8s.io/api/core/v1"
12+
"k8s.io/apimachinery/pkg/labels"
13+
"k8s.io/klog/v2"
14+
"sigs.k8s.io/kube-network-policies/pkg/api"
15+
"sigs.k8s.io/kube-network-policies/pkg/network"
16+
npav1alpha2 "sigs.k8s.io/network-policy-api/apis/v1alpha2"
17+
cnpinformers "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha2"
18+
cnplisters "sigs.k8s.io/network-policy-api/pkg/client/listers/apis/v1alpha2"
19+
)
20+
21+
// ClusterNetworkPolicy implements the PolicyEvaluator interface for the CNP API.
22+
type ClusterNetworkPolicy struct {
23+
tier npav1alpha2.Tier
24+
cnpLister cnplisters.ClusterNetworkPolicyLister
25+
domainResolver DomainResolver
26+
}
27+
28+
var _ PolicyEvaluator = &ClusterNetworkPolicy{}
29+
30+
// NewClusterNetworkPolicy creates a new CNP implementation.
31+
func NewClusterNetworkPolicy(tier npav1alpha2.Tier, cnpInformer cnpinformers.ClusterNetworkPolicyInformer, domainResolver DomainResolver) *ClusterNetworkPolicy {
32+
return &ClusterNetworkPolicy{
33+
tier: tier,
34+
cnpLister: cnpInformer.Lister(),
35+
domainResolver: domainResolver,
36+
}
37+
}
38+
39+
func (c *ClusterNetworkPolicy) Name() string {
40+
return "ClusterNetworkPolicy" + string(c.tier)
41+
}
42+
43+
// EvaluateIngress evaluates ingress rules for the evaluator's specific tier.
44+
func (c *ClusterNetworkPolicy) EvaluateIngress(ctx context.Context, p *network.Packet, srcPod, dstPod *api.PodInfo) (Verdict, error) {
45+
logger := klog.FromContext(ctx)
46+
47+
policies, err := c.getPoliciesForPod(dstPod)
48+
if err != nil || len(policies) == 0 {
49+
return VerdictNext, err
50+
}
51+
52+
action := c.evaluateClusterIngress(policies, srcPod, dstPod, p.DstPort, p.Proto)
53+
logger.V(2).Info("Ingress CNP evaluation", "tier", c.tier, "npolicies", len(policies), "action", action)
54+
55+
return actionToVerdict(action), nil
56+
}
57+
58+
// EvaluateEgress evaluates egress rules for the evaluator's specific tier.
59+
func (c *ClusterNetworkPolicy) EvaluateEgress(ctx context.Context, p *network.Packet, srcPod, dstPod *api.PodInfo) (Verdict, error) {
60+
logger := klog.FromContext(ctx)
61+
62+
policies, err := c.getPoliciesForPod(srcPod)
63+
if err != nil || len(policies) == 0 {
64+
return VerdictNext, err
65+
}
66+
67+
action := c.evaluateClusterEgress(policies, dstPod, p.DstIP, p.DstPort, p.Proto)
68+
logger.V(2).Info("Egress CNP evaluation", "tier", c.tier, "npolicies", len(policies), "action", action)
69+
70+
return actionToVerdict(action), nil
71+
}
72+
73+
// getPoliciesForPod filters policies from the lister that match the pod and the evaluator's tier.
74+
func (c *ClusterNetworkPolicy) getPoliciesForPod(pod *api.PodInfo) ([]*npav1alpha2.ClusterNetworkPolicy, error) {
75+
if pod == nil {
76+
return nil, nil
77+
}
78+
79+
allPolicies, err := c.cnpLister.List(labels.Everything())
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
var result []*npav1alpha2.ClusterNetworkPolicy
85+
for _, policy := range allPolicies {
86+
// Filter by the tier this evaluator instance is responsible for.
87+
if policy.Spec.Tier != c.tier {
88+
continue
89+
}
90+
91+
subject := policy.Spec.Subject
92+
matches := false
93+
if subject.Namespaces != nil && matchesSelector(subject.Namespaces, pod.Namespace.Labels) {
94+
matches = true
95+
}
96+
if !matches && subject.Pods != nil &&
97+
matchesSelector(&subject.Pods.NamespaceSelector, pod.Namespace.Labels) &&
98+
matchesSelector(&subject.Pods.PodSelector, pod.Labels) {
99+
matches = true
100+
}
101+
102+
if matches {
103+
result = append(result, policy)
104+
}
105+
}
106+
107+
// Sort by priority and then by name for deterministic evaluation.
108+
slices.SortFunc(result, func(a, b *npav1alpha2.ClusterNetworkPolicy) int {
109+
if n := cmp.Compare(a.Spec.Priority, b.Spec.Priority); n != 0 {
110+
return n
111+
}
112+
return cmp.Compare(a.Name, b.Name)
113+
})
114+
return result, nil
115+
}
116+
117+
// evaluateClusterEgress evaluates a list of egress policies for a traffic flow.
118+
func (c *ClusterNetworkPolicy) evaluateClusterEgress(
119+
policies []*npav1alpha2.ClusterNetworkPolicy,
120+
dstPod *api.PodInfo,
121+
dstIP net.IP,
122+
dstPort int,
123+
protocol v1.Protocol,
124+
) npav1alpha2.ClusterNetworkPolicyRuleAction {
125+
for _, policy := range policies {
126+
for _, rule := range policy.Spec.Egress {
127+
if rule.Ports != nil {
128+
if !evaluateClusterNetworkPolicyPort(*rule.Ports, dstPod, dstPort, protocol) {
129+
continue
130+
}
131+
}
132+
for _, to := range rule.To {
133+
if to.Namespaces != nil && dstPod != nil && matchesSelector(to.Namespaces, dstPod.Namespace.Labels) {
134+
return rule.Action
135+
}
136+
137+
if to.Pods != nil && dstPod != nil &&
138+
matchesSelector(&to.Pods.NamespaceSelector, dstPod.Namespace.Labels) &&
139+
matchesSelector(&to.Pods.PodSelector, dstPod.Labels) {
140+
return rule.Action
141+
}
142+
143+
if to.Nodes != nil && dstPod != nil && matchesSelector(to.Nodes, dstPod.Node.Labels) {
144+
return rule.Action
145+
}
146+
147+
for _, network := range to.Networks {
148+
_, cidr, err := net.ParseCIDR(string(network))
149+
if err != nil {
150+
continue
151+
}
152+
if cidr.Contains(dstIP) {
153+
return rule.Action
154+
}
155+
}
156+
for _, domain := range to.DomainNames {
157+
if c.domainResolver != nil && c.domainResolver.ContainsIP(string(domain), dstIP) {
158+
return rule.Action
159+
}
160+
}
161+
}
162+
}
163+
}
164+
// Per CNP spec, if no rule matches, the default action is 'Pass'.
165+
return npav1alpha2.ClusterNetworkPolicyRuleActionPass
166+
}
167+
168+
// evaluateClusterIngress evaluates a list of ingress policies for a traffic flow.
169+
func (c *ClusterNetworkPolicy) evaluateClusterIngress(
170+
policies []*npav1alpha2.ClusterNetworkPolicy,
171+
srcPod, dstPod *api.PodInfo,
172+
dstPort int,
173+
protocol v1.Protocol,
174+
) npav1alpha2.ClusterNetworkPolicyRuleAction {
175+
if srcPod == nil {
176+
return npav1alpha2.ClusterNetworkPolicyRuleActionPass
177+
}
178+
for _, policy := range policies {
179+
for _, rule := range policy.Spec.Ingress {
180+
if rule.Ports != nil {
181+
if !evaluateClusterNetworkPolicyPort(*rule.Ports, dstPod, dstPort, protocol) {
182+
continue
183+
}
184+
}
185+
for _, from := range rule.From {
186+
if from.Namespaces != nil && matchesSelector(from.Namespaces, srcPod.Namespace.Labels) {
187+
return rule.Action
188+
}
189+
190+
if from.Pods != nil &&
191+
matchesSelector(&from.Pods.NamespaceSelector, srcPod.Namespace.Labels) &&
192+
matchesSelector(&from.Pods.PodSelector, srcPod.Labels) {
193+
return rule.Action
194+
}
195+
}
196+
}
197+
}
198+
return npav1alpha2.ClusterNetworkPolicyRuleActionPass
199+
}
200+
201+
// evaluateClusterNetworkPolicyPort checks if a specific port and protocol match any port selectors.
202+
func evaluateClusterNetworkPolicyPort(
203+
policyPorts []npav1alpha2.ClusterNetworkPolicyPort,
204+
pod *api.PodInfo,
205+
port int,
206+
protocol v1.Protocol,
207+
) bool {
208+
if len(policyPorts) == 0 {
209+
return true
210+
}
211+
212+
for _, policyPort := range policyPorts {
213+
if policyPort.PortNumber != nil &&
214+
policyPort.PortNumber.Port == int32(port) &&
215+
policyPort.PortNumber.Protocol == protocol {
216+
return true
217+
}
218+
219+
if policyPort.NamedPort != nil {
220+
if pod == nil {
221+
continue
222+
}
223+
for _, containerPort := range pod.ContainerPorts {
224+
if containerPort.Name == *policyPort.NamedPort && v1.Protocol(containerPort.Protocol) == protocol && containerPort.Port == int32(port) {
225+
return true
226+
}
227+
}
228+
}
229+
230+
if policyPort.PortRange != nil &&
231+
policyPort.PortRange.Protocol == protocol &&
232+
policyPort.PortRange.Start <= int32(port) &&
233+
policyPort.PortRange.End >= int32(port) {
234+
return true
235+
}
236+
}
237+
return false
238+
}
239+
240+
// actionToVerdict translates a CNP action into an internal Verdict.
241+
func actionToVerdict(action npav1alpha2.ClusterNetworkPolicyRuleAction) Verdict {
242+
switch action {
243+
case npav1alpha2.ClusterNetworkPolicyRuleActionAllow:
244+
return VerdictAccept
245+
case npav1alpha2.ClusterNetworkPolicyRuleActionDeny:
246+
return VerdictDeny
247+
case npav1alpha2.ClusterNetworkPolicyRuleActionPass:
248+
return VerdictNext
249+
default:
250+
// Default to a "pass" behavior is safest if the action is unknown or empty.
251+
return VerdictNext
252+
}
253+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// SPDX-License-Identifier: APACHE-2.0
2+
3+
package networkpolicy

0 commit comments

Comments
 (0)