mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 21:17:58 +08:00
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>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package test
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -0,0 +1,15 @@
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
auth "zotregistry.io/zot/pkg/test/auth"
|
||||
)
|
||||
|
||||
func TestBearerServer(t *testing.T) {
|
||||
Convey("test MakeAuthTestServer() no serve key", t, func() {
|
||||
So(func() { auth.MakeAuthTestServer("", "") }, ShouldPanic)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/project-zot/mockoidc"
|
||||
)
|
||||
|
||||
func MockOIDCRun() (*mockoidc.MockOIDC, error) {
|
||||
// Create a fresh RSA Private Key for token signing
|
||||
rsaKey, _ := rsa.GenerateKey(rand.Reader, 2048) //nolint: gomnd
|
||||
|
||||
// Create an unstarted MockOIDC server
|
||||
mockServer, _ := mockoidc.NewServer(rsaKey)
|
||||
|
||||
// Create the net.Listener, kernel will chose a valid port
|
||||
listener, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||
|
||||
bearerMiddleware := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(response http.ResponseWriter, req *http.Request) {
|
||||
// stateVal := req.Form.Get("state")
|
||||
header := req.Header.Get("Authorization")
|
||||
parts := strings.SplitN(header, " ", 2) //nolint: gomnd
|
||||
if header != "" {
|
||||
if strings.ToLower(parts[0]) == "bearer" {
|
||||
req.Header.Set("Authorization", strings.Join([]string{"Bearer", parts[1]}, " "))
|
||||
}
|
||||
}
|
||||
|
||||
next.ServeHTTP(response, req)
|
||||
})
|
||||
}
|
||||
|
||||
err := mockServer.AddMiddleware(bearerMiddleware)
|
||||
if err != nil {
|
||||
return mockServer, err
|
||||
}
|
||||
// tlsConfig can be nil if you want HTTP
|
||||
return mockServer, mockServer.Start(listener, nil)
|
||||
}
|
||||
-1534
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,246 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var ErrNoGoModFileFound = errors.New("test: no go.mod file found in parent directories")
|
||||
|
||||
func GetProjectRootDir() (string, error) {
|
||||
workDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for {
|
||||
goModPath := filepath.Join(workDir, "go.mod")
|
||||
|
||||
_, err := os.Stat(goModPath)
|
||||
if err == nil {
|
||||
return workDir, nil
|
||||
}
|
||||
|
||||
if workDir == filepath.Dir(workDir) {
|
||||
return "", ErrNoGoModFileFound
|
||||
}
|
||||
|
||||
workDir = filepath.Dir(workDir)
|
||||
}
|
||||
}
|
||||
|
||||
func CopyFile(sourceFilePath, destFilePath string) error {
|
||||
destFile, err := os.Create(destFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
sourceFile, err := os.Open(sourceFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
if _, err = io.Copy(destFile, sourceFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CopyFiles(sourceDir, destDir string) error {
|
||||
sourceMeta, err := os.Stat(sourceDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CopyFiles os.Stat failed: %w", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(destDir, sourceMeta.Mode()); err != nil {
|
||||
return fmt.Errorf("CopyFiles os.MkdirAll failed: %w", err)
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(sourceDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CopyFiles os.ReadDir failed: %w", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
sourceFilePath := path.Join(sourceDir, file.Name())
|
||||
destFilePath := path.Join(destDir, file.Name())
|
||||
|
||||
if file.IsDir() {
|
||||
if strings.HasPrefix(file.Name(), "_") {
|
||||
// Some tests create the trivy related folders under test/_trivy
|
||||
continue
|
||||
}
|
||||
|
||||
if err = CopyFiles(sourceFilePath, destFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
sourceFile, err := os.Open(sourceFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CopyFiles os.Open failed: %w", err)
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destFile, err := os.Create(destFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CopyFiles os.Create failed: %w", err)
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
if _, err = io.Copy(destFile, sourceFile); err != nil {
|
||||
return fmt.Errorf("io.Copy failed: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CopyTestKeysAndCerts(destDir string) error {
|
||||
files := []string{
|
||||
"ca.crt", "ca.key", "client.cert", "client.csr",
|
||||
"client.key", "server.cert", "server.csr", "server.key",
|
||||
}
|
||||
|
||||
rootPath, err := GetProjectRootDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceDir := filepath.Join(rootPath, "test/data")
|
||||
|
||||
sourceMeta, err := os.Stat(sourceDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CopyFiles os.Stat failed: %w", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(destDir, sourceMeta.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
err = CopyFile(filepath.Join(sourceDir, file), filepath.Join(destDir, file))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteFileWithPermission(path string, data []byte, perm fs.FileMode, overwrite bool) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
flag := os.O_WRONLY | os.O_CREATE
|
||||
|
||||
if overwrite {
|
||||
flag |= os.O_TRUNC
|
||||
} else {
|
||||
flag |= os.O_EXCL
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(path, flag, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
func ReadLogFileAndSearchString(logPath string, stringToMatch string, timeout time.Duration) (bool, error) {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancelFunc()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, nil
|
||||
default:
|
||||
content, err := os.ReadFile(logPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if strings.Contains(string(content), stringToMatch) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ReadLogFileAndCountStringOccurence(logPath string, stringToMatch string,
|
||||
timeout time.Duration, count int,
|
||||
) (bool, error) {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancelFunc()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, nil
|
||||
default:
|
||||
content, err := os.ReadFile(logPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if strings.Count(string(content), stringToMatch) >= count {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func MakeHtpasswdFile() string {
|
||||
// bcrypt(username="test", passwd="test")
|
||||
content := "test:$2y$05$hlbSXDp6hzDLu6VwACS39ORvVRpr3OMR4RlJ31jtlaOEGnPjKZI1m\n"
|
||||
|
||||
return MakeHtpasswdFileFromString(content)
|
||||
}
|
||||
|
||||
func GetCredString(username, password string) string {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
usernameAndHash := fmt.Sprintf("%s:%s", username, string(hash))
|
||||
|
||||
return usernameAndHash
|
||||
}
|
||||
|
||||
func MakeHtpasswdFileFromString(fileContent string) string {
|
||||
htpasswdFile, err := os.CreateTemp("", "htpasswd-")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// bcrypt(username="test", passwd="test")
|
||||
content := []byte(fileContent)
|
||||
if err := os.WriteFile(htpasswdFile.Name(), content, 0o600); err != nil { //nolint:gomnd
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return htpasswdFile.Name()
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
tcommon "zotregistry.io/zot/pkg/test/common"
|
||||
)
|
||||
|
||||
var ErrTestError = errors.New("ErrTestError")
|
||||
|
||||
func TestCopyFiles(t *testing.T) {
|
||||
Convey("sourceDir does not exist", t, func() {
|
||||
err := tcommon.CopyFiles("/path/to/some/unexisting/directory", os.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("destDir is a file", t, func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
err := tcommon.CopyFiles("../../../test/data", dir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = tcommon.CopyFiles(dir, "/etc/passwd")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("sourceDir does not have read permissions", t, func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
err := os.Chmod(dir, 0o300)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = tcommon.CopyFiles(dir, os.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("sourceDir has a subfolder that does not have read permissions", t, func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
sdir := "subdir"
|
||||
err := os.Mkdir(path.Join(dir, sdir), 0o300)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = tcommon.CopyFiles(dir, os.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("sourceDir has a file that does not have read permissions", t, func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
filePath := path.Join(dir, "file.txt")
|
||||
err := os.WriteFile(filePath, []byte("some dummy file content"), 0o644) //nolint: gosec
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.Chmod(filePath, 0o300)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = tcommon.CopyFiles(dir, os.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("sourceDir contains a folder starting with invalid characters", t, func() {
|
||||
srcDir := t.TempDir()
|
||||
dstDir := t.TempDir()
|
||||
|
||||
err := os.MkdirAll(path.Join(srcDir, "_trivy", "db"), 0o755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(path.Join(srcDir, "test-index"), 0o755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
filePathTrivy := path.Join(srcDir, "_trivy", "db", "trivy.db")
|
||||
err = os.WriteFile(filePathTrivy, []byte("some dummy file content"), 0o644) //nolint: gosec
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var index ispec.Index
|
||||
content, err := json.Marshal(index)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(path.Join(srcDir, "test-index", "index.json"), content, 0o644) //nolint: gosec
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = tcommon.CopyFiles(srcDir, dstDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = os.Stat(path.Join(dstDir, "_trivy", "db", "trivy.db"))
|
||||
So(err, ShouldNotBeNil)
|
||||
So(os.IsNotExist(err), ShouldBeTrue)
|
||||
|
||||
_, err = os.Stat(path.Join(dstDir, "test-index", "index.json"))
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
Convey("destFilePath does not exist", t, func() {
|
||||
err := tcommon.CopyFile("/path/to/srcFile", "~/path/to/some/unexisting/destDir/file")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("sourceFile does not exist", t, func() {
|
||||
err := tcommon.CopyFile("/path/to/some/unexisting/file", path.Join(t.TempDir(), "destFile.txt"))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadLogFileAndSearchString(t *testing.T) {
|
||||
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logPath := logFile.Name()
|
||||
defer os.Remove(logPath)
|
||||
|
||||
Convey("Invalid path", t, func() {
|
||||
_, err = tcommon.ReadLogFileAndSearchString("invalidPath",
|
||||
"DB update completed, next update scheduled", 1*time.Second)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Time too short", t, func() {
|
||||
ok, err := tcommon.ReadLogFileAndSearchString(logPath, "invalid string", time.Microsecond)
|
||||
So(err, ShouldBeNil)
|
||||
So(ok, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadLogFileAndCountStringOccurence(t *testing.T) {
|
||||
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = logFile.Write([]byte("line1\n line2\n line3 line1 line2\n line1"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logPath := logFile.Name()
|
||||
defer os.Remove(logPath)
|
||||
|
||||
Convey("Invalid path", t, func() {
|
||||
_, err = tcommon.ReadLogFileAndCountStringOccurence("invalidPath",
|
||||
"DB update completed, next update scheduled", 1*time.Second, 1)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Time too short", t, func() {
|
||||
ok, err := tcommon.ReadLogFileAndCountStringOccurence(logPath, "invalid string", time.Microsecond, 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(ok, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Count occurrence working", t, func() {
|
||||
ok, err := tcommon.ReadLogFileAndCountStringOccurence(logPath, "line1", 90*time.Second, 3)
|
||||
So(err, ShouldBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCopyTestKeysAndCerts(t *testing.T) {
|
||||
Convey("CopyTestKeysAndCerts", t, func() {
|
||||
// ------- Make test files unreadable -------
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "ca.crt")
|
||||
|
||||
_, err := os.Create(file)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(file, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = tcommon.CopyTestKeysAndCerts(dir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(file, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// ------- Copy fails -------
|
||||
|
||||
err = os.Chmod(dir, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = tcommon.CopyTestKeysAndCerts(file)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// ------- Folder creation fails -------
|
||||
|
||||
file = filepath.Join(dir, "a-file.file")
|
||||
_, err = os.Create(file)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = os.Stat(file)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = tcommon.CopyTestKeysAndCerts(file)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
+146
-19
@@ -1,15 +1,45 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/phayes/freeport"
|
||||
"gopkg.in/resty.v1"
|
||||
)
|
||||
|
||||
var ErrNoGoModFileFound = errors.New("test: no go.mod file found in parent directories")
|
||||
const (
|
||||
BaseURL = "http://127.0.0.1:%s"
|
||||
BaseSecureURL = "https://127.0.0.1:%s"
|
||||
SleepTime = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
type isser interface {
|
||||
Is(string) bool
|
||||
}
|
||||
|
||||
// Index returns the index of the first occurrence of name in s,
|
||||
// or -1 if not present.
|
||||
func Index[E isser](s []E, name string) int {
|
||||
for i, v := range s {
|
||||
if v.Is(name) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// Contains reports whether name is present in s.
|
||||
func Contains[E isser](s []E, name string) bool {
|
||||
return Index(s, name) >= 0
|
||||
}
|
||||
|
||||
func Location(baseURL string, resp *resty.Response) string {
|
||||
// For some API responses, the Location header is set and is supposed to
|
||||
@@ -29,24 +59,121 @@ func Location(baseURL string, resp *resty.Response) string {
|
||||
return baseURL + path
|
||||
}
|
||||
|
||||
func GetProjectRootDir() (string, error) {
|
||||
workDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
type Controller interface {
|
||||
Init(ctx context.Context) error
|
||||
Run(ctx context.Context) error
|
||||
Shutdown()
|
||||
GetPort() int
|
||||
}
|
||||
|
||||
for {
|
||||
goModPath := filepath.Join(workDir, "go.mod")
|
||||
type ControllerManager struct {
|
||||
controller Controller
|
||||
// used to stop background tasks(goroutines)
|
||||
cancelRoutinesFunc context.CancelFunc
|
||||
}
|
||||
|
||||
_, err := os.Stat(goModPath)
|
||||
if err == nil {
|
||||
return workDir, nil
|
||||
}
|
||||
|
||||
if workDir == filepath.Dir(workDir) {
|
||||
return "", ErrNoGoModFileFound
|
||||
}
|
||||
|
||||
workDir = filepath.Dir(workDir)
|
||||
func (cm *ControllerManager) RunServer(ctx context.Context) {
|
||||
// Useful to be able to call in the same goroutine for testing purposes
|
||||
if err := cm.controller.Run(ctx); !errors.Is(err, http.ErrServerClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ControllerManager) StartServer() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cm.cancelRoutinesFunc = cancel
|
||||
|
||||
if err := cm.controller.Init(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
cm.RunServer(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
func (cm *ControllerManager) StopServer() {
|
||||
// stop background tasks
|
||||
if cm.cancelRoutinesFunc != nil {
|
||||
cm.cancelRoutinesFunc()
|
||||
}
|
||||
|
||||
cm.controller.Shutdown()
|
||||
}
|
||||
|
||||
func (cm *ControllerManager) WaitServerToBeReady(port string) {
|
||||
url := GetBaseURL(port)
|
||||
WaitTillServerReady(url)
|
||||
}
|
||||
|
||||
func (cm *ControllerManager) StartAndWait(port string) {
|
||||
cm.StartServer()
|
||||
|
||||
url := GetBaseURL(port)
|
||||
WaitTillServerReady(url)
|
||||
}
|
||||
|
||||
func NewControllerManager(controller Controller) ControllerManager {
|
||||
cm := ControllerManager{
|
||||
controller: controller,
|
||||
}
|
||||
|
||||
return cm
|
||||
}
|
||||
|
||||
func WaitTillServerReady(url string) {
|
||||
for {
|
||||
_, err := resty.R().Get(url)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(SleepTime)
|
||||
}
|
||||
}
|
||||
|
||||
func WaitTillTrivyDBDownloadStarted(rootDir string) {
|
||||
for {
|
||||
if _, err := os.Stat(path.Join(rootDir, "_trivy", "db", "trivy.db")); err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(SleepTime)
|
||||
}
|
||||
}
|
||||
|
||||
func GetFreePort() string {
|
||||
port, err := freeport.GetFreePort()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return fmt.Sprint(port)
|
||||
}
|
||||
|
||||
func GetBaseURL(port string) string {
|
||||
return fmt.Sprintf(BaseURL, port)
|
||||
}
|
||||
|
||||
func GetSecureBaseURL(port string) string {
|
||||
return fmt.Sprintf(BaseSecureURL, port)
|
||||
}
|
||||
|
||||
func CustomRedirectPolicy(noOfRedirect int) resty.RedirectPolicy {
|
||||
return resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||
if len(via) >= noOfRedirect {
|
||||
return fmt.Errorf("stopped after %d redirects", noOfRedirect) //nolint: goerr113
|
||||
}
|
||||
|
||||
for key, val := range via[len(via)-1].Header {
|
||||
req.Header[key] = val
|
||||
}
|
||||
|
||||
respCookies := req.Response.Cookies()
|
||||
for _, cookie := range respCookies {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
tcommon "zotregistry.io/zot/pkg/test/common"
|
||||
)
|
||||
|
||||
func TestWaitTillTrivyDBDownloadStarted(t *testing.T) {
|
||||
Convey("finishes successfully", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
go func() {
|
||||
tcommon.WaitTillTrivyDBDownloadStarted(tempDir)
|
||||
}()
|
||||
|
||||
time.Sleep(tcommon.SleepTime)
|
||||
|
||||
_, err := os.Create(path.Join(tempDir, "trivy.db"))
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestControllerManager(t *testing.T) {
|
||||
Convey("Test StartServer Init() panic", t, func() {
|
||||
port := tcommon.GetFreePort()
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
|
||||
// No storage configured
|
||||
So(func() { ctlrManager.StartServer() }, ShouldPanic)
|
||||
})
|
||||
|
||||
Convey("Test RunServer panic", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Invalid port
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = "999999"
|
||||
conf.Storage.RootDirectory = tempDir
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err := ctlr.Init(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(func() { ctlrManager.RunServer(ctx) }, ShouldPanic)
|
||||
})
|
||||
}
|
||||
@@ -1,849 +0,0 @@
|
||||
//go:build sync && scrub && metrics && search
|
||||
// +build sync,scrub,metrics,search
|
||||
|
||||
package test_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
notconfig "github.com/notaryproject/notation-go/config"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
. "zotregistry.io/zot/pkg/test/image-utils"
|
||||
"zotregistry.io/zot/pkg/test/inject"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
var ErrTestError = errors.New("ErrTestError")
|
||||
|
||||
func TestCopyFiles(t *testing.T) {
|
||||
Convey("sourceDir does not exist", t, func() {
|
||||
err := test.CopyFiles("/path/to/some/unexisting/directory", os.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("destDir is a file", t, func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
test.CopyTestFiles("../../test/data", dir)
|
||||
|
||||
err := test.CopyFiles(dir, "/etc/passwd")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("sourceDir does not have read permissions", t, func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
err := os.Chmod(dir, 0o300)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.CopyFiles(dir, os.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("sourceDir has a subfolder that does not have read permissions", t, func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
sdir := "subdir"
|
||||
err := os.Mkdir(path.Join(dir, sdir), 0o300)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.CopyFiles(dir, os.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("sourceDir has a file that does not have read permissions", t, func() {
|
||||
dir := t.TempDir()
|
||||
|
||||
filePath := path.Join(dir, "file.txt")
|
||||
err := os.WriteFile(filePath, []byte("some dummy file content"), 0o644) //nolint: gosec
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.Chmod(filePath, 0o300)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.CopyFiles(dir, os.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("sourceDir contains a folder starting with invalid characters", t, func() {
|
||||
srcDir := t.TempDir()
|
||||
dstDir := t.TempDir()
|
||||
|
||||
err := os.MkdirAll(path.Join(srcDir, "_trivy", "db"), 0o755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(path.Join(srcDir, "test-index"), 0o755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
filePathTrivy := path.Join(srcDir, "_trivy", "db", "trivy.db")
|
||||
err = os.WriteFile(filePathTrivy, []byte("some dummy file content"), 0o644) //nolint: gosec
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var index ispec.Index
|
||||
content, err := json.Marshal(index)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(path.Join(srcDir, "test-index", "index.json"), content, 0o644) //nolint: gosec
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = test.CopyFiles(srcDir, dstDir)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = os.Stat(path.Join(dstDir, "_trivy", "db", "trivy.db"))
|
||||
So(err, ShouldNotBeNil)
|
||||
So(os.IsNotExist(err), ShouldBeTrue)
|
||||
|
||||
_, err = os.Stat(path.Join(dstDir, "test-index", "index.json"))
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
Convey("panic when sourceDir does not exist", t, func() {
|
||||
So(func() { test.CopyTestFiles("/path/to/some/unexisting/directory", os.TempDir()) }, ShouldPanic)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetImageComponents(t *testing.T) {
|
||||
Convey("Inject failures for unreachable lines", t, func() {
|
||||
injected := inject.InjectFailure(0)
|
||||
if injected {
|
||||
_, _, _, err := test.GetImageComponents(100)
|
||||
So(err, ShouldNotBeNil)
|
||||
}
|
||||
})
|
||||
Convey("finishes successfully", t, func() {
|
||||
_, _, _, err := test.GetImageComponents(100)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRandomImageComponents(t *testing.T) {
|
||||
Convey("Inject failures for unreachable lines", t, func() {
|
||||
injected := inject.InjectFailure(0)
|
||||
if injected {
|
||||
_, _, _, err := test.GetRandomImageComponents(100)
|
||||
So(err, ShouldNotBeNil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetImageComponentsWithConfig(t *testing.T) {
|
||||
Convey("Inject failures for unreachable lines", t, func() {
|
||||
injected := inject.InjectFailure(0)
|
||||
if injected {
|
||||
_, _, _, err := test.GetImageComponentsWithConfig(ispec.Image{})
|
||||
So(err, ShouldNotBeNil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWaitTillTrivyDBDownloadStarted(t *testing.T) {
|
||||
Convey("finishes successfully", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
go func() {
|
||||
test.WaitTillTrivyDBDownloadStarted(tempDir)
|
||||
}()
|
||||
|
||||
time.Sleep(test.SleepTime)
|
||||
|
||||
_, err := os.Create(path.Join(tempDir, "trivy.db"))
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestControllerManager(t *testing.T) {
|
||||
Convey("Test StartServer Init() panic", t, func() {
|
||||
port := test.GetFreePort()
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
ctlrManager := test.NewControllerManager(ctlr)
|
||||
|
||||
// No storage configured
|
||||
So(func() { ctlrManager.StartServer() }, ShouldPanic)
|
||||
})
|
||||
|
||||
Convey("Test RunServer panic", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Invalid port
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = "999999"
|
||||
conf.Storage.RootDirectory = tempDir
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
ctlrManager := test.NewControllerManager(ctlr)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err := ctlr.Init(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(func() { ctlrManager.RunServer(ctx) }, ShouldPanic)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadLogFileAndSearchString(t *testing.T) {
|
||||
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logPath := logFile.Name()
|
||||
defer os.Remove(logPath)
|
||||
|
||||
Convey("Invalid path", t, func() {
|
||||
_, err = test.ReadLogFileAndSearchString("invalidPath", "DB update completed, next update scheduled", 1*time.Second)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Time too short", t, func() {
|
||||
ok, err := test.ReadLogFileAndSearchString(logPath, "invalid string", time.Microsecond)
|
||||
So(err, ShouldBeNil)
|
||||
So(ok, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadLogFileAndCountStringOccurence(t *testing.T) {
|
||||
logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = logFile.Write([]byte("line1\n line2\n line3 line1 line2\n line1"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logPath := logFile.Name()
|
||||
defer os.Remove(logPath)
|
||||
|
||||
Convey("Invalid path", t, func() {
|
||||
_, err = test.ReadLogFileAndCountStringOccurence("invalidPath",
|
||||
"DB update completed, next update scheduled", 1*time.Second, 1)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Time too short", t, func() {
|
||||
ok, err := test.ReadLogFileAndCountStringOccurence(logPath, "invalid string", time.Microsecond, 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(ok, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Count occurrence working", t, func() {
|
||||
ok, err := test.ReadLogFileAndCountStringOccurence(logPath, "line1", 90*time.Second, 3)
|
||||
So(err, ShouldBeNil)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
Convey("destFilePath does not exist", t, func() {
|
||||
err := test.CopyFile("/path/to/srcFile", "~/path/to/some/unexisting/destDir/file")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("sourceFile does not exist", t, func() {
|
||||
err := test.CopyFile("/path/to/some/unexisting/file", path.Join(t.TempDir(), "destFile.txt"))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsDigestReference(t *testing.T) {
|
||||
Convey("not digest reference", t, func() {
|
||||
res := test.IsDigestReference("notDigestReference/input")
|
||||
So(res, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("wrong input format", t, func() {
|
||||
res := test.IsDigestReference("wrongInput")
|
||||
So(res, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadNotationSigningkeys(t *testing.T) {
|
||||
Convey("notation directory doesn't exist", t, func() {
|
||||
_, err := test.LoadNotationSigningkeys(t.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("wrong content of signingkeys.json", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("some dummy file content"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = test.LoadNotationSigningkeys(tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("not enough permissions to access signingkeys.json", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("some dummy file content"), 0o300) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = test.LoadNotationSigningkeys(tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("signingkeys.json not exists so it is created successfully", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = test.LoadNotationSigningkeys(tempDir)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("signingkeys.json not exists - error trying to create it", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
// create notation directory without write permissions
|
||||
err := os.Mkdir(dir, 0o555)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = test.LoadNotationSigningkeys(tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadNotationConfig(t *testing.T) {
|
||||
Convey("directory doesn't exist", t, func() {
|
||||
_, err := test.LoadNotationConfig(t.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("wrong content of signingkeys.json", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("some dummy file content"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = test.LoadNotationConfig(tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("check default value of signature format", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("{\"SignatureFormat\": \"\"}"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configInfo, err := test.LoadNotationConfig(tempDir)
|
||||
So(err, ShouldBeNil)
|
||||
So(configInfo.SignatureFormat, ShouldEqual, "jws")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSignWithNotation(t *testing.T) {
|
||||
Convey("notation directory doesn't exist", t, func() {
|
||||
err := test.SignWithNotation("key", "reference", t.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("key not found", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("{}"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.SignWithNotation("key", "reference", tempDir)
|
||||
So(err, ShouldEqual, test.ErrKeyNotFound)
|
||||
})
|
||||
|
||||
Convey("not enough permissions to access notation/localkeys dir", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tdir)
|
||||
|
||||
err = test.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(path.Join(tdir, "notation", "localkeys"), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.SignWithNotation("key", "reference", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(path.Join(tdir, "notation", "localkeys"), 0o755)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("error parsing reference", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tdir)
|
||||
|
||||
err = test.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.SignWithNotation("key", "invalidReference", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("error signing", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tdir)
|
||||
|
||||
err = test.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.SignWithNotation("key", "localhost:8080/invalidreference:1.0", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyWithNotation(t *testing.T) {
|
||||
Convey("notation directory doesn't exist", t, func() {
|
||||
err := test.VerifyWithNotation("reference", t.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("error parsing reference", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tdir)
|
||||
|
||||
err = test.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.VerifyWithNotation("invalidReference", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("error trying to get manifest", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tdir)
|
||||
|
||||
err = test.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.VerifyWithNotation("localhost:8080/invalidreference:1.0", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("invalid content of trustpolicy.json", t, func() {
|
||||
// start a new server
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
dir := t.TempDir()
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.RootDirectory = dir
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
// this blocks
|
||||
cm.StartAndWait(port)
|
||||
defer cm.StopServer()
|
||||
|
||||
repoName := "signed-repo"
|
||||
tag := "1.0"
|
||||
cfg, layers, manifest, err := test.GetImageComponents(2)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = UploadImage(
|
||||
Image{
|
||||
Config: cfg,
|
||||
Layers: layers,
|
||||
Manifest: manifest,
|
||||
}, baseURL, repoName, tag)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
content, err := json.Marshal(manifest)
|
||||
So(err, ShouldBeNil)
|
||||
digest := godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
tempDir := t.TempDir()
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err = os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(notationDir, "trustpolicy.json")
|
||||
err = os.WriteFile(filePath, []byte("some dummy file content"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tempDir)
|
||||
|
||||
err = test.VerifyWithNotation(fmt.Sprintf("localhost:%s/%s:%s", port, repoName, tag), tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListNotarySignatures(t *testing.T) {
|
||||
Convey("error parsing reference", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
_, err = test.ListNotarySignatures("invalidReference", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("error trying to get manifest", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
_, err = test.ListNotarySignatures("localhost:8080/invalidreference:1.0", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateNotationCerts(t *testing.T) {
|
||||
Convey("write key file with permission", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(notationDir, "localkeys")
|
||||
err = os.WriteFile(filePath, []byte("{}"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tempDir)
|
||||
|
||||
err = test.GenerateNotationCerts(t.TempDir(), "cert")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("write cert file with permission", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation", "localkeys")
|
||||
err := os.MkdirAll(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(notationDir, "cert.crt")
|
||||
err = os.WriteFile(filePath, []byte("{}"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(filePath, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tempDir)
|
||||
|
||||
err = test.GenerateNotationCerts(t.TempDir(), "cert")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(filePath, 0o755)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("signingkeys.json file - not enough permission", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(notationDir, "signingkeys.json")
|
||||
_, err = os.Create(filePath) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Chmod(filePath, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tempDir)
|
||||
|
||||
err = test.GenerateNotationCerts(t.TempDir(), "cert")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Remove(filePath)
|
||||
So(err, ShouldBeNil)
|
||||
err = os.RemoveAll(path.Join(notationDir, "localkeys"))
|
||||
So(err, ShouldBeNil)
|
||||
signingKeysBuf, err := json.Marshal(notconfig.SigningKeys{})
|
||||
So(err, ShouldBeNil)
|
||||
err = os.WriteFile(filePath, signingKeysBuf, 0o555) //nolint:gosec // test code
|
||||
So(err, ShouldBeNil)
|
||||
err = test.GenerateNotationCerts(t.TempDir(), "cert")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("keysuite already exists in signingkeys.json", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certName := "cert-test"
|
||||
filePath := path.Join(notationDir, "signingkeys.json")
|
||||
keyPath := path.Join(notationDir, "localkeys", certName+".key")
|
||||
certPath := path.Join(notationDir, "localkeys", certName+".crt")
|
||||
signingKeys := notconfig.SigningKeys{}
|
||||
keySuite := notconfig.KeySuite{
|
||||
Name: certName,
|
||||
X509KeyPair: ¬config.X509KeyPair{
|
||||
KeyPath: keyPath,
|
||||
CertificatePath: certPath,
|
||||
},
|
||||
}
|
||||
signingKeys.Keys = []notconfig.KeySuite{keySuite}
|
||||
signingKeysBuf, err := json.Marshal(signingKeys)
|
||||
So(err, ShouldBeNil)
|
||||
err = os.WriteFile(filePath, signingKeysBuf, 0o600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tempDir)
|
||||
|
||||
err = test.GenerateNotationCerts(t.TempDir(), certName)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("truststore files", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certName := "cert-test"
|
||||
trustStorePath := path.Join(notationDir, fmt.Sprintf("truststore/x509/ca/%s", certName))
|
||||
err = os.MkdirAll(trustStorePath, 0o755)
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Chmod(path.Join(notationDir, "truststore/x509"), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(tempDir)
|
||||
|
||||
err = test.GenerateNotationCerts(tempDir, certName)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.RemoveAll(path.Join(notationDir, "localkeys"))
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Chmod(path.Join(notationDir, "truststore/x509"), 0o755)
|
||||
So(err, ShouldBeNil)
|
||||
_, err = os.Create(path.Join(trustStorePath, "cert-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.GenerateNotationCerts(tempDir, certName)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.RemoveAll(path.Join(notationDir, "localkeys"))
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Remove(path.Join(trustStorePath, "cert-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Chmod(path.Join(notationDir, "truststore/x509/ca", certName), 0o555)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.GenerateNotationCerts(tempDir, certName)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWriteImageToFileSystem(t *testing.T) {
|
||||
Convey("WriteImageToFileSystem errors", t, func() {
|
||||
err := test.WriteImageToFileSystem(Image{}, "repo", "dig", storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
InitRepoFn: func(name string) error {
|
||||
return ErrTestError
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = test.WriteImageToFileSystem(
|
||||
Image{Layers: [][]byte{[]byte("testLayer")}},
|
||||
"repo",
|
||||
"tag",
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
FullBlobUploadFn: func(repo string, body io.Reader, digest godigest.Digest,
|
||||
) (string, int64, error) {
|
||||
return "", 0, ErrTestError
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
count := 0
|
||||
err = test.WriteImageToFileSystem(
|
||||
Image{Layers: [][]byte{[]byte("testLayer")}},
|
||||
"repo",
|
||||
"tag",
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
FullBlobUploadFn: func(repo string, body io.Reader, digest godigest.Digest,
|
||||
) (string, int64, error) {
|
||||
if count == 0 {
|
||||
count++
|
||||
|
||||
return "", 0, nil
|
||||
}
|
||||
|
||||
return "", 0, ErrTestError
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = test.WriteImageToFileSystem(
|
||||
Image{Layers: [][]byte{[]byte("testLayer")}},
|
||||
"repo",
|
||||
"tag",
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte,
|
||||
) (godigest.Digest, godigest.Digest, error) {
|
||||
return "", "", ErrTestError
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBearerServer(t *testing.T) {
|
||||
Convey("test MakeAuthTestServer() no serve key", t, func() {
|
||||
So(func() { test.MakeAuthTestServer("", "") }, ShouldPanic)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCopyTestKeysAndCerts(t *testing.T) {
|
||||
Convey("CopyTestKeysAndCerts", t, func() {
|
||||
// ------- Make test files unreadable -------
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(dir, "ca.crt")
|
||||
|
||||
_, err := os.Create(file)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(file, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.CopyTestKeysAndCerts(dir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(file, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// ------- Copy fails -------
|
||||
|
||||
err = os.Chmod(dir, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.CopyTestKeysAndCerts(file)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// ------- Folder creation fails -------
|
||||
|
||||
file = filepath.Join(dir, "a-file.file")
|
||||
_, err = os.Create(file)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = os.Stat(file)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.CopyTestKeysAndCerts(file)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
package deprecated
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/specs-go"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
"zotregistry.io/zot/pkg/test/image-utils"
|
||||
"zotregistry.io/zot/pkg/test/inject"
|
||||
)
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manifest, error) {
|
||||
config := ispec.Image{
|
||||
Platform: ispec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: ispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
},
|
||||
Author: "ZotUser",
|
||||
}
|
||||
|
||||
configBlob, err := json.Marshal(config)
|
||||
if err = inject.Error(err); err != nil {
|
||||
return ispec.Image{}, [][]byte{}, ispec.Manifest{}, err
|
||||
}
|
||||
|
||||
configDigest := godigest.FromBytes(configBlob)
|
||||
|
||||
layers := [][]byte{
|
||||
make([]byte, layerSize),
|
||||
}
|
||||
|
||||
schemaVersion := 2
|
||||
|
||||
manifest := ispec.Manifest{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: schemaVersion,
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: configDigest,
|
||||
Size: int64(len(configBlob)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: godigest.FromBytes(layers[0]),
|
||||
Size: int64(len(layers[0])),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return config, layers, manifest, nil
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetRandomImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manifest, error) {
|
||||
config := ispec.Image{
|
||||
Platform: ispec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: ispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
},
|
||||
Author: "ZotUser",
|
||||
}
|
||||
|
||||
configBlob, err := json.Marshal(config)
|
||||
if err = inject.Error(err); err != nil {
|
||||
return ispec.Image{}, [][]byte{}, ispec.Manifest{}, err
|
||||
}
|
||||
|
||||
configDigest := godigest.FromBytes(configBlob)
|
||||
|
||||
layers := [][]byte{
|
||||
GetRandomLayer(layerSize),
|
||||
}
|
||||
|
||||
schemaVersion := 2
|
||||
|
||||
manifest := ispec.Manifest{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: schemaVersion,
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: configDigest,
|
||||
Size: int64(len(configBlob)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: godigest.FromBytes(layers[0]),
|
||||
Size: int64(len(layers[0])),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return config, layers, manifest, nil
|
||||
}
|
||||
|
||||
func GetRandomLayer(size int) []byte {
|
||||
layer := make([]byte, size)
|
||||
|
||||
_, err := rand.Read(layer)
|
||||
if err != nil {
|
||||
return layer
|
||||
}
|
||||
|
||||
return layer
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetVulnImageWithConfig(config ispec.Image) (image.Image, error) {
|
||||
vulnerableLayer, err := image.GetLayerWithVulnerability()
|
||||
if err != nil {
|
||||
return image.Image{}, err
|
||||
}
|
||||
|
||||
vulnerableConfig := ispec.Image{
|
||||
Platform: config.Platform,
|
||||
Config: config.Config,
|
||||
RootFS: ispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{"sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5"},
|
||||
},
|
||||
Created: config.Created,
|
||||
History: config.History,
|
||||
}
|
||||
|
||||
img, err := GetImageWithComponents(
|
||||
vulnerableConfig,
|
||||
[][]byte{
|
||||
vulnerableLayer,
|
||||
})
|
||||
if err != nil {
|
||||
return image.Image{}, err
|
||||
}
|
||||
|
||||
return img, err
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetRandomImage() (image.Image, error) {
|
||||
const layerSize = 20
|
||||
|
||||
config, layers, manifest, err := GetRandomImageComponents(layerSize)
|
||||
if err != nil {
|
||||
return image.Image{}, err
|
||||
}
|
||||
|
||||
return image.Image{
|
||||
Manifest: manifest,
|
||||
Layers: layers,
|
||||
Config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetImageComponentsWithConfig(conf ispec.Image) (ispec.Image, [][]byte, ispec.Manifest, error) {
|
||||
configBlob, err := json.Marshal(conf)
|
||||
if err = inject.Error(err); err != nil {
|
||||
return ispec.Image{}, [][]byte{}, ispec.Manifest{}, err
|
||||
}
|
||||
|
||||
configDigest := godigest.FromBytes(configBlob)
|
||||
|
||||
layerSize := 100
|
||||
layer := make([]byte, layerSize)
|
||||
|
||||
_, err = rand.Read(layer)
|
||||
if err != nil {
|
||||
return ispec.Image{}, [][]byte{}, ispec.Manifest{}, err
|
||||
}
|
||||
|
||||
layers := [][]byte{
|
||||
layer,
|
||||
}
|
||||
|
||||
schemaVersion := 2
|
||||
|
||||
manifest := ispec.Manifest{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: schemaVersion,
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: configDigest,
|
||||
Size: int64(len(configBlob)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: godigest.FromBytes(layers[0]),
|
||||
Size: int64(len(layers[0])),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return conf, layers, manifest, nil
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetImageWithConfig(conf ispec.Image) (image.Image, error) {
|
||||
config, layers, manifest, err := GetImageComponentsWithConfig(conf)
|
||||
if err != nil {
|
||||
return image.Image{}, err
|
||||
}
|
||||
|
||||
return image.Image{
|
||||
Manifest: manifest,
|
||||
Config: config,
|
||||
Layers: layers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetImageWithComponents(config ispec.Image, layers [][]byte) (image.Image, error) {
|
||||
configBlob, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return image.Image{}, err
|
||||
}
|
||||
|
||||
manifestLayers := make([]ispec.Descriptor, 0, len(layers))
|
||||
|
||||
for _, layer := range layers {
|
||||
manifestLayers = append(manifestLayers, ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: godigest.FromBytes(layer),
|
||||
Size: int64(len(layer)),
|
||||
})
|
||||
}
|
||||
|
||||
const schemaVersion = 2
|
||||
|
||||
manifest := ispec.Manifest{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: schemaVersion,
|
||||
},
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: "application/vnd.oci.image.config.v1+json",
|
||||
Digest: godigest.FromBytes(configBlob),
|
||||
Size: int64(len(configBlob)),
|
||||
},
|
||||
Layers: manifestLayers,
|
||||
}
|
||||
|
||||
return image.Image{
|
||||
Manifest: manifest,
|
||||
Config: config,
|
||||
Layers: layers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetImageWithSubject(subjectDigest godigest.Digest, mediaType string) (image.Image, error) {
|
||||
num := 100
|
||||
|
||||
conf, layers, manifest, err := GetRandomImageComponents(num)
|
||||
if err != nil {
|
||||
return image.Image{}, err
|
||||
}
|
||||
|
||||
manifest.Subject = &ispec.Descriptor{
|
||||
Digest: subjectDigest,
|
||||
MediaType: mediaType,
|
||||
}
|
||||
|
||||
return image.Image{
|
||||
Manifest: manifest,
|
||||
Config: conf,
|
||||
Layers: layers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetRandomMultiarchImageComponents() (ispec.Index, []image.Image, error) {
|
||||
const layerSize = 100
|
||||
|
||||
randomLayer1 := make([]byte, layerSize)
|
||||
|
||||
_, err := rand.Read(randomLayer1)
|
||||
if err != nil {
|
||||
return ispec.Index{}, []image.Image{}, err
|
||||
}
|
||||
|
||||
image1, err := GetImageWithComponents(
|
||||
ispec.Image{
|
||||
Platform: ispec.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
},
|
||||
[][]byte{
|
||||
randomLayer1,
|
||||
})
|
||||
if err != nil {
|
||||
return ispec.Index{}, []image.Image{}, err
|
||||
}
|
||||
|
||||
randomLayer2 := make([]byte, layerSize)
|
||||
|
||||
_, err = rand.Read(randomLayer2)
|
||||
if err != nil {
|
||||
return ispec.Index{}, []image.Image{}, err
|
||||
}
|
||||
|
||||
image2, err := GetImageWithComponents(
|
||||
ispec.Image{
|
||||
Platform: ispec.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "386",
|
||||
},
|
||||
},
|
||||
[][]byte{
|
||||
randomLayer2,
|
||||
})
|
||||
if err != nil {
|
||||
return ispec.Index{}, []image.Image{}, err
|
||||
}
|
||||
|
||||
randomLayer3 := make([]byte, layerSize)
|
||||
|
||||
_, err = rand.Read(randomLayer3)
|
||||
if err != nil {
|
||||
return ispec.Index{}, []image.Image{}, err
|
||||
}
|
||||
|
||||
image3, err := GetImageWithComponents(
|
||||
ispec.Image{
|
||||
Platform: ispec.Platform{
|
||||
OS: "windows",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
},
|
||||
[][]byte{
|
||||
randomLayer3,
|
||||
})
|
||||
if err != nil {
|
||||
return ispec.Index{}, []image.Image{}, err
|
||||
}
|
||||
|
||||
index := ispec.Index{
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
Manifests: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Digest: getManifestDigest(image1.Manifest),
|
||||
Size: getManifestSize(image1.Manifest),
|
||||
},
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Digest: getManifestDigest(image2.Manifest),
|
||||
Size: getManifestSize(image2.Manifest),
|
||||
},
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Digest: getManifestDigest(image3.Manifest),
|
||||
Size: getManifestSize(image3.Manifest),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return index, []image.Image{image1, image2, image3}, nil
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetRandomMultiarchImage(reference string) (image.MultiarchImage, error) {
|
||||
index, images, err := GetRandomMultiarchImageComponents()
|
||||
if err != nil {
|
||||
return image.MultiarchImage{}, err
|
||||
}
|
||||
|
||||
index.SchemaVersion = 2
|
||||
|
||||
return image.MultiarchImage{
|
||||
Index: index, Images: images, Reference: reference,
|
||||
}, err
|
||||
}
|
||||
|
||||
// Deprecated: Should use the new functions starting with "Create".
|
||||
func GetMultiarchImageForImages(images []image.Image) image.MultiarchImage {
|
||||
var index ispec.Index
|
||||
|
||||
for _, image := range images {
|
||||
index.Manifests = append(index.Manifests, ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Digest: getManifestDigest(image.Manifest),
|
||||
Size: getManifestSize(image.Manifest),
|
||||
})
|
||||
}
|
||||
|
||||
index.SchemaVersion = 2
|
||||
|
||||
return image.MultiarchImage{Index: index, Images: images}
|
||||
}
|
||||
|
||||
func getManifestSize(manifest ispec.Manifest) int64 {
|
||||
manifestBlob, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return int64(len(manifestBlob))
|
||||
}
|
||||
|
||||
func getManifestDigest(manifest ispec.Manifest) godigest.Digest {
|
||||
manifestBlob, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return godigest.FromBytes(manifestBlob)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package deprecated_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/test/deprecated"
|
||||
"zotregistry.io/zot/pkg/test/inject"
|
||||
)
|
||||
|
||||
func TestGetImageComponents(t *testing.T) {
|
||||
Convey("Inject failures for unreachable lines", t, func() {
|
||||
injected := inject.InjectFailure(0)
|
||||
if injected {
|
||||
_, _, _, err := deprecated.GetImageComponents(100) //nolint:staticcheck
|
||||
So(err, ShouldNotBeNil)
|
||||
}
|
||||
})
|
||||
Convey("finishes successfully", t, func() {
|
||||
_, _, _, err := deprecated.GetImageComponents(100) //nolint:staticcheck
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRandomImageComponents(t *testing.T) {
|
||||
Convey("Inject failures for unreachable lines", t, func() {
|
||||
injected := inject.InjectFailure(0)
|
||||
if injected {
|
||||
_, _, _, err := deprecated.GetRandomImageComponents(100) //nolint:staticcheck
|
||||
So(err, ShouldNotBeNil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetImageComponentsWithConfig(t *testing.T) {
|
||||
Convey("Inject failures for unreachable lines", t, func() {
|
||||
injected := inject.InjectFailure(0)
|
||||
if injected {
|
||||
_, _, _, err := deprecated.GetImageComponentsWithConfig(ispec.Image{}) //nolint:staticcheck
|
||||
So(err, ShouldNotBeNil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gopkg.in/resty.v1"
|
||||
|
||||
testc "zotregistry.io/zot/pkg/test/common"
|
||||
tcommon "zotregistry.io/zot/pkg/test/common"
|
||||
"zotregistry.io/zot/pkg/test/inject"
|
||||
)
|
||||
|
||||
@@ -81,7 +81,7 @@ func UploadImage(img Image, baseURL, repo, ref string) error {
|
||||
return ErrPostBlob
|
||||
}
|
||||
|
||||
loc := testc.Location(baseURL, resp)
|
||||
loc := tcommon.Location(baseURL, resp)
|
||||
|
||||
// uploading blob should get 201
|
||||
resp, err = resty.R().
|
||||
@@ -181,7 +181,7 @@ func UploadImageWithBasicAuth(img Image, baseURL, repo, ref, user, password stri
|
||||
return ErrPostBlob
|
||||
}
|
||||
|
||||
loc := testc.Location(baseURL, resp)
|
||||
loc := tcommon.Location(baseURL, resp)
|
||||
|
||||
// uploading blob should get 201
|
||||
resp, err = resty.R().
|
||||
|
||||
@@ -13,15 +13,15 @@ import (
|
||||
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
. "zotregistry.io/zot/pkg/test"
|
||||
tcommon "zotregistry.io/zot/pkg/test/common"
|
||||
. "zotregistry.io/zot/pkg/test/image-utils"
|
||||
"zotregistry.io/zot/pkg/test/inject"
|
||||
)
|
||||
|
||||
func TestUploadImage(t *testing.T) {
|
||||
Convey("Manifest without schemaVersion should fail validation", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
@@ -29,7 +29,7 @@ func TestUploadImage(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
@@ -57,8 +57,8 @@ func TestUploadImage(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Post request results in an error", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
@@ -73,8 +73,8 @@ func TestUploadImage(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Post request status differs from accepted", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
tempDir := t.TempDir()
|
||||
conf := config.New()
|
||||
@@ -88,7 +88,7 @@ func TestUploadImage(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
@@ -101,8 +101,8 @@ func TestUploadImage(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Put request results in an error", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
@@ -110,7 +110,7 @@ func TestUploadImage(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
@@ -124,8 +124,8 @@ func TestUploadImage(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Image uploaded successfully", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
@@ -133,7 +133,7 @@ func TestUploadImage(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
@@ -166,13 +166,13 @@ func TestUploadImage(t *testing.T) {
|
||||
Convey("Upload image with authentification", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
conf := config.New()
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
user1 := "test"
|
||||
password1 := "test"
|
||||
testString1 := GetCredString(user1, password1)
|
||||
htpasswdPath := MakeHtpasswdFileFromString(testString1)
|
||||
testString1 := tcommon.GetCredString(user1, password1)
|
||||
htpasswdPath := tcommon.MakeHtpasswdFileFromString(testString1)
|
||||
defer os.Remove(htpasswdPath)
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
HTPasswd: config.AuthHTPasswd{
|
||||
@@ -213,7 +213,7 @@ func TestUploadImage(t *testing.T) {
|
||||
|
||||
ctlr.Config.Storage.RootDirectory = tempDir
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
@@ -236,8 +236,8 @@ func TestUploadImage(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Blob upload wrong response status code", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
tempDir := t.TempDir()
|
||||
conf := config.New()
|
||||
@@ -246,7 +246,7 @@ func TestUploadImage(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
@@ -289,8 +289,8 @@ func TestUploadImage(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("CreateBlobUpload wrong response status code", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
tempDir := t.TempDir()
|
||||
conf := config.New()
|
||||
@@ -299,7 +299,7 @@ func TestUploadImage(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
@@ -331,8 +331,8 @@ func TestUploadImage(t *testing.T) {
|
||||
|
||||
func TestInjectUploadImage(t *testing.T) {
|
||||
Convey("Inject failures for unreachable lines", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
tempDir := t.TempDir()
|
||||
conf := config.New()
|
||||
@@ -341,7 +341,7 @@ func TestInjectUploadImage(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
@@ -395,8 +395,8 @@ func TestInjectUploadImage(t *testing.T) {
|
||||
|
||||
func TestUploadMultiarchImage(t *testing.T) {
|
||||
Convey("make controller", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
@@ -404,7 +404,7 @@ func TestUploadMultiarchImage(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
@@ -474,8 +474,8 @@ func TestUploadMultiarchImage(t *testing.T) {
|
||||
|
||||
func TestInjectUploadImageWithBasicAuth(t *testing.T) {
|
||||
Convey("Inject failures for unreachable lines", t, func() {
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
|
||||
tempDir := t.TempDir()
|
||||
conf := config.New()
|
||||
@@ -484,8 +484,8 @@ func TestInjectUploadImageWithBasicAuth(t *testing.T) {
|
||||
|
||||
user := "user"
|
||||
password := "password"
|
||||
testString := GetCredString(user, password)
|
||||
htpasswdPath := MakeHtpasswdFileFromString(testString)
|
||||
testString := tcommon.GetCredString(user, password)
|
||||
htpasswdPath := tcommon.MakeHtpasswdFileFromString(testString)
|
||||
defer os.Remove(htpasswdPath)
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
HTPasswd: config.AuthHTPasswd{
|
||||
@@ -495,7 +495,7 @@ func TestInjectUploadImageWithBasicAuth(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"math/big"
|
||||
mathRand "math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -9,7 +13,7 @@ import (
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
testc "zotregistry.io/zot/pkg/test/common"
|
||||
tcommon "zotregistry.io/zot/pkg/test/common"
|
||||
)
|
||||
|
||||
var vulnerableLayer []byte //nolint: gochecknoglobals
|
||||
@@ -26,7 +30,7 @@ func GetLayerWithVulnerability() ([]byte, error) {
|
||||
return vulnerableLayer, nil
|
||||
}
|
||||
|
||||
projectRootDir, err := testc.GetProjectRootDir()
|
||||
projectRootDir, err := tcommon.GetProjectRootDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -127,3 +131,66 @@ func GetDefaultVulnConfig() ispec.Image {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from https://gist.github.com/dopey/c69559607800d2f2f90b1b1ed4e550fb
|
||||
func RandomString(n int) string {
|
||||
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||
|
||||
ret := make([]byte, n)
|
||||
|
||||
for count := 0; count < n; count++ {
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ret[count] = letters[num.Int64()]
|
||||
}
|
||||
|
||||
return string(ret)
|
||||
}
|
||||
|
||||
func GetRandomImageConfig() ([]byte, godigest.Digest) {
|
||||
const maxLen = 16
|
||||
|
||||
randomAuthor := RandomString(maxLen)
|
||||
|
||||
config := ispec.Image{
|
||||
Platform: ispec.Platform{
|
||||
Architecture: "amd64",
|
||||
OS: "linux",
|
||||
},
|
||||
RootFS: ispec.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []godigest.Digest{},
|
||||
},
|
||||
Author: randomAuthor,
|
||||
}
|
||||
|
||||
configBlobContent, err := json.MarshalIndent(&config, "", "\t")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
configBlobDigestRaw := godigest.FromBytes(configBlobContent)
|
||||
|
||||
return configBlobContent, configBlobDigestRaw
|
||||
}
|
||||
|
||||
func GetIndexBlobWithManifests(manifestDigests []godigest.Digest) ([]byte, error) {
|
||||
manifests := make([]ispec.Descriptor, 0, len(manifestDigests))
|
||||
|
||||
for _, manifestDigest := range manifestDigests {
|
||||
manifests = append(manifests, ispec.Descriptor{
|
||||
Digest: manifestDigest,
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
})
|
||||
}
|
||||
|
||||
indexContent := ispec.Index{
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
Manifests: manifests,
|
||||
}
|
||||
|
||||
return json.Marshal(indexContent)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
stypes "zotregistry.io/zot/pkg/storage/types"
|
||||
)
|
||||
|
||||
func WriteImageToFileSystem(image Image, repoName, ref string, storeController stypes.StoreController) error {
|
||||
store := storeController.GetImageStore(repoName)
|
||||
|
||||
err := store.InitRepo(repoName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, layerBlob := range image.Layers {
|
||||
layerReader := bytes.NewReader(layerBlob)
|
||||
layerDigest := godigest.FromBytes(layerBlob)
|
||||
|
||||
_, _, err = store.FullBlobUpload(repoName, layerReader, layerDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
configBlob, err := json.Marshal(image.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configReader := bytes.NewReader(configBlob)
|
||||
configDigest := godigest.FromBytes(configBlob)
|
||||
|
||||
_, _, err = store.FullBlobUpload(repoName, configReader, configDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifestBlob, err := json.Marshal(image.Manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = store.PutImageManifest(repoName, ref, ispec.MediaTypeImageManifest, manifestBlob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteMultiArchImageToFileSystem(multiarchImage MultiarchImage, repoName, ref string,
|
||||
storeController stypes.StoreController,
|
||||
) error {
|
||||
store := storeController.GetImageStore(repoName)
|
||||
|
||||
err := store.InitRepo(repoName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, image := range multiarchImage.Images {
|
||||
err := WriteImageToFileSystem(image, repoName, image.DigestStr(), storeController)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
indexBlob, err := json.Marshal(multiarchImage.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = store.PutImageManifest(repoName, ref, ispec.MediaTypeImageIndex,
|
||||
indexBlob)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package image_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
. "zotregistry.io/zot/pkg/test/image-utils"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
var ErrTestError = errors.New("ErrTestError")
|
||||
|
||||
func TestWriteImageToFileSystem(t *testing.T) {
|
||||
Convey("WriteImageToFileSystem errors", t, func() {
|
||||
err := WriteImageToFileSystem(Image{}, "repo", "dig", storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
InitRepoFn: func(name string) error {
|
||||
return ErrTestError
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = WriteImageToFileSystem(
|
||||
Image{Layers: [][]byte{[]byte("testLayer")}},
|
||||
"repo",
|
||||
"tag",
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
FullBlobUploadFn: func(repo string, body io.Reader, digest godigest.Digest,
|
||||
) (string, int64, error) {
|
||||
return "", 0, ErrTestError
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
count := 0
|
||||
err = WriteImageToFileSystem(
|
||||
Image{Layers: [][]byte{[]byte("testLayer")}},
|
||||
"repo",
|
||||
"tag",
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
FullBlobUploadFn: func(repo string, body io.Reader, digest godigest.Digest,
|
||||
) (string, int64, error) {
|
||||
if count == 0 {
|
||||
count++
|
||||
|
||||
return "", 0, nil
|
||||
}
|
||||
|
||||
return "", 0, ErrTestError
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = WriteImageToFileSystem(
|
||||
Image{Layers: [][]byte{[]byte("testLayer")}},
|
||||
"repo",
|
||||
"tag",
|
||||
storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{
|
||||
PutImageManifestFn: func(repo, reference, mediaType string, body []byte,
|
||||
) (godigest.Digest, godigest.Digest, error) {
|
||||
return "", "", ErrTestError
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//go:build sync && scrub && metrics && search
|
||||
// +build sync,scrub,metrics,search
|
||||
|
||||
package ocilayout
|
||||
package ociutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"zotregistry.io/zot/pkg/extensions/search/convert"
|
||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
stypes "zotregistry.io/zot/pkg/storage/types"
|
||||
)
|
||||
|
||||
type OciUtils interface { //nolint: interfacebloat
|
||||
@@ -44,11 +44,11 @@ type OciUtils interface { //nolint: interfacebloat
|
||||
// OciLayoutInfo ...
|
||||
type BaseOciLayoutUtils struct {
|
||||
Log log.Logger
|
||||
StoreController storage.StoreController
|
||||
StoreController stypes.StoreController
|
||||
}
|
||||
|
||||
// NewBaseOciLayoutUtils initializes a new OciLayoutUtils object.
|
||||
func NewBaseOciLayoutUtils(storeController storage.StoreController, log log.Logger) *BaseOciLayoutUtils {
|
||||
func NewBaseOciLayoutUtils(storeController stypes.StoreController, log log.Logger) *BaseOciLayoutUtils {
|
||||
return &BaseOciLayoutUtils{Log: log, StoreController: storeController}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,8 @@ func (olu BaseOciLayoutUtils) GetImageManifest(repo string, reference string) (i
|
||||
|
||||
// Provide a list of repositories from all the available image stores.
|
||||
func (olu BaseOciLayoutUtils) GetRepositories() ([]string, error) {
|
||||
defaultStore := olu.StoreController.DefaultStore
|
||||
substores := olu.StoreController.SubStore
|
||||
defaultStore := olu.StoreController.GetDefaultImageStore()
|
||||
substores := olu.StoreController.GetImageSubStores()
|
||||
|
||||
repoList, err := defaultStore.GetRepositories()
|
||||
if err != nil {
|
||||
@@ -1,7 +1,7 @@
|
||||
//go:build sync && scrub && metrics && search
|
||||
// +build sync,scrub,metrics,search
|
||||
|
||||
package ocilayout_test
|
||||
package ociutils_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -24,10 +24,11 @@ import (
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/storage/local"
|
||||
. "zotregistry.io/zot/pkg/test"
|
||||
tcommon "zotregistry.io/zot/pkg/test/common"
|
||||
. "zotregistry.io/zot/pkg/test/image-utils"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
ocilayout "zotregistry.io/zot/pkg/test/oci-layout"
|
||||
ociutils "zotregistry.io/zot/pkg/test/oci-utils"
|
||||
signature "zotregistry.io/zot/pkg/test/signature"
|
||||
)
|
||||
|
||||
var ErrTestError = fmt.Errorf("testError")
|
||||
@@ -41,7 +42,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
size := olu.GetImageManifestSize("", "")
|
||||
So(size, ShouldBeZeroValue)
|
||||
@@ -55,7 +56,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
size := olu.GetImageConfigSize("", "")
|
||||
So(size, ShouldBeZeroValue)
|
||||
@@ -93,7 +94,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
size := olu.GetImageConfigSize("", "")
|
||||
So(size, ShouldBeZeroValue)
|
||||
@@ -107,7 +108,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
_, err := olu.GetRepoLastUpdated("")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -133,7 +134,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
_, err = olu.GetImageTagsWithTimestamp("rep")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -177,7 +178,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
_, err = olu.GetImageTagsWithTimestamp("repo")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -220,7 +221,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
_, err = olu.GetExpandedRepoInfo("rep")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -233,7 +234,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController = storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu = ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu = ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
_, err = olu.GetExpandedRepoInfo("rep")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -250,7 +251,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController = storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu = ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu = ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
_, err = olu.GetExpandedRepoInfo("rep")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -264,7 +265,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
_, err := olu.GetImageInfo("", "")
|
||||
So(err, ShouldNotBeNil)
|
||||
@@ -282,7 +283,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
}
|
||||
|
||||
storeController := storage.StoreController{DefaultStore: mockStoreController}
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
|
||||
|
||||
check := olu.CheckManifestSignature("rep", godigest.FromString(""))
|
||||
So(check, ShouldBeFalse)
|
||||
@@ -290,8 +291,8 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
// checkNotarySignature -> true
|
||||
dir := t.TempDir()
|
||||
|
||||
port := GetFreePort()
|
||||
baseURL := GetBaseURL(port)
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.RootDirectory = dir
|
||||
@@ -304,31 +305,19 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlrManager := NewControllerManager(ctlr)
|
||||
ctlrManager := tcommon.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
// push test image to repo
|
||||
config, layers, manifest, err := GetImageComponents(100) //nolint:staticcheck
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
layersSize1 := 0
|
||||
for _, l := range layers {
|
||||
layersSize1 += len(l)
|
||||
}
|
||||
image := CreateRandomImage()
|
||||
|
||||
repo := "repo"
|
||||
tag := "1.0.1"
|
||||
err = UploadImage(
|
||||
Image{
|
||||
Manifest: manifest,
|
||||
Config: config,
|
||||
Layers: layers,
|
||||
}, baseURL, repo, tag,
|
||||
)
|
||||
err := UploadImage(image, baseURL, repo, tag)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
olu = ocilayout.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
|
||||
olu = ociutils.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
|
||||
manifestList, err := olu.GetImageManifests(repo)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(manifestList), ShouldEqual, 1)
|
||||
@@ -336,7 +325,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
|
||||
isSigned := olu.CheckManifestSignature(repo, manifestList[0].Digest)
|
||||
So(isSigned, ShouldBeFalse)
|
||||
|
||||
err = SignImageUsingNotary(fmt.Sprintf("%s:%s", repo, tag), port)
|
||||
err = signature.SignImageUsingNotary(fmt.Sprintf("%s:%s", repo, tag), port)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
isSigned = olu.CheckManifestSignature(repo, manifestList[0].Digest)
|
||||
@@ -355,27 +344,15 @@ func TestExtractImageDetails(t *testing.T) {
|
||||
DefaultStore: imageStore,
|
||||
}
|
||||
|
||||
num := 10
|
||||
config, layers, manifest, err := GetImageComponents(num) //nolint:staticcheck
|
||||
image := CreateRandomImage()
|
||||
|
||||
err := WriteImageToFileSystem(image, "zot-test", "latest", storeController)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = WriteImageToFileSystem(
|
||||
Image{
|
||||
Manifest: manifest,
|
||||
Layers: layers,
|
||||
Config: config,
|
||||
}, "zot-test", "latest", storeController,
|
||||
)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configBlob, err := json.Marshal(config)
|
||||
So(err, ShouldBeNil)
|
||||
configDigest := godigest.FromBytes(configBlob)
|
||||
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, testLogger)
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, testLogger)
|
||||
resDigest, resManifest, resIspecImage, resErr := olu.ExtractImageDetails("zot-test", "latest", testLogger)
|
||||
So(string(resDigest), ShouldContainSubstring, "sha256:8492645f16")
|
||||
So(resManifest.Config.Digest.String(), ShouldContainSubstring, configDigest.Encoded())
|
||||
So(string(resDigest), ShouldEqual, image.ManifestDescriptor.Digest.String())
|
||||
So(resManifest.Config.Digest.String(), ShouldEqual, image.ConfigDescriptor.Digest.String())
|
||||
|
||||
So(resIspecImage.Architecture, ShouldContainSubstring, "amd64")
|
||||
So(resErr, ShouldBeNil)
|
||||
@@ -391,7 +368,7 @@ func TestExtractImageDetails(t *testing.T) {
|
||||
DefaultStore: imageStore,
|
||||
}
|
||||
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, testLogger)
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, testLogger)
|
||||
resDigest, resManifest, resIspecImage, resErr := olu.ExtractImageDetails("zot-test",
|
||||
"latest", testLogger)
|
||||
So(resErr, ShouldEqual, zerr.ErrRepoNotFound)
|
||||
@@ -411,29 +388,17 @@ func TestExtractImageDetails(t *testing.T) {
|
||||
DefaultStore: imageStore,
|
||||
}
|
||||
|
||||
num := 10
|
||||
config, layers, manifest, err := GetImageComponents(num) //nolint:staticcheck
|
||||
image := CreateRandomImage()
|
||||
|
||||
err := WriteImageToFileSystem(image, "zot-test", "latest", storeController)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = WriteImageToFileSystem(
|
||||
Image{
|
||||
Manifest: manifest,
|
||||
Layers: layers,
|
||||
Config: config,
|
||||
}, "zot-test", "latest", storeController,
|
||||
)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configBlob, err := json.Marshal(config)
|
||||
So(err, ShouldBeNil)
|
||||
configDigest := godigest.FromBytes(configBlob)
|
||||
|
||||
err = os.Remove(path.Join(dir, "zot-test/blobs/sha256", configDigest.Encoded()))
|
||||
err = os.Remove(path.Join(dir, "zot-test/blobs/sha256", image.ConfigDescriptor.Digest.Encoded()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
olu := ocilayout.NewBaseOciLayoutUtils(storeController, testLogger)
|
||||
olu := ociutils.NewBaseOciLayoutUtils(storeController, testLogger)
|
||||
resDigest, resManifest, resIspecImage, resErr := olu.ExtractImageDetails("zot-test", "latest", testLogger)
|
||||
So(resErr, ShouldEqual, zerr.ErrBlobNotFound)
|
||||
So(string(resDigest), ShouldEqual, "")
|
||||
@@ -481,7 +446,7 @@ func TestTagsInfo(t *testing.T) {
|
||||
|
||||
allTags = append(allTags, firstTag, secondTag, thirdTag, fourthTag)
|
||||
|
||||
latestTag := ocilayout.GetLatestTag(allTags)
|
||||
latestTag := ociutils.GetLatestTag(allTags)
|
||||
So(latestTag.Tag, ShouldEqual, "1.0.3")
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package test
|
||||
package ociutils
|
||||
|
||||
import (
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -0,0 +1,30 @@
|
||||
package ociutils
|
||||
|
||||
import (
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
|
||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||
zLog "zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/storage/local"
|
||||
stypes "zotregistry.io/zot/pkg/storage/types"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
func GetDefaultImageStore(rootDir string, log zLog.Logger) stypes.ImageStore {
|
||||
return local.NewImageStore(rootDir, false, false, log,
|
||||
monitoring.NewMetricsServer(false, log),
|
||||
mocks.MockedLint{
|
||||
LintFn: func(repo string, manifestDigest godigest.Digest, imageStore stypes.ImageStore) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
},
|
||||
mocks.CacheMock{},
|
||||
)
|
||||
}
|
||||
|
||||
func GetDefaultStoreController(rootDir string, log zLog.Logger) stypes.StoreController {
|
||||
return storage.StoreController{
|
||||
DefaultStore: GetDefaultImageStore(rootDir, log),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
godigest "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"
|
||||
)
|
||||
|
||||
func GetCosignSignatureTagForManifest(manifest ispec.Manifest) (string, error) {
|
||||
manifestBlob, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
manifestDigest := godigest.FromBytes(manifestBlob)
|
||||
|
||||
return GetCosignSignatureTagForDigest(manifestDigest), nil
|
||||
}
|
||||
|
||||
func GetCosignSignatureTagForDigest(manifestDigest godigest.Digest) string {
|
||||
return manifestDigest.Algorithm().String() + "-" + manifestDigest.Encoded() + ".sig"
|
||||
}
|
||||
|
||||
func SignImageUsingCosign(repoTag, port string) error {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
|
||||
tdir, err := os.MkdirTemp("", "cosign")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer os.RemoveAll(tdir)
|
||||
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
// generate a keypair
|
||||
os.Setenv("COSIGN_PASSWORD", "")
|
||||
|
||||
err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageURL := fmt.Sprintf("localhost:%s/%s", port, repoTag)
|
||||
|
||||
const timeoutPeriod = 5
|
||||
|
||||
// sign the image
|
||||
return sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: timeoutPeriod * time.Minute},
|
||||
options.KeyOpts{KeyRef: path.Join(tdir, "cosign.key"), PassFunc: generate.GetPass},
|
||||
options.SignOptions{
|
||||
Registry: options.RegistryOptions{AllowInsecure: true},
|
||||
AnnotationOptions: options.AnnotationOptions{Annotations: []string{"tag=1.0"}},
|
||||
Upload: true,
|
||||
},
|
||||
[]string{imageURL})
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/notaryproject/notation-core-go/signature/jws"
|
||||
"github.com/notaryproject/notation-core-go/testhelper"
|
||||
"github.com/notaryproject/notation-go"
|
||||
notconfig "github.com/notaryproject/notation-go/config"
|
||||
"github.com/notaryproject/notation-go/dir"
|
||||
notreg "github.com/notaryproject/notation-go/registry"
|
||||
"github.com/notaryproject/notation-go/signer"
|
||||
"github.com/notaryproject/notation-go/verifier"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"oras.land/oras-go/v2/registry"
|
||||
"oras.land/oras-go/v2/registry/remote"
|
||||
"oras.land/oras-go/v2/registry/remote/auth"
|
||||
|
||||
tcommon "zotregistry.io/zot/pkg/test/common"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAlreadyExists = errors.New("already exists")
|
||||
ErrKeyNotFound = errors.New("key not found")
|
||||
ErrSignatureVerification = errors.New("signature verification failed")
|
||||
)
|
||||
|
||||
var NotationPathLock = new(sync.Mutex) //nolint: gochecknoglobals
|
||||
|
||||
func LoadNotationPath(tdir string) {
|
||||
dir.UserConfigDir = filepath.Join(tdir, "notation")
|
||||
|
||||
// set user libexec
|
||||
dir.UserLibexecDir = dir.UserConfigDir
|
||||
}
|
||||
|
||||
func GenerateNotationCerts(tdir string, certName string) error {
|
||||
// generate RSA private key
|
||||
bits := 2048
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, bits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyBytes, err := x509.MarshalPKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})
|
||||
|
||||
rsaCertTuple := testhelper.GetRSASelfSignedCertTupleWithPK(key, "cert")
|
||||
|
||||
certBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rsaCertTuple.Cert.Raw})
|
||||
|
||||
// write private key
|
||||
relativeKeyPath, relativeCertPath := dir.LocalKeyPath(certName)
|
||||
|
||||
configFS := dir.ConfigFS()
|
||||
|
||||
keyPath, err := configFS.SysPath(relativeKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certPath, err := configFS.SysPath(relativeCertPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tcommon.WriteFileWithPermission(keyPath, keyPEM, 0o600, false); err != nil { //nolint:gomnd
|
||||
return fmt.Errorf("failed to write key file: %w", err)
|
||||
}
|
||||
|
||||
// write self-signed certificate
|
||||
if err := tcommon.WriteFileWithPermission(certPath, certBytes, 0o644, false); err != nil { //nolint:gomnd
|
||||
return fmt.Errorf("failed to write certificate file: %w", err)
|
||||
}
|
||||
|
||||
signingKeys, err := notconfig.LoadSigningKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keySuite := notconfig.KeySuite{
|
||||
Name: certName,
|
||||
X509KeyPair: ¬config.X509KeyPair{
|
||||
KeyPath: keyPath,
|
||||
CertificatePath: certPath,
|
||||
},
|
||||
}
|
||||
|
||||
// addKeyToSigningKeys
|
||||
if tcommon.Contains(signingKeys.Keys, keySuite.Name) {
|
||||
return ErrAlreadyExists
|
||||
}
|
||||
|
||||
signingKeys.Keys = append(signingKeys.Keys, keySuite)
|
||||
|
||||
// Add to the trust store
|
||||
trustStorePath := path.Join(tdir, fmt.Sprintf("notation/truststore/x509/ca/%s", certName))
|
||||
|
||||
if _, err := os.Stat(filepath.Join(trustStorePath, filepath.Base(certPath))); err == nil {
|
||||
return ErrAlreadyExists
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(trustStorePath, 0o755); err != nil { //nolint:gomnd
|
||||
return fmt.Errorf("GenerateNotationCerts os.MkdirAll failed: %w", err)
|
||||
}
|
||||
|
||||
trustCertPath := path.Join(trustStorePath, fmt.Sprintf("%s%s", certName, dir.LocalCertificateExtension))
|
||||
|
||||
err = tcommon.CopyFile(certPath, trustCertPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save to the SigningKeys.json
|
||||
if err := signingKeys.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SignWithNotation(keyName string, reference string, tdir string) error {
|
||||
ctx := context.TODO()
|
||||
|
||||
// getSigner
|
||||
var newSigner notation.Signer
|
||||
|
||||
mediaType := jws.MediaTypeEnvelope
|
||||
|
||||
// ResolveKey
|
||||
signingKeys, err := LoadNotationSigningkeys(tdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idx := tcommon.Index(signingKeys.Keys, keyName)
|
||||
if idx < 0 {
|
||||
return ErrKeyNotFound
|
||||
}
|
||||
|
||||
key := signingKeys.Keys[idx]
|
||||
|
||||
if key.X509KeyPair != nil {
|
||||
newSigner, err = signer.NewFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// prepareSigningContent
|
||||
// getRepositoryClient
|
||||
authClient := &auth.Client{
|
||||
Credential: func(ctx context.Context, reg string) (auth.Credential, error) {
|
||||
return auth.EmptyCredential, nil
|
||||
},
|
||||
Cache: auth.NewCache(),
|
||||
ClientID: "notation",
|
||||
}
|
||||
|
||||
authClient.SetUserAgent("notation/zot_tests")
|
||||
|
||||
plainHTTP := true
|
||||
|
||||
// Resolve referance
|
||||
ref, err := registry.ParseReference(reference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteRepo := &remote.Repository{
|
||||
Client: authClient,
|
||||
Reference: ref,
|
||||
PlainHTTP: plainHTTP,
|
||||
}
|
||||
|
||||
repositoryOpts := notreg.RepositoryOptions{}
|
||||
|
||||
sigRepo := notreg.NewRepositoryWithOptions(remoteRepo, repositoryOpts)
|
||||
|
||||
sigOpts := notation.SignOptions{
|
||||
SignerSignOptions: notation.SignerSignOptions{
|
||||
SignatureMediaType: mediaType,
|
||||
PluginConfig: map[string]string{},
|
||||
},
|
||||
ArtifactReference: ref.String(),
|
||||
}
|
||||
|
||||
_, err = notation.Sign(ctx, newSigner, sigRepo, sigOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func VerifyWithNotation(reference string, tdir string) error {
|
||||
// check if trustpolicy.json exists
|
||||
trustpolicyPath := path.Join(tdir, "notation/trustpolicy.json")
|
||||
|
||||
if _, err := os.Stat(trustpolicyPath); errors.Is(err, os.ErrNotExist) {
|
||||
trustPolicy := `
|
||||
{
|
||||
"version": "1.0",
|
||||
"trustPolicies": [
|
||||
{
|
||||
"name": "good",
|
||||
"registryScopes": [ "*" ],
|
||||
"signatureVerification": {
|
||||
"level" : "audit"
|
||||
},
|
||||
"trustStores": ["ca:good"],
|
||||
"trustedIdentities": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
file, err := os.Create(trustpolicyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.WriteString(trustPolicy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// start verifying signatures
|
||||
ctx := context.TODO()
|
||||
|
||||
// getRepositoryClient
|
||||
authClient := &auth.Client{
|
||||
Credential: func(ctx context.Context, reg string) (auth.Credential, error) {
|
||||
return auth.EmptyCredential, nil
|
||||
},
|
||||
Cache: auth.NewCache(),
|
||||
ClientID: "notation",
|
||||
}
|
||||
|
||||
authClient.SetUserAgent("notation/zot_tests")
|
||||
|
||||
plainHTTP := true
|
||||
|
||||
// Resolve referance
|
||||
ref, err := registry.ParseReference(reference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteRepo := &remote.Repository{
|
||||
Client: authClient,
|
||||
Reference: ref,
|
||||
PlainHTTP: plainHTTP,
|
||||
}
|
||||
|
||||
repositoryOpts := notreg.RepositoryOptions{}
|
||||
|
||||
repo := notreg.NewRepositoryWithOptions(remoteRepo, repositoryOpts)
|
||||
|
||||
manifestDesc, err := repo.Resolve(ctx, ref.Reference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ref.ValidateReferenceAsDigest(); err != nil {
|
||||
ref.Reference = manifestDesc.Digest.String()
|
||||
}
|
||||
|
||||
// getVerifier
|
||||
newVerifier, err := verifier.NewFromConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteRepo = &remote.Repository{
|
||||
Client: authClient,
|
||||
Reference: ref,
|
||||
PlainHTTP: plainHTTP,
|
||||
}
|
||||
|
||||
repo = notreg.NewRepositoryWithOptions(remoteRepo, repositoryOpts)
|
||||
|
||||
configs := map[string]string{}
|
||||
|
||||
verifyOpts := notation.VerifyOptions{
|
||||
ArtifactReference: ref.String(),
|
||||
PluginConfig: configs,
|
||||
MaxSignatureAttempts: math.MaxInt64,
|
||||
}
|
||||
|
||||
_, outcomes, err := notation.Verify(ctx, newVerifier, repo, verifyOpts)
|
||||
if err != nil || len(outcomes) == 0 {
|
||||
return ErrSignatureVerification
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ListNotarySignatures(reference string, tdir string) ([]godigest.Digest, error) {
|
||||
signatures := []godigest.Digest{}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
// getSignatureRepository
|
||||
ref, err := registry.ParseReference(reference)
|
||||
if err != nil {
|
||||
return signatures, err
|
||||
}
|
||||
|
||||
plainHTTP := true
|
||||
|
||||
// getRepositoryClient
|
||||
authClient := &auth.Client{
|
||||
Credential: func(ctx context.Context, registry string) (auth.Credential, error) {
|
||||
return auth.EmptyCredential, nil
|
||||
},
|
||||
Cache: auth.NewCache(),
|
||||
ClientID: "notation",
|
||||
}
|
||||
|
||||
authClient.SetUserAgent("notation/zot_tests")
|
||||
|
||||
remoteRepo := &remote.Repository{
|
||||
Client: authClient,
|
||||
Reference: ref,
|
||||
PlainHTTP: plainHTTP,
|
||||
}
|
||||
|
||||
sigRepo := notreg.NewRepository(remoteRepo)
|
||||
|
||||
artifactDesc, err := sigRepo.Resolve(ctx, reference)
|
||||
if err != nil {
|
||||
return signatures, err
|
||||
}
|
||||
|
||||
err = sigRepo.ListSignatures(ctx, artifactDesc, func(signatureManifests []ispec.Descriptor) error {
|
||||
for _, sigManifestDesc := range signatureManifests {
|
||||
signatures = append(signatures, sigManifestDesc.Digest)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return signatures, err
|
||||
}
|
||||
|
||||
func LoadNotationSigningkeys(tdir string) (*notconfig.SigningKeys, error) {
|
||||
var err error
|
||||
|
||||
var signingKeysInfo *notconfig.SigningKeys
|
||||
|
||||
filePath := path.Join(tdir, "notation/signingkeys.json")
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
// create file
|
||||
newSigningKeys := notconfig.NewSigningKeys()
|
||||
|
||||
newFile, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return newSigningKeys, err
|
||||
}
|
||||
|
||||
defer newFile.Close()
|
||||
|
||||
encoder := json.NewEncoder(newFile)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
err = encoder.Encode(newSigningKeys)
|
||||
|
||||
return newSigningKeys, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
err = json.NewDecoder(file).Decode(&signingKeysInfo)
|
||||
|
||||
return signingKeysInfo, err
|
||||
}
|
||||
|
||||
func LoadNotationConfig(tdir string) (*notconfig.Config, error) {
|
||||
var configInfo *notconfig.Config
|
||||
|
||||
filePath := path.Join(tdir, "notation/signingkeys.json")
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return configInfo, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
err = json.NewDecoder(file).Decode(&configInfo)
|
||||
if err != nil {
|
||||
return configInfo, err
|
||||
}
|
||||
|
||||
// set default value
|
||||
configInfo.SignatureFormat = strings.ToLower(configInfo.SignatureFormat)
|
||||
if configInfo.SignatureFormat == "" {
|
||||
configInfo.SignatureFormat = "jws"
|
||||
}
|
||||
|
||||
return configInfo, nil
|
||||
}
|
||||
|
||||
func SignImageUsingNotary(repoTag, port string) error {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
|
||||
tdir, err := os.MkdirTemp("", "notation")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer os.RemoveAll(tdir)
|
||||
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
NotationPathLock.Lock()
|
||||
defer NotationPathLock.Unlock()
|
||||
|
||||
LoadNotationPath(tdir)
|
||||
|
||||
// generate a keypair
|
||||
err = GenerateNotationCerts(tdir, "notation-sign-test")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sign the image
|
||||
image := fmt.Sprintf("localhost:%s/%s", port, repoTag)
|
||||
|
||||
err = SignWithNotation("notation-sign-test", image, tdir)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
//go:build sync && scrub && metrics && search
|
||||
// +build sync,scrub,metrics,search
|
||||
|
||||
package signature_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
notconfig "github.com/notaryproject/notation-go/config"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
tcommon "zotregistry.io/zot/pkg/test/common"
|
||||
. "zotregistry.io/zot/pkg/test/image-utils"
|
||||
signature "zotregistry.io/zot/pkg/test/signature"
|
||||
)
|
||||
|
||||
func TestLoadNotationSigningkeys(t *testing.T) {
|
||||
Convey("notation directory doesn't exist", t, func() {
|
||||
_, err := signature.LoadNotationSigningkeys(t.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("wrong content of signingkeys.json", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("some dummy file content"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = signature.LoadNotationSigningkeys(tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("not enough permissions to access signingkeys.json", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("some dummy file content"), 0o300) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = signature.LoadNotationSigningkeys(tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("signingkeys.json not exists so it is created successfully", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = signature.LoadNotationSigningkeys(tempDir)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("signingkeys.json not exists - error trying to create it", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
// create notation directory without write permissions
|
||||
err := os.Mkdir(dir, 0o555)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = signature.LoadNotationSigningkeys(tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadNotationConfig(t *testing.T) {
|
||||
Convey("directory doesn't exist", t, func() {
|
||||
_, err := signature.LoadNotationConfig(t.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("wrong content of signingkeys.json", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("some dummy file content"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
_, err = signature.LoadNotationConfig(tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("check default value of signature format", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("{\"SignatureFormat\": \"\"}"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configInfo, err := signature.LoadNotationConfig(tempDir)
|
||||
So(err, ShouldBeNil)
|
||||
So(configInfo.SignatureFormat, ShouldEqual, "jws")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSignWithNotation(t *testing.T) {
|
||||
Convey("notation directory doesn't exist", t, func() {
|
||||
err := signature.SignWithNotation("key", "reference", t.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("key not found", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
dir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(dir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(dir, "signingkeys.json")
|
||||
err = os.WriteFile(filePath, []byte("{}"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signature.SignWithNotation("key", "reference", tempDir)
|
||||
So(err, ShouldEqual, signature.ErrKeyNotFound)
|
||||
})
|
||||
|
||||
Convey("not enough permissions to access notation/localkeys dir", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tdir)
|
||||
|
||||
err = signature.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(path.Join(tdir, "notation", "localkeys"), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signature.SignWithNotation("key", "reference", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(path.Join(tdir, "notation", "localkeys"), 0o755)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("error parsing reference", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tdir)
|
||||
|
||||
err = signature.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signature.SignWithNotation("key", "invalidReference", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("error signing", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tdir)
|
||||
|
||||
err = signature.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signature.SignWithNotation("key", "localhost:8080/invalidreference:1.0", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyWithNotation(t *testing.T) {
|
||||
Convey("notation directory doesn't exist", t, func() {
|
||||
err := signature.VerifyWithNotation("reference", t.TempDir())
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("error parsing reference", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tdir)
|
||||
|
||||
err = signature.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signature.VerifyWithNotation("invalidReference", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("error trying to get manifest", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tdir)
|
||||
|
||||
err = signature.GenerateNotationCerts(tdir, "key")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signature.VerifyWithNotation("localhost:8080/invalidreference:1.0", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("invalid content of trustpolicy.json", t, func() {
|
||||
// start a new server
|
||||
port := tcommon.GetFreePort()
|
||||
baseURL := tcommon.GetBaseURL(port)
|
||||
dir := t.TempDir()
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.RootDirectory = dir
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
cm := tcommon.NewControllerManager(ctlr)
|
||||
// this blocks
|
||||
cm.StartAndWait(port)
|
||||
defer cm.StopServer()
|
||||
|
||||
repoName := "signed-repo"
|
||||
tag := "1.0"
|
||||
|
||||
image := CreateRandomImage()
|
||||
|
||||
err := UploadImage(image, baseURL, repoName, tag)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
tempDir := t.TempDir()
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err = os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(notationDir, "trustpolicy.json")
|
||||
err = os.WriteFile(filePath, []byte("some dummy file content"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tempDir)
|
||||
|
||||
err = signature.VerifyWithNotation(fmt.Sprintf("localhost:%s/%s:%s", port, repoName, tag), tempDir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListNotarySignatures(t *testing.T) {
|
||||
Convey("error parsing reference", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
_, err = signature.ListNotarySignatures("invalidReference", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("error trying to get manifest", t, func() {
|
||||
cwd, err := os.Getwd()
|
||||
So(err, ShouldBeNil)
|
||||
defer func() { _ = os.Chdir(cwd) }()
|
||||
tdir := t.TempDir()
|
||||
_ = os.Chdir(tdir)
|
||||
|
||||
_, err = signature.ListNotarySignatures("localhost:8080/invalidreference:1.0", tdir)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateNotationCerts(t *testing.T) {
|
||||
Convey("write key file with permission", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(notationDir, "localkeys")
|
||||
err = os.WriteFile(filePath, []byte("{}"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tempDir)
|
||||
|
||||
err = signature.GenerateNotationCerts(t.TempDir(), "cert")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("write cert file with permission", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation", "localkeys")
|
||||
err := os.MkdirAll(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(notationDir, "cert.crt")
|
||||
err = os.WriteFile(filePath, []byte("{}"), 0o666) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = os.Chmod(filePath, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tempDir)
|
||||
|
||||
err = signature.GenerateNotationCerts(t.TempDir(), "cert")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Chmod(filePath, 0o755)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("signingkeys.json file - not enough permission", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
filePath := path.Join(notationDir, "signingkeys.json")
|
||||
_, err = os.Create(filePath) //nolint: gosec
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Chmod(filePath, 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tempDir)
|
||||
|
||||
err = signature.GenerateNotationCerts(t.TempDir(), "cert")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.Remove(filePath)
|
||||
So(err, ShouldBeNil)
|
||||
err = os.RemoveAll(path.Join(notationDir, "localkeys"))
|
||||
So(err, ShouldBeNil)
|
||||
signingKeysBuf, err := json.Marshal(notconfig.SigningKeys{})
|
||||
So(err, ShouldBeNil)
|
||||
err = os.WriteFile(filePath, signingKeysBuf, 0o555) //nolint:gosec // test code
|
||||
So(err, ShouldBeNil)
|
||||
err = signature.GenerateNotationCerts(t.TempDir(), "cert")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("keysuite already exists in signingkeys.json", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certName := "cert-test"
|
||||
filePath := path.Join(notationDir, "signingkeys.json")
|
||||
keyPath := path.Join(notationDir, "localkeys", certName+".key")
|
||||
certPath := path.Join(notationDir, "localkeys", certName+".crt")
|
||||
signingKeys := notconfig.SigningKeys{}
|
||||
keySuite := notconfig.KeySuite{
|
||||
Name: certName,
|
||||
X509KeyPair: ¬config.X509KeyPair{
|
||||
KeyPath: keyPath,
|
||||
CertificatePath: certPath,
|
||||
},
|
||||
}
|
||||
signingKeys.Keys = []notconfig.KeySuite{keySuite}
|
||||
signingKeysBuf, err := json.Marshal(signingKeys)
|
||||
So(err, ShouldBeNil)
|
||||
err = os.WriteFile(filePath, signingKeysBuf, 0o600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tempDir)
|
||||
|
||||
err = signature.GenerateNotationCerts(t.TempDir(), certName)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("truststore files", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
notationDir := path.Join(tempDir, "notation")
|
||||
err := os.Mkdir(notationDir, 0o777)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certName := "cert-test"
|
||||
trustStorePath := path.Join(notationDir, fmt.Sprintf("truststore/x509/ca/%s", certName))
|
||||
err = os.MkdirAll(trustStorePath, 0o755)
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Chmod(path.Join(notationDir, "truststore/x509"), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
signature.NotationPathLock.Lock()
|
||||
defer signature.NotationPathLock.Unlock()
|
||||
|
||||
signature.LoadNotationPath(tempDir)
|
||||
|
||||
err = signature.GenerateNotationCerts(tempDir, certName)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.RemoveAll(path.Join(notationDir, "localkeys"))
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Chmod(path.Join(notationDir, "truststore/x509"), 0o755)
|
||||
So(err, ShouldBeNil)
|
||||
_, err = os.Create(path.Join(trustStorePath, "cert-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signature.GenerateNotationCerts(tempDir, certName)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
err = os.RemoveAll(path.Join(notationDir, "localkeys"))
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Remove(path.Join(trustStorePath, "cert-test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
err = os.Chmod(path.Join(notationDir, "truststore/x509/ca", certName), 0o555)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = signature.GenerateNotationCerts(tempDir, certName)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type RouteHandler struct {
|
||||
Route string
|
||||
// HandlerFunc is the HTTP handler function that receives a writer for output and an HTTP request as input.
|
||||
HandlerFunc http.HandlerFunc
|
||||
// AllowedMethods specifies the HTTP methods allowed for the current route.
|
||||
AllowedMethods []string
|
||||
}
|
||||
|
||||
// Routes is a map that associates HTTP paths to their corresponding HTTP handlers.
|
||||
type HTTPRoutes []RouteHandler
|
||||
|
||||
func StartTestHTTPServer(routes HTTPRoutes, port string) *http.Server {
|
||||
baseURL := GetBaseURL(port)
|
||||
mux := mux.NewRouter()
|
||||
|
||||
mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("{}"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}).Methods(http.MethodGet)
|
||||
|
||||
for _, routeHandler := range routes {
|
||||
mux.HandleFunc(routeHandler.Route, routeHandler.HandlerFunc).Methods(routeHandler.AllowedMethods...)
|
||||
}
|
||||
|
||||
server := &http.Server{ //nolint:gosec
|
||||
Addr: fmt.Sprintf(":%s", port),
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
WaitTillServerReady(baseURL + "/test")
|
||||
|
||||
return server
|
||||
}
|
||||
Reference in New Issue
Block a user