fix patch command logic

Signed-off-by: Anubhav Gupta <mail.anubhav06@gmail.com>
This commit is contained in:
Anubhav Gupta
2023-09-01 09:43:09 +05:30
parent 18b4d3cbec
commit 53f5667694
10 changed files with 59 additions and 43 deletions

View File

@@ -4,9 +4,11 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
ref "github.com/distribution/distribution/reference"
"github.com/docker/distribution/reference"
"github.com/kubescape/go-logger"
"github.com/kubescape/kubescape/v2/core/cautils"
@@ -47,7 +49,6 @@ func GetPatchCmd(ks meta.IKubescape) *cobra.Command {
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().BoolVarP(&patchInfo.IncludeReport, "report", "r", false, "Generate & save a before and after report for the image (json format))")
patchCmd.PersistentFlags().StringVarP(&patchInfo.Username, "username", "u", "", "Username for registry login")
patchCmd.PersistentFlags().StringVarP(&patchInfo.Password, "password", "p", "", "Password for registry login")
@@ -62,10 +63,15 @@ func validateImagePatchInfo(patchInfo *metav1.PatchInfo) error {
return errors.New("image tag is required")
}
// Check if image is in canonical format (required by copacetic for patching images)
// Convert image to canonical format (required by copacetic for patching images)
patchInfoImage, err := cautils.Normalize_image_name(patchInfo.Image)
if err != nil {
return nil
}
patchInfo.Image = patchInfoImage
// Parse the image full name to get image name and tag
named, err := ref.ParseNamed(patchInfo.Image)
if err != nil{
if err != nil {
return err
}
@@ -87,8 +93,18 @@ func validateImagePatchInfo(patchInfo *metav1.PatchInfo) error {
}
}
patchInfo.ImageName = named.Name()
// Extract the "image" name from the canonical Image URL
// If it's an official docker image, we store just the "image-name". Else if a docker repo then we store as "repo/image". Else complete URL
ref, _ := reference.ParseNormalizedNamed(patchInfoImage)
imageName := named.Name()
if strings.Contains(imageName, "docker.io/library/") {
imageName = reference.Path(ref)
imageName = imageName[strings.LastIndex(imageName, "/")+1:]
} else if strings.Contains(imageName, "docker.io/") {
imageName = reference.Path(ref)
}
patchInfo.ImageName = imageName
return nil
}

View File

@@ -1,10 +1,10 @@
package opaprocessor
package cautils
import (
"github.com/docker/distribution/reference"
)
func normalize_image_name(img string) (string, error) {
func Normalize_image_name(img string) (string, error) {
name, err := reference.ParseNormalizedNamed(img)
if err != nil {
return "", err

View File

@@ -1,4 +1,4 @@
package opaprocessor
package cautils
import (
"testing"
@@ -21,7 +21,7 @@ func Test_normalize_name(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
name, _ := normalize_image_name(tt.img)
name, _ := Normalize_image_name(tt.img)
assert.Equal(t, tt.want, name, tt.name)
})
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/kubescape/kubescape/v2/core/pkg/resultshandling"
copa "github.com/project-copacetic/copacetic/pkg/patch"
log "github.com/sirupsen/logrus"
)
func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo) error {
@@ -52,40 +53,37 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo) e
// ===================== Patch the image using copacetic =====================
logger.L().Start("Patching image...")
sout, serr := os.Stdout, os.Stderr
if logger.L().GetLevel() != "debug" {
disableCopaLogger()
}
if err := copa.Patch(ctx, patchInfo.Timeout, patchInfo.BuildkitAddress, patchInfo.Image, fileName, patchInfo.PatchedImageTag, ""); err != nil {
return err
}
logger.L().StopSuccess("Patched image successfully")
if logger.L().GetLevel() != "debug" {
enableCopaLogger(sout, serr)
}
patchedImageName := fmt.Sprintf("%s:%s", patchInfo.ImageName, patchInfo.PatchedImageTag)
logger.L().StopSuccess(fmt.Sprintf("Patched image successfully. Loaded image: %s", patchedImageName))
// ===================== Re-scan the image =====================
// Re-scan the image
patchedImageName := fmt.Sprintf("%s:%s", patchInfo.ImageName, patchInfo.PatchedImageTag)
logger.L().Start(fmt.Sprintf("Re-Scanning image: %s", patchedImageName))
scanResultsPatched, err := svc.Scan(ctx, patchedImageName, creds)
if err != nil {
return err
}
// Save the patched image's scan results to a file in json format, if requested
fileNamePatched := fmt.Sprintf("%s:%s.json", patchInfo.ImageName, patchInfo.PatchedImageTag)
fileNamePatched = strings.ReplaceAll(fileNamePatched, "/", "-")
if patchInfo.IncludeReport {
pres = presenter.GetPresenter("json", "", false, *scanResultsPatched)
writer = printer.GetWriter(ctx, fileNamePatched)
if err := pres.Present(writer); err != nil {
return err
}
}
logger.L().StopSuccess(fmt.Sprintf("Successfully re-scanned image: %s", patchedImageName))
// ===================== Clean up =====================
// Remove the scan results files, which were used to patch the image
if !patchInfo.IncludeReport {
if err := os.Remove(fileName); err != nil {
logger.L().Warning(fmt.Sprintf("failed to remove residual file: %v", fileName), helpers.Error(err))
}
// Remove the scan results file, which was used to patch the image
if err := os.Remove(fileName); err != nil {
logger.L().Warning(fmt.Sprintf("failed to remove residual file: %v", fileName), helpers.Error(err))
}
// ===================== Results Handling =====================
@@ -103,11 +101,16 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo) e
}
resultsHandler.HandleResults(ctx)
if patchInfo.IncludeReport {
logger.L().Success("Results saved", helpers.String("filename", fileName))
logger.L().Success("Results saved", helpers.String("filename", fileNamePatched))
}
return nil
}
func disableCopaLogger() {
os.Stdout, os.Stderr = nil, nil
null, _ := os.Open(os.DevNull)
log.SetOutput(null)
}
func enableCopaLogger(sout, serr *os.File) {
os.Stdout, os.Stderr = sout, serr
log.SetOutput(os.Stdout)
}

View File

@@ -7,9 +7,8 @@ type PatchInfo struct {
PatchedImageTag string // can be empty, if empty then the image tag will be patched with the latest tag
BuildkitAddress string // buildkit address
Timeout time.Duration // timeout for patching an image
IncludeReport bool // include report in the output
// Image registry credentials
// Image registry credentials
Username string // username for registry login
Password string // password for registry login

View File

@@ -106,6 +106,6 @@ var imageNameNormalizeDefinition = func(bctx rego.BuiltinContext, a *ast.Term) (
if err != nil {
return nil, fmt.Errorf("invalid parameter type: %v", err)
}
normalizedName, err := normalize_image_name(string(aStr))
normalizedName, err := cautils.Normalize_image_name(string(aStr))
return ast.StringTerm(normalizedName), err
}

View File

@@ -75,7 +75,6 @@ func printConfigurationsScanning(opaSessionObj *cautils.OPASessionObj, ctx conte
}
func (jp *JsonPrinter) PrintImageScan(ctx context.Context, scanResults *models.PresenterConfig) error {
// presenterConfig, _ := presenter.ValidatedConfig("json", "", false)
pres := presenter.GetPresenter("json", "", false, *scanResults)
return pres.Present(jp.writer)

View File

@@ -48,8 +48,8 @@ func (ip *ImagePrinter) PrintConfigurationsScanning(summaryDetails *reportsummar
func (ip *ImagePrinter) PrintNextSteps() {
if ip.verboseMode {
printNextSteps(ip.writer, []string{CICDSetupText, installHelmText, imagePatchText}, true)
printNextSteps(ip.writer, []string{CICDSetupText, installHelmText}, true)
return
}
printNextSteps(ip.writer, []string{imageScanVerboseRunText, CICDSetupText, installHelmText, imagePatchText}, true)
printNextSteps(ip.writer, []string{imageScanVerboseRunText, CICDSetupText, installHelmText}, true)
}

View File

@@ -27,7 +27,6 @@ var (
clusterScanRunText = fmt.Sprintf("Run a cluster scan: %s", getCallToActionString("'$ kubescape scan'"))
installHelmText = fmt.Sprintf("Install Kubescape in your cluster for continuous monitoring: %s", linkToHelm)
CICDSetupText = fmt.Sprintf("Add Kubescape to your CI/CD pipeline: %s", linkToCICDSetup)
imagePatchText = fmt.Sprintf("Try patching your images with: %s", getCallToActionString("'$ kubescape patch'"))
complianceFrameworks = []string{"nsa", "mitre"}
cveSeverities = []string{"Critical", "High", "Medium", "Low", "Negligible", "Unknown"}
)

2
go.mod
View File

@@ -38,6 +38,7 @@ require (
github.com/schollz/progressbar/v3 v3.13.0
github.com/sergi/go-diff v1.3.1
github.com/sigstore/cosign/v2 v2.1.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4
github.com/whilp/git-urls v1.0.0
@@ -343,7 +344,6 @@ require (
github.com/sigstore/rekor v1.2.2-0.20230530122220-67cc9e58bd23 // indirect
github.com/sigstore/sigstore v1.7.1 // indirect
github.com/sigstore/timestamp-authority v1.1.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.0 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/spdx/tools-golang v0.5.3 // indirect