Compare commits

...

12 Commits

Author SHA1 Message Date
dwertent
0992e6edd7 sort summary table 2021-08-20 00:07:29 +03:00
dwertent
12294ad23c download latest release 2021-08-19 23:23:07 +03:00
David Wertenteil
b2d14a778a Sort controls, update gif
Sort control output
2021-08-19 22:45:01 +03:00
dwertent
b92a44dd7c sort control output 2021-08-19 22:41:35 +03:00
Daniel Grunberger
35ea718080 Add new tests 2021-08-19 17:50:06 +03:00
Daniel Grunberger
47ed057e66 Update tag 2021-08-19 17:45:59 +03:00
David Wertenteil
5e0e5c6231 do not run flow on push 2021-08-19 17:43:48 +03:00
David Wertenteil
4f9a4a6c61 update release 2021-08-19 17:37:22 +03:00
David Wertenteil
22b3544243 Update error handling 2021-08-19 17:36:21 +03:00
danielgrunbergerarmo
297a4fc42b fix error handling 2021-08-19 13:42:23 +03:00
Daniel Grunberger
405cd837a1 Update README.md 2021-08-18 18:45:46 +03:00
dwertent
e6b2688462 update install version 2021-08-18 14:56:48 +03:00
11 changed files with 76 additions and 53 deletions

View File

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

View File

@@ -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
[![Github All Releases](https://img.shields.io/github/downloads/armosec/kubescape/total.svg)]()
# 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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 KiB

BIN
docs/summery.PNG Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

View File

@@ -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:

View File

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

View File

@@ -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()

View File

@@ -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())
}
}

View File

@@ -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
}