add image command

Signed-off-by: Daniel Grunberger <danielgrunberger@armosec.io>
This commit is contained in:
Daniel Grunberger
2023-09-04 18:02:55 +03:00
parent e091269b22
commit 6103b317f6
18 changed files with 467 additions and 271 deletions
+92 -3
View File
@@ -1,3 +1,90 @@
# Scan Command
The scan command is used for scanning container images for vulnerabilities.
It uses [grype](https://github.com/anchore/grype) under the hood for scanning the images.
## Usage
```bash
kubescape image scan <image-name> [flags]
```
### Flags
| Flag | Description | Required | Default |
| ------------------------- | ------------------------------------------------------ |--------- | --------|
| -u, --username | Username for the image registry login | No | |
| -p, --password | Password for the image registry login | No | |
| -f, --format | Output file format. | No | |
| -o, --output | Output file. Print output to file and not stdout | No | |
| -v, --verbose | Display full report | No | false |
| -s, --severity-threshold | Maximum severity of a vulnerability | No | |
| -h, --help | help for scan | No | |
## Example
1. In a seperate terminal, run the `kubescape scan` command:
```bash
sudo kubescape image scan nginx:1.22
```
2. You will get an output like below:
```bash
✅ Successfully scanned image: nginx:1.22
| Severity | Vulnerability | Component | Version | Fixed In |
| -------- | -------------- | ------------- | ----------------------- | -------- |
| Critical | CVE-2023-23914 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| Critical | CVE-2019-8457 | libdb5.3 | 5.3.28+dfsg1-0.8 | wont-fix |
| High | CVE-2022-42916 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-1304 | libext2fs2 | 1.46.2-2 | wont-fix |
| High | CVE-2022-42916 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-1304 | e2fsprogs | 1.46.2-2 | wont-fix |
| High | CVE-2022-1304 | libcom-err2 | 1.46.2-2 | wont-fix |
| High | CVE-2023-27533 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2023-27534 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2023-27533 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-43551 | libcurl4 | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-3715 | bash | 5.1-2+deb11u1 | wont-fix |
| High | CVE-2023-27534 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2022-43551 | curl | 7.74.0-1.3+deb11u7 | wont-fix |
| High | CVE-2021-33560 | libgcrypt20 | 1.8.7-6 | wont-fix |
| High | CVE-2023-2953 | libldap-2.4-2 | 2.4.57+dfsg-3+deb11u1 | wont-fix |
| High | CVE-2022-1304 | libss2 | 1.46.2-2 | wont-fix |
| High | CVE-2020-22218 | libssh2-1 | 1.9.0-2 | wont-fix |
| High | CVE-2023-29491 | libtinfo6 | 6.2+20201114-2+deb11u1 | wont-fix |
| High | CVE-2022-2309 | libxml2 | 2.9.10+dfsg-6.7+deb11u4 | wont-fix |
| High | CVE-2022-4899 | libzstd1 | 1.4.8+dfsg-2.1 | wont-fix |
| High | CVE-2022-1304 | logsave | 1.46.2-2 | wont-fix |
| High | CVE-2023-29491 | ncurses-base | 6.2+20201114-2+deb11u1 | wont-fix |
| High | CVE-2023-29491 | ncurses-bin | 6.2+20201114-2+deb11u1 | wont-fix |
| High | CVE-2023-31484 | perl-base | 5.32.1-4+deb11u2 | wont-fix |
| High | CVE-2020-16156 | perl-base | 5.32.1-4+deb11u2 | wont-fix |
Vulnerability summary - 184 vulnerabilities found:
──────────────────────────────────────────────────
Image: nginx:1.22
* 3 Critical
* 35 High
* 43 Medium
* 103 Other
Most vulnerable components:
* curl (7.74.0-1.3+deb11u7) - 1 Critical, 4 High, 5 Medium, 1 Low, 3 Negligible
* libcurl4 (7.74.0-1.3+deb11u7) - 1 Critical, 4 High, 5 Medium, 1 Low, 3 Negligible
* libtiff5 (4.2.0-1+deb11u4) - 7 Medium, 10 Negligible, 2 Unknown
* libssl1.1 (1.1.1n-0+deb11u4) - 1 High, 5 Medium, 2 Negligible
* openssl (1.1.1n-0+deb11u4) - 1 High, 5 Medium, 2 Negligible
What now?
─────────
* Run with '--verbose'/'-v' flag for detailed vulnerabilities view
* Install Kubescape in your cluster for continuous monitoring and a full vulnerability report: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-cloud-operator
```
# Patch Command
The patch command is used for patching container images with vulnerabilities.
@@ -6,7 +93,7 @@ It uses [copa](https://github.com/project-copacetic/copacetic) and [buildkit](ht
## Usage
```bash
kubescape patch --image <image-name> [flags]
kubescape image patch <image-name> [flags]
```
The patch command can be run in 2 ways:
@@ -43,12 +130,14 @@ The patch command can be run in 2 ways:
| Flag | Description | Required | Default |
| -------------- | ------------------------------------------------------ | -------- | ----------------------------------- |
| -i, --image | Image name to be patched (should be in canonical form) | Yes | |
| -a, --addr | Address of the buildkitd service | No | unix:///run/buildkit/buildkitd.sock |
| -t, --tag | Tag of the resultant patched image | No | image_name-patched |
| --timeout | Timeout for the patching process | No | 5m |
| -u, --username | Username for the image registry login | No | |
| -p, --password | Password for the image registry login | No | |
| -f, --format | Output file format. | No | |
| -o, --output | Output file. Print output to file and not stdout | No | |
| -v, --verbose | Display full report. Default to false | No | |
| -h, --help | help for patch | No | |
@@ -72,7 +161,7 @@ We will demonstrate how to use the patch command with an example of [nginx](http
2. In a seperate terminal, run the `kubescape patch` command:
```bash
sudo kubescape patch --image docker.io/library/nginx:1.22
sudo kubescape image patch docker.io/library/nginx:1.22
```
3. You will get an output like below:
+58
View File
@@ -0,0 +1,58 @@
package image
import (
"fmt"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/spf13/cobra"
)
var imageCmdExamples = fmt.Sprintf(`
# Scan an image
%[1]s image scan <image>:<tag>
# Patch an image
%[1]s image patch <image>:<tag>
`, cautils.ExecName())
func GetImageCmd(ks meta.IKubescape) *cobra.Command {
var scanInfo cautils.ScanInfo
imageCmd := &cobra.Command{
Use: "image",
Short: "Scan or patch an image",
Example: imageCmdExamples,
}
imgScanInfo := &metav1.ImageScanInfo{}
imageCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "", `Output file format. Supported formats: "pretty-printer", "json", "sarif"`)
imageCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout")
imageCmd.PersistentFlags().BoolVarP(&scanInfo.VerboseMode, "verbose", "v", false, "Display full report. Default to false")
imageCmd.PersistentFlags().StringVarP(&scanInfo.FailThresholdSeverity, "severity-threshold", "s", "", "Severity threshold is the severity of a vulnerability at which the command fails and returns exit code 1")
imageCmd.PersistentFlags().StringVarP(&imgScanInfo.Username, "username", "u", "", "Username for registry login")
imageCmd.PersistentFlags().StringVarP(&imgScanInfo.Password, "password", "p", "", "Password for registry login")
imageCmd.AddCommand(getScanCmd(ks, &scanInfo, imgScanInfo))
imageCmd.AddCommand(getPatchCmd(ks, &scanInfo, imgScanInfo))
imageCmd.SetHelpFunc(func(command *cobra.Command, strings []string) {
// hide kube-context and server flags
command.Flags().MarkHidden("kube-context")
command.Flags().MarkHidden("server")
// this will prevent an infinite recursive call for the sub-commands of 'image'
if parent := command.Parent().Parent(); parent != nil {
parent.HelpFunc()(command, strings)
return
}
command.Parent().HelpFunc()(command, strings)
})
return imageCmd
}
+20 -9
View File
@@ -1,4 +1,4 @@
package patch
package image
import (
"context"
@@ -21,22 +21,37 @@ import (
var patchCmdExamples = fmt.Sprintf(`
# Patch the nginx:1.22 image
1) sudo buildkitd # start buildkitd service, run in seperate terminal
2) sudo %[1]s patch --image docker.io/library/nginx:1.22 # patch the image
2) sudo %[1]s patch nginx:1.22 # patch the image
# The patch command can also be run without sudo privileges
# Documentation: https://github.com/kubescape/kubescape/tree/master/cmd/patch
`, cautils.ExecName())
func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
var patchInfo metav1.PatchInfo
func getPatchCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *metav1.ImageScanInfo) *cobra.Command {
patchInfo := metav1.PatchInfo{
Username: imgScanInfo.Username,
Password: imgScanInfo.Password,
}
patchCmd := &cobra.Command{
Use: "patch --image <image-tag> [flags]",
Use: "patch <image_name>:<image-tag> [flags]",
Short: "Patch container images with vulnerabilities ",
Long: `Patch command is for automatically patching images with vulnerabilities.`,
Example: patchCmdExamples,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("the command takes exactly one image name as an argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateImageScanInfo(scanInfo); err != nil {
return err
}
patchInfo.Image = args[0]
if err := validateImagePatchInfo(&patchInfo); err != nil {
return err
}
@@ -45,14 +60,10 @@ func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
},
}
patchCmd.PersistentFlags().StringVarP(&patchInfo.Image, "image", "i", "", "Application image name and tag to patch")
patchCmd.PersistentFlags().StringVarP(&patchInfo.PatchedImageTag, "tag", "t", "", "Tag for the patched image. Defaults to '<image-tag>-patched' ")
patchCmd.PersistentFlags().StringVarP(&patchInfo.BuildkitAddress, "address", "a", "unix:///run/buildkit/buildkitd.sock", "Address of buildkitd service, defaults to local buildkitd.sock")
patchCmd.PersistentFlags().DurationVar(&patchInfo.Timeout, "timeout", 5*time.Minute, "Timeout for the operation, defaults to '5m'")
patchCmd.PersistentFlags().StringVarP(&patchInfo.Username, "username", "u", "", "Username for registry login")
patchCmd.PersistentFlags().StringVarP(&patchInfo.Password, "password", "p", "", "Password for registry login")
return patchCmd
}
+73
View File
@@ -0,0 +1,73 @@
package image
import (
"context"
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/pkg/imagescan"
"github.com/spf13/cobra"
)
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
var (
imageExample = fmt.Sprintf(`
Scan an image for vulnerabilities.
# Scan the 'nginx' image
%[1]s scan image "nginx"
`, cautils.ExecName())
)
// imageCmd represents the image command
func getScanCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *metav1.ImageScanInfo) *cobra.Command {
cmd := &cobra.Command{
Use: "scan <image_name>:<image_tag> [flags]",
Short: "Scan container images for vulnerabilities",
Example: imageExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("the command takes exactly one image name as an argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateImageScanInfo(scanInfo); err != nil {
return err
}
imgScanInfo := &metav1.ImageScanInfo{
Image: args[0],
Username: imgScanInfo.Username,
Password: imgScanInfo.Password,
}
results, err := ks.ScanImage(context.Background(), imgScanInfo, scanInfo)
if err != nil {
return err
}
if imagescan.ExceedsSeverityThreshold(results, imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)) {
utils.TerminateOnExceedingSeverity(scanInfo, logger.L())
}
return err
},
}
return cmd
}
// validateImageScanInfo validates the ScanInfo struct for the `image` command
func validateImageScanInfo(scanInfo *cautils.ScanInfo) error {
severity := scanInfo.FailThresholdSeverity
if err := utils.ValidateSeverity(severity); severity != "" && err != nil {
return err
}
return nil
}
+2 -2
View File
@@ -11,8 +11,8 @@ import (
"github.com/kubescape/kubescape/v2/cmd/config"
"github.com/kubescape/kubescape/v2/cmd/download"
"github.com/kubescape/kubescape/v2/cmd/fix"
"github.com/kubescape/kubescape/v2/cmd/image"
"github.com/kubescape/kubescape/v2/cmd/list"
"github.com/kubescape/kubescape/v2/cmd/patch"
"github.com/kubescape/kubescape/v2/cmd/scan"
"github.com/kubescape/kubescape/v2/cmd/update"
"github.com/kubescape/kubescape/v2/cmd/version"
@@ -95,7 +95,7 @@ func getRootCmd(ks meta.IKubescape) *cobra.Command {
rootCmd.AddCommand(config.GetConfigCmd(ks))
rootCmd.AddCommand(update.GetUpdateCmd())
rootCmd.AddCommand(fix.GetFixCmd(ks))
rootCmd.AddCommand(patch.GetPatchCmd(ks))
rootCmd.AddCommand(image.GetImageCmd(ks))
// deprecated commands
rootCmd.AddCommand(&cobra.Command{
+3 -2
View File
@@ -11,6 +11,7 @@ import (
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/meta"
@@ -114,7 +115,7 @@ func getControlCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comman
if results.GetComplianceScore() < float32(scanInfo.ComplianceThreshold) {
logger.L().Fatal("scan compliance-score is below permitted threshold", helpers.String("compliance score", fmt.Sprintf("%.2f", results.GetComplianceScore())), helpers.String("compliance-threshold", fmt.Sprintf("%.2f", scanInfo.ComplianceThreshold)))
}
enforceSeverityThresholds(results.GetResults().SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(results.GetResults().SummaryDetails.GetResourcesSeverityCounters(), scanInfo, utils.TerminateOnExceedingSeverity)
return nil
},
@@ -129,7 +130,7 @@ func validateControlScanInfo(scanInfo *cautils.ScanInfo) error {
return fmt.Errorf("you can use `omit-raw-resources` or `submit`, but not both")
}
if err := validateSeverity(severity); severity != "" && err != nil {
if err := utils.ValidateSeverity(severity); severity != "" && err != nil {
return err
}
return nil
+4 -19
View File
@@ -15,6 +15,7 @@ import (
logger "github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta"
@@ -136,7 +137,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
logger.L().Fatal("scan compliance-score is below permitted threshold", helpers.String("compliance-score", fmt.Sprintf("%.2f", results.GetComplianceScore())), helpers.String("compliance-threshold", fmt.Sprintf("%.2f", scanInfo.ComplianceThreshold)))
}
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), scanInfo, utils.TerminateOnExceedingSeverity)
return nil
},
}
@@ -145,7 +146,7 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm
// countersExceedSeverityThreshold returns true if severity of failed controls exceed the set severity threshold, else returns false
func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCounters, scanInfo *cautils.ScanInfo) (bool, error) {
targetSeverity := scanInfo.FailThresholdSeverity
if err := validateSeverity(targetSeverity); err != nil {
if err := utils.ValidateSeverity(targetSeverity); err != nil {
return false, err
}
@@ -178,11 +179,6 @@ func countersExceedSeverityThreshold(severityCounters reportsummary.ISeverityCou
}
// terminateOnExceedingSeverity terminates the application on exceeding severity
func terminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l helpers.ILogger) {
l.Fatal("result exceeds severity threshold", helpers.String("set severity threshold", scanInfo.FailThresholdSeverity))
}
// enforceSeverityThresholds ensures that the scan results are below the defined severity threshold
//
// The function forces the application to terminate with an exit code 1 if at least one control failed control that exceeds the set severity threshold
@@ -199,17 +195,6 @@ func enforceSeverityThresholds(severityCounters reportsummary.ISeverityCounters,
}
}
// validateSeverity returns an error if a given severity is not known, nil otherwise
func validateSeverity(severity string) error {
for _, val := range reporthandlingapis.GetSupportedSeverities() {
if strings.EqualFold(severity, val) {
return nil
}
}
return ErrUnknownSeverity
}
// validateFrameworkScanInfo validates the scan info struct for the `scan framework` command
func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
if scanInfo.View == string(cautils.SecurityViewType) {
@@ -229,7 +214,7 @@ func validateFrameworkScanInfo(scanInfo *cautils.ScanInfo) error {
return ErrOmitRawResourcesOrSubmit
}
severity := scanInfo.FailThresholdSeverity
if err := validateSeverity(severity); severity != "" && err != nil {
if err := utils.ValidateSeverity(severity); severity != "" && err != nil {
return err
}
-114
View File
@@ -1,114 +0,0 @@
package scan
import (
"context"
"fmt"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/core"
"github.com/kubescape/kubescape/v2/core/meta"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
"github.com/kubescape/kubescape/v2/pkg/imagescan"
"github.com/spf13/cobra"
)
type imageScanInfo struct {
Username string
Password string
}
// TODO(vladklokun): document image scanning on the Kubescape Docs Hub?
var (
imageExample = fmt.Sprintf(`
This command is still in BETA. Feel free to contact the Kubescape maintainers for more information.
Scan an image for vulnerabilities.
# Scan the 'nginx' image
%[1]s scan image "nginx"
# Image scan documentation:
# https://hub.armosec.io/docs/images
`, cautils.ExecName())
)
// imageCmd represents the image command
func getImageCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo, imgScanInfo *imageScanInfo) *cobra.Command {
cmd := &cobra.Command{
Use: "image <IMAGE_NAME>",
Short: "Scan an image for vulnerabilities",
Example: imageExample,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("the command takes exactly one image name as an argument")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateImageScanInfo(scanInfo); err != nil {
return err
}
failOnSeverity := imagescan.ParseSeverity(scanInfo.FailThresholdSeverity)
ctx := context.Background()
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
creds := imagescan.RegistryCredentials{
Username: imgScanInfo.Username,
Password: imgScanInfo.Password,
}
userInput := args[0]
logger.L().Start(fmt.Sprintf("Scanning image: %s", userInput))
scanResults, err := svc.Scan(ctx, userInput, creds)
if err != nil {
logger.L().StopError(fmt.Sprintf("Failed to scan image: %s", userInput))
return err
}
logger.L().StopSuccess(fmt.Sprintf("Successfully scanned image: %s", userInput))
scanInfo.SetScanType(cautils.ScanTypeImage)
outputPrinters := core.GetOutputPrinters(scanInfo, ctx, "")
uiPrinter := core.GetUIPrinter(ctx, scanInfo, "")
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)
resultsHandler.ImageScanData = []cautils.ImageScanData{
{
PresenterConfig: scanResults,
Image: userInput,
},
}
resultsHandler.HandleResults(ctx)
if imagescan.ExceedsSeverityThreshold(scanResults, failOnSeverity) {
terminateOnExceedingSeverity(scanInfo, logger.L())
}
return err
},
}
cmd.PersistentFlags().StringVarP(&imgScanInfo.Username, "username", "u", "", "Username for registry login")
cmd.PersistentFlags().StringVarP(&imgScanInfo.Password, "password", "p", "", "Password for registry login")
return cmd
}
// validateImageScanInfo validates the ScanInfo struct for the `image` command
func validateImageScanInfo(scanInfo *cautils.ScanInfo) error {
severity := scanInfo.FailThresholdSeverity
if err := validateSeverity(severity); severity != "" && err != nil {
return err
}
return nil
}
+2 -4
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"strings"
"github.com/kubescape/kubescape/v2/cmd/utils"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/kubescape/v2/core/cautils/getter"
"github.com/kubescape/kubescape/v2/core/meta"
@@ -111,9 +112,6 @@ func GetScanCommand(ks meta.IKubescape) *cobra.Command {
scanCmd.AddCommand(getFrameworkCmd(ks, &scanInfo))
scanCmd.AddCommand(getWorkloadCmd(ks, &scanInfo))
isi := &imageScanInfo{}
scanCmd.AddCommand(getImageCmd(ks, &scanInfo, isi))
return scanCmd
}
@@ -140,7 +138,7 @@ func securityScan(scanInfo cautils.ScanInfo, ks meta.IKubescape) error {
return err
}
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), &scanInfo, terminateOnExceedingSeverity)
enforceSeverityThresholds(results.GetData().Report.SummaryDetails.GetResourcesSeverityCounters(), &scanInfo, utils.TerminateOnExceedingSeverity)
return nil
}
-87
View File
@@ -1,8 +1,6 @@
package scan
import (
"context"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
@@ -10,8 +8,6 @@ import (
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary"
"os"
"reflect"
"testing"
)
@@ -176,89 +172,6 @@ func Test_enforceSeverityThresholds(t *testing.T) {
}
}
type spyLogMessage struct {
Message string
Details map[string]string
}
type spyLogger struct {
setItems []spyLogMessage
}
func (l *spyLogger) Error(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Success(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Warning(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Info(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Debug(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) SetLevel(level string) error { return nil }
func (l *spyLogger) GetLevel() string { return "" }
func (l *spyLogger) SetWriter(w *os.File) {}
func (l *spyLogger) GetWriter() *os.File { return &os.File{} }
func (l *spyLogger) LoggerName() string { return "" }
func (l *spyLogger) Ctx(_ context.Context) helpers.ILogger { return l }
func (l *spyLogger) Start(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) StopSuccess(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) StopError(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Fatal(msg string, details ...helpers.IDetails) {
firstDetail := details[0]
detailsMap := map[string]string{firstDetail.Key(): firstDetail.Value().(string)}
newMsg := spyLogMessage{msg, detailsMap}
l.setItems = append(l.setItems, newMsg)
}
func (l *spyLogger) GetSpiedItems() []spyLogMessage {
return l.setItems
}
func Test_terminateOnExceedingSeverity(t *testing.T) {
expectedMessage := "result exceeds severity threshold"
expectedKey := "set severity threshold"
testCases := []struct {
Description string
ExpectedMessage string
ExpectedKey string
ExpectedValue string
Logger *spyLogger
}{
{
"Should log the Critical threshold that was set in scan info",
expectedMessage,
expectedKey,
apis.SeverityCriticalString,
&spyLogger{},
},
{
"Should log the High threshold that was set in scan info",
expectedMessage,
expectedKey,
apis.SeverityHighString,
&spyLogger{},
},
}
for _, tc := range testCases {
t.Run(
tc.Description,
func(t *testing.T) {
want := []spyLogMessage{
{tc.ExpectedMessage, map[string]string{tc.ExpectedKey: tc.ExpectedValue}},
}
scanInfo := &cautils.ScanInfo{FailThresholdSeverity: tc.ExpectedValue}
terminateOnExceedingSeverity(scanInfo, tc.Logger)
got := tc.Logger.GetSpiedItems()
if !reflect.DeepEqual(got, want) {
t.Errorf("got: %v, want: %v", got, want)
}
},
)
}
}
func TestSetSecurityViewScanInfo(t *testing.T) {
tests := []struct {
name string
-29
View File
@@ -96,35 +96,6 @@ func Test_validateFrameworkScanInfo(t *testing.T) {
}
}
func Test_validateSeverity(t *testing.T) {
testCases := []struct {
Description string
Input string
Want error
}{
{"low should be a valid severity", "low", nil},
{"Low should be a valid severity", "Low", nil},
{"medium should be a valid severity", "medium", nil},
{"Medium should be a valid severity", "Medium", nil},
{"high should be a valid severity", "high", nil},
{"Critical should be a valid severity", "Critical", nil},
{"critical should be a valid severity", "critical", nil},
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
input := testCase.Input
want := testCase.Want
got := validateSeverity(input)
if got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
}
}
func Test_validateWorkloadIdentifier(t *testing.T) {
testCases := []struct {
Description string
+28
View File
@@ -0,0 +1,28 @@
package utils
import (
"errors"
"fmt"
"strings"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
reporthandlingapis "github.com/kubescape/opa-utils/reporthandling/apis"
)
var ErrUnknownSeverity = errors.New(fmt.Sprintf("unknown severity. Supported severities are: %s", strings.Join(reporthandlingapis.GetSupportedSeverities(), ", ")))
// validateSeverity returns an error if a given severity is not known, nil otherwise
func ValidateSeverity(severity string) error {
for _, val := range reporthandlingapis.GetSupportedSeverities() {
if strings.EqualFold(severity, val) {
return nil
}
}
return ErrUnknownSeverity
}
func TerminateOnExceedingSeverity(scanInfo *cautils.ScanInfo, l helpers.ILogger) {
l.Fatal("result exceeds severity threshold", helpers.String("Set severity threshold", scanInfo.FailThresholdSeverity))
}
+124
View File
@@ -0,0 +1,124 @@
package utils
import (
"context"
"os"
"reflect"
"testing"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/kubescape/v2/core/cautils"
"github.com/kubescape/opa-utils/reporthandling/apis"
)
type spyLogMessage struct {
Message string
Details map[string]string
}
type spyLogger struct {
setItems []spyLogMessage
}
func (l *spyLogger) Error(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Success(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Warning(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Info(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Debug(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) SetLevel(level string) error { return nil }
func (l *spyLogger) GetLevel() string { return "" }
func (l *spyLogger) SetWriter(w *os.File) {}
func (l *spyLogger) GetWriter() *os.File { return &os.File{} }
func (l *spyLogger) LoggerName() string { return "" }
func (l *spyLogger) Ctx(_ context.Context) helpers.ILogger { return l }
func (l *spyLogger) Start(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) StopSuccess(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) StopError(msg string, details ...helpers.IDetails) {}
func (l *spyLogger) Fatal(msg string, details ...helpers.IDetails) {
firstDetail := details[0]
detailsMap := map[string]string{firstDetail.Key(): firstDetail.Value().(string)}
newMsg := spyLogMessage{msg, detailsMap}
l.setItems = append(l.setItems, newMsg)
}
func (l *spyLogger) GetSpiedItems() []spyLogMessage {
return l.setItems
}
func TestTerminateOnExceedingSeverity(t *testing.T) {
expectedMessage := "result exceeds severity threshold"
expectedKey := "Set severity threshold"
testCases := []struct {
Description string
ExpectedMessage string
ExpectedKey string
ExpectedValue string
Logger *spyLogger
}{
{
"Should log the Critical threshold that was set in scan info",
expectedMessage,
expectedKey,
apis.SeverityCriticalString,
&spyLogger{},
},
{
"Should log the High threshold that was set in scan info",
expectedMessage,
expectedKey,
apis.SeverityHighString,
&spyLogger{},
},
}
for _, tc := range testCases {
t.Run(
tc.Description,
func(t *testing.T) {
want := []spyLogMessage{
{tc.ExpectedMessage, map[string]string{tc.ExpectedKey: tc.ExpectedValue}},
}
scanInfo := &cautils.ScanInfo{FailThresholdSeverity: tc.ExpectedValue}
TerminateOnExceedingSeverity(scanInfo, tc.Logger)
got := tc.Logger.GetSpiedItems()
if !reflect.DeepEqual(got, want) {
t.Errorf("got: %v, want: %v", got, want)
}
},
)
}
}
func TestValidateSeverity(t *testing.T) {
testCases := []struct {
Description string
Input string
Want error
}{
{"low should be a valid severity", "low", nil},
{"Low should be a valid severity", "Low", nil},
{"medium should be a valid severity", "medium", nil},
{"Medium should be a valid severity", "Medium", nil},
{"high should be a valid severity", "high", nil},
{"Critical should be a valid severity", "Critical", nil},
{"critical should be a valid severity", "critical", nil},
{"Unknown should be an invalid severity", "Unknown", ErrUnknownSeverity},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
input := testCase.Input
want := testCase.Want
got := ValidateSeverity(input)
if got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
}
}
+49
View File
@@ -0,0 +1,49 @@
package core
import (
"context"
"fmt"
"github.com/anchore/grype/grype/presenter/models"
logger "github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
ksmetav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
"github.com/kubescape/kubescape/v2/pkg/imagescan"
)
func (ks *Kubescape) ScanImage(ctx context.Context, imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) {
logger.L().Start(fmt.Sprintf("Scanning image: %s", imgScanInfo.Image))
dbCfg, _ := imagescan.NewDefaultDBConfig()
svc := imagescan.NewScanService(dbCfg)
creds := imagescan.RegistryCredentials{
Username: imgScanInfo.Username,
Password: imgScanInfo.Password,
}
scanResults, err := svc.Scan(ctx, imgScanInfo.Image, creds)
if err != nil {
logger.L().StopError(fmt.Sprintf("Failed to scan image: %s", imgScanInfo.Image))
return nil, err
}
logger.L().StopSuccess(fmt.Sprintf("Successfully scanned image: %s", imgScanInfo.Image))
scanInfo.SetScanType(cautils.ScanTypeImage)
outputPrinters := GetOutputPrinters(scanInfo, ctx, "")
uiPrinter := GetUIPrinter(ctx, scanInfo, "")
resultsHandler := resultshandling.NewResultsHandler(nil, outputPrinters, uiPrinter)
resultsHandler.ImageScanData = []cautils.ImageScanData{
{
PresenterConfig: scanResults,
Image: imgScanInfo.Image,
},
}
return scanResults, resultsHandler.HandleResults(ctx)
}
+1 -2
View File
@@ -97,9 +97,8 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo) e
Image: patchedImageName,
},
}
resultsHandler.HandleResults(ctx)
return nil
return resultsHandler.HandleResults(ctx)
}
func disableCopaLogger() {
@@ -0,0 +1,7 @@
package v1
type ImageScanInfo struct {
Username string
Password string
Image string
}
+4
View File
@@ -3,6 +3,7 @@ package meta
import (
"context"
"github.com/anchore/grype/grype/presenter/models"
"github.com/kubescape/kubescape/v2/core/cautils"
metav1 "github.com/kubescape/kubescape/v2/core/meta/datastructures/v1"
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
@@ -25,4 +26,7 @@ type IKubescape interface {
// patch
Patch(ctx context.Context, patchInfo *metav1.PatchInfo) error
// scan image
ScanImage(ctx context.Context, imgScanInfo *metav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error)
}