0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-01-06 22:40:28 -05:00
zot/pkg/api/controller_test.go

2560 lines
66 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"
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)
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
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, 201)
// 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, 201)
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)
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)
})
}