Skip to content

Commit

Permalink
feat: initial code to add table format to target scanners
Browse files Browse the repository at this point in the history
Signed-off-by: deggja <danieldagfinrud@gmail.com>
  • Loading branch information
deggja committed May 15, 2024
1 parent 4ea4e53 commit 3e9fa2b
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 28 deletions.
111 changes: 88 additions & 23 deletions backend/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"fmt"

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
"github.com/deggja/netfetch/backend/pkg/k8s"
"github.com/spf13/cobra"
)
Expand All @@ -20,7 +22,9 @@ var scanCmd = &cobra.Command{
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. You may also target a speecific network policy using --target-policy.`,
Use --cilium to scan for Cilium network policies.
You may also target a specific network policy using the --target flag.
This can be used in combination with --native and --cilium for select policy types.`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var namespace string
Expand Down Expand Up @@ -59,40 +63,36 @@ var scanCmd = &cobra.Command{
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)
}
fmt.Println(createTargetPodsTable(pods, foundNamespace))
}
}
return
}
}

// Handle target policy for Cilium network policies and cluster-wide policies
// 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
// 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)
fmt.Println("Error during Cilium cluster wide network policy search:", err)
} else {
fmt.Printf("Found Cilium cluster-wide network policy '%s'.\n", policy.GetName())
fmt.Printf("Found Cilium clusterwide network policy '%s'.\n", policy.GetName())

// List the pods targeted by this cluster-wide policy
// 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)
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())
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)
}
fmt.Printf("Pods targeted by cluster wide policy '%s':\n", policy.GetName())
fmt.Println(createTargetPodsTable(pods, ""))
}
}
} else {
Expand All @@ -106,9 +106,7 @@ var scanCmd = &cobra.Command{
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)
}
fmt.Println(createTargetPodsTable(pods, foundNamespace))
}
}
return
Expand All @@ -128,9 +126,9 @@ var scanCmd = &cobra.Command{

// Perform Cilium network policy scan if --cilium is used
if cilium {
// Perform cluster-wide Cilium scan first if no namespace is specified
// Perform cluster wide Cilium scan first if no namespace is specified
if namespace == "" {
fmt.Println("Running cluster-wide Cilium network policies scan...")
fmt.Println("Running cluster wide Cilium network policies scan...")
dynamicClient, err := k8s.GetCiliumDynamicClient()
if err != nil {
fmt.Println("Error obtaining dynamic client:", err)
Expand All @@ -139,9 +137,9 @@ var scanCmd = &cobra.Command{

clusterwideScanResult, err := k8s.ScanCiliumClusterwideNetworkPolicies(dynamicClient, false, dryRun, true)
if err != nil {
fmt.Println("Error during cluster-wide Cilium network policies scan:", err)
fmt.Println("Error during cluster wide Cilium network policies scan:", err)
} else {
// Handle the cluster-wide scan result; skip further scanning if all pods are protected
// Handle the cluster wide scan result; skip further scanning if all pods are protected
if clusterwideScanResult.AllPodsProtected {
fmt.Println("All pods are protected by cluster wide cilium policies.\nYour Netfetch security score is: 42/42")
return
Expand All @@ -166,10 +164,77 @@ func handleScanResult(scanResult *k8s.ScanResult) {
// Implement your logic to handle scan results
}

var (
EvenRowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6"))
OddRowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6"))
)

// Function to create a table for pods
func createTargetPodsTable(pods []string, namespace string) string {
podsInfo := [][]string{}
for _, pod := range pods {
podsInfo = append(podsInfo, []string{namespace, pod, "N/A"})
}

t := table.New().
Border(lipgloss.NormalBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
StyleFunc(func(row, col int) lipgloss.Style {
switch {
case row == 0:
return HeaderStyle
case row%2 == 0:
return EvenRowStyle
default:
return OddRowStyle
}
}).
Headers("Namespace", "Pod Name", "IP Address")

for _, row := range podsInfo {
formattedRow := make([]string, 3)
for i := 0; i < 3; i++ {
if i < len(row) {
formattedRow[i] = row[i]
} else {
formattedRow[i] = "N/A"
}
}

t.Row(formattedRow[0], formattedRow[1], formattedRow[2])
}

return t.String()
}

// Function to create a table for policies
func createTargetPoliciesTable(policiesInfo [][]string) string {
t := table.New().
Border(lipgloss.NormalBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))).
StyleFunc(func(row, col int) lipgloss.Style {
switch {
case row == 0:
return HeaderStyle
case row%2 == 0:
return EvenRowStyle
default:
return OddRowStyle
}
}).
Headers("Policy Name")

for _, row := range policiesInfo {
t.Row(row...)
}

return t.String()
}

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().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)
Expand Down
10 changes: 5 additions & 5 deletions backend/pkg/k8s/target-scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ func FindCiliumNetworkPolicyByName(dynamicClient dynamic.Interface, policyName s
return policy, namespace, nil
}
}
return nil, "", fmt.Errorf("Cilium network policy %s not found in any non-system namespace", policyName)
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.
// 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",
Expand All @@ -68,7 +68,7 @@ func FindCiliumClusterWideNetworkPolicyByName(dynamicClient dynamic.Interface, p

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 nil, fmt.Errorf("cilium cluster wide network policy %s not found", policyName)
}
return policy, nil
}
Expand Down Expand Up @@ -164,12 +164,12 @@ func ListPodsTargetedByCiliumNetworkPolicy(dynamicClient dynamic.Interface, poli
return targetedPods, nil
}

// ListPodsTargetedByCiliumClusterWideNetworkPolicy lists all pods targeted by the given Cilium cluster-wide network policy.
// 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)
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
Expand Down

0 comments on commit 3e9fa2b

Please sign in to comment.