diff --git a/controllers/http.go b/controllers/http.go index 727df9a..84fbb25 100644 --- a/controllers/http.go +++ b/controllers/http.go @@ -51,7 +51,7 @@ func (h HTTPController) GenerateSBOM(c *gin.Context) { if err != nil { logger.L().Ctx(ctx).Error("validation error", helpers.Error(err), helpers.String("imageSlug", newScan.ImageSlug), - helpers.String("imageTag", newScan.ImageTag), + helpers.String("imageTagNormalized", newScan.ImageTagNormalized), helpers.String("imageHash", newScan.ImageHash)) _, _ = problem.Of(http.StatusInternalServerError).Append(details).WriteTo(c.Writer) return @@ -64,7 +64,7 @@ func (h HTTPController) GenerateSBOM(c *gin.Context) { if err != nil { logger.L().Ctx(ctx).Error("service error - GenerateSBOM", helpers.Error(err), helpers.String("imageSlug", newScan.ImageSlug), - helpers.String("imageTag", newScan.ImageTag), + helpers.String("imageTagNormalized", newScan.ImageTagNormalized), helpers.String("imageHash", newScan.ImageHash)) } }) @@ -105,7 +105,7 @@ func (h HTTPController) ScanCVE(c *gin.Context) { if err != nil { logger.L().Ctx(ctx).Error("validation error", helpers.Error(err), helpers.String("imageSlug", newScan.ImageSlug), - helpers.String("imageTag", newScan.ImageTag), + helpers.String("imageTagNormalized", newScan.ImageTagNormalized), helpers.String("imageHash", newScan.ImageHash)) _, _ = problem.Of(http.StatusInternalServerError).Append(details).WriteTo(c.Writer) return @@ -119,19 +119,20 @@ func (h HTTPController) ScanCVE(c *gin.Context) { logger.L().Ctx(ctx).Error("service error - ScanCVE", helpers.Error(err), helpers.String("wlid", newScan.Wlid), helpers.String("imageSlug", newScan.ImageSlug), - helpers.String("imageTag", newScan.ImageTag), + helpers.String("imageTagNormalized", newScan.ImageTagNormalized), helpers.String("imageHash", newScan.ImageHash)) } }) } func websocketScanCommandToScanCommand(c wssc.WebsocketScanCommand) domain.ScanCommand { + imageTagNormalized := tools.NormalizeReference(c.ImageTag) command := domain.ScanCommand{ CredentialsList: c.Credentialslist, ImageHash: c.ImageHash, Wlid: c.Wlid, ImageTag: c.ImageTag, - ImageTagNormalized: tools.NormalizeReference(c.ImageTag), + ImageTagNormalized: imageTagNormalized, JobID: c.JobID, ContainerName: c.ContainerName, LastAction: c.LastAction, @@ -139,7 +140,7 @@ func websocketScanCommandToScanCommand(c wssc.WebsocketScanCommand) domain.ScanC Args: c.Args, Session: sessionChainToSession(c.Session), } - if slug, err := names.ImageInfoToSlug(c.ImageTag, c.ImageHash); err == nil { + if slug, err := names.ImageInfoToSlug(imageTagNormalized, c.ImageHash); err == nil { command.ImageSlug = slug } if c.InstanceID != nil { @@ -173,7 +174,7 @@ func (h HTTPController) ScanRegistry(c *gin.Context) { if err != nil { logger.L().Ctx(ctx).Error("validation error", helpers.Error(err), helpers.String("imageSlug", newScan.ImageSlug), - helpers.String("imageTag", newScan.ImageTag), + helpers.String("imageTagNormalized", newScan.ImageTagNormalized), helpers.String("imageHash", newScan.ImageHash)) _, _ = problem.Of(http.StatusInternalServerError).Append(details).WriteTo(c.Writer) return @@ -186,7 +187,7 @@ func (h HTTPController) ScanRegistry(c *gin.Context) { if err != nil { logger.L().Ctx(ctx).Error("service error - ScanRegistry", helpers.Error(err), helpers.String("imageSlug", newScan.ImageSlug), - helpers.String("imageTag", newScan.ImageTag), + helpers.String("imageTagNormalized", newScan.ImageTagNormalized), helpers.String("imageHash", newScan.ImageHash)) } }) diff --git a/core/domain/scan.go b/core/domain/scan.go index 1f43ed4..a57626e 100644 --- a/core/domain/scan.go +++ b/core/domain/scan.go @@ -20,6 +20,7 @@ var ( ErrSBOMWithPartialArtifacts = errors.New("SBOM having partial artifacts") ErrInvalidScanID = errors.New("invalid scanID") ErrMissingImageInfo = errors.New("missing image information") + ErrMissingSBOM = errors.New("missing SBOM") ErrMissingScanID = errors.New("missing scanID") ErrMissingTimestamp = errors.New("missing timestamp") ErrCastingWorkload = errors.New("casting workload") @@ -37,14 +38,15 @@ type ScanCommand struct { ImageSlug string InstanceID string Wlid string - ImageTag string - JobID string - ContainerName string - ParentJobID string - ImageHash string - CredentialsList []registry.AuthConfig - Session Session - LastAction int + // deprecated + ImageTag string + JobID string + ContainerName string + ParentJobID string + ImageHash string + CredentialsList []registry.AuthConfig + Session Session + LastAction int } type Session struct { diff --git a/core/services/scan.go b/core/services/scan.go index 45f4094..a947897 100644 --- a/core/services/scan.go +++ b/core/services/scan.go @@ -71,6 +71,7 @@ func (s *ScanService) checkCreateSBOM(err error, key string) { } // GenerateSBOM implements the "Generate SBOM flow" +// FIXME check if we still use this method func (s *ScanService) GenerateSBOM(ctx context.Context) error { ctx, span := otel.Tracer("").Start(ctx, "ScanService.GenerateSBOM") defer span.End() @@ -97,7 +98,7 @@ func (s *ScanService) GenerateSBOM(ctx context.Context) error { // if SBOM is not available, create it if sbom.Content == nil { // create SBOM - sbom, err = s.sbomCreator.CreateSBOM(ctx, workload.ImageSlug, workload.ImageHash, workload.ImageTag, optionsFromWorkload(workload)) + sbom, err = s.sbomCreator.CreateSBOM(ctx, workload.ImageSlug, workload.ImageHash, workload.ImageTagNormalized, optionsFromWorkload(workload)) s.checkCreateSBOM(err, workload.ImageHash) if err != nil { return err @@ -167,20 +168,9 @@ func (s *ScanService) ScanCVE(ctx context.Context) error { // if SBOM is not available, create it if sbom.Content == nil { - // create SBOM - sbom, err = s.sbomCreator.CreateSBOM(ctx, workload.ImageSlug, workload.ImageHash, workload.ImageTag, optionsFromWorkload(workload)) - s.checkCreateSBOM(err, workload.ImageHash) - if err != nil { - return fmt.Errorf("error creating SBOM: %w", err) - } - // store SBOM - if s.storage { - err = s.sbomRepository.StoreSBOM(ctx, sbom) - if err != nil { - logger.L().Ctx(ctx).Warning("error storing SBOM", helpers.Error(err), - helpers.String("imageSlug", workload.ImageSlug)) - } - } + logger.L().Ctx(ctx).Warning("missing SBOM", + helpers.String("imageSlug", workload.ImageSlug)) + return domain.ErrMissingSBOM } // do not process timed out SBOM @@ -325,8 +315,8 @@ func (s *ScanService) ScanRegistry(ctx context.Context) error { } // create SBOM - sbom, err := s.sbomCreator.CreateSBOM(ctx, workload.ImageSlug, workload.ImageHash, workload.ImageTag, optionsFromWorkload(workload)) - s.checkCreateSBOM(err, workload.ImageTag) + sbom, err := s.sbomCreator.CreateSBOM(ctx, workload.ImageSlug, workload.ImageHash, workload.ImageTagNormalized, optionsFromWorkload(workload)) + s.checkCreateSBOM(err, workload.ImageTagNormalized) if err != nil { repErr := s.platform.ReportError(ctx, err) if repErr != nil { @@ -400,8 +390,8 @@ func generateScanID(workload domain.ScanCommand, scannerVersion string) string { return fmt.Sprintf("%s-%s", workload.InstanceID, scannerVersion) } - if workload.ImageTag != "" && workload.ImageHash != "" { - sum := sha256.Sum256([]byte(workload.ImageTag + workload.ImageHash + scannerVersion)) + if workload.ImageTagNormalized != "" && workload.ImageHash != "" { + sum := sha256.Sum256([]byte(workload.ImageTagNormalized + workload.ImageHash + scannerVersion)) if scanID := fmt.Sprintf("%x", sum); armotypes.ValidateContainerScanID(scanID) { return scanID } @@ -421,7 +411,7 @@ func optionsFromWorkload(workload domain.ScanCommand) domain.RegistryOptions { } logger.L().Debug("created registryOptions from workload", - helpers.String("imageTag", workload.ImageTag), + helpers.String("imageTagNormalized", workload.ImageTagNormalized), helpers.String("credentials", credentialsLog(options.Credentials))) return options } @@ -533,7 +523,7 @@ func (s *ScanService) ValidateScanRegistry(ctx context.Context, workload domain. ctx = enrichContext(ctx, workload, s.sbomCreator.Version()) // validate inputs - if workload.ImageTag == "" || workload.ImageSlug == "" { + if workload.ImageTagNormalized == "" || workload.ImageSlug == "" { return ctx, domain.ErrMissingImageInfo } // add imageSlug to parent span @@ -543,7 +533,7 @@ func (s *ScanService) ValidateScanRegistry(ctx context.Context, workload domain. ctx = trace.ContextWithSpan(ctx, parentSpan) } // check if previous image pull resulted in TOOMANYREQUESTS error - if _, ok := s.tooManyRequests.Get(workload.ImageTag); ok { + if _, ok := s.tooManyRequests.Get(workload.ImageTagNormalized); ok { return ctx, domain.ErrTooManyRequests } return ctx, nil diff --git a/core/services/scan_test.go b/core/services/scan_test.go index e949bf0..8d7b4fd 100644 --- a/core/services/scan_test.go +++ b/core/services/scan_test.go @@ -128,19 +128,15 @@ func TestScanService_GenerateSBOM(t *testing.T) { func TestScanService_ScanCVE(t *testing.T) { tests := []struct { - createSBOMError bool name string instanceID string emptyWlid bool cveManifest bool - sbom bool storage bool getErrorCVE bool getErrorSBOM bool storeErrorCVE bool - storeErrorSBOM bool timeout bool - toomanyrequests bool workload bool wantCvep bool wantEmptyReport bool @@ -154,19 +150,7 @@ func TestScanService_ScanCVE(t *testing.T) { { name: "no storage", workload: true, - wantErr: false, - }, - { - name: "create SBOM error", - createSBOMError: true, - workload: true, - wantErr: true, - }, - { - name: "create SBOM too many requests", - toomanyrequests: true, - workload: true, - wantErr: true, + wantErr: true, }, { name: "empty wlid", @@ -185,7 +169,6 @@ func TestScanService_ScanCVE(t *testing.T) { name: "second scan", storage: true, cveManifest: true, - sbom: true, workload: true, wantEmptyReport: false, wantErr: false, @@ -195,14 +178,7 @@ func TestScanService_ScanCVE(t *testing.T) { getErrorSBOM: true, storage: true, workload: true, - wantErr: false, - }, - { - name: "store SBOM failed", - storeErrorSBOM: true, - storage: true, - workload: true, - wantErr: false, + wantErr: true, }, { name: "get CVE failed", @@ -220,7 +196,6 @@ func TestScanService_ScanCVE(t *testing.T) { }, { name: "timeout SBOM", - sbom: true, storage: true, timeout: true, workload: true, @@ -228,7 +203,6 @@ func TestScanService_ScanCVE(t *testing.T) { }, { name: "with SBOMp", - sbom: true, instanceID: "ee9bdd0adec9ce004572faf3492f583aa82042a8b3a9d5c7d9179dc03c531eef", storage: true, workload: true, @@ -243,9 +217,9 @@ func TestScanService_ScanCVE(t *testing.T) { if tt.emptyWlid { wlid = "" } - sbomAdapter := adapters.NewMockSBOMAdapter(tt.createSBOMError, tt.timeout, tt.toomanyrequests) + sbomAdapter := adapters.NewMockSBOMAdapter(false, tt.timeout, false) cveAdapter := adapters.NewMockCVEAdapter() - storageSBOM := repositories.NewMemoryStorage(tt.getErrorSBOM, tt.storeErrorSBOM) + storageSBOM := repositories.NewMemoryStorage(tt.getErrorSBOM, false) storageCVE := repositories.NewMemoryStorage(tt.getErrorCVE, tt.storeErrorCVE) s := NewScanService(sbomAdapter, storageSBOM, @@ -270,15 +244,13 @@ func TestScanService_ScanCVE(t *testing.T) { ctx, err = s.ValidateScanCVE(ctx, workload) require.NoError(t, err) } - if tt.sbom { - sbom, err := sbomAdapter.CreateSBOM(ctx, "imageSlug", imageHash, "", domain.RegistryOptions{}) + sbom, err := sbomAdapter.CreateSBOM(ctx, "imageSlug", imageHash, "", domain.RegistryOptions{}) + require.NoError(t, err) + _ = storageSBOM.StoreSBOM(ctx, sbom) + if tt.cveManifest { + cve, err := cveAdapter.ScanSBOM(ctx, sbom) require.NoError(t, err) - _ = storageSBOM.StoreSBOM(ctx, sbom) - if tt.cveManifest { - cve, err := cveAdapter.ScanSBOM(ctx, sbom) - require.NoError(t, err) - _ = storageCVE.StoreCVE(ctx, cve, false) - } + _ = storageCVE.StoreCVE(ctx, cve, false) } var sbomp domain.SBOM if tt.instanceID != "" { @@ -291,10 +263,6 @@ func TestScanService_ScanCVE(t *testing.T) { if err := s.ScanCVE(ctx); (err != nil) != tt.wantErr { t.Errorf("ScanCVE() error = %v, wantErr %v", err, tt.wantErr) } - if tt.toomanyrequests { - _, err := s.ValidateScanCVE(ctx, workload) - assert.Equal(t, domain.ErrTooManyRequests, err) - } if tt.wantCvep { cvep, err := storageCVE.GetCVE(ctx, sbomp.Name, sbomAdapter.Version(), cveAdapter.Version(ctx), cveAdapter.DBVersion(ctx)) require.NoError(t, err) @@ -492,8 +460,8 @@ func TestScanService_ScanRegistry(t *testing.T) { false, false) ctx := context.TODO() workload := domain.ScanCommand{ - ImageSlug: "imageSlug", - ImageTag: "k8s.gcr.io/kube-proxy:v1.24.3", + ImageSlug: "imageSlug", + ImageTagNormalized: "k8s.gcr.io/kube-proxy:v1.24.3", } workload.CredentialsList = []registry.AuthConfig{ { @@ -537,8 +505,8 @@ func TestScanService_ValidateScanRegistry(t *testing.T) { { name: "with imageID", workload: domain.ScanCommand{ - ImageSlug: "imageSlug", - ImageTag: "k8s.gcr.io/kube-proxy:v1.24.3", + ImageSlug: "imageSlug", + ImageTagNormalized: "k8s.gcr.io/kube-proxy:v1.24.3", }, wantErr: false, }, @@ -574,8 +542,8 @@ func Test_generateScanID(t *testing.T) { name: "generate scanID with imageHash", args: args{ workload: domain.ScanCommand{ - ImageTag: "k8s.gcr.io/kube-proxy:v1.24.3", - ImageHash: "sha256:6f9c1c5b5b1b2b3b4b5b6b7b8b9b0b1b2b3b4b5b6b7b8b9b0b1b2b3b4b5b6b7b", + ImageTagNormalized: "k8s.gcr.io/kube-proxy:v1.24.3", + ImageHash: "sha256:6f9c1c5b5b1b2b3b4b5b6b7b8b9b0b1b2b3b4b5b6b7b8b9b0b1b2b3b4b5b6b7b", }, }, want: "2d0ee020566e8ff66542c5cd9e324111731c6a49d237fea3bd880448dac1a37f", diff --git a/internal/tools/tools.go b/internal/tools/tools.go index e4d6f37..50efce6 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -106,5 +106,5 @@ func NormalizeReference(ref string) string { if err != nil { return ref } - return n.String() + return reference.TagNameOnly(n).String() } diff --git a/internal/tools/tools_test.go b/internal/tools/tools_test.go index 2a9757a..863326a 100644 --- a/internal/tools/tools_test.go +++ b/internal/tools/tools_test.go @@ -66,6 +66,13 @@ func TestNormalizeReference(t *testing.T) { args args want string }{ + { + name: "image tag only - assuming latest", + args: args{ + ref: "nginx", + }, + want: "docker.io/library/nginx:latest", + }, { name: "image tag", args: args{ diff --git a/repositories/apiserver.go b/repositories/apiserver.go index 38f24c0..08797b8 100644 --- a/repositories/apiserver.go +++ b/repositories/apiserver.go @@ -187,7 +187,7 @@ func (a *APIServerStore) StoreCVE(ctx context.Context, cve domain.CVEManifest, w retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { // retrieve the latest version before attempting update // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver - result, getErr := a.StorageClient.VulnerabilityManifests(a.Namespace).Get(context.Background(), cve.Name, metav1.GetOptions{}) + result, getErr := a.StorageClient.VulnerabilityManifests(a.Namespace).Get(context.Background(), cve.Name, metav1.GetOptions{ResourceVersion: "metadata"}) if getErr != nil { return getErr } @@ -389,6 +389,10 @@ func (a *APIServerStore) StoreCVESummary(ctx context.Context, cve domain.CVEMani if err != nil { return err } + if workloadNamespace == "" { + // fallback to default namespace + workloadNamespace = a.Namespace + } manifest := v1beta1.VulnerabilityManifestSummary{ ObjectMeta: metav1.ObjectMeta{ @@ -407,13 +411,10 @@ func (a *APIServerStore) StoreCVESummary(ctx context.Context, cve domain.CVEMani retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { // retrieve the latest version before attempting update // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver - result, getErr := a.StorageClient.VulnerabilityManifestSummaries(workloadNamespace).Get(context.Background(), manifest.Name, metav1.GetOptions{}) + result, getErr := a.StorageClient.VulnerabilityManifestSummaries(workloadNamespace).Get(context.Background(), manifest.Name, metav1.GetOptions{ResourceVersion: "metadata"}) if getErr != nil { return getErr } - result.ResourceVersion = "" - result.UID = "" - // update the vulnerability manifest result.Annotations = manifest.Annotations result.Labels = manifest.Labels