Merge branch 'armosec:dev' into dev

This commit is contained in:
YiscahLevySilas1
2021-11-18 16:47:58 +02:00
committed by GitHub
16 changed files with 407 additions and 80 deletions
+5 -1
View File
@@ -58,6 +58,7 @@ Want to contribute? Want to discuss something? Have an issue?
* [Overview](https://youtu.be/wdBkt_0Qhbg)
* [Scanning Kubernetes YAML files](https://youtu.be/Ox6DaR7_4ZI)
* [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM)
* [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80)
## Install on Windows
@@ -158,8 +159,9 @@ kubescape scan framework nsa --format prometheus
```
#### Scan with exceptions, objects with exceptions will be presented as `exclude` and not `fail`
[Full documentation](examples/exceptions/README.md)
```
kubescape scan framework nsa --exceptions examples/exceptions.json
kubescape scan framework nsa --exceptions examples/exceptions/exclude-kube-namespaces.json
```
#### Scan Helm charts - Render the helm chart using [`helm template`](https://helm.sh/docs/helm/helm_template/) and pass to stdout
@@ -175,6 +177,8 @@ helm template bitnami/mysql --generate-name --dry-run | kubescape scan framework
### Offline Support
[Video tutorial](https://youtu.be/IGXL9s37smM)
It is possible to run Kubescape offline!
First download the framework and then scan with `--use-from` flag
+2 -2
View File
@@ -131,7 +131,7 @@ func (c *EmptyConfig) GetDefaultNS() string { return k8sinterf
func (c *EmptyConfig) GetBackendAPI() getter.IBackend { return nil } // TODO: return mock obj
func (c *EmptyConfig) GetClusterName() string { return adoptClusterName(k8sinterface.GetClusterName()) }
func (c *EmptyConfig) GenerateURL() {
message := fmt.Sprintf("\nCheckout for more cool features: https://%s\n", getter.GetArmoAPIConnector().GetFrontendURL())
message := fmt.Sprintf("\nYou 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 registering here: https://%s\n", getter.GetArmoAPIConnector().GetFrontendURL())
InfoTextDisplay(os.Stdout, fmt.Sprintf("\n%s\n", message))
}
@@ -161,7 +161,7 @@ func (c *ClusterConfig) GetDefaultNS() string { return c.defau
func (c *ClusterConfig) GetBackendAPI() getter.IBackend { return c.backendAPI }
func (c *ClusterConfig) GenerateURL() {
message := "Checkout for more cool features: "
message := "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 registering here: "
u := url.URL{}
u.Scheme = "https"
+7 -27
View File
@@ -1,8 +1,6 @@
package cautils
import (
"io"
"os"
"path/filepath"
"github.com/armosec/kubescape/cautils/getter"
@@ -69,24 +67,6 @@ func (scanInfo *ScanInfo) setUseFrom() {
}
}
func (scanInfo *ScanInfo) SetInputPatterns(args []string) error {
if args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stout to file
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
return nil
}
func (scanInfo *ScanInfo) setOutputFile() {
if scanInfo.Output == "" {
return
@@ -107,20 +87,20 @@ func (scanInfo *ScanInfo) ScanRunningCluster() bool {
return len(scanInfo.InputPatterns) == 0
}
func (scanInfo *ScanInfo) SetPolicyIdentifierForGivenFrameworks(frameworks []string) {
for _, framework := range frameworks {
if !scanInfo.contains(framework) {
func (scanInfo *ScanInfo) SetPolicyIdentifiers(policies []string, kind reporthandling.NotificationPolicyKind) {
for _, policy := range policies {
if !scanInfo.contains(policy) {
newPolicy := reporthandling.PolicyIdentifier{}
newPolicy.Kind = reporthandling.KindFramework
newPolicy.Name = framework
newPolicy.Kind = kind // reporthandling.KindFramework
newPolicy.Name = policy
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
}
}
}
func (scanInfo *ScanInfo) contains(framework string) bool {
func (scanInfo *ScanInfo) contains(policyName string) bool {
for _, policy := range scanInfo.PolicyIdentifier {
if policy.Name == framework {
if policy.Name == policyName {
return true
}
}
+21 -12
View File
@@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"io"
"os"
"strings"
@@ -21,7 +22,7 @@ var controlCmd = &cobra.Command{
controls := strings.Split(args[0], ",")
if len(controls) > 1 {
if controls[1] == "" {
return fmt.Errorf("usage: <control_one>,<control_two>")
return fmt.Errorf("usage: <control-0>,<control-1>")
}
}
} else {
@@ -34,23 +35,31 @@ var controlCmd = &cobra.Command{
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
if len(args) == 0 {
scanInfo.SetPolicyIdentifierForGivenFrameworks(getter.NativeFrameworks)
} else {
controls := strings.Split(args[0], ",")
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
scanInfo.PolicyIdentifier = setScanForFirstControl(controls)
scanInfo.SetPolicyIdentifiers(getter.NativeFrameworks, reporthandling.KindFramework)
scanInfo.ScanAll = true
} else { // expected control or list of control sepparated by ","
if len(controls) > 1 {
scanInfo.PolicyIdentifier = SetScanForGivenControls(controls[1:])
}
// Read controls from input args
scanInfo.SetPolicyIdentifiers(strings.Split(args[0], ","), reporthandling.KindControl)
if len(args) > 1 {
// Set scan to run on yamls
if err := scanInfo.SetInputPatterns(args); err != nil {
return err
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.FrameworkScan = false
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
+36 -21
View File
@@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"io"
"os"
"strings"
@@ -18,34 +19,48 @@ var frameworkCmd = &cobra.Command{
Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin",
ValidArgs: getter.NativeFrameworks,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
if len(args) > 0 {
frameworks := strings.Split(args[0], ",")
if len(frameworks) > 1 {
if frameworks[1] == "" {
return fmt.Errorf("usage: <framework-0>,<framework-1>")
}
}
} else {
return fmt.Errorf("requires at least one framework name")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
flagValidationFramework()
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
// If no framework provided, use all
if len(args) == 0 {
scanInfo.SetPolicyIdentifierForGivenFrameworks(getter.NativeFrameworks)
var frameworks []string
if len(args) == 0 { // scan all frameworks
frameworks = getter.NativeFrameworks
scanInfo.ScanAll = true
} else {
// Read frameworks from input args
scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{}
frameworks := strings.Split(strings.Join(strings.Fields(args[0]), ""), ",")
scanInfo.PolicyIdentifier = SetScanForFirstFramework(frameworks)
if len(frameworks) > 1 {
scanInfo.SetPolicyIdentifierForGivenFrameworks(frameworks[1:])
}
frameworks = strings.Split(args[0], ",")
if len(args) > 1 {
// expected yaml/url input
if err := scanInfo.SetInputPatterns(args); err != nil {
return err
if len(args[1:]) == 0 || args[1] != "-" {
scanInfo.InputPatterns = args[1:]
} else { // store stdin to file - do NOT move to separate function !!
tempFile, err := os.CreateTemp(".", "tmp-kubescape*.yaml")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
if _, err := io.Copy(tempFile, os.Stdin); err != nil {
return err
}
scanInfo.InputPatterns = []string{tempFile.Name()}
}
}
}
scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
scanInfo.Init()
cautils.SetSilentMode(scanInfo.Silent)
err := clihandler.ScanCliSetup(&scanInfo)
@@ -62,13 +77,13 @@ func init() {
scanInfo.FrameworkScan = true
}
func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier {
newPolicy := reporthandling.PolicyIdentifier{}
newPolicy.Kind = reporthandling.KindFramework
newPolicy.Name = frameworks[0]
scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
return scanInfo.PolicyIdentifier
}
// func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier {
// newPolicy := reporthandling.PolicyIdentifier{}
// newPolicy.Kind = reporthandling.KindFramework
// newPolicy.Name = frameworks[0]
// scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy)
// return scanInfo.PolicyIdentifier
// }
func flagValidationFramework() {
if scanInfo.Submit && scanInfo.Local {
+3 -2
View File
@@ -101,9 +101,10 @@ func setPolicyGetter(scanInfo *cautils.ScanInfo, customerGUID string) {
if scanInfo.ScanAll {
frameworks, err := g.ListCustomFrameworks(customerGUID)
if err != nil {
glog.Error("could not get custom frameworks")
glog.Error("failed to get custom frameworks") // handle error
return
}
scanInfo.SetPolicyIdentifierForGivenFrameworks(frameworks)
scanInfo.SetPolicyIdentifiers(frameworks, reporthandling.KindFramework)
}
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
### Examples
* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/News-Features/Feature-Stories/Article-View/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework
* Scan a running Kubernetes cluster with [`nsa`](https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/2716980/nsa-cisa-release-kubernetes-hardening-guidance/) framework
```
kubescape scan framework nsa --exclude-namespaces kube-system,kube-public
```
+179
View File
@@ -0,0 +1,179 @@
# Kubescape Exceptions
Kubescape Exceptions is the proper way of excluding failed resources from effecting the risk score.
e.g. When a `kube-system` resource fails and it is ok, simply add the resource to the exceptions configurations.
## Definitions
* `name`- Exception name - unique name representing the exception
* `policyType`- Do not change
* `actions`- List of available actions. Currently alertOnly is supported
* `resources`- List of resources to apply this exception on
* `designatorType: Attributes`- An attribute-based declaration {key: value}
Supported keys:
* `name`: k8s resource name (case-sensitive, regex supported)
* `kind`: k8s resource kind (case-sensitive, regex supported)
* `namespace`: k8s resource namespace (case-sensitive, regex supported)
* `cluster`: k8s cluster name (usually it is the `current-context`) (case-sensitive, regex supported)
* resource labels as key value (case-sensitive, regex NOT supported)
* `posturePolicies`- An attribute-based declaration {key: value}
* `frameworkName` - Framework names can be find [here](https://github.com/armosec/regolibrary/tree/master/frameworks)
* `controlName` - Control names can be find [here](https://github.com/armosec/regolibrary/tree/master/controls)
* `controlID` - Not yet supported
* `ruleName` - Rule names can be find [here](https://github.com/armosec/regolibrary/tree/master/rules)
## Usage
The `resources` list and `posturePolicies` list are design to be a combination of the resources and policies to exclude
> You must declare at least one resource and one policy
e.g. If you wish to exclude all namespaces with the label `"environment": "dev"`, the resource list should look as following:
```
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"namespace": ".*",
"environment": "dev"
}
}
]
```
But if you wish to exclude all namespaces **OR** any resource with the label `"environment": "dev"`, the resource list should look as following:
```
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"namespace": ".*"
}
},
{
"designatorType": "Attributes",
"attributes": {
"environment": "dev"
}
}
]
```
Same works with the `posturePolicies` list ->
e.g. If you wish to exclude the resources declared in the `resources` list that failed when scanning the `NSA` framework **AND** failed the `Allowed hostPath` control, the `posturePolicies` list should look as following:
```
"posturePolicies": [
{
"frameworkName": "NSA",
"controlName": "Allowed hostPath"
}
]
```
But if you wish to exclude the resources declared in the `resources` list that failed when scanning the `NSA` framework **OR** failed the `Allowed hostPath` control, the `posturePolicies` list should look as following:
```
"posturePolicies": [
{
"frameworkName": "NSA"
},
{
"controlName": "Allowed hostPath"
}
]
```
## Examples
Here are some examples demonstrating the different ways the exceptions file can be configured
### Exclude control
Exclude the ["Allowed hostPath" control](https://github.com/armosec/regolibrary/blob/master/controls/allowedhostpath.json#L2) by declaring the control in the `"posturePolicies"` section.
The resources
```
[
{
"name": "exclude-allowed-hostPath-control",
"policyType": "postureExceptionPolicy",
"actions": [
"alertOnly"
],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"kind": ".*"
}
}
],
"posturePolicies": [
{
"controlName": "Allowed hostPath"
}
]
}
]
```
### Exclude deployments in the default namespace that failed the "Allowed hostPath" control
```
[
{
"name": "exclude-deployments-in-ns-default",
"policyType": "postureExceptionPolicy",
"actions": [
"alertOnly"
],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"namespace": "default",
"kind": "Deployment"
}
}
],
"posturePolicies": [
{
"controlName": "Allowed hostPath"
}
]
}
]
```
### Exclude resources with label "app=nginx" running in a minikube cluster that failed the "NSA" or "MITRE" framework
```
[
{
"name": "exclude-nginx-minikube",
"policyType": "postureExceptionPolicy",
"actions": [
"alertOnly"
],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"cluster": "minikube",
"app": "nginx"
}
}
],
"posturePolicies": [
{
"frameworkName": "NSA"
},
{
"frameworkName": "MITRE"
}
]
}
]
```
@@ -0,0 +1,22 @@
[
{
"name": "exclude-allowed-hostPath-control",
"policyType": "postureExceptionPolicy",
"actions": [
"alertOnly"
],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"kind": ".*"
}
}
],
"posturePolicies": [
{
"controlName": "Allowed hostPath"
}
]
}
]
@@ -0,0 +1,23 @@
[
{
"name": "exclude-deployments-in-ns-default",
"policyType": "postureExceptionPolicy",
"actions": [
"alertOnly"
],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"namespace": "default",
"kind": "Deployment"
}
}
],
"posturePolicies": [
{
"controlName": "Allowed hostPath"
}
]
}
]
@@ -28,6 +28,12 @@
"posturePolicies": [
{
"frameworkName": "NSA"
},
{
"frameworkName": "MITRE"
},
{
"frameworkName": "ArmoBest"
}
]
}
@@ -0,0 +1,26 @@
[
{
"name": "exclude-nginx-in-minikube",
"policyType": "postureExceptionPolicy",
"actions": [
"alertOnly"
],
"resources": [
{
"designatorType": "Attributes",
"attributes": {
"cluster": "minikube",
"app": "nginx"
}
}
],
"posturePolicies": [
{
"frameworkName": "NSA"
},
{
"frameworkName": "MITRE"
}
]
}
]
+2 -2
View File
@@ -3,9 +3,9 @@ module github.com/armosec/kubescape
go 1.17
require (
github.com/armosec/armoapi-go v0.0.8
github.com/armosec/armoapi-go v0.0.23
github.com/armosec/k8s-interface v0.0.8
github.com/armosec/opa-utils v0.0.39
github.com/armosec/opa-utils v0.0.42
github.com/armosec/rbac-utils v0.0.1
github.com/armosec/utils-go v0.0.3
github.com/briandowns/spinner v1.16.0
+4 -5
View File
@@ -84,13 +84,12 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armosec/armoapi-go v0.0.2/go.mod h1:vIK17yoKbJRQyZXWWLe3AqfqCRITxW8qmSkApyq5xFs=
github.com/armosec/armoapi-go v0.0.7/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/armoapi-go v0.0.8 h1:JPa9rZynuE2RucamDh6dsy/sjCScmWDsyt1zagJFCDo=
github.com/armosec/armoapi-go v0.0.8/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/armoapi-go v0.0.23 h1:jqoLIWM5CR7DCD9fpFgN0ePqtHvOCoZv/XzCwsUluJU=
github.com/armosec/armoapi-go v0.0.23/go.mod h1:iaVVGyc23QGGzAdv4n+szGQg3Rbpixn9yQTU3qWRpaw=
github.com/armosec/k8s-interface v0.0.8 h1:Eo3Qen4yFXxzVem49FNeij2ckyzHSAJ0w6PZMaSEIm8=
github.com/armosec/k8s-interface v0.0.8/go.mod h1:xxS+V5QT3gVQTwZyAMMDrYLWGrfKOpiJ7Jfhfa0w9sM=
github.com/armosec/opa-utils v0.0.39 h1:YOPMmwZaseqZT2/io918YycrMoJYu+ggbINnZJR9ZUA=
github.com/armosec/opa-utils v0.0.39/go.mod h1:JaE2a0kB2O22JZCqBBfOS9Pvh9rXs9xXXb9lVo01rTs=
github.com/armosec/opa-utils v0.0.42 h1:7YzQJNVBmM0+1nWOAiUgDt+mvlVEwApg80FjMh4oxXo=
github.com/armosec/opa-utils v0.0.42/go.mod h1:OqewZoSqKD5udtQ4lGFixb8yyFNqLq9zqinlAL6KSjM=
github.com/armosec/rbac-utils v0.0.1 h1:N2MI98F/0zbDjmRZ29CNElU1AXkFLk5csd/qAHOBdXY=
github.com/armosec/rbac-utils v0.0.1/go.mod h1:pQ8CBiij8kSKV7aeZm9FMvtZN28VgA7LZcYyTWimq40=
github.com/armosec/utils-go v0.0.2/go.mod h1:itWmRLzRdsnwjpEOomL0mBWGnVNNIxSjDAdyc+b0iUo=
+7 -1
View File
@@ -7,7 +7,13 @@ def get_exec_from_args(args: list):
def run_command(command):
try:
return f"{subprocess.check_output(command, stderr=subprocess.STDOUT)}"
return f"{subprocess.check_output(command, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)}"
except Exception as e:
return f"{e}"
def assertion(msg):
errors = ["Error: invalid parameter", "exit status 1"]
for e in errors:
assert e not in msg, msg
+63 -6
View File
@@ -3,15 +3,72 @@ import smoke_utils
import sys
def full_scan(kubescape_exec: str):
return smoke_utils.run_command(command=[kubescape_exec, "scan", "framework", "nsa", os.path.join("..", "*.yaml")])
all_files = os.path.join("..", "*.yaml")
# all_files = os.path.join("..", "examples", "online-boutique", "*.yaml")
single_file = os.path.join("..", "examples", "online-boutique", "frontend.yaml")
def scan_all(kubescape_exec: str):
return smoke_utils.run_command(command=[kubescape_exec, "scan", all_files])
def scan_control_name(kubescape_exec: str):
return smoke_utils.run_command(command=[kubescape_exec, "scan", "control", 'Allowed hostPath', all_files])
def scan_control_id(kubescape_exec: str):
return smoke_utils.run_command(command=[kubescape_exec, "scan", "control", 'C-0006', all_files])
def scan_controls(kubescape_exec: str):
return smoke_utils.run_command(command=[kubescape_exec, "scan", "control", 'Allowed hostPath,Allow privilege escalation', all_files])
def scan_framework(kubescape_exec: str):
return smoke_utils.run_command(command=[kubescape_exec, "scan", "framework", "nsa", all_files])
def scan_frameworks(kubescape_exec: str):
return smoke_utils.run_command(command=[kubescape_exec, "scan", "framework", "nsa,mitre,armobest", all_files])
def scan_from_stdin(kubescape_exec: str):
return smoke_utils.run_command(command=["cat", single_file, "|", kubescape_exec, "scan", "framework", "nsa", "-"])
def run(kubescape_exec: str):
# return
print("Testing E2E yaml files")
msg = full_scan(kubescape_exec=kubescape_exec)
assert "exit status 1" not in msg, msg
print("Testing E2E on yaml files")
# TODO - fix support
# print("Testing scan all yaml files")
# msg = scan_all(kubescape_exec=kubescape_exec)
# smoke_utils.assertion(msg)
print("Testing scan control name")
msg = scan_control_name(kubescape_exec=kubescape_exec)
smoke_utils.assertion(msg)
print("Testing scan control id")
msg = scan_control_id(kubescape_exec=kubescape_exec)
smoke_utils.assertion(msg)
print("Testing scan controls")
msg = scan_controls(kubescape_exec=kubescape_exec)
smoke_utils.assertion(msg)
print("Testing scan framework")
msg = scan_framework(kubescape_exec=kubescape_exec)
smoke_utils.assertion(msg)
print("Testing scan frameworks")
msg = scan_frameworks(kubescape_exec=kubescape_exec)
smoke_utils.assertion(msg)
# TODO - fix test
# print("Testing scan from stdin")
# msg = scan_from_stdin(kubescape_exec=kubescape_exec)
# smoke_utils.assertion(msg)
print("Done E2E yaml files")