Skip to content

Commit

Permalink
Merge pull request #202 from deggja/feat/kubeconfig-flag
Browse files Browse the repository at this point in the history
feat: add ability to use optional kubeconfig flag to all commands
  • Loading branch information
deggja authored Nov 15, 2024
2 parents 6d07c07 + 6010053 commit c197371
Show file tree
Hide file tree
Showing 7 changed files with 417 additions and 368 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ Run `netfetch` in dryrun against a cluster.
netfetch scan --dryrun
```

You can also specify the desired kubeconfig file by using the `--kubeconfig /path/to/config` flag.

```sh
netfetch scan --kubeconfig /Users/xxx/.kube/config
```

Run `netfetch` in dryrun against a namespace

```sh
Expand Down Expand Up @@ -188,7 +194,7 @@ The Netfetch Dashboard offers an intuitive interface for interacting with your K

### Netfetch score 🥇

The `netfetch` tool provides a basic score at the end of each scan. The score ranges from 1 to 42, with 1 being the lowest and 42 being the highest possible score.
The `netfetch` tool provides a basic score at the end of each scan. The score ranges from 1 to 100, with 1 being the lowest and 100 being the highest possible score.

Your score will decrease based on the amount of workloads in your cluster that are running without being targeted by a network policy.

Expand Down
202 changes: 19 additions & 183 deletions backend/cmd/dash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
Expand All @@ -23,25 +22,19 @@ var dashCmd = &cobra.Command{
Short: "Launch the Netfetch interactive dashboard",
Run: func(cmd *cobra.Command, args []string) {
port, _ := cmd.Flags().GetString("port")
startDashboardServer(port)
startDashboardServer(port, kubeconfigPath)
},
}

func init() {
rootCmd.AddCommand(dashCmd)

dashCmd.Flags().StringP("port", "p", "8080", "Port for the interactive dashboard")
}

func setNoCacheHeaders(w http.ResponseWriter) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
}

func startDashboardServer(port string) {
func startDashboardServer(port string, kubeconfigPath string) {
// Verify connection to cluster or throw error
clientset, err := k8s.GetClientset()
clientset, err := k8s.GetClientset(kubeconfigPath)
if err != nil {
log.Fatalf("You are not connected to a Kubernetes cluster. Please connect to a cluster and re-run the command: %v", err)
return
Expand Down Expand Up @@ -72,16 +65,16 @@ func startDashboardServer(port string) {

// Set up handlers
http.HandleFunc("/", dashboardHandler)
http.HandleFunc("/scan", k8s.HandleScanRequest)
http.HandleFunc("/namespaces", k8s.HandleNamespaceListRequest)
http.HandleFunc("/add-policy", k8s.HandleAddPolicyRequest)
http.HandleFunc("/create-policy", HandleCreatePolicyRequest)
http.HandleFunc("/namespaces-with-policies", handleNamespacesWithPoliciesRequest)
http.HandleFunc("/namespace-policies", handleNamespacePoliciesRequest)
http.HandleFunc("/visualization", k8s.HandleVisualizationRequest)
http.HandleFunc("/visualization/cluster", handleClusterVisualizationRequest)
http.HandleFunc("/policy-yaml", k8s.HandlePolicyYAMLRequest)
http.HandleFunc("/pod-info", handlePodInfoRequest)
http.HandleFunc("/scan", k8s.HandleScanRequest(kubeconfigPath))
http.HandleFunc("/namespaces", k8s.HandleNamespaceListRequest(kubeconfigPath))
http.HandleFunc("/add-policy", k8s.HandleAddPolicyRequest(kubeconfigPath))
http.HandleFunc("/create-policy", k8s.HandleCreatePolicyRequest(kubeconfigPath))
http.HandleFunc("/namespaces-with-policies", k8s.HandleNamespacesWithPoliciesRequest(kubeconfigPath))
http.HandleFunc("/namespace-policies", k8s.HandleNamespacePoliciesRequest(kubeconfigPath))
http.HandleFunc("/visualization", k8s.HandleVisualizationRequest(kubeconfigPath))
http.HandleFunc("/visualization/cluster", k8s.HandleClusterVisualizationRequest(kubeconfigPath))
http.HandleFunc("/policy-yaml", k8s.HandlePolicyYAMLRequest(kubeconfigPath))
http.HandleFunc("/pod-info", k8s.HandlePodInfoRequest(kubeconfigPath))

// Wrap the default serve mux with the CORS middleware
handler := c.Handler(http.DefaultServeMux)
Expand Down Expand Up @@ -125,169 +118,6 @@ func dashboardHandler(w http.ResponseWriter, r *http.Request) {
http.FileServer(statikFS).ServeHTTP(w, r)
}

// handleNamespacesWithPoliciesRequest handles the HTTP request for serving a list of namespaces with network policies.
func handleNamespacesWithPoliciesRequest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}

clientset, err := k8s.GetClientset()
if err != nil {
log.Fatalf("You are not connected to a Kubernetes cluster. Please connect to a cluster and re-run the command: %v", err)
return
}

namespaces, err := k8s.GatherNamespacesWithPolicies(clientset)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

setNoCacheHeaders(w)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(struct {
Namespaces []string `json:"namespaces"`
}{Namespaces: namespaces}); err != nil {
http.Error(w, "Failed to encode namespaces data", http.StatusInternalServerError)
}
}

// handleNamespacePoliciesRequest handles the HTTP request for serving a list of network policies in a namespace.
func handleNamespacePoliciesRequest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}

// Extract the namespace parameter from the query string
namespace := r.URL.Query().Get("namespace")
if namespace == "" {
http.Error(w, "Namespace parameter is required", http.StatusBadRequest)
return
}

// Obtain the Kubernetes clientset
clientset, err := k8s.GetClientset()
if err != nil {
http.Error(w, fmt.Sprintf("Failed to create Kubernetes client: %v", err), http.StatusInternalServerError)
return
}

// Fetch network policies from the specified namespace
policies, err := clientset.NetworkingV1().NetworkPolicies(namespace).List(context.Background(), metav1.ListOptions{})
if err != nil {
http.Error(w, fmt.Sprintf("Failed to get network policies: %v", err), http.StatusInternalServerError)
return
}

// Convert the list of network policies to a more simple structure if needed or encode directly
// For example, you might want to return only the names and some identifiers of the policies

setNoCacheHeaders(w)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(policies)
}

// handleClusterVisualizationRequest handles the HTTP request for serving cluster-wide visualization data.
func handleClusterVisualizationRequest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}

clientset, err := k8s.GetClientset()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// Call the function to gather cluster-wide visualization data
clusterVizData, err := k8s.GatherClusterVisualizationData(clientset)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

setNoCacheHeaders(w)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(clusterVizData); err != nil {
http.Error(w, "Failed to encode cluster visualization data", http.StatusInternalServerError)
}
}

// handlePodInfoRequest handles the HTTP request for serving pod information.
func handlePodInfoRequest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}

// Extract the namespace parameter from the query string
namespace := r.URL.Query().Get("namespace")
if namespace == "" {
http.Error(w, "Namespace parameter is required", http.StatusBadRequest)
return
}

// Obtain the Kubernetes clientset
clientset, err := k8s.GetClientset()
if err != nil {
http.Error(w, fmt.Sprintf("Failed to create Kubernetes client: %v", err), http.StatusInternalServerError)
return
}

// Fetch pod information from the specified namespace
podInfo, err := k8s.GetPodInfo(clientset, namespace)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to get pod information: %v", err), http.StatusInternalServerError)
return
}

setNoCacheHeaders(w)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(podInfo)
}

// HandleCreatePolicyRequest handles the HTTP request to create a network policy from YAML.
func HandleCreatePolicyRequest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}

var policyRequest struct {
YAML string `json:"yaml"`
Namespace string `json:"namespace"`
}
if err := json.NewDecoder(r.Body).Decode(&policyRequest); err != nil {
http.Error(w, fmt.Sprintf("Failed to decode request body: %v", err), http.StatusBadRequest)
return
}
defer r.Body.Close()

clientset, err := k8s.GetClientset()
if err != nil {
http.Error(w, fmt.Sprintf("Failed to create Kubernetes client: %v", err), http.StatusInternalServerError)
return
}

networkPolicy, err := k8s.YAMLToNetworkPolicy(policyRequest.YAML)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to parse network policy YAML: %v", err), http.StatusBadRequest)
return
}

createdPolicy, err := clientset.NetworkingV1().NetworkPolicies(policyRequest.Namespace).Create(context.Background(), networkPolicy, metav1.CreateOptions{})
if err != nil {
http.Error(w, fmt.Sprintf("Failed to create network policy: %v", err), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(createdPolicy)
}

var HeaderStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("6")).
Expand All @@ -298,3 +128,9 @@ var HeaderStyle = lipgloss.NewStyle().
PaddingRight(4).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("99"))

func init() {
dashCmd.Flags().StringVar(&kubeconfigPath, "kubeconfig", "", "Path to the kubeconfig file (optional)")
dashCmd.Flags().StringP("port", "p", "8080", "Port for the interactive dashboard")
rootCmd.AddCommand(dashCmd)
}
24 changes: 13 additions & 11 deletions backend/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import (
)

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

var scanCmd = &cobra.Command{
Expand All @@ -33,12 +34,12 @@ var scanCmd = &cobra.Command{
}

// Initialize the Kubernetes clients
clientset, err := k8s.GetClientset()
clientset, err := k8s.GetClientset(kubeconfigPath)
if err != nil {
fmt.Println("Error creating Kubernetes client:", err)
return
}
dynamicClient, err := k8s.GetCiliumDynamicClient()
dynamicClient, err := k8s.GetCiliumDynamicClient(kubeconfigPath)
if err != nil {
fmt.Println("Error creating Kubernetes dynamic client:", err)
return
Expand Down Expand Up @@ -115,7 +116,7 @@ var scanCmd = &cobra.Command{
// 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...")
nativeScanResult, err := k8s.ScanNetworkPolicies(namespace, dryRun, false, true, true, true)
nativeScanResult, err := k8s.ScanNetworkPolicies(namespace, dryRun, false, true, true, true, kubeconfigPath)
if err != nil {
fmt.Println("Error during Kubernetes native network policies scan:", err)
} else {
Expand All @@ -129,13 +130,13 @@ var scanCmd = &cobra.Command{
// Perform cluster wide Cilium scan first if no namespace is specified
if namespace == "" {
fmt.Println("Running cluster wide Cilium network policies scan...")
dynamicClient, err := k8s.GetCiliumDynamicClient()
dynamicClient, err := k8s.GetCiliumDynamicClient(kubeconfigPath)
if err != nil {
fmt.Println("Error obtaining dynamic client:", err)
return
}

clusterwideScanResult, err := k8s.ScanCiliumClusterwideNetworkPolicies(dynamicClient, false, dryRun, true)
clusterwideScanResult, err := k8s.ScanCiliumClusterwideNetworkPolicies(dynamicClient, false, dryRun, true, kubeconfigPath)
if err != nil {
fmt.Println("Error during cluster wide Cilium network policies scan:", err)
} else {
Expand All @@ -150,7 +151,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)
ciliumScanResult, err := k8s.ScanCiliumNetworkPolicies(namespace, dryRun, false, true, true, true, kubeconfigPath)
if err != nil {
fmt.Println("Error during Cilium network policies scan:", err)
} else {
Expand Down Expand Up @@ -196,6 +197,7 @@ func createTargetPodsTable(pods [][]string) string {
}

func init() {
scanCmd.Flags().StringVar(&kubeconfigPath, "kubeconfig", "", "Path to the kubeconfig file (optional)")
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)")
Expand Down
Loading

0 comments on commit c197371

Please sign in to comment.