Skip to content

Commit 5bb9674

Browse files
authored
send skipTLSVerify and Insecure in image scanning command (#212)
* 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]>
1 parent 6529cd8 commit 5bb9674

File tree

5 files changed

+151
-20
lines changed

5 files changed

+151
-20
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/armosec/utils-k8s-go v0.0.24
1111
github.com/cenkalti/backoff v2.2.1+incompatible
1212
github.com/deckarep/golang-set/v2 v2.6.0
13+
github.com/distribution/reference v0.5.0
1314
github.com/docker/docker v24.0.7+incompatible
1415
github.com/go-openapi/runtime v0.26.0
1516
github.com/google/go-containerregistry v0.16.1
@@ -98,7 +99,6 @@ require (
9899
github.com/containerd/typeurl/v2 v2.1.1 // indirect
99100
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
100101
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
101-
github.com/distribution/reference v0.5.0 // indirect
102102
github.com/docker/cli v24.0.0+incompatible // indirect
103103
github.com/docker/distribution v2.8.3+incompatible // indirect
104104
github.com/docker/docker-credential-helpers v0.7.0 // indirect

mainhandler/imageregistryhandler.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,13 @@ func (registryScan *registryScan) setRegistryInfoFromConfigMap(registryInfo *arm
800800
registryInfo.Exclude = registryConfig.Exclude
801801
}
802802

803-
func getRegistryScanSecrets(k8sAPI *k8sinterface.KubernetesApi, namespace, secretName string) ([]k8sinterface.IWorkload, error) {
803+
// KubernetesApiSecrets is an interface for getting workloads from k8s api
804+
type IWorkloadsGetter interface {
805+
GetWorkload(namespace, kind, name string) (k8sinterface.IWorkload, error)
806+
ListWorkloads2(namespace, kind string) ([]k8sinterface.IWorkload, error)
807+
}
808+
809+
func getRegistryScanSecrets(k8sAPI IWorkloadsGetter, namespace, secretName string) ([]k8sinterface.IWorkload, error) {
804810
if secretName != "" {
805811
secret, err := k8sAPI.GetWorkload(namespace, "Secret", secretName)
806812
if err == nil && secret != nil {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"apiVersion": "v1",
3+
"kind": "Secret",
4+
"type": "Opaque",
5+
"data": {
6+
"registriesAuth": "WyAgICAgCiAgewogICAgInJlZ2lzdHJ5IjogImRvY2tlci5pbyIsCiAgICAidXNlcm5hbWUiOiAidGVzdC11c2VyIiwKICAgICJwYXNzd29yZCI6ICJ0ZXN0LXBhc3MiLAogICAgImF1dGhfbWV0aG9kIjogImNyZWRlbnRpYWxzIiwKICAgICJodHRwIjogdHJ1ZQogIH0sCiAgewogICAgInJlZ2lzdHJ5IjogInF1YXkuaW8iLAogICAgInVzZXJuYW1lIjogInRlc3QtdXNlci1xdWF5IiwKICAgICJwYXNzd29yZCI6ICJ0ZXN0LXBhc3MtcXVheSIsCiAgICAiYXV0aF9tZXRob2QiOiAiY3JlZGVudGlhbHMiLAogICAgInNraXBUbHNWZXJpZnkiOiB0cnVlCiAgfQpdCg=="
7+
},
8+
"metadata": {
9+
"creationTimestamp": "2024-03-03T13:45:44Z",
10+
"name": "kubescape-registry-scan-test-secret",
11+
"namespace": "kubescape",
12+
"resourceVersion": "80227",
13+
"uid": "0617be65-cf6c-4cac-8bcf-d26592b34156"
14+
}
15+
}

mainhandler/vulnscan.go

+71-18
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010
"time"
1111

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

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

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

326328
span.AddEvent("scanning", trace.WithAttributes(attribute.String("wlid", actionHandler.wlid)))
327-
cmd := actionHandler.getImageScanCommand(containerData, sessionObj, authConfig)
329+
cmd := actionHandler.getImageScanCommand(containerData, sessionObj, imageScanConfig)
328330

329331
if err := sendCommandToScanner(ctx, actionHandler.config, cmd, sessionObj.Command.CommandName); err != nil {
330332
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
345347
return fmt.Errorf("failed to get container for image %s", actionHandler.command.Args[utils.ArgsContainerData])
346348
}
347349

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

351354
if err := sendCommandToScanner(ctx, actionHandler.config, cmd, apis.TypeScanImages); err != nil {
352355
return fmt.Errorf("failed to send command to scanner with err %v", err)
353356
}
354357
return nil
355358
}
356-
func (actionHandler *ActionHandler) getImageScanCommand(containerData *utils.ContainerData, sessionObj *utils.SessionObj, authConfig []dockerregistry.AuthConfig) *apis.WebsocketScanCommand {
359+
func (actionHandler *ActionHandler) getImageScanCommand(containerData *utils.ContainerData, sessionObj *utils.SessionObj, imageScanConfig *ImageScanConfig) *apis.WebsocketScanCommand {
360+
357361
cmd := &apis.WebsocketScanCommand{
358362
ImageScanParams: apis.ImageScanParams{
359363
Session: apis.SessionChain{
360364
ActionTitle: string(sessionObj.Command.CommandName),
361365
JobIDs: make([]string, 0),
362366
Timestamp: sessionObj.Reporter.GetTimestamp(),
363367
},
368+
Args: map[string]interface{}{},
364369
ImageTag: containerData.ImageTag,
365-
Credentialslist: authConfig,
370+
Credentialslist: imageScanConfig.authConfigs,
366371
JobID: sessionObj.Reporter.GetJobID(),
367372
},
368373
Wlid: containerData.Wlid,
369374
ContainerName: containerData.ContainerName,
370375
ImageHash: containerData.ImageID,
371376
}
372377

378+
if imageScanConfig.skipTLSVerify != nil && *imageScanConfig.skipTLSVerify {
379+
logger.L().Debug("setting skipTLSVerify (true) in image scan command", helpers.String("imageTag", containerData.ImageTag))
380+
cmd.Args[identifiers.AttributeSkipTLSVerify] = true
381+
}
382+
383+
if imageScanConfig.insecure != nil && *imageScanConfig.insecure {
384+
logger.L().Debug("setting insecure (true) in image scan command", helpers.String("imageTag", containerData.ImageTag))
385+
cmd.Args[identifiers.AttributeUseHTTP] = true
386+
}
387+
373388
// Add instanceID only if container is not empty
374389
if containerData.Slug != "" {
375390
cmd.InstanceID = &containerData.Slug
@@ -381,15 +396,29 @@ func (actionHandler *ActionHandler) getImageScanCommand(containerData *utils.Con
381396
return cmd
382397
}
383398

384-
func (actionHandler *ActionHandler) getAuthConfig(pod *corev1.Pod, imageTag string) ([]dockerregistry.AuthConfig, error) {
385-
registryAuth := []dockerregistry.AuthConfig{}
399+
type ImageScanConfig struct {
400+
skipTLSVerify *bool
401+
insecure *bool
402+
authConfigs []dockerregistry.AuthConfig
403+
}
404+
405+
func getImageScanConfig(k8sAPI IWorkloadsGetter, namespace string, pod *corev1.Pod, imageTag string) (*ImageScanConfig, error) {
406+
imageScanConfig := ImageScanConfig{}
407+
registryName := getRegistryNameFromImageTag(imageTag)
408+
logger.L().Debug("parsed registry name from image tag", helpers.String("registryName", registryName), helpers.String("imageTag", imageTag))
386409

387410
// build a list of secrets from the ImagePullSecrets
388-
if secrets, err := getRegistryScanSecrets(actionHandler.k8sAPI, actionHandler.config.Namespace(), ""); err == nil && len(secrets) > 0 {
411+
if secrets, err := getRegistryScanSecrets(k8sAPI, namespace, ""); err == nil && len(secrets) > 0 {
389412
for i := range secrets {
390413
if auth, err := parseRegistryAuthSecret(secrets[i]); err == nil {
391414
for _, authConfig := range auth {
392-
registryAuth = append(registryAuth, dockerregistry.AuthConfig{
415+
// if we have a registry name and it matches the current registry, check if we need to skip TLS verification
416+
if registryName != "" && containsIgnoreCase(authConfig.Registry, registryName) {
417+
imageScanConfig.skipTLSVerify = authConfig.SkipTLSVerify
418+
imageScanConfig.insecure = authConfig.Insecure
419+
}
420+
421+
imageScanConfig.authConfigs = append(imageScanConfig.authConfigs, dockerregistry.AuthConfig{
393422
Username: authConfig.Username,
394423
Password: authConfig.Password,
395424
ServerAddress: authConfig.Registry,
@@ -399,17 +428,19 @@ func (actionHandler *ActionHandler) getAuthConfig(pod *corev1.Pod, imageTag stri
399428
}
400429
}
401430

402-
// TODO: this should not happen every scan
403-
// build a list of secrets from the the registry secrets
404-
secrets, err := cloudsupport.GetImageRegistryCredentials(imageTag, pod)
405-
if err != nil {
406-
return nil, err
407-
}
408-
for i := range secrets {
409-
registryAuth = append(registryAuth, secrets[i])
431+
if pod != nil {
432+
// TODO: this should not happen every scan
433+
// build a list of secrets from the the registry secrets
434+
secrets, err := cloudsupport.GetImageRegistryCredentials(imageTag, pod)
435+
if err != nil {
436+
return nil, err
437+
}
438+
for i := range secrets {
439+
imageScanConfig.authConfigs = append(imageScanConfig.authConfigs, secrets[i])
440+
}
410441
}
411442

412-
return registryAuth, nil
443+
return &imageScanConfig, nil
413444
}
414445

415446
func prepareSessionChain(sessionObj *utils.SessionObj, websocketScanCommand *apis.WebsocketScanCommand, actionHandler *ActionHandler) {
@@ -505,3 +536,25 @@ func sendCommandToScanner(ctx context.Context, config config.IConfig, webSocketS
505536
}
506537
return err
507538
}
539+
540+
func normalizeReference(ref string) string {
541+
n, err := reference.ParseNormalizedNamed(ref)
542+
if err != nil {
543+
return ref
544+
}
545+
return n.String()
546+
}
547+
548+
func getRegistryNameFromImageTag(imageTag string) string {
549+
imageTagNormalized := normalizeReference(imageTag)
550+
imageInfo, err := armometadata.ImageTagToImageInfo(imageTagNormalized)
551+
if err != nil {
552+
return ""
553+
}
554+
return imageInfo.Registry
555+
}
556+
557+
// containsIgnoreCase reports whether substr is within s (ignoring case)
558+
func containsIgnoreCase(s, substr string) bool {
559+
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
560+
}

mainhandler/vulnscan_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -1 +1,58 @@
11
package mainhandler
2+
3+
import (
4+
"testing"
5+
6+
_ "embed"
7+
8+
dockerregistry "github.com/docker/docker/api/types/registry"
9+
"github.com/kubescape/k8s-interface/k8sinterface"
10+
"github.com/kubescape/k8s-interface/workloadinterface"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
//go:embed testdata/vulnscan/registry-secret.json
15+
var registrySecret []byte
16+
17+
type WorkloadsGetterMock struct{}
18+
19+
func (mock *WorkloadsGetterMock) GetWorkload(namespace, kind, name string) (k8sinterface.IWorkload, error) {
20+
wl, err := workloadinterface.NewWorkload(registrySecret)
21+
if err != nil {
22+
panic(err)
23+
}
24+
return wl, nil
25+
}
26+
func (mock *WorkloadsGetterMock) ListWorkloads2(namespace, kind string) ([]k8sinterface.IWorkload, error) {
27+
wl, _ := mock.GetWorkload(namespace, kind, "")
28+
return []k8sinterface.IWorkload{wl}, nil
29+
}
30+
31+
func Test_ActionHandler_getImageScanConfig(t *testing.T) {
32+
expectedAuthConfigs := []dockerregistry.AuthConfig{
33+
{
34+
Username: "test-user",
35+
Password: "test-pass",
36+
ServerAddress: "docker.io",
37+
},
38+
{
39+
Username: "test-user-quay",
40+
Password: "test-pass-quay",
41+
ServerAddress: "quay.io",
42+
},
43+
}
44+
45+
k8sApiMock := &WorkloadsGetterMock{}
46+
47+
res, err := getImageScanConfig(k8sApiMock, "", nil, "nginx:latest") // no registry treated as docker.io
48+
assert.NoError(t, err)
49+
assert.Equal(t, expectedAuthConfigs, res.authConfigs)
50+
assert.True(t, *res.insecure)
51+
assert.Nil(t, res.skipTLSVerify)
52+
53+
res, err = getImageScanConfig(k8sApiMock, "", nil, "quay.IO/kubescape/nginx:latest")
54+
assert.NoError(t, err)
55+
assert.Equal(t, expectedAuthConfigs, res.authConfigs)
56+
assert.Nil(t, res.insecure)
57+
assert.True(t, *res.skipTLSVerify)
58+
}

0 commit comments

Comments
 (0)