2019-06-20 18:36:40 -05:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
2024-08-02 16:23:53 -05:00
|
|
|
"context"
|
2023-05-26 13:08:19 -05:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
2019-06-20 18:36:40 -05:00
|
|
|
|
2024-08-02 16:23:53 -05:00
|
|
|
"github.com/distribution/distribution/v3/registry/storage/driver/factory"
|
2022-10-22 15:46:13 -05:00
|
|
|
godigest "github.com/opencontainers/go-digest"
|
2022-11-08 03:38:16 -05:00
|
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
2022-10-20 11:39:20 -05:00
|
|
|
|
2024-01-31 23:34:07 -05:00
|
|
|
zerr "zotregistry.dev/zot/errors"
|
|
|
|
"zotregistry.dev/zot/pkg/api/config"
|
|
|
|
zcommon "zotregistry.dev/zot/pkg/common"
|
|
|
|
"zotregistry.dev/zot/pkg/extensions/monitoring"
|
|
|
|
"zotregistry.dev/zot/pkg/log"
|
|
|
|
common "zotregistry.dev/zot/pkg/storage/common"
|
|
|
|
"zotregistry.dev/zot/pkg/storage/constants"
|
|
|
|
"zotregistry.dev/zot/pkg/storage/local"
|
|
|
|
"zotregistry.dev/zot/pkg/storage/s3"
|
|
|
|
storageTypes "zotregistry.dev/zot/pkg/storage/types"
|
2019-06-20 18:36:40 -05:00
|
|
|
)
|
|
|
|
|
2023-05-26 13:08:19 -05:00
|
|
|
func New(config *config.Config, linter common.Lint, metrics monitoring.MetricServer,
|
|
|
|
log log.Logger,
|
|
|
|
) (StoreController, error) {
|
|
|
|
storeController := StoreController{}
|
|
|
|
|
|
|
|
if config.Storage.RootDirectory == "" {
|
|
|
|
// we can't proceed without global storage
|
2023-12-08 03:05:02 -05:00
|
|
|
log.Error().Err(zerr.ErrImgStoreNotFound).Str("component", "controller").
|
|
|
|
Msg("no storage config provided")
|
2023-05-26 13:08:19 -05:00
|
|
|
|
2023-11-24 03:38:36 -05:00
|
|
|
return storeController, zerr.ErrImgStoreNotFound
|
2023-05-26 13:08:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// no need to validate hard links work on s3
|
|
|
|
if config.Storage.Dedupe && config.Storage.StorageDriver == nil {
|
|
|
|
err := local.ValidateHardLink(config.Storage.RootDirectory)
|
|
|
|
if err != nil {
|
|
|
|
log.Warn().Msg("input storage root directory filesystem does not supports hardlinking," +
|
|
|
|
"disabling dedupe functionality")
|
|
|
|
|
|
|
|
config.Storage.Dedupe = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var defaultStore storageTypes.ImageStore
|
|
|
|
|
|
|
|
if config.Storage.StorageDriver == nil {
|
2023-11-24 03:38:36 -05:00
|
|
|
cacheDriver, err := CreateCacheDatabaseDriver(config.Storage.StorageConfig, log)
|
|
|
|
if err != nil {
|
|
|
|
return storeController, err
|
|
|
|
}
|
|
|
|
|
2023-05-26 13:08:19 -05:00
|
|
|
// false positive lint - linter does not implement Lint method
|
|
|
|
//nolint:typecheck,contextcheck
|
2023-09-01 12:54:39 -05:00
|
|
|
rootDir := config.Storage.RootDirectory
|
|
|
|
defaultStore = local.NewImageStore(rootDir,
|
2024-10-31 02:44:04 -05:00
|
|
|
config.Storage.Dedupe, config.Storage.Commit, log, metrics, linter, cacheDriver, config.HTTP.Compat,
|
2023-05-26 13:08:19 -05:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
storeName := fmt.Sprintf("%v", config.Storage.StorageDriver["name"])
|
|
|
|
if storeName != constants.S3StorageDriverName {
|
2023-11-24 03:38:36 -05:00
|
|
|
log.Error().Err(zerr.ErrBadConfig).Str("storageDriver", storeName).
|
2023-05-26 13:08:19 -05:00
|
|
|
Msg("unsupported storage driver")
|
2023-11-24 03:38:36 -05:00
|
|
|
|
|
|
|
return storeController, fmt.Errorf("storageDriver '%s' unsupported storage driver: %w", storeName, zerr.ErrBadConfig)
|
2023-05-26 13:08:19 -05:00
|
|
|
}
|
2024-08-02 16:23:53 -05:00
|
|
|
|
2023-05-26 13:08:19 -05:00
|
|
|
// Init a Storager from connection string.
|
2024-08-02 16:23:53 -05:00
|
|
|
store, err := factory.Create(context.Background(), storeName, config.Storage.StorageDriver)
|
2023-05-26 13:08:19 -05:00
|
|
|
if err != nil {
|
2023-12-08 03:05:02 -05:00
|
|
|
log.Error().Err(err).Str("rootDir", config.Storage.RootDirectory).Msg("failed to create s3 service")
|
2023-05-26 13:08:19 -05:00
|
|
|
|
|
|
|
return storeController, err
|
|
|
|
}
|
|
|
|
|
|
|
|
/* in the case of s3 config.Storage.RootDirectory is used for caching blobs locally and
|
|
|
|
config.Storage.StorageDriver["rootdirectory"] is the actual rootDir in s3 */
|
|
|
|
rootDir := "/"
|
|
|
|
if config.Storage.StorageDriver["rootdirectory"] != nil {
|
|
|
|
rootDir = fmt.Sprintf("%v", config.Storage.StorageDriver["rootdirectory"])
|
|
|
|
}
|
|
|
|
|
2023-11-24 03:38:36 -05:00
|
|
|
cacheDriver, err := CreateCacheDatabaseDriver(config.Storage.StorageConfig, log)
|
|
|
|
if err != nil {
|
|
|
|
return storeController, err
|
|
|
|
}
|
|
|
|
|
2023-05-26 13:08:19 -05:00
|
|
|
// false positive lint - linter does not implement Lint method
|
|
|
|
//nolint: typecheck,contextcheck
|
|
|
|
defaultStore = s3.NewImageStore(rootDir, config.Storage.RootDirectory,
|
2024-10-31 02:44:04 -05:00
|
|
|
config.Storage.Dedupe, config.Storage.Commit, log, metrics, linter, store, cacheDriver, config.HTTP.Compat)
|
2023-05-26 13:08:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
storeController.DefaultStore = defaultStore
|
|
|
|
|
|
|
|
if config.Storage.SubPaths != nil {
|
|
|
|
if len(config.Storage.SubPaths) > 0 {
|
|
|
|
subPaths := config.Storage.SubPaths
|
|
|
|
|
|
|
|
//nolint: contextcheck
|
|
|
|
subImageStore, err := getSubStore(config, subPaths, linter, metrics, log)
|
|
|
|
if err != nil {
|
2023-12-08 03:05:02 -05:00
|
|
|
log.Error().Err(err).Str("component", "controller").Msg("failed to get sub image store")
|
2023-05-26 13:08:19 -05:00
|
|
|
|
|
|
|
return storeController, err
|
|
|
|
}
|
|
|
|
|
|
|
|
storeController.SubStore = subImageStore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return storeController, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getSubStore(cfg *config.Config, subPaths map[string]config.StorageConfig,
|
|
|
|
linter common.Lint, metrics monitoring.MetricServer, log log.Logger,
|
|
|
|
) (map[string]storageTypes.ImageStore, error) {
|
|
|
|
imgStoreMap := make(map[string]storageTypes.ImageStore, 0)
|
|
|
|
|
|
|
|
subImageStore := make(map[string]storageTypes.ImageStore)
|
|
|
|
|
|
|
|
// creating image store per subpaths
|
|
|
|
for route, storageConfig := range subPaths {
|
|
|
|
// no need to validate hard links work on s3
|
|
|
|
if storageConfig.Dedupe && storageConfig.StorageDriver == nil {
|
|
|
|
err := local.ValidateHardLink(storageConfig.RootDirectory)
|
|
|
|
if err != nil {
|
|
|
|
log.Warn().Msg("input storage root directory filesystem does not supports hardlinking, " +
|
|
|
|
"disabling dedupe functionality")
|
|
|
|
|
|
|
|
storageConfig.Dedupe = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if storageConfig.StorageDriver == nil {
|
|
|
|
// Compare if subpath root dir is same as default root dir
|
|
|
|
isSame, _ := config.SameFile(cfg.Storage.RootDirectory, storageConfig.RootDirectory)
|
|
|
|
|
|
|
|
if isSame {
|
2023-12-08 03:05:02 -05:00
|
|
|
log.Error().Err(zerr.ErrBadConfig).
|
|
|
|
Msg("invalid sub path storage directory, it must be different to the root directory")
|
2023-05-26 13:08:19 -05:00
|
|
|
|
2023-11-24 03:38:36 -05:00
|
|
|
return nil, zerr.ErrBadConfig
|
2023-05-26 13:08:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
isUnique := true
|
|
|
|
|
|
|
|
// Compare subpath unique files
|
|
|
|
for file := range imgStoreMap {
|
|
|
|
// We already have image storage for this file
|
|
|
|
if compareImageStore(file, storageConfig.RootDirectory) {
|
|
|
|
subImageStore[route] = imgStoreMap[file]
|
|
|
|
|
|
|
|
isUnique = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// subpath root directory is unique
|
|
|
|
// add it to uniqueSubFiles
|
|
|
|
// Create a new image store and assign it to imgStoreMap
|
|
|
|
if isUnique {
|
2023-11-24 03:38:36 -05:00
|
|
|
cacheDriver, err := CreateCacheDatabaseDriver(storageConfig, log)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-09-01 12:54:39 -05:00
|
|
|
rootDir := storageConfig.RootDirectory
|
|
|
|
imgStoreMap[storageConfig.RootDirectory] = local.NewImageStore(rootDir,
|
2024-10-31 02:44:04 -05:00
|
|
|
storageConfig.Dedupe, storageConfig.Commit, log, metrics, linter, cacheDriver, cfg.HTTP.Compat,
|
2023-09-01 12:54:39 -05:00
|
|
|
)
|
2023-05-26 13:08:19 -05:00
|
|
|
|
|
|
|
subImageStore[route] = imgStoreMap[storageConfig.RootDirectory]
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
storeName := fmt.Sprintf("%v", storageConfig.StorageDriver["name"])
|
|
|
|
if storeName != constants.S3StorageDriverName {
|
2023-11-24 03:38:36 -05:00
|
|
|
log.Error().Err(zerr.ErrBadConfig).Str("storageDriver", storeName).
|
2023-05-26 13:08:19 -05:00
|
|
|
Msg("unsupported storage driver")
|
2023-11-24 03:38:36 -05:00
|
|
|
|
|
|
|
return nil, fmt.Errorf("storageDriver '%s' unsupported storage driver: %w", storeName, zerr.ErrBadConfig)
|
2023-05-26 13:08:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Init a Storager from connection string.
|
2024-08-02 16:23:53 -05:00
|
|
|
store, err := factory.Create(context.Background(), storeName, storageConfig.StorageDriver)
|
2023-05-26 13:08:19 -05:00
|
|
|
if err != nil {
|
2023-12-08 03:05:02 -05:00
|
|
|
log.Error().Err(err).Str("rootDir", storageConfig.RootDirectory).Msg("failed to create s3 service")
|
2023-05-26 13:08:19 -05:00
|
|
|
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
/* in the case of s3 c.Config.Storage.RootDirectory is used for caching blobs locally and
|
|
|
|
c.Config.Storage.StorageDriver["rootdirectory"] is the actual rootDir in s3 */
|
|
|
|
rootDir := "/"
|
|
|
|
if cfg.Storage.StorageDriver["rootdirectory"] != nil {
|
|
|
|
rootDir = fmt.Sprintf("%v", cfg.Storage.StorageDriver["rootdirectory"])
|
|
|
|
}
|
|
|
|
|
2023-11-24 03:38:36 -05:00
|
|
|
cacheDriver, err := CreateCacheDatabaseDriver(storageConfig, log)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Any("config", storageConfig).
|
2023-12-08 03:05:02 -05:00
|
|
|
Msg("failed to create storage driver")
|
2023-11-24 03:38:36 -05:00
|
|
|
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-05-26 13:08:19 -05:00
|
|
|
// false positive lint - linter does not implement Lint method
|
|
|
|
//nolint: typecheck
|
|
|
|
subImageStore[route] = s3.NewImageStore(rootDir, storageConfig.RootDirectory,
|
2024-10-31 02:44:04 -05:00
|
|
|
storageConfig.Dedupe, storageConfig.Commit, log, metrics, linter, store, cacheDriver, cfg.HTTP.Compat,
|
2023-05-26 13:08:19 -05:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return subImageStore, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func compareImageStore(root1, root2 string) bool {
|
|
|
|
isSameFile, err := config.SameFile(root1, root2)
|
|
|
|
if err != nil {
|
2024-07-29 12:32:51 -05:00
|
|
|
// This error is path error that means either of root directory doesn't exist, in that case do string match
|
2023-05-26 13:08:19 -05:00
|
|
|
return strings.EqualFold(root1, root2)
|
|
|
|
}
|
|
|
|
|
|
|
|
return isSameFile
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckIsImageSignature checks if the given image (repo:tag) represents a signature. The function
|
|
|
|
// returns:
|
|
|
|
//
|
|
|
|
// - bool: if the image is a signature or not
|
|
|
|
//
|
|
|
|
// - string: the type of signature
|
|
|
|
//
|
|
|
|
// - string: the digest of the image it signs
|
|
|
|
//
|
|
|
|
// - error: any errors that occur.
|
|
|
|
func CheckIsImageSignature(repoName string, manifestBlob []byte, reference string,
|
|
|
|
) (bool, string, godigest.Digest, error) {
|
|
|
|
var manifestContent ispec.Manifest
|
|
|
|
|
|
|
|
err := json.Unmarshal(manifestBlob, &manifestContent)
|
|
|
|
if err != nil {
|
|
|
|
return false, "", "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestArtifactType := zcommon.GetManifestArtifactType(manifestContent)
|
|
|
|
|
|
|
|
// check notation signature
|
2023-09-06 11:58:00 -05:00
|
|
|
if manifestArtifactType == zcommon.ArtifactTypeNotation && manifestContent.Subject != nil {
|
2023-05-26 13:08:19 -05:00
|
|
|
return true, NotationType, manifestContent.Subject.Digest, nil
|
|
|
|
}
|
|
|
|
|
2023-11-06 17:09:39 -05:00
|
|
|
// check cosign signature (OCI 1.1 support)
|
|
|
|
if manifestArtifactType == zcommon.ArtifactTypeCosign && manifestContent.Subject != nil {
|
|
|
|
return true, CosignType, manifestContent.Subject.Digest, nil
|
|
|
|
}
|
|
|
|
|
2023-10-30 15:06:04 -05:00
|
|
|
if tag := reference; zcommon.IsCosignTag(reference) {
|
2023-05-26 13:08:19 -05:00
|
|
|
prefixLen := len("sha256-")
|
|
|
|
digestLen := 64
|
|
|
|
signedImageManifestDigestEncoded := tag[prefixLen : prefixLen+digestLen]
|
|
|
|
|
|
|
|
signedImageManifestDigest := godigest.NewDigestFromEncoded(godigest.SHA256,
|
|
|
|
signedImageManifestDigestEncoded)
|
|
|
|
|
|
|
|
return true, CosignType, signedImageManifestDigest, nil
|
|
|
|
}
|
2021-07-16 22:53:05 -05:00
|
|
|
|
2023-05-26 13:08:19 -05:00
|
|
|
return false, "", "", nil
|
2020-06-30 12:56:58 -05:00
|
|
|
}
|