diff --git a/errors/errors.go b/errors/errors.go index 4b9a9bfa..74b523c8 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -34,4 +34,5 @@ var ( ErrConfigNotFound = errors.New("cli: config with the given name does not exist") ErrNoURLProvided = errors.New("cli: no URL provided in argument or via config. see 'zot config -h'") ErrIllegalConfigKey = errors.New("cli: given config key is not allowed") + ErrScanNotSupported = errors.New("search: scanning of image media type not supported") ) diff --git a/go.mod b/go.mod index 1957bb69..b3ee67ff 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/go-chi/chi v4.0.2+incompatible // indirect github.com/go-ldap/ldap/v3 v3.1.3 github.com/gofrs/uuid v3.2.0+incompatible + github.com/google/go-containerregistry v0.0.0-20200331213917-3d03ed9b1ca2 github.com/gorilla/handlers v1.4.2 github.com/gorilla/mux v1.7.4 github.com/json-iterator/go v1.1.9 diff --git a/pkg/extensions/search/BUILD.bazel b/pkg/extensions/search/BUILD.bazel index b3df86e3..d30eab15 100644 --- a/pkg/extensions/search/BUILD.bazel +++ b/pkg/extensions/search/BUILD.bazel @@ -10,6 +10,7 @@ go_library( importpath = "github.com/anuvu/zot/pkg/extensions/search", visibility = ["//visibility:public"], deps = [ + "//errors:go_default_library", "//pkg/extensions/search/cve:go_default_library", "//pkg/log:go_default_library", "//pkg/storage:go_default_library", diff --git a/pkg/extensions/search/cve/BUILD.bazel b/pkg/extensions/search/cve/BUILD.bazel index 813d46b6..22b108c3 100644 --- a/pkg/extensions/search/cve/BUILD.bazel +++ b/pkg/extensions/search/cve/BUILD.bazel @@ -9,10 +9,15 @@ go_library( importpath = "github.com/anuvu/zot/pkg/extensions/search/cve", visibility = ["//visibility:public"], deps = [ + "//errors:go_default_library", "//pkg/log:go_default_library", "@com_github_aquasecurity_trivy//integration:go_default_library", "@com_github_aquasecurity_trivy//integration/config:go_default_library", "@com_github_aquasecurity_trivy//pkg/report:go_default_library", + "@com_github_google_go_containerregistry//pkg/v1:go_default_library", + "@com_github_google_go_containerregistry//pkg/v1/types:go_default_library", + "@com_github_opencontainers_go_digest//:go_default_library", + "@com_github_opencontainers_image_spec//specs-go/v1:go_default_library", ], ) diff --git a/pkg/extensions/search/cve/cve.go b/pkg/extensions/search/cve/cve.go index fdf3841a..95618be7 100644 --- a/pkg/extensions/search/cve/cve.go +++ b/pkg/extensions/search/cve/cve.go @@ -1,10 +1,21 @@ package cveinfo import ( + "encoding/json" + "io/ioutil" + "os" + "path" + "strings" + + "github.com/anuvu/zot/errors" "github.com/anuvu/zot/pkg/log" integration "github.com/aquasecurity/trivy/integration" config "github.com/aquasecurity/trivy/integration/config" "github.com/aquasecurity/trivy/pkg/report" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/types" + godigest "github.com/opencontainers/go-digest" + ispec "github.com/opencontainers/image-spec/specs-go/v1" ) // UpdateCVEDb ... @@ -31,3 +42,91 @@ func NewTrivyConfig(dir string) (*config.Config, error) { func ScanImage(config *config.Config) (report.Results, error) { return integration.ScanTrivyImage(config.TrivyConfig) } + +func (cveinfo CveInfo) IsValidImageFormat(imagePath string) (bool, error) { + imageDir := getImageDir(imagePath) + + if !dirExists(imageDir) { + cveinfo.Log.Error().Msg("Image Directory not exists") + + return false, errors.ErrRepoNotFound + } + + buf, err := ioutil.ReadFile(path.Join(imageDir, "index.json")) + + if err != nil { + if os.IsNotExist(err) { + cveinfo.Log.Error().Err(err).Msg("Index.json does not exist") + + return false, errors.ErrRepoNotFound + } + + cveinfo.Log.Error().Err(err).Msg("Unable to open index.json") + + return false, errors.ErrRepoNotFound + } + + var index ispec.Index + + var blobManifest v1.Manifest + + var digest godigest.Digest + + if err := json.Unmarshal(buf, &index); err != nil { + cveinfo.Log.Error().Err(err).Msg("Unable to marshal index.json file") + + return false, err + } + + for _, m := range index.Manifests { + digest = m.Digest + + blobBuf, err := ioutil.ReadFile(path.Join(imageDir, "blobs", digest.Algorithm().String(), digest.Encoded())) + if err != nil { + cveinfo.Log.Error().Err(err).Msg("Failed to read manifest file") + + return false, err + } + + if err := json.Unmarshal(blobBuf, &blobManifest); err != nil { + cveinfo.Log.Error().Err(err).Msg("Invalid manifest json") + + return false, err + } + + imageLayers := blobManifest.Layers + + for _, imageLayer := range imageLayers { + switch imageLayer.MediaType { + case types.OCILayer, types.DockerLayer: + return true, nil + + default: + cveinfo.Log.Debug().Msg("Image media type not supported for scanning") + return false, nil + } + } + } + + return false, nil +} + +func dirExists(d string) bool { + fi, err := os.Stat(d) + if err != nil && os.IsNotExist(err) { + return false + } + + return fi.IsDir() +} + +func getImageDir(imageName string) string { + var imageDir string + if strings.Contains(imageName, ":") { + imageDir = strings.Split(imageName, ":")[0] + } else { + imageDir = imageName + } + + return imageDir +} diff --git a/pkg/extensions/search/resolver.go b/pkg/extensions/search/resolver.go index 85b22833..80d4322f 100644 --- a/pkg/extensions/search/resolver.go +++ b/pkg/extensions/search/resolver.go @@ -7,6 +7,7 @@ import ( "path" "strings" + "github.com/anuvu/zot/errors" "github.com/anuvu/zot/pkg/log" cveinfo "github.com/anuvu/zot/pkg/extensions/search/cve" @@ -54,6 +55,19 @@ func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*CVE r.cveInfo.Log.Info().Str("Scanning Image", image).Msg("") + isValidImage, err := r.cveInfo.IsValidImageFormat(r.cveInfo.CveTrivyConfig.TrivyConfig.Input) + if !isValidImage { + r.cveInfo.Log.Debug().Msg("Image media type not supported for scanning") + + return &CVEResultForImage{}, errors.ErrScanNotSupported + } + + if err != nil { + r.cveInfo.Log.Error().Err(err).Msg("Error scanning image repository") + + return &CVEResultForImage{}, err + } + cveResults, err := cveinfo.ScanImage(r.cveInfo.CveTrivyConfig) if err != nil { r.cveInfo.Log.Error().Err(err).Msg("Error scanning image repository") @@ -143,6 +157,19 @@ func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*ImgR for _, repo := range repoList { r.cveInfo.Log.Info().Str("Extracting list of tags available in image", repo).Msg("") + isValidImage, err := r.cveInfo.IsValidImageFormat(path.Join(r.dir, repo)) + if !isValidImage { + r.cveInfo.Log.Debug().Str("Image media type not supported for scanning", repo) + + continue + } + + if err != nil { + r.cveInfo.Log.Error().Err(err).Str("Error reading image media type", repo) + + return cveResult, err + } + if err != nil { r.cveInfo.Log.Error().Err(err).Str("Error reading image media type", repo)