Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions adapters/v1/sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type SidecarSBOMAdapter struct {
client sbomscanner.SBOMScannerClient
maxImageSize int64
maxSBOMSize int
proxyRegistryMap map[string]string
scanTimeout time.Duration
scanEmbeddedSBOMs bool
memoryLimit string
Expand All @@ -45,11 +46,13 @@ func NewSidecarSBOMAdapter(
maxSBOMSize int,
scanEmbeddedSBOMs bool,
memoryLimit string,
proxyRegistryMap map[string]string,
) *SidecarSBOMAdapter {
return &SidecarSBOMAdapter{
client: client,
maxImageSize: maxImageSize,
maxSBOMSize: maxSBOMSize,
proxyRegistryMap: proxyRegistryMap,
scanTimeout: scanTimeout,
scanEmbeddedSBOMs: scanEmbeddedSBOMs,
memoryLimit: memoryLimit,
Expand All @@ -75,9 +78,12 @@ func (s *SidecarSBOMAdapter) CreateSBOM(ctx context.Context, name, imageID, imag
}
domainSBOM.Annotations[helpersv1.ImageTagMetadataKey] = imageTag

pullImageID := rewriteImageRef(imageID, s.proxyRegistryMap)
pullImageTag := rewriteImageRef(imageTag, s.proxyRegistryMap)

req := sbomscanner.ScanRequest{
ImageID: imageID,
ImageTag: imageTag,
ImageID: pullImageID,
ImageTag: pullImageTag,
Options: options,
MaxImageSize: s.maxImageSize,
MaxSBOMSize: int32(s.maxSBOMSize),
Expand Down
8 changes: 4 additions & 4 deletions adapters/v1/sidecar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestSidecarSBOMAdapter_CreateSBOM_Success(t *testing.T) {
},
}

adapter := NewSidecarSBOMAdapter(mock, 5*time.Minute, 512*1024*1024, 20*1024*1024, false, "5Gi")
adapter := NewSidecarSBOMAdapter(mock, 5*time.Minute, 512*1024*1024, 20*1024*1024, false, "5Gi", nil)

sbom, err := adapter.CreateSBOM(context.Background(), "test-sbom", "", "nginx:latest", domain.RegistryOptions{})
require.NoError(t, err)
Expand All @@ -81,7 +81,7 @@ func TestSidecarSBOMAdapter_CreateSBOM_TooLarge(t *testing.T) {
},
}

adapter := NewSidecarSBOMAdapter(mock, 5*time.Minute, 512*1024*1024, 20*1024*1024, false, "5Gi")
adapter := NewSidecarSBOMAdapter(mock, 5*time.Minute, 512*1024*1024, 20*1024*1024, false, "5Gi", nil)

sbom, err := adapter.CreateSBOM(context.Background(), "test-sbom", "", "large-image:latest", domain.RegistryOptions{})
require.NoError(t, err)
Expand All @@ -100,7 +100,7 @@ func TestSidecarSBOMAdapter_CreateSBOM_CrashRetry(t *testing.T) {
},
}

adapter := NewSidecarSBOMAdapter(mock, 5*time.Minute, 512*1024*1024, 20*1024*1024, false, "5Gi")
adapter := NewSidecarSBOMAdapter(mock, 5*time.Minute, 512*1024*1024, 20*1024*1024, false, "5Gi", nil)

// First two attempts should return crash error (for retry)
for i := 0; i < 2; i++ {
Expand All @@ -122,6 +122,6 @@ func TestSidecarSBOMAdapter_Version(t *testing.T) {
healthReady: true,
}

adapter := NewSidecarSBOMAdapter(mock, 5*time.Minute, 512*1024*1024, 20*1024*1024, false, "5Gi")
adapter := NewSidecarSBOMAdapter(mock, 5*time.Minute, 512*1024*1024, 20*1024*1024, false, "5Gi", nil)
assert.Equal(t, "v0.100.0", adapter.Version())
}
41 changes: 37 additions & 4 deletions adapters/v1/syft.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"runtime"
"sort"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -52,6 +53,7 @@ func gcpCredentials(ctx context.Context) (*image.RegistryCredentials, error) {
type SyftAdapter struct {
maxImageSize int64
maxSBOMSize int
proxyRegistryMap map[string]string
pullMutex sync.Mutex
scanTimeout time.Duration
scanEmbeddedSBOMs bool
Expand All @@ -62,15 +64,44 @@ const digestDelim = "@"
var _ ports.SBOMCreator = (*SyftAdapter)(nil)

// NewSyftAdapter initializes the SyftAdapter struct
func NewSyftAdapter(scanTimeout time.Duration, maxImageSize int64, maxSBOMSize int, scanEmbeddedSBOMs bool) *SyftAdapter {
func NewSyftAdapter(scanTimeout time.Duration, maxImageSize int64, maxSBOMSize int, scanEmbeddedSBOMs bool, proxyRegistryMap map[string]string) *SyftAdapter {
return &SyftAdapter{
maxImageSize: maxImageSize,
maxSBOMSize: maxSBOMSize,
proxyRegistryMap: proxyRegistryMap,
scanTimeout: scanTimeout,
scanEmbeddedSBOMs: scanEmbeddedSBOMs,
}
}

func rewriteImageRef(imageRef string, proxyMap map[string]string) string {
if len(proxyMap) == 0 {
return imageRef
}
keys := make([]string, 0, len(proxyMap))
for k := range proxyMap {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool { return len(keys[i]) > len(keys[j]) })
for _, original := range keys {
proxy := strings.TrimRight(proxyMap[original], "/")
if proxy == "" {
continue
}
if strings.HasPrefix(imageRef, original+"/") {
return proxy + imageRef[len(original):]
}
// treat docker.io and index.docker.io as the same registry
if original == "docker.io" && strings.HasPrefix(imageRef, "index.docker.io/") {
return proxy + imageRef[len("index.docker.io"):]
}
if original == "index.docker.io" && strings.HasPrefix(imageRef, "docker.io/") {
return proxy + imageRef[len("docker.io"):]
}
}
return imageRef
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

func NormalizeImageID(imageID, imageTag string) string {
// registry scanning doesn't provide imageID, so we use imageTag as a reference
if imageID == "" {
Expand Down Expand Up @@ -158,13 +189,15 @@ func (s *SyftAdapter) CreateSBOM(ctx context.Context, name, imageID, imageTag st
logger.L().Debug("downloading image", helpers.String("imageID", imageID))

ctxWithSize := context.WithValue(context.Background(), image.MaxImageSize, s.maxImageSize)
src, err := syft.GetSource(ctxWithSize, imageID, syft.DefaultGetSourceConfig().WithRegistryOptions(&registryOptions).WithSources("registry"))
pullRef := rewriteImageRef(imageID, s.proxyRegistryMap)
src, err := syft.GetSource(ctxWithSize, pullRef, syft.DefaultGetSourceConfig().WithRegistryOptions(&registryOptions).WithSources("registry"))

if err != nil && strings.Contains(err.Error(), "MANIFEST_UNKNOWN") {
logger.L().Debug("got MANIFEST_UNKNOWN, retrying with imageTag",
helpers.String("imageTag", imageTag),
helpers.String("imageID", imageID))
src, err = syft.GetSource(ctxWithSize, imageTag, syft.DefaultGetSourceConfig().WithRegistryOptions(&registryOptions).WithSources("registry"))
pullRef = rewriteImageRef(imageTag, s.proxyRegistryMap)
src, err = syft.GetSource(ctxWithSize, pullRef, syft.DefaultGetSourceConfig().WithRegistryOptions(&registryOptions).WithSources("registry"))
}

if err != nil && strings.Contains(err.Error(), "401 Unauthorized") {
Expand All @@ -183,7 +216,7 @@ func (s *SyftAdapter) CreateSBOM(ctx context.Context, name, imageID, imageTag st
logger.L().Debug("got 401, retrying without credentials",
helpers.String("imageID", imageID))
registryOptions.Credentials = nil
src, err = syft.GetSource(ctxWithSize, imageID, syft.DefaultGetSourceConfig().WithRegistryOptions(&registryOptions).WithSources("registry"))
src, err = syft.GetSource(ctxWithSize, rewriteImageRef(imageID, s.proxyRegistryMap), syft.DefaultGetSourceConfig().WithRegistryOptions(&registryOptions).WithSources("registry"))
}
}

Expand Down
93 changes: 90 additions & 3 deletions adapters/v1/syft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func Test_syftAdapter_CreateSBOM(t *testing.T) {
if tt.scanTimeout > 0 {
scanTimeout = tt.scanTimeout
}
s := NewSyftAdapter(scanTimeout, maxImageSize, maxSBOMSize, tt.scanEmbeddedSBOMs)
s := NewSyftAdapter(scanTimeout, maxImageSize, maxSBOMSize, tt.scanEmbeddedSBOMs, nil)
got, err := s.CreateSBOM(context.TODO(), "name", tt.imageID, tt.imageTag, tt.options)
if (err != nil) != tt.wantErr {
t.Errorf("CreateSBOM() error = %v, wantErr %v", err, tt.wantErr)
Expand Down Expand Up @@ -207,7 +207,7 @@ func TestIsGCPRegistry(t *testing.T) {
}

func Test_syftAdapter_Version(t *testing.T) {
s := NewSyftAdapter(5*time.Minute, 512*1024*1024, 20*1024*1024, false)
s := NewSyftAdapter(5*time.Minute, 512*1024*1024, 20*1024*1024, false, nil)
version := s.Version()
assert.NotEqual(t, version, "")
}
Expand All @@ -225,7 +225,7 @@ func Test_syftAdapter_transformations(t *testing.T) {
sbom := toSyftModel(d)

// Convert to domain.sbom
s := NewSyftAdapter(5*time.Minute, 512*1024*1024, 20*1024*1024, false)
s := NewSyftAdapter(5*time.Minute, 512*1024*1024, 20*1024*1024, false, nil)
domainSBOM, err := s.syftToDomain(*sbom)
require.NoError(t, err)

Expand Down Expand Up @@ -286,3 +286,90 @@ func TestNormalizeImageID(t *testing.T) {
})
}
}

func TestRewriteImageRef(t *testing.T) {
tests := []struct {
name string
imageRef string
proxyMap map[string]string
want string
}{
{
name: "nil map returns original",
imageRef: "docker.io/library/nginx:latest",
proxyMap: nil,
want: "docker.io/library/nginx:latest",
},
{
name: "empty map returns original",
imageRef: "docker.io/library/nginx:latest",
proxyMap: map[string]string{},
want: "docker.io/library/nginx:latest",
},
{
name: "empty proxy value is skipped, returns original",
imageRef: "docker.io/library/nginx:latest",
proxyMap: map[string]string{"docker.io": ""},
want: "docker.io/library/nginx:latest",
},
{
name: "trailing slash in proxy value is stripped",
imageRef: "docker.io/library/nginx:latest",
proxyMap: map[string]string{"docker.io": "mirror.io/"},
want: "mirror.io/library/nginx:latest",
},
{
name: "basic rewrite",
imageRef: "docker.io/library/alpine:3.18",
proxyMap: map[string]string{"docker.io": "mirror.example.com"},
want: "mirror.example.com/library/alpine:3.18",
},
{
name: "index.docker.io in ref matched by docker.io key",
imageRef: "index.docker.io/library/alpine:latest",
proxyMap: map[string]string{"docker.io": "mirror.example.com"},
want: "mirror.example.com/library/alpine:latest",
},
{
name: "docker.io in ref matched by index.docker.io key",
imageRef: "docker.io/library/alpine:latest",
proxyMap: map[string]string{"index.docker.io": "mirror.example.com"},
want: "mirror.example.com/library/alpine:latest",
},
{
name: "digest ref is rewritten",
imageRef: "docker.io/library/alpine@sha256:e2e16842c9b54d985bf1ef9242a313f36b856181f188de21313820e177002501",
proxyMap: map[string]string{"docker.io": "mirror.example.com"},
want: "mirror.example.com/library/alpine@sha256:e2e16842c9b54d985bf1ef9242a313f36b856181f188de21313820e177002501",
},
{
name: "non-matching prefix returns original",
imageRef: "quay.io/kubescape/kubevuln:latest",
proxyMap: map[string]string{"docker.io": "mirror.example.com"},
want: "quay.io/kubescape/kubevuln:latest",
},
{
name: "longest prefix wins over shorter prefix",
imageRef: "docker.io/library/nginx:latest",
proxyMap: map[string]string{
"docker.io": "generic-mirror.example.com",
"docker.io/library": "library-mirror.example.com",
},
want: "library-mirror.example.com/nginx:latest",
},
{
name: "multiple entries, correct one matches",
imageRef: "quay.io/kubescape/kubevuln:latest",
proxyMap: map[string]string{
"docker.io": "mirror.example.com",
"quay.io": "quay-mirror.example.com",
},
want: "quay-mirror.example.com/kubescape/kubevuln:latest",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, rewriteImageRef(tt.imageRef, tt.proxyMap))
})
}
}
6 changes: 3 additions & 3 deletions cmd/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ func main() {
if err != nil {
logger.L().Warning("failed to connect to SBOM scanner sidecar, falling back to in-process Syft",
helpers.Error(err))
sbomAdapter = v1.NewSyftAdapter(c.ScanTimeout, c.MaxImageSize, c.MaxSBOMSize, c.ScanEmbeddedSboms)
sbomAdapter = v1.NewSyftAdapter(c.ScanTimeout, c.MaxImageSize, c.MaxSBOMSize, c.ScanEmbeddedSboms, c.ProxyRegistryMap)
} else {
memoryLimit := os.Getenv("SCANNER_MEMORY_LIMIT")
sbomAdapter = v1.NewSidecarSBOMAdapter(scannerClient, c.ScanTimeout, c.MaxImageSize, c.MaxSBOMSize, c.ScanEmbeddedSboms, memoryLimit)
sbomAdapter = v1.NewSidecarSBOMAdapter(scannerClient, c.ScanTimeout, c.MaxImageSize, c.MaxSBOMSize, c.ScanEmbeddedSboms, memoryLimit, c.ProxyRegistryMap)
}
} else {
sbomAdapter = v1.NewSyftAdapter(c.ScanTimeout, c.MaxImageSize, c.MaxSBOMSize, c.ScanEmbeddedSboms)
sbomAdapter = v1.NewSyftAdapter(c.ScanTimeout, c.MaxImageSize, c.MaxSBOMSize, c.ScanEmbeddedSboms, c.ProxyRegistryMap)
}
cveAdapter := v1.NewGrypeAdapter(c.ListingURL, c.UseDefaultMatchers)

Expand Down
5 changes: 3 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ type Config struct {
NodeSbomGeneration bool `mapstructure:"nodeSbomGeneration"`
PartialRelevancy bool `mapstructure:"partialRelevancy"`
ScanConcurrency int `mapstructure:"scanConcurrency"`
ScanEmbeddedSboms bool `mapstructure:"scanEmbeddedSBOMs"`
ScanTimeout time.Duration `mapstructure:"scanTimeout"`
ProxyRegistryMap map[string]string `mapstructure:"proxyRegistryMap"`
ScanEmbeddedSboms bool `mapstructure:"scanEmbeddedSBOMs"`
ScanTimeout time.Duration `mapstructure:"scanTimeout"`
Storage bool `mapstructure:"storage"`
StoreFilteredSbom bool `mapstructure:"storeFilteredSbom"`
UseDefaultMatchers bool `mapstructure:"useDefaultMatchers"`
Expand Down
Loading