mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
af30c06aff
previously mount blob will look for blob that is provided in http request and try to hard link that path but ideally we should look for path from our cache and do the hard link of that particular path. this commit does the same.
2607 lines
68 KiB
Go
2607 lines
68 KiB
Go
// +build extended
|
|
|
|
package api_test
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
"github.com/anuvu/zot/errors"
|
|
"github.com/anuvu/zot/pkg/api"
|
|
"github.com/chartmuseum/auth"
|
|
"github.com/mitchellh/mapstructure"
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/phayes/freeport"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
vldap "github.com/nmcclain/ldap"
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
"gopkg.in/resty.v1"
|
|
)
|
|
|
|
const (
|
|
BaseURL = "http://127.0.0.1:%s"
|
|
BaseSecureURL = "https://127.0.0.1:%s"
|
|
username = "test"
|
|
passphrase = "test"
|
|
ServerCert = "../../test/data/server.cert"
|
|
ServerKey = "../../test/data/server.key"
|
|
CACert = "../../test/data/ca.crt"
|
|
AuthorizedNamespace = "everyone/isallowed"
|
|
UnauthorizedNamespace = "fortknox/notallowed"
|
|
ALICE = "alice"
|
|
)
|
|
|
|
type (
|
|
accessTokenResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
}
|
|
|
|
authHeader struct {
|
|
Realm string
|
|
Service string
|
|
Scope string
|
|
}
|
|
)
|
|
|
|
func getFreePort() string {
|
|
port, err := freeport.GetFreePort()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return fmt.Sprint(port)
|
|
}
|
|
|
|
func getBaseURL(port string, secure bool) string {
|
|
if secure {
|
|
return fmt.Sprintf(BaseSecureURL, port)
|
|
}
|
|
|
|
return fmt.Sprintf(BaseURL, port)
|
|
}
|
|
|
|
func makeHtpasswdFile() string {
|
|
f, err := ioutil.TempFile("", "htpasswd-")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// bcrypt(username="test", passwd="test")
|
|
content := []byte("test:$2y$05$hlbSXDp6hzDLu6VwACS39ORvVRpr3OMR4RlJ31jtlaOEGnPjKZI1m\n")
|
|
if err := ioutil.WriteFile(f.Name(), content, 0600); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return f.Name()
|
|
}
|
|
|
|
func makeHtpasswdFileFromString(fileContent string) string {
|
|
f, err := ioutil.TempFile("", "htpasswd-")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// bcrypt(username="test", passwd="test")
|
|
content := []byte(fileContent)
|
|
if err := ioutil.WriteFile(f.Name(), content, 0600); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return f.Name()
|
|
}
|
|
|
|
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 TestNew(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
config := api.NewConfig()
|
|
So(config, ShouldNotBeNil)
|
|
So(api.NewController(config), ShouldNotBeNil)
|
|
})
|
|
}
|
|
|
|
func TestHtpasswdSingleCred(t *testing.T) {
|
|
Convey("Single cred", t, func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
singleCredtests := []string{}
|
|
user := ALICE
|
|
password := ALICE
|
|
singleCredtests = append(singleCredtests, getCredString(user, password))
|
|
singleCredtests = append(singleCredtests, getCredString(user, password)+"\n")
|
|
|
|
for _, testString := range singleCredtests {
|
|
func() {
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
|
|
htpasswdPath := makeHtpasswdFileFromString(testString)
|
|
defer os.Remove(htpasswdPath)
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func(controller *api.Controller) {
|
|
// this blocks
|
|
if err := controller.Run(); err != nil {
|
|
return
|
|
}
|
|
}(c)
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
defer func(controller *api.Controller) {
|
|
ctx := context.Background()
|
|
_ = controller.Server.Shutdown(ctx)
|
|
}(c)
|
|
// with creds, should get expected status code
|
|
resp, _ := resty.R().SetBasicAuth(user, password).Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
//with invalid creds, it should fail
|
|
resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
}()
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestHtpasswdTwoCreds(t *testing.T) {
|
|
Convey("Two creds", t, func() {
|
|
twoCredTests := []string{}
|
|
user1 := "alicia"
|
|
password1 := "aliciapassword"
|
|
user2 := "bob"
|
|
password2 := "robert"
|
|
twoCredTests = append(twoCredTests, getCredString(user1, password1)+"\n"+
|
|
getCredString(user2, password2))
|
|
|
|
twoCredTests = append(twoCredTests, getCredString(user1, password1)+"\n"+
|
|
getCredString(user2, password2)+"\n")
|
|
|
|
twoCredTests = append(twoCredTests, getCredString(user1, password1)+"\n\n"+
|
|
getCredString(user2, password2)+"\n\n")
|
|
|
|
for _, testString := range twoCredTests {
|
|
func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFileFromString(testString)
|
|
defer os.Remove(htpasswdPath)
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func(controller *api.Controller) {
|
|
// this blocks
|
|
if err := controller.Run(); err != nil {
|
|
return
|
|
}
|
|
}(c)
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func(controller *api.Controller) {
|
|
ctx := context.Background()
|
|
_ = controller.Server.Shutdown(ctx)
|
|
}(c)
|
|
// with creds, should get expected status code
|
|
resp, _ := resty.R().SetBasicAuth(user1, password1).Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
resp, _ = resty.R().SetBasicAuth(user2, password2).Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
//with invalid creds, it should fail
|
|
resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
}()
|
|
}
|
|
})
|
|
}
|
|
func TestHtpasswdFiveCreds(t *testing.T) {
|
|
Convey("Five creds", t, func() {
|
|
tests := map[string]string{
|
|
"michael": "scott",
|
|
"jim": "halpert",
|
|
"dwight": "shrute",
|
|
"pam": "bessley",
|
|
"creed": "bratton",
|
|
}
|
|
credString := strings.Builder{}
|
|
for key, val := range tests {
|
|
credString.WriteString(getCredString(key, val) + "\n")
|
|
}
|
|
|
|
func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFileFromString(credString.String())
|
|
defer os.Remove(htpasswdPath)
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func(controller *api.Controller) {
|
|
// this blocks
|
|
if err := controller.Run(); err != nil {
|
|
return
|
|
}
|
|
}(c)
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func(controller *api.Controller) {
|
|
ctx := context.Background()
|
|
_ = controller.Server.Shutdown(ctx)
|
|
}(c)
|
|
// with creds, should get expected status code
|
|
for key, val := range tests {
|
|
resp, _ := resty.R().SetBasicAuth(key, val).Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
}
|
|
|
|
//with invalid creds, it should fail
|
|
resp, _ := resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
}()
|
|
})
|
|
}
|
|
func TestBasicAuth(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFile()
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
// without creds, should get access error
|
|
resp, err := resty.R().Get(baseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
var e api.Error
|
|
err = json.Unmarshal(resp.Body(), &e)
|
|
So(err, ShouldBeNil)
|
|
|
|
// with creds, should get expected status code
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
}
|
|
|
|
func TestMultipleInstance(t *testing.T) {
|
|
Convey("Negative test zot multiple instance", t, func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFile()
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
c := api.NewController(config)
|
|
err := c.Run()
|
|
So(err, ShouldEqual, errors.ErrImgStoreNotFound)
|
|
|
|
globalDir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(globalDir)
|
|
|
|
subDir, err := ioutil.TempDir("/tmp", "oci-sub-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(subDir)
|
|
|
|
c.Config.Storage.RootDirectory = globalDir
|
|
subPathMap := make(map[string]api.StorageConfig)
|
|
|
|
subPathMap["/a"] = api.StorageConfig{RootDirectory: subDir}
|
|
|
|
go func() {
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
client := resty.New()
|
|
|
|
tagResponse, err := client.R().SetBasicAuth(username, passphrase).
|
|
Get(baseURL + "/v2/zot-test/tags/list")
|
|
So(err, ShouldBeNil)
|
|
So(tagResponse.StatusCode(), ShouldEqual, 404)
|
|
})
|
|
|
|
Convey("Test zot multiple instance", t, func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFile()
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
c := api.NewController(config)
|
|
globalDir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(globalDir)
|
|
|
|
subDir, err := ioutil.TempDir("/tmp", "oci-sub-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(subDir)
|
|
|
|
c.Config.Storage.RootDirectory = globalDir
|
|
subPathMap := make(map[string]api.StorageConfig)
|
|
|
|
subPathMap["/a"] = api.StorageConfig{RootDirectory: subDir}
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
// without creds, should get access error
|
|
resp, err := resty.R().Get(baseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
var e api.Error
|
|
err = json.Unmarshal(resp.Body(), &e)
|
|
So(err, ShouldBeNil)
|
|
|
|
// with creds, should get expected status code
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
}
|
|
|
|
func TestTLSWithBasicAuth(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
caCert, err := ioutil.ReadFile(CACert)
|
|
So(err, ShouldBeNil)
|
|
caCertPool := x509.NewCertPool()
|
|
caCertPool.AppendCertsFromPEM(caCert)
|
|
htpasswdPath := makeHtpasswdFile()
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
secureBaseURL := getBaseURL(port, true)
|
|
|
|
resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
|
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
config.HTTP.TLS = &api.TLSConfig{
|
|
Cert: ServerCert,
|
|
Key: ServerKey,
|
|
}
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
// accessing insecure HTTP site should fail
|
|
resp, err := resty.R().Get(baseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// without creds, should get access error
|
|
resp, err = resty.R().Get(secureBaseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
var e api.Error
|
|
err = json.Unmarshal(resp.Body(), &e)
|
|
So(err, ShouldBeNil)
|
|
|
|
// with creds, should get expected status code
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
}
|
|
|
|
func TestTLSWithBasicAuthAllowReadAccess(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
caCert, err := ioutil.ReadFile(CACert)
|
|
So(err, ShouldBeNil)
|
|
caCertPool := x509.NewCertPool()
|
|
caCertPool.AppendCertsFromPEM(caCert)
|
|
htpasswdPath := makeHtpasswdFile()
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
secureBaseURL := getBaseURL(port, true)
|
|
|
|
resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
|
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
config.HTTP.TLS = &api.TLSConfig{
|
|
Cert: ServerCert,
|
|
Key: ServerKey,
|
|
}
|
|
config.HTTP.AllowReadAccess = true
|
|
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
// accessing insecure HTTP site should fail
|
|
resp, err := resty.R().Get(baseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// without creds, should still be allowed to access
|
|
resp, err = resty.R().Get(secureBaseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
// with creds, should get expected status code
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
// without creds, writes should fail
|
|
resp, err = resty.R().Post(secureBaseURL + "/v2/repo/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
})
|
|
}
|
|
|
|
func TestTLSMutualAuth(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
caCert, err := ioutil.ReadFile(CACert)
|
|
So(err, ShouldBeNil)
|
|
caCertPool := x509.NewCertPool()
|
|
caCertPool.AppendCertsFromPEM(caCert)
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
secureBaseURL := getBaseURL(port, true)
|
|
|
|
resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
|
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
config.HTTP.TLS = &api.TLSConfig{
|
|
Cert: ServerCert,
|
|
Key: ServerKey,
|
|
CACert: CACert,
|
|
}
|
|
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
// accessing insecure HTTP site should fail
|
|
resp, err := resty.R().Get(baseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// without client certs and creds, should get conn error
|
|
_, err = resty.R().Get(secureBaseURL)
|
|
So(err, ShouldNotBeNil)
|
|
|
|
// with creds but without certs, should get conn error
|
|
_, err = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
|
|
So(err, ShouldNotBeNil)
|
|
|
|
// setup TLS mutual auth
|
|
cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key")
|
|
So(err, ShouldBeNil)
|
|
|
|
resty.SetCertificates(cert)
|
|
defer func() { resty.SetCertificates(tls.Certificate{}) }()
|
|
|
|
// with client certs but without creds, should succeed
|
|
resp, err = resty.R().Get(secureBaseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
// with client certs and creds, should get expected status code
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
// with client certs, creds shouldn't matter
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
}
|
|
|
|
func TestTLSMutualAuthAllowReadAccess(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
caCert, err := ioutil.ReadFile(CACert)
|
|
So(err, ShouldBeNil)
|
|
caCertPool := x509.NewCertPool()
|
|
caCertPool.AppendCertsFromPEM(caCert)
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
secureBaseURL := getBaseURL(port, true)
|
|
|
|
resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
|
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
config.HTTP.TLS = &api.TLSConfig{
|
|
Cert: ServerCert,
|
|
Key: ServerKey,
|
|
CACert: CACert,
|
|
}
|
|
config.HTTP.AllowReadAccess = true
|
|
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
// accessing insecure HTTP site should fail
|
|
resp, err := resty.R().Get(baseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// without client certs and creds, reads are allowed
|
|
resp, err = resty.R().Get(secureBaseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
// with creds but without certs, reads are allowed
|
|
resp, err = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
// without creds, writes should fail
|
|
resp, err = resty.R().Post(secureBaseURL + "/v2/repo/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
// setup TLS mutual auth
|
|
cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key")
|
|
So(err, ShouldBeNil)
|
|
|
|
resty.SetCertificates(cert)
|
|
defer func() { resty.SetCertificates(tls.Certificate{}) }()
|
|
|
|
// with client certs but without creds, should succeed
|
|
resp, err = resty.R().Get(secureBaseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
// with client certs and creds, should get expected status code
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
// with client certs, creds shouldn't matter
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
}
|
|
|
|
func TestTLSMutualAndBasicAuth(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
caCert, err := ioutil.ReadFile(CACert)
|
|
So(err, ShouldBeNil)
|
|
caCertPool := x509.NewCertPool()
|
|
caCertPool.AppendCertsFromPEM(caCert)
|
|
htpasswdPath := makeHtpasswdFile()
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
secureBaseURL := getBaseURL(port, true)
|
|
|
|
resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
|
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
config.HTTP.TLS = &api.TLSConfig{
|
|
Cert: ServerCert,
|
|
Key: ServerKey,
|
|
CACert: CACert,
|
|
}
|
|
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
// accessing insecure HTTP site should fail
|
|
resp, err := resty.R().Get(baseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// without client certs and creds, should fail
|
|
_, err = resty.R().Get(secureBaseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// with creds but without certs, should succeed
|
|
_, err = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// setup TLS mutual auth
|
|
cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key")
|
|
So(err, ShouldBeNil)
|
|
|
|
resty.SetCertificates(cert)
|
|
defer func() { resty.SetCertificates(tls.Certificate{}) }()
|
|
|
|
// with client certs but without creds, should get access error
|
|
resp, err = resty.R().Get(secureBaseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
// with client certs and creds, should get expected status code
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
}
|
|
|
|
func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
caCert, err := ioutil.ReadFile(CACert)
|
|
So(err, ShouldBeNil)
|
|
caCertPool := x509.NewCertPool()
|
|
caCertPool.AppendCertsFromPEM(caCert)
|
|
htpasswdPath := makeHtpasswdFile()
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
secureBaseURL := getBaseURL(port, true)
|
|
|
|
resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
|
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
config.HTTP.TLS = &api.TLSConfig{
|
|
Cert: ServerCert,
|
|
Key: ServerKey,
|
|
CACert: CACert,
|
|
}
|
|
config.HTTP.AllowReadAccess = true
|
|
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
// accessing insecure HTTP site should fail
|
|
resp, err := resty.R().Get(baseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// without client certs and creds, should fail
|
|
_, err = resty.R().Get(secureBaseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// with creds but without certs, should succeed
|
|
_, err = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// setup TLS mutual auth
|
|
cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key")
|
|
So(err, ShouldBeNil)
|
|
|
|
resty.SetCertificates(cert)
|
|
defer func() { resty.SetCertificates(tls.Certificate{}) }()
|
|
|
|
// with client certs but without creds, reads should succeed
|
|
resp, err = resty.R().Get(secureBaseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
// with only client certs, writes should fail
|
|
resp, err = resty.R().Post(secureBaseURL + "/v2/repo/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
// with client certs and creds, should get expected status code
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
}
|
|
|
|
const (
|
|
LDAPAddress = "127.0.0.1"
|
|
LDAPPort = 9636
|
|
LDAPBaseDN = "ou=test"
|
|
LDAPBindDN = "cn=reader," + LDAPBaseDN
|
|
LDAPBindPassword = "bindPassword"
|
|
)
|
|
|
|
type testLDAPServer struct {
|
|
server *vldap.Server
|
|
quitCh chan bool
|
|
}
|
|
|
|
func newTestLDAPServer() *testLDAPServer {
|
|
l := &testLDAPServer{}
|
|
quitCh := make(chan bool)
|
|
server := vldap.NewServer()
|
|
server.QuitChannel(quitCh)
|
|
server.BindFunc("", l)
|
|
server.SearchFunc("", l)
|
|
l.server = server
|
|
l.quitCh = quitCh
|
|
|
|
return l
|
|
}
|
|
|
|
func (l *testLDAPServer) Start() {
|
|
addr := fmt.Sprintf("%s:%d", LDAPAddress, LDAPPort)
|
|
|
|
go func() {
|
|
if err := l.server.ListenAndServe(addr); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
_, err := net.Dial("tcp", addr)
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func (l *testLDAPServer) Stop() {
|
|
l.quitCh <- true
|
|
}
|
|
|
|
func (l *testLDAPServer) Bind(bindDN, bindSimplePw string, conn net.Conn) (vldap.LDAPResultCode, error) {
|
|
if bindDN == "" || bindSimplePw == "" {
|
|
return vldap.LDAPResultInappropriateAuthentication, errors.ErrRequireCred
|
|
}
|
|
|
|
if (bindDN == LDAPBindDN && bindSimplePw == LDAPBindPassword) ||
|
|
(bindDN == fmt.Sprintf("cn=%s,%s", username, LDAPBaseDN) && bindSimplePw == passphrase) {
|
|
return vldap.LDAPResultSuccess, nil
|
|
}
|
|
|
|
return vldap.LDAPResultInvalidCredentials, errors.ErrInvalidCred
|
|
}
|
|
|
|
func (l *testLDAPServer) Search(boundDN string, req vldap.SearchRequest,
|
|
conn net.Conn) (vldap.ServerSearchResult, error) {
|
|
check := fmt.Sprintf("(uid=%s)", username)
|
|
if check == req.Filter {
|
|
return vldap.ServerSearchResult{
|
|
Entries: []*vldap.Entry{
|
|
{DN: fmt.Sprintf("cn=%s,%s", username, LDAPBaseDN)},
|
|
},
|
|
ResultCode: vldap.LDAPResultSuccess,
|
|
}, nil
|
|
}
|
|
|
|
return vldap.ServerSearchResult{}, nil
|
|
}
|
|
|
|
func TestBasicAuthWithLDAP(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
l := newTestLDAPServer()
|
|
l.Start()
|
|
defer l.Stop()
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
LDAP: &api.LDAPConfig{
|
|
Insecure: true,
|
|
Address: LDAPAddress,
|
|
Port: LDAPPort,
|
|
BindDN: LDAPBindDN,
|
|
BindPassword: LDAPBindPassword,
|
|
BaseDN: LDAPBaseDN,
|
|
UserAttribute: "uid",
|
|
},
|
|
}
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
// without creds, should get access error
|
|
resp, err := resty.R().Get(baseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
var e api.Error
|
|
err = json.Unmarshal(resp.Body(), &e)
|
|
So(err, ShouldBeNil)
|
|
|
|
// with creds, should get expected status code
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
}
|
|
|
|
func TestBearerAuth(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
authTestServer := makeAuthTestServer()
|
|
defer authTestServer.Close()
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
|
|
u, err := url.Parse(authTestServer.URL)
|
|
So(err, ShouldBeNil)
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
Bearer: &api.BearerConfig{
|
|
Cert: ServerCert,
|
|
Realm: authTestServer.URL + "/auth/token",
|
|
Service: u.Host,
|
|
},
|
|
}
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
So(err, ShouldBeNil)
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
blob := []byte("hello, blob!")
|
|
digest := godigest.FromBytes(blob).String()
|
|
|
|
resp, err := resty.R().Get(baseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader := parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
var goodToken accessTokenResponse
|
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
Get(baseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
resp, err = resty.R().Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
loc := resp.Header().Get("Location")
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
SetQueryParam("digest", digest).
|
|
SetBody(blob).
|
|
Put(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
SetQueryParam("digest", digest).
|
|
SetBody(blob).
|
|
Put(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
Get(baseURL + "/v2/" + AuthorizedNamespace + "/tags/list")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
Get(baseURL + "/v2/" + AuthorizedNamespace + "/tags/list")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
resp, err = resty.R().
|
|
Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
var badToken accessTokenResponse
|
|
err = json.Unmarshal(resp.Body(), &badToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", badToken.AccessToken)).
|
|
Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
})
|
|
}
|
|
|
|
func TestBearerAuthWithAllowReadAccess(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
authTestServer := makeAuthTestServer()
|
|
defer authTestServer.Close()
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
|
|
u, err := url.Parse(authTestServer.URL)
|
|
So(err, ShouldBeNil)
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
Bearer: &api.BearerConfig{
|
|
Cert: ServerCert,
|
|
Realm: authTestServer.URL + "/auth/token",
|
|
Service: u.Host,
|
|
},
|
|
}
|
|
config.HTTP.AllowReadAccess = true
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
So(err, ShouldBeNil)
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func() {
|
|
ctx := context.Background()
|
|
_ = c.Server.Shutdown(ctx)
|
|
}()
|
|
|
|
blob := []byte("hello, blob!")
|
|
digest := godigest.FromBytes(blob).String()
|
|
|
|
resp, err := resty.R().Get(baseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader := parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
var goodToken accessTokenResponse
|
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
Get(baseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
resp, err = resty.R().Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
loc := resp.Header().Get("Location")
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
SetQueryParam("digest", digest).
|
|
SetBody(blob).
|
|
Put(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
SetQueryParam("digest", digest).
|
|
SetBody(blob).
|
|
Put(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
Get(baseURL + "/v2/" + AuthorizedNamespace + "/tags/list")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
err = json.Unmarshal(resp.Body(), &goodToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
|
|
Get(baseURL + "/v2/" + AuthorizedNamespace + "/tags/list")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
resp, err = resty.R().
|
|
Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
|
|
authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
|
|
resp, err = resty.R().
|
|
SetQueryParam("service", authorizationHeader.Service).
|
|
SetQueryParam("scope", authorizationHeader.Scope).
|
|
Get(authorizationHeader.Realm)
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
var badToken accessTokenResponse
|
|
err = json.Unmarshal(resp.Body(), &badToken)
|
|
So(err, ShouldBeNil)
|
|
|
|
resp, err = resty.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", badToken.AccessToken)).
|
|
Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
})
|
|
}
|
|
|
|
func makeAuthTestServer() *httptest.Server {
|
|
cmTokenGenerator, err := auth.NewTokenGenerator(&auth.TokenGeneratorOptions{
|
|
PrivateKeyPath: ServerKey,
|
|
Audience: "Zot Registry",
|
|
Issuer: "Zot",
|
|
AddKIDHeader: true,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
authTestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
scope := r.URL.Query().Get("scope")
|
|
parts := strings.Split(scope, ":")
|
|
name := parts[1]
|
|
actions := strings.Split(parts[2], ",")
|
|
if name == UnauthorizedNamespace {
|
|
actions = []string{}
|
|
}
|
|
access := []auth.AccessEntry{
|
|
{
|
|
Name: name,
|
|
Type: "repository",
|
|
Actions: actions,
|
|
},
|
|
}
|
|
token, err := cmTokenGenerator.GenerateToken(access, time.Minute*1)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprintf(w, `{"access_token": "%s"}`, token)
|
|
}))
|
|
|
|
return authTestServer
|
|
}
|
|
|
|
func parseBearerAuthHeader(authHeaderRaw string) *authHeader {
|
|
re := regexp.MustCompile(`([a-zA-z]+)="(.+?)"`)
|
|
matches := re.FindAllStringSubmatch(authHeaderRaw, -1)
|
|
m := make(map[string]string)
|
|
|
|
for i := 0; i < len(matches); i++ {
|
|
m[matches[i][1]] = matches[i][2]
|
|
}
|
|
|
|
var h authHeader
|
|
if err := mapstructure.Decode(m, &h); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return &h
|
|
}
|
|
|
|
func TestInvalidCases(t *testing.T) {
|
|
Convey("Invalid repo dir", t, func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFileFromString(getCredString(username, passphrase))
|
|
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
|
|
c := api.NewController(config)
|
|
|
|
err := os.Mkdir("oci-repo-test", 0000)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
defer stopServer(c)
|
|
|
|
c.Config.Storage.RootDirectory = "oci-repo-test"
|
|
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
digest := "sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78"
|
|
name := "zot-c-test"
|
|
|
|
client := resty.New()
|
|
|
|
params := make(map[string]string)
|
|
params["from"] = "zot-cveid-test"
|
|
params["mount"] = digest
|
|
|
|
postResponse, err := client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(fmt.Sprintf("%s/v2/%s/blobs/uploads/", baseURL, name))
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 500)
|
|
})
|
|
}
|
|
func TestHTTPReadOnly(t *testing.T) {
|
|
Convey("Single cred", t, func() {
|
|
singleCredtests := []string{}
|
|
user := ALICE
|
|
password := ALICE
|
|
singleCredtests = append(singleCredtests, getCredString(user, password))
|
|
singleCredtests = append(singleCredtests, getCredString(user, password)+"\n")
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
|
|
for _, testString := range singleCredtests {
|
|
func() {
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
// enable read-only mode
|
|
config.HTTP.ReadOnly = true
|
|
|
|
htpasswdPath := makeHtpasswdFileFromString(testString)
|
|
defer os.Remove(htpasswdPath)
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
c := api.NewController(config)
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
c.Config.Storage.RootDirectory = dir
|
|
go func(controller *api.Controller) {
|
|
// this blocks
|
|
if err := controller.Run(); err != nil {
|
|
return
|
|
}
|
|
}(c)
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
defer func(controller *api.Controller) {
|
|
ctx := context.Background()
|
|
_ = controller.Server.Shutdown(ctx)
|
|
}(c)
|
|
|
|
// with creds, should get expected status code
|
|
resp, _ := resty.R().SetBasicAuth(user, password).Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
// with creds, any modifications should still fail on read-only mode
|
|
resp, err = resty.R().SetBasicAuth(user, password).
|
|
Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 405)
|
|
|
|
//with invalid creds, it should fail
|
|
resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 401)
|
|
}()
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCrossRepoMount(t *testing.T) {
|
|
Convey("Cross Repo Mount", t, func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFileFromString(getCredString(username, passphrase))
|
|
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
|
|
c := api.NewController(config)
|
|
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = copyFiles("../../test/data", dir)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
c.Config.Storage.RootDirectory = dir
|
|
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
params := make(map[string]string)
|
|
|
|
digest := "sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29"
|
|
|
|
d := godigest.Digest(digest)
|
|
|
|
name := "zot-cve-test"
|
|
|
|
params["mount"] = digest
|
|
params["from"] = name
|
|
|
|
client := resty.New()
|
|
headResponse, err := client.R().SetBasicAuth(username, passphrase).
|
|
Head(fmt.Sprintf("%s/v2/%s/blobs/%s", baseURL, name, digest))
|
|
So(err, ShouldBeNil)
|
|
So(headResponse.StatusCode(), ShouldEqual, 200)
|
|
|
|
// All invalid request of mount should return 202.
|
|
params["mount"] = "sha:"
|
|
|
|
postResponse, err := client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(baseURL + "/v2/zot-c-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 202)
|
|
|
|
incorrectParams := make(map[string]string)
|
|
incorrectParams["mount"] = "sha256:63a795ca90aa6e7dda60941e826810a4cd0a2e73ea02bf458241df2a5c973e29"
|
|
incorrectParams["from"] = "zot-x-test"
|
|
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(incorrectParams).
|
|
Post(baseURL + "/v2/zot-y-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 202)
|
|
|
|
// Use correct request
|
|
// This is correct request but it will return 202 because blob is not present in cache.
|
|
params["mount"] = digest
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(baseURL + "/v2/zot-c-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 202)
|
|
|
|
// Send same request again
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(baseURL + "/v2/zot-c-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 202)
|
|
|
|
// Valid requests
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(baseURL + "/v2/zot-d-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 202)
|
|
|
|
headResponse, err = client.R().SetBasicAuth(username, passphrase).
|
|
Head(fmt.Sprintf("%s/v2/zot-cv-test/blobs/%s", baseURL, digest))
|
|
So(err, ShouldBeNil)
|
|
So(headResponse.StatusCode(), ShouldEqual, 404)
|
|
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).Post(baseURL + "/v2/zot-c-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 202)
|
|
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(baseURL + "/v2/ /blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 404)
|
|
|
|
digest = "sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29"
|
|
|
|
blob := "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29"
|
|
|
|
buf, err := ioutil.ReadFile(path.Join(c.Config.Storage.RootDirectory, "zot-cve-test/blobs/sha256/"+blob))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
postResponse, err = client.R().SetHeader("Content-type", "application/octet-stream").
|
|
SetBasicAuth(username, passphrase).SetQueryParam("digest", "sha256:"+blob).
|
|
SetBody(buf).Post(baseURL + "/v2/zot-d-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 201)
|
|
|
|
// We have uploaded a blob and since we have provided digest it should be full blob upload and there should be entry
|
|
// in cache, now try mount blob request status and it should be 201 because now blob is present in cache
|
|
// and it should do hard link.
|
|
|
|
params["mount"] = digest
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(baseURL + "/v2/zot-mount-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 201)
|
|
|
|
// Check os.SameFile here
|
|
cachePath := path.Join(c.Config.Storage.RootDirectory, "zot-d-test", "blobs/sha256", d.Hex())
|
|
|
|
cacheFi, err := os.Stat(cachePath)
|
|
So(err, ShouldBeNil)
|
|
|
|
linkPath := path.Join(c.Config.Storage.RootDirectory, "zot-mount-test", "blobs/sha256", d.Hex())
|
|
|
|
linkFi, err := os.Stat(linkPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
So(os.SameFile(cacheFi, linkFi), ShouldEqual, true)
|
|
|
|
// Now try another mount request and this time it should be from above uploaded repo i.e zot-mount-test
|
|
// mount request should pass and should return 201.
|
|
params["mount"] = digest
|
|
params["from"] = "zot-mount-test"
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(baseURL + "/v2/zot-mount1-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 201)
|
|
|
|
linkPath = path.Join(c.Config.Storage.RootDirectory, "zot-mount1-test", "blobs/sha256", d.Hex())
|
|
|
|
linkFi, err = os.Stat(linkPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
So(os.SameFile(cacheFi, linkFi), ShouldEqual, true)
|
|
|
|
headResponse, err = client.R().SetBasicAuth(username, passphrase).
|
|
Head(fmt.Sprintf("%s/v2/zot-cv-test/blobs/%s", baseURL, digest))
|
|
So(err, ShouldBeNil)
|
|
So(headResponse.StatusCode(), ShouldEqual, 200)
|
|
|
|
// Invalid request
|
|
params = make(map[string]string)
|
|
params["mount"] = "sha256:"
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(baseURL + "/v2/zot-mount-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 405)
|
|
|
|
params = make(map[string]string)
|
|
params["from"] = "zot-cve-test"
|
|
postResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).SetQueryParams(params).
|
|
Post(baseURL + "/v2/zot-mount-test/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(postResponse.StatusCode(), ShouldEqual, 405)
|
|
})
|
|
|
|
Convey("Disable dedupe and cache", t, func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFileFromString(getCredString(username, passphrase))
|
|
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
|
|
c := api.NewController(config)
|
|
|
|
//defer stopServer(c)
|
|
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = copyFiles("../../test/data", dir)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
c.Config.Storage.RootDirectory = dir
|
|
c.Config.Storage.Dedupe = false
|
|
c.Config.Storage.GC = false
|
|
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
digest := "sha256:7a0437f04f83f084b7ed68ad9c4a4947e12fc4e1b006b38129bac89114ec3621"
|
|
name := "zot-c-test"
|
|
|
|
client := resty.New()
|
|
headResponse, err := client.R().SetBasicAuth(username, passphrase).
|
|
Head(fmt.Sprintf("%s/v2/%s/blobs/%s", baseURL, name, digest))
|
|
So(err, ShouldBeNil)
|
|
So(headResponse.StatusCode(), ShouldEqual, 404)
|
|
})
|
|
}
|
|
func TestParallelRequests(t *testing.T) {
|
|
testCases := []struct {
|
|
srcImageName string
|
|
srcImageTag string
|
|
destImageName string
|
|
destImageTag string
|
|
testCaseName string
|
|
}{
|
|
{
|
|
srcImageName: "zot-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-1-test",
|
|
destImageTag: "0.0.1",
|
|
testCaseName: "Request-1",
|
|
},
|
|
{
|
|
srcImageName: "zot-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-2-test",
|
|
testCaseName: "Request-2",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "a/zot-3-test",
|
|
testCaseName: "Request-3",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "b/zot-4-test",
|
|
testCaseName: "Request-4",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-5-test",
|
|
testCaseName: "Request-5",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-1-test",
|
|
testCaseName: "Request-6",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-2-test",
|
|
testCaseName: "Request-7",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-3-test",
|
|
testCaseName: "Request-8",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-4-test",
|
|
testCaseName: "Request-9",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-5-test",
|
|
testCaseName: "Request-10",
|
|
},
|
|
{
|
|
srcImageName: "zot-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-1-test",
|
|
destImageTag: "0.0.1",
|
|
testCaseName: "Request-11",
|
|
},
|
|
{
|
|
srcImageName: "zot-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "zot-2-test",
|
|
testCaseName: "Request-12",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "a/zot-3-test",
|
|
testCaseName: "Request-13",
|
|
},
|
|
{
|
|
srcImageName: "zot-cve-test",
|
|
srcImageTag: "0.0.1",
|
|
destImageName: "b/zot-4-test",
|
|
testCaseName: "Request-14",
|
|
},
|
|
}
|
|
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFileFromString(getCredString(username, passphrase))
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
|
|
c := api.NewController(config)
|
|
|
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
firstSubDir, err := ioutil.TempDir("", "oci-sub-dir")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
secondSubDir, err := ioutil.TempDir("", "oci-sub-dir")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
subPaths := make(map[string]api.StorageConfig)
|
|
|
|
subPaths["/a"] = api.StorageConfig{RootDirectory: firstSubDir}
|
|
subPaths["/b"] = api.StorageConfig{RootDirectory: secondSubDir}
|
|
|
|
c.Config.Storage.SubPaths = subPaths
|
|
|
|
c.Config.Storage.RootDirectory = dir
|
|
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
// without creds, should get access error
|
|
for i, testcase := range testCases {
|
|
testcase := testcase
|
|
j := i
|
|
|
|
t.Run(testcase.testCaseName, func(t *testing.T) {
|
|
t.Parallel()
|
|
client := resty.New()
|
|
|
|
tagResponse, err := client.R().SetBasicAuth(username, passphrase).
|
|
Get(baseURL + "/v2/" + testcase.destImageName + "/tags/list")
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.NotEqual(t, tagResponse.StatusCode(), 400, "bad request")
|
|
|
|
manifestList := getAllManifests(path.Join("../../test/data", testcase.srcImageName))
|
|
|
|
for _, manifest := range manifestList {
|
|
headResponse, err := client.R().SetBasicAuth(username, passphrase).
|
|
Head(baseURL + "/v2/" + testcase.destImageName + "/manifests/" + manifest)
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.Equal(t, headResponse.StatusCode(), 404, "response status code should return 404")
|
|
|
|
getResponse, err := client.R().SetBasicAuth(username, passphrase).
|
|
Get(baseURL + "/v2/" + testcase.destImageName + "/manifests/" + manifest)
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.Equal(t, getResponse.StatusCode(), 404, "response status code should return 404")
|
|
}
|
|
|
|
blobList := getAllBlobs(path.Join("../../test/data", testcase.srcImageName))
|
|
|
|
for _, blob := range blobList {
|
|
// Get request of blob
|
|
headResponse, err := client.R().
|
|
SetBasicAuth(username, passphrase).
|
|
Head(baseURL + "/v2/" + testcase.destImageName + "/blobs/sha256:" + blob)
|
|
|
|
assert.Equal(t, err, nil, "Should not be nil")
|
|
assert.NotEqual(t, headResponse.StatusCode(), 500, "internal server error should not occurred")
|
|
|
|
getResponse, err := client.R().
|
|
SetBasicAuth(username, passphrase).
|
|
Get(baseURL + "/v2/" + testcase.destImageName + "/blobs/sha256:" + blob)
|
|
|
|
assert.Equal(t, err, nil, "Should not be nil")
|
|
assert.NotEqual(t, getResponse.StatusCode(), 500, "internal server error should not occurred")
|
|
|
|
blobPath := path.Join("../../test/data", testcase.srcImageName, "blobs/sha256", blob)
|
|
|
|
buf, err := ioutil.ReadFile(blobPath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Post request of blob
|
|
postResponse, err := client.R().
|
|
SetHeader("Content-type", "application/octet-stream").
|
|
SetBasicAuth(username, passphrase).
|
|
SetBody(buf).Post(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/")
|
|
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.NotEqual(t, postResponse.StatusCode(), 500, "response status code should not return 500")
|
|
|
|
// Post request with query parameter
|
|
|
|
if j%2 == 0 {
|
|
postResponse, err = client.R().
|
|
SetHeader("Content-type", "application/octet-stream").
|
|
SetBasicAuth(username, passphrase).
|
|
SetBody(buf).
|
|
Post(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/")
|
|
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.NotEqual(t, postResponse.StatusCode(), 500, "response status code should not return 500")
|
|
|
|
var sessionID string
|
|
sessionIDList := postResponse.Header().Values("Blob-Upload-UUID")
|
|
if len(sessionIDList) == 0 {
|
|
location := postResponse.Header().Values("Location")
|
|
firstLocation := location[0]
|
|
splitLocation := strings.Split(firstLocation, "/")
|
|
sessionID = splitLocation[len(splitLocation)-1]
|
|
} else {
|
|
sessionID = sessionIDList[0]
|
|
}
|
|
|
|
file, err := os.Open(blobPath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
reader := bufio.NewReader(file)
|
|
|
|
b := make([]byte, 5*1024*1024)
|
|
|
|
if j%4 == 0 {
|
|
readContent := 0
|
|
for {
|
|
n, err := reader.Read(b)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
panic(err)
|
|
}
|
|
// Patch request of blob
|
|
|
|
patchResponse, err := client.R().
|
|
SetBody(b[0:n]).
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
SetHeader("Content-Length", fmt.Sprintf("%d", n)).
|
|
SetHeader("Content-Range", fmt.Sprintf("%d", readContent)+"-"+fmt.Sprintf("%d", readContent+n-1)).
|
|
SetBasicAuth(username, passphrase).
|
|
Patch(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/" + sessionID)
|
|
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.NotEqual(t, patchResponse.StatusCode(), 500, "response status code should not return 500")
|
|
|
|
readContent += n
|
|
}
|
|
} else {
|
|
for {
|
|
n, err := reader.Read(b)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
panic(err)
|
|
}
|
|
// Patch request of blob
|
|
|
|
patchResponse, err := client.R().SetBody(b[0:n]).SetHeader("Content-type", "application/octet-stream").
|
|
SetBasicAuth(username, passphrase).
|
|
Patch(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/" + sessionID)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.NotEqual(t, patchResponse.StatusCode(), 500, "response status code should not return 500")
|
|
}
|
|
}
|
|
} else {
|
|
postResponse, err = client.R().
|
|
SetHeader("Content-type", "application/octet-stream").
|
|
SetBasicAuth(username, passphrase).
|
|
SetBody(buf).SetQueryParam("digest", "sha256:"+blob).
|
|
Post(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/")
|
|
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.NotEqual(t, postResponse.StatusCode(), 500, "response status code should not return 500")
|
|
}
|
|
|
|
headResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).
|
|
Head(baseURL + "/v2/" + testcase.destImageName + "/blobs/sha256:" + blob)
|
|
|
|
assert.Equal(t, err, nil, "Should not be nil")
|
|
assert.NotEqual(t, headResponse.StatusCode(), 500, "response should return success code")
|
|
|
|
getResponse, err = client.R().
|
|
SetBasicAuth(username, passphrase).
|
|
Get(baseURL + "/v2/" + testcase.destImageName + "/blobs/sha256:" + blob)
|
|
|
|
assert.Equal(t, err, nil, "Should not be nil")
|
|
assert.NotEqual(t, getResponse.StatusCode(), 500, "response should return success code")
|
|
|
|
if i < 5 { // nolint: scopelint
|
|
deleteResponse, err := client.R().
|
|
SetBasicAuth(username, passphrase).
|
|
Delete(baseURL + "/v2/" + testcase.destImageName + "/blobs/sha256:" + blob)
|
|
|
|
assert.Equal(t, err, nil, "Should not be nil")
|
|
assert.Equal(t, deleteResponse.StatusCode(), 202, "response should return success code")
|
|
}
|
|
}
|
|
|
|
tagResponse, err = client.R().SetBasicAuth(username, passphrase).
|
|
Get(baseURL + "/v2/" + testcase.destImageName + "/tags/list")
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.Equal(t, tagResponse.StatusCode(), 200, "response status code should return success code")
|
|
|
|
repoResponse, err := client.R().SetBasicAuth(username, passphrase).
|
|
Get(baseURL + "/v2/_catalog")
|
|
assert.Equal(t, err, nil, "Error should be nil")
|
|
assert.Equal(t, repoResponse.StatusCode(), 200, "response status code should return success code")
|
|
})
|
|
}
|
|
}
|
|
|
|
func getAllBlobs(imagePath string) []string {
|
|
blobList := make([]string, 0)
|
|
|
|
if !dirExists(imagePath) {
|
|
return []string{}
|
|
}
|
|
|
|
buf, err := ioutil.ReadFile(path.Join(imagePath, "index.json"))
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var index ispec.Index
|
|
if err := json.Unmarshal(buf, &index); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var digest godigest.Digest
|
|
|
|
for _, m := range index.Manifests {
|
|
digest = m.Digest
|
|
blobList = append(blobList, digest.Encoded())
|
|
p := path.Join(imagePath, "blobs", digest.Algorithm().String(), digest.Encoded())
|
|
|
|
buf, err = ioutil.ReadFile(p)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var manifest ispec.Manifest
|
|
if err := json.Unmarshal(buf, &manifest); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
blobList = append(blobList, manifest.Config.Digest.Encoded())
|
|
|
|
for _, layer := range manifest.Layers {
|
|
blobList = append(blobList, layer.Digest.Encoded())
|
|
}
|
|
}
|
|
|
|
return blobList
|
|
}
|
|
|
|
func getAllManifests(imagePath string) []string {
|
|
manifestList := make([]string, 0)
|
|
|
|
if !dirExists(imagePath) {
|
|
return []string{}
|
|
}
|
|
|
|
buf, err := ioutil.ReadFile(path.Join(imagePath, "index.json"))
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var index ispec.Index
|
|
if err := json.Unmarshal(buf, &index); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var digest godigest.Digest
|
|
|
|
for _, m := range index.Manifests {
|
|
digest = m.Digest
|
|
manifestList = append(manifestList, digest.Encoded())
|
|
}
|
|
|
|
return manifestList
|
|
}
|
|
|
|
func dirExists(d string) bool {
|
|
fi, err := os.Stat(d)
|
|
if err != nil && os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
|
|
if !fi.IsDir() {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func copyFiles(sourceDir string, destDir string) error {
|
|
sourceMeta, err := os.Stat(sourceDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.MkdirAll(destDir, sourceMeta.Mode()); err != nil {
|
|
return err
|
|
}
|
|
|
|
files, err := ioutil.ReadDir(sourceDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, file := range files {
|
|
sourceFilePath := path.Join(sourceDir, file.Name())
|
|
destFilePath := path.Join(destDir, file.Name())
|
|
|
|
if file.IsDir() {
|
|
if err = copyFiles(sourceFilePath, destFilePath); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
sourceFile, err := os.Open(sourceFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer sourceFile.Close()
|
|
|
|
destFile, err := os.Create(destFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer destFile.Close()
|
|
|
|
if _, err = io.Copy(destFile, sourceFile); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func stopServer(ctrl *api.Controller) {
|
|
err := ctrl.Server.Shutdown(context.Background())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = os.RemoveAll(ctrl.Config.Storage.RootDirectory)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func TestHardLink(t *testing.T) {
|
|
Convey("Validate hard link", t, func() {
|
|
port := getFreePort()
|
|
baseURL := getBaseURL(port, false)
|
|
|
|
config := api.NewConfig()
|
|
config.HTTP.Port = port
|
|
htpasswdPath := makeHtpasswdFileFromString(getCredString(username, passphrase))
|
|
|
|
config.HTTP.Auth = &api.AuthConfig{
|
|
HTPasswd: api.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
}
|
|
|
|
c := api.NewController(config)
|
|
|
|
dir, err := ioutil.TempDir("", "hard-link-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
err = os.Chmod(dir, 0400)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
subDir, err := ioutil.TempDir("", "sub-hardlink-test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(subDir)
|
|
|
|
err = os.Chmod(subDir, 0400)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
c.Config.Storage.RootDirectory = dir
|
|
subPaths := make(map[string]api.StorageConfig)
|
|
|
|
subPaths["/a"] = api.StorageConfig{RootDirectory: subDir, Dedupe: true}
|
|
|
|
c.Config.Storage.SubPaths = subPaths
|
|
|
|
go func() {
|
|
// this blocks
|
|
if err := c.Run(); err != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// wait till ready
|
|
for {
|
|
_, err := resty.R().Get(baseURL)
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
err = os.Chmod(dir, 0644)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = os.Chmod(subDir, 0644)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
So(c.Config.Storage.Dedupe, ShouldEqual, false)
|
|
})
|
|
}
|