generated from kubernetes/kubernetes-template-project
-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
1,017 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
package networkpolicy | ||
|
||
import ( | ||
"cmp" | ||
"fmt" | ||
"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" | ||
) | ||
|
||
// adminNetworkPolicyAction evaluate the Admin Network Policies on the packet | ||
// that is evaluated both from the origin and the destination perspective. | ||
// There can be 3 possible outcomes: | ||
// 1. Policies Allow in both direction then the packet is Allowed and NOT evaluated by Network Policies | ||
// 2. Policies Deny in any direction then the packet is Dropped and NOT evaluated by Network Policies | ||
// 3. Policies Pass and or Allow then the packet is passed to be evaluated by Network Policies | ||
func (c *Controller) adminNetworkPolicyAction(p packet) npav1alpha1.AdminNetworkPolicyRuleAction { | ||
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 | ||
srcPodAdminNetworkPolices := c.getAdminNetworkPoliciesForPod(srcPod) | ||
dstPodAdminNetworkPolices := c.getAdminNetworkPoliciesForPod(dstPod) | ||
msg := fmt.Sprintf("checking packet %s:", p.String()) | ||
if srcPod != nil { | ||
msg += fmt.Sprintf(" SrcPod (%s/%s): %d AdminNetworkPolicy", srcPod.Name, srcPod.Namespace, len(srcPodAdminNetworkPolices)) | ||
} | ||
if dstPod != nil { | ||
msg += fmt.Sprintf(" DstPod (%s/%s): %d AdminNetworkPolicy", dstPod.Name, dstPod.Namespace, len(dstPodAdminNetworkPolices)) | ||
} | ||
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. | ||
// This is the first packet originated from srcPod so we need to check: | ||
// 1. srcPod egress is accepted | ||
for _, policy := range srcPodAdminNetworkPolices { | ||
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, dstPod, dstPort, 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 { | ||
if c.namespaceSelector(to.Namespaces, dstPod) { | ||
return rule.Action | ||
} | ||
} | ||
|
||
if to.Pods != nil { | ||
if c.namespaceSelector(&to.Pods.NamespaceSelector, dstPod) && | ||
podSelector(&to.Pods.PodSelector, dstPod) { | ||
return rule.Action | ||
} | ||
} | ||
|
||
if to.Nodes != nil { | ||
if c.nodeSelector(to.Nodes, dstPod) { | ||
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(dstIP) { | ||
return rule.Action | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// 2. dstPod ingress is accepted | ||
for _, policy := range dstPodAdminNetworkPolices { | ||
// 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 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, srcPod, srcPort, 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 _, 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, srcPod) { | ||
return rule.Action | ||
} | ||
} | ||
|
||
if from.Pods != nil { | ||
if c.namespaceSelector(&from.Pods.NamespaceSelector, srcPod) && | ||
podSelector(&from.Pods.PodSelector, srcPod) { | ||
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.namespaceLister.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 | ||
} |
Oops, something went wrong.