mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 21:17:58 +08:00
feat(graphql & repodb): add info about signature validity (#1344)
Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"zotregistry.io/zot/pkg/meta/bolt"
|
||||
"zotregistry.io/zot/pkg/meta/common"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
"zotregistry.io/zot/pkg/meta/version"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
)
|
||||
@@ -726,6 +727,102 @@ func (bdw *DBWrapper) IncrementImageDownloads(repo string, reference string) err
|
||||
return err
|
||||
}
|
||||
|
||||
func (bdw *DBWrapper) UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error {
|
||||
err := bdw.DB.Update(func(transaction *bbolt.Tx) error {
|
||||
// get ManifestData of signed manifest
|
||||
manifestBuck := transaction.Bucket([]byte(bolt.ManifestDataBucket))
|
||||
mdBlob := manifestBuck.Get([]byte(manifestDigest))
|
||||
|
||||
var blob []byte
|
||||
|
||||
if len(mdBlob) != 0 {
|
||||
var manifestData repodb.ManifestData
|
||||
|
||||
err := json.Unmarshal(mdBlob, &manifestData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("repodb: %w error while unmashaling manifest meta for digest %s", err, manifestDigest)
|
||||
}
|
||||
|
||||
blob = manifestData.ManifestBlob
|
||||
} else {
|
||||
var indexData repodb.IndexData
|
||||
|
||||
indexBuck := transaction.Bucket([]byte(bolt.IndexDataBucket))
|
||||
idBlob := indexBuck.Get([]byte(manifestDigest))
|
||||
|
||||
if len(idBlob) == 0 {
|
||||
// manifest meta not found, updating signatures with details about validity and author will not be performed
|
||||
return nil
|
||||
}
|
||||
|
||||
err := json.Unmarshal(idBlob, &indexData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("repodb: %w error while unmashaling index meta for digest %s", err, manifestDigest)
|
||||
}
|
||||
|
||||
blob = indexData.IndexBlob
|
||||
}
|
||||
|
||||
// update signatures with details about validity and author
|
||||
repoBuck := transaction.Bucket([]byte(bolt.RepoMetadataBucket))
|
||||
|
||||
repoMetaBlob := repoBuck.Get([]byte(repo))
|
||||
if repoMetaBlob == nil {
|
||||
return zerr.ErrRepoMetaNotFound
|
||||
}
|
||||
|
||||
var repoMeta repodb.RepoMetadata
|
||||
|
||||
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestSignatures := repodb.ManifestSignatures{}
|
||||
for sigType, sigs := range repoMeta.Signatures[manifestDigest.String()] {
|
||||
signaturesInfo := []repodb.SignatureInfo{}
|
||||
|
||||
for _, sigInfo := range sigs {
|
||||
layersInfo := []repodb.LayerInfo{}
|
||||
|
||||
for _, layerInfo := range sigInfo.LayersInfo {
|
||||
author, date, isTrusted, _ := signatures.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey,
|
||||
manifestDigest, blob, repo)
|
||||
|
||||
if isTrusted {
|
||||
layerInfo.Signer = author
|
||||
}
|
||||
|
||||
if !date.IsZero() {
|
||||
layerInfo.Signer = author
|
||||
layerInfo.Date = date
|
||||
}
|
||||
|
||||
layersInfo = append(layersInfo, layerInfo)
|
||||
}
|
||||
|
||||
signaturesInfo = append(signaturesInfo, repodb.SignatureInfo{
|
||||
SignatureManifestDigest: sigInfo.SignatureManifestDigest,
|
||||
LayersInfo: layersInfo,
|
||||
})
|
||||
}
|
||||
|
||||
manifestSignatures[sigType] = signaturesInfo
|
||||
}
|
||||
|
||||
repoMeta.Signatures[manifestDigest.String()] = manifestSignatures
|
||||
|
||||
repoMetaBlob, err = json.Marshal(repoMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return repoBuck.Put([]byte(repo), repoMetaBlob)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (bdw *DBWrapper) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
|
||||
sygMeta repodb.SignatureMetadata,
|
||||
) error {
|
||||
@@ -780,10 +877,17 @@ func (bdw *DBWrapper) AddManifestSignature(repo string, signedManifestDigest god
|
||||
|
||||
signatureSlice := manifestSignatures[sygMeta.SignatureType]
|
||||
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
|
||||
signatureSlice = append(signatureSlice, repodb.SignatureInfo{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
})
|
||||
if sygMeta.SignatureType == signatures.NotationSignature {
|
||||
signatureSlice = append(signatureSlice, repodb.SignatureInfo{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
})
|
||||
} else if sygMeta.SignatureType == signatures.CosignSignature {
|
||||
signatureSlice = []repodb.SignatureInfo{{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
manifestSignatures[sygMeta.SignatureType] = signatureSlice
|
||||
|
||||
@@ -3,8 +3,15 @@ package bolt_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/notaryproject/notation-core-go/signature/jws"
|
||||
"github.com/notaryproject/notation-go"
|
||||
"github.com/notaryproject/notation-go/signer"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
@@ -14,6 +21,7 @@ import (
|
||||
"zotregistry.io/zot/pkg/meta/bolt"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
@@ -308,6 +316,20 @@ func TestWrapperErrors(t *testing.T) {
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.AddManifestSignature("repo1", digest.FromString("dig"),
|
||||
repodb.SignatureMetadata{
|
||||
SignatureType: "cosign",
|
||||
SignatureDigest: "digest2",
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
repoData, err := boltdbWrapper.GetRepoMeta("repo1")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature]),
|
||||
ShouldEqual, 1)
|
||||
So(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature][0].SignatureManifestDigest,
|
||||
ShouldEqual, "digest2")
|
||||
|
||||
err = boltdbWrapper.AddManifestSignature("repo1", digest.FromString("dig"),
|
||||
repodb.SignatureMetadata{
|
||||
SignatureType: "notation",
|
||||
@@ -930,6 +952,231 @@ func TestWrapperErrors(t *testing.T) {
|
||||
_, err := boltdbWrapper.GetUserRepoMeta(ctx, "repo")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("UpdateSignaturesValidity", func() {
|
||||
Convey("manifestMeta of signed manifest not found", func() {
|
||||
err := boltdbWrapper.UpdateSignaturesValidity("repo", digest.FromString("dig"))
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("repoMeta of signed manifest not found", func() {
|
||||
// repo Meta not found
|
||||
err := boltdbWrapper.SetManifestData(digest.FromString("dig"), repodb.ManifestData{
|
||||
ManifestBlob: []byte("Bad Manifest"),
|
||||
ConfigBlob: []byte("Bad Manifest"),
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.UpdateSignaturesValidity("repo", digest.FromString("dig"))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("manifest - bad content", func() {
|
||||
err := boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||
dataBuck := tx.Bucket([]byte(bolt.ManifestDataBucket))
|
||||
|
||||
return dataBuck.Put([]byte("digest1"), []byte("wrong json"))
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.UpdateSignaturesValidity("repo1", "digest1")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("index - bad content", func() {
|
||||
err := boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||
dataBuck := tx.Bucket([]byte(bolt.IndexDataBucket))
|
||||
|
||||
return dataBuck.Put([]byte("digest1"), []byte("wrong json"))
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.UpdateSignaturesValidity("repo1", "digest1")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("repo - bad content", func() {
|
||||
// repo Meta not found
|
||||
err := boltdbWrapper.SetManifestData(digest.FromString("dig"), repodb.ManifestData{
|
||||
ManifestBlob: []byte("Bad Manifest"),
|
||||
ConfigBlob: []byte("Bad Manifest"),
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||
repoBuck := tx.Bucket([]byte(bolt.RepoMetadataBucket))
|
||||
|
||||
return repoBuck.Put([]byte("repo1"), []byte("wrong json"))
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.UpdateSignaturesValidity("repo1", digest.FromString("dig"))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("VerifySignature -> untrusted signature", func() {
|
||||
err := boltdbWrapper.SetManifestData(digest.FromString("dig"), repodb.ManifestData{
|
||||
ManifestBlob: []byte("Bad Manifest"),
|
||||
ConfigBlob: []byte("Bad Manifest"),
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||
repoBuck := tx.Bucket([]byte(bolt.RepoMetadataBucket))
|
||||
|
||||
return repoBuck.Put([]byte("repo1"), repoMetaBlob)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
layerInfo := repodb.LayerInfo{LayerDigest: "", LayerContent: []byte{}, SignatureKey: ""}
|
||||
|
||||
err = boltdbWrapper.AddManifestSignature("repo1", digest.FromString("dig"),
|
||||
repodb.SignatureMetadata{
|
||||
SignatureType: signatures.CosignSignature,
|
||||
SignatureDigest: string(digest.FromString("signature digest")),
|
||||
LayersInfo: []repodb.LayerInfo{layerInfo},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.UpdateSignaturesValidity("repo1", digest.FromString("dig"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
repoData, err := boltdbWrapper.GetRepoMeta("repo1")
|
||||
So(err, ShouldBeNil)
|
||||
So(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature][0].LayersInfo[0].Signer,
|
||||
ShouldBeEmpty)
|
||||
So(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature][0].LayersInfo[0].Date,
|
||||
ShouldBeZeroValue)
|
||||
})
|
||||
|
||||
Convey("VerifySignature -> trusted signature", func() {
|
||||
_, _, manifest, _ := test.GetRandomImageComponents(10)
|
||||
manifestContent, _ := json.Marshal(manifest)
|
||||
manifestDigest := digest.FromBytes(manifestContent)
|
||||
|
||||
err := boltdbWrapper.SetManifestData(manifestDigest, repodb.ManifestData{
|
||||
ManifestBlob: manifestContent,
|
||||
ConfigBlob: []byte("configContent"),
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||
repoBuck := tx.Bucket([]byte(bolt.RepoMetadataBucket))
|
||||
|
||||
return repoBuck.Put([]byte("repo"), repoMetaBlob)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
mediaType := jws.MediaTypeEnvelope
|
||||
|
||||
signOpts := notation.SignerSignOptions{
|
||||
SignatureMediaType: mediaType,
|
||||
PluginConfig: map[string]string{},
|
||||
ExpiryDuration: 24 * time.Hour,
|
||||
}
|
||||
|
||||
tdir := t.TempDir()
|
||||
keyName := "notation-sign-test"
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tdir)
|
||||
|
||||
err = test.GenerateNotationCerts(tdir, keyName)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// getSigner
|
||||
var newSigner notation.Signer
|
||||
|
||||
// ResolveKey
|
||||
signingKeys, err := test.LoadNotationSigningkeys(tdir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
idx := test.Index(signingKeys.Keys, keyName)
|
||||
So(idx, ShouldBeGreaterThanOrEqualTo, 0)
|
||||
|
||||
key := signingKeys.Keys[idx]
|
||||
|
||||
if key.X509KeyPair != nil {
|
||||
newSigner, err = signer.NewFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath)
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
descToSign := ispec.Descriptor{
|
||||
MediaType: manifest.MediaType,
|
||||
Digest: manifestDigest,
|
||||
Size: int64(len(manifestContent)),
|
||||
}
|
||||
sig, _, err := newSigner.Sign(ctx, descToSign, signOpts)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
layerInfo := repodb.LayerInfo{
|
||||
LayerDigest: string(digest.FromBytes(sig)),
|
||||
LayerContent: sig, SignatureKey: mediaType,
|
||||
}
|
||||
|
||||
err = boltdbWrapper.AddManifestSignature("repo", manifestDigest,
|
||||
repodb.SignatureMetadata{
|
||||
SignatureType: signatures.NotationSignature,
|
||||
SignatureDigest: string(digest.FromString("signature digest")),
|
||||
LayersInfo: []repodb.LayerInfo{layerInfo},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(tdir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
trustpolicyPath := path.Join(tdir, "_notation/trustpolicy.json")
|
||||
|
||||
if _, err := os.Stat(trustpolicyPath); errors.Is(err, os.ErrNotExist) {
|
||||
trustPolicy := `
|
||||
{
|
||||
"version": "1.0",
|
||||
"trustPolicies": [
|
||||
{
|
||||
"name": "notation-sign-test",
|
||||
"registryScopes": [ "*" ],
|
||||
"signatureVerification": {
|
||||
"level" : "strict"
|
||||
},
|
||||
"trustStores": ["ca:notation-sign-test"],
|
||||
"trustedIdentities": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
file, err := os.Create(trustpolicyPath)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString(trustPolicy)
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
truststore := "_notation/truststore/x509/ca/notation-sign-test"
|
||||
truststoreSrc := "notation/truststore/x509/ca/notation-sign-test"
|
||||
err = os.MkdirAll(path.Join(tdir, truststore), 0o755)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.CopyFile(path.Join(tdir, truststoreSrc, "notation-sign-test.crt"),
|
||||
path.Join(tdir, truststore, "notation-sign-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = boltdbWrapper.UpdateSignaturesValidity("repo", manifestDigest) //nolint:contextcheck
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
repoData, err := boltdbWrapper.GetRepoMeta("repo")
|
||||
So(err, ShouldBeNil)
|
||||
So(repoData.Signatures[string(manifestDigest)][signatures.NotationSignature][0].LayersInfo[0].Signer,
|
||||
ShouldNotBeEmpty)
|
||||
So(repoData.Signatures[string(manifestDigest)][signatures.NotationSignature][0].LayersInfo[0].Date,
|
||||
ShouldNotBeZeroValue)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"zotregistry.io/zot/pkg/meta/common"
|
||||
"zotregistry.io/zot/pkg/meta/dynamo"
|
||||
"zotregistry.io/zot/pkg/meta/repodb" //nolint:go-staticcheck
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
"zotregistry.io/zot/pkg/meta/version"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
)
|
||||
@@ -616,6 +617,10 @@ func (dwr *DBWrapper) IncrementImageDownloads(repo string, reference string) err
|
||||
return dwr.SetRepoMeta(repo, repoMeta)
|
||||
}
|
||||
|
||||
func (dwr *DBWrapper) UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dwr *DBWrapper) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
|
||||
sygMeta repodb.SignatureMetadata,
|
||||
) error {
|
||||
@@ -656,10 +661,17 @@ func (dwr *DBWrapper) AddManifestSignature(repo string, signedManifestDigest god
|
||||
|
||||
signatureSlice := manifestSignatures[sygMeta.SignatureType]
|
||||
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
|
||||
signatureSlice = append(signatureSlice, repodb.SignatureInfo{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
})
|
||||
if sygMeta.SignatureType == signatures.NotationSignature {
|
||||
signatureSlice = append(signatureSlice, repodb.SignatureInfo{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
})
|
||||
} else if sygMeta.SignatureType == signatures.CosignSignature {
|
||||
signatureSlice = []repodb.SignatureInfo{{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
manifestSignatures[sygMeta.SignatureType] = signatureSlice
|
||||
|
||||
@@ -7,13 +7,6 @@ import (
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
const (
|
||||
SignaturesDirPath = "/tmp/zot/signatures"
|
||||
SigKey = "dev.cosignproject.cosign/signature"
|
||||
NotationType = "notation"
|
||||
CosignType = "cosign"
|
||||
)
|
||||
|
||||
// Used to model changes to an object after a call to the DB.
|
||||
type ToggleState int
|
||||
|
||||
@@ -97,6 +90,9 @@ type RepoDB interface { //nolint:interfacebloat
|
||||
// DeleteSignature delets signature metadata to a given manifest from the database
|
||||
DeleteSignature(repo string, signedManifestDigest godigest.Digest, sm SignatureMetadata) error
|
||||
|
||||
// UpdateSignaturesValidity checks and updates signatures validity of a given manifest
|
||||
UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error
|
||||
|
||||
// SearchRepos searches for repos given a search string
|
||||
SearchRepos(ctx context.Context, searchText string, filter Filter, requestedPage PageInput) (
|
||||
[]RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, PageInfo, error)
|
||||
@@ -183,6 +179,7 @@ type LayerInfo struct {
|
||||
LayerContent []byte
|
||||
SignatureKey string
|
||||
Signer string
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
type SignatureInfo struct {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||
dynamodb_wrapper "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
)
|
||||
|
||||
func New(storageConfig config.StorageConfig, log log.Logger) (repodb.RepoDB, error) {
|
||||
@@ -34,6 +35,11 @@ func New(storageConfig config.StorageConfig, log log.Logger) (repodb.RepoDB, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = signatures.InitCosignAndNotationDirs(params.RootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Create("boltdb", driver, params, log) //nolint:contextcheck
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@ package repodbfactory_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/bolt"
|
||||
"zotregistry.io/zot/pkg/meta/dynamo"
|
||||
@@ -73,6 +75,31 @@ func TestCreateBoltDB(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
Convey("InitCosignAndNotationDirs fails", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
var storageConfig config.StorageConfig
|
||||
|
||||
storageConfig.RootDirectory = rootDir
|
||||
storageConfig.RemoteCache = false
|
||||
log := log.NewLogger("debug", "")
|
||||
|
||||
_, err := os.Create(path.Join(rootDir, "repo.db"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(rootDir, 0o555)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
newRepodb, err := repodbfactory.New(storageConfig, log)
|
||||
So(newRepodb, ShouldBeNil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(rootDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func skipDynamo(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
)
|
||||
|
||||
@@ -105,15 +106,30 @@ func ParseRepo(repo string, repoDB RepoDB, storeController storage.StoreControll
|
||||
}
|
||||
|
||||
if isSignature {
|
||||
err := repoDB.AddManifestSignature(repo, signedManifestDigest,
|
||||
layers, err := GetSignatureLayersInfo(repo, tag, manifest.Digest.String(), signatureType, manifestBlob,
|
||||
imageStore, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = repoDB.AddManifestSignature(repo, signedManifestDigest,
|
||||
SignatureMetadata{
|
||||
SignatureType: signatureType,
|
||||
SignatureDigest: digest.String(),
|
||||
LayersInfo: layers,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("repository", repo).Str("tag", tag).
|
||||
Str("manifestDigest", signedManifestDigest.String()).
|
||||
Msg("load-repo: failed set signature meta for signed image manifest digest")
|
||||
Msg("load-repo: failed set signature meta for signed image")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
err = repoDB.UpdateSignaturesValidity(repo, signedManifestDigest)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("repository", repo).Str("reference", tag).Str("digest", signedManifestDigest.String()).Msg(
|
||||
"load-repo: failed verify signatures validity for signed image")
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -199,6 +215,98 @@ func isManifestMetaPresent(repo string, manifest ispec.Descriptor, repoDB RepoDB
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func GetSignatureLayersInfo(
|
||||
repo, tag, manifestDigest, signatureType string, manifestBlob []byte, imageStore storage.ImageStore, log log.Logger,
|
||||
) ([]LayerInfo, error) {
|
||||
switch signatureType {
|
||||
case signatures.CosignSignature:
|
||||
return getCosignSignatureLayersInfo(repo, tag, manifestDigest, manifestBlob, imageStore, log)
|
||||
case signatures.NotationSignature:
|
||||
return getNotationSignatureLayersInfo(repo, manifestDigest, manifestBlob, imageStore, log)
|
||||
default:
|
||||
return []LayerInfo{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getCosignSignatureLayersInfo(
|
||||
repo, tag, manifestDigest string, manifestBlob []byte, imageStore storage.ImageStore, log log.Logger,
|
||||
) ([]LayerInfo, error) {
|
||||
layers := []LayerInfo{}
|
||||
|
||||
var manifestContent ispec.Manifest
|
||||
if err := json.Unmarshal(manifestBlob, &manifestContent); err != nil {
|
||||
log.Error().Err(err).Str("repository", repo).Str("reference", tag).Str("digest", manifestDigest).Msg(
|
||||
"load-repo: unable to marshal blob index")
|
||||
|
||||
return layers, err
|
||||
}
|
||||
|
||||
for _, layer := range manifestContent.Layers {
|
||||
layerContent, err := imageStore.GetBlobContent(repo, layer.Digest)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("repository", repo).Str("reference", tag).Str("layerDigest", layer.Digest.String()).Msg(
|
||||
"load-repo: unable to get cosign signature layer content")
|
||||
|
||||
return layers, err
|
||||
}
|
||||
|
||||
layerSigKey, ok := layer.Annotations[signatures.CosignSigKey]
|
||||
if !ok {
|
||||
log.Error().Err(err).Str("repository", repo).Str("reference", tag).Str("layerDigest", layer.Digest.String()).Msg(
|
||||
"load-repo: unable to get specific annotation of cosign signature")
|
||||
}
|
||||
|
||||
layers = append(layers, LayerInfo{
|
||||
LayerDigest: layer.Digest.String(),
|
||||
LayerContent: layerContent,
|
||||
SignatureKey: layerSigKey,
|
||||
})
|
||||
}
|
||||
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
func getNotationSignatureLayersInfo(
|
||||
repo, manifestDigest string, manifestBlob []byte, imageStore storage.ImageStore, log log.Logger,
|
||||
) ([]LayerInfo, error) {
|
||||
layers := []LayerInfo{}
|
||||
|
||||
var manifestContent ispec.Manifest
|
||||
if err := json.Unmarshal(manifestBlob, &manifestContent); err != nil {
|
||||
log.Error().Err(err).Str("repository", repo).Str("reference", manifestDigest).Msg(
|
||||
"load-repo: unable to marshal blob index")
|
||||
|
||||
return layers, err
|
||||
}
|
||||
|
||||
if len(manifestContent.Layers) != 1 {
|
||||
log.Error().Err(zerr.ErrBadManifest).Str("repository", repo).Str("reference", manifestDigest).
|
||||
Msg("load-repo: notation signature manifest requires exactly one layer but it does not")
|
||||
|
||||
return layers, zerr.ErrBadManifest
|
||||
}
|
||||
|
||||
layer := manifestContent.Layers[0].Digest
|
||||
|
||||
layerContent, err := imageStore.GetBlobContent(repo, layer)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("repository", repo).Str("reference", manifestDigest).Str("layerDigest", layer.String()).Msg(
|
||||
"load-repo: unable to get notation signature blob content")
|
||||
|
||||
return layers, err
|
||||
}
|
||||
|
||||
layerSigKey := manifestContent.Layers[0].MediaType
|
||||
|
||||
layers = append(layers, LayerInfo{
|
||||
LayerDigest: layer.String(),
|
||||
LayerContent: layerContent,
|
||||
SignatureKey: layerSigKey,
|
||||
})
|
||||
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
// NewManifestMeta takes raw data about an image and createa a new ManifestMetadate object.
|
||||
func NewManifestData(repoName string, manifestBlob []byte, imageStore storage.ImageStore,
|
||||
) (ManifestData, error) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
bolt_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||
dynamo_wrapper "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/storage/local"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
@@ -242,6 +243,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
Digest: "123",
|
||||
},
|
||||
ArtifactType: "application/vnd.cncf.notary.signature",
|
||||
Layers: []ispec.Descriptor{{MediaType: ispec.MediaTypeImageLayer}},
|
||||
}
|
||||
|
||||
manifestBlob, err := json.Marshal(manifestContent)
|
||||
@@ -259,6 +261,100 @@ func TestParseStorageErrors(t *testing.T) {
|
||||
|
||||
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
repoDB.AddManifestSignatureFn = func(repo string, signedManifestDigest godigest.Digest,
|
||||
sm repodb.SignatureMetadata,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
repoDB.UpdateSignaturesValidityFn = func(repo string, signedManifestDigest godigest.Digest,
|
||||
) error {
|
||||
return ErrTestError
|
||||
}
|
||||
|
||||
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("GetSignatureLayersInfo errors", func() {
|
||||
// get notation signature layers info
|
||||
badNotationManifestContent := ispec.Manifest{
|
||||
Subject: &ispec.Descriptor{
|
||||
Digest: "123",
|
||||
},
|
||||
ArtifactType: "application/vnd.cncf.notary.signature",
|
||||
}
|
||||
|
||||
badNotationManifestBlob, err := json.Marshal(badNotationManifestContent)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
imageStore.GetImageManifestFn = func(repo, reference string) ([]byte, godigest.Digest, string, error) {
|
||||
return badNotationManifestBlob, "", "", nil
|
||||
}
|
||||
|
||||
// wrong number of layers of notation manifest
|
||||
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
notationManifestContent := ispec.Manifest{
|
||||
Subject: &ispec.Descriptor{
|
||||
Digest: "123",
|
||||
},
|
||||
ArtifactType: "application/vnd.cncf.notary.signature",
|
||||
Layers: []ispec.Descriptor{{MediaType: ispec.MediaTypeImageLayer}},
|
||||
}
|
||||
|
||||
notationManifestBlob, err := json.Marshal(notationManifestContent)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
imageStore.GetImageManifestFn = func(repo, reference string) ([]byte, godigest.Digest, string, error) {
|
||||
return notationManifestBlob, "", "", nil
|
||||
}
|
||||
|
||||
imageStore.GetBlobContentFn = func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||
return []byte{}, ErrTestError
|
||||
}
|
||||
|
||||
// unable to get layer content
|
||||
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, _, cosignManifestContent, _ := test.GetRandomImageComponents(10)
|
||||
_, _, signedManifest, _ := test.GetRandomImageComponents(10)
|
||||
signatureTag, err := test.GetCosignSignatureTagForManifest(signedManifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cosignManifestContent.Annotations = map[string]string{ispec.AnnotationRefName: signatureTag}
|
||||
|
||||
cosignManifestBlob, err := json.Marshal(cosignManifestContent)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
imageStore.GetImageManifestFn = func(repo, reference string) ([]byte, godigest.Digest, string, error) {
|
||||
return cosignManifestBlob, "", "", nil
|
||||
}
|
||||
|
||||
indexContent := ispec.Index{
|
||||
Manifests: []ispec.Descriptor{
|
||||
{
|
||||
Digest: godigest.FromString("cosignSig"),
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Annotations: map[string]string{
|
||||
ispec.AnnotationRefName: signatureTag,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
indexBlob, err := json.Marshal(indexContent)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
imageStore.GetIndexContentFn = func(repo string) ([]byte, error) {
|
||||
return indexBlob, nil
|
||||
}
|
||||
|
||||
// unable to get layer content
|
||||
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -534,3 +630,22 @@ func skipIt(t *testing.T) {
|
||||
t.Skip("Skipping testing without AWS S3 mock server")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSignatureLayersInfo(t *testing.T) {
|
||||
Convey("wrong signature type", t, func() {
|
||||
layers, err := repodb.GetSignatureLayersInfo("repo", "tag", "123", "wrong signature type", []byte{},
|
||||
nil, log.NewLogger("debug", ""))
|
||||
So(err, ShouldBeNil)
|
||||
So(layers, ShouldBeEmpty)
|
||||
})
|
||||
|
||||
Convey("error while unmarshaling manifest content", t, func() {
|
||||
_, err := repodb.GetSignatureLayersInfo("repo", "tag", "123", signatures.CosignSignature, []byte("bad manifest"),
|
||||
nil, log.NewLogger("debug", ""))
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, err = repodb.GetSignatureLayersInfo("repo", "tag", "123", signatures.NotationSignature, []byte("bad manifest"),
|
||||
nil, log.NewLogger("debug", ""))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user