0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-23 22:27:35 -05:00
zot/pkg/extensions/imagetrust/image_trust_test.go
Andrei Aaron ba6f347d8d
refactor(pkg/test): split logic in pkg/test/common.go into multiple packages (#1861)
Which could be imported independently. See more details:
1. "zotregistry.io/zot/pkg/test/common" - currently used as
   tcommon "zotregistry.io/zot/pkg/test/common" - inside pkg/test
   test "zotregistry.io/zot/pkg/test/common" - in tests
   . "zotregistry.io/zot/pkg/test/common" - in tests
Decouple zb from code in test/pkg in order to keep the size small.

2. "zotregistry.io/zot/pkg/test/image-utils" - curently used as
   . "zotregistry.io/zot/pkg/test/image-utils"

3. "zotregistry.io/zot/pkg/test/deprecated" -  curently used as
   "zotregistry.io/zot/pkg/test/deprecated"
This one will bre replaced gradually by image-utils in the future.

4. "zotregistry.io/zot/pkg/test/signature" - (cosign + notation) use as
   "zotregistry.io/zot/pkg/test/signature"

5. "zotregistry.io/zot/pkg/test/auth" - (bearer + oidc)  curently used as
   authutils "zotregistry.io/zot/pkg/test/auth"

 6. "zotregistry.io/zot/pkg/test/oci-utils" -  curently used as
   ociutils "zotregistry.io/zot/pkg/test/oci-utils"

Some unused functions were removed, some were replaced, and in
a few cases specific funtions were moved to the files they were used in.

Added an interface for the StoreController, this reduces the number of imports
of the entire image store, decreasing binary size for tests.
If the zb code was still coupled with pkg/test, this would have reflected in zb size.

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
2023-09-27 11:34:48 -07:00

1373 lines
41 KiB
Go

//go:build imagetrust
// +build imagetrust
package imagetrust_test
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"testing"
"time"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
guuid "github.com/gofrs/uuid"
"github.com/notaryproject/notation-go"
notreg "github.com/notaryproject/notation-go/registry"
"github.com/notaryproject/notation-go/verifier/trustpolicy"
"github.com/opencontainers/go-digest"
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"
"gopkg.in/resty.v1"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
zcommon "zotregistry.io/zot/pkg/common"
extconf "zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/extensions/imagetrust"
test "zotregistry.io/zot/pkg/test/common"
"zotregistry.io/zot/pkg/test/deprecated"
. "zotregistry.io/zot/pkg/test/image-utils"
"zotregistry.io/zot/pkg/test/mocks"
"zotregistry.io/zot/pkg/test/signature"
)
var (
errExpiryError = errors.New("expiry err")
errUnexpectedError = errors.New("unexpected err")
)
func TestInitCosignAndNotationDirs(t *testing.T) {
Convey("InitCosignDir error", t, func() {
dir := t.TempDir()
err := os.Chmod(dir, 0o000)
So(err, ShouldBeNil)
_, err = imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldNotBeNil)
err = os.Chmod(dir, 0o500)
So(err, ShouldBeNil)
_, err = imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldNotBeNil)
pubKeyStorage := &imagetrust.PublicKeyLocalStorage{}
cosignDir, err := pubKeyStorage.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 = imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldNotBeNil)
_, err = imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldNotBeNil)
err = os.Chmod(dir, 0o500)
So(err, ShouldBeNil)
_, err = imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldNotBeNil)
_, err = imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldNotBeNil)
certStorage := &imagetrust.CertificateLocalStorage{}
notationDir, err := certStorage.GetNotationDirPath()
So(notationDir, ShouldBeEmpty)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
})
Convey("UploadCertificate - notationDir is not set", t, func() {
rootDir := t.TempDir()
signature.NotationPathLock.Lock()
defer signature.NotationPathLock.Unlock()
signature.LoadNotationPath(rootDir)
// generate a keypair
err := signature.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)
certStorgae := &imagetrust.CertificateLocalStorage{}
err = imagetrust.UploadCertificate(certStorgae, certificateContent, "ca")
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)
pubKeyStorage := &imagetrust.PublicKeyLocalStorage{}
err = imagetrust.UploadPublicKey(pubKeyStorage, publicKeyContent)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
})
}
func TestVerifySignatures(t *testing.T) {
Convey("wrong manifest content", t, func() {
manifestContent := []byte("wrong json")
imgTrustStore := &imagetrust.ImageTrustStore{}
_, _, _, err := imgTrustStore.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
So(err, ShouldNotBeNil)
})
Convey("empty manifest digest", t, func() {
image, err := deprecated.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
imgTrustStore := &imagetrust.ImageTrustStore{}
_, _, _, err = imgTrustStore.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrBadManifestDigest)
})
Convey("wrong signature type", t, func() {
image, err := deprecated.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
imgTrustStore := &imagetrust.ImageTrustStore{}
_, _, _, err = imgTrustStore.VerifySignature("wrongType", []byte(""), "", manifestDigest, manifestContent, "repo")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrInvalidSignatureType)
})
Convey("verify cosign signature", t, func() {
repo := "repo" //nolint:goconst
tag := "test" //nolint:goconst
image, err := deprecated.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
Convey("cosignDir is not set", func() {
imgTrustStore := &imagetrust.ImageTrustStore{
CosignStorage: &imagetrust.PublicKeyLocalStorage{},
}
_, _, _, err = imgTrustStore.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()
pubKeyStorage, err := imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldBeNil)
cosignDir, err := pubKeyStorage.GetCosignDirPath()
So(err, ShouldBeNil)
err = os.Chmod(cosignDir, 0o300)
So(err, ShouldBeNil)
imgTrustStore := &imagetrust.ImageTrustStore{
CosignStorage: pubKeyStorage,
}
_, _, _, err = imgTrustStore.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
So(err, ShouldNotBeNil)
})
Convey("no valid public key", func() {
dir := t.TempDir()
pubKeyStorage, err := imagetrust.NewPublicKeyLocalStorage(dir)
So(err, ShouldBeNil)
cosignDir, err := pubKeyStorage.GetCosignDirPath()
So(err, ShouldBeNil)
err = test.WriteFileWithPermission(path.Join(cosignDir, "file"), []byte("not a public key"), 0o600, false)
So(err, ShouldBeNil)
imgTrustStore := &imagetrust.ImageTrustStore{
CosignStorage: pubKeyStorage,
}
_, _, isTrusted, err := imgTrustStore.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 := UploadImage(image, baseURL, repo, tag)
So(err, ShouldBeNil)
pubKeyStorage, err := imagetrust.NewPublicKeyLocalStorage(rootDir)
So(err, ShouldBeNil)
cosignDir, err := pubKeyStorage.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[zcommon.CosignSigKey]
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, cosignSig.Layers[0].Digest)
So(err, ShouldBeNil)
}
}
imgTrustStore := &imagetrust.ImageTrustStore{
CosignStorage: pubKeyStorage,
}
// signature is trusted
author, _, isTrusted, err := imgTrustStore.VerifySignature("cosign", rawSignature, sigKey, manifestDigest,
manifestContent, repo)
So(err, ShouldBeNil)
So(isTrusted, ShouldBeTrue)
So(author, ShouldNotBeEmpty)
})
})
Convey("verify notation signature", t, func() {
repo := "repo" //nolint:goconst
tag := "test" //nolint:goconst
image, err := deprecated.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
Convey("notationDir is not set", func() {
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: &imagetrust.CertificateLocalStorage{},
}
_, _, _, err = imgTrustStore.VerifySignature("notation", []byte("signature"), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
})
Convey("no signature provided", func() {
dir := t.TempDir()
certStorage, err := imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldBeNil)
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: certStorage,
}
_, _, isTrusted, err := imgTrustStore.VerifySignature("notation", []byte(""), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
So(isTrusted, ShouldBeFalse)
})
Convey("trustpolicy.json does not exist", func() {
dir := t.TempDir()
certStorage, err := imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldBeNil)
notationDir, _ := certStorage.GetNotationDirPath()
err = os.Remove(path.Join(notationDir, "trustpolicy.json"))
So(err, ShouldBeNil)
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: certStorage,
}
_, _, _, err = imgTrustStore.VerifySignature("notation", []byte("signature"), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
})
Convey("trustpolicy.json has invalid content", func() {
dir := t.TempDir()
certStorage, err := imagetrust.NewCertificateLocalStorage(dir)
So(err, ShouldBeNil)
notationDir, err := certStorage.GetNotationDirPath()
So(err, ShouldBeNil)
err = test.WriteFileWithPermission(path.Join(notationDir, "trustpolicy.json"), []byte("invalid content"),
0o600, true)
So(err, ShouldBeNil)
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: certStorage,
}
_, _, _, err = imgTrustStore.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 := UploadImage(image, baseURL, repo, tag)
So(err, ShouldBeNil)
certStorage, err := imagetrust.NewCertificateLocalStorage(rootDir)
So(err, ShouldBeNil)
notationDir, err := certStorage.GetNotationDirPath()
So(err, ShouldBeNil)
signature.NotationPathLock.Lock()
defer signature.NotationPathLock.Unlock()
signature.LoadNotationPath(notationDir)
// generate a keypair
err = signature.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 = signature.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, &notationSig)
So(err, ShouldBeNil)
sigKey = notationSig.Layers[0].MediaType
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, notationSig.Layers[0].Digest)
So(err, ShouldBeNil)
}
}
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: certStorage,
}
// signature is trusted
author, _, isTrusted, err := imgTrustStore.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 = imgTrustStore.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 := imagetrust.CheckExpiryErr([]*notation.ValidationResult{{Error: nil, Type: "wrongtype"}}, time.Now(),
nil)
So(isExpiryErr, ShouldBeFalse)
isExpiryErr = imagetrust.CheckExpiryErr([]*notation.ValidationResult{{
Error: nil, Type: trustpolicy.TypeAuthenticTimestamp,
}}, time.Now(), errExpiryError)
So(isExpiryErr, ShouldBeFalse)
})
Convey("expiry err", t, func() {
isExpiryErr := imagetrust.CheckExpiryErr([]*notation.ValidationResult{
{Error: errExpiryError, Type: trustpolicy.TypeExpiry},
}, time.Now(), errExpiryError)
So(isExpiryErr, ShouldBeTrue)
isExpiryErr = imagetrust.CheckExpiryErr([]*notation.ValidationResult{
{Error: errExpiryError, Type: trustpolicy.TypeAuthenticTimestamp},
}, time.Now().AddDate(0, 0, -1), errExpiryError)
So(isExpiryErr, ShouldBeTrue)
})
}
func TestLocalTrustStoreUploadErr(t *testing.T) {
Convey("certificate can't be stored", t, func() {
rootDir := t.TempDir()
signature.NotationPathLock.Lock()
defer signature.NotationPathLock.Unlock()
signature.LoadNotationPath(rootDir)
// generate a keypair
err := signature.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)
certStorage, err := imagetrust.NewCertificateLocalStorage(rootDir)
So(err, ShouldBeNil)
notationDir, err := certStorage.GetNotationDirPath()
So(err, ShouldBeNil)
err = os.Chmod(path.Join(notationDir, "truststore/x509/ca/default"), 0o100)
So(err, ShouldBeNil)
err = imagetrust.UploadCertificate(certStorage, certificateContent, "ca")
So(err, ShouldNotBeNil)
})
}
func TestLocalTrustStore(t *testing.T) {
Convey("NewLocalImageTrustStore error", t, func() {
rootDir := t.TempDir()
err := os.Chmod(rootDir, 0o000)
So(err, ShouldBeNil)
_, err = imagetrust.NewLocalImageTrustStore(rootDir)
So(err, ShouldNotBeNil)
err = os.Chmod(rootDir, 0o700)
So(err, ShouldBeNil)
notationDir := path.Join(rootDir, "_notation")
err = os.MkdirAll(notationDir, 0o000)
So(err, ShouldBeNil)
_, err = imagetrust.NewLocalImageTrustStore(rootDir)
So(err, ShouldNotBeNil)
err = os.Chmod(notationDir, 0o700)
So(err, ShouldBeNil)
err = os.MkdirAll(path.Join(notationDir, "truststore"), 0o500)
So(err, ShouldBeNil)
_, err = imagetrust.NewLocalImageTrustStore(rootDir)
So(err, ShouldNotBeNil)
err = os.Chmod(path.Join(notationDir, "truststore"), 0o700)
So(err, ShouldBeNil)
err = os.MkdirAll(path.Join(notationDir, "truststore/x509/ca/default"), 0o700)
So(err, ShouldBeNil)
err = os.Chmod(path.Join(notationDir, "truststore/x509/ca"), 0o000)
So(err, ShouldBeNil)
_, err = imagetrust.NewLocalImageTrustStore(rootDir)
So(err, ShouldNotBeNil)
err = os.Chmod(path.Join(notationDir, "truststore/x509/ca"), 0o700)
So(err, ShouldBeNil)
})
Convey("InitTrustpolicy error", t, func() {
notationStorage := &imagetrust.CertificateLocalStorage{}
err := notationStorage.InitTrustpolicy([]byte{})
So(err, ShouldNotBeNil)
})
Convey("GetVerifier error", t, func() {
notationStorage := &imagetrust.CertificateLocalStorage{}
_, err := notationStorage.GetVerifier(&trustpolicy.Document{})
So(err, ShouldNotBeNil)
})
Convey("GetPublicKeyVerifier errors", t, func() {
cosignStorage := &imagetrust.PublicKeyLocalStorage{}
_, _, err := cosignStorage.GetPublicKeyVerifier("")
So(err, ShouldNotBeNil)
rootDir := t.TempDir()
cosignStorage, err = imagetrust.NewPublicKeyLocalStorage(rootDir)
So(err, ShouldBeNil)
_, _, err = cosignStorage.GetPublicKeyVerifier("inexistentfile")
So(err, ShouldNotBeNil)
})
Convey("test with local storage", t, func() {
rootDir := t.TempDir()
imageTrustStore, err := imagetrust.NewLocalImageTrustStore(rootDir)
So(err, ShouldBeNil)
var dbDriverParams map[string]interface{}
RunUploadTests(t, *imageTrustStore)
RunVerificationTests(t, dbDriverParams)
})
}
func TestAWSTrustStore(t *testing.T) {
skipIt(t)
trustpolicy := "trustpolicy"
Convey("NewAWSImageTrustStore error", t, func() {
_, err := imagetrust.NewAWSImageTrustStore("us-east-2", "wrong;endpoint")
So(err, ShouldNotBeNil)
})
Convey("InitTrustpolicy retry", t, func() {
smanager, err := imagetrust.GetSecretsManagerClient("us-east-2", os.Getenv("DYNAMODBMOCK_ENDPOINT"))
So(err, ShouldBeNil)
smCache := imagetrust.GetSecretsManagerRetrieval("us-east-2", os.Getenv("DYNAMODBMOCK_ENDPOINT"))
description := "notation trustpolicy file"
content := "trustpolicy content"
_, err = smanager.CreateSecret(context.Background(),
&secretsmanager.CreateSecretInput{
Name: &trustpolicy,
Description: &description,
SecretString: &content,
})
So(err, ShouldBeNil)
secretsManagerMock := mocks.SecretsManagerMock{
DeleteSecretFn: func(ctx context.Context, params *secretsmanager.DeleteSecretInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.DeleteSecretOutput, error) {
return &secretsmanager.DeleteSecretOutput{}, nil
},
CreateSecretFn: func(ctx context.Context, params *secretsmanager.CreateSecretInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.CreateSecretOutput, error) {
return smanager.CreateSecret(ctx, params, optFns...)
},
}
secretsManagerCacheMock := mocks.SecretsManagerCacheMock{
GetSecretStringFn: func(secretID string) (string, error) {
return "", errUnexpectedError
},
}
_, err = imagetrust.NewCertificateAWSStorage(secretsManagerMock, secretsManagerCacheMock)
So(err, ShouldNotBeNil)
_, err = imagetrust.NewCertificateAWSStorage(secretsManagerMock, smCache)
So(err, ShouldNotBeNil)
secretsManagerMock = mocks.SecretsManagerMock{
DeleteSecretFn: func(ctx context.Context, params *secretsmanager.DeleteSecretInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.DeleteSecretOutput, error) {
return &secretsmanager.DeleteSecretOutput{}, errUnexpectedError
},
CreateSecretFn: func(ctx context.Context, params *secretsmanager.CreateSecretInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.CreateSecretOutput, error) {
return smanager.CreateSecret(ctx, params, optFns...)
},
}
_, err = imagetrust.NewCertificateAWSStorage(secretsManagerMock, smCache)
So(err, ShouldNotBeNil)
secretsManagerMock = mocks.SecretsManagerMock{
DeleteSecretFn: func(ctx context.Context, params *secretsmanager.DeleteSecretInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.DeleteSecretOutput, error) {
go func() {
time.Sleep(3 * time.Second)
smanager.DeleteSecret(ctx, params, optFns...) //nolint:errcheck
}()
return &secretsmanager.DeleteSecretOutput{}, nil
},
CreateSecretFn: func(ctx context.Context, params *secretsmanager.CreateSecretInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.CreateSecretOutput, error) {
return smanager.CreateSecret(ctx, params, optFns...)
},
}
_, err = imagetrust.NewCertificateAWSStorage(secretsManagerMock, smCache)
So(err, ShouldBeNil)
})
Convey("GetCertificates errors", t, func() {
smanager, err := imagetrust.GetSecretsManagerClient("us-east-2", os.Getenv("DYNAMODBMOCK_ENDPOINT"))
So(err, ShouldBeNil)
smCache := imagetrust.GetSecretsManagerRetrieval("us-east-2", os.Getenv("DYNAMODBMOCK_ENDPOINT"))
notationStorage, err := imagetrust.NewCertificateAWSStorage(smanager, smCache)
So(err, ShouldBeNil)
_, err = notationStorage.GetCertificates(context.Background(), "wrongType", "")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrInvalidTruststoreType)
_, err = notationStorage.GetCertificates(context.Background(), "ca", "invalid;name")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrInvalidTruststoreName)
name := "ca/test/digest"
description := "notation certificate"
content := "invalid certificate content"
_, err = smanager.CreateSecret(context.Background(),
&secretsmanager.CreateSecretInput{
Name: &name,
Description: &description,
SecretString: &content,
})
So(err, ShouldBeNil)
_, err = notationStorage.GetCertificates(context.Background(), "ca", "test")
So(err, ShouldNotBeNil)
newName := "ca/newtest/digest"
newSecret := base64.StdEncoding.EncodeToString([]byte(content))
_, err = smanager.CreateSecret(context.Background(),
&secretsmanager.CreateSecretInput{
Name: &newName,
Description: &description,
SecretString: &newSecret,
})
So(err, ShouldBeNil)
_, err = notationStorage.GetCertificates(context.Background(), "ca", "newtest")
So(err, ShouldNotBeNil)
secretsManagerMock := mocks.SecretsManagerMock{
ListSecretsFn: func(ctx context.Context, params *secretsmanager.ListSecretsInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.ListSecretsOutput, error) {
return &secretsmanager.ListSecretsOutput{}, errUnexpectedError
},
}
notationStorage, err = imagetrust.NewCertificateAWSStorage(secretsManagerMock, smCache)
So(err, ShouldBeNil)
_, err = notationStorage.GetCertificates(context.Background(), "ca", "newtest")
So(err, ShouldNotBeNil)
secretsManagerMock = mocks.SecretsManagerMock{
ListSecretsFn: func(ctx context.Context, params *secretsmanager.ListSecretsInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.ListSecretsOutput, error) {
return &secretsmanager.ListSecretsOutput{SecretList: []types.SecretListEntry{{Name: &name}}}, nil
},
}
secretsManagerCacheMock := mocks.SecretsManagerCacheMock{
GetSecretStringFn: func(secretID string) (string, error) {
return "", errUnexpectedError
},
}
notationStorage, err = imagetrust.NewCertificateAWSStorage(secretsManagerMock, secretsManagerCacheMock)
So(err, ShouldBeNil)
_, err = notationStorage.GetCertificates(context.Background(), "ca", "newtest")
So(err, ShouldNotBeNil)
})
Convey("GetPublicKeyVerifier errors", t, func() {
smanager, err := imagetrust.GetSecretsManagerClient("us-east-2", os.Getenv("DYNAMODBMOCK_ENDPOINT"))
So(err, ShouldBeNil)
smCache := imagetrust.GetSecretsManagerRetrieval("us-east-2", os.Getenv("DYNAMODBMOCK_ENDPOINT"))
cosignStorage := imagetrust.NewPublicKeyAWSStorage(smanager, smCache)
_, _, err = cosignStorage.GetPublicKeyVerifier("badsecret")
So(err, ShouldNotBeNil)
secretName := "digest"
description := "cosign public key"
secret := "invalid public key content"
_, err = smanager.CreateSecret(context.Background(),
&secretsmanager.CreateSecretInput{
Name: &secretName,
Description: &description,
SecretString: &secret,
})
So(err, ShouldBeNil)
_, _, err = cosignStorage.GetPublicKeyVerifier(secretName)
So(err, ShouldNotBeNil)
secretName = "newdigest"
newSecret := base64.StdEncoding.EncodeToString([]byte(secret))
_, err = smanager.CreateSecret(context.Background(),
&secretsmanager.CreateSecretInput{
Name: &secretName,
Description: &description,
SecretString: &newSecret,
})
So(err, ShouldBeNil)
_, _, err = cosignStorage.GetPublicKeyVerifier(secretName)
So(err, ShouldNotBeNil)
})
Convey("GetPublicKeys error", t, func() {
secretsManagerMock := mocks.SecretsManagerMock{
ListSecretsFn: func(ctx context.Context, params *secretsmanager.ListSecretsInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.ListSecretsOutput, error) {
return &secretsmanager.ListSecretsOutput{}, errUnexpectedError
},
}
cosignStorage := imagetrust.NewPublicKeyAWSStorage(secretsManagerMock, nil)
_, err := cosignStorage.GetPublicKeys()
So(err, ShouldNotBeNil)
})
Convey("StorePublicKeys error", t, func() {
secretsManagerMock := mocks.SecretsManagerMock{
CreateSecretFn: func(ctx context.Context, params *secretsmanager.CreateSecretInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.CreateSecretOutput, error) {
return &secretsmanager.CreateSecretOutput{}, errUnexpectedError
},
}
cosignStorage := imagetrust.NewPublicKeyAWSStorage(secretsManagerMock, nil)
err := cosignStorage.StorePublicKey(digest.FromString("dig"), []byte("content"))
So(err, ShouldNotBeNil)
})
Convey("VerifySignature - trustpolicy.json does not exist", t, func() {
repo := "repo"
image := CreateRandomImage()
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
secretsManagerMock := mocks.SecretsManagerMock{
CreateSecretFn: func(ctx context.Context, params *secretsmanager.CreateSecretInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.CreateSecretOutput, error) {
return &secretsmanager.CreateSecretOutput{}, nil
},
}
secretsManagerCacheMock := mocks.SecretsManagerCacheMock{
GetSecretStringFn: func(secretID string) (string, error) {
return "", errUnexpectedError
},
}
notationStorage, err := imagetrust.NewCertificateAWSStorage(secretsManagerMock, secretsManagerCacheMock)
So(err, ShouldBeNil)
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: notationStorage,
}
_, _, _, err = imgTrustStore.VerifySignature("notation", []byte("signature"), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
})
Convey("VerifySignature - trustpolicy.json has invalid content", t, func() {
repo := "repo"
image := CreateRandomImage()
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
secretsManagerMock := mocks.SecretsManagerMock{
CreateSecretFn: func(ctx context.Context, params *secretsmanager.CreateSecretInput,
optFns ...func(*secretsmanager.Options),
) (*secretsmanager.CreateSecretOutput, error) {
return &secretsmanager.CreateSecretOutput{}, nil
},
}
secretsManagerCacheMock := mocks.SecretsManagerCacheMock{
GetSecretStringFn: func(secretID string) (string, error) {
return "invalid content", nil
},
}
notationStorage, err := imagetrust.NewCertificateAWSStorage(secretsManagerMock, secretsManagerCacheMock)
So(err, ShouldBeNil)
imgTrustStore := &imagetrust.ImageTrustStore{
NotationStorage: notationStorage,
}
_, _, _, err = imgTrustStore.VerifySignature("notation", []byte("signature"), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
secretsManagerCacheMock = mocks.SecretsManagerCacheMock{
GetSecretStringFn: func(secretID string) (string, error) {
return base64.StdEncoding.EncodeToString([]byte("invalid content")), nil
},
}
notationStorage, err = imagetrust.NewCertificateAWSStorage(secretsManagerMock, secretsManagerCacheMock)
So(err, ShouldBeNil)
imgTrustStore = &imagetrust.ImageTrustStore{
NotationStorage: notationStorage,
}
_, _, _, err = imgTrustStore.VerifySignature("notation", []byte("signature"), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
secretsManagerCacheMock = mocks.SecretsManagerCacheMock{
GetSecretStringFn: func(secretID string) (string, error) {
return base64.StdEncoding.EncodeToString([]byte(`{"Version": {"bad": "input"}}`)), nil
},
}
notationStorage, err = imagetrust.NewCertificateAWSStorage(secretsManagerMock, secretsManagerCacheMock)
So(err, ShouldBeNil)
imgTrustStore = &imagetrust.ImageTrustStore{
NotationStorage: notationStorage,
}
_, _, _, err = imgTrustStore.VerifySignature("notation", []byte("signature"), "", manifestDigest,
manifestContent, repo)
So(err, ShouldNotBeNil)
})
Convey("test with AWS storage", t, func() {
uuid, err := guuid.NewV4()
if err != nil {
panic(err)
}
repoMetaTablename := "RepoMetadataTable" + uuid.String()
manifestDataTablename := "ManifestDataTable" + uuid.String()
versionTablename := "Version" + uuid.String()
indexDataTablename := "IndexDataTable" + uuid.String()
userDataTablename := "UserDataTable" + uuid.String()
apiKeyTablename := "ApiKeyTable" + uuid.String()
dynamoDBDriverParams := map[string]interface{}{
"name": "dynamoDB",
"endpoint": os.Getenv("DYNAMODBMOCK_ENDPOINT"),
"region": "us-east-2",
"repometatablename": repoMetaTablename,
"manifestdatatablename": manifestDataTablename,
"indexdatatablename": indexDataTablename,
"userdatatablename": userDataTablename,
"apikeytablename": apiKeyTablename,
"versiontablename": versionTablename,
}
t.Logf("using dynamo driver options: %v", dynamoDBDriverParams)
imageTrustStore, err := imagetrust.NewAWSImageTrustStore(
"us-east-2",
os.Getenv("DYNAMODBMOCK_ENDPOINT"),
)
So(err, ShouldBeNil)
RunUploadTests(t, *imageTrustStore)
RunVerificationTests(t, dynamoDBDriverParams)
})
}
func RunUploadTests(t *testing.T, imageTrustStore imagetrust.ImageTrustStore) { //nolint: thelper
cosignStorage := imageTrustStore.CosignStorage
notationStorage := imageTrustStore.NotationStorage
Convey("public key - invalid content", func() {
err := imagetrust.UploadPublicKey(cosignStorage, []byte("wrong content"))
So(err, ShouldNotBeNil)
})
Convey("upload public key successfully", func() {
cwd, err := os.Getwd()
So(err, ShouldBeNil)
keyDir := t.TempDir()
_ = os.Chdir(keyDir)
// 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(keyDir, "cosign.pub"))
So(err, ShouldBeNil)
So(publicKeyContent, ShouldNotBeNil)
err = imagetrust.UploadPublicKey(cosignStorage, publicKeyContent)
So(err, ShouldBeNil)
})
Convey("invalid truststore type", func() {
err := imagetrust.UploadCertificate(notationStorage,
[]byte("certificate content"), "wrongType",
)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, zerr.ErrInvalidTruststoreType)
})
Convey("invalid certificate content", func() {
content := "invalid certificate content"
err := imagetrust.UploadCertificate(notationStorage,
[]byte(content), "ca",
)
So(err, ShouldNotBeNil)
content = `-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
`
err = imagetrust.UploadCertificate(notationStorage,
[]byte(content), "ca",
)
So(err, ShouldNotBeNil)
content = ``
err = imagetrust.UploadCertificate(notationStorage,
[]byte(content), "ca",
)
So(err, ShouldNotBeNil)
})
Convey("upload certificate successfully", func() {
certDir := t.TempDir()
signature.NotationPathLock.Lock()
defer signature.NotationPathLock.Unlock()
signature.LoadNotationPath(certDir)
// generate a keypair
err := signature.GenerateNotationCerts(certDir, "notation-upload-test")
So(err, ShouldBeNil)
certificateContent, err := os.ReadFile(path.Join(certDir, "notation/localkeys", "notation-upload-test.crt"))
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
err = imagetrust.UploadCertificate(notationStorage, certificateContent, "ca")
So(err, ShouldBeNil)
})
}
func RunVerificationTests(t *testing.T, dbDriverParams map[string]interface{}) { //nolint: thelper
Convey("verify signatures are trusted", func() {
defaultValue := true
rootDir := t.TempDir()
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
So(err, ShouldBeNil)
logPath := logFile.Name()
defer os.Remove(logPath)
writers := io.MultiWriter(os.Stdout, logFile)
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.GC = false
if dbDriverParams != nil {
conf.Storage.RemoteCache = true
conf.Storage.CacheDriver = dbDriverParams
}
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Trust = &extconf.ImageTrustConfig{}
conf.Extensions.Trust.Enable = &defaultValue
conf.Extensions.Trust.Cosign = defaultValue
conf.Extensions.Trust.Notation = defaultValue
ctlr := api.NewController(conf)
ctlr.Log.Logger = ctlr.Log.Output(writers)
ctlr.Config.Storage.RootDirectory = rootDir
cm := test.NewControllerManager(ctlr)
cm.StartAndWait(conf.HTTP.Port)
defer cm.StopServer()
repo := "repo" //nolint:goconst
tag := "test" //nolint:goconst
Convey("verify cosign signature is trusted", func() {
image, err := deprecated.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
err = UploadImage(image, baseURL, repo, tag)
So(err, ShouldBeNil)
cwd, err := os.Getwd()
So(err, ShouldBeNil)
keyDir := t.TempDir()
_ = os.Chdir(keyDir)
// 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(keyDir, "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)
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[zcommon.CosignSigKey]
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, cosignSig.Layers[0].Digest)
So(err, ShouldBeNil)
}
}
publicKeyContent, err := os.ReadFile(path.Join(keyDir, "cosign.pub"))
So(err, ShouldBeNil)
So(publicKeyContent, ShouldNotBeNil)
// upload the public key
client := resty.New()
resp, err := client.R().SetHeader("Content-type", "application/octet-stream").
SetBody(publicKeyContent).Post(baseURL + constants.FullCosign)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
imageTrustStore := ctlr.MetaDB.ImageTrustStore()
// signature is trusted
author, _, isTrusted, err := imageTrustStore.VerifySignature("cosign", rawSignature, sigKey, manifestDigest,
manifestContent, repo)
So(err, ShouldBeNil)
So(isTrusted, ShouldBeTrue)
So(author, ShouldNotBeEmpty)
})
Convey("verify notation signature is trusted", func() {
image, err := deprecated.GetRandomImage() //nolint:staticcheck
So(err, ShouldBeNil)
manifestContent, err := json.Marshal(image.Manifest)
So(err, ShouldBeNil)
manifestDigest := image.Digest()
err = UploadImage(image, baseURL, repo, tag)
So(err, ShouldBeNil)
notationDir := t.TempDir()
signature.NotationPathLock.Lock()
defer signature.NotationPathLock.Unlock()
signature.LoadNotationPath(notationDir)
uuid, err := guuid.NewV4()
So(err, ShouldBeNil)
certName := fmt.Sprintf("notation-sign-test-%s", uuid)
// generate a keypair
err = signature.GenerateNotationCerts(notationDir, certName)
So(err, ShouldBeNil)
// sign the image
imageURL := fmt.Sprintf("localhost:%s/%s", port, fmt.Sprintf("%s:%s", repo, tag))
err = signature.SignWithNotation(certName, imageURL, notationDir)
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 {
blobContent, err := ctlr.StoreController.DefaultStore.GetBlobContent(repo, manifest.Digest)
So(err, ShouldBeNil)
var notationSig ispec.Manifest
err = json.Unmarshal(blobContent, &notationSig)
So(err, ShouldBeNil)
t.Logf("Processing manifest %v", notationSig)
if notationSig.Config.MediaType != notreg.ArtifactTypeNotation ||
notationSig.Subject.Digest != manifestDigest {
continue
}
sigKey = notationSig.Layers[0].MediaType
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, notationSig.Layers[0].Digest)
So(err, ShouldBeNil)
t.Logf("Identified notation signature manifest %v", notationSig)
break
}
So(sigKey, ShouldNotBeEmpty)
certificateContent, err := os.ReadFile(
path.Join(notationDir,
fmt.Sprintf("notation/truststore/x509/ca/%s", certName),
fmt.Sprintf("%s.crt", certName),
),
)
So(err, ShouldBeNil)
So(certificateContent, ShouldNotBeNil)
client := resty.New()
resp, err := client.R().SetHeader("Content-type", "application/octet-stream").
SetBody(certificateContent).Post(baseURL + constants.FullNotation)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
imageTrustStore := ctlr.MetaDB.ImageTrustStore()
// signature is trusted
author, _, isTrusted, err := imageTrustStore.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
manifestContent, repo)
So(err, ShouldBeNil)
So(isTrusted, ShouldBeTrue)
So(author, ShouldEqual, "CN=cert,O=Notary,L=Seattle,ST=WA,C=US")
})
})
}
func skipIt(t *testing.T) {
t.Helper()
if os.Getenv("DYNAMODBMOCK_ENDPOINT") == "" {
t.Skip("Skipping testing without AWS mock server")
}
}