Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: format target policy scanner output #147

Merged
merged 5 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions .github/scripts/bump_version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ if [ ! -z "$latest_tag" ]; then
IFS='.' read -r major minor patch <<< "${latest_tag}"
fi

new_tag="$major.$minor.$patch"

# Analyze commit messages since the last tag for versioning
for commit in $(git rev-list $latest_tag..HEAD); do
message=$(git log --format=%B -n 1 $commit)
Expand All @@ -22,20 +24,26 @@ for commit in $(git rev-list $latest_tag..HEAD); do
let major+=1
minor=0
patch=0
new_tag="${major}.${minor}.${patch}"
break
elif [[ $message == *"#minor"* ]]; then
let minor+=1
patch=0
new_tag="${major}.${minor}.${patch}"
elif [[ $message == *"#patch"* ]]; then
let patch+=1
new_tag="${major}.${minor}.${patch}"
fi
done

new_tag="${major}.${minor}.${patch}"

# Set output for the next steps using environment file
echo "new_tag=$new_tag" >> $GITHUB_ENV
# Check if new version is different from the latest tag
if [ "$new_tag" != "$latest_tag" ]; then
# Set output for the next steps using environment file
echo "new_tag=$new_tag" >> $GITHUB_ENV

# Create the new tag
git tag $new_tag
git push --tags
# Create the new tag
git tag $new_tag
git push --tags
else
echo "new_tag=" >> $GITHUB_ENV
fi
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,16 @@ jobs:
run: |
chmod +x .github/scripts/bump_version.sh
.github/scripts/bump_version.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Build Application
if: env.new_tag != ''
run: |
go build -ldflags "-X 'github.com/deggja/netfetch/backend/cmd.Version=${{ env.new_tag }}'" -o netfetch

- name: Run GoReleaser
if: env.new_tag != ''
uses: goreleaser/goreleaser-action@v2
with:
version: latest
Expand Down
93 changes: 47 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,24 @@

This project aims to demystify network policies in Kubernetes. It's a work in progress!

The `netfetch` tool is designed to scan Kubernetes namespaces for network policies, checking if your workloads are targeted by a network policy or not.

What can I use `netfetch` for? 🤔

CLI:
- Scan your Kubernetes cluster or namespace to identify pods running with no ingress and egress restrictions.
- Save the output of your scans in a text file to analyze.
- Create implicit default deny network policies in namespaces that do not have one.
- Get a score calculated for your cluster or namespace based on the findings of the scans.

Dashboard:
- Scan your cluster or namespace and list pods running without network restrictions in a table.
- Visualise all existing network policies and pods in your cluster or namespace in a network map you can interact with.
- Double click a network policy in a network map to preview the YAML of that policy.
- Create implicit default deny network policies in namespaces that do not have one.
- Get suggestions for network policies that you can edit & apply to your namespaces by analysing existing pods.
- Get a score calculated for your cluster or namespace based on the findings of the scans.
The `netfetch` tool will scan your Kubernetes cluster and let you know if you have any pods running without being targeted by network policies.

| Feature | CLI | Dashboard |
|------------------------------------------------------------------------|------|-----------|
| Scan cluster identify pods without network policies | ✓ | ✓ |
| Save scan output to a text file | ✓ | |
| Visualize network policies and pods in a interactive network map | | ✓ |
| Create default deny network policies where this is missing | ✓ | ✓ |
| Get suggestions for network policies based on existing workloads | | ✓ |
| Calculate a security score based on scan findings | ✓ | ✓ |
| Scan a specific policy by name to see what pods it targets | ✓ | |

### NetworkPolicy type support in Netfetch

Dashboard:
* Kubernetes

CLI:
* Kubernetes
* Cilium
| Type | CLI | Dashboard |
|-----------|------|-----------|
| Kubernetes| ✓ | ✓ |
| Cilium | ✓ | |

Support for additional types of network policies is in the works. No support for the type you need? Check out [issues](https://github.com/deggja/netfetch/issues) for an existing request or create a new one if there is none.

Expand Down Expand Up @@ -118,7 +110,7 @@ netfetch scan --dryrun
Run `netfetch` in dryrun against a namespace

```sh
netfetch scan production --dryrun
netfetch scan crossplane-system --dryrun
```

![netfetch-demo](https://github.com/deggja/netfetch/assets/15778492/015e9d9f-a678-4a14-a8bd-607f02c13d9f)
Expand All @@ -129,61 +121,70 @@ Scan entire cluster.
netfetch scan
```

Scan a namespace called production.
Scan a namespace called crossplane-system.

```sh
netfetch scan production
netfetch scan crossplane-system
```

Scan entire cluster for Cilium Network Policies.
Scan entire cluster for Cilium Network Policies and or Cluster Wide Cilium Network Policies.

```sh
netfetch scan --cilium
```

Scan a namespace called production.
Scan a namespace called production for regular Cilium Network Policies.

```sh
netfetch scan production --cilium
```

### Using the dashboard 📟

Launch the dashboard:
Scan a specific network policy.

```sh
netfetch dash
netfetch scan --target my-policy-name
```

You may also specify a port for the dashboard to run on (default is 8080).
Scan a specific Cilium Network Policy.

```sh
netfetch dash --port 8081
netfetch scan --cilium --target default-cilium-default-deny-all
```

While in the dashboard, you have a couple of options.
[![asciicast](https://asciinema.org/a/661200.svg)](https://asciinema.org/a/661200)

You can use the `Scan cluster` button, which is the equivalent to the CLI `netfetch scan` command. This will populate the table view with all pods not targeted by a network policy.
### Using the dashboard 📟

Scanning a specific namespace is done by selecting the namespace of choice from the `Select a namespace` dropdown and using the `Scan namespace` button. This is the equivalent to the CLI `netfetch scan namespace` command.
Launch the dashboard:

This will populate the table view with all pods not targeted by a network policy in that specific namespace. In addition to this, if there are any pods in the cluster already targeted by a network policy - it will create a visualisation of this in a network map rendered using [D3](https://d3-graph-gallery.com/network.html) below the table view.
```sh
netfetch dash
```

![Netfetch Dashboard](https://github.com/deggja/netfetch/blob/main/frontend/dash/src/assets/new-dash.png)
You may also specify a port for the dashboard to run on (default is 8080).

You can click the `Create cluster map` button to do exactly that. This will render a network map with D3, fetching all pods and policies in all the namespaces you have access to in the cluster.
```sh
netfetch dash --port 8081
```

![Cluster map](https://github.com/deggja/netfetch/blob/main/frontend/dash/src/assets/new-clustermap.png)
### Dashboard functionality overview

Inside the network map visualisations, you can double click the network policy nodes to preview the YAML of that policy.
The Netfetch Dashboard offers an intuitive interface for interacting with your Kubernetes cluster's network policies. Below is a detailed overview of the functionalities available through the dashboard:

![Network map](https://github.com/deggja/netfetch/blob/main/frontend/dash/src/assets/new-ns.png)
| Action | Description | Screenshot Link |
|----------------------|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
| Scan Cluster | Initiates a cluster-wide scan to identify pods without network policies, similar to `netfetch scan`. | ![Netfetch Dashboard](https://github.com/deggja/netfetch/blob/main/frontend/dash/src/assets/new-dash.png) |
| Scan Namespace | Scans a selected namespace for pods not covered by network policies, equivalent to `netfetch scan namespace`. | ![Cluster map](https://github.com/deggja/netfetch/blob/main/frontend/dash/src/assets/new-clustermap.png) |
| Create Cluster Map | Generates a D3-rendered network map of all pods and policies across accessible namespaces. | ![Network map](https://github.com/deggja/netfetch/blob/main/frontend/dash/src/assets/new-ns.png) |
| Suggest Policy | Provides network policy suggestions based on existing workloads within a selected namespace. | ![Suggested policies](https://github.com/deggja/netfetch/blob/main/frontend/dash/src/assets/new-suggestpolicy.png) |

When scanning a specific namespace using the `Select namespace` dropdown, you may click `Suggest policy` to get network policy suggestions based on your existing workloads.
### Interactive Features

![Suggested policies](https://github.com/deggja/netfetch/blob/main/frontend/dash/src/assets/new-suggestpolicy.png)
- **Table View**: Shows pods not targeted by network policies. It updates based on the cluster or namespace scans.
- **Network Map Visualization**: Rendered using D3 to show how pods and policies interact within the cluster.
- **Policy Preview**: Double-click network policy nodes within the network map to view policy YAML.
- **Policy Editing**: Edit suggested policies directly within the dashboard or copy the YAML for external use.

You may also edit the suggestions inline by using the "Edit" button or copy the YAML of the policy and use it outside of netfetch.

### Netfetch score 🥇

Expand Down
76 changes: 52 additions & 24 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
pods, err := k8s.ListPodsTargetedByCiliumClusterWideNetworkPolicy(dynamicClient, policy)
// List the pods targeted by this cluster wide policy
pods, err := k8s.ListPodsTargetedByCiliumClusterWideNetworkPolicy(clientset, 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,40 @@ func handleScanResult(scanResult *k8s.ScanResult) {
// Implement your logic to handle scan results
}

var (
headerStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("6")).Align(lipgloss.Center)
evenRowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6"))
oddRowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6"))
tableBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("99"))
)

// Function to create a table for pods
func createTargetPodsTable(pods [][]string, namespace string) string {
t := table.New().
Border(lipgloss.NormalBorder()).
BorderStyle(tableBorderStyle).
StyleFunc(func(row, col int) lipgloss.Style {
if row == 0 {
return headerStyle
}
if row%2 == 0 {
return evenRowStyle
}
return oddRowStyle
}).
Headers("Namespace", "Pod Name", "IP Address")

for _, podDetails := range pods {
t.Row(podDetails...)
}

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
Loading
Loading