Merge pull request #213 from dwertent/master

support include namespaces
This commit is contained in:
David Wertenteil
2021-11-04 12:09:27 +02:00
committed by GitHub
6 changed files with 117 additions and 34 deletions

View File

@@ -78,6 +78,7 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser
| flag | default | description | options |
|-----------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|
| `-e`/`--exclude-namespaces` | Scan all namespaces | Namespaces to exclude from scanning. Recommended to exclude `kube-system` and `kube-public` namespaces | |
| `--include-namespaces` | Scan all namespaces | Scan specific namespaces | |
| `-s`/`--silent` | Display progress messages | Silent progress messages | |
| `-t`/`--fail-threshold` | `0` (do not fail) | fail command (return exit code 1) if result bellow threshold | `0` -> `100` |
| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit`/`prometheus` |
@@ -110,12 +111,21 @@ kubescape scan framework mitre --submit
kubescape scan control "Privileged container"
```
* Scan specific namespaces
```
kubescape scan framework nsa --include-namespaces development,staging,production
```
* Scan cluster and exclude some namespaces
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
* Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI)
```
kubescape scan framework nsa *.yaml
```
* Scan kubernetes manifest files from a public github repository
```
kubescape scan framework nsa https://github.com/armosec/kubescape
@@ -123,17 +133,17 @@ kubescape scan framework nsa https://github.com/armosec/kubescape
* Output in `json` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format json --output results.json
kubescape scan framework nsa --format json --output results.json
```
* Output in `junit xml` format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format junit --output results.xml
kubescape scan framework nsa --format junit --output results.xml
```
* Output in `prometheus` metrics format
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public --format prometheus
kubescape scan framework nsa --format prometheus
```
* Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail`

View File

@@ -19,6 +19,7 @@ type ScanInfo struct {
Format string // Format results (table, json, junit ...)
Output string // Store results in an output file, Output file name
ExcludedNamespaces string // DEPRECATED?
IncludeNamespaces string // DEPRECATED?
InputPatterns []string // Yaml files input patterns
Silent bool // Silent mode - Do not print progress logs
FailThreshold uint16 // Failure score threshold

View File

@@ -37,7 +37,8 @@ func init() {
rootCmd.AddCommand(scanCmd)
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not submitted")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to Armo backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system, kube-public")
scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public")
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"/"prometheus"`)
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")

View File

@@ -39,6 +39,17 @@ func getReporter(scanInfo *cautils.ScanInfo) reporter.IReport {
return reporter.NewReportEventReceiver("", "")
}
func getFieldSelector(scanInfo *cautils.ScanInfo) resourcehandler.IFieldSelector {
if scanInfo.IncludeNamespaces != "" {
return resourcehandler.NewIncludeSelector(scanInfo.IncludeNamespaces)
}
if scanInfo.ExcludedNamespaces != "" {
return resourcehandler.NewExcludeSelector(scanInfo.ExcludedNamespaces)
}
return &resourcehandler.EmptySelector{}
}
func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
var resourceHandler resourcehandler.IResourceHandler
var clusterConfig cautils.IClusterConfig
@@ -55,7 +66,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces {
reportHandler = reporter.NewReportMock()
} else {
k8s := k8sinterface.NewKubernetesApi()
resourceHandler = resourcehandler.NewK8sResourceHandler(k8s, scanInfo.ExcludedNamespaces)
resourceHandler = resourcehandler.NewK8sResourceHandler(k8s, getFieldSelector(scanInfo))
clusterConfig = cautils.ClusterConfigSetup(scanInfo, k8s, getter.GetArmoAPIConnector())
// setup reporter

View File

@@ -0,0 +1,76 @@
package resourcehandler
import (
"fmt"
"strings"
"github.com/armosec/k8s-interface/k8sinterface"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type IFieldSelector interface {
GetNamespacesSelector(*schema.GroupVersionResource) string
}
type EmptySelector struct {
}
func (es *EmptySelector) GetNamespacesSelector(resource *schema.GroupVersionResource) string {
return ""
}
type ExcludeSelector struct {
namespace string
}
func NewExcludeSelector(ns string) *ExcludeSelector {
return &ExcludeSelector{namespace: ns}
}
type IncludeSelector struct {
namespace string
}
func NewIncludeSelector(ns string) *IncludeSelector {
return &IncludeSelector{namespace: ns}
}
func (es *ExcludeSelector) GetNamespacesSelector(resource *schema.GroupVersionResource) string {
return getNamespacesSelector(resource, es.namespace, "!=")
}
func (is *IncludeSelector) GetNamespacesSelector(resource *schema.GroupVersionResource) string {
return getNamespacesSelector(resource, is.namespace, "==")
}
func getNamespacesSelector(resource *schema.GroupVersionResource, ns, operator string) string {
fieldSelectors := ""
fieldSelector := "metadata."
if resource.Resource == "namespaces" {
fieldSelector += "name"
} else if k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
fieldSelector += "namespace"
} else {
return ""
}
namespacesSlice := strings.Split(ns, ",")
for _, n := range namespacesSlice {
fieldSelectors += fmt.Sprintf("%s!=%s,", fieldSelector, n)
}
return fieldSelectors
}
func setFieldSelector(listOptions *metav1.ListOptions, resource *schema.GroupVersionResource, excludedNamespaces string) {
fieldSelector := "metadata."
if resource.Resource == "namespaces" {
fieldSelector += "name"
} else if k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
fieldSelector += "namespace"
} else {
return
}
excludedNamespacesSlice := strings.Split(excludedNamespaces, ",")
for _, excludedNamespace := range excludedNamespacesSlice {
listOptions.FieldSelector += fmt.Sprintf("%s!=%s,", fieldSelector, excludedNamespace)
}
}

View File

@@ -3,7 +3,6 @@ package resourcehandler
import (
"context"
"fmt"
"strings"
"github.com/armosec/kubescape/cautils"
"github.com/armosec/opa-utils/reporthandling"
@@ -21,14 +20,14 @@ import (
)
type K8sResourceHandler struct {
k8s *k8sinterface.KubernetesApi
excludedNamespaces string // excluded namespaces (separated by comma)
k8s *k8sinterface.KubernetesApi
fieldSelector IFieldSelector
}
func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, excludedNamespaces string) *K8sResourceHandler {
func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, fieldSelector IFieldSelector) *K8sResourceHandler {
return &K8sResourceHandler{
k8s: k8s,
excludedNamespaces: excludedNamespaces,
k8s: k8s,
fieldSelector: fieldSelector,
}
}
@@ -43,7 +42,7 @@ func (k8sHandler *K8sResourceHandler) GetResources(frameworks []reporthandling.F
_, namespace, labels := armotypes.DigestPortalDesignator(designator)
// pull k8s recourses
if err := k8sHandler.pullResources(k8sResourcesMap, namespace, labels, k8sHandler.excludedNamespaces); err != nil {
if err := k8sHandler.pullResources(k8sResourcesMap, namespace, labels); err != nil {
return k8sResourcesMap, err
}
@@ -59,13 +58,13 @@ func (k8sHandler *K8sResourceHandler) GetClusterAPIServerInfo() *version.Info {
}
return clusterAPIServerInfo
}
func (k8sHandler *K8sResourceHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]string, excludedNamespaces string) error {
func (k8sHandler *K8sResourceHandler) pullResources(k8sResources *cautils.K8SResources, namespace string, labels map[string]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 := k8sHandler.pullSingleResource(&gvr, namespace, labels, excludedNamespaces)
result, err := k8sHandler.pullSingleResource(&gvr, namespace, labels)
if err != nil {
// handle error
if errs == nil {
@@ -81,13 +80,13 @@ func (k8sHandler *K8sResourceHandler) pullResources(k8sResources *cautils.K8SRes
return errs
}
func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string, excludedNamespaces string) ([]unstructured.Unstructured, error) {
func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupVersionResource, namespace string, labels map[string]string) ([]unstructured.Unstructured, error) {
// set labels
listOptions := metav1.ListOptions{}
if excludedNamespaces != "" {
setFieldSelector(&listOptions, resource, excludedNamespaces)
}
listOptions.FieldSelector += k8sHandler.fieldSelector.GetNamespacesSelector(resource)
if len(labels) > 0 {
set := k8slabels.Set(labels)
listOptions.LabelSelector = set.AsSelector().String()
@@ -110,18 +109,3 @@ func (k8sHandler *K8sResourceHandler) pullSingleResource(resource *schema.GroupV
return result.Items, nil
}
func setFieldSelector(listOptions *metav1.ListOptions, resource *schema.GroupVersionResource, excludedNamespaces string) {
fieldSelector := "metadata."
if resource.Resource == "namespaces" {
fieldSelector += "name"
} else if k8sinterface.IsNamespaceScope(resource.Group, resource.Resource) {
fieldSelector += "namespace"
} else {
return
}
excludedNamespacesSlice := strings.Split(excludedNamespaces, ",")
for _, excludedNamespace := range excludedNamespacesSlice {
listOptions.FieldSelector += fmt.Sprintf("%s!=%s,", fieldSelector, excludedNamespace)
}
}