diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8c72e1..79ff198 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,11 +37,11 @@ jobs: cd backend go mod tidy - - name: Bump version and tag - id: bump_version - run: | - chmod +x .github/scripts/bump_version.sh - .github/scripts/bump_version.sh + # - name: Bump version and tag + # id: bump_version + # run: | + # chmod +x .github/scripts/bump_version.sh + # .github/scripts/bump_version.sh - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 diff --git a/.goreleaser.yml b/.goreleaser.yml index 4cb5f3c..5e9be80 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -13,7 +13,7 @@ changelog: regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' order: 0 - title: "Bug fixes" - regexp: '^.*?bug(\([[:word:]]+\))??!?:.+$' + regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$' order: 1 - title: "Documentation Updates" regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$' diff --git a/backend/cmd/scan.go b/backend/cmd/scan.go index d0d4780..7fc4bd5 100644 --- a/backend/cmd/scan.go +++ b/backend/cmd/scan.go @@ -63,7 +63,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) - fmt.Println(createTargetPodsTable(pods, foundNamespace)) + fmt.Println(createTargetPodsTable(pods)) } } return @@ -92,7 +92,7 @@ var scanCmd = &cobra.Command{ 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()) - fmt.Println(createTargetPodsTable(pods, "")) + fmt.Println(createTargetPodsTable(pods)) } } } else { @@ -106,7 +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) - fmt.Println(createTargetPodsTable(pods, foundNamespace)) + fmt.Println(createTargetPodsTable(pods)) } } return @@ -141,7 +141,7 @@ var scanCmd = &cobra.Command{ } else { // 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") + fmt.Println("All pods are protected by cluster wide cilium policies.\nYour Netfetch security score is: 100/100") return } handleScanResult(clusterwideScanResult) @@ -149,6 +149,7 @@ var scanCmd = &cobra.Command{ } // Proceed with normal Cilium network policy scan + fmt.Println("Running cilium network policies scan...") ciliumScanResult, err := k8s.ScanCiliumNetworkPolicies(namespace, dryRun, false, true, true, true) if err != nil { fmt.Println("Error during Cilium network policies scan:", err) @@ -172,7 +173,7 @@ var ( ) // Function to create a table for pods -func createTargetPodsTable(pods [][]string, namespace string) string { +func createTargetPodsTable(pods [][]string) string { t := table.New(). Border(lipgloss.NormalBorder()). BorderStyle(tableBorderStyle). diff --git a/backend/pkg/k8s/cilium-scanner.go b/backend/pkg/k8s/cilium-scanner.go index bd7acf4..98358f7 100644 --- a/backend/pkg/k8s/cilium-scanner.go +++ b/backend/pkg/k8s/cilium-scanner.go @@ -178,8 +178,18 @@ func fetchCiliumPolicies(dynamicClient dynamic.Interface, nsName string, writer return unstructuredPolicies, hasDenyAll, nil } +// helper function to ensure pods are not added to list multiple times +func addUniquePodDetail(podDetails []string, detail string) []string { + for _, d := range podDetails { + if d == detail { + return podDetails // pod already in the list. + } + } + return append(podDetails, detail) // add pod if its not in list +} + // determinePodCoverage identifies unprotected pods in a namespace based on the fetched Cilium policies. -func determinePodCoverage(clientset *kubernetes.Clientset, nsName string, policies []*unstructured.Unstructured, hasDenyAll bool, writer *bufio.Writer, scanResult *ScanResult) ([]string, error) { +func determinePodCoverage(clientset *kubernetes.Clientset, nsName string, policies []*unstructured.Unstructured, hasDenyAll bool, writer *bufio.Writer) ([]string, error) { unprotectedPods := []string{} pods, err := clientset.CoreV1().Pods(nsName).List(context.TODO(), metav1.ListOptions{}) @@ -194,16 +204,15 @@ func determinePodCoverage(clientset *kubernetes.Clientset, nsName string, polici continue } podIdentifier := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name) - if _, exists := globallyProtectedPods[podIdentifier]; !exists { - if !IsPodProtected(writer, clientset, pod, policies, hasDenyAll, globallyProtectedPods) { - unprotectedPodDetails := fmt.Sprintf("%s %s %s", pod.Namespace, pod.Name, pod.Status.PodIP) - unprotectedPods = append(unprotectedPods, unprotectedPodDetails) - scanResult.UnprotectedPods = append(scanResult.UnprotectedPods, unprotectedPodDetails) - } else { - globallyProtectedPods[podIdentifier] = struct{}{} // Mark the pod as protected globally - } - } - } + if _, exists := globallyProtectedPods[podIdentifier]; !exists { + if !IsPodProtected(writer, clientset, pod, policies, hasDenyAll, globallyProtectedPods) { + unprotectedPodDetails := fmt.Sprintf("%s %s %s", pod.Namespace, pod.Name, pod.Status.PodIP) + unprotectedPods = addUniquePodDetail(unprotectedPods, unprotectedPodDetails) + } else { + globallyProtectedPods[podIdentifier] = struct{}{} // Mark the pod as protected globally + } + } + } return unprotectedPods, nil } @@ -215,7 +224,7 @@ func processNamespacePoliciesCilium(dynamicClient dynamic.Interface, clientset * return err } - unprotectedPods, err := determinePodCoverage(clientset, nsName, ciliumPolicies, hasDenyAll, writer, scanResult) + unprotectedPods, err := determinePodCoverage(clientset, nsName, ciliumPolicies, hasDenyAll, writer) if err != nil { return err } @@ -356,7 +365,7 @@ func ScanCiliumNetworkPolicies(specificNamespace string, dryRun bool, returnResu if printScore { // Print the final score - fmt.Printf("\nYour Netfetch security score is: %d/42\n", score) + fmt.Printf("\nYour Netfetch security score is: %d/100\n", score) } hasStartedCiliumScan = false @@ -563,7 +572,7 @@ func ScanCiliumClusterwideNetworkPolicies(dynamicClient dynamic.Interface, print UserDeniedPolicies: false, AllPodsProtected: false, HasDenyAll: []string{}, - Score: 0, // or some initial value + Score: 50, // or some initial value } defaultDenyAllFound, appliesToEntireCluster, partialDenyAllPolicies, partialDenyAllFound := analyzeClusterwidePolicies(unstructuredPolicies) diff --git a/backend/pkg/k8s/scanner.go b/backend/pkg/k8s/scanner.go index c67204b..3d60415 100644 --- a/backend/pkg/k8s/scanner.go +++ b/backend/pkg/k8s/scanner.go @@ -313,7 +313,7 @@ func ScanNetworkPolicies(specificNamespace string, dryRun bool, returnResult boo if printScore { // Print the final score - fmt.Printf("\nYour Netfetch security score is: %d/42\n", score) + fmt.Printf("\nYour Netfetch security score is: %d/100\n", score) } hasStartedNativeScan = false @@ -397,20 +397,24 @@ func IsSystemNamespace(namespace string) bool { // Scoring logic func CalculateScore(hasPolicies bool, hasDenyAll bool, unprotectedPodsCount int) int { - score := 42 // Start with the highest score + score := 50 // Start with a base score of 50 - if !hasPolicies { - score -= 20 - } + if hasDenyAll { + score += 20 // Add 20 points for having deny-all policies + } else if !hasPolicies { + score -= 20 // Subtract 20 points if there are no policies at all + } - // Deduct score based on the number of unprotected pods - score -= unprotectedPodsCount + // Deduct score based on the number of unprotected pods + score -= unprotectedPodsCount - if score < 1 { - score = 1 // Minimum score - } + if score > 100 { + score = 100 + } else if score < 1 { + score = 1 + } - return score + return score } // INTERACTIVE DASHBOARD LOGIC diff --git a/backend/pkg/k8s/scanner_test.go b/backend/pkg/k8s/scanner_test.go index bbb0fc7..3f383a0 100644 --- a/backend/pkg/k8s/scanner_test.go +++ b/backend/pkg/k8s/scanner_test.go @@ -79,29 +79,29 @@ func TestIsSystemNamespace(t *testing.T) { } func TestCalculateScore(t *testing.T) { - // Test case 1: All conditions met, maximum score expected - score1 := CalculateScore(true, true, 0) - if score1 != 42 { - t.Fatalf("Expected score to be 42, got %d", score1) - } + // Test case 1: Policies exist, deny all exists, no unprotected pods + score1 := CalculateScore(true, true, 0) + if score1 != 70 { // 50 base + 20 for deny-all + t.Fatalf("Expected score to be 70, got %d", score1) + } - // Test case 2: No policies, no deny all, 5 unprotected pods - score2 := CalculateScore(false, false, 5) - if score2 != 17 { - t.Fatalf("Expected score to be 17, got %d", score2) - } + // Test case 2: No policies, no deny all, 5 unprotected pods + score2 := CalculateScore(false, false, 5) + if score2 != 25 { // 50 base - 20 for no policies - 5 for unprotected pods + t.Fatalf("Expected score to be 25, got %d", score2) + } - // Test case 3: No policies, no deny all, no unprotected pods - score3 := CalculateScore(false, false, 0) - if score3 != 22 { - t.Fatalf("Expected score to be 1, got %d", score3) - } + // Test case 3: No policies, no deny all, no unprotected pods + score3 := CalculateScore(false, false, 0) + if score3 != 30 { // 50 base - 20 for no policies + t.Fatalf("Expected score to be 30, got %d", score3) + } - // Test case 4: Policies exist, deny all exists, no unprotected pods - score4 := CalculateScore(true, true, 0) - if score4 != 42 { - t.Fatalf("Expected score to be 42, got %d", score4) - } + // Test case 4: Policies exist, deny all exists, no unprotected pods (repeat of case 1 for consistency) + score4 := CalculateScore(true, true, 0) + if score4 != 70 { + t.Fatalf("Expected score to be 70, got %d", score4) + } } func TestGetPodInfo(t *testing.T) {