Skip to content

Commit aceb991

Browse files
committed
ClusterNetworkPolicy
1 parent f3ab703 commit aceb991

File tree

3 files changed

+291
-5
lines changed

3 files changed

+291
-5
lines changed

cmd/main.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ 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+
npav1alpha2 "sigs.k8s.io/network-policy-api/apis/v1alpha2"
1920
npaclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned"
2021
npainformers "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions"
21-
"sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha1"
22+
npainformersv1alpha1 "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha1"
23+
npainformersv1alpha2 "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha2"
2224

2325
"k8s.io/apimachinery/pkg/api/meta"
2426
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -38,6 +40,7 @@ var (
3840
failOpen bool
3941
adminNetworkPolicy bool // AdminNetworkPolicy is alpha so keep it feature gated behind a flag
4042
baselineAdminNetworkPolicy bool // BaselineAdminNetworkPolicy is alpha so keep it feature gated behind a flag
43+
clusterNetworkPolicy bool // ClusterNetworkPolicy is alpha so keep it feature gated behind a flag
4144
queueID int
4245
metricsBindAddress string
4346
hostnameOverride string
@@ -49,6 +52,7 @@ func init() {
4952
flag.BoolVar(&failOpen, "fail-open", false, "If set, don't drop packets if the controller is not running")
5053
flag.BoolVar(&adminNetworkPolicy, "admin-network-policy", false, "If set, enable Admin Network Policy API")
5154
flag.BoolVar(&baselineAdminNetworkPolicy, "baseline-admin-network-policy", false, "If set, enable Baseline Admin Network Policy API")
55+
flag.BoolVar(&clusterNetworkPolicy, "cluster-network-policy", false, "If set, enable Cluster-network-policy")
5256
flag.IntVar(&queueID, "nfqueue-id", 100, "Number of the nfqueue used")
5357
flag.StringVar(&metricsBindAddress, "metrics-bind-address", ":9080", "The IP address and port for the metrics server to serve on")
5458
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 +135,8 @@ func run() int {
131135
var npaClient *npaclient.Clientset
132136
var npaInformerFactory npainformers.SharedInformerFactory
133137
var nodeInformer coreinformers.NodeInformer
134-
if adminNetworkPolicy || baselineAdminNetworkPolicy {
138+
139+
if adminNetworkPolicy || baselineAdminNetworkPolicy || clusterNetworkPolicy {
135140
nodeInformer = informersFactory.Core().V1().Nodes()
136141
npaClient, err = npaclient.NewForConfig(npaConfig)
137142
if err != nil {
@@ -140,14 +145,18 @@ func run() int {
140145
npaInformerFactory = npainformers.NewSharedInformerFactory(npaClient, 0)
141146
}
142147

143-
var anpInformer v1alpha1.AdminNetworkPolicyInformer
148+
var anpInformer npainformersv1alpha1.AdminNetworkPolicyInformer
144149
if adminNetworkPolicy {
145150
anpInformer = npaInformerFactory.Policy().V1alpha1().AdminNetworkPolicies()
146151
}
147-
var banpInformer v1alpha1.BaselineAdminNetworkPolicyInformer
152+
var banpInformer npainformersv1alpha1.BaselineAdminNetworkPolicyInformer
148153
if baselineAdminNetworkPolicy {
149154
banpInformer = npaInformerFactory.Policy().V1alpha1().BaselineAdminNetworkPolicies()
150155
}
156+
var cnpInformer npainformersv1alpha2.ClusterNetworkPolicyInformer
157+
if clusterNetworkPolicy {
158+
cnpInformer = npaInformerFactory.Policy().V1alpha2().ClusterNetworkPolicies()
159+
}
151160

152161
nsInformer := informersFactory.Core().V1().Namespaces()
153162
networkPolicyInfomer := informersFactory.Networking().V1().NetworkPolicies()
@@ -203,7 +212,10 @@ func run() int {
203212
evaluators = append(evaluators, networkpolicy.NewLoggingPolicy())
204213
}
205214

206-
if adminNetworkPolicy {
215+
var domainResolver networkpolicy.DomainResolver
216+
// If AdminNetworkPolicy or ClusterNetworkPolicy are enabled, we need a domain resolver.
217+
if adminNetworkPolicy || clusterNetworkPolicy {
218+
klog.Infof("AdminNetworkPolicy or ClusterNetworkPolicy enabled, starting domain cache")
207219
// Admin Network Policy need to associate IP addresses to Domains
208220
// NewDomainCache implements the interface DomainResolver using
209221
// nftables to create a cache with the resolved IP addresses from the
@@ -215,13 +227,23 @@ func run() int {
215227
klog.Infof("domain cache controller exited: %v", err)
216228
}
217229
}()
230+
}
218231

232+
if adminNetworkPolicy {
219233
evaluators = append(evaluators, networkpolicy.NewAdminNetworkPolicy(
220234
anpInformer,
221235
domainResolver,
222236
))
223237
}
224238

239+
if clusterNetworkPolicy {
240+
evaluators = append(evaluators, networkpolicy.NewClusterNetworkPolicy(
241+
npav1alpha2.AdminTier,
242+
cnpInformer,
243+
domainResolver,
244+
))
245+
}
246+
225247
// Standard Network Policy goes after AdminNetworkPolicy and before BaselineAdminNetworkPolicy
226248
evaluators = append(evaluators, networkpolicy.NewStandardNetworkPolicy(
227249
networkPolicyInfomer,
@@ -233,6 +255,14 @@ func run() int {
233255
))
234256
}
235257

258+
if clusterNetworkPolicy {
259+
evaluators = append(evaluators, networkpolicy.NewClusterNetworkPolicy(
260+
npav1alpha2.BaselineTier,
261+
cnpInformer,
262+
nil,
263+
))
264+
}
265+
236266
http.Handle("/metrics", promhttp.Handler())
237267
go func() {
238268
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)