mirror of
https://github.com/project-zot/zot.git
synced 2024-12-23 22:27:35 -05:00
43160dcc43
We encountered some problems with using the existing folder structure, but it looks like running the tooling with the latest versions works after we regenerated the project using 'gql init' and refactoring to separate the login previously in resolvers.go. - the autogenerated code is now under the gql_generated folder - the file resolvers.go now contains only the code which is not rewritten by the gqlgen framework - the file schema.resolvers.go is rewritten when gqlgen runs, and we'll only keep there the actual resolvers matching query names Changes we observed to schema.resolvers.go when gqlgen runs include reordering methods, and renaming function parameters to match the names used in schema.graphql - we now have a gqlgen.yaml config file which governs the behavior of gqlgen (can be tweaked to restructure the folder structure of the generated code in the future) Looks like the new graphql server has better validation 1 Returns 422 instead of 200 for missing query string - had to update tests 2 Correctly uncovered an error in a test for a bad `%` in query string. As as result of 2, a `masked` bug was found in the way we check if images are signed with Notary, the signatures were reasched for with the media type of the image manifest itself instead of the media type for notation. Fixed this bug, and improved error messages. This bug would have also been reproducible with main branch if the bad `%` in the test would have fixed. Updated the linter to ignore some issues with the code which is always rewritten when running: `go run github.com/99designs/gqlgen@v0.17.13 generate` Add a workflow to test gqlgen works and has no uncommitted changes Signed-off-by: Andrei Aaron <andaaron@cisco.com>
322 lines
8 KiB
Go
322 lines
8 KiB
Go
// Package common ...
|
|
package common
|
|
|
|
import (
|
|
"encoding/json"
|
|
goerrors "errors"
|
|
"fmt"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
|
notreg "github.com/notaryproject/notation/pkg/registry"
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"zotregistry.io/zot/errors"
|
|
"zotregistry.io/zot/pkg/log"
|
|
"zotregistry.io/zot/pkg/storage"
|
|
)
|
|
|
|
// OciLayoutInfo ...
|
|
type OciLayoutUtils struct {
|
|
Log log.Logger
|
|
StoreController storage.StoreController
|
|
}
|
|
|
|
type RepoInfo struct {
|
|
Manifests []Manifest `json:"manifests"`
|
|
}
|
|
|
|
type Manifest struct {
|
|
Tag string `json:"tag"`
|
|
Digest string `json:"digest"`
|
|
IsSigned bool `json:"isSigned"`
|
|
Layers []Layer `json:"layers"`
|
|
}
|
|
|
|
type Layer struct {
|
|
Size string `json:"size"`
|
|
Digest string `json:"digest"`
|
|
}
|
|
|
|
// NewOciLayoutUtils initializes a new OciLayoutUtils object.
|
|
func NewOciLayoutUtils(storeController storage.StoreController, log log.Logger) *OciLayoutUtils {
|
|
return &OciLayoutUtils{Log: log, StoreController: storeController}
|
|
}
|
|
|
|
// Below method will return image path including root dir, root dir is determined by splitting.
|
|
func (olu OciLayoutUtils) GetImageManifests(image string) ([]ispec.Descriptor, error) {
|
|
imageStore := olu.StoreController.GetImageStore(image)
|
|
|
|
buf, err := imageStore.GetIndexContent(image)
|
|
if err != nil {
|
|
if goerrors.Is(errors.ErrRepoNotFound, err) {
|
|
olu.Log.Error().Err(err).Msg("index.json doesn't exist")
|
|
|
|
return nil, errors.ErrRepoNotFound
|
|
}
|
|
|
|
olu.Log.Error().Err(err).Msg("unable to open index.json")
|
|
|
|
return nil, errors.ErrRepoNotFound
|
|
}
|
|
|
|
var index ispec.Index
|
|
|
|
if err := json.Unmarshal(buf, &index); err != nil {
|
|
olu.Log.Error().Err(err).Str("dir", path.Join(imageStore.RootDir(), image)).Msg("invalid JSON")
|
|
|
|
return nil, errors.ErrRepoNotFound
|
|
}
|
|
|
|
return index.Manifests, nil
|
|
}
|
|
|
|
//nolint: interfacer
|
|
func (olu OciLayoutUtils) GetImageBlobManifest(imageDir string, digest godigest.Digest) (v1.Manifest, error) {
|
|
var blobIndex v1.Manifest
|
|
|
|
imageStore := olu.StoreController.GetImageStore(imageDir)
|
|
|
|
blobBuf, err := imageStore.GetBlobContent(imageDir, digest.String())
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to open image metadata file")
|
|
|
|
return blobIndex, err
|
|
}
|
|
|
|
if err := json.Unmarshal(blobBuf, &blobIndex); err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to marshal blob index")
|
|
|
|
return blobIndex, err
|
|
}
|
|
|
|
return blobIndex, nil
|
|
}
|
|
|
|
//nolint: interfacer
|
|
func (olu OciLayoutUtils) GetImageInfo(imageDir string, hash v1.Hash) (ispec.Image, error) {
|
|
var imageInfo ispec.Image
|
|
|
|
imageStore := olu.StoreController.GetImageStore(imageDir)
|
|
|
|
blobBuf, err := imageStore.GetBlobContent(imageDir, hash.String())
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to open image layers file")
|
|
|
|
return imageInfo, err
|
|
}
|
|
|
|
if err := json.Unmarshal(blobBuf, &imageInfo); err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to marshal blob index")
|
|
|
|
return imageInfo, err
|
|
}
|
|
|
|
return imageInfo, err
|
|
}
|
|
|
|
func (olu OciLayoutUtils) IsValidImageFormat(image string) (bool, error) {
|
|
imageDir, inputTag := GetImageDirAndTag(image)
|
|
|
|
manifests, err := olu.GetImageManifests(imageDir)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, manifest := range manifests {
|
|
tag, ok := manifest.Annotations[ispec.AnnotationRefName]
|
|
|
|
if ok && inputTag != "" && tag != inputTag {
|
|
continue
|
|
}
|
|
|
|
blobManifest, err := olu.GetImageBlobManifest(imageDir, manifest.Digest)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
imageLayers := blobManifest.Layers
|
|
|
|
for _, imageLayer := range imageLayers {
|
|
switch imageLayer.MediaType {
|
|
case types.OCILayer, types.DockerLayer:
|
|
return true, nil
|
|
|
|
default:
|
|
olu.Log.Debug().Msg("image media type not supported for scanning")
|
|
|
|
return false, errors.ErrScanNotSupported
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// GetImageTagsWithTimestamp returns a list of image tags with timestamp available in the specified repository.
|
|
func (olu OciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo, error) {
|
|
tagsInfo := make([]TagInfo, 0)
|
|
|
|
manifests, err := olu.GetImageManifests(repo)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to read image manifests")
|
|
|
|
return tagsInfo, err
|
|
}
|
|
|
|
for _, manifest := range manifests {
|
|
digest := manifest.Digest
|
|
|
|
val, ok := manifest.Annotations[ispec.AnnotationRefName]
|
|
if ok {
|
|
imageBlobManifest, err := olu.GetImageBlobManifest(repo, digest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to read image blob manifest")
|
|
|
|
return tagsInfo, err
|
|
}
|
|
|
|
imageInfo, err := olu.GetImageInfo(repo, imageBlobManifest.Config.Digest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to read image info")
|
|
|
|
return tagsInfo, err
|
|
}
|
|
|
|
var timeStamp time.Time
|
|
|
|
if len(imageInfo.History) != 0 {
|
|
timeStamp = *imageInfo.History[0].Created
|
|
} else {
|
|
timeStamp = time.Time{}
|
|
}
|
|
|
|
tagsInfo = append(tagsInfo, TagInfo{Name: val, Timestamp: timeStamp, Digest: digest.String()})
|
|
}
|
|
}
|
|
|
|
return tagsInfo, nil
|
|
}
|
|
|
|
// check notary signature corresponding to repo name, manifest digest and mediatype.
|
|
func (olu OciLayoutUtils) checkNotarySignature(name string, digest godigest.Digest) bool {
|
|
imageStore := olu.StoreController.GetImageStore(name)
|
|
mediaType := notreg.ArtifactTypeNotation
|
|
|
|
_, err := imageStore.GetReferrers(name, digest.String(), mediaType)
|
|
if err != nil {
|
|
olu.Log.Info().Err(err).Str("repo", name).Str("digest",
|
|
digest.String()).Str("mediatype", mediaType).Msg("invalid notary signature")
|
|
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// check cosign signature corresponding to manifest.
|
|
func (olu OciLayoutUtils) checkCosignSignature(name string, digest godigest.Digest) bool {
|
|
imageStore := olu.StoreController.GetImageStore(name)
|
|
|
|
// if manifest is signed using cosign mechanism, cosign adds a new manifest.
|
|
// new manifest is tagged as sha256-<manifest-digest>.sig.
|
|
reference := fmt.Sprintf("sha256-%s.sig", digest.Encoded())
|
|
|
|
_, _, _, err := imageStore.GetImageManifest(name, reference) // nolint: dogsled
|
|
if err != nil {
|
|
olu.Log.Info().Err(err).Str("repo", name).Str("digest",
|
|
digest.String()).Msg("invalid cosign signature")
|
|
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// checks if manifest is signed or not
|
|
// checks for notary or cosign signature
|
|
// if cosign signature found it does not looks for notary signature.
|
|
func (olu OciLayoutUtils) checkManifestSignature(name string, digest godigest.Digest) bool {
|
|
if !olu.checkCosignSignature(name, digest) {
|
|
return olu.checkNotarySignature(name, digest)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (olu OciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) {
|
|
repo := RepoInfo{}
|
|
|
|
manifests := make([]Manifest, 0)
|
|
|
|
manifestList, err := olu.GetImageManifests(name)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("error getting image manifests")
|
|
|
|
return RepoInfo{}, err
|
|
}
|
|
|
|
for _, man := range manifestList {
|
|
manifestInfo := Manifest{}
|
|
|
|
manifestInfo.Digest = man.Digest.Encoded()
|
|
|
|
manifestInfo.IsSigned = false
|
|
|
|
tag, ok := man.Annotations[ispec.AnnotationRefName]
|
|
if !ok {
|
|
tag = "latest"
|
|
}
|
|
|
|
manifestInfo.Tag = tag
|
|
|
|
manifest, err := olu.GetImageBlobManifest(name, man.Digest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("error getting image manifest blob")
|
|
|
|
return RepoInfo{}, err
|
|
}
|
|
|
|
manifestInfo.IsSigned = olu.checkManifestSignature(name, man.Digest)
|
|
|
|
layers := make([]Layer, 0)
|
|
|
|
for _, layer := range manifest.Layers {
|
|
layerInfo := Layer{}
|
|
|
|
layerInfo.Digest = layer.Digest.Hex
|
|
|
|
layerInfo.Size = strconv.FormatInt(layer.Size, 10)
|
|
|
|
layers = append(layers, layerInfo)
|
|
}
|
|
|
|
manifestInfo.Layers = layers
|
|
|
|
manifests = append(manifests, manifestInfo)
|
|
}
|
|
|
|
repo.Manifests = manifests
|
|
|
|
return repo, nil
|
|
}
|
|
|
|
func GetImageDirAndTag(imageName string) (string, string) {
|
|
var imageDir string
|
|
|
|
var imageTag string
|
|
|
|
if strings.Contains(imageName, ":") {
|
|
splitImageName := strings.Split(imageName, ":")
|
|
imageDir = splitImageName[0]
|
|
imageTag = splitImageName[1]
|
|
} else {
|
|
imageDir = imageName
|
|
}
|
|
|
|
return imageDir, imageTag
|
|
}
|