diff --git a/tools/azure-npm-to-cilium-validator/README.md b/tools/azure-npm-to-cilium-validator/README.md new file mode 100644 index 0000000000..c7697f28ef --- /dev/null +++ b/tools/azure-npm-to-cilium-validator/README.md @@ -0,0 +1,55 @@ +# Azure NPM to Cilium Validator + +This tool validates the migration from Azure NPM to Cilium. It will provide information on if you can safely proceed with a manual update from Azure NPM to Cilium. It will verify the following checks to determine if the cluster is safe to migrate. + +- NetworkPolicy with endPort +- NetworkPolicy with ipBlock +- NetworkPolicy with named Ports +- NetworkPolicy with Egress Policies (not Allow All) +- Disruption for some Services (LoadBalancer or NodePort) with externalTrafficPolicy=Cluster + +## Prerequisites + +- Go 1.16 or later +- A Kubernetes cluster with Azure NPM installed + +## Installation + +Clone the repository and navigate to the tool directory: + +```bash +git clone https://github.com/Azure/azure-container-networking.git +cd azure-container-networking/tools/azure-npm-to-cilium-validator +``` + +## Setting Up Dependencies + +Initialize the Go module and download dependencies: + +```bash +go mod tidy && go mod vendor +``` + +## Running the Tool + +Run the following command with the path to your kube config file with the cluster you want to validate. + +```bash +go run azure-npm-to-cilium-validator.go --kubeconfig ~/.kube/config +``` + +This will execute the validator and print the migration summary. You can use the `--detailed-migration-summary` flag to get more information on flagged network policies and services as well as total number of network policies, services, and pods on the cluster targeted. + +```bash +go run azure-npm-to-cilium-validator.go --kubeconfig ~/.kube/config --detailed-migration-summary +``` + +## Running Tests + +To run the tests for the Azure NPM to Cilium Validator, use the following command in the azure-npm-to-cilium-validator directory: + +```bash +go test . +``` + +This will execute all the test files in azure-npm-to-cilium-validator_test.go and provide a summary of the test results. diff --git a/tools/azure-npm-to-cilium-validator/azure-npm-to-cilium-validator.go b/tools/azure-npm-to-cilium-validator/azure-npm-to-cilium-validator.go new file mode 100644 index 0000000000..ed5983bf5b --- /dev/null +++ b/tools/azure-npm-to-cilium-validator/azure-npm-to-cilium-validator.go @@ -0,0 +1,646 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "time" + + "github.com/Azure/azure-container-networking/npm/metrics" + "github.com/olekukonko/tablewriter" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/klog/v2" +) + +// Note: The operationID is set to a high number so it doesn't conflict with other telemetry +const scriptMetricOperationID = 10000 + +// Use this tool to validate if your cluster is ready to migrate from Azure Network Policy Manager (NPM) to Cilium. +func main() { + // Parse the kubeconfig flag + kubeconfig := flag.String("kubeconfig", "~/.kube/config", "absolute path to the kubeconfig file") + detailedMigrationSummary := flag.Bool("detailed-migration-summary", false, "display flagged network polices/services and total cluster resource count") + 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) + } + + // Copy namespaces.Items into a slice of pointers + namespacePointers := make([]*corev1.Namespace, len(namespaces.Items)) + for i := range namespaces.Items { + namespacePointers[i] = &namespaces.Items[i] + } + + // Store network policies and services in maps + policiesByNamespace := make(map[string][]*networkingv1.NetworkPolicy) + servicesByNamespace := make(map[string][]*corev1.Service) + podsByNamespace := make(map[string][]*corev1.Pod) + + // Iterate over namespaces and store policies/services + for _, ns := range namespacePointers { + // 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] = make([]*networkingv1.NetworkPolicy, len(networkPolicies.Items)) + for i := range networkPolicies.Items { + policiesByNamespace[ns.Name][i] = &networkPolicies.Items[i] + } + + // 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] = make([]*corev1.Service, len(services.Items)) + for i := range services.Items { + servicesByNamespace[ns.Name][i] = &services.Items[i] + } + + // Get pods + pods, err := clientset.CoreV1().Pods(ns.Name).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + fmt.Printf("Error getting pods in namespace %s: %v\n", ns.Name, err) + continue + } + podsByNamespace[ns.Name] = make([]*corev1.Pod, len(pods.Items)) + for i := range pods.Items { + podsByNamespace[ns.Name][i] = &pods.Items[i] + } + } + + // Create telemetry handle + // Note: npmVersionNum and imageVersion telemetry is not needed for this tool so they are set to abitrary values + err = metrics.CreateTelemetryHandle(0, "NPM-script-v0.0.1", "014c22bd-4107-459e-8475-67909e96edcb") + + if err != nil { + klog.Infof("CreateTelemetryHandle failed with error %v. AITelemetry is not initialized.", err) + } + + // Print the migration summary + printMigrationSummary(detailedMigrationSummary, namespaces, policiesByNamespace, servicesByNamespace, podsByNamespace) +} + +func printMigrationSummary( + detailedMigrationSummary *bool, + namespaces *corev1.NamespaceList, + policiesByNamespace map[string][]*networkingv1.NetworkPolicy, + servicesByNamespace map[string][]*corev1.Service, + podsByNamespace map[string][]*corev1.Pod, +) { + // Get the network policies with endports + ingressEndportNetworkPolicy, egressEndportNetworkPolicy := getEndportNetworkPolicies(policiesByNamespace) + + // Send endPort telemetry + metrics.SendLog(scriptMetricOperationID, fmt.Sprintf("[migration script] Found %d network policies with endPort", len(ingressEndportNetworkPolicy)+len(egressEndportNetworkPolicy)), metrics.DonotPrint) + + // Get the network policies with cidr + ingressPoliciesWithCIDR, egressPoliciesWithCIDR := getCIDRNetworkPolicies(policiesByNamespace) + + // Send cidr telemetry + metrics.SendLog(scriptMetricOperationID, fmt.Sprintf("[migration script] Found %d network policies with CIDR", len(ingressPoliciesWithCIDR)+len(egressPoliciesWithCIDR)), metrics.DonotPrint) + + // Get the named port + ingressPoliciesWithNamedPort, egressPoliciesWithNamedPort := getNamedPortPolicies(policiesByNamespace) + + // Send named port telemetry + metrics.SendLog(scriptMetricOperationID, fmt.Sprintf("[migration script] Found %d network policies with named port", len(ingressPoliciesWithNamedPort)+len(egressPoliciesWithNamedPort)), metrics.DonotPrint) + + // Get the network policies with egress (except not egress allow all) + egressPolicies := getEgressPolicies(policiesByNamespace) + + // Send egress telemetry + metrics.SendLog(scriptMetricOperationID, fmt.Sprintf("[migration script] Found %d network policies with egress", len(egressPolicies)), metrics.DonotPrint) + + // Get services that have externalTrafficPolicy!=Local that are unsafe (might have traffic disruption) + unsafeServices := getUnsafeExternalTrafficPolicyClusterServices(namespaces, servicesByNamespace, policiesByNamespace) + + // Send unsafe services telemetry + metrics.SendLog(scriptMetricOperationID, fmt.Sprintf("[migration script] Found %d services with externalTrafficPolicy=Cluster", len(unsafeServices)), metrics.DonotPrint) + + unsafeNetworkPolicesInCluster := false + unsafeServicesInCluster := false + if len(ingressEndportNetworkPolicy) > 0 || len(egressEndportNetworkPolicy) > 0 || + len(ingressPoliciesWithCIDR) > 0 || len(egressPoliciesWithCIDR) > 0 || + len(ingressPoliciesWithNamedPort) > 0 || len(egressPoliciesWithNamedPort) > 0 || + len(egressPolicies) > 0 { + unsafeNetworkPolicesInCluster = true + } + if len(unsafeServices) > 0 { + unsafeServicesInCluster = true + } + + if unsafeNetworkPolicesInCluster || unsafeServicesInCluster { + // Send cluster unsafe telemetry + metrics.SendLog(scriptMetricOperationID, "[migration script] Fails some checks. Unsafe to migrate this cluster", metrics.DonotPrint) + } else { + // Send cluster safe telemetry + metrics.SendLog(scriptMetricOperationID, "[migration script] Passes all checks. Safe to migrate this cluster", metrics.DonotPrint) + } + + // Close the metrics before table is rendered and wait one second to prevent formatting issues + metrics.Close() + time.Sleep(time.Second) + + // Print the migration summary table + renderMigrationSummaryTable(ingressEndportNetworkPolicy, egressEndportNetworkPolicy, ingressPoliciesWithCIDR, egressPoliciesWithCIDR, ingressPoliciesWithNamedPort, egressPoliciesWithNamedPort, egressPolicies, unsafeServices) + + // Print the flagged resource table and cluster resource table if the detailed-report flag is set + if *detailedMigrationSummary { + if unsafeNetworkPolicesInCluster { + renderFlaggedNetworkPolicyTable(ingressEndportNetworkPolicy, egressEndportNetworkPolicy, ingressPoliciesWithCIDR, egressPoliciesWithCIDR, ingressPoliciesWithNamedPort, egressPoliciesWithNamedPort, egressPolicies) + } + if unsafeServicesInCluster { + renderFlaggedServiceTable(unsafeServices) + } + renderClusterResourceTable(policiesByNamespace, servicesByNamespace, podsByNamespace) + } + + // Print if the cluster is safe to migrate + if unsafeNetworkPolicesInCluster || unsafeServicesInCluster { + fmt.Println("\n\033[31m✘ Review above issues before migration.\033[0m") + fmt.Println("Please see \033[32maka.ms/azurenpmtocilium\033[0m 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.") + } else { + fmt.Println("\n\033[32m✔ Safe to migrate this cluster.\033[0m") + fmt.Println("For more details please see \033[32maka.ms/azurenpmtocilium\033[0m.") + } +} + +func renderMigrationSummaryTable( + ingressEndportNetworkPolicy, + egressEndportNetworkPolicy, + ingressPoliciesWithCIDR, + egressPoliciesWithCIDR, + ingressPoliciesWithNamedPort, + egressPoliciesWithNamedPort, + egressPolicies, + unsafeServices []string, +) { + migrationSummarytable := tablewriter.NewWriter(os.Stdout) + migrationSummarytable.SetHeader([]string{"Breaking Change", "Upgrade compatibility", "Count"}) + migrationSummarytable.SetRowLine(true) + if len(ingressEndportNetworkPolicy) == 0 && len(egressEndportNetworkPolicy) == 0 { + migrationSummarytable.Append([]string{"NetworkPolicy with endPort", "✅", fmt.Sprintf("0")}) + } else { + migrationSummarytable.Append([]string{"NetworkPolicy with endPort", "❌", fmt.Sprintf("%d", len(ingressEndportNetworkPolicy)+len(egressEndportNetworkPolicy))}) + } + if len(ingressPoliciesWithCIDR) == 0 && len(egressPoliciesWithCIDR) == 0 { + migrationSummarytable.Append([]string{"NetworkPolicy with CIDR", "✅", "0"}) + } else { + migrationSummarytable.Append([]string{"NetworkPolicy with CIDR", "❌", fmt.Sprintf("%d", len(ingressPoliciesWithCIDR)+len(egressPoliciesWithCIDR))}) + } + if len(ingressPoliciesWithNamedPort) == 0 && len(egressPoliciesWithNamedPort) == 0 { + migrationSummarytable.Append([]string{"NetworkPolicy with Named Port", "✅", "0"}) + } else { + migrationSummarytable.Append([]string{"NetworkPolicy with Named Port", "❌", fmt.Sprintf("%d", len(ingressPoliciesWithNamedPort)+len(egressPoliciesWithNamedPort))}) + } + if len(egressPolicies) == 0 { + migrationSummarytable.Append([]string{"NetworkPolicy with Egress (Not Allow All Egress)", "✅", "0"}) + } else { + migrationSummarytable.Append([]string{"NetworkPolicy with Egress (Not Allow All Egress)", "❌", fmt.Sprintf("%d", len(egressPolicies))}) + } + if len(unsafeServices) == 0 { + migrationSummarytable.Append([]string{"Disruption for some Services with externalTrafficPolicy=Cluster", "✅", "0"}) + } else { + migrationSummarytable.Append([]string{"Disruption for some Services with externalTrafficPolicy=Cluster", "❌", fmt.Sprintf("%d", len(unsafeServices))}) + } + + fmt.Println("\nMigration Summary:") + migrationSummarytable.Render() +} + +func renderFlaggedNetworkPolicyTable( + ingressEndportNetworkPolicy, + egressEndportNetworkPolicy, + ingressPoliciesWithCIDR, + egressPoliciesWithCIDR, + ingressPoliciesWithNamedPort, + egressPoliciesWithNamedPort, + egressPolicies []string, +) { + flaggedResourceTable := tablewriter.NewWriter(os.Stdout) + flaggedResourceTable.SetHeader([]string{"Network Policy", "NetworkPolicy with endPort", "NetworkPolicy with CIDR", "NetworkPolicy with Named Port", "NetworkPolicy with Egress (Not Allow All Egress)"}) + flaggedResourceTable.SetRowLine(true) + + // Create a map to store the policies and their flags + policyFlags := make(map[string][]string) + + // Helper function to add a flag to a policy + addFlag := func(policy string, flag string) { + if _, exists := policyFlags[policy]; !exists { + policyFlags[policy] = []string{"✅", "✅", "✅", "✅"} + } + switch flag { + case "ingressEndPort": + policyFlags[policy][0] = "❌ (ingress)" + case "egressEndPort": + policyFlags[policy][0] = "❌ (egress)" + case "ingressCIDR": + policyFlags[policy][1] = "❌ (ingress)" + case "egressCIDR": + policyFlags[policy][1] = "❌ (egress)" + case "ingressNamedPort": + policyFlags[policy][2] = "❌ (ingress)" + case "egressNamedPort": + policyFlags[policy][2] = "❌ (egress)" + case "Egress": + policyFlags[policy][3] = "❌" + } + } + + // Add flags for each policy + for _, policy := range ingressEndportNetworkPolicy { + addFlag(policy, "ingressEndPort") + } + for _, policy := range egressEndportNetworkPolicy { + addFlag(policy, "egressEndPort") + } + for _, policy := range ingressPoliciesWithCIDR { + addFlag(policy, "ingressCIDR") + } + for _, policy := range egressPoliciesWithCIDR { + addFlag(policy, "egressCIDR") + } + for _, policy := range ingressPoliciesWithNamedPort { + addFlag(policy, "ingressNamedPort") + } + for _, policy := range egressPoliciesWithNamedPort { + addFlag(policy, "egressNamedPort") + } + for _, policy := range egressPolicies { + addFlag(policy, "Egress") + } + + // Append the policies and their flags to the table + for policy, flags := range policyFlags { + flaggedResourceTable.Append([]string{policy, flags[0], flags[1], flags[2], flags[3]}) + } + + fmt.Println("\nFlagged Network Policies:") + flaggedResourceTable.Render() +} + +func renderFlaggedServiceTable(unsafeServices []string) { + fmt.Println("\nFlagged Services:") + flaggedResourceTable := tablewriter.NewWriter(os.Stdout) + flaggedResourceTable.SetHeader([]string{"Service", "Disruption for some Services with externalTrafficPolicy=Cluster"}) + flaggedResourceTable.SetRowLine(true) + for _, service := range unsafeServices { + flaggedResourceTable.Append([]string{fmt.Sprintf("%s", service), "❌"}) + } + flaggedResourceTable.Render() +} + +func renderClusterResourceTable(policiesByNamespace map[string][]*networkingv1.NetworkPolicy, servicesByNamespace map[string][]*corev1.Service, podsByNamespace map[string][]*corev1.Pod) { + resourceTable := tablewriter.NewWriter(os.Stdout) + resourceTable.SetHeader([]string{"Resource", "Count"}) + resourceTable.SetRowLine(true) + + // Count the total number of policies + totalPolicies := 0 + for _, policies := range policiesByNamespace { + totalPolicies += len(policies) + } + resourceTable.Append([]string{"NetworkPolicy", fmt.Sprintf("%d", totalPolicies)}) + + // Count the total number of services + totalServices := 0 + for _, services := range servicesByNamespace { + totalServices += len(services) + } + resourceTable.Append([]string{"Service", fmt.Sprintf("%d", totalServices)}) + + // Count the total number of pods + totalPods := 0 + for _, pods := range podsByNamespace { + totalPods += len(pods) + } + resourceTable.Append([]string{"Pod", fmt.Sprintf("%d", totalPods)}) + + fmt.Println("\nCluster Resources:") + resourceTable.Render() +} + +func getEndportNetworkPolicies(policiesByNamespace map[string][]*networkingv1.NetworkPolicy) (ingressPoliciesWithEndport, egressPoliciesWithEndport []string) { + for namespace, policies := range policiesByNamespace { + for _, policy := range policies { + // Check the ingress field for endport + for _, ingress := range policy.Spec.Ingress { + foundEndPort := checkEndportInPolicyRules(ingress.Ports) + if foundEndPort { + ingressPoliciesWithEndport = append(ingressPoliciesWithEndport, fmt.Sprintf("%s/%s", namespace, policy.Name)) + break + } + } + // Check the egress field for endport + for _, egress := range policy.Spec.Egress { + foundEndPort := checkEndportInPolicyRules(egress.Ports) + if foundEndPort { + egressPoliciesWithEndport = append(egressPoliciesWithEndport, fmt.Sprintf("%s/%s", namespace, policy.Name)) + break + } + } + } + } + return ingressPoliciesWithEndport, egressPoliciesWithEndport +} + +func checkEndportInPolicyRules(ports []networkingv1.NetworkPolicyPort) bool { + for _, port := range ports { + if port.EndPort != nil { + return true + } + } + return false +} + +func getCIDRNetworkPolicies(policiesByNamespace map[string][]*networkingv1.NetworkPolicy) (ingressPoliciesWithCIDR, egressPoliciesWithCIDR []string) { + for namespace, policies := range policiesByNamespace { + for _, policy := range policies { + // Check the ingress field for cidr + for _, ingress := range policy.Spec.Ingress { + foundCIDRIngress := checkCIDRInPolicyRules(ingress.From) + if foundCIDRIngress { + ingressPoliciesWithCIDR = append(ingressPoliciesWithCIDR, fmt.Sprintf("%s/%s", namespace, policy.Name)) + break + } + } + // Check the egress field for cidr + for _, egress := range policy.Spec.Egress { + foundCIDREgress := checkCIDRInPolicyRules(egress.To) + if foundCIDREgress { + egressPoliciesWithCIDR = append(egressPoliciesWithCIDR, fmt.Sprintf("%s/%s", namespace, policy.Name)) + break + } + } + } + } + return ingressPoliciesWithCIDR, egressPoliciesWithCIDR +} + +// Check for CIDR in ingress or egress rules +func checkCIDRInPolicyRules(to []networkingv1.NetworkPolicyPeer) bool { + for _, toRule := range to { + if toRule.IPBlock != nil && toRule.IPBlock.CIDR != "" { + return true + } + } + return false +} + +func getNamedPortPolicies(policiesByNamespace map[string][]*networkingv1.NetworkPolicy) (ingressPoliciesWithNamedPort, egressPoliciesWithNamedPort []string) { + for namespace, policies := range policiesByNamespace { + for _, policy := range policies { + // Check the ingress field for named port + for _, ingress := range policy.Spec.Ingress { + if checkNamedPortInPolicyRules(ingress.Ports) { + ingressPoliciesWithNamedPort = append(ingressPoliciesWithNamedPort, fmt.Sprintf("%s/%s", namespace, policy.Name)) + break + } + } + // Check the egress field for named port + for _, egress := range policy.Spec.Egress { + if checkNamedPortInPolicyRules(egress.Ports) { + egressPoliciesWithNamedPort = append(egressPoliciesWithNamedPort, fmt.Sprintf("%s/%s", namespace, policy.Name)) + break + } + } + } + } + return ingressPoliciesWithNamedPort, egressPoliciesWithNamedPort +} + +func checkNamedPortInPolicyRules(ports []networkingv1.NetworkPolicyPort) bool { + for _, port := range ports { + // If port is a string it is a named port + if port.Port.Type == intstr.String { + return true + } + } + return false +} + +func getEgressPolicies(policiesByNamespace map[string][]*networkingv1.NetworkPolicy) []string { + var egressPolicies []string + for namespace, policies := range policiesByNamespace { + for _, policy := range policies { + for _, policyType := range policy.Spec.PolicyTypes { + // If the policy is an egress type and has no egress field it is an deny all flag it + if policyType == networkingv1.PolicyTypeEgress && len(policy.Spec.Egress) == 0 { + egressPolicies = append(egressPolicies, fmt.Sprintf("%s/%s", namespace, policy.Name)) + break + } + } + for _, egress := range policy.Spec.Egress { + // If the policy has a egress field thats not an egress allow all flag it + if len(egress.To) > 0 || len(egress.Ports) > 0 { + egressPolicies = append(egressPolicies, fmt.Sprintf("%s/%s", namespace, policy.Name)) + break + } + } + } + } + return egressPolicies +} + +func getUnsafeExternalTrafficPolicyClusterServices( + namespaces *corev1.NamespaceList, + servicesByNamespace map[string][]*corev1.Service, + policiesByNamespace map[string][]*networkingv1.NetworkPolicy, +) (unsafeServices []string) { + var riskServices, safeServices []string + + for i := range namespaces.Items { + namespace := &namespaces.Items[i] + // Check if are there ingress policies in the namespace if not skip + policyListAtNamespace := policiesByNamespace[namespace.Name] + if !hasIngressPolicies(policyListAtNamespace) { + continue + } + serviceListAtNamespace := servicesByNamespace[namespace.Name] + + // Check if are there services with externalTrafficPolicy=Cluster (applicable if Type=NodePort or Type=LoadBalancer) + for _, service := range serviceListAtNamespace { + if service.Spec.Type == corev1.ServiceTypeLoadBalancer || service.Spec.Type == corev1.ServiceTypeNodePort { + externalTrafficPolicy := service.Spec.ExternalTrafficPolicy + // If the service has externalTrafficPolicy is set to "Cluster" add it to the riskServices list (ExternalTrafficPolicy: "" defaults to Cluster) + if externalTrafficPolicy != corev1.ServiceExternalTrafficPolicyTypeLocal { + // Any service with externalTrafficPolicy=Cluster is at risk so need to elimate any services that are incorrectly flagged + riskServices = append(riskServices, fmt.Sprintf("%s/%s", namespace.Name, service.Name)) + // Check if are there services with selector that are allowed by a network policy that can be safely migrated + if checkNoServiceRisk(service, policyListAtNamespace) { + safeServices = append(safeServices, fmt.Sprintf("%s/%s", namespace.Name, service.Name)) + } + } + } + } + } + + // Remove all the safe services from the services at risk + unsafeServices = difference(riskServices, safeServices) + return unsafeServices +} + +func hasIngressPolicies(policies []*networkingv1.NetworkPolicy) bool { + // Check if any policy is ingress (including allow all and deny all) + for _, policy := range policies { + for _, policyType := range policy.Spec.PolicyTypes { + if policyType == networkingv1.PolicyTypeIngress { + return true + } + } + } + return false +} + +func checkNoServiceRisk(service *corev1.Service, policiesListAtNamespace []*networkingv1.NetworkPolicy) bool { + for _, policy := range policiesListAtNamespace { + // Skips deny all policies as they do not have any ingress rules + for _, ingress := range policy.Spec.Ingress { + // Check for each policy label that that label is present in the service labels meaning the service is being targeted by the policy + if checkPolicyMatchServiceLabels(service.Spec.Selector, policy.Spec.PodSelector) { + // Check if there is an allow all ingress policy as the policy allows all services in the namespace + if len(ingress.From) == 0 && len(ingress.Ports) == 0 { + return true + } + // If there are no ingress from but there are ports in the policy; check if the service is safe + if len(ingress.From) == 0 { + // If the policy targets all pods (allow all) or only pods that are in the service selector, check if traffic is allowed to all the service's target ports + // Note: ingress.Ports.protocol will never be nil if len(ingress.Ports) is greater than 0. It defaults to "TCP" if not set + // Note: for loadbalancer services the health probe always hits the service target ports + if checkServiceTargetPortMatchPolicyPorts(service.Spec.Ports, ingress.Ports) { + return true + } + } + } + } + } + return false +} + +func checkPolicyMatchServiceLabels(serviceLabels map[string]string, podSelector metav1.LabelSelector) bool { + // Check if there is an target all ingress policy with empty selectors if so the service is safe + if len(podSelector.MatchLabels) == 0 && len(podSelector.MatchExpressions) == 0 { + return true + } + + // Return false if the policy has matchExpressions + // Note: does not check matchExpressions. It will only validate based on matchLabels + if len(podSelector.MatchExpressions) > 0 { + return false + } + + // Return false if the policy has more labels than the service + if len(podSelector.MatchLabels) > len(serviceLabels) { + return false + } + + // Check for each policy label that that label is present in the service labels + // Note: a policy with no matchLabels is an allow all policy + for policyKey, policyValue := range podSelector.MatchLabels { + matchedPolicyLabelToServiceLabel := false + for serviceKey, serviceValue := range serviceLabels { + if policyKey == serviceKey && policyValue == serviceValue { + matchedPolicyLabelToServiceLabel = true + break + } + } + if !matchedPolicyLabelToServiceLabel { + return false + } + } + return true +} + +func checkServiceTargetPortMatchPolicyPorts(servicePorts []corev1.ServicePort, policyPorts []networkingv1.NetworkPolicyPort) bool { + // If the service has no ports then it is at risk + if len(servicePorts) == 0 { + return false + } + + for _, servicePort := range servicePorts { + // If the target port is a string then it is a named port and service is at risk + if servicePort.TargetPort.Type == intstr.String { + return false + } + + // If the target port is 0 then it is at risk as Cilium treats port 0 in a special way + if servicePort.TargetPort.IntValue() == 0 { + return false + } + + // Check if all the services target ports are in the policies ingress ports + matchedserviceTargetPortToPolicyPort := false + for _, policyPort := range policyPorts { + // If the policy only has a protocol check the protocol against the service + // Note: if a network policy on NPM just targets a protocol it will allow all traffic with containing that protocol (ignoring the port) + // Note: an empty protocols default to "TCP" for both policies and services + if policyPort.Port == nil && policyPort.Protocol != nil { + if string(servicePort.Protocol) == string(*policyPort.Protocol) { + matchedserviceTargetPortToPolicyPort = true + break + } + continue + } + // If the port is a string then it is a named port and it cant be evaluated + if policyPort.Port.Type == intstr.String { + continue + } + // Cilium treats port 0 in a special way so skip policys allowing port 0 + if int(policyPort.Port.IntVal) == 0 { + continue + } + // Check if the service target port and protocol matches the policy port and protocol + // Note: that the service target port will never been undefined as it defaults to port which is a required field when Ports is defined + // Note: an empty protocols default to "TCP" for both policies and services + if servicePort.TargetPort.IntValue() == int(policyPort.Port.IntVal) && string(servicePort.Protocol) == string(*policyPort.Protocol) { + matchedserviceTargetPortToPolicyPort = true + break + } + } + if !matchedserviceTargetPortToPolicyPort { + return false + } + } + return true +} + +func difference(slice1, slice2 []string) []string { + m := make(map[string]struct{}) + for _, s := range slice2 { + m[s] = struct{}{} + } + var diff []string + for _, s := range slice1 { + if _, ok := m[s]; !ok { + diff = append(diff, s) + } + } + return diff +} diff --git a/tools/azure-npm-to-cilium-validator/azure-npm-to-cilium-validator_test.go b/tools/azure-npm-to-cilium-validator/azure-npm-to-cilium-validator_test.go new file mode 100644 index 0000000000..3e1f37e0f8 --- /dev/null +++ b/tools/azure-npm-to-cilium-validator/azure-npm-to-cilium-validator_test.go @@ -0,0 +1,2904 @@ +package main + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + intstr "k8s.io/apimachinery/pkg/util/intstr" +) + +// Test function for getEndportNetworkPolicies +func TestGetEndportNetworkPolicies(t *testing.T) { + tests := []struct { + name string + policiesByNamespace map[string][]*networkingv1.NetworkPolicy + expectedIngressEndportPolicies []string + expectedEgressEndportPolicies []string + }{ + { + name: "No policies", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{}, + expectedIngressEndportPolicies: []string{}, + expectedEgressEndportPolicies: []string{}, + }, + { + name: "No endport in policies", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "no-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80))}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressEndportPolicies: []string{}, + expectedEgressEndportPolicies: []string{}, + }, + { + name: "Ingress endport in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressEndportPolicies: []string{"namespace1/ingress-endport-policy"}, + expectedEgressEndportPolicies: []string{}, + }, + { + name: "Egress endport in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressEndportPolicies: []string{}, + expectedEgressEndportPolicies: []string{"namespace1/egress-endport-policy"}, + }, + { + name: "Both ingress and egress endport in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-and-egress-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressEndportPolicies: []string{"namespace1/ingress-and-egress-endport-policy"}, + expectedEgressEndportPolicies: []string{"namespace1/ingress-and-egress-endport-policy"}, + }, + { + name: "Multiple polices in a namespace with ingress or egress endport", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-and-egress-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressEndportPolicies: []string{"namespace1/ingress-endport-policy", "namespace1/ingress-and-egress-endport-policy"}, + expectedEgressEndportPolicies: []string{"namespace1/egress-endport-policy", "namespace1/ingress-and-egress-endport-policy"}, + }, + { + name: "Multiple polices in multiple namespaces with ingress or egress endport or no endport", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-and-egress-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80)), EndPort: int32Ptr(90)}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "no-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80))}, + }, + }, + }, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "no-endport-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80))}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressEndportPolicies: []string{"namespace1/ingress-endport-policy", "namespace1/ingress-and-egress-endport-policy"}, + expectedEgressEndportPolicies: []string{"namespace1/ingress-and-egress-endport-policy", "namespace2/egress-endport-policy"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ingressPolicies, egressPolicies := getEndportNetworkPolicies(tt.policiesByNamespace) + if !equal(ingressPolicies, tt.expectedIngressEndportPolicies) { + t.Errorf("expected ingress policies %v, got %v", tt.expectedIngressEndportPolicies, ingressPolicies) + } + if !equal(egressPolicies, tt.expectedEgressEndportPolicies) { + t.Errorf("expected egress policies %v, got %v", tt.expectedEgressEndportPolicies, egressPolicies) + } + }) + } +} + +func TestGetCIDRNetworkPolicies(t *testing.T) { + tests := []struct { + name string + policiesByNamespace map[string][]*networkingv1.NetworkPolicy + expectedIngressCIDRPolicies []string + expectedEgressCIDRPolicies []string + }{ + { + name: "No policies", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{}, + expectedIngressCIDRPolicies: []string{}, + expectedEgressCIDRPolicies: []string{}, + }, + { + name: "No CIDR in policies", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "no-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{}, + expectedEgressCIDRPolicies: []string{}, + }, + { + name: "Ingress CIDR in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{"namespace1/ingress-cidr-policy"}, + expectedEgressCIDRPolicies: []string{}, + }, + { + name: "Egress CIDR in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{}, + expectedEgressCIDRPolicies: []string{"namespace1/egress-cidr-policy"}, + }, + { + name: "Both ingress and egress CIDR in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-and-egress-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{"namespace1/ingress-and-egress-cidr-policy"}, + expectedEgressCIDRPolicies: []string{"namespace1/ingress-and-egress-cidr-policy"}, + }, + { + name: "Multiple polices in a namespace with ingress or egress CIDR", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-and-egress-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{"namespace1/ingress-cidr-policy", "namespace1/ingress-and-egress-cidr-policy"}, + expectedEgressCIDRPolicies: []string{"namespace1/egress-cidr-policy", "namespace1/ingress-and-egress-cidr-policy"}, + }, + { + name: "Multiple polices in multiple namespaces with ingress or egress CIDR or no CIDR", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-and-egress-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "192.168.0.0/16"}}, + }, + }, + }, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {IPBlock: &networkingv1.IPBlock{CIDR: "10.0.0.0/8"}}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "no-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "no-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{"namespace1/ingress-cidr-policy", "namespace1/ingress-and-egress-cidr-policy"}, + expectedEgressCIDRPolicies: []string{"namespace2/egress-cidr-policy", "namespace1/ingress-and-egress-cidr-policy"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ingressPolicies, egressPolicies := getCIDRNetworkPolicies(tt.policiesByNamespace) + if !equal(ingressPolicies, tt.expectedIngressCIDRPolicies) { + t.Errorf("expected ingress policies %v, got %v", tt.expectedIngressCIDRPolicies, ingressPolicies) + } + if !equal(egressPolicies, tt.expectedEgressCIDRPolicies) { + t.Errorf("expected egress policies %v, got %v", tt.expectedEgressCIDRPolicies, egressPolicies) + } + }) + } +} + +func TestGetNamedPortPolicies(t *testing.T) { + tests := []struct { + name string + policiesByNamespace map[string][]*networkingv1.NetworkPolicy + expectedIngressCIDRPolicies []string + expectedEgressCIDRPolicies []string + }{ + { + name: "No policies", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{}, + expectedIngressCIDRPolicies: []string{}, + expectedEgressCIDRPolicies: []string{}, + }, + { + name: "No named port in policies", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "no-cidr-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{}, + expectedEgressCIDRPolicies: []string{}, + }, + { + name: "Ingress named port in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{"namespace1/ingress-named-port-policy"}, + expectedEgressCIDRPolicies: []string{}, + }, + { + name: "Ingress int port in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-int-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{}, + expectedEgressCIDRPolicies: []string{}, + }, + { + name: "Egress named port in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{}, + expectedEgressCIDRPolicies: []string{"namespace1/egress-named-port-policy"}, + }, + { + name: "Egress int port in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-int-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{}, + expectedEgressCIDRPolicies: []string{}, + }, + { + name: "Both ingress and egress name ports in policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-and-egress-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{"namespace1/ingress-and-egress-named-port-policy"}, + expectedEgressCIDRPolicies: []string{"namespace1/ingress-and-egress-named-port-policy"}, + }, + { + name: "Multiple polices in a namespace with ingress or egress named ports", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-and-egress-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{"namespace1/ingress-named-port-policy", "namespace1/ingress-and-egress-named-port-policy"}, + expectedEgressCIDRPolicies: []string{"namespace1/egress-named-port-policy", "namespace1/ingress-and-egress-named-port-policy"}, + }, + { + name: "Multiple polices in multiple namespaces with ingress or egress CIDR or no CIDR", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-and-egress-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-int-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "no-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "no-named-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-int-port-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedIngressCIDRPolicies: []string{"namespace1/ingress-named-port-policy", "namespace1/ingress-and-egress-named-port-policy"}, + expectedEgressCIDRPolicies: []string{"namespace2/egress-named-port-policy", "namespace1/ingress-and-egress-named-port-policy"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ingressPolicies, egressPolicies := getNamedPortPolicies(tt.policiesByNamespace) + if !equal(ingressPolicies, tt.expectedIngressCIDRPolicies) { + t.Errorf("expected ingress policies %v, got %v", tt.expectedIngressCIDRPolicies, ingressPolicies) + } + if !equal(egressPolicies, tt.expectedEgressCIDRPolicies) { + t.Errorf("expected egress policies %v, got %v", tt.expectedEgressCIDRPolicies, egressPolicies) + } + }) + } +} + +func TestGetEgressPolicies(t *testing.T) { + tests := []struct { + name string + policiesByNamespace map[string][]*networkingv1.NetworkPolicy + expectedEgressPolicies []string + }{ + { + name: "No policies", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{}, + expectedEgressPolicies: []string{}, + }, + { + name: "No egress in policies", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "no-egress-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + }, + }, + expectedEgressPolicies: []string{}, + }, + { + name: "Allow all egress policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-egress-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + PolicyTypes: []networkingv1.PolicyType{"Egress"}, + Egress: []networkingv1.NetworkPolicyEgressRule{ + {}, + }, + }, + }, + }, + }, + expectedEgressPolicies: []string{}, + }, + { + name: "Deny all egress policy", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "deny-all-egress-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + PolicyTypes: []networkingv1.PolicyType{"Egress"}, + }, + }, + }, + }, + expectedEgressPolicies: []string{"namespace1/deny-all-egress-policy"}, + }, + { + name: "Egress policy with To field", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-to-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + }, + }, + expectedEgressPolicies: []string{"namespace1/egress-to-policy"}, + }, + { + name: "Egress policy with Ports field", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-ports-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80))}, + }, + }, + }, + }, + }, + }, + }, + expectedEgressPolicies: []string{"namespace1/egress-ports-policy"}, + }, + { + name: "Egress policy with both To and Ports fields", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-to-and-ports-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80))}, + }, + }, + }, + }, + }, + }, + }, + expectedEgressPolicies: []string{"namespace1/egress-to-and-ports-policy"}, + }, + { + name: "Multiple egress polices in a namespace with To or Port fields", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-to-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-ports-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80))}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-to-and-ports-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80))}, + }, + }, + }, + }, + }, + }, + }, + expectedEgressPolicies: []string{"namespace1/egress-to-policy", "namespace1/egress-ports-policy", "namespace1/egress-to-and-ports-policy"}, + }, + { + name: "Multiple egresss polices in multiple namespaces with To or Port fields or no egress", + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-to-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-to-and-ports-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80))}, + }, + }, + }, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-ports-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + {Port: intstrPtr(intstr.FromInt(80))}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "no-egress-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "egress-to-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Egress: []networkingv1.NetworkPolicyEgressRule{ + { + To: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-egress-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + PolicyTypes: []networkingv1.PolicyType{"Egress"}, + Egress: []networkingv1.NetworkPolicyEgressRule{ + {}, + }, + }, + }, + }, + "namespace4": { + { + ObjectMeta: metav1.ObjectMeta{Name: "no-egress-policy"}, + Spec: networkingv1.NetworkPolicySpec{ + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + {PodSelector: &metav1.LabelSelector{}}, + }, + }, + }, + }, + }, + }, + }, + expectedEgressPolicies: []string{"namespace1/egress-to-policy", "namespace1/egress-to-and-ports-policy", "namespace2/egress-ports-policy", "namespace3/egress-to-policy"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + egressPolicies := getEgressPolicies(tt.policiesByNamespace) + if !equal(egressPolicies, tt.expectedEgressPolicies) { + t.Errorf("expected egress policies %v, got %v", tt.expectedEgressPolicies, egressPolicies) + } + }) + } +} + +func TestGetExternalTrafficPolicyClusterServices(t *testing.T) { + tests := []struct { + name string + namespaces *corev1.NamespaceList + servicesByNamespace map[string][]*corev1.Service + policiesByNamespace map[string][]*networkingv1.NetworkPolicy + expectedUnsafeServices []string + }{ + // Scenarios where there are no LoadBalancer or NodePort services + { + name: "No namespaces", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{}, + }, + servicesByNamespace: map[string][]*corev1.Service{}, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{}, + expectedUnsafeServices: []string{}, + }, + { + name: "Namespace with no policies and services", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": {}, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": {}, + }, + expectedUnsafeServices: []string{}, + }, + // Scenarios where there are LoadBalancer or NodePort services but externalTrafficPolicy is not Cluster + { + name: "LoadBalancer service with externalTrafficPolicy=Local with no selector and a deny all ingress policy with no selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeLocal, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "deny-all-ingress-policy-with-no-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "NodePort service with externalTrafficPolicy=Local with no selector and a deny all ingress policy with no selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeLocal, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "deny-all-ingress-policy-with-no-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + // Scenarios where there are LoadBalancer or NodePort services with externalTrafficPolicy=Cluster but no policies + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with no selector and no policies", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": {}, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "NodePort service with externalTrafficPolicy=Cluster with no selector and no policies", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": {}, + }, + expectedUnsafeServices: []string{}, + }, + // Scenarios where there are LoadBalancer or NodePort services with externalTrafficPolicy=Cluster and policies allow traffic + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with no selector and an allow all ingress policy with no selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-no-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an allow all ingress policy with a matching selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an ingress policy with a matching selector and ports", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "NodePort service with externalTrafficPolicy=Cluster with no selector and an allow all ingress policy with no selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-no-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "NodePort service with externalTrafficPolicy=Cluster with a selector and an allow all ingress policy with a matching selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Selector: map[string]string{"app": "test"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "NodePort service with externalTrafficPolicy=Cluster with a selector and an allow all ingress policy with a matching selector and ports", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + // Scenarios where there are LoadBalancer or NodePort services with externalTrafficPolicy=Cluster and policies deny traffic + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with no selector and a deny all ingress policy with no selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "deny-all-ingress-policy-with-no-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-no-selector"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with no selector and an allow all ingress policy with a selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-no-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-no-selector"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an deny all ingress policy with a matching selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "deny-all-ingress-policy-with-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster and matching policy that has a pod selector but no ports", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "external-traffic-policy-cluster-service"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + Selector: map[string]string{"app": "test"}, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "policy1"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + { + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/external-traffic-policy-cluster-service"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an allow all ingress policy with a selector that doesnt match", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test", "app3": "test3", "app4": "test4"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test", "app2": "test2"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an allow all ingress policy with a selector that has more labels", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test", "app2": "test2", "app3": "test3"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an ingress policy with a matching selector but ports dont match", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-named-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + { + Port: 100, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(100), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + { + Port: intstrPtr(intstr.FromInt(90)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector-and-named-ports"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an ingress policy with a matching selector but uses named ports", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-named-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromString("http"), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-policy-with-selector-and-named-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromString("http")), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector-and-named-ports"}, + }, + // Scenarios covering edge cases + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with no selector and a allow all and deny all ingress policy with no selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "deny-all-ingress-policy-with-no-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-no-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and a allow all and deny all ingress policy with a matching selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "deny-all-ingress-policy-with-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-a-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and no ports and an ingress policy with a matching selector and ports", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with no selector and an allow all ingress policy with a matchExpressions selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-matchexpressins-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "app", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"test"}, + }, + }, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector-and-ports"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an allow all ingress policy with a matchExpressions selector", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-no-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-matchexpressions-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "app", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"test"}, + }, + }, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-no-selector"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an ingress policy with a matching selector and protocol with no ports", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an allow all ingress policy with a matching selector and port and port=0", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(0)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and targetport=0 and an allow all ingress policy with a matching selector and different ports", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(0), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector-and-ports"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and targetport=0 and an allow all ingress policy with a matching selector and ports=0", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(0), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(0)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector-and-ports"}, + }, + { + name: "LoadBalancer service with externalTrafficPolicy=Cluster with a selector and an ingress policy with a matching selector and ports and pod/namespace selectors", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + { + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + }, + }, + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector-and-ports"}, + }, + // Scenarios where there are LoadBalancer or NodePort services with externalTrafficPolicy=Cluster and there are multiple namespaces + { + name: "LoadBalancer or NodePort services with externalTrafficPolicy=Cluster and allow all ingress policies with matching label and ports in multiple namespaces", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "namespace2"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "namespace3"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{}, + }, + { + name: "LoadBalancer or NodePort services with externalTrafficPolicy=Cluster and allow all ingress policies without matching label and ports in multiple namespaces", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "namespace2"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "namespace3"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test2"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + { + Port: 90, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(90), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolUDP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector", "namespace2/service-with-selector-and-ports", "namespace3/service-with-selector-and-ports"}, + }, + { + name: "LoadBalancer or NodePort services with externalTrafficPolicy=Cluster and allow all ingress policies with some matching label and ports in multiple namespaces", + namespaces: &corev1.NamespaceList{ + Items: []corev1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "namespace2"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "namespace3"}}, + }, + }, + servicesByNamespace: map[string][]*corev1.Service{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-match"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-no-match"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test2"}, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports-match"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports-no-match"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + { + Port: 90, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(90), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports-match"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolUDP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "service-with-selector-and-ports-no-match"}, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "test"}, + Ports: []corev1.ServicePort{ + { + Port: 80, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + }, + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + }, + }, + }, + }, + policiesByNamespace: map[string][]*networkingv1.NetworkPolicy{ + "namespace1": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }, + }, + }, + "namespace2": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Port: intstrPtr(intstr.FromInt(80)), + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolTCP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + "namespace3": { + { + ObjectMeta: metav1.ObjectMeta{Name: "allow-all-ingress-policy-with-selector-and-ports"}, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"app": "test"}, + }, + PolicyTypes: []networkingv1.PolicyType{"Ingress"}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + Ports: []networkingv1.NetworkPolicyPort{ + { + Protocol: func() *corev1.Protocol { + protocol := corev1.ProtocolUDP + return &protocol + }(), + }, + }, + }, + }, + }, + }, + }, + }, + expectedUnsafeServices: []string{"namespace1/service-with-selector-no-match", "namespace2/service-with-selector-and-ports-no-match", "namespace3/service-with-selector-and-ports-no-match"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + unsafeServices := getUnsafeExternalTrafficPolicyClusterServices(tt.namespaces, tt.servicesByNamespace, tt.policiesByNamespace) + if !equal(unsafeServices, tt.expectedUnsafeServices) { + t.Errorf("expected unsafe services %v, got %v", tt.expectedUnsafeServices, unsafeServices) + } + }) + } +} + +// Helper to test the list output of functions +func equal(a, b []string) bool { + if len(a) != len(b) { + return false + } + m := make(map[string]bool) + for _, v := range a { + m[v] = true + } + for _, v := range b { + if !m[v] { + return false + } + } + return true +} + +// Helper function to create a pointer to an intstr.IntOrString +func intstrPtr(i intstr.IntOrString) *intstr.IntOrString { + return &i +} + +// Helper function to create a pointer to an int32 +func int32Ptr(i int32) *int32 { + return &i +} diff --git a/tools/azure-npm-to-cilium-validator/go.mod b/tools/azure-npm-to-cilium-validator/go.mod new file mode 100644 index 0000000000..4784f37a76 --- /dev/null +++ b/tools/azure-npm-to-cilium-validator/go.mod @@ -0,0 +1,85 @@ +module azure-npm-to-cilium-validator + +go 1.23 + +toolchain go1.23.6 + +require ( + github.com/Azure/azure-container-networking v1.6.21 + github.com/olekukonko/tablewriter v0.0.5 + k8s.io/api v0.30.7 + k8s.io/apimachinery v0.30.7 + k8s.io/client-go v0.30.7 + k8s.io/klog/v2 v2.130.1 +) + +require ( + code.cloudfoundry.org/clock v1.0.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Microsoft/hcsshim v0.12.9 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/cgroups/v3 v3.0.3 // indirect + github.com/containerd/errdefs v0.3.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/typeurl/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/mock v1.7.0-rc.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/microsoft/ApplicationInsights-Go v0.4.4 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/ginkgo/v2 v2.19.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.61.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/stretchr/testify v1.10.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/grpc v1.69.2 // indirect + google.golang.org/protobuf v1.36.3 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog v1.0.0 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/tools/azure-npm-to-cilium-validator/go.sum b/tools/azure-npm-to-cilium-validator/go.sum new file mode 100644 index 0000000000..cad51c3538 --- /dev/null +++ b/tools/azure-npm-to-cilium-validator/go.sum @@ -0,0 +1,314 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= +code.cloudfoundry.org/clock v1.0.0 h1:kFXWQM4bxYvdBw2X8BbBeXwQNgfoWv1vqAk2ZZyBN2o= +code.cloudfoundry.org/clock v1.0.0/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= +github.com/Azure/azure-container-networking v1.6.21 h1:1O+6D7upf23qMlPRhlB9EPdj9sqpgXiwyCTnSscQ2VM= +github.com/Azure/azure-container-networking v1.6.21/go.mod h1:ecy7xVz3A+vpH6oAyYZLQfN1yrRSQc3iN9a31w0N8VI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= +github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= +github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= +github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/microsoft/ApplicationInsights-Go v0.4.4 h1:G4+H9WNs6ygSCe6sUyxRc2U81TI5Es90b2t/MwX5KqY= +github.com/microsoft/ApplicationInsights-Go v0.4.4/go.mod h1:fKRUseBqkw6bDiXTs3ESTiU/4YTIHsQS4W3fP2ieF4U= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= +github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tedsuo/ifrit v0.0.0-20180802180643-bea94bb476cc/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.30.7 h1:wB2eHI+IptVYsz5WsAQpI6+Dqi3+11wEWBqIh4fh980= +k8s.io/api v0.30.7/go.mod h1:bR0EwbmhYmJvUoeza7ZzBUmYCrVXccQ9JOdfv0BxhH0= +k8s.io/apimachinery v0.30.7 h1:CoQFxvzPFKwU1eJGN/8LgM3ZJBC3hKgvwGqRrL43uIY= +k8s.io/apimachinery v0.30.7/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.7 h1:DQRfuGWxDzxPEyyiTE/fxzAsZcj2p9sbc5671njR52w= +k8s.io/client-go v0.30.7/go.mod h1:oED9+njB91ExCc4BNPAotniB7WH1ig7CmiBx5pVA1yw= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +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= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=