Skip to content

Commit

Permalink
send skipTLSVerify and Insecure in image scanning command (#212)
Browse files Browse the repository at this point in the history
* send skipTLSVerify and Insecure in image scanning command

Signed-off-by: Amir Malka <[email protected]>

* update go.mod

Signed-off-by: Amir Malka <[email protected]>

* fix test

Signed-off-by: Amir Malka <[email protected]>

* make map

Signed-off-by: Amir Malka <[email protected]>

---------

Signed-off-by: Amir Malka <[email protected]>
amirmalka authored Mar 4, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 6529cd8 commit 5bb9674
Showing 5 changed files with 151 additions and 20 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ require (
github.com/armosec/utils-k8s-go v0.0.24
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/deckarep/golang-set/v2 v2.6.0
github.com/distribution/reference v0.5.0
github.com/docker/docker v24.0.7+incompatible
github.com/go-openapi/runtime v0.26.0
github.com/google/go-containerregistry v0.16.1
@@ -98,7 +99,6 @@ require (
github.com/containerd/typeurl/v2 v2.1.1 // indirect
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/cli v24.0.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
8 changes: 7 additions & 1 deletion mainhandler/imageregistryhandler.go
Original file line number Diff line number Diff line change
@@ -800,7 +800,13 @@ func (registryScan *registryScan) setRegistryInfoFromConfigMap(registryInfo *arm
registryInfo.Exclude = registryConfig.Exclude
}

func getRegistryScanSecrets(k8sAPI *k8sinterface.KubernetesApi, namespace, secretName string) ([]k8sinterface.IWorkload, error) {
// KubernetesApiSecrets is an interface for getting workloads from k8s api
type IWorkloadsGetter interface {
GetWorkload(namespace, kind, name string) (k8sinterface.IWorkload, error)
ListWorkloads2(namespace, kind string) ([]k8sinterface.IWorkload, error)
}

func getRegistryScanSecrets(k8sAPI IWorkloadsGetter, namespace, secretName string) ([]k8sinterface.IWorkload, error) {
if secretName != "" {
secret, err := k8sAPI.GetWorkload(namespace, "Secret", secretName)
if err == nil && secret != nil {
15 changes: 15 additions & 0 deletions mainhandler/testdata/vulnscan/registry-secret.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"apiVersion": "v1",
"kind": "Secret",
"type": "Opaque",
"data": {
"registriesAuth": "WyAgICAgCiAgewogICAgInJlZ2lzdHJ5IjogImRvY2tlci5pbyIsCiAgICAidXNlcm5hbWUiOiAidGVzdC11c2VyIiwKICAgICJwYXNzd29yZCI6ICJ0ZXN0LXBhc3MiLAogICAgImF1dGhfbWV0aG9kIjogImNyZWRlbnRpYWxzIiwKICAgICJodHRwIjogdHJ1ZQogIH0sCiAgewogICAgInJlZ2lzdHJ5IjogInF1YXkuaW8iLAogICAgInVzZXJuYW1lIjogInRlc3QtdXNlci1xdWF5IiwKICAgICJwYXNzd29yZCI6ICJ0ZXN0LXBhc3MtcXVheSIsCiAgICAiYXV0aF9tZXRob2QiOiAiY3JlZGVudGlhbHMiLAogICAgInNraXBUbHNWZXJpZnkiOiB0cnVlCiAgfQpdCg=="
},
"metadata": {
"creationTimestamp": "2024-03-03T13:45:44Z",
"name": "kubescape-registry-scan-test-secret",
"namespace": "kubescape",
"resourceVersion": "80227",
"uid": "0617be65-cf6c-4cac-8bcf-d26592b34156"
}
}
89 changes: 71 additions & 18 deletions mainhandler/vulnscan.go
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import (
"strings"
"time"

"github.com/distribution/reference"
dockerregistry "github.com/docker/docker/api/types/registry"
"github.com/kubescape/backend/pkg/server/v1/systemreports"
"github.com/kubescape/go-logger"
@@ -27,6 +28,7 @@ import (
"github.com/armosec/armoapi-go/apis"
apitypes "github.com/armosec/armoapi-go/armotypes"
"github.com/armosec/armoapi-go/identifiers"
"github.com/armosec/utils-k8s-go/armometadata"

"github.com/armosec/utils-go/httputils"
"github.com/kubescape/k8s-interface/cloudsupport"
@@ -318,13 +320,13 @@ func (actionHandler *ActionHandler) scanImage(ctx context.Context, sessionObj *u
return fmt.Errorf("failed to get container for image %s", actionHandler.command.Args[utils.ArgsContainerData])
}

authConfig, err := actionHandler.getAuthConfig(pod, containerData.ImageTag)
imageScanConfig, err := getImageScanConfig(actionHandler.k8sAPI, actionHandler.config.Namespace(), pod, containerData.ImageTag)
if err != nil {
return fmt.Errorf("failed to get auth config for image %s", containerData.ImageTag)
}

span.AddEvent("scanning", trace.WithAttributes(attribute.String("wlid", actionHandler.wlid)))
cmd := actionHandler.getImageScanCommand(containerData, sessionObj, authConfig)
cmd := actionHandler.getImageScanCommand(containerData, sessionObj, imageScanConfig)

if err := sendCommandToScanner(ctx, actionHandler.config, cmd, sessionObj.Command.CommandName); err != nil {
return fmt.Errorf("failed to send command to scanner with err %v", err)
@@ -345,31 +347,44 @@ func (actionHandler *ActionHandler) scanFilteredSBOM(ctx context.Context, sessio
return fmt.Errorf("failed to get container for image %s", actionHandler.command.Args[utils.ArgsContainerData])
}

// scanning a filtered SBOM (SBOM already downloaded) so AuthConfig can be empty
span.AddEvent("scanning", trace.WithAttributes(attribute.String("wlid", actionHandler.wlid)))
cmd := actionHandler.getImageScanCommand(containerData, sessionObj, []dockerregistry.AuthConfig{})
cmd := actionHandler.getImageScanCommand(containerData, sessionObj, &ImageScanConfig{})

if err := sendCommandToScanner(ctx, actionHandler.config, cmd, apis.TypeScanImages); err != nil {
return fmt.Errorf("failed to send command to scanner with err %v", err)
}
return nil
}
func (actionHandler *ActionHandler) getImageScanCommand(containerData *utils.ContainerData, sessionObj *utils.SessionObj, authConfig []dockerregistry.AuthConfig) *apis.WebsocketScanCommand {
func (actionHandler *ActionHandler) getImageScanCommand(containerData *utils.ContainerData, sessionObj *utils.SessionObj, imageScanConfig *ImageScanConfig) *apis.WebsocketScanCommand {

cmd := &apis.WebsocketScanCommand{
ImageScanParams: apis.ImageScanParams{
Session: apis.SessionChain{
ActionTitle: string(sessionObj.Command.CommandName),
JobIDs: make([]string, 0),
Timestamp: sessionObj.Reporter.GetTimestamp(),
},
Args: map[string]interface{}{},
ImageTag: containerData.ImageTag,
Credentialslist: authConfig,
Credentialslist: imageScanConfig.authConfigs,
JobID: sessionObj.Reporter.GetJobID(),
},
Wlid: containerData.Wlid,
ContainerName: containerData.ContainerName,
ImageHash: containerData.ImageID,
}

if imageScanConfig.skipTLSVerify != nil && *imageScanConfig.skipTLSVerify {
logger.L().Debug("setting skipTLSVerify (true) in image scan command", helpers.String("imageTag", containerData.ImageTag))
cmd.Args[identifiers.AttributeSkipTLSVerify] = true
}

if imageScanConfig.insecure != nil && *imageScanConfig.insecure {
logger.L().Debug("setting insecure (true) in image scan command", helpers.String("imageTag", containerData.ImageTag))
cmd.Args[identifiers.AttributeUseHTTP] = true
}

// Add instanceID only if container is not empty
if containerData.Slug != "" {
cmd.InstanceID = &containerData.Slug
@@ -381,15 +396,29 @@ func (actionHandler *ActionHandler) getImageScanCommand(containerData *utils.Con
return cmd
}

func (actionHandler *ActionHandler) getAuthConfig(pod *corev1.Pod, imageTag string) ([]dockerregistry.AuthConfig, error) {
registryAuth := []dockerregistry.AuthConfig{}
type ImageScanConfig struct {
skipTLSVerify *bool
insecure *bool
authConfigs []dockerregistry.AuthConfig
}

func getImageScanConfig(k8sAPI IWorkloadsGetter, namespace string, pod *corev1.Pod, imageTag string) (*ImageScanConfig, error) {
imageScanConfig := ImageScanConfig{}
registryName := getRegistryNameFromImageTag(imageTag)
logger.L().Debug("parsed registry name from image tag", helpers.String("registryName", registryName), helpers.String("imageTag", imageTag))

// build a list of secrets from the ImagePullSecrets
if secrets, err := getRegistryScanSecrets(actionHandler.k8sAPI, actionHandler.config.Namespace(), ""); err == nil && len(secrets) > 0 {
if secrets, err := getRegistryScanSecrets(k8sAPI, namespace, ""); err == nil && len(secrets) > 0 {
for i := range secrets {
if auth, err := parseRegistryAuthSecret(secrets[i]); err == nil {
for _, authConfig := range auth {
registryAuth = append(registryAuth, dockerregistry.AuthConfig{
// if we have a registry name and it matches the current registry, check if we need to skip TLS verification
if registryName != "" && containsIgnoreCase(authConfig.Registry, registryName) {
imageScanConfig.skipTLSVerify = authConfig.SkipTLSVerify
imageScanConfig.insecure = authConfig.Insecure
}

imageScanConfig.authConfigs = append(imageScanConfig.authConfigs, dockerregistry.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.Registry,
@@ -399,17 +428,19 @@ func (actionHandler *ActionHandler) getAuthConfig(pod *corev1.Pod, imageTag stri
}
}

// TODO: this should not happen every scan
// build a list of secrets from the the registry secrets
secrets, err := cloudsupport.GetImageRegistryCredentials(imageTag, pod)
if err != nil {
return nil, err
}
for i := range secrets {
registryAuth = append(registryAuth, secrets[i])
if pod != nil {
// TODO: this should not happen every scan
// build a list of secrets from the the registry secrets
secrets, err := cloudsupport.GetImageRegistryCredentials(imageTag, pod)
if err != nil {
return nil, err
}
for i := range secrets {
imageScanConfig.authConfigs = append(imageScanConfig.authConfigs, secrets[i])
}
}

return registryAuth, nil
return &imageScanConfig, nil
}

func prepareSessionChain(sessionObj *utils.SessionObj, websocketScanCommand *apis.WebsocketScanCommand, actionHandler *ActionHandler) {
@@ -505,3 +536,25 @@ func sendCommandToScanner(ctx context.Context, config config.IConfig, webSocketS
}
return err
}

func normalizeReference(ref string) string {
n, err := reference.ParseNormalizedNamed(ref)
if err != nil {
return ref
}
return n.String()
}

func getRegistryNameFromImageTag(imageTag string) string {
imageTagNormalized := normalizeReference(imageTag)
imageInfo, err := armometadata.ImageTagToImageInfo(imageTagNormalized)
if err != nil {
return ""
}
return imageInfo.Registry
}

// containsIgnoreCase reports whether substr is within s (ignoring case)
func containsIgnoreCase(s, substr string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}
57 changes: 57 additions & 0 deletions mainhandler/vulnscan_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,58 @@
package mainhandler

import (
"testing"

_ "embed"

dockerregistry "github.com/docker/docker/api/types/registry"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/stretchr/testify/assert"
)

//go:embed testdata/vulnscan/registry-secret.json
var registrySecret []byte

type WorkloadsGetterMock struct{}

func (mock *WorkloadsGetterMock) GetWorkload(namespace, kind, name string) (k8sinterface.IWorkload, error) {
wl, err := workloadinterface.NewWorkload(registrySecret)
if err != nil {
panic(err)
}
return wl, nil
}
func (mock *WorkloadsGetterMock) ListWorkloads2(namespace, kind string) ([]k8sinterface.IWorkload, error) {
wl, _ := mock.GetWorkload(namespace, kind, "")
return []k8sinterface.IWorkload{wl}, nil
}

func Test_ActionHandler_getImageScanConfig(t *testing.T) {
expectedAuthConfigs := []dockerregistry.AuthConfig{
{
Username: "test-user",
Password: "test-pass",
ServerAddress: "docker.io",
},
{
Username: "test-user-quay",
Password: "test-pass-quay",
ServerAddress: "quay.io",
},
}

k8sApiMock := &WorkloadsGetterMock{}

res, err := getImageScanConfig(k8sApiMock, "", nil, "nginx:latest") // no registry treated as docker.io
assert.NoError(t, err)
assert.Equal(t, expectedAuthConfigs, res.authConfigs)
assert.True(t, *res.insecure)
assert.Nil(t, res.skipTLSVerify)

res, err = getImageScanConfig(k8sApiMock, "", nil, "quay.IO/kubescape/nginx:latest")
assert.NoError(t, err)
assert.Equal(t, expectedAuthConfigs, res.authConfigs)
assert.Nil(t, res.insecure)
assert.True(t, *res.skipTLSVerify)
}

0 comments on commit 5bb9674

Please sign in to comment.