diff --git a/src/controller/artifact/controller.go b/src/controller/artifact/controller.go index 6db99fccb08b..042ec1a77c65 100644 --- a/src/controller/artifact/controller.go +++ b/src/controller/artifact/controller.go @@ -56,6 +56,8 @@ import ( model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag" ) +const inTotoContentType = "application/vnd.in-toto+json" + var ( // Ctl is a global artifact controller instance Ctl = NewController() @@ -113,6 +115,8 @@ type Controller interface { RemoveLabel(ctx context.Context, artifactID int64, labelID int64) (err error) // Walk walks the artifact tree rooted at root, calling walkFn for each artifact in the tree, including root. Walk(ctx context.Context, root *Artifact, walkFn func(*Artifact) error, option *Option) error + // HasUnscannableLayer check artifact with digest if has unscannable layer + HasUnscannableLayer(ctx context.Context, dgst string) (bool, error) } // NewController creates an instance of the default artifact controller @@ -759,3 +763,20 @@ func (c *controller) populateAccessories(ctx context.Context, art *Artifact) { } art.Accessories = accs } + +// IsInToto check if it is a in-toto sbom, if it contains any blob with a content_type is application/vnd.in-toto+json, then consider as in-toto sbom +func (c *controller) HasUnscannableLayer(ctx context.Context, dgst string) (bool, error) { + if len(dgst) == 0 { + return false, nil + } + blobs, err := c.blobMgr.GetByArt(ctx, dgst) + if err != nil { + return false, err + } + for _, b := range blobs { + if b.ContentType == inTotoContentType { + return true, nil + } + } + return false, nil +} diff --git a/src/controller/artifact/controller_test.go b/src/controller/artifact/controller_test.go index ce58b5d280bd..cfc0223e410d 100644 --- a/src/controller/artifact/controller_test.go +++ b/src/controller/artifact/controller_test.go @@ -35,6 +35,7 @@ import ( accessorymodel "github.com/goharbor/harbor/src/pkg/accessory/model" basemodel "github.com/goharbor/harbor/src/pkg/accessory/model/base" "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/blob/models" "github.com/goharbor/harbor/src/pkg/label/model" repomodel "github.com/goharbor/harbor/src/pkg/repository/model" model_tag "github.com/goharbor/harbor/src/pkg/tag/model/tag" @@ -678,6 +679,29 @@ func (c *controllerTestSuite) TestWalk() { } } +func (c *controllerTestSuite) TestIsIntoto() { + blobs := []*models.Blob{ + {Digest: "sha256:00000", ContentType: "application/vnd.oci.image.manifest.v1+json"}, + {Digest: "sha256:22222", ContentType: "application/vnd.oci.image.config.v1+json"}, + {Digest: "sha256:11111", ContentType: inTotoContentType}, + } + c.blobMgr.On("GetByArt", mock.Anything, mock.Anything).Return(blobs, nil).Once() + isIntoto, err := c.ctl.HasUnscannableLayer(context.Background(), "sha256: 77777") + c.Nil(err) + c.True(isIntoto) + + blobs2 := []*models.Blob{ + {Digest: "sha256:00000", ContentType: "application/vnd.oci.image.manifest.v1+json"}, + {Digest: "sha256:22222", ContentType: "application/vnd.oci.image.config.v1+json"}, + {Digest: "sha256:11111", ContentType: "application/vnd.oci.image.layer.v1.tar+gzip"}, + } + + c.blobMgr.On("GetByArt", mock.Anything, mock.Anything).Return(blobs2, nil).Once() + isIntoto2, err := c.ctl.HasUnscannableLayer(context.Background(), "sha256: 8888") + c.Nil(err) + c.False(isIntoto2) +} + func TestControllerTestSuite(t *testing.T) { suite.Run(t, &controllerTestSuite{}) } diff --git a/src/controller/scan/base_controller.go b/src/controller/scan/base_controller.go index c70221a0fa59..5bc62d0c6617 100644 --- a/src/controller/scan/base_controller.go +++ b/src/controller/scan/base_controller.go @@ -52,7 +52,6 @@ import ( sbomModel "github.com/goharbor/harbor/src/pkg/scan/sbom/model" "github.com/goharbor/harbor/src/pkg/scan/vuln" "github.com/goharbor/harbor/src/pkg/task" - "github.com/goharbor/harbor/src/testing/controller/artifact" ) var ( @@ -111,8 +110,6 @@ type basicController struct { rc robot.Controller // Tag controller tagCtl tag.Controller - // Artifact controller - artCtl artifact.Controller // UUID generator uuid uuidGenerator // Configuration getter func @@ -199,6 +196,17 @@ func (bc *basicController) collectScanningArtifacts(ctx context.Context, r *scan return nil } + // because there are lots of in-toto sbom artifacts in dockerhub and replicated to Harbor, they are considered as image type + // when scanning these type of sbom artifact, they cause many pannic in the harbor-core log + // to avoid pannic, skip scan the in-toto sbom artifact sbom artifact + unscannable, err := bc.ar.HasUnscannableLayer(ctx, a.Digest) + if err != nil { + return err + } + if unscannable { + return nil + } + supported := hasCapability(r, a) if !supported && a.IsImageIndex() { diff --git a/src/controller/scan/checker.go b/src/controller/scan/checker.go index a4d0e3ba3470..7713aa3c3194 100644 --- a/src/controller/scan/checker.go +++ b/src/controller/scan/checker.go @@ -86,6 +86,17 @@ func (c *checker) IsScannable(ctx context.Context, art *artifact.Artifact) (bool return artifact.ErrBreak } + // because there are lots of in-toto sbom artifacts in dockerhub and replicated to Harbor, they are considered as image type + // when scanning these type of sbom artifact, they cause many pannic in the harbor-core log + // to avoid pannic, skip scan the in-toto sbom artifact sbom artifact + unscannable, err := c.artifactCtl.HasUnscannableLayer(ctx, a.Digest) + if err != nil { + return err + } + if unscannable { + return nil + } + return nil } diff --git a/src/controller/scan/checker_test.go b/src/controller/scan/checker_test.go index e96cb1e511fd..716e574be06e 100644 --- a/src/controller/scan/checker_test.go +++ b/src/controller/scan/checker_test.go @@ -81,7 +81,7 @@ func (suite *CheckerTestSuite) TestIsScannable() { walkFn := args.Get(2).(func(*artifact.Artifact) error) walkFn(art) }) - + mock.OnAnything(c.artifactCtl, "HasUnscannableLayer").Return(false, nil).Once() isScannable, err := c.IsScannable(context.TODO(), art) suite.Nil(err) suite.False(isScannable) @@ -97,6 +97,7 @@ func (suite *CheckerTestSuite) TestIsScannable() { walkFn := args.Get(2).(func(*artifact.Artifact) error) walkFn(art) }) + mock.OnAnything(c.artifactCtl, "HasUnscannableLayer").Return(false, nil).Once() isScannable, err := c.IsScannable(context.TODO(), art) suite.Nil(err) diff --git a/src/testing/controller/artifact/controller.go b/src/testing/controller/artifact/controller.go index 7af5d556f176..8fe07f29789a 100644 --- a/src/testing/controller/artifact/controller.go +++ b/src/testing/controller/artifact/controller.go @@ -238,6 +238,34 @@ func (_m *Controller) GetByReference(ctx context.Context, repository string, ref return r0, r1 } +// HasUnscannableLayer provides a mock function with given fields: ctx, dgst +func (_m *Controller) HasUnscannableLayer(ctx context.Context, dgst string) (bool, error) { + ret := _m.Called(ctx, dgst) + + if len(ret) == 0 { + panic("no return value specified for HasUnscannableLayer") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { + return rf(ctx, dgst) + } + if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { + r0 = rf(ctx, dgst) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, dgst) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // List provides a mock function with given fields: ctx, query, option func (_m *Controller) List(ctx context.Context, query *q.Query, option *artifact.Option) ([]*artifact.Artifact, error) { ret := _m.Called(ctx, query, option)