Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tool: validator script for Azure NPM to cilium migration #3372

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
337 changes: 337 additions & 0 deletions tools/azure-npm-to-cilium-validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
package main

import (
"context"
"flag"
"fmt"
"log"

corev1 "k8s.io/api/core/v1"

Check failure on line 9 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

dupImport: package is imported 2 times under different aliases on lines 9 and 10 (gocritic)

Check failure on line 9 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

dupImport: package is imported 2 times under different aliases on lines 9 and 10 (gocritic)

Check failure on line 9 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

dupImport: package is imported 2 times under different aliases on lines 9 and 10 (gocritic)

Check failure on line 9 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

dupImport: package is imported 2 times under different aliases on lines 9 and 10 (gocritic)
v1 "k8s.io/api/core/v1"

Check failure on line 10 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

dupImport: package is imported 2 times under different aliases on lines 9 and 10 (gocritic)

Check failure on line 10 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

dupImport: package is imported 2 times under different aliases on lines 9 and 10 (gocritic)

Check failure on line 10 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

dupImport: package is imported 2 times under different aliases on lines 9 and 10 (gocritic)

Check failure on line 10 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

dupImport: package is imported 2 times under different aliases on lines 9 and 10 (gocritic)
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

// FOR DEMO
// kubectl delete networkpolicy --all --all-namespaces
// k apply -f network-polices-and-services-npm-script-test
// k get networkpolicies -A -o wide
// cd AzureRepo/azure-container-networking/tools
// go run azure-npm-to-cilium-validator.go --kubeconfig ~/.kube/config
func main() {
// Parse the kubeconfig flag
kubeconfig := flag.String("kubeconfig", "~/.kube/config", "absolute path to the kubeconfig file")
flag.Parse()

// Build the Kubernetes client config
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
log.Fatalf("Error building kubeconfig: %v", err)
}

// Create a Kubernetes client
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Fatalf("Error creating Kubernetes client: %v", err)
}

// Get namespaces
namespaces, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
if err != nil {
log.Fatalf("Error getting namespaces: %v\n", err)
}

// Store network policies and services in maps
policiesByNamespace := make(map[string][]networkingv1.NetworkPolicy)
servicesByNamespace := make(map[string][]corev1.Service)

// Iterate over namespaces and store policies/services
for _, ns := range namespaces.Items {

Check failure on line 51 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

rangeValCopy: each iteration copies 328 bytes (consider pointers or indexing) (gocritic)

Check failure on line 51 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

rangeValCopy: each iteration copies 328 bytes (consider pointers or indexing) (gocritic)

Check failure on line 51 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

rangeValCopy: each iteration copies 328 bytes (consider pointers or indexing) (gocritic)

Check failure on line 51 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

rangeValCopy: each iteration copies 328 bytes (consider pointers or indexing) (gocritic)
fmt.Printf("Writing policies and services for namespace %s...\n", ns.Name)

// Get network policies
networkPolicies, err := clientset.NetworkingV1().NetworkPolicies(ns.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
fmt.Printf("Error getting network policies in namespace %s: %v\n", ns.Name, err)
continue
}
policiesByNamespace[ns.Name] = networkPolicies.Items

// Get services
services, err := clientset.CoreV1().Services(ns.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
fmt.Printf("Error getting services in namespace %s: %v\n", ns.Name, err)
continue
}
servicesByNamespace[ns.Name] = services.Items
}

fmt.Println("Migration Summary:")
fmt.Println("+------------------------------+-------------------------------+")
fmt.Printf("%-30s | %-30s \n", "Breaking Change", "No Impact / Safe to Migrate")
fmt.Println("+------------------------------+-------------------------------+")

// Check the endports of the network policies
checkEndportNetworkPolicies(policiesByNamespace)

fmt.Println("+------------------------------+-------------------------------+")

// Check the cidr of the network policies
checkCIDRNetworkPolicies(policiesByNamespace)

fmt.Println("+------------------------------+-------------------------------+")

// Check services that have externalTrafficPolicy!=Local
checkExternalTrafficPolicyServices(namespaces, servicesByNamespace, policiesByNamespace)

fmt.Println("+------------------------------+-------------------------------+")
fmt.Println("Please see aka.ms/azurenpmtocilium for instructions on how to evaluate/assess the above warnings marked by ❌.")
fmt.Println("NOTE: rerun this script if any modifications (create/update/delete) are made to services or policies.")
}

func checkEndportNetworkPolicies(policiesByNamespace map[string][]networkingv1.NetworkPolicy) {
networkPolicyWithEndport := false
for namespace, policies := range policiesByNamespace {
for _, policy := range policies {

Check failure on line 97 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 97 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 97 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 97 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)
foundEndPort := false
for _, egress := range policy.Spec.Egress {
for _, port := range egress.Ports {
if port.EndPort != nil {
foundEndPort = true
break // Exit egress.port loop
}
}
if foundEndPort {
break // Exit egress loop
}
}
if foundEndPort {
if !networkPolicyWithEndport {
fmt.Printf("%-30s | %-30s \n", "NetworkPolicy with endPort", "❌")
fmt.Println("Details:")
networkPolicyWithEndport = true
}
fmt.Printf("❌ Found NetworkPolicy: %s with endPort field in namespace: %s\n", policy.Name, namespace)
}
}
}
// Print no impact if no network policy has endport
if !networkPolicyWithEndport {
fmt.Printf("%-30s | %-30s \n", "NetworkPolicy with endPort", "✅")
}
}

func checkCIDRNetworkPolicies(policiesByNamespace map[string][]networkingv1.NetworkPolicy) {
networkPolicyWithCIDR := false
for namespace, policies := range policiesByNamespace {
for _, policy := range policies {

Check failure on line 129 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 129 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 129 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 129 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)
foundCIDRIngress := false
foundCIDREgress := false
// Check the ingress field for cidr
for _, ingress := range policy.Spec.Ingress {
for _, from := range ingress.From {
if from.IPBlock != nil {
if from.IPBlock.CIDR != "" {
foundCIDRIngress = true
break // Exit ingress.from.ipBlock loop
}
}
}
if foundCIDRIngress {
break // Exit ingress loop
}
}
// Print the network policy if it has an ingress cidr
if foundCIDRIngress {
if !networkPolicyWithCIDR {
fmt.Printf("%-30s | %-30s \n", "NetworkPolicy with cidr", "❌")
fmt.Println("Details:")
networkPolicyWithCIDR = true
}
fmt.Printf("❌ Found NetworkPolicy: %s with ingress cidr field in namespace: %s\n", policy.Name, namespace)
}
// Check the egress field for cidr
for _, egress := range policy.Spec.Egress {
for _, to := range egress.To {
if to.IPBlock != nil {
if to.IPBlock.CIDR != "" {
foundCIDREgress = true
break // Exit egress.to.ipBlock loop
}
}
}
if foundCIDREgress {
break // Exit egress loop
}
}
// Print the network policy if it has an egress cidr
if foundCIDREgress {
if !networkPolicyWithCIDR {
fmt.Printf("%-30s | %-30s \n", "NetworkPolicy with cidr", "❌")
fmt.Println("Details:")
networkPolicyWithCIDR = true
}
fmt.Printf("❌ Found NetworkPolicy: %s with egress cidr field in namespace: %s\n", policy.Name, namespace)
}
}
}
// Print no impact if no network policy has cidr
if !networkPolicyWithCIDR {
fmt.Printf("%-30s | %-30s \n", "NetworkPolicy with cidr", "✅")
}
}

func checkExternalTrafficPolicyServices(namespaces *corev1.NamespaceList, servicesByNamespace map[string][]corev1.Service, policiesByNamespace map[string][]networkingv1.NetworkPolicy) {
var servicesAtRisk, noSelectorServices, safeServices []string
for _, ns := range namespaces.Items {

Check failure on line 188 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

rangeValCopy: each iteration copies 328 bytes (consider pointers or indexing) (gocritic)

Check failure on line 188 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

rangeValCopy: each iteration copies 328 bytes (consider pointers or indexing) (gocritic)

Check failure on line 188 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

rangeValCopy: each iteration copies 328 bytes (consider pointers or indexing) (gocritic)

Check failure on line 188 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

rangeValCopy: each iteration copies 328 bytes (consider pointers or indexing) (gocritic)
// Check 0: are there ingress policies in the namespace?
if !hasIngressPolicies(policiesByNamespace[ns.Name]) {
fmt.Printf("Skipping namespace %s as it has no ingress NetworkPolicy rules.\n", ns.Name)
continue
}
serviceListAtNamespace := servicesByNamespace[ns.Name]
fmt.Printf("Checking NetworkPolicy targeting services with externalTrafficPolicy=Cluster in namespace %s...\n", ns.Name)

// Check 1: are there services with externalTrafficPolicy=Cluster (applicable if Type=NodePort or Type=LoadBalancer)
for _, service := range serviceListAtNamespace {

Check failure on line 198 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

rangeValCopy: each iteration copies 592 bytes (consider pointers or indexing) (gocritic)

Check failure on line 198 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

rangeValCopy: each iteration copies 592 bytes (consider pointers or indexing) (gocritic)

Check failure on line 198 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

rangeValCopy: each iteration copies 592 bytes (consider pointers or indexing) (gocritic)

Check failure on line 198 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

rangeValCopy: each iteration copies 592 bytes (consider pointers or indexing) (gocritic)
if service.Spec.Type == v1.ServiceTypeLoadBalancer || service.Spec.Type == v1.ServiceTypeNodePort {
servicePorts := []string{}
// get the Port and Protocol of the service
for _, port := range service.Spec.Ports {
servicePorts = append(servicePorts, fmt.Sprintf("%d/%s", port.Port, port.Protocol))
}
externalTrafficPolicy := service.Spec.ExternalTrafficPolicy
// If the service has externalTrafficPolicy is set to "Cluster" add it to the servicesAtRisk list
if externalTrafficPolicy == v1.ServiceExternalTrafficPolicyTypeCluster {
servicesAtRisk = append(servicesAtRisk, fmt.Sprintf("%s/%s", ns.Name, service.Name))
// If the service has no selector add it to the noSelectorServices list
if service.Spec.Selector == nil {
noSelectorServices = append(noSelectorServices, fmt.Sprintf("%s/%s", ns.Name, service.Name))
} else {
// Check if are there services with selector that match the network policy
checkServiceRisk(service, ns.Name, servicePorts, policiesByNamespace[ns.Name], safeServices)
}
}
}
}
}
}

func hasIngressPolicies(policies []networkingv1.NetworkPolicy) bool {
for _, policy := range policies {

Check failure on line 223 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 223 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 223 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 223 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)
for _, ingress := range policy.Spec.Ingress {
if len(ingress.From) > 0 {
return true
}
}
}
return false
}

func checkServiceRisk(service v1.Service, namespace string, servicePorts []string, policiesListAtNamespace []networkingv1.NetworkPolicy, safeServices []string) {
for _, policy := range policiesListAtNamespace {

Check failure on line 234 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 234 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 234 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)

Check failure on line 234 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

rangeValCopy: each iteration copies 368 bytes (consider pointers or indexing) (gocritic)
for _, ingress := range policy.Spec.Ingress {
// If there are no ingress from and ports in the policy check; check if the service is safe
if len(ingress.From) == 0 && len(ingress.Ports) == 0 {
if matchSelector(&metav1.LabelSelector{MatchLabels: service.Spec.Selector}, &policy.Spec.PodSelector) {
safeServices = append(safeServices, fmt.Sprintf("%s/%s", namespace, service.Name))
return
}
}
// If there are no ingress from but there are ports in the policy; check if the service is safe
if len(ingress.From) == 0 && len(ingress.Ports) > 0 {
if matchSelector(&metav1.LabelSelector{MatchLabels: service.Spec.Selector}, &policy.Spec.PodSelector) {
matchingPorts := []string{}
for _, port := range ingress.Ports {
matchingPorts = append(matchingPorts, fmt.Sprintf("%d/%s", port.Port.IntVal, string(*port.Protocol)))
}
for _, sevicePort := range servicePorts {
if contains(matchingPorts, sevicePort) {
safeServices = append(safeServices, fmt.Sprintf("%s/%s", namespace, service.Name))
return
}
}
}
}
}
}
}

func matchSelector(serviceSelector *metav1.LabelSelector, policyPodSelector *metav1.LabelSelector) bool {

Check failure on line 262 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

paramTypeCombine: func(serviceSelector *metav1.LabelSelector, policyPodSelector *metav1.LabelSelector) bool could be replaced with func(serviceSelector, policyPodSelector *metav1.LabelSelector) bool (gocritic)

Check failure on line 262 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

paramTypeCombine: func(serviceSelector *metav1.LabelSelector, policyPodSelector *metav1.LabelSelector) bool could be replaced with func(serviceSelector, policyPodSelector *metav1.LabelSelector) bool (gocritic)

Check failure on line 262 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

paramTypeCombine: func(serviceSelector *metav1.LabelSelector, policyPodSelector *metav1.LabelSelector) bool could be replaced with func(serviceSelector, policyPodSelector *metav1.LabelSelector) bool (gocritic)

Check failure on line 262 in tools/azure-npm-to-cilium-validator.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

paramTypeCombine: func(serviceSelector *metav1.LabelSelector, policyPodSelector *metav1.LabelSelector) bool could be replaced with func(serviceSelector, policyPodSelector *metav1.LabelSelector) bool (gocritic)
if serviceSelector == nil || policyPodSelector == nil {
return false
}

// Get the labels from the pod selector in the network policy and selector in the service
policyPodLabels := policyPodSelector.MatchLabels
serviceLabels := serviceSelector.MatchLabels

// If the labels in the policy pod selector are present in the service selector then return true
if checkPolicyMatchServiceLabels(serviceLabels, policyPodLabels) {
return true
}

// Get the expressions from the pod selector in the network policy and selector in the service
policyPodExpressions := policyPodSelector.MatchExpressions
serviceExpressions := serviceSelector.MatchExpressions

for _, serviceExpression := range serviceExpressions {
key := serviceExpression.Key
operator := serviceExpression.Operator
values := serviceExpression.Values

// Check if any of the policy expressions match the service expression
matchingExpression := metav1.LabelSelectorRequirement{}
foundMatchingExpression := false
for _, policyExpression := range policyPodExpressions {
if policyExpression.Key == key {
matchingExpression = policyExpression
foundMatchingExpression = true
break
}
}
// If the expression is not found then return false
if !foundMatchingExpression {
return false
}

// Check if the values in the service expression are present in the matching policy expression
if operator == metav1.LabelSelectorOpIn {
for _, value := range values {
if !contains(matchingExpression.Values, value) {
return false
}
}
} else if operator == metav1.LabelSelectorOpNotIn {
for _, value := range values {
if contains(matchingExpression.Values, value) {
return false
}
}
}
}

return true
}

func checkPolicyMatchServiceLabels(serviceLabels, policyPodLabels map[string]string) bool {
// Count the number of labels that match
matchLabelCount := 0
for policyKey, policyValue := range policyPodLabels {
for serviceKey, serviceValue := range serviceLabels {
if serviceKey == policyKey && serviceValue == policyValue {
matchLabelCount++
}
}
}

// If the number of labels that match is equal to the number of labels in the policy pod selector then return true
// as that means all the match labels in the policy pod selector are present in the service selector
if matchLabelCount == len(policyPodLabels) {
return true
}

return false
}
Loading
Loading