Skip to content

Commit

Permalink
Merge pull request #145 from deggja/feat/target-cilium
Browse files Browse the repository at this point in the history
feat: added initial functionality for target scanning cilium network policies and cluster wide cilium netpols #patch
  • Loading branch information
deggja authored May 15, 2024
2 parents dbac77e + 1d0b245 commit 82a18ca
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 0 deletions.
46 changes: 46 additions & 0 deletions backend/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,52 @@ var scanCmd = &cobra.Command{
}
}

// Handle target policy for Cilium network policies and cluster-wide policies
if targetPolicy != "" && cilium {
fmt.Println("Policy type: Cilium")
fmt.Printf("Searching for Cilium network policy '%s' across all non-system namespaces...\n", targetPolicy)
policy, foundNamespace, err := k8s.FindCiliumNetworkPolicyByName(dynamicClient, targetPolicy)
if err != nil {
// If not found in namespaces, search for cluster-wide policy
fmt.Println("Cilium network policy not found in namespaces, searching for cluster-wide policy...")
policy, err = k8s.FindCiliumClusterWideNetworkPolicyByName(dynamicClient, targetPolicy)
if err != nil {
fmt.Println("Error during Cilium cluster-wide network policy search:", err)
} else {
fmt.Printf("Found Cilium cluster-wide network policy '%s'.\n", policy.GetName())

// List the pods targeted by this cluster-wide policy
pods, err := k8s.ListPodsTargetedByCiliumClusterWideNetworkPolicy(dynamicClient, policy)
if err != nil {
fmt.Printf("Error listing pods targeted by cluster-wide policy %s: %v\n", policy.GetName(), err)
} else if len(pods) == 0 {
fmt.Printf("No pods targeted by cluster-wide policy '%s'.\n", policy.GetName())
} else {
fmt.Printf("Pods targeted by cluster-wide policy '%s':\n", policy.GetName())
for _, pod := range pods {
fmt.Printf(" - %s\n", pod)
}
}
}
} else {
fmt.Printf("Found Cilium network policy '%s' in namespace '%s'.\n", policy.GetName(), foundNamespace)

// List the pods targeted by this policy
pods, err := k8s.ListPodsTargetedByCiliumNetworkPolicy(dynamicClient, policy, foundNamespace)
if err != nil {
fmt.Printf("Error listing pods targeted by policy %s: %v\n", policy.GetName(), err)
} else if len(pods) == 0 {
fmt.Printf("No pods targeted by policy '%s' in namespace '%s'.\n", policy.GetName(), foundNamespace)
} else {
fmt.Printf("Pods targeted by policy '%s' in namespace '%s':\n", policy.GetName(), foundNamespace)
for _, pod := range pods {
fmt.Printf(" - %s\n", pod)
}
}
}
return
}

// Default to native scan if no specific type is mentioned or if --native is used
if !cilium || native {
fmt.Println("Running native network policies scan...")
Expand Down
Binary file removed backend/netfetch
Binary file not shown.
116 changes: 116 additions & 0 deletions backend/pkg/k8s/target-scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package k8s
import (
"context"
"fmt"
"regexp"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -35,6 +36,44 @@ func FindNativeNetworkPolicyByName(dynamicClient dynamic.Interface, clientset *k
return nil, "", fmt.Errorf("network policy %s not found in any non-system namespace", policyName)
}

// FindCiliumNetworkPolicyByName searches for a specific Cilium network policy by name across all non-system namespaces.
func FindCiliumNetworkPolicyByName(dynamicClient dynamic.Interface, policyName string) (*unstructured.Unstructured, string, error) {
gvr := schema.GroupVersionResource{
Group: "cilium.io",
Version: "v2",
Resource: "ciliumnetworkpolicies",
}

namespaces, err := GetAllNonSystemNamespaces(dynamicClient)
if err != nil {
return nil, "", fmt.Errorf("error getting namespaces: %v", err)
}

for _, namespace := range namespaces {
policy, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), policyName, v1.GetOptions{})
if err == nil {
return policy, namespace, nil
}
}
return nil, "", fmt.Errorf("Cilium network policy %s not found in any non-system namespace", policyName)
}

// FindCiliumClusterWideNetworkPolicyByName searches for a specific cluster-wide Cilium network policy by name.
func FindCiliumClusterWideNetworkPolicyByName(dynamicClient dynamic.Interface, policyName string) (*unstructured.Unstructured, error) {
gvr := schema.GroupVersionResource{
Group: "cilium.io",
Version: "v2",
Resource: "ciliumclusterwidenetworkpolicies",
}

policy, err := dynamicClient.Resource(gvr).Get(context.TODO(), policyName, v1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("Cilium cluster-wide network policy %s not found", policyName)
}
return policy, nil
}


// GetAllNonSystemNamespaces returns a list of all non-system namespaces using a dynamic client.
func GetAllNonSystemNamespaces(dynamicClient dynamic.Interface) ([]string, error) {
gvr := schema.GroupVersionResource{
Expand Down Expand Up @@ -90,3 +129,80 @@ func ListPodsTargetedByNetworkPolicy(dynamicClient dynamic.Interface, policy *un

return targetedPods, nil
}

// ListPodsTargetedByCiliumNetworkPolicy lists all pods targeted by the given Cilium network policy in the specified namespace.
func ListPodsTargetedByCiliumNetworkPolicy(dynamicClient dynamic.Interface, policy *unstructured.Unstructured, namespace string) ([]string, error) {
// Retrieve the PodSelector (matchLabels)
podSelector, found, err := unstructured.NestedMap(policy.Object, "spec", "endpointSelector", "matchLabels")
if err != nil {
return nil, fmt.Errorf("failed to retrieve pod selector from Cilium network policy %s: %v", policy.GetName(), err)
}

// Check if the selector is empty
selector := make(labels.Set)
if found && len(podSelector) > 0 {
for key, value := range podSelector {
if strValue, ok := value.(string); ok {
selector[key] = strValue
} else {
return nil, fmt.Errorf("invalid type for selector value %v in policy %s", value, policy.GetName())
}
}
}

// Fetch pods based on the selector
pods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.AsSelectorPreValidated().String()})
if err != nil {
return nil, fmt.Errorf("error listing pods in namespace %s: %v", namespace, err)
}

var targetedPods []string
for _, pod := range pods.Items {
targetedPods = append(targetedPods, pod.Name)
}

return targetedPods, nil
}

// ListPodsTargetedByCiliumClusterWideNetworkPolicy lists all pods targeted by the given Cilium cluster-wide network policy.
func ListPodsTargetedByCiliumClusterWideNetworkPolicy(dynamicClient dynamic.Interface, policy *unstructured.Unstructured) ([]string, error) {
// Retrieve the PodSelector (matchLabels)
podSelector, found, err := unstructured.NestedMap(policy.Object, "spec", "endpointSelector", "matchLabels")
if err != nil {
return nil, fmt.Errorf("failed to retrieve pod selector from Cilium cluster-wide network policy %s: %v", policy.GetName(), err)
}

// Regex for valid Kubernetes label keys
validLabelKey := regexp.MustCompile(`^[A-Za-z0-9][-A-Za-z0-9_.]*[A-Za-z0-9]$`)

// Check if the selector is empty
selector := make(labels.Set)
if found && len(podSelector) > 0 {
for key, value := range podSelector {
// Skip reserved labels
if !validLabelKey.MatchString(key) {
fmt.Printf("Skipping reserved label key %s in policy %s\n", key, policy.GetName())
continue
}
if strValue, ok := value.(string); ok {
selector[key] = strValue
} else {
return nil, fmt.Errorf("invalid type for selector value %v in policy %s", value, policy.GetName())
}
}
}

// Fetch pods based on the selector
pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{LabelSelector: selector.AsSelectorPreValidated().String()})
if err != nil {
return nil, fmt.Errorf("error listing pods: %v", err)
}

var targetedPods []string
for _, pod := range pods.Items {
targetedPods = append(targetedPods, pod.Name)
}

return targetedPods, nil
}

0 comments on commit 82a18ca

Please sign in to comment.