mirror of
https://github.com/project-zot/zot.git
synced 2024-12-23 22:27:35 -05:00
7e3d063319
Signed-off-by: Laurentiu Niculae <themelopeus@gmail.com>
442 lines
12 KiB
Go
442 lines
12 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"
|
|
)
|
|
|
|
type OciLayoutUtils interface {
|
|
GetImageManifests(image string) ([]ispec.Descriptor, error)
|
|
GetImageBlobManifest(imageDir string, digest godigest.Digest) (v1.Manifest, error)
|
|
GetImageInfo(imageDir string, hash v1.Hash) (ispec.Image, error)
|
|
IsValidImageFormat(image string) (bool, error)
|
|
GetImageTagsWithTimestamp(repo string) ([]TagInfo, error)
|
|
GetImageLastUpdated(repo string, manifestDigest godigest.Digest) time.Time
|
|
GetImagePlatform(repo string, manifestDigest godigest.Digest) (string, string)
|
|
GetImageVendor(repo string, manifestDigest godigest.Digest) string
|
|
GetImageManifestSize(repo string, manifestDigest godigest.Digest) int64
|
|
GetImageConfigSize(repo string, manifestDigest godigest.Digest) int64
|
|
GetRepoLastUpdated(repo string) (time.Time, error)
|
|
GetExpandedRepoInfo(name string) (RepoInfo, error)
|
|
}
|
|
|
|
// OciLayoutInfo ...
|
|
type BaseOciLayoutUtils 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"`
|
|
}
|
|
|
|
// NewBaseOciLayoutUtils initializes a new OciLayoutUtils object.
|
|
func NewBaseOciLayoutUtils(storeController storage.StoreController, log log.Logger) *BaseOciLayoutUtils {
|
|
return &BaseOciLayoutUtils{Log: log, StoreController: storeController}
|
|
}
|
|
|
|
// Below method will return image path including root dir, root dir is determined by splitting.
|
|
func (olu BaseOciLayoutUtils) 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 BaseOciLayoutUtils) 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 BaseOciLayoutUtils) 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 BaseOciLayoutUtils) 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 BaseOciLayoutUtils) 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 BaseOciLayoutUtils) 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 BaseOciLayoutUtils) 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 BaseOciLayoutUtils) checkManifestSignature(name string, digest godigest.Digest) bool {
|
|
if !olu.checkCosignSignature(name, digest) {
|
|
return olu.checkNotarySignature(name, digest)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (olu BaseOciLayoutUtils) GetImageLastUpdated(repo string, manifestDigest godigest.Digest) time.Time {
|
|
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to read image blob")
|
|
|
|
return time.Time{}
|
|
}
|
|
|
|
imageInfo, err := olu.GetImageInfo(repo, imageBlobManifest.Config.Digest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to read image info")
|
|
|
|
return time.Time{}
|
|
}
|
|
|
|
var timeStamp time.Time
|
|
|
|
if len(imageInfo.History) != 0 {
|
|
timeStamp = *imageInfo.History[0].Created
|
|
} else {
|
|
timeStamp = time.Time{}
|
|
}
|
|
|
|
return timeStamp
|
|
}
|
|
|
|
func (olu BaseOciLayoutUtils) GetImagePlatform(repo string, manifestDigest godigest.Digest) (
|
|
string, string,
|
|
) {
|
|
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("can't get image blob manifest")
|
|
|
|
return "", ""
|
|
}
|
|
|
|
imageConfig, err := olu.GetImageInfo(repo, imageBlobManifest.Config.Digest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("extension api: error reading image config")
|
|
|
|
return "", ""
|
|
}
|
|
|
|
return imageConfig.OS, imageConfig.Architecture
|
|
}
|
|
|
|
func (olu BaseOciLayoutUtils) GetImageVendor(repo string, manifestDigest godigest.Digest) string {
|
|
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("can't get image blob manifest")
|
|
|
|
return ""
|
|
}
|
|
|
|
imageConfig, err := olu.GetImageInfo(repo, imageBlobManifest.Config.Digest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("extension api: error reading image config")
|
|
|
|
return ""
|
|
}
|
|
|
|
return imageConfig.Config.Labels["vendor"]
|
|
}
|
|
|
|
func (olu BaseOciLayoutUtils) GetImageManifestSize(repo string, manifestDigest godigest.Digest) int64 {
|
|
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("can't get image blob manifest")
|
|
|
|
return 0
|
|
}
|
|
|
|
return imageBlobManifest.Config.Size
|
|
}
|
|
|
|
func (olu BaseOciLayoutUtils) GetImageConfigSize(repo string, manifestDigest godigest.Digest) int64 {
|
|
imageBlobManifest, err := olu.GetImageBlobManifest(repo, manifestDigest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("can't get image blob manifest")
|
|
|
|
return 0
|
|
}
|
|
imageStore := olu.StoreController.GetImageStore(repo)
|
|
|
|
buf, err := imageStore.GetBlobContent(repo, imageBlobManifest.Config.Digest.String())
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("error when getting blob content")
|
|
|
|
return int64(len(buf))
|
|
}
|
|
|
|
return int64(len(buf))
|
|
}
|
|
|
|
func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (time.Time, error) {
|
|
tagsInfo, err := olu.GetImageTagsWithTimestamp(repo)
|
|
if err != nil || len(tagsInfo) == 0 {
|
|
return time.Time{}, err
|
|
}
|
|
|
|
latestTag := GetLatestTag(tagsInfo)
|
|
|
|
return latestTag.Timestamp, nil
|
|
}
|
|
|
|
func (olu BaseOciLayoutUtils) 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
|
|
}
|