Compare commits

...

19 Commits

Author SHA1 Message Date
Daniel Grunberger
e5b35fcb55 Merge pull request #6 from Daniel-GrunbergerCA/master
Update output format
2021-08-18 14:54:59 +03:00
Daniel Grunberger
35449e3d4e Merge branch 'master' into master 2021-08-18 14:54:33 +03:00
danielgrunbergerarmo
9509c69d87 update install version 2021-08-18 14:51:05 +03:00
danielgrunbergerarmo
34170faae9 update readme.md 2021-08-18 14:45:49 +03:00
danielgrunbergerarmo
d5d0da8ac3 fix non-namespacd resources 2021-08-18 14:33:14 +03:00
danielgrunbergerarmo
8b7a4b1e48 fix output format 2021-08-18 14:21:44 +03:00
danielgrunbergerarmo
d5383fe218 fix field selector for non-namespaced resources 2021-08-18 14:04:00 +03:00
dwertent
46b9cf35ac update version 2021-08-18 12:05:01 +03:00
danielgrunbergerarmo
329d341fbf delete build.yaml 2021-08-18 11:58:23 +03:00
David Wertenteil
6be692c66f Merge pull request #4 from Daniel-GrunbergerCA/master
add exclude-namespaces flag
2021-08-18 11:56:43 +03:00
danielgrunbergerarmo
3c062238ad add cli flag 2021-08-18 11:48:10 +03:00
Daniel Grunberger
954224e9f6 Update README.md 2021-08-17 17:39:50 +03:00
David Wertenteil
a5f99e0a8d Merge pull request #3 from dwertent/master
Update description display
2021-08-17 16:27:53 +03:00
dwertent
d484aeb62c update description 2021-08-17 16:24:23 +03:00
Daniel Grunberger
8c3eeab7ed Update README.md 2021-08-17 13:37:24 +03:00
Benyamin Hirschberg
cea8266734 Update README.md 2021-08-15 22:35:29 +03:00
Benyamin Hirschberg
eefaf7b23c adding progress bar 2021-08-15 21:53:26 +03:00
Benyamin Hirschberg
bc61755f67 Update install.sh 2021-08-15 21:46:37 +03:00
Benyamin Hirschberg
c462d1ec2f Update build.yaml 2021-08-15 21:43:11 +03:00
12 changed files with 67 additions and 34 deletions

View File

@@ -18,7 +18,7 @@ jobs:
go-version: 1.16
- name: Build
run: mkdir build && go mod tidy && go build -ldflags "-w" -o build/kubescape
run: mkdir build && go mod tidy && go build -ldflags "-w -s" -o build/kubescape
- name: Chmod
run: chmod +x build/kubescape
@@ -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

@@ -9,20 +9,27 @@ Tests are configured with YAML files, making this tool easy to update as test sp
## Installation
To install the tool locally, run this:
`curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh | /bin/bash`
```
curl -s https://raw.githubusercontent.com/armosec/kubescape/master/install.sh | /bin/bash
```
<img src="docs/install.jpeg">
## Run
To get a fast check of the security posture of your Kubernetes cluster, run this:
`kubescape scan framework nsa`
```
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">
# Status
[![build](https://github.com/armosec/kubescape/actions/workflows/build.yaml/badge.svg)](https://github.com/armosec/kubescape/actions/workflows/build.yaml)
[![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:
@@ -33,17 +40,14 @@ To get a fast check of the security posture of your Kubernetes cluster, run this
Kubescape is running the following tests according to what is defined by [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/)
* Non-root containers
* Immutable container filesystem
* Building secure container images
* Privileged containers
* hostPID, hostIPC privileges
* hostNetwork access
* allowedHostPaths field
* Protecting pod service account tokens
* Pods in kube-system and kube-public
* Resource policies
* Control plane hardening
* Encrypted secrets
* Anonymous Requests
* Exposed dashboard
## Technology

View File

@@ -16,6 +16,7 @@ var InfoDisplay = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
var InfoTextDisplay = color.New(color.Faint, color.FgHiYellow).FprintfFunc()
var SimpleDisplay = color.New(color.Bold, color.FgHiWhite).FprintfFunc()
var SuccessDisplay = color.New(color.Bold, color.FgHiGreen).FprintfFunc()
var DescriptionDisplay = color.New(color.Faint, color.FgWhite).FprintfFunc()
var Spinner *spinner.Spinner

View File

@@ -42,11 +42,11 @@ type ControlReport struct {
Description string `json:"description"`
}
type RuleReport struct {
Name string `json:"name"`
Remediation string `json:"remediation"`
RuleStatus RuleStatus `json:"ruleStatus"`
RuleResponses []RuleResponse `json:"ruleResponses"`
NumOfResources int
Name string `json:"name"`
Remediation string `json:"remediation"`
RuleStatus RuleStatus `json:"ruleStatus"`
RuleResponses []RuleResponse `json:"ruleResponses"`
ListInputResources []map[string]interface{} `json:"-"`
}
type RuleStatus struct {
Status string `json:"status"`

View File

@@ -70,7 +70,10 @@ func ParseRegoResult(regoResult *rego.ResultSet) ([]RuleResponse, error) {
func (controlReport *ControlReport) GetNumberOfResources() int {
sum := 0
for i := range controlReport.RuleReports {
sum += controlReport.RuleReports[i].NumOfResources
if controlReport.RuleReports[i].ListInputResources == nil {
continue
}
sum += len(controlReport.RuleReports[i].ListInputResources)
}
return sum
}

View File

@@ -41,11 +41,11 @@ func (flagHandler *FlagHandler) ParseFlag() {
}
func (flagHandler *FlagHandler) Help() {
fmt.Println("Run: kube-escape scan framework nsa")
fmt.Println("Run: kubescape scan framework nsa --exclude-namespaces kube-system,kube-public")
}
func (flagHandler *FlagHandler) Version() {
fmt.Println("bla.bla.bla")
fmt.Println("betav1")
}
func (flagHandler *FlagHandler) Scan() {

View File

@@ -6,14 +6,16 @@ echo
BASE_DIR=~/.kubescape
KUBESCAPE_EXEC=kubescape
RELEASE=v0.0.12
RELEASE=v0.0.29
DOWNLOAD_URL="https://github.com/armosec/kubescape/releases/download/$RELEASE/kubescape"
mkdir -p $BASE_DIR
OUTPUT=$BASE_DIR/$KUBESCAPE_EXEC
curl -sL $DOWNLOAD_URL -o $OUTPUT
curl --progress-bar -L $DOWNLOAD_URL -o $OUTPUT
echo -e "\033[32m[V] Downloaded Kubescape"
sudo chmod +x $OUTPUT
@@ -24,5 +26,5 @@ rm -rf $BASE_DIR
echo -e "[V] Finished Installation"
echo
echo -e "\033[35m Usage: $ $KUBESCAPE_EXEC scan framework nsa"
echo -e "\033[35m Usage: $ $KUBESCAPE_EXEC scan framework nsa --exclude-namespaces kube-system,kube-public"
echo

View File

@@ -87,7 +87,7 @@ func (opap *OPAProcessor) ProcessRulesHandler(opaSessionObj *cautils.OPASessionO
} else {
ruleReport.RuleStatus.Status = "success"
}
ruleReport.NumOfResources = len(k8sObjects)
ruleReport.ListInputResources = k8sObjects
ruleReports = append(ruleReports, ruleReport)
}
controlReport.RuleReports = ruleReports

View File

@@ -1,6 +1,7 @@
package policyhandler
import (
"flag"
"fmt"
"kube-escape/cautils"
@@ -52,7 +53,11 @@ func (policyHandler *PolicyHandler) HandleNotificationRequest(notification *opap
// get k8s resources
cautils.ProgressTextDisplay("Accessing Kubernetes objects")
glog.Infof(fmt.Sprintf("Getting kubernetes objects. reportID: %s", notification.ReportID))
k8sResources, err := policyHandler.getK8sResources(frameworks, &notification.Designators)
excludedNamespaces := ""
if flag.Arg(3) == "--exclude-namespaces" {
excludedNamespaces = flag.Arg(4)
}
k8sResources, err := policyHandler.getK8sResources(frameworks, &notification.Designators, excludedNamespaces)
if err != nil || len(*k8sResources) == 0 {
glog.Error(err)
} else {

View File

@@ -3,6 +3,7 @@ package policyhandler
import (
"fmt"
"kube-escape/cautils"
"strings"
"kube-escape/cautils/k8sinterface"
@@ -18,7 +19,7 @@ import (
const SelectAllResources = "*"
func (policyHandler *PolicyHandler) getK8sResources(frameworks []opapolicy.Framework, designator *armotypes.PortalDesignator) (*cautils.K8SResources, error) {
func (policyHandler *PolicyHandler) getK8sResources(frameworks []opapolicy.Framework, designator *armotypes.PortalDesignator, excludedNamespaces string) (*cautils.K8SResources, error) {
// build resources map
k8sResourcesMap := setResourceMap(frameworks)
@@ -26,20 +27,20 @@ func (policyHandler *PolicyHandler) getK8sResources(frameworks []opapolicy.Frame
_, namespace, labels := armotypes.DigestPortalDesignator(designator)
// pull k8s recourses
if err := policyHandler.pullResources(k8sResourcesMap, namespace, labels); err != nil {
if err := policyHandler.pullResources(k8sResourcesMap, namespace, labels, excludedNamespaces); err != nil {
return k8sResourcesMap, err
}
return k8sResourcesMap, nil
}
func (policyHandler *PolicyHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]string) error {
func (policyHandler *PolicyHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]string, excludedNamespaces string) error {
var errs error
for groupResource := range *k8sResources {
apiGroup, apiVersion, resource := k8sinterface.StringToResourceGroup(groupResource)
gvr := schema.GroupVersionResource{Group: apiGroup, Version: apiVersion, Resource: resource}
result, err := policyHandler.pullSingleResource(&gvr, namespace, labels)
result, err := policyHandler.pullSingleResource(&gvr, namespace, labels, excludedNamespaces)
if err != nil {
// handle error
if errs == nil {
@@ -55,10 +56,16 @@ func (policyHandler *PolicyHandler) pullResources(k8sResources *cautils.K8SResou
return errs
}
func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string) ([]unstructured.Unstructured, error) {
func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string, excludedNamespaces string) ([]unstructured.Unstructured, error) {
// set labels
listOptions := metav1.ListOptions{}
if excludedNamespaces != "" && k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
excludedNamespacesSlice := strings.Split(excludedNamespaces, ",")
for _, excludedNamespace := range excludedNamespacesSlice {
listOptions.FieldSelector += "metadata.namespace!=" + excludedNamespace + ","
}
}
if labels != nil && len(labels) > 0 {
set := k8slabels.Set(labels)
listOptions.LabelSelector = set.AsSelector().String()
@@ -66,7 +73,6 @@ func (policyHandler *PolicyHandler) pullSingleResource(resource *schema.GroupVer
// set dynamic object
var clientResource dynamic.ResourceInterface
if namespace != "" && k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
clientResource = policyHandler.k8s.DynamicClient.Resource(*resource).Namespace(namespace)
} else {

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"kube-escape/cautils"
"os"
"strings"
"kube-escape/cautils/k8sinterface"
"kube-escape/cautils/opapolicy"
@@ -14,7 +13,7 @@ import (
"github.com/olekukonko/tablewriter"
)
var INDENT = " "
var INDENT = " "
type Printer struct {
opaSessionObj *chan *cautils.OPASessionObj
@@ -62,7 +61,7 @@ func (printer *Printer) SummerySetup(postureReport *opapolicy.PostureReport) {
TotalResources: cr.GetNumberOfResources(),
TotalFailed: len(workloadsSummery),
WorkloadSummery: mapResources,
Description: strings.ReplaceAll(cr.Description, ". ", fmt.Sprintf(".\n%s%s", INDENT, INDENT)),
Description: cr.Description,
}
}
}
@@ -72,9 +71,21 @@ func (printer *Printer) PrintResults() {
for control, controlSummery := range printer.summery {
printer.printTitle(control, &controlSummery)
printer.printResult(control, &controlSummery)
if controlSummery.TotalResources > 0 {
printer.printSummery(control, &controlSummery)
}
}
}
func (print *Printer) printSummery(controlName string, controlSummery *ControlSummery) {
cautils.SimpleDisplay(os.Stdout, "Summary - ")
cautils.SuccessDisplay(os.Stdout, "Passed:%v ", controlSummery.TotalResources-controlSummery.TotalFailed)
cautils.FailureDisplay(os.Stdout, "Failed:%v ", controlSummery.TotalFailed)
cautils.InfoDisplay(os.Stdout, "Total:%v\n\n", controlSummery.TotalResources)
}
func (printer *Printer) printTitle(controlName string, controlSummery *ControlSummery) {
cautils.InfoDisplay(os.Stdout, "[control: %s] ", controlName)
if controlSummery.TotalResources == 0 {
@@ -85,7 +96,7 @@ func (printer *Printer) printTitle(controlName string, controlSummery *ControlSu
cautils.FailureDisplay(os.Stdout, "failed %v\n", emoji.SadButRelievedFace)
}
cautils.SimpleDisplay(os.Stdout, "%sDescription: %s\n", INDENT, controlSummery.Description)
cautils.DescriptionDisplay(os.Stdout, "Description: %s\n", controlSummery.Description)
}
func (printer *Printer) printResult(controlName string, controlSummery *ControlSummery) {
@@ -93,8 +104,9 @@ func (printer *Printer) printResult(controlName string, controlSummery *ControlS
indent := INDENT
for ns, rsc := range controlSummery.WorkloadSummery {
preIndent := indent
indent += indent
cautils.SimpleDisplay(os.Stdout, "%sNamespace %s\n", indent, ns)
if ns != "" {
cautils.SimpleDisplay(os.Stdout, "%sNamespace %s\n", indent, ns)
}
preIndent2 := indent
for r := range rsc {
indent += indent

View File

@@ -14,7 +14,7 @@ type ControlSummery struct {
TotalResources int
TotalFailed int
Description string
WorkloadSummery map[string][]WorkloadSummery
WorkloadSummery map[string][]WorkloadSummery // <namespace>:[<WorkloadSummery>]
}
type WorkloadSummery struct {