From 0aa1865708194995a8310bdd758d8c8b535dd31d Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Sun, 21 Apr 2024 13:18:40 +0000 Subject: [PATCH 01/11] add admin network policy --- cmd/main.go | 31 +- go.mod | 1 + go.sum | 2 + pkg/networkpolicy/adminnetworkpolicy.go | 239 ++++++ pkg/networkpolicy/adminnetworkpolicy_test.go | 739 +++++++++++++++++++ pkg/networkpolicy/controller.go | 105 ++- pkg/networkpolicy/controller_test.go | 119 +++ pkg/networkpolicy/networkpolicy.go | 33 - pkg/networkpolicy/networkpolicy_test.go | 84 +-- pkg/networkpolicy/packet.go | 3 +- 10 files changed, 1230 insertions(+), 126 deletions(-) create mode 100644 pkg/networkpolicy/adminnetworkpolicy.go create mode 100644 pkg/networkpolicy/adminnetworkpolicy_test.go create mode 100644 pkg/networkpolicy/controller_test.go diff --git a/cmd/main.go b/cmd/main.go index 55db485..0b90c3a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -13,9 +13,13 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "sigs.k8s.io/knftables" "sigs.k8s.io/kube-network-policies/pkg/networkpolicy" + npaclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned" + npainformers "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions" + "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/informers" + v1 "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/klog/v2" @@ -25,12 +29,14 @@ import ( var ( failOpen bool + adminNetworkPolicy bool // AdminNetworkPolicy is alpha so keep it feature gated behind a flag queueID int metricsBindAddress string ) func init() { flag.BoolVar(&failOpen, "fail-open", false, "If set, don't drop packets if the controller is not running") + flag.BoolVar(&adminNetworkPolicy, "admin-network-policy", false, "If set, enable Admin Network Policy API") flag.IntVar(&queueID, "nfqueue-id", 100, "Number of the nfqueue used") flag.StringVar(&metricsBindAddress, "metrics-bind-address", ":9080", "The IP address and port for the metrics server to serve on") @@ -55,8 +61,9 @@ func main() { } cfg := networkpolicy.Config{ - FailOpen: failOpen, - QueueID: queueID, + AdminNetworkPolicy: adminNetworkPolicy, + FailOpen: failOpen, + QueueID: queueID, } // creates the in-cluster config config, err := rest.InClusterConfig() @@ -83,6 +90,20 @@ func main() { informersFactory := informers.NewSharedInformerFactory(clientset, 0) + var npaClient *npaclient.Clientset + var npaInformerFactory npainformers.SharedInformerFactory + var npaInformer v1alpha1.AdminNetworkPolicyInformer + var nodeInformer v1.NodeInformer + if adminNetworkPolicy { + nodeInformer = informersFactory.Core().V1().Nodes() + npaClient, err = npaclient.NewForConfig(config) + if err != nil { + klog.Fatalf("Failed to create Network client: %v", err) + } + npaInformerFactory = npainformers.NewSharedInformerFactory(npaClient, 0) + npaInformer = npaInformerFactory.Policy().V1alpha1().AdminNetworkPolicies() + } + http.Handle("/metrics", promhttp.Handler()) go func() { err := http.ListenAndServe(metricsBindAddress, nil) @@ -95,6 +116,9 @@ func main() { informersFactory.Networking().V1().NetworkPolicies(), informersFactory.Core().V1().Namespaces(), informersFactory.Core().V1().Pods(), + nodeInformer, + npaClient, + npaInformer, cfg, ) go func() { @@ -103,6 +127,9 @@ func main() { }() informersFactory.Start(ctx.Done()) + if adminNetworkPolicy { + npaInformerFactory.Start(ctx.Done()) + } select { case <-signalCh: diff --git a/go.mod b/go.mod index d704179..db0f1be 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( k8s.io/klog/v2 v2.120.1 k8s.io/utils v0.0.0-20240310230437-4693a0247e57 sigs.k8s.io/knftables v0.0.16 + sigs.k8s.io/network-policy-api v0.1.5 ) require ( diff --git a/go.sum b/go.sum index ad1b0d9..a9661b2 100644 --- a/go.sum +++ b/go.sum @@ -195,6 +195,8 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/knftables v0.0.16 h1:ZpTfNsjnidgoXdxxzcZLdSctqkpSO3QB3jo3zQ4PXqM= sigs.k8s.io/knftables v0.0.16/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= +sigs.k8s.io/network-policy-api v0.1.5 h1:xyS7VAaM9EfyB428oFk7WjWaCK6B129i+ILUF4C8l6E= +sigs.k8s.io/network-policy-api v0.1.5/go.mod h1:D7Nkr43VLNd7iYryemnj8qf0N/WjBzTZDxYA+g4u1/Y= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/pkg/networkpolicy/adminnetworkpolicy.go b/pkg/networkpolicy/adminnetworkpolicy.go new file mode 100644 index 0000000..ff7a6b5 --- /dev/null +++ b/pkg/networkpolicy/adminnetworkpolicy.go @@ -0,0 +1,239 @@ +package networkpolicy + +import ( + "cmp" + "net" + "slices" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/klog/v2" + npav1alpha1 "sigs.k8s.io/network-policy-api/apis/v1alpha1" +) + +func (c *Controller) evaluateAdminEgress(adminNetworkPolices []*npav1alpha1.AdminNetworkPolicy, pod *v1.Pod, ip net.IP, port int, protocol v1.Protocol) npav1alpha1.AdminNetworkPolicyRuleAction { + for _, policy := range adminNetworkPolices { + for _, rule := range policy.Spec.Egress { + // Ports allows for matching traffic based on port and protocols. + // This field is a list of destination ports for the outgoing egress traffic. + // If Ports is not set then the rule does not filter traffic via port. + if rule.Ports != nil { + if !evaluateAdminNetworkPolicyPort(*rule.Ports, pod, port, protocol) { + continue + } + } + // To is the List of destinations whose traffic this rule applies to. + // If any AdminNetworkPolicyEgressPeer matches the destination of outgoing + // traffic then the specified action is applied. + // This field must be defined and contain at least one item. + for _, to := range rule.To { + // Exactly one of the selector pointers must be set for a given peer. If a + // consumer observes none of its fields are set, they must assume an unknown + // option has been specified and fail closed. + if to.Namespaces != nil && pod != nil { + if c.namespaceSelector(to.Namespaces, pod) { + return rule.Action + } + } + + if to.Pods != nil && pod != nil { + if c.namespaceSelector(&to.Pods.NamespaceSelector, pod) && + podSelector(&to.Pods.PodSelector, pod) { + return rule.Action + } + } + + if to.Nodes != nil && pod != nil { + if c.nodeSelector(to.Nodes, pod) { + return rule.Action + } + } + + for _, network := range to.Networks { + _, cidr, err := net.ParseCIDR(string(network)) + if err != nil { // this has been validated by the API + continue + } + if cidr.Contains(ip) { + return rule.Action + } + } + } + } + } + + return npav1alpha1.AdminNetworkPolicyRuleActionPass +} + +func (c *Controller) evaluateAdminIngress(adminNetworkPolices []*npav1alpha1.AdminNetworkPolicy, pod *v1.Pod, port int, protocol v1.Protocol) npav1alpha1.AdminNetworkPolicyRuleAction { + // Ingress rules only apply to pods + if pod == nil { + return npav1alpha1.AdminNetworkPolicyRuleActionPass + } + for _, policy := range adminNetworkPolices { + // Ingress is the list of Ingress rules to be applied to the selected pods. A total of 100 rules will be allowed in each ANP instance. The relative precedence of ingress rules within a single ANP object (all of which share the priority) will be determined by the order in which the rule is written. Thus, a rule that appears at the top of the ingress rules would take the highest precedence. + // ANPs with no ingress rules do not affect ingress traffic. + for _, rule := range policy.Spec.Ingress { + // Ports allows for matching traffic based on port and protocols. + // This field is a list of ports which should be matched on the pods selected for this policy + // i.e the subject of the policy. So it matches on the destination port for the ingress traffic. + // If Ports is not set then the rule does not filter traffic via port. + if rule.Ports != nil { + if !evaluateAdminNetworkPolicyPort(*rule.Ports, pod, port, protocol) { + continue + } + } + // From is the list of sources whose traffic this rule applies to. + // If any AdminNetworkPolicyIngressPeer matches the source of incoming traffic then the specified action is applied. + // This field must be defined and contain at least one item. + for _, from := range rule.From { + // Exactly one of the selector pointers must be set for a given peer. If a + // consumer observes none of its fields are set, they must assume an unknown + // option has been specified and fail closed. + if from.Namespaces != nil { + if c.namespaceSelector(from.Namespaces, pod) { + return rule.Action + } + } + + if from.Pods != nil { + if c.namespaceSelector(&from.Pods.NamespaceSelector, pod) && + podSelector(&from.Pods.PodSelector, pod) { + return rule.Action + } + } + } + + } + } + + return npav1alpha1.AdminNetworkPolicyRuleActionPass +} + +// namespaceSelector return true if the namespace selector matches the pod +func (c *Controller) namespaceSelector(selector *metav1.LabelSelector, pod *v1.Pod) bool { + nsSelector, err := metav1.LabelSelectorAsSelector(selector) + if err != nil { + return false + } + + namespaces, err := c.namespaceLister.List(nsSelector) + if err != nil { + return false + } + + for _, ns := range namespaces { + if pod.Namespace == ns.Name { + return true + } + } + return false +} + +// podSelector return true if the pod selector matches the pod +func podSelector(selector *metav1.LabelSelector, pod *v1.Pod) bool { + podSelector, err := metav1.LabelSelectorAsSelector(selector) + if err != nil { + return false + } + return podSelector.Matches(labels.Set(pod.Labels)) +} + +// nodeSelector return true if the node selector matches the pod +func (c *Controller) nodeSelector(selector *metav1.LabelSelector, pod *v1.Pod) bool { + nodeSelector, err := metav1.LabelSelectorAsSelector(selector) + if err != nil { + return false + } + nodes, err := c.nodeLister.List(nodeSelector) + if err != nil { + return false + } + for _, node := range nodes { + if pod.Spec.NodeName == node.Name { + return true + } + } + return false +} + +// getAdminNetworkPoliciesForPod returns the list of Admin Network Policies matching the Pod +// The list is ordered by priority, from higher to lower. +func (c *Controller) getAdminNetworkPoliciesForPod(pod *v1.Pod) []*npav1alpha1.AdminNetworkPolicy { + if pod == nil { + return nil + } + // Get all the network policies that affect this pod + networkPolices, err := c.adminNetworkPolicyLister.List(labels.Everything()) + if err != nil { + klog.Infof("getAdminNetworkPoliciesForPod error: %v", err) + return nil + } + + result := []*npav1alpha1.AdminNetworkPolicy{} + for _, policy := range networkPolices { + if policy.Spec.Subject.Namespaces != nil && + c.namespaceSelector(policy.Spec.Subject.Namespaces, pod) { + klog.V(2).Infof("Pod %s/%s match AdminNetworkPolicy %s", pod.Name, pod.Namespace, policy.Name) + result = append(result, policy) + } + + if policy.Spec.Subject.Pods != nil && + c.namespaceSelector(&policy.Spec.Subject.Pods.NamespaceSelector, pod) && + podSelector(&policy.Spec.Subject.Pods.PodSelector, pod) { + klog.V(2).Infof("Pod %s/%s match AdminNetworkPolicy %s", pod.Name, pod.Namespace, policy.Name) + result = append(result, policy) + } + } + // Rules with lower priority values have higher precedence + slices.SortFunc(result, func(a, b *npav1alpha1.AdminNetworkPolicy) int { + if n := cmp.Compare(a.Spec.Priority, b.Spec.Priority); n != 0 { + return n + } + // If priorities are equal, order by name + return cmp.Compare(a.Name, b.Name) + }) + return result +} + +func evaluateAdminNetworkPolicyPort(networkPolicyPorts []npav1alpha1.AdminNetworkPolicyPort, pod *v1.Pod, port int, protocol v1.Protocol) bool { + // AdminNetworkPolicyPort describes how to select network ports on pod(s). + // Exactly one field must be set. + if len(networkPolicyPorts) == 0 { + return true + } + + for _, policyPort := range networkPolicyPorts { + // Port number + if policyPort.PortNumber != nil && + policyPort.PortNumber.Port == int32(port) && + policyPort.PortNumber.Protocol == protocol { + return true + } + + // Named Port + if policyPort.NamedPort != nil { + if pod == nil { + continue + } + for _, container := range pod.Spec.Containers { + for _, p := range container.Ports { + if p.Name == *policyPort.NamedPort { + return true + } + } + } + } + + // Port range + if policyPort.PortRange != nil && + policyPort.PortRange.Protocol == protocol && + policyPort.PortRange.Start <= int32(port) && + policyPort.PortRange.End >= int32(port) { + return true + } + + } + return false +} diff --git a/pkg/networkpolicy/adminnetworkpolicy_test.go b/pkg/networkpolicy/adminnetworkpolicy_test.go new file mode 100644 index 0000000..02253c2 --- /dev/null +++ b/pkg/networkpolicy/adminnetworkpolicy_test.go @@ -0,0 +1,739 @@ +package networkpolicy + +import ( + "net" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/component-base/logs" + "k8s.io/klog/v2" + "k8s.io/utils/ptr" + npav1alpha1 "sigs.k8s.io/network-policy-api/apis/v1alpha1" +) + +type adminNetpolTweak func(networkPolicy *npav1alpha1.AdminNetworkPolicy) + +func makeAdminNetworkPolicyCustom(name, ns string, tweaks ...adminNetpolTweak) *npav1alpha1.AdminNetworkPolicy { + networkAdminPolicy := &npav1alpha1.AdminNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns}, + Spec: npav1alpha1.AdminNetworkPolicySpec{}, + } + for _, fn := range tweaks { + fn(networkAdminPolicy) + } + return networkAdminPolicy +} + +func Test_adminNetworkPolicyAction(t *testing.T) { + _, err := logs.GlogSetter("4") + if err != nil { + t.Fatal(err) + } + state := klog.CaptureState() + t.Cleanup(state.Restore) + + podA := makePod("a", "foo", "192.168.1.11") + podB := makePod("b", "bar", "192.168.2.22") + // podC will not match neither selectors or namespaces + podC := makePod("c", "blocked", "192.168.3.33") + podC.Labels = map[string]string{"c": "d"} + // podD is same namespace PodB with different selectors + podD := makePod("d", "bar", "192.168.4.44") + podD.Labels = map[string]string{"c": "d"} + + npaDefaultDenyIngress := makeAdminNetworkPolicyCustom("default-deny-ingress", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 110 + networkPolicy.Spec.Ingress = []npav1alpha1.AdminNetworkPolicyIngressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionDeny, + From: []npav1alpha1.AdminNetworkPolicyIngressPeer{{Namespaces: &metav1.LabelSelector{}}}, + }} + }) + + npaDefaultDenyEgress := makeAdminNetworkPolicyCustom("default-deny-egress", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 110 + networkPolicy.Spec.Egress = []npav1alpha1.AdminNetworkPolicyEgressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionDeny, + To: []npav1alpha1.AdminNetworkPolicyEgressPeer{{Namespaces: &metav1.LabelSelector{}}}, + }} + }) + + npaAllowAllIngress := makeAdminNetworkPolicyCustom("default-allow-ingress", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 100 + networkPolicy.Spec.Ingress = []npav1alpha1.AdminNetworkPolicyIngressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionAllow, + From: []npav1alpha1.AdminNetworkPolicyIngressPeer{{Namespaces: &metav1.LabelSelector{}}}, + }} + }) + + npaAllowAllIngressPod := makeAdminNetworkPolicyCustom("default-allow-ingress-pods", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 110 + networkPolicy.Spec.Ingress = []npav1alpha1.AdminNetworkPolicyIngressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionAllow, + From: []npav1alpha1.AdminNetworkPolicyIngressPeer{{ + Pods: &npav1alpha1.NamespacedPod{ + PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + }}, + }} + }) + + npaAllowMultiPortEgress := makeAdminNetworkPolicyCustom("multiport-egress", "foo", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 10 + networkPolicy.Spec.Egress = []npav1alpha1.AdminNetworkPolicyEgressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []npav1alpha1.AdminNetworkPolicyEgressPeer{{Namespaces: &metav1.LabelSelector{}}}, + Ports: &[]npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }}}, + }} + }) + + npaAllowMultiPortEgressNode := makeAdminNetworkPolicyCustom("multiport-egress", "foo", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 10 + networkPolicy.Spec.Egress = []npav1alpha1.AdminNetworkPolicyEgressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []npav1alpha1.AdminNetworkPolicyEgressPeer{{Nodes: &metav1.LabelSelector{}}}, + Ports: &[]npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }}}, + }} + }) + + npaAllowMultiPortEgressCIDR := makeAdminNetworkPolicyCustom("multiport-egress", "foo", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 10 + networkPolicy.Spec.Egress = []npav1alpha1.AdminNetworkPolicyEgressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []npav1alpha1.AdminNetworkPolicyEgressPeer{{ + Networks: []npav1alpha1.CIDR{"192.168.0.0/16"}, + }}, + Ports: &[]npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }}}, + }} + }) + + npaAllowMultiPortEgressPodSelector := makeAdminNetworkPolicyCustom("multiport-egress", "foo", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 100 + networkPolicy.Spec.Egress = []npav1alpha1.AdminNetworkPolicyEgressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []npav1alpha1.AdminNetworkPolicyEgressPeer{{ + Pods: &npav1alpha1.NamespacedPod{ + PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + }}, + Ports: &[]npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }}}, + }} + }) + + npaMultiPortEgressNsSelector := makeAdminNetworkPolicyCustom("multiport-egress-ns", "foo", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 10 + networkPolicy.Spec.Egress = []npav1alpha1.AdminNetworkPolicyEgressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []npav1alpha1.AdminNetworkPolicyEgressPeer{{ + Pods: &npav1alpha1.NamespacedPod{ + NamespaceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + }}, + Ports: &[]npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }}}, + }} + }) + + npaMultiPortEgressPodNsSelector := makeAdminNetworkPolicyCustom("multiport-egress-pod-ns", "foo", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 10 + networkPolicy.Spec.Egress = []npav1alpha1.AdminNetworkPolicyEgressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionAllow, + To: []npav1alpha1.AdminNetworkPolicyEgressPeer{{ + Pods: &npav1alpha1.NamespacedPod{ + NamespaceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + }}, + Ports: &[]npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }}}, + }} + }) + + npaMultiPortIngressPodNsSelector := makeAdminNetworkPolicyCustom("multiport-ingress-pod-ns", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{Namespaces: &metav1.LabelSelector{}} + networkPolicy.Spec.Priority = 10 + networkPolicy.Spec.Ingress = []npav1alpha1.AdminNetworkPolicyIngressRule{{ + Action: npav1alpha1.AdminNetworkPolicyRuleActionAllow, + From: []npav1alpha1.AdminNetworkPolicyIngressPeer{{ + Pods: &npav1alpha1.NamespacedPod{ + NamespaceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + }}, + Ports: &[]npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }}}, + }} + }) + + tests := []struct { + name string + networkpolicy []*npav1alpha1.AdminNetworkPolicy + namespace []*v1.Namespace + pod []*v1.Pod + node []*v1.Node + p packet + expect bool + }{ + { + name: "no network policy", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "deny ingress", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyIngress}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: false, + }, + { + name: "deny egress", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.2.22"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.1.11"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: false, + }, + { + name: "allow all override deny ingress if it has higher priority", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyIngress, npaAllowAllIngress}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "allow ingress", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaAllowAllIngressPod}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("10.0.0.1"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport allow egress port", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress, npaAllowMultiPortEgress}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport allow egress node port", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress, npaAllowMultiPortEgressNode}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport deny egress port", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress, npaAllowMultiPortEgress}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 30080, + proto: v1.ProtocolTCP, + }, + expect: false, + }, + { + name: "multiport allow egress", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress, npaAllowMultiPortEgressCIDR}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport allow egress port selector not match ns", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress, npaAllowMultiPortEgressPodSelector}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport allow egress ns selector", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress, npaMultiPortEgressNsSelector}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport allow egress ns selector fail", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress, npaMultiPortEgressNsSelector}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.3.33"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport allow egress ns and pod selector", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress, npaMultiPortEgressPodNsSelector}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport allow egress ns and pod selector fail", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyEgress, npaMultiPortEgressPodNsSelector}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.3.33"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport allow ingress ns and pod selector", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyIngress, npaMultiPortIngressPodNsSelector}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.2.22"), + dstPort: 80, + proto: v1.ProtocolTCP, + }, + expect: true, + }, + { + name: "multiport allow ingress ns and pod selector fail", + networkpolicy: []*npav1alpha1.AdminNetworkPolicy{npaDefaultDenyIngress, npaMultiPortIngressPodNsSelector}, + namespace: []*v1.Namespace{makeNamespace("foo"), makeNamespace("bar")}, + pod: []*v1.Pod{podA, podB, podC, podD}, + p: packet{ + srcIP: net.ParseIP("192.168.1.11"), + srcPort: 52345, + dstIP: net.ParseIP("192.168.4.44"), + dstPort: 9080, + proto: v1.ProtocolTCP, + }, + expect: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + controller := newController() + // Add objects to the Store + for _, n := range tt.networkpolicy { + err := controller.adminNetworkpolicyStore.Add(n) + if err != nil { + t.Fatal(err) + } + } + for _, n := range tt.namespace { + err := controller.namespaceStore.Add(n) + if err != nil { + t.Fatal(err) + } + } + for _, p := range tt.pod { + err := controller.podStore.Add(p) + if err != nil { + t.Fatal(err) + } + } + err := controller.nodeStore.Add(makeNode("testnode")) + if err != nil { + t.Fatal(err) + } + + ok := controller.evaluatePacket(tt.p) + if ok != tt.expect { + t.Errorf("expected %v got %v", tt.expect, ok) + } + + }) + } +} + +func Test_evaluateAdminNetworkPolicyPort(t *testing.T) { + tests := []struct { + name string + networkPolicyPorts []npav1alpha1.AdminNetworkPolicyPort + pod *v1.Pod + port int + protocol v1.Protocol + want bool + }{ + { + name: "empty", + pod: makePod("test", "nstest", "192.168.1.1"), + want: true, + }, + { + name: "match port", + networkPolicyPorts: []npav1alpha1.AdminNetworkPolicyPort{{ + PortNumber: &npav1alpha1.Port{ + Protocol: v1.ProtocolTCP, + Port: 80, + }, + }}, + pod: makePod("test", "nstest", "192.168.1.1"), + port: 80, + protocol: v1.ProtocolTCP, + want: true, + }, + { + name: "wrong port protocol", + networkPolicyPorts: []npav1alpha1.AdminNetworkPolicyPort{{ + PortNumber: &npav1alpha1.Port{ + Protocol: v1.ProtocolTCP, + Port: 80, + }, + }}, + pod: makePod("test", "nstest", "192.168.1.1"), + port: 80, + protocol: v1.ProtocolUDP, + want: false, + }, + { + name: "wrong port number", + networkPolicyPorts: []npav1alpha1.AdminNetworkPolicyPort{{ + PortNumber: &npav1alpha1.Port{ + Protocol: v1.ProtocolTCP, + Port: 80, + }, + }}, + pod: makePod("test", "nstest", "192.168.1.1"), + port: 443, + protocol: v1.ProtocolTCP, + want: false, + }, + { + name: "match port named", + networkPolicyPorts: []npav1alpha1.AdminNetworkPolicyPort{{ + NamedPort: ptr.To[string]("http"), + }}, + pod: makePod("test", "nstest", "192.168.1.1"), + port: 80, + protocol: v1.ProtocolTCP, + want: true, + }, + { + name: "match port range", + networkPolicyPorts: []npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }, + }}, + pod: makePod("test", "nstest", "192.168.1.1"), + port: 80, + protocol: v1.ProtocolTCP, + want: true, + }, + { + name: "out port range", + networkPolicyPorts: []npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }, + }}, + pod: makePod("test", "nstest", "192.168.1.1"), + port: 180, + protocol: v1.ProtocolTCP, + want: false, + }, + { + name: "inside port range but wrong protocol", + networkPolicyPorts: []npav1alpha1.AdminNetworkPolicyPort{{ + PortRange: &npav1alpha1.PortRange{ + Protocol: v1.ProtocolTCP, + Start: 80, + End: 120, + }, + }}, + pod: makePod("test", "nstest", "192.168.1.1"), + port: 90, + protocol: v1.ProtocolUDP, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := evaluateAdminNetworkPolicyPort(tt.networkPolicyPorts, tt.pod, tt.port, tt.protocol); got != tt.want { + t.Errorf("evaluateAdminNetworkPolicyPort() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestController_getAdminNetworkPoliciesForPod(t *testing.T) { + _, err := logs.GlogSetter("4") + if err != nil { + t.Fatal(err) + } + tests := []struct { + name string + networkpolicy *npav1alpha1.AdminNetworkPolicy + want bool + }{ + { + name: "empty", + networkpolicy: &npav1alpha1.AdminNetworkPolicy{}, + want: false, + }, + { + name: "empty namespace selector matches all", + networkpolicy: makeAdminNetworkPolicyCustom("anp", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{ + Namespaces: &metav1.LabelSelector{}, + } + }, + ), + want: true, + }, + { + name: "match namespace", + networkpolicy: makeAdminNetworkPolicyCustom("anp", "foo", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{ + Namespaces: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + } + }, + ), + want: true, + }, + { + name: "do not match namespace", + networkpolicy: makeAdminNetworkPolicyCustom("anp", "foo", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{ + Namespaces: &metav1.LabelSelector{MatchLabels: map[string]string{"c": "do not match"}}, + } + }, + ), + want: false, + }, + { + name: "empty selectors matches all", + networkpolicy: makeAdminNetworkPolicyCustom("anp", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{ + Pods: &npav1alpha1.NamespacedPod{}, + } + }, + ), + want: true, + }, + { + name: "match pod selectors", + networkpolicy: makeAdminNetworkPolicyCustom("anp", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{ + Pods: &npav1alpha1.NamespacedPod{ + PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + } + }, + ), + want: true, + }, + { + name: "match namespace selectors", + networkpolicy: makeAdminNetworkPolicyCustom("anp", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{ + Pods: &npav1alpha1.NamespacedPod{ + NamespaceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + } + }, + ), + want: true, + }, + { + name: "match namespace and selectors", + networkpolicy: makeAdminNetworkPolicyCustom("anp", "bar", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{ + Pods: &npav1alpha1.NamespacedPod{ + NamespaceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + } + }, + ), + want: true, + }, + { + name: "do not match namespace match pod", + networkpolicy: makeAdminNetworkPolicyCustom("anp", "foo", + func(networkPolicy *npav1alpha1.AdminNetworkPolicy) { + networkPolicy.Spec.Subject = npav1alpha1.AdminNetworkPolicySubject{ + Pods: &npav1alpha1.NamespacedPod{ + NamespaceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"c": "do not match"}}, + PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + } + }, + ), + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + controller := newController() + // Add objects to the Store + err := controller.adminNetworkpolicyStore.Add(tt.networkpolicy) + if err != nil { + t.Fatal(err) + } + err = controller.namespaceStore.Add(makeNamespace("foo")) + if err != nil { + t.Fatal(err) + } + + if got := controller.getAdminNetworkPoliciesForPod(makePod("a", "foo", "192.168.1.11")); len(got) > 0 != tt.want { + t.Errorf("Controller.getAdminNetworkPoliciesForPod() = %v, want %v", len(got) > 0, tt.want) + } + }) + } +} diff --git a/pkg/networkpolicy/controller.go b/pkg/networkpolicy/controller.go index 46fee1a..91707fa 100644 --- a/pkg/networkpolicy/controller.go +++ b/pkg/networkpolicy/controller.go @@ -9,6 +9,7 @@ import ( "github.com/mdlayher/netlink" v1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/meta" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -22,7 +23,12 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" + "sigs.k8s.io/knftables" + npav1alpha1 "sigs.k8s.io/network-policy-api/apis/v1alpha1" + npaclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned" + policyinformers "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions/apis/v1alpha1" + policylisters "sigs.k8s.io/network-policy-api/pkg/client/listers/apis/v1alpha1" ) // Network policies are hard to implement efficiently, and in large clusters this is @@ -47,8 +53,9 @@ const ( ) type Config struct { - FailOpen bool // allow traffic if the controller is not available - QueueID int + FailOpen bool // allow traffic if the controller is not available + AdminNetworkPolicy bool + QueueID int } // NewController returns a new *Controller. @@ -57,6 +64,9 @@ func NewController(client clientset.Interface, networkpolicyInformer networkinginformers.NetworkPolicyInformer, namespaceInformer coreinformers.NamespaceInformer, podInformer coreinformers.PodInformer, + nodeInformer coreinformers.NodeInformer, + npaClient npaclient.Interface, + adminNetworkPolicyInformer policyinformers.AdminNetworkPolicyInformer, config Config, ) *Controller { klog.V(2).Info("Creating event broadcaster") @@ -136,6 +146,13 @@ func NewController(client clientset.Interface, c.namespacesSynced = namespaceInformer.Informer().HasSynced c.networkpolicyLister = networkpolicyInformer.Lister() c.networkpoliciesSynced = networkpolicyInformer.Informer().HasSynced + if config.AdminNetworkPolicy { + c.npaClient = npaClient + c.nodeLister = nodeInformer.Lister() + c.nodesSynced = nodeInformer.Informer().HasSynced + c.adminNetworkPolicyLister = adminNetworkPolicyInformer.Lister() + c.adminNetworkPolicySynced = adminNetworkPolicyInformer.Informer().HasSynced + } c.eventBroadcaster = broadcaster c.eventRecorder = recorder @@ -159,6 +176,12 @@ type Controller struct { podLister corelisters.PodLister podsSynced cache.InformerSynced + npaClient npaclient.Interface + + adminNetworkPolicyLister policylisters.AdminNetworkPolicyLister + adminNetworkPolicySynced cache.InformerSynced + nodeLister corelisters.NodeLister + nodesSynced cache.InformerSynced // function to get the Pod given an IP // if an error or not found it returns nil getPodAssignedToIP func(podIP string) *v1.Pod @@ -178,7 +201,11 @@ func (c *Controller) Run(ctx context.Context) error { // Wait for the caches to be synced klog.Info("Waiting for informer caches to sync") - if !cache.WaitForNamedCacheSync(controllerName, ctx.Done(), c.networkpoliciesSynced, c.namespacesSynced, c.podsSynced) { + caches := []cache.InformerSynced{c.networkpoliciesSynced, c.namespacesSynced, c.podsSynced} + if c.config.AdminNetworkPolicy { + caches = append(caches, c.adminNetworkPolicySynced, c.nodesSynced) + } + if !cache.WaitForNamedCacheSync(controllerName, ctx.Done(), caches...) { return fmt.Errorf("error syncing cache") } @@ -243,7 +270,8 @@ func (c *Controller) Run(ctx context.Context) error { c.nfq = nf - // Parse the packet and check if should be accepted + // Parse the packet and check if it should be accepted + // Packets should be evaludated independently in each direction fn := func(a nfqueue.Attribute) int { verdict := nfqueue.NfDrop if c.config.FailOpen { @@ -255,10 +283,11 @@ func (c *Controller) Run(ctx context.Context) error { packet, err := parsePacket(*a.Payload) if err != nil { - klog.Infof("Can not process packet %d accepting it: %v", *a.PacketID, err) + klog.Infof("Can not process packet %d applying default policy (failOpen: %v): %v", *a.PacketID, c.config.FailOpen, err) c.nfq.SetVerdict(*a.PacketID, verdict) //nolint:errcheck return 0 } + packet.id = *a.PacketID defer func() { processingTime := float64(time.Since(startTime).Microseconds()) @@ -268,8 +297,7 @@ func (c *Controller) Run(ctx context.Context) error { klog.V(2).Infof("Finished syncing packet %d took: %v accepted: %v", *a.PacketID, time.Since(startTime), verdict == nfqueue.NfAccept) }() - // Network Policy - if c.acceptNetworkPolicy(packet) { + if c.evaluatePacket(packet) { verdict = nfqueue.NfAccept } else { verdict = nfqueue.NfDrop @@ -298,6 +326,69 @@ func (c *Controller) Run(ctx context.Context) error { return nil } +// evaluatePacket evalute the network policies using the following order: +// 1. AdminNetworkPolicies in Egress for the source Pod/IP +// 2. NetworkPolicies in Egress (if needed) for the source Pod/IP +// 3. AdminNetworkPolicies in Ingress for the destination Pod/IP +// 4. NetworkPolicies in Ingress (if needed) for the destination Pod/IP +func (c *Controller) evaluatePacket(p packet) bool { + srcIP := p.srcIP + srcPod := c.getPodAssignedToIP(srcIP.String()) + srcPort := p.srcPort + dstIP := p.dstIP + dstPod := c.getPodAssignedToIP(dstIP.String()) + dstPort := p.dstPort + protocol := p.proto + + klog.V(2).Infof("Evaluating packet %s", p.String()) + + // Evalute Egress Policies + + // Admin Network Policies are evaluated first + evaluateEgressNetworkPolicy := true + if c.config.AdminNetworkPolicy { + srcPodAdminNetworkPolices := c.getAdminNetworkPoliciesForPod(srcPod) + action := c.evaluateAdminEgress(srcPodAdminNetworkPolices, dstPod, dstIP, dstPort, protocol) + klog.V(2).Infof("[Packet %d] Egress AdminNetworkPolicies: %d Action: %s", p.id, len(srcPodAdminNetworkPolices), action) + switch action { + case npav1alpha1.AdminNetworkPolicyRuleActionDeny: // Deny the packet no need to check anything else + return false + case npav1alpha1.AdminNetworkPolicyRuleActionAllow: // Packet is allowed in Egress so no need to evalute Network Policies + evaluateEgressNetworkPolicy = false + case npav1alpha1.AdminNetworkPolicyRuleActionPass: // Packet need to evalute Egress Network Policies + } + } + if evaluateEgressNetworkPolicy { + srcPodNetworkPolices := c.getNetworkPoliciesForPod(srcPod) + // if is not allowed no need to evalute more + allowed := c.evaluator(srcPodNetworkPolices, networkingv1.PolicyTypeEgress, srcPod, srcPort, dstPod, dstIP, dstPort, protocol) + klog.V(2).Infof("[Packet %d] Egress NetworkPolicies: %d Allowed: %v", p.id, len(srcPodNetworkPolices), allowed) + if !allowed { + return false + } + } + // Evalute Ingress Policies + + // Admin Network Policies are evaluated first + if c.config.AdminNetworkPolicy { + dstPodAdminNetworkPolices := c.getAdminNetworkPoliciesForPod(dstPod) + action := c.evaluateAdminIngress(dstPodAdminNetworkPolices, srcPod, dstPort, protocol) + klog.V(2).Infof("[Packet %d] Ingress AdminNetworkPolicies: %d Action: %s", p.id, len(dstPodAdminNetworkPolices), action) + switch action { + case npav1alpha1.AdminNetworkPolicyRuleActionDeny: // Deny the packet no need to check anything else + return false + case npav1alpha1.AdminNetworkPolicyRuleActionAllow: // Packet is allowed in Egress so no need to evalute Network Policies + return true + case npav1alpha1.AdminNetworkPolicyRuleActionPass: // Packet need to evalute Egress Network Policies + } + } + dstPodNetworkPolices := c.getNetworkPoliciesForPod(dstPod) + // if is not allowed no need to evalute more + allowed := c.evaluator(dstPodNetworkPolices, networkingv1.PolicyTypeIngress, dstPod, dstPort, srcPod, srcIP, srcPort, protocol) + klog.V(2).Infof("[Packet %d] Egress NetworkPolicies: %d Allowed: %v", p.id, len(dstPodNetworkPolices), allowed) + return allowed +} + // syncNFTablesRules adds the necessary rules to process the first connection packets in userspace // and check if network policies must apply. // TODO: We can divert only the traffic affected by network policies using a set in nftables or an IPset. diff --git a/pkg/networkpolicy/controller_test.go b/pkg/networkpolicy/controller_test.go new file mode 100644 index 0000000..2328d6f --- /dev/null +++ b/pkg/networkpolicy/controller_test.go @@ -0,0 +1,119 @@ +package networkpolicy + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" + "sigs.k8s.io/knftables" + npaclientfake "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/fake" + npainformers "sigs.k8s.io/network-policy-api/pkg/client/informers/externalversions" +) + +func makeNamespace(name string) *v1.Namespace { + return &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + "kubernetes.io/metadata.name": name, + "a": "b", + }, + }, + } +} + +func makeNode(name string) *v1.Node { + return &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + "kubernetes.io/node": name, + "a": "b", + }, + }, + } +} + +func makePod(name, ns string, ip string) *v1.Pod { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + Labels: map[string]string{ + "a": "b", + }, + }, + Spec: v1.PodSpec{ + NodeName: "testnode", + Containers: []v1.Container{ + { + Name: "write-pod", + Command: []string{"/bin/sh"}, + Ports: []v1.ContainerPort{{ + Name: "http", + ContainerPort: 80, + Protocol: v1.ProtocolTCP, + }}, + }, + }, + }, + Status: v1.PodStatus{ + PodIPs: []v1.PodIP{ + {IP: ip}, + }, + }, + } + + return pod + +} + +var ( + alwaysReady = func() bool { return true } + protocolTCP = v1.ProtocolTCP + protocolUDP = v1.ProtocolUDP +) + +type networkpolicyController struct { + *Controller + adminNetworkpolicyStore cache.Store + networkpolicyStore cache.Store + namespaceStore cache.Store + podStore cache.Store + nodeStore cache.Store +} + +func newController() *networkpolicyController { + client := fake.NewSimpleClientset() + informersFactory := informers.NewSharedInformerFactory(client, 0) + + npaClient := npaclientfake.NewSimpleClientset() + npaInformerFactory := npainformers.NewSharedInformerFactory(npaClient, 0) + + controller := NewController(client, + &knftables.Fake{}, + informersFactory.Networking().V1().NetworkPolicies(), + informersFactory.Core().V1().Namespaces(), + informersFactory.Core().V1().Pods(), + informersFactory.Core().V1().Nodes(), + npaClient, + npaInformerFactory.Policy().V1alpha1().AdminNetworkPolicies(), + Config{ + AdminNetworkPolicy: true, + }, + ) + controller.networkpoliciesSynced = alwaysReady + controller.namespacesSynced = alwaysReady + controller.podsSynced = alwaysReady + controller.nodesSynced = alwaysReady + controller.adminNetworkPolicySynced = alwaysReady + return &networkpolicyController{ + controller, + npaInformerFactory.Policy().V1alpha1().AdminNetworkPolicies().Informer().GetStore(), + informersFactory.Networking().V1().NetworkPolicies().Informer().GetStore(), + informersFactory.Core().V1().Namespaces().Informer().GetStore(), + informersFactory.Core().V1().Pods().Informer().GetStore(), + informersFactory.Core().V1().Nodes().Informer().GetStore(), + } +} diff --git a/pkg/networkpolicy/networkpolicy.go b/pkg/networkpolicy/networkpolicy.go index 14c74bc..976bda5 100644 --- a/pkg/networkpolicy/networkpolicy.go +++ b/pkg/networkpolicy/networkpolicy.go @@ -1,7 +1,6 @@ package networkpolicy import ( - "fmt" "net" v1 "k8s.io/api/core/v1" @@ -41,38 +40,6 @@ func (c *Controller) getNetworkPoliciesForPod(pod *v1.Pod) []*networkingv1.Netwo return result } -func (c *Controller) acceptNetworkPolicy(p packet) bool { - srcIP := p.srcIP - srcPod := c.getPodAssignedToIP(srcIP.String()) - srcPort := p.srcPort - dstIP := p.dstIP - dstPod := c.getPodAssignedToIP(dstIP.String()) - dstPort := p.dstPort - protocol := p.proto - srcPodNetworkPolices := c.getNetworkPoliciesForPod(srcPod) - dstPodNetworkPolices := c.getNetworkPoliciesForPod(dstPod) - - msg := fmt.Sprintf("checking packet %s", p.String()) - if srcPod != nil { - msg += fmt.Sprintf("\nSrcPod (%s/%s): %d NetworkPolicy", srcPod.Name, srcPod.Namespace, len(srcPodNetworkPolices)) - } - if dstPod != nil { - msg += fmt.Sprintf("\nDstPod (%s/%s): %d NetworkPolicy", dstPod.Name, dstPod.Namespace, len(dstPodNetworkPolices)) - } - klog.V(2).Infof("%s", msg) - - // For a connection from a source pod to a destination pod to be allowed, - // both the egress policy on the source pod and the ingress policy on the - // destination pod need to allow the connection. - // If either side does not allow the connection, it will not happen. - - // This is the first packet originated from srcPod so we need to check: - // 1. srcPod egress is accepted - // 2. dstPod ingress is accepted - return c.evaluator(srcPodNetworkPolices, networkingv1.PolicyTypeEgress, srcPod, srcPort, dstPod, dstIP, dstPort, protocol) && - c.evaluator(dstPodNetworkPolices, networkingv1.PolicyTypeIngress, dstPod, dstPort, srcPod, srcIP, srcPort, protocol) -} - // validator obtains a verdict for network policies that applies to a src Pod in the direction // passed as parameter func (c *Controller) evaluator( diff --git a/pkg/networkpolicy/networkpolicy_test.go b/pkg/networkpolicy/networkpolicy_test.go index 1106859..7d14393 100644 --- a/pkg/networkpolicy/networkpolicy_test.go +++ b/pkg/networkpolicy/networkpolicy_test.go @@ -8,13 +8,9 @@ import ( networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/tools/cache" "k8s.io/component-base/logs" "k8s.io/klog/v2" "k8s.io/utils/ptr" - "sigs.k8s.io/knftables" ) type netpolTweak func(networkPolicy *networkingv1.NetworkPolicy) @@ -44,84 +40,6 @@ func makePort(proto *v1.Protocol, port intstr.IntOrString, endPort int32) networ return r } -func makeNamespace(name string) *v1.Namespace { - return &v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{ - "kubernetes.io/metadata.name": name, - "a": "b", - }, - }, - } -} -func makePod(name, ns string, ip string) *v1.Pod { - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ns, - Labels: map[string]string{ - "a": "b", - }, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "write-pod", - Command: []string{"/bin/sh"}, - Ports: []v1.ContainerPort{{ - Name: "http", - ContainerPort: 80, - Protocol: v1.ProtocolTCP, - }}, - }, - }, - }, - Status: v1.PodStatus{ - PodIPs: []v1.PodIP{ - {IP: ip}, - }, - }, - } - - return pod - -} - -var ( - alwaysReady = func() bool { return true } - protocolTCP = v1.ProtocolTCP - protocolUDP = v1.ProtocolUDP -) - -type networkpolicyController struct { - *Controller - networkpolicyStore cache.Store - namespaceStore cache.Store - podStore cache.Store -} - -func newController() *networkpolicyController { - client := fake.NewSimpleClientset() - informersFactory := informers.NewSharedInformerFactory(client, 0) - controller := NewController(client, - &knftables.Fake{}, - informersFactory.Networking().V1().NetworkPolicies(), - informersFactory.Core().V1().Namespaces(), - informersFactory.Core().V1().Pods(), - Config{}, - ) - controller.networkpoliciesSynced = alwaysReady - controller.namespacesSynced = alwaysReady - controller.podsSynced = alwaysReady - return &networkpolicyController{ - controller, - informersFactory.Networking().V1().NetworkPolicies().Informer().GetStore(), - informersFactory.Core().V1().Namespaces().Informer().GetStore(), - informersFactory.Core().V1().Pods().Informer().GetStore(), - } -} - func TestSyncPacket(t *testing.T) { _, err := logs.GlogSetter("4") if err != nil { @@ -521,7 +439,7 @@ func TestSyncPacket(t *testing.T) { } } - ok := controller.acceptNetworkPolicy(tt.p) + ok := controller.evaluatePacket(tt.p) if ok != tt.expect { t.Errorf("expected %v got %v", ok, tt.expect) } diff --git a/pkg/networkpolicy/packet.go b/pkg/networkpolicy/packet.go index eb7348f..157c19e 100644 --- a/pkg/networkpolicy/packet.go +++ b/pkg/networkpolicy/packet.go @@ -10,6 +10,7 @@ import ( ) type packet struct { + id uint32 family v1.IPFamily srcIP net.IP dstIP net.IP @@ -20,7 +21,7 @@ type packet struct { } func (p packet) String() string { - return fmt.Sprintf("%s:%d %s:%d %s\n%s", p.srcIP.String(), p.srcPort, p.dstIP.String(), p.dstPort, p.proto, hex.Dump(p.payload)) + return fmt.Sprintf("[%d] %s:%d %s:%d %s\n%s", p.id, p.srcIP.String(), p.srcPort, p.dstIP.String(), p.dstPort, p.proto, hex.Dump(p.payload)) } // https://en.wikipedia.org/wiki/Internet_Protocol_version_4#Packet_structure From c22eef7ece1c9ffcfa01a7cc2a8393002b2920ca Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Wed, 24 Apr 2024 10:41:40 +0000 Subject: [PATCH 02/11] add conformance test for network admin policies --- .github/workflows/npa.yml | 152 ++++++++++++++++++++++++++++++++++++++ install-anp.yaml | 103 ++++++++++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 .github/workflows/npa.yml create mode 100644 install-anp.yaml diff --git a/.github/workflows/npa.yml b/.github/workflows/npa.yml new file mode 100644 index 0000000..97e5599 --- /dev/null +++ b/.github/workflows/npa.yml @@ -0,0 +1,152 @@ + +name: e2e_npa + +on: + push: + branches: + - 'main' + tags: + - 'v*' + pull_request: + branches: [ main ] + workflow_dispatch: + +env: + GO_VERSION: "1.22.0" + K8S_VERSION: "v1.29.2" + KIND_VERSION: "v0.22.0" + IMAGE_NAME: registry.k8s.io/networking/kube-network-policies + KIND_CLUSTER_NAME: kind + +permissions: write-all + +jobs: + build: + name: build + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.GO_VERSION }} + id: go + + - name: Check out code + uses: actions/checkout@v2 + + - name: Build + run: | + docker build -t registry.k8s.io/networking/kube-network-policies:test -f Dockerfile . + mkdir _output + docker save registry.k8s.io/networking/kube-network-policies:test > _output/kube-network-policies-image.tar + + - uses: actions/upload-artifact@v2 + with: + name: test-image + path: _output/kube-network-policies-image.tar + + e2e_npa: + name: e2e_npa + runs-on: ubuntu-22.04 + timeout-minutes: 100 + needs: + - build + strategy: + fail-fast: false + matrix: + # TODO add "dual", waiting on KEP https://github.com/kubernetes/enhancements/tree/master/keps/sig-network/3705-cloud-node-ips + ipFamily: ["ipv4", "ipv6"] + env: + JOB_NAME: "kube-network-policies-${{ matrix.ipFamily }}" + IP_FAMILY: ${{ matrix.ipFamily }} + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Enable ipv4 and ipv6 forwarding + run: | + sudo sysctl -w net.ipv6.conf.all.forwarding=1 + sudo sysctl -w net.ipv4.ip_forward=1 + + - name: Set up environment (download dependencies) + run: | + TMP_DIR=$(mktemp -d) + # kubectl + curl -L https://dl.k8s.io/${{ env.K8S_VERSION }}/bin/linux/amd64/kubectl -o ${TMP_DIR}/kubectl + # kind + curl -Lo ${TMP_DIR}/kind https://kind.sigs.k8s.io/dl/${{ env.KIND_VERSION }}/kind-linux-amd64 + # Install + sudo cp ${TMP_DIR}/kubectl /usr/local/bin/kubectl + sudo cp ${TMP_DIR}/kind /usr/local/bin/kind + sudo chmod +x /usr/local/bin/* + + - name: Create multi node cluster + run: | + # output_dir + mkdir -p _artifacts + # create cluster + cat < _artifacts/kubeconfig.conf + + - uses: actions/download-artifact@v2 + with: + name: test-image + + - name: Install kube-network-policies + run: | + /usr/local/bin/kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/network-policy-api/v0.1.5/config/crd/experimental/policy.networking.k8s.io_adminnetworkpolicies.yaml + # preload kube-network-policies image + docker load --input kube-network-policies-image.tar + /usr/local/bin/kind load docker-image registry.k8s.io/networking/kube-network-policies:test --name ${{ env.KIND_CLUSTER_NAME}} + sed -i s#registry.k8s.io/networking/kube-network-policies.*#registry.k8s.io/networking/kube-network-policies:test# install-anp.yaml + /usr/local/bin/kubectl apply -f ./install-anp.yaml + + - name: Get Cluster status + run: | + # wait network is ready + sleep 5 + /usr/local/bin/kubectl get nodes -o wide + /usr/local/bin/kubectl get pods -A + /usr/local/bin/kubectl wait --timeout=1m --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns + /usr/local/bin/kubectl wait --timeout=1m --for=condition=ready pods --namespace=kube-system -l app=kube-network-policies + + - name: Run tests + run: | + # https://network-policy-api.sigs.k8s.io/npeps/npep-137-conformance-profiles/#integration + git clone https://github.com/kubernetes-sigs/network-policy-api.git + cd network-policy-api/ + go mod download + go test -v ./conformance -run TestConformanceProfiles -args --conformance-profiles=AdminNetworkPolicy --organization=kubernetes --project=kube-network-policies --url=https://github.com/kubernetes-sigs/kube-network-policies --version=0.1.1 --contact=antonio.ojea.garcia@gmail.com --additional-info=https://github.com/kubernetes-sigs/kube-network-policies + cd - + + - name: Upload Junit Reports + if: always() + uses: actions/upload-artifact@v2 + with: + name: kind-junit-${{ env.JOB_NAME }}-${{ github.run_id }} + path: './_artifacts/*.xml' + + - name: Export logs + if: always() + run: | + /usr/local/bin/kind export logs --name ${KIND_CLUSTER_NAME} --loglevel=debug ./_artifacts/logs + + - name: Upload logs + if: always() + uses: actions/upload-artifact@v2 + with: + name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }} + path: ./_artifacts/logs diff --git a/install-anp.yaml b/install-anp.yaml new file mode 100644 index 0000000..560759f --- /dev/null +++ b/install-anp.yaml @@ -0,0 +1,103 @@ +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: kube-network-policies +rules: + - apiGroups: + - "" + resources: + - pods + - namespaces + - nodes + verbs: + - list + - watch + - apiGroups: + - "networking.k8s.io" + resources: + - networkpolicies + verbs: + - list + - watch + - apiGroups: + - "policy.networking.k8s.io" + resources: + - adminnetworkpolicies + verbs: + - list + - watch +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: kube-network-policies +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kube-network-policies +subjects: +- kind: ServiceAccount + name: kube-network-policies + namespace: kube-system +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kube-network-policies + namespace: kube-system +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: kube-network-policies + namespace: kube-system + labels: + tier: node + app: kube-network-policies + k8s-app: kube-network-policies +spec: + selector: + matchLabels: + app: kube-network-policies + template: + metadata: + labels: + tier: node + app: kube-network-policies + k8s-app: kube-network-policies + spec: + hostNetwork: true + dnsPolicy: ClusterFirst + nodeSelector: + kubernetes.io/os: linux + tolerations: + - operator: Exists + effect: NoSchedule + serviceAccountName: kube-network-policies + containers: + - name: kube-network-policies + image: registry.k8s.io/networking/kube-network-policies:v0.1.0 + args: + - /bin/netpol + - -v + - "2" + - -admin-network-policy + - "true" + volumeMounts: + - name: lib-modules + mountPath: /lib/modules + readOnly: true + resources: + requests: + cpu: "100m" + memory: "50Mi" + securityContext: + privileged: true + capabilities: + add: ["NET_ADMIN"] + volumes: + - name: lib-modules + hostPath: + path: /lib/modules +--- From f20c7b09880ba2d18929a680eb9f94fc463497cb Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Thu, 25 Apr 2024 11:34:56 +0000 Subject: [PATCH 03/11] implement baseline admin network policy --- cmd/main.go | 26 ++-- install-anp.yaml | 3 + pkg/networkpolicy/controller.go | 63 +++++++-- pkg/networkpolicy/controller_test.go | 16 ++- ...innetworkpolicy.go => networkpolicyapi.go} | 130 ++++++++++++++++++ ...olicy_test.go => networkpolicyapi_test.go} | 0 6 files changed, 214 insertions(+), 24 deletions(-) rename pkg/networkpolicy/{adminnetworkpolicy.go => networkpolicyapi.go} (59%) rename pkg/networkpolicy/{adminnetworkpolicy_test.go => networkpolicyapi_test.go} (100%) diff --git a/cmd/main.go b/cmd/main.go index 0b90c3a..04c4c6b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -28,15 +28,17 @@ import ( ) var ( - failOpen bool - adminNetworkPolicy bool // AdminNetworkPolicy is alpha so keep it feature gated behind a flag - queueID int - metricsBindAddress string + failOpen bool + adminNetworkPolicy bool // AdminNetworkPolicy is alpha so keep it feature gated behind a flag + baselineAdminNetworkPolicy bool // BaselineAdminNetworkPolicy is alpha so keep it feature gated behind a flag + queueID int + metricsBindAddress string ) func init() { flag.BoolVar(&failOpen, "fail-open", false, "If set, don't drop packets if the controller is not running") flag.BoolVar(&adminNetworkPolicy, "admin-network-policy", false, "If set, enable Admin Network Policy API") + flag.BoolVar(&baselineAdminNetworkPolicy, "baseline-admin-network-policy", false, "If set, enable Baseline Admin Network Policy API") flag.IntVar(&queueID, "nfqueue-id", 100, "Number of the nfqueue used") flag.StringVar(&metricsBindAddress, "metrics-bind-address", ":9080", "The IP address and port for the metrics server to serve on") @@ -92,16 +94,23 @@ func main() { var npaClient *npaclient.Clientset var npaInformerFactory npainformers.SharedInformerFactory - var npaInformer v1alpha1.AdminNetworkPolicyInformer var nodeInformer v1.NodeInformer - if adminNetworkPolicy { + if adminNetworkPolicy || baselineAdminNetworkPolicy { nodeInformer = informersFactory.Core().V1().Nodes() npaClient, err = npaclient.NewForConfig(config) if err != nil { klog.Fatalf("Failed to create Network client: %v", err) } npaInformerFactory = npainformers.NewSharedInformerFactory(npaClient, 0) - npaInformer = npaInformerFactory.Policy().V1alpha1().AdminNetworkPolicies() + } + + var anpInformer v1alpha1.AdminNetworkPolicyInformer + if adminNetworkPolicy { + anpInformer = npaInformerFactory.Policy().V1alpha1().AdminNetworkPolicies() + } + var banpInformer v1alpha1.BaselineAdminNetworkPolicyInformer + if baselineAdminNetworkPolicy { + banpInformer = npaInformerFactory.Policy().V1alpha1().BaselineAdminNetworkPolicies() } http.Handle("/metrics", promhttp.Handler()) @@ -118,7 +127,8 @@ func main() { informersFactory.Core().V1().Pods(), nodeInformer, npaClient, - npaInformer, + anpInformer, + banpInformer, cfg, ) go func() { diff --git a/install-anp.yaml b/install-anp.yaml index 560759f..054c695 100644 --- a/install-anp.yaml +++ b/install-anp.yaml @@ -24,6 +24,7 @@ rules: - "policy.networking.k8s.io" resources: - adminnetworkpolicies + - baselineadminnetworkpolicies verbs: - list - watch @@ -84,6 +85,8 @@ spec: - "2" - -admin-network-policy - "true" + - -baseline-admin-network-policy + - "true" volumeMounts: - name: lib-modules mountPath: /lib/modules diff --git a/pkg/networkpolicy/controller.go b/pkg/networkpolicy/controller.go index 91707fa..8027812 100644 --- a/pkg/networkpolicy/controller.go +++ b/pkg/networkpolicy/controller.go @@ -53,9 +53,10 @@ const ( ) type Config struct { - FailOpen bool // allow traffic if the controller is not available - AdminNetworkPolicy bool - QueueID int + FailOpen bool // allow traffic if the controller is not available + AdminNetworkPolicy bool + BaselineAdminNetworkPolicy bool + QueueID int } // NewController returns a new *Controller. @@ -67,6 +68,7 @@ func NewController(client clientset.Interface, nodeInformer coreinformers.NodeInformer, npaClient npaclient.Interface, adminNetworkPolicyInformer policyinformers.AdminNetworkPolicyInformer, + baselineAdminNetworkPolicyInformer policyinformers.BaselineAdminNetworkPolicyInformer, config Config, ) *Controller { klog.V(2).Info("Creating event broadcaster") @@ -146,14 +148,22 @@ func NewController(client clientset.Interface, c.namespacesSynced = namespaceInformer.Informer().HasSynced c.networkpolicyLister = networkpolicyInformer.Lister() c.networkpoliciesSynced = networkpolicyInformer.Informer().HasSynced - if config.AdminNetworkPolicy { + if config.AdminNetworkPolicy || config.BaselineAdminNetworkPolicy { c.npaClient = npaClient c.nodeLister = nodeInformer.Lister() c.nodesSynced = nodeInformer.Informer().HasSynced + } + + if config.AdminNetworkPolicy { c.adminNetworkPolicyLister = adminNetworkPolicyInformer.Lister() c.adminNetworkPolicySynced = adminNetworkPolicyInformer.Informer().HasSynced } + if config.BaselineAdminNetworkPolicy { + c.baselineAdminNetworkPolicyLister = baselineAdminNetworkPolicyInformer.Lister() + c.baselineAdminNetworkPolicySynced = baselineAdminNetworkPolicyInformer.Informer().HasSynced + } + c.eventBroadcaster = broadcaster c.eventRecorder = recorder @@ -178,10 +188,12 @@ type Controller struct { npaClient npaclient.Interface - adminNetworkPolicyLister policylisters.AdminNetworkPolicyLister - adminNetworkPolicySynced cache.InformerSynced - nodeLister corelisters.NodeLister - nodesSynced cache.InformerSynced + adminNetworkPolicyLister policylisters.AdminNetworkPolicyLister + adminNetworkPolicySynced cache.InformerSynced + baselineAdminNetworkPolicyLister policylisters.BaselineAdminNetworkPolicyLister + baselineAdminNetworkPolicySynced cache.InformerSynced + nodeLister corelisters.NodeLister + nodesSynced cache.InformerSynced // function to get the Pod given an IP // if an error or not found it returns nil getPodAssignedToIP func(podIP string) *v1.Pod @@ -202,8 +214,14 @@ func (c *Controller) Run(ctx context.Context) error { // Wait for the caches to be synced klog.Info("Waiting for informer caches to sync") caches := []cache.InformerSynced{c.networkpoliciesSynced, c.namespacesSynced, c.podsSynced} + if c.config.AdminNetworkPolicy || c.config.BaselineAdminNetworkPolicy { + caches = append(caches, c.nodesSynced) + } if c.config.AdminNetworkPolicy { - caches = append(caches, c.adminNetworkPolicySynced, c.nodesSynced) + caches = append(caches, c.adminNetworkPolicySynced) + } + if c.config.BaselineAdminNetworkPolicy { + caches = append(caches, c.baselineAdminNetworkPolicySynced) } if !cache.WaitForNamedCacheSync(controllerName, ctx.Done(), caches...) { return fmt.Errorf("error syncing cache") @@ -367,6 +385,17 @@ func (c *Controller) evaluatePacket(p packet) bool { return false } } + if c.config.BaselineAdminNetworkPolicy { + srcPodBaselineAdminNetworkPolices := c.getBaselineAdminNetworkPoliciesForPod(srcPod) + action := c.evaluateBaselineAdminEgress(srcPodBaselineAdminNetworkPolices, dstPod, dstIP, dstPort, protocol) + klog.V(2).Infof("[Packet %d] Egress BaselineAdminNetworkPolicies: %d Action: %s", p.id, len(srcPodBaselineAdminNetworkPolices), action) + switch action { + case npav1alpha1.BaselineAdminNetworkPolicyRuleActionDeny: // Deny the packet no need to check anything else + return false + case npav1alpha1.BaselineAdminNetworkPolicyRuleActionAllow: + } + } + // Evalute Ingress Policies // Admin Network Policies are evaluated first @@ -386,7 +415,21 @@ func (c *Controller) evaluatePacket(p packet) bool { // if is not allowed no need to evalute more allowed := c.evaluator(dstPodNetworkPolices, networkingv1.PolicyTypeIngress, dstPod, dstPort, srcPod, srcIP, srcPort, protocol) klog.V(2).Infof("[Packet %d] Egress NetworkPolicies: %d Allowed: %v", p.id, len(dstPodNetworkPolices), allowed) - return allowed + if !allowed { + return false + } + if c.config.BaselineAdminNetworkPolicy { + dstPodBaselineAdminNetworkPolices := c.getBaselineAdminNetworkPoliciesForPod(dstPod) + action := c.evaluateBaselineAdminIngress(dstPodBaselineAdminNetworkPolices, srcPod, dstPort, protocol) + klog.V(2).Infof("[Packet %d] Egress BaselineAdminNetworkPolicies: %d Action: %s", p.id, len(dstPodBaselineAdminNetworkPolices), action) + switch action { + case npav1alpha1.BaselineAdminNetworkPolicyRuleActionDeny: // Deny the packet no need to check anything else + return false + case npav1alpha1.BaselineAdminNetworkPolicyRuleActionAllow: + return true + } + } + return true } // syncNFTablesRules adds the necessary rules to process the first connection packets in userspace diff --git a/pkg/networkpolicy/controller_test.go b/pkg/networkpolicy/controller_test.go index 2328d6f..b1f448c 100644 --- a/pkg/networkpolicy/controller_test.go +++ b/pkg/networkpolicy/controller_test.go @@ -77,11 +77,12 @@ var ( type networkpolicyController struct { *Controller - adminNetworkpolicyStore cache.Store - networkpolicyStore cache.Store - namespaceStore cache.Store - podStore cache.Store - nodeStore cache.Store + adminNetworkpolicyStore cache.Store + baselineAdminNetworkpolicyStore cache.Store + networkpolicyStore cache.Store + namespaceStore cache.Store + podStore cache.Store + nodeStore cache.Store } func newController() *networkpolicyController { @@ -99,8 +100,10 @@ func newController() *networkpolicyController { informersFactory.Core().V1().Nodes(), npaClient, npaInformerFactory.Policy().V1alpha1().AdminNetworkPolicies(), + npaInformerFactory.Policy().V1alpha1().BaselineAdminNetworkPolicies(), Config{ - AdminNetworkPolicy: true, + AdminNetworkPolicy: true, + BaselineAdminNetworkPolicy: true, }, ) controller.networkpoliciesSynced = alwaysReady @@ -111,6 +114,7 @@ func newController() *networkpolicyController { return &networkpolicyController{ controller, npaInformerFactory.Policy().V1alpha1().AdminNetworkPolicies().Informer().GetStore(), + npaInformerFactory.Policy().V1alpha1().BaselineAdminNetworkPolicies().Informer().GetStore(), informersFactory.Networking().V1().NetworkPolicies().Informer().GetStore(), informersFactory.Core().V1().Namespaces().Informer().GetStore(), informersFactory.Core().V1().Pods().Informer().GetStore(), diff --git a/pkg/networkpolicy/adminnetworkpolicy.go b/pkg/networkpolicy/networkpolicyapi.go similarity index 59% rename from pkg/networkpolicy/adminnetworkpolicy.go rename to pkg/networkpolicy/networkpolicyapi.go index ff7a6b5..bf039a0 100644 --- a/pkg/networkpolicy/adminnetworkpolicy.go +++ b/pkg/networkpolicy/networkpolicyapi.go @@ -237,3 +237,133 @@ func evaluateAdminNetworkPolicyPort(networkPolicyPorts []npav1alpha1.AdminNetwor } return false } + +// getBaselineAdminNetworkPoliciesForPod returns the list of Baseline Admin Network Policies matching the Pod +// The list is ordered by priority, from higher to lower. +func (c *Controller) getBaselineAdminNetworkPoliciesForPod(pod *v1.Pod) []*npav1alpha1.BaselineAdminNetworkPolicy { + if pod == nil { + return nil + } + // Get all the network policies that affect this pod + networkPolices, err := c.baselineAdminNetworkPolicyLister.List(labels.Everything()) + if err != nil { + klog.Infof("getAdminNetworkPoliciesForPod error: %v", err) + return nil + } + + result := []*npav1alpha1.BaselineAdminNetworkPolicy{} + for _, policy := range networkPolices { + if policy.Spec.Subject.Namespaces != nil && + c.namespaceSelector(policy.Spec.Subject.Namespaces, pod) { + klog.V(2).Infof("Pod %s/%s match AdminNetworkPolicy %s", pod.Name, pod.Namespace, policy.Name) + result = append(result, policy) + } + + if policy.Spec.Subject.Pods != nil && + c.namespaceSelector(&policy.Spec.Subject.Pods.NamespaceSelector, pod) && + podSelector(&policy.Spec.Subject.Pods.PodSelector, pod) { + klog.V(2).Infof("Pod %s/%s match AdminNetworkPolicy %s", pod.Name, pod.Namespace, policy.Name) + result = append(result, policy) + } + } + return result +} + +func (c *Controller) evaluateBaselineAdminEgress(adminNetworkPolices []*npav1alpha1.BaselineAdminNetworkPolicy, pod *v1.Pod, ip net.IP, port int, protocol v1.Protocol) npav1alpha1.BaselineAdminNetworkPolicyRuleAction { + for _, policy := range adminNetworkPolices { + for _, rule := range policy.Spec.Egress { + // Ports allows for matching traffic based on port and protocols. + // This field is a list of destination ports for the outgoing egress traffic. + // If Ports is not set then the rule does not filter traffic via port. + if rule.Ports != nil { + if !evaluateAdminNetworkPolicyPort(*rule.Ports, pod, port, protocol) { + continue + } + } + // To is the List of destinations whose traffic this rule applies to. + // If any AdminNetworkPolicyEgressPeer matches the destination of outgoing + // traffic then the specified action is applied. + // This field must be defined and contain at least one item. + for _, to := range rule.To { + // Exactly one of the selector pointers must be set for a given peer. If a + // consumer observes none of its fields are set, they must assume an unknown + // option has been specified and fail closed. + if to.Namespaces != nil && pod != nil { + if c.namespaceSelector(to.Namespaces, pod) { + return rule.Action + } + } + + if to.Pods != nil && pod != nil { + if c.namespaceSelector(&to.Pods.NamespaceSelector, pod) && + podSelector(&to.Pods.PodSelector, pod) { + return rule.Action + } + } + + if to.Nodes != nil && pod != nil { + if c.nodeSelector(to.Nodes, pod) { + return rule.Action + } + } + + for _, network := range to.Networks { + _, cidr, err := net.ParseCIDR(string(network)) + if err != nil { // this has been validated by the API + continue + } + if cidr.Contains(ip) { + return rule.Action + } + } + } + } + } + + return npav1alpha1.BaselineAdminNetworkPolicyRuleActionAllow +} + +func (c *Controller) evaluateBaselineAdminIngress(adminNetworkPolices []*npav1alpha1.BaselineAdminNetworkPolicy, pod *v1.Pod, port int, protocol v1.Protocol) npav1alpha1.BaselineAdminNetworkPolicyRuleAction { + // Ingress rules only apply to pods + if pod == nil { + return npav1alpha1.BaselineAdminNetworkPolicyRuleActionAllow + } + for _, policy := range adminNetworkPolices { + // Ingress is the list of Ingress rules to be applied to the selected pods. A total of 100 rules will be allowed in each ANP instance. The relative precedence of ingress rules within a single ANP object (all of which share the priority) will be determined by the order in which the rule is written. Thus, a rule that appears at the top of the ingress rules would take the highest precedence. + // ANPs with no ingress rules do not affect ingress traffic. + for _, rule := range policy.Spec.Ingress { + // Ports allows for matching traffic based on port and protocols. + // This field is a list of ports which should be matched on the pods selected for this policy + // i.e the subject of the policy. So it matches on the destination port for the ingress traffic. + // If Ports is not set then the rule does not filter traffic via port. + if rule.Ports != nil { + if !evaluateAdminNetworkPolicyPort(*rule.Ports, pod, port, protocol) { + continue + } + } + // From is the list of sources whose traffic this rule applies to. + // If any AdminNetworkPolicyIngressPeer matches the source of incoming traffic then the specified action is applied. + // This field must be defined and contain at least one item. + for _, from := range rule.From { + // Exactly one of the selector pointers must be set for a given peer. If a + // consumer observes none of its fields are set, they must assume an unknown + // option has been specified and fail closed. + if from.Namespaces != nil { + if c.namespaceSelector(from.Namespaces, pod) { + return rule.Action + } + } + + if from.Pods != nil { + if c.namespaceSelector(&from.Pods.NamespaceSelector, pod) && + podSelector(&from.Pods.PodSelector, pod) { + return rule.Action + } + } + } + + } + } + + return npav1alpha1.BaselineAdminNetworkPolicyRuleActionAllow +} diff --git a/pkg/networkpolicy/adminnetworkpolicy_test.go b/pkg/networkpolicy/networkpolicyapi_test.go similarity index 100% rename from pkg/networkpolicy/adminnetworkpolicy_test.go rename to pkg/networkpolicy/networkpolicyapi_test.go From 223fdfade74dd4bc0443bccc9f64c4d9beb0fe48 Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Thu, 25 Apr 2024 11:39:03 +0000 Subject: [PATCH 04/11] fix e2e --- .github/workflows/npa.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/npa.yml b/.github/workflows/npa.yml index 97e5599..9036db5 100644 --- a/.github/workflows/npa.yml +++ b/.github/workflows/npa.yml @@ -107,7 +107,8 @@ jobs: - name: Install kube-network-policies run: | - /usr/local/bin/kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/network-policy-api/v0.1.5/config/crd/experimental/policy.networking.k8s.io_adminnetworkpolicies.yaml + /usr/local/bin/kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/network-policy-api/main/config/crd/experimental/policy.networking.k8s.io_adminnetworkpolicies.yaml + /usr/local/bin/kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/network-policy-api/main/config/crd/experimental/policy.networking.k8s.io_baselineadminnetworkpolicies.yaml # preload kube-network-policies image docker load --input kube-network-policies-image.tar /usr/local/bin/kind load docker-image registry.k8s.io/networking/kube-network-policies:test --name ${{ env.KIND_CLUSTER_NAME}} @@ -129,7 +130,7 @@ jobs: git clone https://github.com/kubernetes-sigs/network-policy-api.git cd network-policy-api/ go mod download - go test -v ./conformance -run TestConformanceProfiles -args --conformance-profiles=AdminNetworkPolicy --organization=kubernetes --project=kube-network-policies --url=https://github.com/kubernetes-sigs/kube-network-policies --version=0.1.1 --contact=antonio.ojea.garcia@gmail.com --additional-info=https://github.com/kubernetes-sigs/kube-network-policies + go test -v ./conformance -run TestConformanceProfiles -args --conformance-profiles=AdminNetworkPolicy,BaselineAdminNetworkPolicy --organization=kubernetes --project=kube-network-policies --url=https://github.com/kubernetes-sigs/kube-network-policies --version=0.1.1 --contact=antonio.ojea.garcia@gmail.com --additional-info=https://github.com/kubernetes-sigs/kube-network-policies cd - - name: Upload Junit Reports From e6f7f97832b36df79b9a00db8fac10a56653bfcd Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Thu, 25 Apr 2024 11:44:44 +0000 Subject: [PATCH 05/11] enable the informer for both admin or baseline --- cmd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/main.go b/cmd/main.go index 04c4c6b..99356c5 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -137,7 +137,7 @@ func main() { }() informersFactory.Start(ctx.Done()) - if adminNetworkPolicy { + if adminNetworkPolicy || baselineAdminNetworkPolicy { npaInformerFactory.Start(ctx.Done()) } From bdd1fbee15ef873ceb8f16a4fbd98bbcffabe67d Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Thu, 25 Apr 2024 12:09:34 +0000 Subject: [PATCH 06/11] respect priorities for admin network policies --- pkg/networkpolicy/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/networkpolicy/controller.go b/pkg/networkpolicy/controller.go index 8027812..c248cee 100644 --- a/pkg/networkpolicy/controller.go +++ b/pkg/networkpolicy/controller.go @@ -385,7 +385,7 @@ func (c *Controller) evaluatePacket(p packet) bool { return false } } - if c.config.BaselineAdminNetworkPolicy { + if c.config.BaselineAdminNetworkPolicy && evaluateEgressNetworkPolicy { srcPodBaselineAdminNetworkPolices := c.getBaselineAdminNetworkPoliciesForPod(srcPod) action := c.evaluateBaselineAdminEgress(srcPodBaselineAdminNetworkPolices, dstPod, dstIP, dstPort, protocol) klog.V(2).Infof("[Packet %d] Egress BaselineAdminNetworkPolicies: %d Action: %s", p.id, len(srcPodBaselineAdminNetworkPolices), action) From 32e0f7117e8ef44a9ff51ce5a8f87fa07996c8c0 Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Thu, 25 Apr 2024 12:32:59 +0000 Subject: [PATCH 07/11] fix pipeline --- pkg/networkpolicy/controller.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/pkg/networkpolicy/controller.go b/pkg/networkpolicy/controller.go index c248cee..0b0c2ef 100644 --- a/pkg/networkpolicy/controller.go +++ b/pkg/networkpolicy/controller.go @@ -347,8 +347,10 @@ func (c *Controller) Run(ctx context.Context) error { // evaluatePacket evalute the network policies using the following order: // 1. AdminNetworkPolicies in Egress for the source Pod/IP // 2. NetworkPolicies in Egress (if needed) for the source Pod/IP -// 3. AdminNetworkPolicies in Ingress for the destination Pod/IP -// 4. NetworkPolicies in Ingress (if needed) for the destination Pod/IP +// 3. BaselineAdminNetworkPolicies in Egress (if needed) for the source Pod/IP +// 4. AdminNetworkPolicies in Ingress for the destination Pod/IP +// 5. NetworkPolicies in Ingress (if needed) for the destination Pod/IP +// 6. BaselineAdminNetworkPolicies in Ingress (if needed) for the destination Pod/IP func (c *Controller) evaluatePacket(p packet) bool { srcIP := p.srcIP srcPod := c.getPodAssignedToIP(srcIP.String()) @@ -376,16 +378,19 @@ func (c *Controller) evaluatePacket(p packet) bool { case npav1alpha1.AdminNetworkPolicyRuleActionPass: // Packet need to evalute Egress Network Policies } } + evaluateAdminEgressNetworkPolicy := evaluateEgressNetworkPolicy if evaluateEgressNetworkPolicy { srcPodNetworkPolices := c.getNetworkPoliciesForPod(srcPod) - // if is not allowed no need to evalute more + if len(srcPodNetworkPolices) > 0 { + evaluateAdminEgressNetworkPolicy = false + } allowed := c.evaluator(srcPodNetworkPolices, networkingv1.PolicyTypeEgress, srcPod, srcPort, dstPod, dstIP, dstPort, protocol) klog.V(2).Infof("[Packet %d] Egress NetworkPolicies: %d Allowed: %v", p.id, len(srcPodNetworkPolices), allowed) if !allowed { return false } } - if c.config.BaselineAdminNetworkPolicy && evaluateEgressNetworkPolicy { + if c.config.BaselineAdminNetworkPolicy && evaluateAdminEgressNetworkPolicy { srcPodBaselineAdminNetworkPolices := c.getBaselineAdminNetworkPoliciesForPod(srcPod) action := c.evaluateBaselineAdminEgress(srcPodBaselineAdminNetworkPolices, dstPod, dstIP, dstPort, protocol) klog.V(2).Infof("[Packet %d] Egress BaselineAdminNetworkPolicies: %d Action: %s", p.id, len(srcPodBaselineAdminNetworkPolices), action) @@ -411,12 +416,12 @@ func (c *Controller) evaluatePacket(p packet) bool { case npav1alpha1.AdminNetworkPolicyRuleActionPass: // Packet need to evalute Egress Network Policies } } + // Network policies override Baseline Admin Network Policies dstPodNetworkPolices := c.getNetworkPoliciesForPod(dstPod) - // if is not allowed no need to evalute more - allowed := c.evaluator(dstPodNetworkPolices, networkingv1.PolicyTypeIngress, dstPod, dstPort, srcPod, srcIP, srcPort, protocol) - klog.V(2).Infof("[Packet %d] Egress NetworkPolicies: %d Allowed: %v", p.id, len(dstPodNetworkPolices), allowed) - if !allowed { - return false + if len(dstPodNetworkPolices) > 0 { + allowed := c.evaluator(dstPodNetworkPolices, networkingv1.PolicyTypeIngress, dstPod, dstPort, srcPod, srcIP, srcPort, protocol) + klog.V(2).Infof("[Packet %d] Egress NetworkPolicies: %d Allowed: %v", p.id, len(dstPodNetworkPolices), allowed) + return allowed } if c.config.BaselineAdminNetworkPolicy { dstPodBaselineAdminNetworkPolices := c.getBaselineAdminNetworkPoliciesForPod(dstPod) From 8a85f9e95461b39d693cddaf6953ff0865fba1a7 Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Thu, 25 Apr 2024 12:45:27 +0000 Subject: [PATCH 08/11] enable baseline network policy --- cmd/main.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 99356c5..930a5fb 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -63,9 +63,10 @@ func main() { } cfg := networkpolicy.Config{ - AdminNetworkPolicy: adminNetworkPolicy, - FailOpen: failOpen, - QueueID: queueID, + AdminNetworkPolicy: adminNetworkPolicy, + BaselineAdminNetworkPolicy: baselineAdminNetworkPolicy, + FailOpen: failOpen, + QueueID: queueID, } // creates the in-cluster config config, err := rest.InClusterConfig() From a48b42795dc91f0bd44b5a53cb24b673d309dd09 Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Thu, 25 Apr 2024 13:01:24 +0000 Subject: [PATCH 09/11] add logging --- cmd/main.go | 2 ++ pkg/networkpolicy/controller.go | 1 + 2 files changed, 3 insertions(+) diff --git a/cmd/main.go b/cmd/main.go index 930a5fb..7f81e87 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -53,6 +53,8 @@ func main() { klog.InitFlags(nil) flag.Parse() + klog.Infof("flags: %v", flag.Args()) + nft, err := knftables.New(knftables.InetFamily, "kube-network-policies") if err != nil { klog.Fatalf("Error initializing nftables: %v", err) diff --git a/pkg/networkpolicy/controller.go b/pkg/networkpolicy/controller.go index 0b0c2ef..8ff6535 100644 --- a/pkg/networkpolicy/controller.go +++ b/pkg/networkpolicy/controller.go @@ -77,6 +77,7 @@ func NewController(client clientset.Interface, broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")}) recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: controllerName}) + klog.V(2).Infof("Creating controller: %#v", config) c := &Controller{ client: client, config: config, From c19977d4a20bdd1c5650cef2c772111f8cba664b Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Thu, 25 Apr 2024 13:20:18 +0000 Subject: [PATCH 10/11] fix install manifest --- install-anp.yaml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/install-anp.yaml b/install-anp.yaml index 054c695..705f39c 100644 --- a/install-anp.yaml +++ b/install-anp.yaml @@ -78,15 +78,9 @@ spec: serviceAccountName: kube-network-policies containers: - name: kube-network-policies - image: registry.k8s.io/networking/kube-network-policies:v0.1.0 - args: - - /bin/netpol - - -v - - "2" - - -admin-network-policy - - "true" - - -baseline-admin-network-policy - - "true" + image: aojea/kube-netpol:v0.1.0 + command: ["/bin/netpol"] + args: ["-v=2", "-admin-network-policy=true", "-baseline-admin-network-policy=true"] volumeMounts: - name: lib-modules mountPath: /lib/modules From 729b384fffad84052da55d6d29a0d43494aabaaf Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Thu, 25 Apr 2024 13:31:21 +0000 Subject: [PATCH 11/11] fix image --- install-anp.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install-anp.yaml b/install-anp.yaml index 705f39c..4852358 100644 --- a/install-anp.yaml +++ b/install-anp.yaml @@ -78,7 +78,7 @@ spec: serviceAccountName: kube-network-policies containers: - name: kube-network-policies - image: aojea/kube-netpol:v0.1.0 + image: registry.k8s.io/networking/kube-network-policies:v0.1.0 command: ["/bin/netpol"] args: ["-v=2", "-admin-network-policy=true", "-baseline-admin-network-policy=true"] volumeMounts: