Skip to content

Commit

Permalink
Merge pull request kubescape#894 from kubescape/dev
Browse files Browse the repository at this point in the history
Enhancing CLI capabilities and SARIF output
  • Loading branch information
David Wertenteil authored Nov 6, 2022
2 parents dd7a8fd + 691fa61 commit 6e9a2f5
Show file tree
Hide file tree
Showing 43 changed files with 1,225 additions and 156 deletions.
22 changes: 11 additions & 11 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ jobs:
asset_name: kubescape-${{ matrix.os }}-sha256
asset_content_type: application/octet-stream

# publish-image:
# if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
# uses: ./.github/workflows/build-image.yaml
# needs: create-release
# with:
# client: "image-release"
# image_name: "quay.io/${{ github.repository_owner }}/kubescape"
# image_tag: "v2.0.${{ github.run_number }}"
# support_platforms: true
# cosign: true
# secrets: inherit
publish-image:
if: ${{ github.repository == 'kubescape/kubescape' }} # TODO
uses: ./.github/workflows/build-image.yaml
needs: create-release
with:
client: "image-release"
image_name: "quay.io/${{ github.repository_owner }}/kubescape"
image_tag: "v2.0.${{ github.run_number }}"
support_platforms: false
cosign: true
secrets: inherit
27 changes: 13 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh

## Run:
```sh
kubescape scan --submit --enable-host-scan --verbose
kubescape scan --enable-host-scan --verbose
```

<img src="docs/summary.png">
Expand Down Expand Up @@ -175,22 +175,22 @@ Or to your profile (not preferred): `nix-env --install -A nixpkgs.kubescape`
### Examples


#### Scan a running Kubernetes cluster and submit results to the [Kubescape SaaS version](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
#### Scan a running Kubernetes cluster
```
kubescape scan --submit --enable-host-scan --verbose
kubescape scan --enable-host-scan --verbose
```
> Read [here](https://hub.armosec.io/docs/host-sensor?utm_source=github&utm_medium=repository) more about the `enable-host-scan` flag
#### 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 and submit results to the [Kubescape SaaS version](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
#### 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 --submit
kubescape scan framework nsa
```
#### Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework and submit results to the [Kubescape SaaS version](https://cloud.armosec.io?utm_source=github&utm_medium=repository)
#### Scan a running Kubernetes cluster with [`MITRE ATT&CK®`](https://www.microsoft.com/security/blog/2021/03/23/secure-containerized-environments-with-updated-threat-matrix-for-kubernetes/) framework
```
kubescape scan framework mitre --submit
kubescape scan framework mitre
```
Expand All @@ -214,14 +214,13 @@ kubescape scan --include-namespaces development,staging,production
kubescape scan --exclude-namespaces kube-system,kube-public
```
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI). Submit the results in case the directory is a git repo. [docs](https://hub.armosec.io/docs/repository-scanning?utm_source=github&utm_medium=repository)
#### Scan local `yaml`/`json` files before deploying. [Take a look at the demonstration](https://youtu.be/Ox6DaR7_4ZI).
```
kubescape scan *.yaml --submit
kubescape scan *.yaml
```
#### Scan Kubernetes manifest files from a git repository [and submit the results](https://hub.armosec.io/docs/repository-scanning?utm_source=github&utm_medium=repository)
```
kubescape scan https://github.com/kubescape/kubescape --submit
#### Scan Kubernetes manifest files from a git repository
kubescape scan https://github.com/kubescape/kubescape
```

#### Display all scanned resources (including the resources which passed)
Expand Down Expand Up @@ -268,13 +267,13 @@ kubescape scan --exceptions examples/exceptions/exclude-kube-namespaces.json

#### Scan Helm charts
```
kubescape scan </path/to/directory> --submit
kubescape scan </path/to/directory>
```
> Kubescape will load the default value file
#### Scan Kustomize Directory
```
kubescape scan </path/to/directory> --submit
kubescape scan </path/to/directory>
```
> Kubescape will generate Kubernetes Yaml Objects using 'Kustomize' file and scans them for security.
Expand Down
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/kubescape/kubescape/v2/cmd/list"
"github.com/kubescape/kubescape/v2/cmd/scan"
"github.com/kubescape/kubescape/v2/cmd/submit"
"github.com/kubescape/kubescape/v2/cmd/update"
"github.com/kubescape/kubescape/v2/cmd/version"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
Expand Down Expand Up @@ -76,6 +77,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.AddCommand(completion.GetCompletionCmd())
rootCmd.AddCommand(version.GetVersionCmd())
rootCmd.AddCommand(config.GetConfigCmd(ks))
rootCmd.AddCommand(update.GetUpdateCmd())

return rootCmd
}
Expand Down
16 changes: 6 additions & 10 deletions cmd/scan/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var scanCmdExamples = `
Scan command is for scanning an existing cluster or kubernetes manifest files based on pre-defined frameworks
# Scan current cluster with all frameworks
kubescape scan --submit --enable-host-scan --verbose
kubescape scan --enable-host-scan --verbose
# Scan kubernetes YAML manifest files
kubescape scan *.yaml
Expand Down Expand Up @@ -71,37 +71,33 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal")
scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them")
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. Notice, when running with `exclude-namespace` kubescape does not scan cluster-scoped objects.")

scanCmd.PersistentFlags().Float32VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1")

scanCmd.PersistentFlags().StringVar(&scanInfo.FailThresholdSeverity, "severity-threshold", "", "Severity threshold is the severity of failed controls at which the command fails and returns exit code 1")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html"`)
scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer", "json", "junit", "prometheus", "pdf", "html", "sarif"`)
scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b")
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().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to configured backend.")
scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display all of the input resources and not only failed resources")
scanCmd.PersistentFlags().StringVar(&scanInfo.View, "view", string(cautils.ResourceViewType), fmt.Sprintf("View results based on the %s/%s. default is --view=%s", cautils.ResourceViewType, cautils.ControlViewType, cautils.ResourceViewType))
scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest")
scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest")
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().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host scanner DaemonSet. Use this flag cautiously")
scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be different between versions, this is for maintaining backward and forward compatibility. Supported:'v1'/'v2'")
scanCmd.PersistentFlags().StringVar(&scanInfo.CustomClusterName, "cluster-name", "", "Set the custom name of the cluster. Not same as the kube-context flag")
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "Submit the scan results to Kubescape SaaS 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")

// Deprecated flags - remove 1.May.2022
scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages")
scanCmd.PersistentFlags().MarkDeprecated("silent", "use '--logger' flag instead. Flag will be removed at 1.May.2022")

// hidden flags
scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemonSet can not run pods on the nodes
scanCmd.PersistentFlags().MarkHidden("silent") // this flag should be deprecated since we added the --logger support
// scanCmd.PersistentFlags().MarkHidden("format-version") // meant for testing different output approaches and not for common use

// Retrieve --kubeconfig flag from https://github.com/kubernetes/kubectl/blob/master/pkg/cmd/cmd.go
scanCmd.PersistentFlags().AddGoFlag(flag.Lookup("kubeconfig"))

hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy Kubescape host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valuable data from cluster nodes for certain controls. Yaml file: https://github.com/kubescape/kubescape/blob/master/core/pkg/hostsensorutils/hostsensor.yaml")
hostF.NoOptDefVal = "true"
hostF.DefValue = "false, for no TTY in stdin"

Expand Down
59 changes: 59 additions & 0 deletions cmd/update/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package update

//This update command updates to the latest kubescape release.
//Example:-
// kubescape update

import (
"os/exec"
"runtime"

logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/spf13/cobra"
)

func GetUpdateCmd() *cobra.Command {
updateCmd := &cobra.Command{
Use: "update",
Short: "Update your version",
Long: ``,
RunE: func(_ *cobra.Command, args []string) error {
//Checking the user's version of kubescape to the latest release
if cautils.BuildNumber == cautils.LatestReleaseVersion {
//your version == latest version
logger.L().Info(("You are in the latest version"))
} else {

const OSTYPE string = runtime.GOOS
var ShellToUse string
switch OSTYPE {

case "windows":
cautils.StartSpinner()
//run the installation command for windows
ShellToUse = "powershell"
_, err := exec.Command(ShellToUse, "-c", "iwr -useb https://raw.githubusercontent.com/kubescape/kubescape/master/install.ps1 | iex").Output()

if err != nil {
logger.L().Fatal(err.Error())
}
cautils.StopSpinner()

default:
ShellToUse = "bash"
cautils.StartSpinner()
//run the installation command for linux and macOS
_, err := exec.Command(ShellToUse, "-c", "curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash").Output()
if err != nil {
logger.L().Fatal(err.Error())
}

cautils.StopSpinner()
}
}
return nil
},
}
return updateCmd
}
4 changes: 2 additions & 2 deletions core/cautils/fileutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ func loadFiles(rootPath string, filePaths []string) (map[string][]workloadinterf
for j := range w {
lw := localworkload.NewLocalWorkload(w[j].GetObject())
if relPath, err := filepath.Rel(rootPath, path); err == nil {
lw.SetPath(relPath)
lw.SetPath(fmt.Sprintf("%s:%d", relPath, j))
} else {
lw.SetPath(path)
lw.SetPath(fmt.Sprintf("%s:%d", path, j))
}
wSlice = append(wSlice, lw)
}
Expand Down
9 changes: 9 additions & 0 deletions core/cautils/getter/downloadreleasedpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package getter
import (
"strings"

"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/gitregostore"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
Expand Down Expand Up @@ -99,3 +100,11 @@ func contains(s []string, str string) bool {
}
return false
}

func (drp *DownloadReleasedPolicy) GetExceptions(clusterName string) ([]armotypes.PostureExceptionPolicy, error) {
exceptions, err := drp.gs.GetSystemPostureExceptionPolicies()
if err != nil {
return nil, err
}
return exceptions, nil
}
42 changes: 42 additions & 0 deletions core/cautils/getter/gcpcloudapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package getter

import (
"context"
"os"

containeranalysis "cloud.google.com/go/containeranalysis/apiv1"
)

type GCPCloudAPI struct {
credentialsPath string
context context.Context
client *containeranalysis.Client
projectID string
credentialsCheck bool
}

func GetGlobalGCPCloudAPIConnector() *GCPCloudAPI {

if os.Getenv("KS_GCP_CREDENTIALS_PATH") == "" || os.Getenv("KS_GCP_PROJECT_ID") == "" {
return &GCPCloudAPI{
credentialsCheck: false,
}
} else {
return &GCPCloudAPI{
context: context.Background(),
credentialsPath: os.Getenv("KS_GCP_CREDENTIALS_PATH"),
projectID: os.Getenv("KS_GCP_PROJECT_ID"),
credentialsCheck: true,
}
}
}

func (api *GCPCloudAPI) SetClient(client *containeranalysis.Client) {
api.client = client
}

func (api *GCPCloudAPI) GetCredentialsPath() string { return api.credentialsPath }
func (api *GCPCloudAPI) GetClient() *containeranalysis.Client { return api.client }
func (api *GCPCloudAPI) GetProjectID() string { return api.projectID }
func (api *GCPCloudAPI) GetCredentialsCheck() bool { return api.credentialsCheck }
func (api *GCPCloudAPI) GetContext() context.Context { return api.context }
9 changes: 7 additions & 2 deletions core/cautils/getter/loadpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,19 @@ func (lp *LoadPolicy) GetControlsInputs(clusterName string) (map[string][]string
filePath := lp.filePath()
accountConfig := &armotypes.CustomerConfig{}
f, err := os.ReadFile(filePath)
fileName := filepath.Base(filePath)
if err != nil {
return nil, err
formattedError := fmt.Errorf("Error opening %s file, \"controls-config\" will be downloaded from ARMO management portal", fileName)
return nil, formattedError
}

if err = json.Unmarshal(f, &accountConfig.Settings.PostureControlInputs); err == nil {
return accountConfig.Settings.PostureControlInputs, nil
}
return nil, err

formattedError := fmt.Errorf("Error reading %s file, %s, \"controls-config\" will be downloaded from ARMO management portal", fileName, err.Error())

return nil, formattedError
}

// temporary support for a list of files
Expand Down
1 change: 0 additions & 1 deletion core/cautils/rootinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ type RootInfo struct {

KSCloudBEURLs string // Kubescape Cloud URL
KSCloudBEURLsDep string // Kubescape Cloud URL

}
type CloudURLs struct {
CloudReportURL string
Expand Down
1 change: 1 addition & 0 deletions core/cautils/scaninfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ func metadataGitLocal(input string) (*reporthandlingv2.RepoContextMetadata, erro
Date: commit.Committer.Date,
CommitterName: commit.Committer.Name,
}
context.LocalRootPath = getAbsPath(input)

return context, nil
}
Expand Down
7 changes: 5 additions & 2 deletions core/cautils/versioncheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const SKIP_VERSION_CHECK = "KS_SKIP_UPDATE_CHECK"

var BuildNumber string
var Client string
var LatestReleaseVersion string

const UnknownBuildNumber = "unknown"

Expand Down Expand Up @@ -108,9 +109,11 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques
return fmt.Errorf("failed to get latest version")
}

LatestReleaseVersion := latestVersion.ClientUpdate

if latestVersion.ClientUpdate != "" {
if BuildNumber != "" && semver.Compare(BuildNumber, latestVersion.ClientUpdate) == -1 {
logger.L().Warning(warningMessage(latestVersion.ClientUpdate))
if BuildNumber != "" && semver.Compare(BuildNumber, LatestReleaseVersion) == -1 {
logger.L().Warning(warningMessage(LatestReleaseVersion))
}
}

Expand Down
2 changes: 1 addition & 1 deletion core/core/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func downloadExceptions(downloadInfo *metav1.DownloadInfo) error {
var err error
tenant := getTenantConfig(&downloadInfo.Credentials, "", "", getKubernetesApi())

exceptionsGetter := getExceptionsGetter("")
exceptionsGetter := getExceptionsGetter("", tenant.GetAccountID(), nil)
exceptions := []armotypes.PostureExceptionPolicy{}
if tenant.GetAccountID() != "" {
exceptions, err = exceptionsGetter.GetExceptions(tenant.GetContextName())
Expand Down
Loading

0 comments on commit 6e9a2f5

Please sign in to comment.