Skip to content

Commit

Permalink
Merge pull request #138 from deggja/feat/scan-policy
Browse files Browse the repository at this point in the history
feat: adding new feature to specify specific policy to scan
  • Loading branch information
deggja authored May 9, 2024
2 parents 9846a23 + 07e7fb9 commit b88d7b7
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 13 deletions.
17 changes: 11 additions & 6 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@ changelog:
exclude:
- '^Merge pull request'
groups:
- title: "Features"
regexp: "^feat:"
- title: "Bug Fixes"
regexp: "^(fix|bugfix):"
- title: Features
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 0
- title: "Bug fixes"
regexp: '^.*?bug(\([[:word:]]+\))??!?:.+$'
order: 1
- title: "Documentation Updates"
regexp: "^docs:"
regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$'
order: 2
- title: "Other Changes"
regexp: "^(ci|build|misc|perf|deps):"
order: 3
- title: "Miscellaneous"
regexp: ".*" # Matches everything that doesn't fit above groups
regexp: ".*"
order: 4

# Build Configuration
builds:
Expand Down
52 changes: 47 additions & 5 deletions backend/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,66 @@ import (
)

var (
dryRun bool
native bool
cilium bool
verbose bool
dryRun bool
native bool
cilium bool
verbose bool
targetPolicy string
)

var scanCmd = &cobra.Command{
Use: "scan [namespace]",
Short: "Scan Kubernetes namespaces for network policies",
Long: `Scan Kubernetes namespaces for network policies.
By default, it scans for native Kubernetes network policies.
Use --cilium to scan for Cilium network policies.`,
Use --cilium to scan for Cilium network policies. You may also target a speecific network policy using --target-policy.`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var namespace string
if len(args) > 0 {
namespace = args[0]
}

// Initialize the Kubernetes clients
clientset, err := k8s.GetClientset()
if err != nil {
fmt.Println("Error creating Kubernetes client:", err)
return
}
dynamicClient, err := k8s.GetCiliumDynamicClient()
if err != nil {
fmt.Println("Error creating Kubernetes dynamic client:", err)
return
}

// Handle target policy for native Kubernetes network policies
if targetPolicy != "" {
if !cilium || native {
fmt.Println("Policy type: Kubernetes")
fmt.Printf("Searching for Kubernetes native network policy '%s' across all non-system namespaces...\n", targetPolicy)
policy, foundNamespace, err := k8s.FindNativeNetworkPolicyByName(dynamicClient, clientset, targetPolicy)
if err != nil {
fmt.Println("Error during Kubernetes native network policy search:", err)
} else {
fmt.Printf("Found Kubernetes native network policy '%s' in namespace '%s'.\n", policy.GetName(), foundNamespace)

// List the pods targeted by this policy
pods, err := k8s.ListPodsTargetedByNetworkPolicy(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 Expand Up @@ -83,6 +124,7 @@ func init() {
scanCmd.Flags().BoolVarP(&dryRun, "dryrun", "d", false, "Perform a dry run without applying any changes")
scanCmd.Flags().BoolVar(&native, "native", false, "Scan only native network policies")
scanCmd.Flags().BoolVar(&cilium, "cilium", false, "Scan only Cilium network policies (includes cluster-wide policies if no namespace is specified)")
scanCmd.Flags().StringVarP(&targetPolicy, "target", "t", "", "Scan a specific network policy by name")
scanCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")
rootCmd.AddCommand(scanCmd)
}
92 changes: 92 additions & 0 deletions backend/pkg/k8s/target-scanner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package k8s

import (
"context"
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
)

// FindNativeNetworkPolicyByName searches for a specific native network policy by name across all non-system namespaces.
func FindNativeNetworkPolicyByName(dynamicClient dynamic.Interface, clientset *kubernetes.Clientset, policyName string) (*unstructured.Unstructured, string, error) {
gvr := schema.GroupVersionResource{
Group: "networking.k8s.io",
Version: "v1",
Resource: "networkpolicies",
}

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("network policy %s not found in any non-system namespace", policyName)
}

// GetAllNonSystemNamespaces returns a list of all non-system namespaces using a dynamic client.
func GetAllNonSystemNamespaces(dynamicClient dynamic.Interface) ([]string, error) {
gvr := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "namespaces",
}

namespacesList, err := dynamicClient.Resource(gvr).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("error listing namespaces: %v", err)
}

var namespaces []string
for _, ns := range namespacesList.Items {
if !IsSystemNamespace(ns.GetName()) {
namespaces = append(namespaces, ns.GetName())
}
}
return namespaces, nil
}

// ListPodsTargetedByNetworkPolicy lists all pods targeted by the given network policy in the specified namespace.
func ListPodsTargetedByNetworkPolicy(dynamicClient dynamic.Interface, policy *unstructured.Unstructured, namespace string) ([]string, error) {
// Retrieve the PodSelector (matchLabels)
podSelector, found, err := unstructured.NestedMap(policy.Object, "spec", "podSelector", "matchLabels")
if err != nil {
return nil, fmt.Errorf("failed to retrieve pod selector from 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
}
2 changes: 1 addition & 1 deletion backend/statik/statik.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion charts/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Stage 1: Build the Vue.js frontend
FROM node:latest as nodebuilder
FROM node:21 as nodebuilder

WORKDIR /app/frontend

Expand Down

0 comments on commit b88d7b7

Please sign in to comment.