Skip to content

Commit

Permalink
Feat: added functionality to add output to a file after successfull scan
Browse files Browse the repository at this point in the history
Signed-off-by: deggja <danieldagfinrud@gmail.com>
  • Loading branch information
deggja committed Nov 24, 2023
1 parent 2097424 commit 2e0d4a1
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 27 deletions.
Binary file modified netfetch
Binary file not shown.
2 changes: 1 addition & 1 deletion netfetch.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Netfetch < Formula
desc "CLI tool to scan for network policies in Kubernetes clusters"
homepage "https://github.com/deggja/netfetch"
url "https://github.com/deggja/netfetch/releases/download/0.0.8/netfetch_0.0.8_darwin_amd64.tar.gz"
url "https://github.com/deggja/netfetch/releases/download/0.0.9/netfetch_0.0.9_darwin_amd64.tar.gz"
sha256 "0d9ba6bb2c0509b8a1d97bc680fccf48f2689e4f4195865aeb4dfa08356d8db0"

def install
Expand Down
117 changes: 91 additions & 26 deletions pkg/k8s/scanner.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package k8s

import (
"bufio"
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/AlecAivazis/survey/v2"
networkingv1 "k8s.io/api/networking/v1"
Expand All @@ -13,8 +17,19 @@ import (
"k8s.io/client-go/util/homedir"
)

// Helper function to write to both buffer and standard output
func printToBoth(writer *bufio.Writer, s string) {
// Print to standard output
fmt.Print(s)
// Write the same output to buffer
fmt.Fprint(writer, s)
}

// ScanNetworkPolicies scans all non-system namespaces for network policies
func ScanNetworkPolicies() {
// Buffer to hold the output
var output bytes.Buffer
writer := bufio.NewWriter(&output)
// Use the default kubeconfig path if running outside the cluster
var kubeconfig string
if home := homedir.HomeDir(); home != "" {
Expand Down Expand Up @@ -42,25 +57,33 @@ func ScanNetworkPolicies() {

// Flag to track if any network policy is missing or if there are any uncovered pods
missingPoliciesOrUncoveredPods := false

// Flag to track if user denied to create default netpol
userDeniedPolicyApplication := false

// Track namespaces where user denied policy application
deniedNamespaces := []string{}

for _, ns := range namespaces.Items {
if isSystemNamespace(ns.Name) {
continue
}

policies, err := clientset.NetworkingV1().NetworkPolicies(ns.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
fmt.Printf("\nError listing network policies in namespace %s: %s\n", ns.Name, err)
errorMsg := fmt.Sprintf("\nError listing network policies in namespace %s: %s\n", ns.Name, err)
printToBoth(writer, errorMsg)
continue
}

// Check if there's a default deny all policy
hasDefaultDenyAll := hasDefaultDenyAllPolicy(policies.Items)

// Initialize coveredPods map and hasPolicies flag
coveredPods := make(map[string]bool)
hasPolicies := len(policies.Items) > 0

for _, policy := range policies.Items {
// ... existing policy handling code ...

// Get the pods targeted by this policy
selector, err := metav1.LabelSelectorAsSelector(&policy.Spec.PodSelector)
Expand All @@ -82,10 +105,13 @@ func ScanNetworkPolicies() {
}
}

var tableOutput strings.Builder

if !hasPolicies || !hasDefaultDenyAllPolicy(policies.Items) {
allPods, err := clientset.CoreV1().Pods(ns.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
fmt.Printf("Error listing all pods in namespace %s: %s\n", ns.Name, err)
errorMsg := fmt.Sprintf("Error listing all pods in namespace %s: %s\n", ns.Name, err)
printToBoth(writer, errorMsg)
continue
}

Expand All @@ -102,44 +128,83 @@ func ScanNetworkPolicies() {
}
}

if unprotectedPods {
if len(unprotectedPodDetails) > 0 {
fmt.Printf("\nNetfetch found the following unprotected pods:\n\n")
fmt.Printf("%-30s %-20s %-15s\n", "Namespace", "Pod Name", "Pod IP")
for _, detail := range unprotectedPodDetails {
fmt.Println(detail)
if !hasDefaultDenyAll {
if unprotectedPods {
if len(unprotectedPodDetails) > 0 {
tableOutput.WriteString("\nNetfetch found the following unprotected pods:\n\n")
tableOutput.WriteString(fmt.Sprintf("%-30s %-20s %-15s\n", "Namespace", "Pod Name", "Pod IP"))
for _, detail := range unprotectedPodDetails {
tableOutput.WriteString(detail + "\n")
}
fmt.Print(tableOutput.String()) // Print the table only to standard output
writer.WriteString(tableOutput.String()) // Add the table content to the buffer
}
}

confirm := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("Do you want to add a default deny all network policy to the namespace %s?", ns.Name),
}
survey.AskOne(prompt, &confirm, nil)

if confirm {
err := createAndApplyDefaultDenyPolicy(clientset, ns.Name)
if err != nil {
fmt.Printf("\nFailed to apply default deny policy in namespace %s: %s\n", ns.Name, err)
confirm := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("Do you want to add a default deny all network policy to the namespace %s?", ns.Name),
}
survey.AskOne(prompt, &confirm, nil)

if confirm {
err := createAndApplyDefaultDenyPolicy(clientset, ns.Name)
if err != nil {
errorPolicyMsg := fmt.Sprintf("\nFailed to apply default deny policy in namespace %s: %s\n", ns.Name, err)
printToBoth(writer, errorPolicyMsg)
} else {
successPolicyMsg := fmt.Sprintf("\nApplied default deny policy in namespace %s\n", ns.Name)
printToBoth(writer, successPolicyMsg)
}
} else {
fmt.Printf("\nApplied default deny policy in namespace %s\n", ns.Name)
userDeniedPolicyApplication = true
deniedNamespaces = append(deniedNamespaces, ns.Name) // Add namespace to the list
}
} else {
userDeniedPolicyApplication = true
}
}
}
}

// Write to buffer instead of directly to stdout
writer.Flush()

// Ask the user whether to save the output to a file
// Only prompt for saving to file if the buffer is not empty
if output.Len() > 0 {
saveToFile := false
prompt := &survey.Confirm{
Message: "Do you want to save the output to netfetch.txt?",
}
survey.AskOne(prompt, &saveToFile, nil)

if saveToFile {
err := os.WriteFile("netfetch.txt", output.Bytes(), 0644)
if err != nil {
errorFileMsg := fmt.Sprintf("Error writing to file: %s\n", err)
printToBoth(writer, errorFileMsg)
} else {
successFileMsg := "Output file created: netfetch.txt\n"
printToBoth(writer, successFileMsg)
}
} else {
noFileMsg := "Output file not created.\n"
printToBoth(writer, noFileMsg)
}

}

// Print appropriate message based on scan results
if missingPoliciesOrUncoveredPods {
if userDeniedPolicyApplication {
fmt.Println("\nYou should assess the need of implementing either an implicit default deny all network policy or a network policy that targets the pods currently not selected by a network policy. Check the Kubernetes documentation for more information on network policies: https://kubernetes.io/docs/concepts/services-networking/network-policies/")
printToBoth(writer, "\nFor the following namespaces, you should assess the need of implementing network policies:\n")
for _, ns := range deniedNamespaces {
fmt.Println(" -", ns)
}
printToBoth(writer, "\nConsider either an implicit default deny all network policy or a policy that targets the pods not selected by a network policy. Check the Kubernetes documentation for more information on network policies: https://kubernetes.io/docs/concepts/services-networking/network-policies/\n")
} else {
fmt.Println("\nNetfetch scan completed.")
printToBoth(writer, "\nNetfetch scan completed!\n")
}
} else {
fmt.Println("\nNo network policies missing. You are good to go!")
printToBoth(writer, "\nNo network policies missing. You are good to go!\n")
}
}

Expand Down

0 comments on commit 2e0d4a1

Please sign in to comment.