mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0992e6edd7 | ||
|
|
12294ad23c | ||
|
|
b2d14a778a | ||
|
|
b92a44dd7c | ||
|
|
35ea718080 | ||
|
|
47ed057e66 | ||
|
|
5e0e5c6231 | ||
|
|
4f9a4a6c61 | ||
|
|
22b3544243 | ||
|
|
297a4fc42b | ||
|
|
405cd837a1 | ||
|
|
e6b2688462 |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -50,4 +50,4 @@ jobs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: build/kubescape
|
||||
asset_name: kubescape
|
||||
asset_content_type: application/octet-stream
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
28
README.md
28
README.md
@@ -3,7 +3,7 @@
|
||||
Kubescape is the first tool for testing if Kubernetes is deployed securely as defined in [Kubernetes Hardening Guidance by to NSA and CISA](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/)
|
||||
Tests are configured with YAML files, making this tool easy to update as test specifications evolve.
|
||||
|
||||
<img src="docs/using-mov.gif">
|
||||
<img src="docs/demo.gif">
|
||||
|
||||
# TL;DR
|
||||
## Installation
|
||||
@@ -24,7 +24,7 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
|
||||
If you wish to scan all namespaces in your cluster, remove the `--exclude-namespaces` flag.
|
||||
|
||||
<img src="docs/run.jpeg">
|
||||
<img src="docs/summery.PNG">
|
||||
|
||||
|
||||
# Status
|
||||
@@ -32,7 +32,23 @@ If you wish to scan all namespaces in your cluster, remove the `--exclude-namesp
|
||||
[]()
|
||||
|
||||
# How to build
|
||||
`go mod tidy && go build -o kubescape` :zany_face:
|
||||
|
||||
1. Clone Project
|
||||
```
|
||||
git clone git@github.com:armosec/kubescape.git kubescape && cd "$_"
|
||||
```
|
||||
|
||||
2. Build
|
||||
```
|
||||
go mod tidy && go build -o kubescape .
|
||||
```
|
||||
|
||||
3. Run
|
||||
```
|
||||
./kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
|
||||
```
|
||||
|
||||
4. Enjoy :zany_face:
|
||||
|
||||
# Under the hood
|
||||
|
||||
@@ -48,6 +64,12 @@ Kubescape is running the following tests according to what is defined by [Kubern
|
||||
* Resource policies
|
||||
* Control plane hardening
|
||||
* Exposed dashboard
|
||||
* Allow privilege escalation
|
||||
* Applications credentials in configuration files
|
||||
* Cluster-admin binding
|
||||
* Exec into container
|
||||
* Dangerous capabilities
|
||||
* Insecure capabilities
|
||||
|
||||
|
||||
## Technology
|
||||
|
||||
@@ -27,13 +27,16 @@ type KubernetesApi struct {
|
||||
|
||||
// NewKubernetesApi -
|
||||
func NewKubernetesApi() *KubernetesApi {
|
||||
|
||||
kubernetesClient, err := kubernetes.NewForConfig(GetK8sConfig())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("kubernetes.NewForConfig - Failed to load config file, reason: %s", err.Error()))
|
||||
fmt.Printf("Failed to load config file, reason: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
dynamicClient, err := dynamic.NewForConfig(GetK8sConfig())
|
||||
dynamicClient, err := dynamic.NewForConfig(K8SConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("dynamic.NewForConfig - Failed to load config file, reason: %s", err.Error()))
|
||||
fmt.Printf("Failed to load config file, reason: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return &KubernetesApi{
|
||||
@@ -50,11 +53,11 @@ var RunningIncluster bool
|
||||
func LoadK8sConfig() error {
|
||||
kubeconfig, err := clientcmd.BuildConfigFromFlags("", ConfigPath)
|
||||
if err != nil {
|
||||
kubeconfig, err = restclient.InClusterConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load kubernetes config from file: '%s', err: %v", ConfigPath, err)
|
||||
}
|
||||
RunningIncluster = true
|
||||
// kubeconfig, err = restclient.InClusterConfig()
|
||||
// if err != nil {
|
||||
return fmt.Errorf("Failed to load kubernetes config from file: '%s'.\n", ConfigPath)
|
||||
// }
|
||||
// RunningIncluster = true
|
||||
} else {
|
||||
RunningIncluster = false
|
||||
}
|
||||
@@ -66,7 +69,9 @@ func LoadK8sConfig() error {
|
||||
func GetK8sConfig() *restclient.Config {
|
||||
if K8SConfig == nil {
|
||||
if err := LoadK8sConfig(); err != nil {
|
||||
return nil
|
||||
// print error
|
||||
fmt.Printf("%s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
return K8SConfig
|
||||
|
||||
@@ -28,7 +28,7 @@ type RuleResponse struct {
|
||||
|
||||
type AlertObject struct {
|
||||
K8SApiObjects []map[string]interface{} `json:"k8sApiObjects,omitempty"`
|
||||
ExternalObjects []map[string]interface{} `json:"externalObjects,omitempty"`
|
||||
ExternalObjects map[string]interface{} `json:"externalObjects,omitempty"`
|
||||
}
|
||||
|
||||
type FrameworkReport struct {
|
||||
|
||||
BIN
docs/demo.gif
Executable file
BIN
docs/demo.gif
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 897 KiB |
BIN
docs/summery.PNG
Executable file
BIN
docs/summery.PNG
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
@@ -8,8 +8,6 @@ import (
|
||||
|
||||
"kube-escape/cautils/armotypes"
|
||||
"kube-escape/cautils/opapolicy"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type CLIHandler struct {
|
||||
@@ -43,7 +41,8 @@ func (clihandler *CLIHandler) Scan() error {
|
||||
case opapolicy.TypeExecPostureScan:
|
||||
go func() {
|
||||
if err := clihandler.policyHandler.HandleNotificationRequest(policyNotification); err != nil {
|
||||
glog.Error(err)
|
||||
fmt.Printf("%v\n", err)
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
default:
|
||||
|
||||
@@ -7,9 +7,9 @@ echo
|
||||
BASE_DIR=~/.kubescape
|
||||
KUBESCAPE_EXEC=kubescape
|
||||
|
||||
RELEASE=v0.0.29
|
||||
|
||||
DOWNLOAD_URL="https://github.com/armosec/kubescape/releases/download/$RELEASE/kubescape"
|
||||
DOWNLOAD_URL=$(curl --silent "https://api.github.com/repos/armosec/kubescape/releases/latest" | grep -o "browser_download_url.*")
|
||||
DOWNLOAD_URL=${DOWNLOAD_URL//\"}
|
||||
DOWNLOAD_URL=${DOWNLOAD_URL/browser_download_url: /}
|
||||
|
||||
mkdir -p $BASE_DIR
|
||||
|
||||
|
||||
2
main.go
2
main.go
@@ -24,6 +24,7 @@ func main() {
|
||||
|
||||
func CliSetup() error {
|
||||
k8s := k8sinterface.NewKubernetesApi()
|
||||
|
||||
processNotification := make(chan *cautils.OPASessionObj)
|
||||
reportResults := make(chan *cautils.OPASessionObj)
|
||||
|
||||
@@ -42,7 +43,6 @@ func CliSetup() error {
|
||||
reporterObj := opaprocessor.NewOPAProcessor(&processNotification, &reportResults)
|
||||
reporterObj.ProcessRulesListenner()
|
||||
}()
|
||||
|
||||
p := printer.NewPrinter(&reportResults)
|
||||
p.ActionPrint()
|
||||
|
||||
|
||||
@@ -138,23 +138,13 @@ func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *opapoli
|
||||
// backend
|
||||
receivedFrameworks, err := d.OPAFRAMEWORKGet(rule.Name)
|
||||
if err != nil {
|
||||
errs = fmt.Errorf("%v\nKind: %v, Name: %s, error: %s", errs, rule.Kind, rule.Name, err.Error())
|
||||
errs = fmt.Errorf("Could not download framework, please check if this framework exists")
|
||||
}
|
||||
frameworks = append(frameworks, receivedFrameworks...)
|
||||
case opapolicy.KindControl:
|
||||
receivedControls := []opapolicy.Control{} //, err := policyHandler.cacli.OPAFRAMEWORKGet(rule.Name, !k8sinterface.RunningIncluster)
|
||||
|
||||
// receivedControls, err := policyHandler.cacli.OPACONTROLGet(rule.Name)
|
||||
// if err != nil {
|
||||
// errs = fmt.Errorf("%v\nKind: %v, Name: %s, error: %s", errs, rule.Kind, rule.Name, err.Error())
|
||||
// }
|
||||
framework := opapolicy.Framework{ // TODO - wrap control by framework properly
|
||||
Controls: receivedControls,
|
||||
}
|
||||
frameworks = append(frameworks, framework)
|
||||
default:
|
||||
err := fmt.Errorf("missing rule kind, expected: %s", opapolicy.KindFramework)
|
||||
errs = fmt.Errorf("%v\nerror: %s", errs, err.Error())
|
||||
err := fmt.Errorf("Missing rule kind, expected: %s", opapolicy.KindFramework)
|
||||
errs = fmt.Errorf("%s", err.Error())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,20 +4,21 @@ import (
|
||||
"fmt"
|
||||
"kube-escape/cautils"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"kube-escape/cautils/k8sinterface"
|
||||
"kube-escape/cautils/opapolicy"
|
||||
|
||||
"github.com/enescakir/emoji"
|
||||
"github.com/golang/glog"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var INDENT = " "
|
||||
|
||||
type Printer struct {
|
||||
opaSessionObj *chan *cautils.OPASessionObj
|
||||
summery Summery
|
||||
opaSessionObj *chan *cautils.OPASessionObj
|
||||
summery Summery
|
||||
sortedControlNames []string
|
||||
}
|
||||
|
||||
func NewPrinter(opaSessionObj *chan *cautils.OPASessionObj) *Printer {
|
||||
@@ -29,12 +30,6 @@ func NewPrinter(opaSessionObj *chan *cautils.OPASessionObj) *Printer {
|
||||
|
||||
func (printer *Printer) ActionPrint() {
|
||||
|
||||
// recover
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
glog.Errorf("RECOVER in ActionSendReportListenner, reason: %v", err)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
opaSessionObj := <-*printer.opaSessionObj
|
||||
|
||||
@@ -65,15 +60,18 @@ func (printer *Printer) SummerySetup(postureReport *opapolicy.PostureReport) {
|
||||
}
|
||||
}
|
||||
}
|
||||
printer.sortedControlNames = printer.getSortedControlsNames()
|
||||
|
||||
}
|
||||
|
||||
func (printer *Printer) PrintResults() {
|
||||
for control, controlSummery := range printer.summery {
|
||||
printer.printTitle(control, &controlSummery)
|
||||
printer.printResult(control, &controlSummery)
|
||||
for i := 0; i < len(printer.sortedControlNames); i++ {
|
||||
controlSummery := printer.summery[printer.sortedControlNames[i]]
|
||||
printer.printTitle(printer.sortedControlNames[i], &controlSummery)
|
||||
printer.printResult(printer.sortedControlNames[i], &controlSummery)
|
||||
|
||||
if controlSummery.TotalResources > 0 {
|
||||
printer.printSummery(control, &controlSummery)
|
||||
if printer.summery[printer.sortedControlNames[i]].TotalResources > 0 {
|
||||
printer.printSummery(printer.sortedControlNames[i], &controlSummery)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -147,7 +145,6 @@ func generateFooter(numControlers, sumFailed, sumTotal int) []string {
|
||||
row = append(row, fmt.Sprintf("%d%s", percentage(sumTotal, sumFailed), "%"))
|
||||
return row
|
||||
}
|
||||
|
||||
func (printer *Printer) PrintSummaryTable() {
|
||||
summaryTable := tablewriter.NewWriter(os.Stdout)
|
||||
summaryTable.SetAutoWrapText(false)
|
||||
@@ -157,11 +154,21 @@ func (printer *Printer) PrintSummaryTable() {
|
||||
sumTotal := 0
|
||||
sumFailed := 0
|
||||
|
||||
for k, v := range printer.summery {
|
||||
summaryTable.Append(generateRow(k, v))
|
||||
sumFailed += v.TotalFailed
|
||||
sumTotal += v.TotalResources
|
||||
for i := 0; i < len(printer.sortedControlNames); i++ {
|
||||
controlSummery := printer.summery[printer.sortedControlNames[i]]
|
||||
summaryTable.Append(generateRow(printer.sortedControlNames[i], controlSummery))
|
||||
sumFailed += controlSummery.TotalFailed
|
||||
sumTotal += controlSummery.TotalResources
|
||||
}
|
||||
summaryTable.SetFooter(generateFooter(len(printer.summery), sumFailed, sumTotal))
|
||||
summaryTable.Render()
|
||||
}
|
||||
|
||||
func (printer *Printer) getSortedControlsNames() []string {
|
||||
controlNames := make([]string, 0, len(printer.summery))
|
||||
for k := range printer.summery {
|
||||
controlNames = append(controlNames, k)
|
||||
}
|
||||
sort.Strings(controlNames)
|
||||
return controlNames
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user