From 5f2f8adbc3e84df9c0b53b5835901b544b8d86e4 Mon Sep 17 00:00:00 2001 From: Andreea-Lupu Date: Tue, 4 Jul 2023 15:15:54 +0300 Subject: [PATCH] feat: upload cosign public key and notation certificates to cloud Signed-off-by: Andreea-Lupu --- go.mod | 2 + go.sum | 9 + pkg/api/routes.go | 2 +- pkg/extensions/extension_image_trust.go | 21 +- .../extension_image_trust_disabled.go | 2 +- pkg/meta/boltdb/boltdb.go | 28 +- pkg/meta/dynamodb/dynamodb.go | 15 +- pkg/meta/meta.go | 6 - pkg/meta/meta_test.go | 29 +- pkg/meta/signatures/cosign.go | 242 +++++++++--- pkg/meta/signatures/notation.go | 352 ++++++++++++++---- pkg/meta/signatures/signatures.go | 120 +++++- pkg/meta/signatures/signatures_test.go | 177 ++++++--- pkg/meta/types/types.go | 9 + pkg/test/mocks/repo_db_mock.go | 10 + 15 files changed, 791 insertions(+), 233 deletions(-) diff --git a/go.mod b/go.mod index 81cba5e7..fe9d6378 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,8 @@ require ( require ( github.com/aquasecurity/trivy v0.43.1 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.2 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.2 + github.com/aws/aws-secretsmanager-caching-go v1.1.2 github.com/containers/image/v5 v5.25.0 github.com/gobwas/glob v0.2.3 github.com/google/go-github/v52 v52.0.0 diff --git a/go.sum b/go.sum index 34553c11..927d1f75 100644 --- a/go.sum +++ b/go.sum @@ -405,6 +405,7 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.287/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go v1.44.322 h1:7JfwifGRGQMHd99PvfXqxBaZsjuRaOF6e3X9zRx2uYo= github.com/aws/aws-sdk-go v1.44.322/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250= @@ -466,6 +467,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EO github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU= github.com/aws/aws-sdk-go-v2/service/kms v1.23.0 h1:NXYeZBNg35rDBhcus60DFkIP7q6RNSkarLx+37ERX1g= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.2 h1:3x1Qilin49XQ1rK6pDNAfG+DmCFPfB7Rrpl+FUDAR/0= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.16.2/go.mod h1:HEBBc70BYi5eUvxBqC3xXjU/04NO96X/XNUe5qhC7Bc= github.com/aws/aws-sdk-go-v2/service/sso v1.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM= github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 h1:nneMBM2p79PGWBQovYO/6Xnc2ryRMw3InnDJq1FHkSY= github.com/aws/aws-sdk-go-v2/service/sso v1.12.12/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY= @@ -474,6 +477,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12/go.mod h1:E4VrHCPzmVB/KFXt github.com/aws/aws-sdk-go-v2/service/sts v1.6.0/go.mod h1:q7o0j7d7HrJk/vr9uUt3BVRASvcU7gYZB9PUgPiByXg= github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 h1:XFJ2Z6sNUUcAz9poj+245DMkrHE4h2j5I9/xD50RHfE= github.com/aws/aws-sdk-go-v2/service/sts v1.19.2/go.mod h1:dp0yLPsLBOi++WTxzCjA/oZqi6NPIhoR+uF7GeMU9eg= +github.com/aws/aws-secretsmanager-caching-go v1.1.2 h1:tY3pRhAkaohm75KFpGHoqjWrnRpznqrc8iX/wTLVpH0= +github.com/aws/aws-secretsmanager-caching-go v1.1.2/go.mod h1:s3Or+O0O8obPyDJz6875Rg1WApAbQ64L0WTBwYNnKLo= github.com/aws/smithy-go v1.6.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.11.0/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= @@ -1808,6 +1813,7 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2078,6 +2084,7 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2088,6 +2095,7 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2105,6 +2113,7 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 4f955856..e198eabd 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -193,7 +193,7 @@ func (rh *RouteHandler) SetupRoutes() { // Preconditions for enabling the actual extension routes are part of extensions themselves ext.SetupSearchRoutes(rh.c.Config, prefixedRouter, rh.c.StoreController, rh.c.MetaDB, rh.c.CveInfo, rh.c.Log) - ext.SetupImageTrustRoutes(rh.c.Config, prefixedRouter, rh.c.Log) + ext.SetupImageTrustRoutes(rh.c.Config, rh.c.MetaDB, prefixedRouter, rh.c.Log) ext.SetupMgmtRoutes(rh.c.Config, prefixedRouter, rh.c.Log) ext.SetupUserPreferencesRoutes(rh.c.Config, prefixedRouter, rh.c.MetaDB, rh.c.Log) // last should always be UI because it will setup a http.FileServer and paths will be resolved by this FileServer. diff --git a/pkg/extensions/extension_image_trust.go b/pkg/extensions/extension_image_trust.go index 19c41ceb..b33f9b3d 100644 --- a/pkg/extensions/extension_image_trust.go +++ b/pkg/extensions/extension_image_trust.go @@ -21,16 +21,11 @@ import ( "zotregistry.io/zot/pkg/scheduler" ) -const ( - ConfigResource = "config" - SignaturesResource = "signatures" -) - func IsBuiltWithImageTrustExtension() bool { return true } -func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, log log.Logger) { +func SetupImageTrustRoutes(conf *config.Config, metaDB mTypes.MetaDB, router *mux.Router, log log.Logger) { if !conf.IsImageTrustEnabled() || (!conf.IsCosignEnabled() && !conf.IsNotationEnabled()) { log.Info().Msg("skip enabling the image trust routes as the config prerequisites are not met") @@ -39,7 +34,8 @@ func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, log log.Logg log.Info().Msg("setting up image trust routes") - trust := ImageTrust{Conf: conf, Log: log} + sigStore, _ := metaDB.SignatureStorage().(*signatures.SigStore) + trust := ImageTrust{Conf: conf, SigStore: sigStore, Log: log} allowedMethods := zcommon.AllowedMethods(http.MethodPost) if conf.IsNotationEnabled() { @@ -70,8 +66,9 @@ func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, log log.Logg } type ImageTrust struct { - Conf *config.Config - Log log.Logger + Conf *config.Config + SigStore *signatures.SigStore + Log log.Logger } // Cosign handler godoc @@ -93,7 +90,7 @@ func (trust *ImageTrust) HandleCosignPublicKeyUpload(response http.ResponseWrite return } - err = signatures.UploadPublicKey(body) + err = signatures.UploadPublicKey(trust.SigStore.CosignStorage, body) if err != nil { if errors.Is(err, zerr.ErrInvalidPublicKeyContent) { response.WriteHeader(http.StatusBadRequest) @@ -151,7 +148,7 @@ func (trust *ImageTrust) HandleNotationCertificateUpload(response http.ResponseW return } - err = signatures.UploadCertificate(body, truststoreType, truststoreName) + err = signatures.UploadCertificate(trust.SigStore.NotationStorage, body, truststoreType, truststoreName) if err != nil { if errors.Is(err, zerr.ErrInvalidTruststoreType) || errors.Is(err, zerr.ErrInvalidTruststoreName) || @@ -178,6 +175,6 @@ func EnableImageTrustVerification(conf *config.Config, taskScheduler *scheduler. generator := signatures.NewTaskGenerator(metaDB, log) numberOfHours := 2 - interval := time.Duration(numberOfHours) * time.Minute + interval := time.Duration(numberOfHours) * time.Hour taskScheduler.SubmitGenerator(generator, interval, scheduler.MediumPriority) } diff --git a/pkg/extensions/extension_image_trust_disabled.go b/pkg/extensions/extension_image_trust_disabled.go index 0e123563..88730a6b 100644 --- a/pkg/extensions/extension_image_trust_disabled.go +++ b/pkg/extensions/extension_image_trust_disabled.go @@ -16,7 +16,7 @@ func IsBuiltWithImageTrustExtension() bool { return false } -func SetupImageTrustRoutes(config *config.Config, router *mux.Router, log log.Logger) { +func SetupImageTrustRoutes(config *config.Config, metaDB mTypes.MetaDB, router *mux.Router, log log.Logger) { log.Warn().Msg("skipping setting up image trust routes because given zot binary doesn't include this feature," + "please build a binary that does so") } diff --git a/pkg/meta/boltdb/boltdb.go b/pkg/meta/boltdb/boltdb.go index a0a6aaaf..2fb0d6f7 100644 --- a/pkg/meta/boltdb/boltdb.go +++ b/pkg/meta/boltdb/boltdb.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "path/filepath" "strings" "time" @@ -23,9 +24,10 @@ import ( ) type BoltDB struct { - DB *bbolt.DB - Patches []func(DB *bbolt.DB) error - Log log.Logger + DB *bbolt.DB + Patches []func(DB *bbolt.DB) error + SigStore *signatures.SigStore + Log log.Logger } func New(boltDB *bbolt.DB, log log.Logger) (*BoltDB, error) { @@ -71,13 +73,25 @@ func New(boltDB *bbolt.DB, log log.Logger) (*BoltDB, error) { return nil, err } + dir := filepath.Dir(boltDB.Path()) + + sigStore, err := signatures.NewLocalSigStore(dir) + if err != nil { + return nil, err + } + return &BoltDB{ - DB: boltDB, - Patches: version.GetBoltDBPatches(), - Log: log, + DB: boltDB, + Patches: version.GetBoltDBPatches(), + SigStore: sigStore, + Log: log, }, nil } +func (bdw *BoltDB) SignatureStorage() mTypes.SignatureStorage { + return bdw.SigStore +} + func (bdw *BoltDB) SetManifestData(manifestDigest godigest.Digest, manifestData mTypes.ManifestData) error { err := bdw.DB.Update(func(tx *bbolt.Tx) error { buck := tx.Bucket([]byte(ManifestDataBucket)) @@ -778,7 +792,7 @@ func (bdw *BoltDB) UpdateSignaturesValidity(repo string, manifestDigest godigest layersInfo := []mTypes.LayerInfo{} for _, layerInfo := range sigInfo.LayersInfo { - author, date, isTrusted, _ := signatures.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey, + author, date, isTrusted, _ := bdw.SigStore.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey, manifestDigest, blob, repo) if isTrusted { diff --git a/pkg/meta/dynamodb/dynamodb.go b/pkg/meta/dynamodb/dynamodb.go index d889cddc..6d3ab243 100644 --- a/pkg/meta/dynamodb/dynamodb.go +++ b/pkg/meta/dynamodb/dynamodb.go @@ -36,10 +36,16 @@ type DynamoDB struct { UserDataTablename string VersionTablename string Patches []func(client *dynamodb.Client, tableNames map[string]string) error + SigStore *signatures.SigStore Log log.Logger } func New(client *dynamodb.Client, params DBDriverParameters, log log.Logger) (*DynamoDB, error) { + sigStore, err := signatures.NewCloudSigStore(params.Region, params.Endpoint) + if err != nil { + return nil, err + } + dynamoWrapper := DynamoDB{ Client: client, RepoMetaTablename: params.RepoMetaTablename, @@ -49,10 +55,11 @@ func New(client *dynamodb.Client, params DBDriverParameters, log log.Logger) (*D UserDataTablename: params.UserDataTablename, APIKeyTablename: params.APIKeyTablename, Patches: version.GetDynamoDBPatches(), + SigStore: sigStore, Log: log, } - err := dynamoWrapper.createVersionTable() + err = dynamoWrapper.createVersionTable() if err != nil { return nil, err } @@ -86,6 +93,10 @@ func New(client *dynamodb.Client, params DBDriverParameters, log log.Logger) (*D return &dynamoWrapper, nil } +func (dwr *DynamoDB) SignatureStorage() mTypes.SignatureStorage { + return dwr.SigStore +} + func (dwr *DynamoDB) SetManifestData(manifestDigest godigest.Digest, manifestData mTypes.ManifestData) error { mdAttributeValue, err := attributevalue.Marshal(manifestData) if err != nil { @@ -658,7 +669,7 @@ func (dwr *DynamoDB) UpdateSignaturesValidity(repo string, manifestDigest godige layersInfo := []mTypes.LayerInfo{} for _, layerInfo := range sigInfo.LayersInfo { - author, date, isTrusted, _ := signatures.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey, + author, date, isTrusted, _ := dwr.SigStore.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey, manifestDigest, blob, repo) if isTrusted { diff --git a/pkg/meta/meta.go b/pkg/meta/meta.go index 2ded9f4d..7d47c78e 100644 --- a/pkg/meta/meta.go +++ b/pkg/meta/meta.go @@ -9,7 +9,6 @@ import ( "zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/meta/boltdb" mdynamodb "zotregistry.io/zot/pkg/meta/dynamodb" - "zotregistry.io/zot/pkg/meta/signatures" mTypes "zotregistry.io/zot/pkg/meta/types" ) @@ -33,11 +32,6 @@ func New(storageConfig config.StorageConfig, log log.Logger) (mTypes.MetaDB, err return nil, err } - err = signatures.InitCosignAndNotationDirs(params.RootDir) - if err != nil { - return nil, err - } - return Create("boltdb", driver, params, log) //nolint:contextcheck } diff --git a/pkg/meta/meta_test.go b/pkg/meta/meta_test.go index e63fced3..6f9d5cb8 100644 --- a/pkg/meta/meta_test.go +++ b/pkg/meta/meta_test.go @@ -6,6 +6,7 @@ import ( "math/rand" "os" "path" + "path/filepath" "testing" "time" @@ -59,7 +60,11 @@ func TestBoltDB(t *testing.T) { err = os.Chmod("repo.db", 0o600) So(err, ShouldBeNil) - defer os.Remove("repo.db") + defer func() { + os.Remove("repo.db") + os.RemoveAll("_cosign") + os.RemoveAll("_notation") + }() }) Convey("BoltDB Wrapper", t, func() { @@ -70,7 +75,13 @@ func TestBoltDB(t *testing.T) { log := log.NewLogger("debug", "") boltdbWrapper, err := boltdb.New(boltDriver, log) - defer os.Remove("repo.db") + + defer func() { + os.Remove("repo.db") + os.RemoveAll("_cosign") + os.RemoveAll("_notation") + }() + So(boltdbWrapper, ShouldNotBeNil) So(err, ShouldBeNil) @@ -1208,10 +1219,14 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func }) So(err, ShouldBeNil) - err = signatures.InitNotationDir(tdir) + notationDirPath, err := metaDB.(*boltdb.BoltDB).SigStore. + NotationStorage.(*signatures.CertificateLocalStorage).GetNotationDirPath() So(err, ShouldBeNil) - trustpolicyPath := path.Join(tdir, "_notation/trustpolicy.json") + notationDir, err := filepath.Abs(notationDirPath) + So(err, ShouldBeNil) + + trustpolicyPath := path.Join(notationDir, "trustpolicy.json") trustPolicy := ` { @@ -1239,13 +1254,13 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func _, err = file.WriteString(trustPolicy) So(err, ShouldBeNil) - truststore := "_notation/truststore/x509/ca/notation-sign-test" + truststore := "truststore/x509/ca/notation-sign-test" truststoreSrc := "notation/truststore/x509/ca/notation-sign-test" - err = os.MkdirAll(path.Join(tdir, truststore), 0o755) + err = os.MkdirAll(path.Join(notationDir, truststore), 0o755) So(err, ShouldBeNil) err = test.CopyFile(path.Join(tdir, truststoreSrc, "notation-sign-test.crt"), - path.Join(tdir, truststore, "notation-sign-test.crt")) + path.Join(notationDir, truststore, "notation-sign-test.crt")) So(err, ShouldBeNil) err = metaDB.UpdateSignaturesValidity(repo, manifestDigest) //nolint:contextcheck diff --git a/pkg/meta/signatures/cosign.go b/pkg/meta/signatures/cosign.go index 85560158..1e0bd5e1 100644 --- a/pkg/meta/signatures/cosign.go +++ b/pkg/meta/signatures/cosign.go @@ -10,10 +10,14 @@ import ( "os" "path" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" + "github.com/aws/aws-secretsmanager-caching-go/secretcache" godigest "github.com/opencontainers/go-digest" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore/pkg/cryptoutils" + sigstoreSigs "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/options" zerr "zotregistry.io/zot/errors" @@ -24,116 +28,242 @@ const ( cosignDirRelativePath = "_cosign" ) -var cosignDir = "" //nolint:gochecknoglobals +type PublicKeyLocalStorage struct { + cosignDir string +} -func InitCosignDir(rootDir string) error { +type publicKeyCloudStorage struct { + secretsManagerClient *secretsmanager.Client + secretsManagerCache *secretcache.Cache +} + +type publicKeyStorage interface { + StorePublicKey(name godigest.Digest, publicKeyContent []byte) error + GetPublicKeyVerifier(name string) (sigstoreSigs.Verifier, []byte, error) + GetPublicKeys() ([]string, error) +} + +func NewPublicKeyLocalStorage(rootDir string) (*PublicKeyLocalStorage, error) { dir := path.Join(rootDir, cosignDirRelativePath) _, err := os.Stat(dir) if os.IsNotExist(err) { err = os.MkdirAll(dir, defaultDirPerms) if err != nil { - return err + return nil, err } } - if err == nil { - cosignDir = dir + if err != nil { + return nil, err } - return err + return &PublicKeyLocalStorage{ + cosignDir: dir, + }, nil } -func GetCosignDirPath() (string, error) { - if cosignDir != "" { - return cosignDir, nil +func NewPublicKeyCloudStorage( + secretsManagerClient *secretsmanager.Client, secretsManagerCache *secretcache.Cache, +) *publicKeyCloudStorage { + return &publicKeyCloudStorage{ + secretsManagerClient: secretsManagerClient, + secretsManagerCache: secretsManagerCache, + } +} + +func (local *PublicKeyLocalStorage) GetCosignDirPath() (string, error) { + if local.cosignDir != "" { + return local.cosignDir, nil } return "", zerr.ErrSignConfigDirNotSet } func VerifyCosignSignature( - repo string, digest godigest.Digest, signatureKey string, layerContent []byte, + cosignStorage publicKeyStorage, repo string, digest godigest.Digest, signatureKey string, layerContent []byte, ) (string, bool, error) { - cosignDir, err := GetCosignDirPath() + publicKeys, err := cosignStorage.GetPublicKeys() if err != nil { return "", false, err } - files, err := os.ReadDir(cosignDir) - if err != nil { - return "", false, err - } + for _, publicKey := range publicKeys { + // cosign verify the image + pubKeyVerifier, pubKeyContent, err := cosignStorage.GetPublicKeyVerifier(publicKey) + if err != nil { + continue + } - for _, file := range files { - if !file.IsDir() { - // cosign verify the image - ctx := context.Background() - keyRef := path.Join(cosignDir, file.Name()) - hashAlgorithm := crypto.SHA256 + pkcs11Key, ok := pubKeyVerifier.(*pkcs11key.Key) + if ok { + defer pkcs11Key.Close() + } - pubKey, err := sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, hashAlgorithm) - if err != nil { - continue - } + verifier := pubKeyVerifier - pkcs11Key, ok := pubKey.(*pkcs11key.Key) - if ok { - defer pkcs11Key.Close() - } + b64sig := signatureKey - verifier := pubKey + signature, err := base64.StdEncoding.DecodeString(b64sig) + if err != nil { + continue + } - b64sig := signatureKey + compressed := io.NopCloser(bytes.NewReader(layerContent)) - signature, err := base64.StdEncoding.DecodeString(b64sig) - if err != nil { - continue - } + payload, err := io.ReadAll(compressed) + if err != nil { + continue + } - compressed := io.NopCloser(bytes.NewReader(layerContent)) + err = verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), + options.WithContext(context.Background())) - payload, err := io.ReadAll(compressed) - if err != nil { - continue - } - - err = verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx)) - - if err == nil { - publicKey, err := os.ReadFile(keyRef) - if err != nil { - continue - } - - return string(publicKey), true, nil - } + if err == nil { + return string(pubKeyContent), true, nil } } return "", false, nil } -func UploadPublicKey(publicKeyContent []byte) error { +func (local *PublicKeyLocalStorage) GetPublicKeyVerifier(fileName string) (sigstoreSigs.Verifier, []byte, error) { + cosignDir, err := local.GetCosignDirPath() + if err != nil { + return nil, []byte{}, err + } + + ctx := context.Background() + keyRef := path.Join(cosignDir, fileName) + hashAlgorithm := crypto.SHA256 + + pubKeyContent, err := os.ReadFile(keyRef) + if err != nil { + return nil, nil, err + } + + pubKey, err := sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, hashAlgorithm) + if err != nil { + return nil, nil, err + } + + return pubKey, pubKeyContent, nil +} + +func (cloud *publicKeyCloudStorage) GetPublicKeyVerifier(secretName string) (sigstoreSigs.Verifier, []byte, error) { + hashAlgorithm := crypto.SHA256 + + // get key + raw, err := cloud.secretsManagerCache.GetSecretString(secretName) + if err != nil { + return nil, nil, err + } + + rawDecoded, err := base64.StdEncoding.DecodeString(raw) + if err != nil { + return nil, nil, err + } + + // PEM encoded file. + key, err := cryptoutils.UnmarshalPEMToPublicKey(rawDecoded) + if err != nil { + return nil, nil, err + } + + pubKey, err := sigstoreSigs.LoadVerifier(key, hashAlgorithm) + if err != nil { + return nil, nil, err + } + + return pubKey, rawDecoded, nil +} + +func (local *PublicKeyLocalStorage) GetPublicKeys() ([]string, error) { + cosignDir, err := local.GetCosignDirPath() + if err != nil { + return []string{}, err + } + + files, err := os.ReadDir(cosignDir) + if err != nil { + return []string{}, err + } + + publicKeys := []string{} + for _, file := range files { + publicKeys = append(publicKeys, file.Name()) + } + + return publicKeys, nil +} + +func (cloud *publicKeyCloudStorage) GetPublicKeys() ([]string, error) { + ctx := context.Background() + listSecretsInput := secretsmanager.ListSecretsInput{ + Filters: []types.Filter{ + { + Key: types.FilterNameStringTypeDescription, + Values: []string{"cosign public key"}, + }, + }, + } + + secrets, err := cloud.secretsManagerClient.ListSecrets(ctx, &listSecretsInput) + if err != nil { + return []string{}, err + } + + publicKeys := []string{} + + for _, secret := range secrets.SecretList { + publicKeys = append(publicKeys, *(secret.Name)) + } + + return publicKeys, nil +} + +func UploadPublicKey(cosignStorage publicKeyStorage, publicKeyContent []byte) error { // validate public key if ok, err := validatePublicKey(publicKeyContent); !ok { return err } + name := godigest.FromBytes(publicKeyContent) + + return cosignStorage.StorePublicKey(name, publicKeyContent) +} + +func (local *PublicKeyLocalStorage) StorePublicKey(name godigest.Digest, publicKeyContent []byte) error { // add public key to "{rootDir}/_cosign/{name.pub}" - configDir, err := GetCosignDirPath() + cosignDir, err := local.GetCosignDirPath() if err != nil { return err } - name := godigest.FromBytes(publicKeyContent) - // store public key - publicKeyPath := path.Join(configDir, name.String()) + publicKeyPath := path.Join(cosignDir, name.String()) return os.WriteFile(publicKeyPath, publicKeyContent, defaultFilePerms) } +func (cloud *publicKeyCloudStorage) StorePublicKey(name godigest.Digest, publicKeyContent []byte) error { + n := name.Encoded() + description := "cosign public key" + secret := base64.StdEncoding.EncodeToString(publicKeyContent) + secretInputParam := &secretsmanager.CreateSecretInput{ + Name: &n, + Description: &description, + SecretString: &secret, + } + + _, err := cloud.secretsManagerClient.CreateSecret(context.Background(), secretInputParam) + if err != nil { + return err + } + + return nil +} + func validatePublicKey(publicKeyContent []byte) (bool, error) { _, err := cryptoutils.UnmarshalPEMToPublicKey(publicKeyContent) if err != nil { diff --git a/pkg/meta/signatures/notation.go b/pkg/meta/signatures/notation.go index 6f195cdf..0178b538 100644 --- a/pkg/meta/signatures/notation.go +++ b/pkg/meta/signatures/notation.go @@ -1,8 +1,10 @@ package signatures import ( + "bytes" "context" "crypto/x509" + "encoding/base64" "encoding/json" "encoding/pem" "errors" @@ -14,6 +16,9 @@ import ( "sync" "time" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" + "github.com/aws/aws-secretsmanager-caching-go/secretcache" _ "github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" @@ -29,34 +34,73 @@ import ( const notationDirRelativePath = "_notation" -var ( - notationDir = "" //nolint:gochecknoglobals - TrustpolicyLock = new(sync.Mutex) //nolint: gochecknoglobals -) +var TrustpolicyLock = new(sync.Mutex) //nolint: gochecknoglobals -func InitNotationDir(rootDir string) error { +type CertificateLocalStorage struct { + notationDir string +} + +type certificateCloudStorage struct { + secretsManagerClient *secretsmanager.Client + secretsManagerCache *secretcache.Cache +} + +type certificateStorage interface { + LoadTrustPolicyDocument() (*trustpolicy.Document, error) + StoreCertificate(certificateContent []byte, truststoreType, truststoreName string) error + UpdateTrustPolicyDocument(trustpolicyDocContent []byte) error + GetVerifier(policyDoc *trustpolicy.Document) (notation.Verifier, error) + InitTrustpolicy(trustpolicy []byte) error +} + +func NewCertificateLocalStorage(rootDir string) (*CertificateLocalStorage, error) { dir := path.Join(rootDir, notationDirRelativePath) _, err := os.Stat(dir) if os.IsNotExist(err) { err = os.MkdirAll(dir, defaultDirPerms) if err != nil { - return err + return nil, err } } - if err == nil { - notationDir = dir - - if _, err := LoadTrustPolicyDocument(notationDir); os.IsNotExist(err) { - return InitTrustpolicyFile(notationDir) - } + if err != nil { + return nil, err } - return err + certStorage := &CertificateLocalStorage{ + notationDir: dir, + } + + _, err = certStorage.LoadTrustPolicyDocument() + if os.IsNotExist(err) { + if err := InitTrustpolicyFile(certStorage); err != nil { + return nil, err + } + } else if err != nil { + return nil, err + } + + return certStorage, nil } -func InitTrustpolicyFile(configDir string) error { +func NewCertificateCloudStorage( + secretsManagerClient *secretsmanager.Client, secretsManagerCache *secretcache.Cache, +) (*certificateCloudStorage, error) { + certStorage := &certificateCloudStorage{ + secretsManagerClient: secretsManagerClient, + secretsManagerCache: secretsManagerCache, + } + + err := InitTrustpolicyFile(certStorage) + if err != nil { + return nil, err + } + + return certStorage, nil +} + +func InitTrustpolicyFile(notationStorage certificateStorage) error { // according to https://github.com/notaryproject/notation/blob/main/specs/commandline/verify.md // the value of signatureVerification.level field from trustpolicy.json file // could be one of these values: `strict`, `permissive`, `audit` or `skip` @@ -65,40 +109,123 @@ func InitTrustpolicyFile(configDir string) error { // a certificate that verifies a signature, but that certificate has expired, then the // signature is not trusted; if this field were set to `permissive` then the // signature would be trusted) - trustPolicy := ` - { - "version": "1.0", - "trustPolicies": [ - { - "name": "default-config", - "registryScopes": [ "*" ], - "signatureVerification": { - "level" : "strict" - }, - "trustStores": [], - "trustedIdentities": [ - "*" - ] - } - ] - }` + trustPolicy := `{ + "version": "1.0", + "trustPolicies": [ + { + "name": "default-config", + "registryScopes": [ "*" ], + "signatureVerification": { + "level" : "strict" + }, + "trustStores": [], + "trustedIdentities": [ + "*" + ] + } + ] +}` + return notationStorage.InitTrustpolicy([]byte(trustPolicy)) +} + +func (local *CertificateLocalStorage) InitTrustpolicy(trustpolicy []byte) error { TrustpolicyLock.Lock() defer TrustpolicyLock.Unlock() - return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), []byte(trustPolicy), defaultDirPerms) + notationDir, err := local.GetNotationDirPath() + if err != nil { + return err + } + + return os.WriteFile(path.Join(notationDir, dir.PathTrustPolicy), trustpolicy, defaultDirPerms) } -func GetNotationDirPath() (string, error) { - if notationDir != "" { - return notationDir, nil +func (cloud *certificateCloudStorage) InitTrustpolicy(trustpolicy []byte) error { + name := "trustpolicy" + description := "notation trustpolicy file" + secret := base64.StdEncoding.EncodeToString(trustpolicy) + secretInputParam := &secretsmanager.CreateSecretInput{ + Name: &name, + Description: &description, + SecretString: &secret, + } + + _, err := cloud.secretsManagerClient.CreateSecret(context.Background(), secretInputParam) + + return err +} + +func (local *CertificateLocalStorage) GetNotationDirPath() (string, error) { + if local.notationDir != "" { + return local.notationDir, nil } return "", zerr.ErrSignConfigDirNotSet } +func (cloud *certificateCloudStorage) GetCertificates( + ctx context.Context, storeType truststore.Type, namedStore string, +) ([]*x509.Certificate, error) { + certificates := []*x509.Certificate{} + + if !validateTruststoreType(string(storeType)) { + return []*x509.Certificate{}, zerr.ErrInvalidTruststoreType + } + + if !validateTruststoreName(namedStore) { + return []*x509.Certificate{}, zerr.ErrInvalidTruststoreName + } + + listSecretsInput := secretsmanager.ListSecretsInput{ + Filters: []types.Filter{ + { + Key: types.FilterNameStringTypeName, + Values: []string{path.Join(string(storeType), namedStore)}, + }, + }, + } + + secrets, err := cloud.secretsManagerClient.ListSecrets(ctx, &listSecretsInput) + if err != nil { + return []*x509.Certificate{}, err + } + + for _, secret := range secrets.SecretList { + // get key + raw, err := cloud.secretsManagerCache.GetSecretString(*(secret.Name)) + if err != nil { + return []*x509.Certificate{}, err + } + + rawDecoded, err := base64.StdEncoding.DecodeString(raw) + if err != nil { + return []*x509.Certificate{}, err + } + + certs, _, err := parseAndValidateCertificateContent(rawDecoded) + if err != nil { + return []*x509.Certificate{}, err + } + + err = truststore.ValidateCertificates(certs) + if err != nil { + return []*x509.Certificate{}, err + } + + certificates = append(certificates, certs...) + } + + return certificates, nil +} + // Equivalent function for trustpolicy.LoadDocument() but using a specific SysFS not the one returned by ConfigFS(). -func LoadTrustPolicyDocument(notationDir string) (*trustpolicy.Document, error) { +func (local *CertificateLocalStorage) LoadTrustPolicyDocument() (*trustpolicy.Document, error) { + notationDir, err := local.GetNotationDirPath() + if err != nil { + return nil, err + } + jsonFile, err := dir.NewSysFS(notationDir).Open(dir.PathTrustPolicy) if err != nil { return nil, err @@ -116,33 +243,69 @@ func LoadTrustPolicyDocument(notationDir string) (*trustpolicy.Document, error) return policyDocument, nil } -// NewFromConfig returns a verifier based on local file system. -// Equivalent function for verifier.NewFromConfig() -// but using LoadTrustPolicyDocumnt() function instead of trustpolicy.LoadDocument() function. -func NewFromConfig() (notation.Verifier, error) { - notationDir, err := GetNotationDirPath() +func (cloud *certificateCloudStorage) LoadTrustPolicyDocument() (*trustpolicy.Document, error) { + policyDocument := &trustpolicy.Document{} + + raw, err := cloud.secretsManagerCache.GetSecretString("trustpolicy") if err != nil { return nil, err } + rawDecoded, err := base64.StdEncoding.DecodeString(raw) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + + err = json.Compact(&buf, rawDecoded) + if err != nil { + return nil, err + } + + err = json.Unmarshal(buf.Bytes(), policyDocument) + if err != nil { + return nil, err + } + + return policyDocument, nil +} + +// NewFromConfig returns a verifier based on local file system. +// Equivalent function for verifier.NewFromConfig() +// but using LoadTrustPolicyDocumnt() function instead of trustpolicy.LoadDocument() function. +func NewFromConfig(notationStorage certificateStorage) (notation.Verifier, error) { // Load trust policy. TrustpolicyLock.Lock() defer TrustpolicyLock.Unlock() - policyDocument, err := LoadTrustPolicyDocument(notationDir) + policyDocument, err := notationStorage.LoadTrustPolicyDocument() if err != nil { return nil, err } - // Load trust store. + return notationStorage.GetVerifier(policyDocument) +} + +func (local *CertificateLocalStorage) GetVerifier(policyDoc *trustpolicy.Document) (notation.Verifier, error) { + notationDir, err := local.GetNotationDirPath() + if err != nil { + return nil, err + } x509TrustStore := truststore.NewX509TrustStore(dir.NewSysFS(notationDir)) - return verifier.New(policyDocument, x509TrustStore, + return verifier.New(policyDoc, x509TrustStore, plugin.NewCLIManager(dir.NewSysFS(path.Join(notationDir, dir.PathPlugins)))) } +func (cloud *certificateCloudStorage) GetVerifier(policyDoc *trustpolicy.Document) (notation.Verifier, error) { + return verifier.New(policyDoc, cloud, + plugin.NewCLIManager(dir.NewSysFS(path.Join(dir.PathPlugins)))) +} + func VerifyNotationSignature( - artifactDescriptor ispec.Descriptor, artifactReference string, rawSignature []byte, signatureMediaType string, + notationStorage certificateStorage, artifactDescriptor ispec.Descriptor, artifactReference string, + rawSignature []byte, signatureMediaType string, ) (string, time.Time, bool, error) { var ( date time.Time @@ -158,7 +321,7 @@ func VerifyNotationSignature( } // Initialize verifier. - verifier, err := NewFromConfig() + verifier, err := NewFromConfig(notationStorage) if err != nil { return author, date, false, err } @@ -216,7 +379,9 @@ func CheckExpiryErr(verificationResults []*notation.ValidationResult, notAfter t return false } -func UploadCertificate(certificateContent []byte, truststoreType, truststoreName string) error { +func UploadCertificate( + notationStorage certificateStorage, certificateContent []byte, truststoreType, truststoreName string, +) error { // validate truststore type if !validateTruststoreType(truststoreType) { return zerr.ErrInvalidTruststoreType @@ -228,35 +393,22 @@ func UploadCertificate(certificateContent []byte, truststoreType, truststoreName } // validate certificate - if ok, err := validateCertificate(certificateContent); !ok { + if _, ok, err := parseAndValidateCertificateContent(certificateContent); !ok { return err } - // add certificate to "{rootDir}/_notation/truststore/x509/{type}/{name}/{name.crt}" - configDir, err := GetNotationDirPath() - if err != nil { - return err - } - - name := godigest.FromBytes(certificateContent) - // store certificate - truststorePath := path.Join(configDir, dir.TrustStoreDir, "x509", truststoreType, truststoreName, name.String()) - - if err := os.MkdirAll(filepath.Dir(truststorePath), defaultDirPerms); err != nil { - return err - } - - err = os.WriteFile(truststorePath, certificateContent, defaultFilePerms) + err := notationStorage.StoreCertificate(certificateContent, truststoreType, truststoreName) if err != nil { return err } + // update "trustpolicy.json" file // add certificate to "trustpolicy.json" TrustpolicyLock.Lock() defer TrustpolicyLock.Unlock() - trustpolicyDoc, err := LoadTrustPolicyDocument(configDir) + trustpolicyDoc, err := notationStorage.LoadTrustPolicyDocument() if err != nil { return err } @@ -276,7 +428,67 @@ func UploadCertificate(certificateContent []byte, truststoreType, truststoreName return err } - return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), trustpolicyDocContent, defaultFilePerms) + return notationStorage.UpdateTrustPolicyDocument(trustpolicyDocContent) +} + +func (local *CertificateLocalStorage) StoreCertificate(certificateContent []byte, + truststoreType, truststoreName string, +) error { + // add certificate to "{rootDir}/_notation/truststore/x509/{type}/{name}/{name.crt}" + configDir, err := local.GetNotationDirPath() + if err != nil { + return err + } + + name := godigest.FromBytes(certificateContent) + + // store certificate + truststorePath := path.Join(configDir, dir.TrustStoreDir, "x509", truststoreType, truststoreName, name.String()) + + if err := os.MkdirAll(filepath.Dir(truststorePath), defaultDirPerms); err != nil { + return err + } + + return os.WriteFile(truststorePath, certificateContent, defaultFilePerms) +} + +func (local *CertificateLocalStorage) UpdateTrustPolicyDocument(content []byte) error { + configDir, err := local.GetNotationDirPath() + if err != nil { + return err + } + + return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), content, defaultFilePerms) +} + +func (cloud *certificateCloudStorage) StoreCertificate(certificateContent []byte, + truststoreType, truststoreName string, +) error { + name := path.Join(truststoreType, truststoreName, godigest.FromBytes(certificateContent).Encoded()) + description := "notation certificate" + secret := base64.StdEncoding.EncodeToString(certificateContent) + secretInputParam := &secretsmanager.CreateSecretInput{ + Name: &name, + Description: &description, + SecretString: &secret, + } + + _, err := cloud.secretsManagerClient.CreateSecret(context.Background(), secretInputParam) + + return err +} + +func (cloud *certificateCloudStorage) UpdateTrustPolicyDocument(content []byte) error { + trustpolicyName := "trustpolicy" + trustpolicySecret := base64.StdEncoding.EncodeToString(content) + trustpolicySecretInputParam := &secretsmanager.UpdateSecretInput{ + SecretId: &trustpolicyName, + SecretString: &trustpolicySecret, + } + + _, err := cloud.secretsManagerClient.UpdateSecret(context.Background(), trustpolicySecretInputParam) + + return err } func validateTruststoreType(truststoreType string) bool { @@ -294,7 +506,7 @@ func validateTruststoreName(truststoreName string) bool { } // implementation from https://github.com/notaryproject/notation-core-go/blob/main/x509/cert.go#L20 -func validateCertificate(certificateContent []byte) (bool, error) { +func parseAndValidateCertificateContent(certificateContent []byte) ([]*x509.Certificate, bool, error) { var certs []*x509.Certificate block, rest := pem.Decode(certificateContent) @@ -302,7 +514,7 @@ func validateCertificate(certificateContent []byte) (bool, error) { // data may be in DER format derCerts, err := x509.ParseCertificates(certificateContent) if err != nil { - return false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err) + return []*x509.Certificate{}, false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err) } certs = append(certs, derCerts...) @@ -311,7 +523,7 @@ func validateCertificate(certificateContent []byte) (bool, error) { for block != nil { cert, err := x509.ParseCertificate(block.Bytes) if err != nil { - return false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err) + return []*x509.Certificate{}, false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err) } certs = append(certs, cert) block, rest = pem.Decode(rest) @@ -319,9 +531,9 @@ func validateCertificate(certificateContent []byte) (bool, error) { } if len(certs) == 0 { - return false, fmt.Errorf("%w: no valid certificates found in payload", + return []*x509.Certificate{}, false, fmt.Errorf("%w: no valid certificates found in payload", zerr.ErrInvalidCertificateContent) } - return true, nil + return certs, true, nil } diff --git a/pkg/meta/signatures/signatures.go b/pkg/meta/signatures/signatures.go index 2f07e826..16d9cedb 100644 --- a/pkg/meta/signatures/signatures.go +++ b/pkg/meta/signatures/signatures.go @@ -5,6 +5,14 @@ import ( "encoding/json" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + aws1 "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/aws/aws-sdk-go/aws/session" + smanager "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/aws/aws-secretsmanager-caching-go/secretcache" godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -21,18 +29,106 @@ const ( defaultFilePerms = 0o644 ) -func InitCosignAndNotationDirs(rootDir string) error { - err := InitCosignDir(rootDir) - if err != nil { - return err - } - - err = InitNotationDir(rootDir) - - return err +type SigStore struct { + CosignStorage publicKeyStorage + NotationStorage certificateStorage } -func VerifySignature( +func NewLocalSigStore(rootDir string) (*SigStore, error) { + publicKeyStorage, err := NewPublicKeyLocalStorage(rootDir) + if err != nil { + return nil, err + } + + certStorage, err := NewCertificateLocalStorage(rootDir) + if err != nil { + return nil, err + } + + return &SigStore{ + CosignStorage: publicKeyStorage, + NotationStorage: certStorage, + }, nil +} + +func NewCloudSigStore(region, endpoint string) (*SigStore, error) { + secretsManagerClient, err := GetSecretsManagerClient(region, endpoint) + if err != nil { + return nil, err + } + + secretsManagerCache, err := GetSecretsManagerRetrieval(region, endpoint) + if err != nil { + return nil, err + } + + publicKeyStorage := NewPublicKeyCloudStorage(secretsManagerClient, secretsManagerCache) + + certStorage, err := NewCertificateCloudStorage(secretsManagerClient, secretsManagerCache) + if err != nil { + return nil, err + } + + return &SigStore{ + CosignStorage: publicKeyStorage, + NotationStorage: certStorage, + }, nil +} + +func GetSecretsManagerClient(region, endpoint string) (*secretsmanager.Client, error) { + customResolver := aws.EndpointResolverWithOptionsFunc( + func(service, region string, options ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{ + PartitionID: "aws", + URL: endpoint, + SigningRegion: region, + }, nil + }) + + // Using the SDK's default configuration, loading additional config + // and credentials values from the environment variables, shared + // credentials, and shared configuration files + cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region), + config.WithEndpointResolverWithOptions(customResolver)) + if err != nil { + return nil, err + } + + return secretsmanager.NewFromConfig(cfg), nil +} + +func GetSecretsManagerRetrieval(region, endpoint string) (*secretcache.Cache, error) { + endpointFunc := func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) { + return endpoints.ResolvedEndpoint{ + PartitionID: "aws", + URL: endpoint, + SigningRegion: region, + }, nil + } + customResolver := endpoints.ResolverFunc(endpointFunc) + + cfg := aws1.NewConfig().WithRegion(region).WithEndpointResolver(customResolver) + + newSession := session.Must(session.NewSession()) + + client := smanager.New(newSession, cfg) + // Create a custom CacheConfig struct + config := secretcache.CacheConfig{ + MaxCacheSize: secretcache.DefaultMaxCacheSize, + VersionStage: secretcache.DefaultVersionStage, + CacheItemTTL: secretcache.DefaultCacheItemTTL, + } + + // Instantiate the cache + cache, _ := secretcache.New( + func(c *secretcache.Cache) { c.CacheConfig = config }, + func(c *secretcache.Cache) { c.Client = client }, + ) + + return cache, nil +} + +func (sigStore *SigStore) VerifySignature( signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, manifestContent []byte, repo string, ) (string, time.Time, bool, error) { @@ -53,11 +149,11 @@ func VerifySignature( switch signatureType { case CosignSignature: - author, isValid, err := VerifyCosignSignature(repo, manifestDigest, sigKey, rawSignature) + author, isValid, err := VerifyCosignSignature(sigStore.CosignStorage, repo, manifestDigest, sigKey, rawSignature) return author, time.Time{}, isValid, err case NotationSignature: - return VerifyNotationSignature(desc, manifestDigest.String(), rawSignature, sigKey) + return VerifyNotationSignature(sigStore.NotationStorage, desc, manifestDigest.String(), rawSignature, sigKey) default: return "", time.Time{}, false, zerr.ErrInvalidSignatureType } diff --git a/pkg/meta/signatures/signatures_test.go b/pkg/meta/signatures/signatures_test.go index 6a711774..48cef545 100644 --- a/pkg/meta/signatures/signatures_test.go +++ b/pkg/meta/signatures/signatures_test.go @@ -33,16 +33,17 @@ func TestInitCosignAndNotationDirs(t *testing.T) { err := os.Chmod(dir, 0o000) So(err, ShouldBeNil) - err = signatures.InitCosignAndNotationDirs(dir) + _, err = signatures.NewPublicKeyLocalStorage(dir) So(err, ShouldNotBeNil) err = os.Chmod(dir, 0o500) So(err, ShouldBeNil) - err = signatures.InitCosignAndNotationDirs(dir) + _, err = signatures.NewPublicKeyLocalStorage(dir) So(err, ShouldNotBeNil) - cosignDir, err := signatures.GetCosignDirPath() + pubKeyStorage := &signatures.PublicKeyLocalStorage{} + cosignDir, err := pubKeyStorage.GetCosignDirPath() So(cosignDir, ShouldBeEmpty) So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet) @@ -53,22 +54,23 @@ func TestInitCosignAndNotationDirs(t *testing.T) { err := os.Chmod(dir, 0o000) So(err, ShouldBeNil) - err = signatures.InitCosignAndNotationDirs(dir) + _, err = signatures.NewPublicKeyLocalStorage(dir) So(err, ShouldNotBeNil) - err = signatures.InitNotationDir(dir) + _, err = signatures.NewCertificateLocalStorage(dir) So(err, ShouldNotBeNil) err = os.Chmod(dir, 0o500) So(err, ShouldBeNil) - err = signatures.InitCosignAndNotationDirs(dir) + _, err = signatures.NewPublicKeyLocalStorage(dir) So(err, ShouldNotBeNil) - err = signatures.InitNotationDir(dir) + _, err = signatures.NewCertificateLocalStorage(dir) So(err, ShouldNotBeNil) - notationDir, err := signatures.GetNotationDirPath() + certStorgae := &signatures.CertificateLocalStorage{} + notationDir, err := certStorgae.GetNotationDirPath() So(notationDir, ShouldBeEmpty) So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet) @@ -90,7 +92,8 @@ func TestInitCosignAndNotationDirs(t *testing.T) { So(err, ShouldBeNil) So(certificateContent, ShouldNotBeNil) - err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test") + certStorgae := &signatures.CertificateLocalStorage{} + err = signatures.UploadCertificate(certStorgae, certificateContent, "ca", "notation-upload-test") So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet) }) @@ -114,7 +117,8 @@ func TestInitCosignAndNotationDirs(t *testing.T) { So(err, ShouldBeNil) So(publicKeyContent, ShouldNotBeNil) - err = signatures.UploadPublicKey(publicKeyContent) + pubKeyStorage := &signatures.PublicKeyLocalStorage{} + err = signatures.UploadPublicKey(pubKeyStorage, publicKeyContent) So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet) }) @@ -124,7 +128,8 @@ func TestVerifySignatures(t *testing.T) { Convey("wrong manifest content", t, func() { manifestContent := []byte("wrong json") - _, _, _, err := signatures.VerifySignature("", []byte(""), "", "", manifestContent, "repo") + sigStore := &signatures.SigStore{} + _, _, _, err := sigStore.VerifySignature("", []byte(""), "", "", manifestContent, "repo") So(err, ShouldNotBeNil) }) @@ -135,7 +140,8 @@ func TestVerifySignatures(t *testing.T) { manifestContent, err := json.Marshal(image.Manifest) So(err, ShouldBeNil) - _, _, _, err = signatures.VerifySignature("", []byte(""), "", "", manifestContent, "repo") + sigStore := &signatures.SigStore{} + _, _, _, err = sigStore.VerifySignature("", []byte(""), "", "", manifestContent, "repo") So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrBadManifestDigest) }) @@ -149,7 +155,8 @@ func TestVerifySignatures(t *testing.T) { manifestDigest := image.Digest() - _, _, _, err = signatures.VerifySignature("wrongType", []byte(""), "", manifestDigest, manifestContent, "repo") + sigStore := &signatures.SigStore{} + _, _, _, err = sigStore.VerifySignature("wrongType", []byte(""), "", manifestDigest, manifestContent, "repo") So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrInvalidSignatureType) }) @@ -166,7 +173,11 @@ func TestVerifySignatures(t *testing.T) { manifestDigest := image.Digest() Convey("cosignDir is not set", func() { - _, _, _, err = signatures.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo) + sigStore := &signatures.SigStore{ + CosignStorage: &signatures.PublicKeyLocalStorage{}, + } + + _, _, _, err = sigStore.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo) So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet) }) @@ -174,31 +185,39 @@ func TestVerifySignatures(t *testing.T) { Convey("cosignDir does not have read permissions", func() { dir := t.TempDir() - err := signatures.InitCosignDir(dir) + pubKeyStorage, err := signatures.NewPublicKeyLocalStorage(dir) So(err, ShouldBeNil) - cosignDir, err := signatures.GetCosignDirPath() + cosignDir, err := pubKeyStorage.GetCosignDirPath() So(err, ShouldBeNil) err = os.Chmod(cosignDir, 0o300) So(err, ShouldBeNil) - _, _, _, err = signatures.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo) + sigStore := &signatures.SigStore{ + CosignStorage: pubKeyStorage, + } + + _, _, _, err = sigStore.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo) So(err, ShouldNotBeNil) }) Convey("no valid public key", func() { dir := t.TempDir() - err := signatures.InitCosignDir(dir) + pubKeyStorage, err := signatures.NewPublicKeyLocalStorage(dir) So(err, ShouldBeNil) - cosignDir, err := signatures.GetCosignDirPath() + cosignDir, err := pubKeyStorage.GetCosignDirPath() So(err, ShouldBeNil) err = test.WriteFileWithPermission(path.Join(cosignDir, "file"), []byte("not a public key"), 0o600, false) So(err, ShouldBeNil) - _, _, isTrusted, err := signatures.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo) + sigStore := &signatures.SigStore{ + CosignStorage: pubKeyStorage, + } + + _, _, isTrusted, err := sigStore.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo) So(err, ShouldBeNil) So(isTrusted, ShouldBeFalse) }) @@ -221,10 +240,10 @@ func TestVerifySignatures(t *testing.T) { err := test.UploadImage(image, baseURL, repo, tag) So(err, ShouldBeNil) - err = signatures.InitCosignDir(rootDir) + pubKeyStorage, err := signatures.NewPublicKeyLocalStorage(rootDir) So(err, ShouldBeNil) - cosignDir, err := signatures.GetCosignDirPath() + cosignDir, err := pubKeyStorage.GetCosignDirPath() So(err, ShouldBeNil) cwd, err := os.Getwd() @@ -280,8 +299,12 @@ func TestVerifySignatures(t *testing.T) { } } + sigStore := &signatures.SigStore{ + CosignStorage: pubKeyStorage, + } + // signature is trusted - author, _, isTrusted, err := signatures.VerifySignature("cosign", rawSignature, sigKey, manifestDigest, + author, _, isTrusted, err := sigStore.VerifySignature("cosign", rawSignature, sigKey, manifestDigest, manifestContent, repo) So(err, ShouldBeNil) So(isTrusted, ShouldBeTrue) @@ -301,7 +324,11 @@ func TestVerifySignatures(t *testing.T) { manifestDigest := image.Digest() Convey("notationDir is not set", func() { - _, _, _, err = signatures.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo) + sigStore := &signatures.SigStore{ + NotationStorage: &signatures.CertificateLocalStorage{}, + } + + _, _, _, err = sigStore.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo) So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet) }) @@ -309,10 +336,14 @@ func TestVerifySignatures(t *testing.T) { Convey("no signature provided", func() { dir := t.TempDir() - err := signatures.InitNotationDir(dir) + certStorage, err := signatures.NewCertificateLocalStorage(dir) So(err, ShouldBeNil) - _, _, isTrusted, err := signatures.VerifySignature("notation", []byte(""), "", manifestDigest, manifestContent, repo) + sigStore := &signatures.SigStore{ + NotationStorage: certStorage, + } + + _, _, isTrusted, err := sigStore.VerifySignature("notation", []byte(""), "", manifestDigest, manifestContent, repo) So(err, ShouldNotBeNil) So(isTrusted, ShouldBeFalse) }) @@ -320,32 +351,40 @@ func TestVerifySignatures(t *testing.T) { Convey("trustpolicy.json does not exist", func() { dir := t.TempDir() - err := signatures.InitNotationDir(dir) + certStorage, err := signatures.NewCertificateLocalStorage(dir) So(err, ShouldBeNil) - notationDir, _ := signatures.GetNotationDirPath() + notationDir, _ := certStorage.GetNotationDirPath() err = os.Remove(path.Join(notationDir, "trustpolicy.json")) So(err, ShouldBeNil) - _, _, _, err = signatures.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo) + sigStore := &signatures.SigStore{ + NotationStorage: certStorage, + } + + _, _, _, err = sigStore.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo) So(err, ShouldNotBeNil) }) Convey("trustpolicy.json has invalid content", func() { dir := t.TempDir() - err := signatures.InitNotationDir(dir) + certStorage, err := signatures.NewCertificateLocalStorage(dir) So(err, ShouldBeNil) - notationDir, err := signatures.GetNotationDirPath() + notationDir, err := certStorage.GetNotationDirPath() So(err, ShouldBeNil) err = test.WriteFileWithPermission(path.Join(notationDir, "trustpolicy.json"), []byte("invalid content"), 0o600, true) So(err, ShouldBeNil) - _, _, _, err = signatures.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, + sigStore := &signatures.SigStore{ + NotationStorage: certStorage, + } + + _, _, _, err = sigStore.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo) So(err, ShouldNotBeNil) }) @@ -368,10 +407,10 @@ func TestVerifySignatures(t *testing.T) { err := test.UploadImage(image, baseURL, repo, tag) So(err, ShouldBeNil) - err = signatures.InitNotationDir(rootDir) + certStorage, err := signatures.NewCertificateLocalStorage(rootDir) So(err, ShouldBeNil) - notationDir, err := signatures.GetNotationDirPath() + notationDir, err := certStorage.GetNotationDirPath() So(err, ShouldBeNil) test.NotationPathLock.Lock() @@ -443,8 +482,12 @@ func TestVerifySignatures(t *testing.T) { } } + sigStore := &signatures.SigStore{ + NotationStorage: certStorage, + } + // signature is trusted - author, _, isTrusted, err := signatures.VerifySignature("notation", rawSignature, sigKey, manifestDigest, + author, _, isTrusted, err := sigStore.VerifySignature("notation", rawSignature, sigKey, manifestDigest, manifestContent, repo) So(err, ShouldBeNil) So(isTrusted, ShouldBeTrue) @@ -454,7 +497,7 @@ func TestVerifySignatures(t *testing.T) { So(err, ShouldBeNil) // signature is not trusted - author, _, isTrusted, err = signatures.VerifySignature("notation", rawSignature, sigKey, manifestDigest, + author, _, isTrusted, err = sigStore.VerifySignature("notation", rawSignature, sigKey, manifestDigest, manifestContent, repo) So(err, ShouldNotBeNil) So(isTrusted, ShouldBeFalse) @@ -490,7 +533,11 @@ func TestCheckExpiryErr(t *testing.T) { func TestUploadPublicKey(t *testing.T) { Convey("public key - invalid content", t, func() { - err := signatures.UploadPublicKey([]byte("wrong content")) + dir := t.TempDir() + pubKeyStorage, err := signatures.NewPublicKeyLocalStorage(dir) + So(err, ShouldBeNil) + + err = signatures.UploadPublicKey(pubKeyStorage, []byte("wrong content")) So(err, ShouldNotBeNil) }) @@ -513,41 +560,53 @@ func TestUploadPublicKey(t *testing.T) { So(err, ShouldBeNil) So(publicKeyContent, ShouldNotBeNil) - err = signatures.InitCosignDir(rootDir) + pubKeyStorage, err := signatures.NewPublicKeyLocalStorage(rootDir) So(err, ShouldBeNil) - err = signatures.UploadPublicKey(publicKeyContent) + err = signatures.UploadPublicKey(pubKeyStorage, publicKeyContent) So(err, ShouldBeNil) }) } func TestUploadCertificate(t *testing.T) { Convey("invalid truststore type", t, func() { - err := signatures.UploadCertificate([]byte("certificate content"), "wrongType", "store") + dir := t.TempDir() + certStorage, err := signatures.NewCertificateLocalStorage(dir) + So(err, ShouldBeNil) + + err = signatures.UploadCertificate(certStorage, []byte("certificate content"), "wrongType", "store") So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrInvalidTruststoreType) }) Convey("invalid truststore name", t, func() { - err := signatures.UploadCertificate([]byte("certificate content"), "ca", "*store?") + dir := t.TempDir() + certStorage, err := signatures.NewCertificateLocalStorage(dir) + So(err, ShouldBeNil) + + err = signatures.UploadCertificate(certStorage, []byte("certificate content"), "ca", "*store?") So(err, ShouldNotBeNil) So(err, ShouldEqual, zerr.ErrInvalidTruststoreName) }) Convey("invalid certificate content", t, func() { - err := signatures.UploadCertificate([]byte("invalid content"), "ca", "store") + dir := t.TempDir() + certStorage, err := signatures.NewCertificateLocalStorage(dir) + So(err, ShouldBeNil) + + err = signatures.UploadCertificate(certStorage, []byte("invalid content"), "ca", "store") So(err, ShouldNotBeNil) content := `-----BEGIN CERTIFICATE----- -----END CERTIFICATE----- ` - err = signatures.UploadCertificate([]byte(content), "ca", "store") + err = signatures.UploadCertificate(certStorage, []byte(content), "ca", "store") So(err, ShouldNotBeNil) content = `` - err = signatures.UploadCertificate([]byte(content), "ca", "store") + err = signatures.UploadCertificate(certStorage, []byte(content), "ca", "store") So(err, ShouldNotBeNil) }) @@ -567,16 +626,16 @@ func TestUploadCertificate(t *testing.T) { So(err, ShouldBeNil) So(certificateContent, ShouldNotBeNil) - err = signatures.InitNotationDir(rootDir) + certStorage, err := signatures.NewCertificateLocalStorage(rootDir) So(err, ShouldBeNil) - notationDir, err := signatures.GetNotationDirPath() + notationDir, err := certStorage.GetNotationDirPath() So(err, ShouldBeNil) err = os.Chmod(notationDir, 0o100) So(err, ShouldBeNil) - err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test") + err = signatures.UploadCertificate(certStorage, certificateContent, "ca", "notation-upload-test") So(err, ShouldNotBeNil) err = os.Chmod(notationDir, 0o777) @@ -599,10 +658,10 @@ func TestUploadCertificate(t *testing.T) { So(err, ShouldBeNil) So(certificateContent, ShouldNotBeNil) - err = signatures.InitNotationDir(rootDir) + certStorage, err := signatures.NewCertificateLocalStorage(rootDir) So(err, ShouldBeNil) - notationDir, err := signatures.GetNotationDirPath() + notationDir, err := certStorage.GetNotationDirPath() So(err, ShouldBeNil) err = os.MkdirAll(path.Join(notationDir, "truststore/x509/ca/notation-upload-test"), 0o777) @@ -611,7 +670,7 @@ func TestUploadCertificate(t *testing.T) { err = os.Chmod(path.Join(notationDir, "truststore/x509/ca/notation-upload-test"), 0o100) So(err, ShouldBeNil) - err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test") + err = signatures.UploadCertificate(certStorage, certificateContent, "ca", "notation-upload-test") So(err, ShouldNotBeNil) }) @@ -631,17 +690,17 @@ func TestUploadCertificate(t *testing.T) { So(err, ShouldBeNil) So(certificateContent, ShouldNotBeNil) - err = signatures.InitNotationDir(rootDir) + certStorage, err := signatures.NewCertificateLocalStorage(rootDir) So(err, ShouldBeNil) - notationDir, err := signatures.GetNotationDirPath() + notationDir, err := certStorage.GetNotationDirPath() So(err, ShouldBeNil) err = test.WriteFileWithPermission(path.Join(notationDir, "trustpolicy.json"), []byte("invalid content"), 0o600, true) So(err, ShouldBeNil) - err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test") + err = signatures.UploadCertificate(certStorage, certificateContent, "ca", "notation-upload-test") So(err, ShouldNotBeNil) }) @@ -661,13 +720,13 @@ func TestUploadCertificate(t *testing.T) { So(err, ShouldBeNil) So(certificateContent, ShouldNotBeNil) - err = signatures.InitNotationDir(rootDir) + certStorage, err := signatures.NewCertificateLocalStorage(rootDir) So(err, ShouldBeNil) - notationDir, err := signatures.GetNotationDirPath() + notationDir, err := certStorage.GetNotationDirPath() So(err, ShouldBeNil) - trustpolicyDoc, err := signatures.LoadTrustPolicyDocument(notationDir) + trustpolicyDoc, err := certStorage.LoadTrustPolicyDocument() So(err, ShouldBeNil) trustpolicyDoc.TrustPolicies[0].TrustStores = append(trustpolicyDoc.TrustPolicies[0].TrustStores, @@ -679,7 +738,7 @@ func TestUploadCertificate(t *testing.T) { err = os.WriteFile(path.Join(notationDir, "trustpolicy.json"), trustpolicyDocContent, 0o400) So(err, ShouldBeNil) - err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test") + err = signatures.UploadCertificate(certStorage, certificateContent, "ca", "notation-upload-test") So(err, ShouldBeNil) }) @@ -699,10 +758,10 @@ func TestUploadCertificate(t *testing.T) { So(err, ShouldBeNil) So(certificateContent, ShouldNotBeNil) - err = signatures.InitNotationDir(rootDir) + certStorage, err := signatures.NewCertificateLocalStorage(rootDir) So(err, ShouldBeNil) - err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test") + err = signatures.UploadCertificate(certStorage, certificateContent, "ca", "notation-upload-test") So(err, ShouldBeNil) }) } diff --git a/pkg/meta/types/types.go b/pkg/meta/types/types.go index c170c001..079d7ec4 100644 --- a/pkg/meta/types/types.go +++ b/pkg/meta/types/types.go @@ -121,6 +121,8 @@ type MetaDB interface { //nolint:interfacebloat []RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, error) PatchDB() error + + SignatureStorage() SignatureStorage } type UserDB interface { //nolint:interfacebloat @@ -156,6 +158,13 @@ type UserDB interface { //nolint:interfacebloat DeleteUserAPIKey(ctx context.Context, id string) error } +type SignatureStorage interface { + VerifySignature( + signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, manifestContent []byte, + repo string, + ) (string, time.Time, bool, error) +} + type ManifestMetadata struct { ManifestBlob []byte ConfigBlob []byte diff --git a/pkg/test/mocks/repo_db_mock.go b/pkg/test/mocks/repo_db_mock.go index 1ebdced8..6dabbd4d 100644 --- a/pkg/test/mocks/repo_db_mock.go +++ b/pkg/test/mocks/repo_db_mock.go @@ -102,6 +102,16 @@ type MetaDBMock struct { DeleteUserAPIKeyFn func(ctx context.Context, id string) error PatchDBFn func() error + + SignatureStorageFn func() mTypes.SignatureStorage +} + +func (sdm MetaDBMock) SignatureStorage() mTypes.SignatureStorage { + if sdm.SignatureStorageFn != nil { + return sdm.SignatureStorageFn() + } + + return nil } func (sdm MetaDBMock) SetRepoDescription(repo, description string) error {