mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 20:38:08 +08:00
refactor: move /pkg/meta/signatures under /pkg/extensions/imagetrust (#1712)
- the size of the binary-minimal becomes 32MB - "signatures" package is renamed into "imagetrust" and moved under extensions - if the binary is not built using "imagetrust" tag then the signatures verification will not be performed Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
This commit is contained in:
@@ -14,9 +14,9 @@ import (
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
"zotregistry.io/zot/pkg/extensions/imagetrust"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/common"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
"zotregistry.io/zot/pkg/meta/version"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
@@ -778,7 +778,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, _ := imagetrust.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey,
|
||||
manifestDigest, blob, repo)
|
||||
|
||||
if isTrusted {
|
||||
@@ -869,12 +869,12 @@ func (bdw *BoltDB) AddManifestSignature(repo string, signedManifestDigest godige
|
||||
|
||||
signatureSlice := manifestSignatures[sygMeta.SignatureType]
|
||||
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
|
||||
if sygMeta.SignatureType == signatures.NotationSignature {
|
||||
if sygMeta.SignatureType == zcommon.NotationSignature {
|
||||
signatureSlice = append(signatureSlice, mTypes.SignatureInfo{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
})
|
||||
} else if sygMeta.SignatureType == signatures.CosignSignature {
|
||||
} else if sygMeta.SignatureType == zcommon.CosignSignature {
|
||||
signatureSlice = []mTypes.SignatureInfo{{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
"go.etcd.io/bbolt"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/boltdb"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
@@ -545,9 +545,9 @@ func TestWrapperErrors(t *testing.T) {
|
||||
|
||||
repoData, err := boltdbWrapper.GetRepoMeta("repo1")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature]),
|
||||
So(len(repoData.Signatures[string(digest.FromString("dig"))][zcommon.CosignSignature]),
|
||||
ShouldEqual, 1)
|
||||
So(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature][0].SignatureManifestDigest,
|
||||
So(repoData.Signatures[string(digest.FromString("dig"))][zcommon.CosignSignature][0].SignatureManifestDigest,
|
||||
ShouldEqual, "digest2")
|
||||
|
||||
err = boltdbWrapper.AddManifestSignature("repo1", digest.FromString("dig"),
|
||||
|
||||
@@ -17,9 +17,9 @@ import (
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
"zotregistry.io/zot/pkg/extensions/imagetrust"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/common"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
"zotregistry.io/zot/pkg/meta/version"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
@@ -658,7 +658,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, _ := imagetrust.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey,
|
||||
manifestDigest, blob, repo)
|
||||
|
||||
if isTrusted {
|
||||
@@ -727,12 +727,12 @@ func (dwr *DynamoDB) AddManifestSignature(repo string, signedManifestDigest godi
|
||||
|
||||
signatureSlice := manifestSignatures[sygMeta.SignatureType]
|
||||
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
|
||||
if sygMeta.SignatureType == signatures.NotationSignature {
|
||||
if sygMeta.SignatureType == zcommon.NotationSignature {
|
||||
signatureSlice = append(signatureSlice, mTypes.SignatureInfo{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
})
|
||||
} else if sygMeta.SignatureType == signatures.CosignSignature {
|
||||
} else if sygMeta.SignatureType == zcommon.CosignSignature {
|
||||
signatureSlice = []mTypes.SignatureInfo{{
|
||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||
LayersInfo: sygMeta.LayersInfo,
|
||||
|
||||
+2
-2
@@ -6,10 +6,10 @@ import (
|
||||
|
||||
"zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/extensions/imagetrust"
|
||||
"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,7 +33,7 @@ func New(storageConfig config.StorageConfig, log log.Logger) (mTypes.MetaDB, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = signatures.InitCosignAndNotationDirs(params.RootDir)
|
||||
err = imagetrust.InitCosignAndNotationDirs(params.RootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
//go:build imagetrust
|
||||
// +build imagetrust
|
||||
|
||||
package meta_test
|
||||
|
||||
import (
|
||||
@@ -20,12 +23,12 @@ import (
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/extensions/imagetrust"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta"
|
||||
"zotregistry.io/zot/pkg/meta/boltdb"
|
||||
"zotregistry.io/zot/pkg/meta/common"
|
||||
mdynamodb "zotregistry.io/zot/pkg/meta/dynamodb"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
@@ -1208,7 +1211,7 @@ func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(tdir)
|
||||
err = imagetrust.InitNotationDir(tdir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
trustpolicyPath := path.Join(tdir, "_notation/trustpolicy.json")
|
||||
|
||||
+3
-4
@@ -11,7 +11,6 @@ import (
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
storageTypes "zotregistry.io/zot/pkg/storage/types"
|
||||
@@ -225,9 +224,9 @@ func GetSignatureLayersInfo(repo, tag, manifestDigest, signatureType string, man
|
||||
imageStore storageTypes.ImageStore, log log.Logger,
|
||||
) ([]mTypes.LayerInfo, error) {
|
||||
switch signatureType {
|
||||
case signatures.CosignSignature:
|
||||
case zcommon.CosignSignature:
|
||||
return getCosignSignatureLayersInfo(repo, tag, manifestDigest, manifestBlob, imageStore, log)
|
||||
case signatures.NotationSignature:
|
||||
case zcommon.NotationSignature:
|
||||
return getNotationSignatureLayersInfo(repo, manifestDigest, manifestBlob, imageStore, log)
|
||||
default:
|
||||
return []mTypes.LayerInfo{}, nil
|
||||
@@ -256,7 +255,7 @@ func getCosignSignatureLayersInfo(
|
||||
return layers, err
|
||||
}
|
||||
|
||||
layerSigKey, ok := layer.Annotations[signatures.CosignSigKey]
|
||||
layerSigKey, ok := layer.Annotations[zcommon.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")
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta"
|
||||
"zotregistry.io/zot/pkg/meta/boltdb"
|
||||
"zotregistry.io/zot/pkg/meta/dynamodb"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/storage/local"
|
||||
@@ -611,11 +611,11 @@ func TestGetSignatureLayersInfo(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("error while unmarshaling manifest content", t, func() {
|
||||
_, err := meta.GetSignatureLayersInfo("repo", "tag", "123", signatures.CosignSignature, []byte("bad manifest"),
|
||||
_, err := meta.GetSignatureLayersInfo("repo", "tag", "123", zcommon.CosignSignature, []byte("bad manifest"),
|
||||
nil, log.NewLogger("debug", ""))
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
_, err = meta.GetSignatureLayersInfo("repo", "tag", "123", signatures.NotationSignature, []byte("bad manifest"),
|
||||
_, err = meta.GetSignatureLayersInfo("repo", "tag", "123", zcommon.NotationSignature, []byte("bad manifest"),
|
||||
nil, log.NewLogger("debug", ""))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
package signatures
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
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"
|
||||
"github.com/sigstore/sigstore/pkg/signature/options"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
CosignSigKey = "dev.cosignproject.cosign/signature"
|
||||
cosignDirRelativePath = "_cosign"
|
||||
)
|
||||
|
||||
var cosignDir = "" //nolint:gochecknoglobals
|
||||
|
||||
func InitCosignDir(rootDir string) error {
|
||||
dir := path.Join(rootDir, cosignDirRelativePath)
|
||||
|
||||
_, err := os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, defaultDirPerms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
cosignDir = dir
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetCosignDirPath() (string, error) {
|
||||
if cosignDir != "" {
|
||||
return cosignDir, nil
|
||||
}
|
||||
|
||||
return "", zerr.ErrSignConfigDirNotSet
|
||||
}
|
||||
|
||||
func VerifyCosignSignature(
|
||||
repo string, digest godigest.Digest, signatureKey string, layerContent []byte,
|
||||
) (string, bool, error) {
|
||||
cosignDir, err := GetCosignDirPath()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(cosignDir)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
// cosign verify the image
|
||||
ctx := context.Background()
|
||||
keyRef := path.Join(cosignDir, file.Name())
|
||||
hashAlgorithm := crypto.SHA256
|
||||
|
||||
pubKey, err := sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, hashAlgorithm)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pkcs11Key, ok := pubKey.(*pkcs11key.Key)
|
||||
if ok {
|
||||
defer pkcs11Key.Close()
|
||||
}
|
||||
|
||||
verifier := pubKey
|
||||
|
||||
b64sig := signatureKey
|
||||
|
||||
signature, err := base64.StdEncoding.DecodeString(b64sig)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
compressed := io.NopCloser(bytes.NewReader(layerContent))
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
func UploadPublicKey(publicKeyContent []byte) error {
|
||||
// validate public key
|
||||
if ok, err := validatePublicKey(publicKeyContent); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// add public key to "{rootDir}/_cosign/{name.pub}"
|
||||
configDir, err := GetCosignDirPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := godigest.FromBytes(publicKeyContent)
|
||||
|
||||
// store public key
|
||||
publicKeyPath := path.Join(configDir, name.String())
|
||||
|
||||
return os.WriteFile(publicKeyPath, publicKeyContent, defaultFilePerms)
|
||||
}
|
||||
|
||||
func validatePublicKey(publicKeyContent []byte) (bool, error) {
|
||||
_, err := cryptoutils.UnmarshalPEMToPublicKey(publicKeyContent)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: %w", zerr.ErrInvalidPublicKeyContent, err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
package signatures
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/notaryproject/notation-core-go/signature/jws"
|
||||
"github.com/notaryproject/notation-go"
|
||||
"github.com/notaryproject/notation-go/dir"
|
||||
"github.com/notaryproject/notation-go/plugin"
|
||||
"github.com/notaryproject/notation-go/verifier"
|
||||
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
||||
"github.com/notaryproject/notation-go/verifier/truststore"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
)
|
||||
|
||||
const notationDirRelativePath = "_notation"
|
||||
|
||||
var (
|
||||
notationDir = "" //nolint:gochecknoglobals
|
||||
TrustpolicyLock = new(sync.Mutex) //nolint: gochecknoglobals
|
||||
)
|
||||
|
||||
func InitNotationDir(rootDir string) error {
|
||||
dir := path.Join(rootDir, notationDirRelativePath)
|
||||
|
||||
_, err := os.Stat(dir)
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, defaultDirPerms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
notationDir = dir
|
||||
|
||||
if _, err := LoadTrustPolicyDocument(notationDir); os.IsNotExist(err) {
|
||||
return InitTrustpolicyFile(notationDir)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func InitTrustpolicyFile(configDir string) 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`
|
||||
// this default trustpolicy.json file sets the signatureVerification.level
|
||||
// to `strict` which enforces all validations (this means that even if there is
|
||||
// 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": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
TrustpolicyLock.Lock()
|
||||
defer TrustpolicyLock.Unlock()
|
||||
|
||||
return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), []byte(trustPolicy), defaultDirPerms)
|
||||
}
|
||||
|
||||
func GetNotationDirPath() (string, error) {
|
||||
if notationDir != "" {
|
||||
return notationDir, nil
|
||||
}
|
||||
|
||||
return "", zerr.ErrSignConfigDirNotSet
|
||||
}
|
||||
|
||||
// Equivalent function for trustpolicy.LoadDocument() but using a specific SysFS not the one returned by ConfigFS().
|
||||
func LoadTrustPolicyDocument(notationDir string) (*trustpolicy.Document, error) {
|
||||
jsonFile, err := dir.NewSysFS(notationDir).Open(dir.PathTrustPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer jsonFile.Close()
|
||||
|
||||
policyDocument := &trustpolicy.Document{}
|
||||
|
||||
err = json.NewDecoder(jsonFile).Decode(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() (notation.Verifier, error) {
|
||||
notationDir, err := GetNotationDirPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load trust policy.
|
||||
TrustpolicyLock.Lock()
|
||||
defer TrustpolicyLock.Unlock()
|
||||
|
||||
policyDocument, err := LoadTrustPolicyDocument(notationDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load trust store.
|
||||
x509TrustStore := truststore.NewX509TrustStore(dir.NewSysFS(notationDir))
|
||||
|
||||
return verifier.New(policyDocument, x509TrustStore,
|
||||
plugin.NewCLIManager(dir.NewSysFS(path.Join(notationDir, dir.PathPlugins))))
|
||||
}
|
||||
|
||||
func VerifyNotationSignature(
|
||||
artifactDescriptor ispec.Descriptor, artifactReference string, rawSignature []byte, signatureMediaType string,
|
||||
) (string, time.Time, bool, error) {
|
||||
var (
|
||||
date time.Time
|
||||
author string
|
||||
)
|
||||
|
||||
// If there's no signature associated with the reference.
|
||||
if len(rawSignature) == 0 {
|
||||
return author, date, false, notation.ErrorSignatureRetrievalFailed{
|
||||
Msg: fmt.Sprintf("no signature associated with %q is provided, make sure the image was signed successfully",
|
||||
artifactReference),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize verifier.
|
||||
verifier, err := NewFromConfig()
|
||||
if err != nil {
|
||||
return author, date, false, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Set VerifyOptions.
|
||||
opts := notation.VerifierVerifyOptions{
|
||||
// ArtifactReference is important to validate registry scope format
|
||||
// If "registryScopes" field from trustpolicy.json file is not wildcard then "domain:80/repo@" should not be hardcoded
|
||||
ArtifactReference: "domain:80/repo@" + artifactReference,
|
||||
SignatureMediaType: signatureMediaType,
|
||||
PluginConfig: map[string]string{},
|
||||
}
|
||||
|
||||
// Verify the notation signature which should be associated with the artifactDescriptor.
|
||||
outcome, err := verifier.Verify(ctx, artifactDescriptor, rawSignature, opts)
|
||||
if outcome.EnvelopeContent != nil {
|
||||
author = outcome.EnvelopeContent.SignerInfo.CertificateChain[0].Subject.String()
|
||||
|
||||
if outcome.VerificationLevel == trustpolicy.LevelStrict && (err == nil ||
|
||||
CheckExpiryErr(outcome.VerificationResults, outcome.EnvelopeContent.SignerInfo.CertificateChain[0].NotAfter, err)) {
|
||||
expiry := outcome.EnvelopeContent.SignerInfo.SignedAttributes.Expiry
|
||||
if !expiry.IsZero() && expiry.Before(outcome.EnvelopeContent.SignerInfo.CertificateChain[0].NotAfter) {
|
||||
date = outcome.EnvelopeContent.SignerInfo.SignedAttributes.Expiry
|
||||
} else {
|
||||
date = outcome.EnvelopeContent.SignerInfo.CertificateChain[0].NotAfter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return author, date, false, err
|
||||
}
|
||||
|
||||
// Verification Succeeded.
|
||||
return author, date, true, nil
|
||||
}
|
||||
|
||||
func CheckExpiryErr(verificationResults []*notation.ValidationResult, notAfter time.Time, err error) bool {
|
||||
for _, result := range verificationResults {
|
||||
if result.Type == trustpolicy.TypeExpiry {
|
||||
if errors.Is(err, result.Error) {
|
||||
return true
|
||||
}
|
||||
} else if result.Type == trustpolicy.TypeAuthenticTimestamp {
|
||||
if errors.Is(err, result.Error) && time.Now().After(notAfter) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func UploadCertificate(certificateContent []byte, truststoreType, truststoreName string) error {
|
||||
// validate truststore type
|
||||
if !validateTruststoreType(truststoreType) {
|
||||
return zerr.ErrInvalidTruststoreType
|
||||
}
|
||||
|
||||
// validate truststore name
|
||||
if !validateTruststoreName(truststoreName) {
|
||||
return zerr.ErrInvalidTruststoreName
|
||||
}
|
||||
|
||||
// validate certificate
|
||||
if ok, err := validateCertificate(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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add certificate to "trustpolicy.json"
|
||||
TrustpolicyLock.Lock()
|
||||
defer TrustpolicyLock.Unlock()
|
||||
|
||||
trustpolicyDoc, err := LoadTrustPolicyDocument(configDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
truststoreToAppend := fmt.Sprintf("%s:%s", truststoreType, truststoreName)
|
||||
|
||||
for _, t := range trustpolicyDoc.TrustPolicies[0].TrustStores {
|
||||
if t == truststoreToAppend {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
trustpolicyDoc.TrustPolicies[0].TrustStores = append(trustpolicyDoc.TrustPolicies[0].TrustStores, truststoreToAppend)
|
||||
|
||||
trustpolicyDocContent, err := json.Marshal(trustpolicyDoc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(path.Join(configDir, dir.PathTrustPolicy), trustpolicyDocContent, defaultFilePerms)
|
||||
}
|
||||
|
||||
func validateTruststoreType(truststoreType string) bool {
|
||||
for _, t := range truststore.Types {
|
||||
if string(t) == truststoreType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func validateTruststoreName(truststoreName string) bool {
|
||||
return regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`).MatchString(truststoreName)
|
||||
}
|
||||
|
||||
// implementation from https://github.com/notaryproject/notation-core-go/blob/main/x509/cert.go#L20
|
||||
func validateCertificate(certificateContent []byte) (bool, error) {
|
||||
var certs []*x509.Certificate
|
||||
|
||||
block, rest := pem.Decode(certificateContent)
|
||||
if block == nil {
|
||||
// data may be in DER format
|
||||
derCerts, err := x509.ParseCertificates(certificateContent)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err)
|
||||
}
|
||||
|
||||
certs = append(certs, derCerts...)
|
||||
} else {
|
||||
// data is in PEM format
|
||||
for block != nil {
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err)
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
block, rest = pem.Decode(rest)
|
||||
}
|
||||
}
|
||||
|
||||
if len(certs) == 0 {
|
||||
return false, fmt.Errorf("%w: no valid certificates found in payload",
|
||||
zerr.ErrInvalidCertificateContent)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package signatures
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
"zotregistry.io/zot/pkg/scheduler"
|
||||
)
|
||||
|
||||
const (
|
||||
CosignSignature = "cosign"
|
||||
NotationSignature = "notation"
|
||||
defaultDirPerms = 0o700
|
||||
defaultFilePerms = 0o644
|
||||
)
|
||||
|
||||
func InitCosignAndNotationDirs(rootDir string) error {
|
||||
err := InitCosignDir(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = InitNotationDir(rootDir)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func VerifySignature(
|
||||
signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, manifestContent []byte,
|
||||
repo string,
|
||||
) (string, time.Time, bool, error) {
|
||||
var manifest ispec.Manifest
|
||||
if err := json.Unmarshal(manifestContent, &manifest); err != nil {
|
||||
return "", time.Time{}, false, err
|
||||
}
|
||||
|
||||
desc := ispec.Descriptor{
|
||||
MediaType: manifest.MediaType,
|
||||
Digest: manifestDigest,
|
||||
Size: int64(len(manifestContent)),
|
||||
}
|
||||
|
||||
if manifestDigest.String() == "" {
|
||||
return "", time.Time{}, false, zerr.ErrBadManifestDigest
|
||||
}
|
||||
|
||||
switch signatureType {
|
||||
case CosignSignature:
|
||||
author, isValid, err := VerifyCosignSignature(repo, manifestDigest, sigKey, rawSignature)
|
||||
|
||||
return author, time.Time{}, isValid, err
|
||||
case NotationSignature:
|
||||
return VerifyNotationSignature(desc, manifestDigest.String(), rawSignature, sigKey)
|
||||
default:
|
||||
return "", time.Time{}, false, zerr.ErrInvalidSignatureType
|
||||
}
|
||||
}
|
||||
|
||||
func NewTaskGenerator(metaDB mTypes.MetaDB, log log.Logger) scheduler.TaskGenerator {
|
||||
return &sigValidityTaskGenerator{
|
||||
repos: []mTypes.RepoMetadata{},
|
||||
metaDB: metaDB,
|
||||
repoIndex: -1,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
type sigValidityTaskGenerator struct {
|
||||
repos []mTypes.RepoMetadata
|
||||
metaDB mTypes.MetaDB
|
||||
repoIndex int
|
||||
done bool
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (gen *sigValidityTaskGenerator) Next() (scheduler.Task, error) {
|
||||
if len(gen.repos) == 0 {
|
||||
ctx := context.Background()
|
||||
|
||||
repos, err := gen.metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMetadata) bool {
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gen.repos = repos
|
||||
}
|
||||
|
||||
gen.repoIndex++
|
||||
|
||||
if gen.repoIndex >= len(gen.repos) {
|
||||
gen.done = true
|
||||
|
||||
gen.log.Info().Msg("finished generating tasks for updating signatures validity")
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NewValidityTask(gen.metaDB, gen.repos[gen.repoIndex], gen.log), nil
|
||||
}
|
||||
|
||||
func (gen *sigValidityTaskGenerator) IsDone() bool {
|
||||
return gen.done
|
||||
}
|
||||
|
||||
func (gen *sigValidityTaskGenerator) IsReady() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (gen *sigValidityTaskGenerator) Reset() {
|
||||
gen.done = false
|
||||
gen.repoIndex = -1
|
||||
gen.repos = []mTypes.RepoMetadata{}
|
||||
|
||||
gen.log.Info().Msg("finished resetting task generator for updating signatures validity")
|
||||
}
|
||||
|
||||
type validityTask struct {
|
||||
metaDB mTypes.MetaDB
|
||||
repo mTypes.RepoMetadata
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewValidityTask(metaDB mTypes.MetaDB, repo mTypes.RepoMetadata, log log.Logger) *validityTask {
|
||||
return &validityTask{metaDB, repo, log}
|
||||
}
|
||||
|
||||
func (validityT *validityTask) DoWork() error {
|
||||
validityT.log.Info().Msg("updating signatures validity")
|
||||
|
||||
for signedManifest, sigs := range validityT.repo.Signatures {
|
||||
if len(sigs[CosignSignature]) != 0 || len(sigs[NotationSignature]) != 0 {
|
||||
err := validityT.metaDB.UpdateSignaturesValidity(validityT.repo.Name, godigest.Digest(signedManifest))
|
||||
if err != nil {
|
||||
validityT.log.Info().Msg("error while verifying signatures")
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validityT.log.Info().Msg("verifying signatures successfully completed")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,708 +0,0 @@
|
||||
package signatures_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/notaryproject/notation-go"
|
||||
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sigstore/cosign/v2/cmd/cosign/cli/generate"
|
||||
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
|
||||
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
|
||||
var errExpiryError = errors.New("expiry err")
|
||||
|
||||
func TestInitCosignAndNotationDirs(t *testing.T) {
|
||||
Convey("InitCosignDir error", t, func() {
|
||||
dir := t.TempDir()
|
||||
err := os.Chmod(dir, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.InitCosignAndNotationDirs(dir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(dir, 0o500)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.InitCosignAndNotationDirs(dir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
cosignDir, err := signatures.GetCosignDirPath()
|
||||
So(cosignDir, ShouldBeEmpty)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||
})
|
||||
|
||||
Convey("InitNotationDir error", t, func() {
|
||||
dir := t.TempDir()
|
||||
err := os.Chmod(dir, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.InitCosignAndNotationDirs(dir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(dir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(dir, 0o500)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.InitCosignAndNotationDirs(dir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(dir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
notationDir, err := signatures.GetNotationDirPath()
|
||||
So(notationDir, ShouldBeEmpty)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||
})
|
||||
|
||||
Convey("UploadCertificate - notationDir is not set", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(rootDir)
|
||||
|
||||
// generate a keypair
|
||||
err := test.GenerateNotationCerts(rootDir, "notation-upload-test")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certificateContent, err := os.ReadFile(path.Join(rootDir, "notation/localkeys", "notation-upload-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
So(certificateContent, ShouldNotBeNil)
|
||||
|
||||
err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||
})
|
||||
|
||||
Convey("UploadPublicKey - cosignDir is not set", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_ = os.Chdir(rootDir)
|
||||
|
||||
// generate a keypair
|
||||
os.Setenv("COSIGN_PASSWORD", "")
|
||||
err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_ = os.Chdir(cwd)
|
||||
|
||||
publicKeyContent, err := os.ReadFile(path.Join(rootDir, "cosign.pub"))
|
||||
So(err, ShouldBeNil)
|
||||
So(publicKeyContent, ShouldNotBeNil)
|
||||
|
||||
err = signatures.UploadPublicKey(publicKeyContent)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifySignatures(t *testing.T) {
|
||||
Convey("wrong manifest content", t, func() {
|
||||
manifestContent := []byte("wrong json")
|
||||
|
||||
_, _, _, err := signatures.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("empty manifest digest", t, func() {
|
||||
image, err := test.GetRandomImage() //nolint:staticcheck
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestContent, err := json.Marshal(image.Manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = signatures.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrBadManifestDigest)
|
||||
})
|
||||
|
||||
Convey("wrong signature type", t, func() {
|
||||
image, err := test.GetRandomImage() //nolint:staticcheck
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestContent, err := json.Marshal(image.Manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestDigest := image.Digest()
|
||||
|
||||
_, _, _, err = signatures.VerifySignature("wrongType", []byte(""), "", manifestDigest, manifestContent, "repo")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidSignatureType)
|
||||
})
|
||||
|
||||
Convey("verify cosign signature", t, func() {
|
||||
repo := "repo"
|
||||
tag := "test"
|
||||
image, err := test.GetRandomImage() //nolint:staticcheck
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestContent, err := json.Marshal(image.Manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestDigest := image.Digest()
|
||||
|
||||
Convey("cosignDir is not set", func() {
|
||||
_, _, _, err = signatures.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||
})
|
||||
|
||||
Convey("cosignDir does not have read permissions", func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
err := signatures.InitCosignDir(dir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cosignDir, err := signatures.GetCosignDirPath()
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Chmod(cosignDir, 0o300)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = signatures.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("no valid public key", func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
err := signatures.InitCosignDir(dir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cosignDir, err := signatures.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)
|
||||
So(err, ShouldBeNil)
|
||||
So(isTrusted, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("signature is trusted", func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.GC = false
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = rootDir
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
err := test.UploadImage(image, baseURL, repo, tag)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.InitCosignDir(rootDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cosignDir, err := signatures.GetCosignDirPath()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_ = os.Chdir(cosignDir)
|
||||
|
||||
// generate a keypair
|
||||
os.Setenv("COSIGN_PASSWORD", "")
|
||||
err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_ = os.Chdir(cwd)
|
||||
|
||||
// sign the image
|
||||
err = sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: 1 * time.Minute},
|
||||
options.KeyOpts{KeyRef: path.Join(cosignDir, "cosign.key"), PassFunc: generate.GetPass},
|
||||
options.SignOptions{
|
||||
Registry: options.RegistryOptions{AllowInsecure: true},
|
||||
AnnotationOptions: options.AnnotationOptions{Annotations: []string{fmt.Sprintf("tag=%s", tag)}},
|
||||
Upload: true,
|
||||
},
|
||||
[]string{fmt.Sprintf("localhost:%s/%s@%s", port, repo, manifestDigest.String())})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Remove(path.Join(cosignDir, "cosign.key"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
indexContent, err := ctlr.StoreController.DefaultStore.GetIndexContent(repo)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var index ispec.Index
|
||||
err = json.Unmarshal(indexContent, &index)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var rawSignature []byte
|
||||
var sigKey string
|
||||
|
||||
for _, manifest := range index.Manifests {
|
||||
if manifest.Digest != manifestDigest {
|
||||
blobContent, err := ctlr.StoreController.DefaultStore.GetBlobContent(repo, manifest.Digest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var cosignSig ispec.Manifest
|
||||
|
||||
err = json.Unmarshal(blobContent, &cosignSig)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sigKey = cosignSig.Layers[0].Annotations[signatures.CosignSigKey]
|
||||
|
||||
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, cosignSig.Layers[0].Digest)
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
}
|
||||
|
||||
// signature is trusted
|
||||
author, _, isTrusted, err := signatures.VerifySignature("cosign", rawSignature, sigKey, manifestDigest,
|
||||
manifestContent, repo)
|
||||
So(err, ShouldBeNil)
|
||||
So(isTrusted, ShouldBeTrue)
|
||||
So(author, ShouldNotBeEmpty)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("verify notation signature", t, func() {
|
||||
repo := "repo"
|
||||
tag := "test"
|
||||
image, err := test.GetRandomImage() //nolint:staticcheck
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestContent, err := json.Marshal(image.Manifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestDigest := image.Digest()
|
||||
|
||||
Convey("notationDir is not set", func() {
|
||||
_, _, _, err = signatures.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||
})
|
||||
|
||||
Convey("no signature provided", func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
err := signatures.InitNotationDir(dir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, isTrusted, err := signatures.VerifySignature("notation", []byte(""), "", manifestDigest, manifestContent, repo)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(isTrusted, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("trustpolicy.json does not exist", func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
err := signatures.InitNotationDir(dir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
notationDir, _ := signatures.GetNotationDirPath()
|
||||
|
||||
err = os.Remove(path.Join(notationDir, "trustpolicy.json"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, _, _, err = signatures.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("trustpolicy.json has invalid content", func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
err := signatures.InitNotationDir(dir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
notationDir, err := signatures.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,
|
||||
repo)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("signature is trusted", func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.GC = false
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = rootDir
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
err := test.UploadImage(image, baseURL, repo, tag)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(rootDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
notationDir, err := signatures.GetNotationDirPath()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(notationDir)
|
||||
|
||||
// generate a keypair
|
||||
err = test.GenerateNotationCerts(notationDir, "notation-sign-test")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// sign the image
|
||||
image := fmt.Sprintf("localhost:%s/%s", port, fmt.Sprintf("%s:%s", repo, tag))
|
||||
|
||||
err = test.SignWithNotation("notation-sign-test", image, notationDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.CopyFiles(path.Join(notationDir, "notation", "truststore"), path.Join(notationDir, "truststore"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.RemoveAll(path.Join(notationDir, "notation"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
trustPolicy := `
|
||||
{
|
||||
"version": "1.0",
|
||||
"trustPolicies": [
|
||||
{
|
||||
"name": "notation-sign-test",
|
||||
"registryScopes": [ "*" ],
|
||||
"signatureVerification": {
|
||||
"level" : "strict"
|
||||
},
|
||||
"trustStores": ["ca:notation-sign-test"],
|
||||
"trustedIdentities": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
err = test.WriteFileWithPermission(path.Join(notationDir, "trustpolicy.json"), []byte(trustPolicy), 0o600, true)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
indexContent, err := ctlr.StoreController.DefaultStore.GetIndexContent(repo)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var index ispec.Index
|
||||
err = json.Unmarshal(indexContent, &index)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var rawSignature []byte
|
||||
var sigKey string
|
||||
|
||||
for _, manifest := range index.Manifests {
|
||||
if manifest.Digest != manifestDigest {
|
||||
blobContent, err := ctlr.StoreController.DefaultStore.GetBlobContent(repo, manifest.Digest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
var notationSig ispec.Manifest
|
||||
|
||||
err = json.Unmarshal(blobContent, ¬ationSig)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sigKey = notationSig.Layers[0].MediaType
|
||||
|
||||
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, notationSig.Layers[0].Digest)
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
}
|
||||
|
||||
// signature is trusted
|
||||
author, _, isTrusted, err := signatures.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
|
||||
manifestContent, repo)
|
||||
So(err, ShouldBeNil)
|
||||
So(isTrusted, ShouldBeTrue)
|
||||
So(author, ShouldNotBeEmpty)
|
||||
|
||||
err = os.Truncate(path.Join(notationDir, "truststore/x509/ca/notation-sign-test/notation-sign-test.crt"), 0)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// signature is not trusted
|
||||
author, _, isTrusted, err = signatures.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
|
||||
manifestContent, repo)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(isTrusted, ShouldBeFalse)
|
||||
So(author, ShouldNotBeEmpty)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckExpiryErr(t *testing.T) {
|
||||
Convey("no expiry err", t, func() {
|
||||
isExpiryErr := signatures.CheckExpiryErr([]*notation.ValidationResult{{Error: nil, Type: "wrongtype"}}, time.Now(),
|
||||
nil)
|
||||
So(isExpiryErr, ShouldBeFalse)
|
||||
|
||||
isExpiryErr = signatures.CheckExpiryErr([]*notation.ValidationResult{{
|
||||
Error: nil, Type: trustpolicy.TypeAuthenticTimestamp,
|
||||
}}, time.Now(), errExpiryError)
|
||||
So(isExpiryErr, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("expiry err", t, func() {
|
||||
isExpiryErr := signatures.CheckExpiryErr([]*notation.ValidationResult{
|
||||
{Error: errExpiryError, Type: trustpolicy.TypeExpiry},
|
||||
}, time.Now(), errExpiryError)
|
||||
So(isExpiryErr, ShouldBeTrue)
|
||||
|
||||
isExpiryErr = signatures.CheckExpiryErr([]*notation.ValidationResult{
|
||||
{Error: errExpiryError, Type: trustpolicy.TypeAuthenticTimestamp},
|
||||
}, time.Now().AddDate(0, 0, -1), errExpiryError)
|
||||
So(isExpiryErr, ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUploadPublicKey(t *testing.T) {
|
||||
Convey("public key - invalid content", t, func() {
|
||||
err := signatures.UploadPublicKey([]byte("wrong content"))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("upload public key successfully", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_ = os.Chdir(rootDir)
|
||||
|
||||
// generate a keypair
|
||||
os.Setenv("COSIGN_PASSWORD", "")
|
||||
err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_ = os.Chdir(cwd)
|
||||
|
||||
publicKeyContent, err := os.ReadFile(path.Join(rootDir, "cosign.pub"))
|
||||
So(err, ShouldBeNil)
|
||||
So(publicKeyContent, ShouldNotBeNil)
|
||||
|
||||
err = signatures.InitCosignDir(rootDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.UploadPublicKey(publicKeyContent)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUploadCertificate(t *testing.T) {
|
||||
Convey("invalid truststore type", t, func() {
|
||||
err := signatures.UploadCertificate([]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?")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, zerr.ErrInvalidTruststoreName)
|
||||
})
|
||||
|
||||
Convey("invalid certificate content", t, func() {
|
||||
err := signatures.UploadCertificate([]byte("invalid content"), "ca", "store")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
content := `-----BEGIN CERTIFICATE-----
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
err = signatures.UploadCertificate([]byte(content), "ca", "store")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
content = ``
|
||||
|
||||
err = signatures.UploadCertificate([]byte(content), "ca", "store")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("truststore dir can not be created", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(rootDir)
|
||||
|
||||
// generate a keypair
|
||||
err := test.GenerateNotationCerts(rootDir, "notation-upload-test")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certificateContent, err := os.ReadFile(path.Join(rootDir, "notation/localkeys", "notation-upload-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
So(certificateContent, ShouldNotBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(rootDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
notationDir, err := signatures.GetNotationDirPath()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(notationDir, 0o100)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("certificate can't be stored", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(rootDir)
|
||||
|
||||
// generate a keypair
|
||||
err := test.GenerateNotationCerts(rootDir, "notation-upload-test")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certificateContent, err := os.ReadFile(path.Join(rootDir, "notation/localkeys", "notation-upload-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
So(certificateContent, ShouldNotBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(rootDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
notationDir, err := signatures.GetNotationDirPath()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.MkdirAll(path.Join(notationDir, "truststore/x509/ca/notation-upload-test"), 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(path.Join(notationDir, "truststore/x509/ca/notation-upload-test"), 0o100)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("trustpolicy - invalid content", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(rootDir)
|
||||
|
||||
// generate a keypair
|
||||
err := test.GenerateNotationCerts(rootDir, "notation-upload-test")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certificateContent, err := os.ReadFile(path.Join(rootDir, "notation/localkeys", "notation-upload-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
So(certificateContent, ShouldNotBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(rootDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
notationDir, err := signatures.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")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("trustpolicy - truststore already exists", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(rootDir)
|
||||
|
||||
// generate a keypair
|
||||
err := test.GenerateNotationCerts(rootDir, "notation-upload-test")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certificateContent, err := os.ReadFile(path.Join(rootDir, "notation/localkeys", "notation-upload-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
So(certificateContent, ShouldNotBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(rootDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
notationDir, err := signatures.GetNotationDirPath()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
trustpolicyDoc, err := signatures.LoadTrustPolicyDocument(notationDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
trustpolicyDoc.TrustPolicies[0].TrustStores = append(trustpolicyDoc.TrustPolicies[0].TrustStores,
|
||||
"ca:notation-upload-test")
|
||||
|
||||
trustpolicyDocContent, err := json.Marshal(trustpolicyDoc)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.WriteFile(path.Join(notationDir, "trustpolicy.json"), trustpolicyDocContent, 0o400)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("upload certificate successfully", t, func() {
|
||||
rootDir := t.TempDir()
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(rootDir)
|
||||
|
||||
// generate a keypair
|
||||
err := test.GenerateNotationCerts(rootDir, "notation-upload-test")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certificateContent, err := os.ReadFile(path.Join(rootDir, "notation/localkeys", "notation-upload-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
So(certificateContent, ShouldNotBeNil)
|
||||
|
||||
err = signatures.InitNotationDir(rootDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signatures.UploadCertificate(certificateContent, "ca", "notation-upload-test")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user