github.com/anuvu/zot@v1.3.4/pkg/api/controller_test.go (about)

     1  //go:build extended
     2  // +build extended
     3  
     4  package api_test
     5  
     6  import (
     7  	"bufio"
     8  	"context"
     9  	"crypto/tls"
    10  	"crypto/x509"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"io/ioutil"
    15  	"net"
    16  	"net/http"
    17  	"net/http/httptest"
    18  	"net/url"
    19  	"os"
    20  	"os/exec"
    21  	"path"
    22  	"regexp"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/anuvu/zot/errors"
    28  	"github.com/anuvu/zot/pkg/api"
    29  	"github.com/anuvu/zot/pkg/api/config"
    30  	"github.com/anuvu/zot/pkg/storage"
    31  	. "github.com/anuvu/zot/test"
    32  	"github.com/chartmuseum/auth"
    33  	"github.com/mitchellh/mapstructure"
    34  	vldap "github.com/nmcclain/ldap"
    35  	notreg "github.com/notaryproject/notation/pkg/registry"
    36  	godigest "github.com/opencontainers/go-digest"
    37  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    38  	artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1"
    39  	"github.com/sigstore/cosign/cmd/cosign/cli/generate"
    40  	"github.com/sigstore/cosign/cmd/cosign/cli/options"
    41  	"github.com/sigstore/cosign/cmd/cosign/cli/sign"
    42  	"github.com/sigstore/cosign/cmd/cosign/cli/verify"
    43  	. "github.com/smartystreets/goconvey/convey"
    44  	"github.com/stretchr/testify/assert"
    45  	"golang.org/x/crypto/bcrypt"
    46  	"gopkg.in/resty.v1"
    47  )
    48  
    49  const (
    50  	username               = "test"
    51  	passphrase             = "test"
    52  	ServerCert             = "../../test/data/server.cert"
    53  	ServerKey              = "../../test/data/server.key"
    54  	CACert                 = "../../test/data/ca.crt"
    55  	AuthorizedNamespace    = "everyone/isallowed"
    56  	UnauthorizedNamespace  = "fortknox/notallowed"
    57  	ALICE                  = "alice"
    58  	AuthorizationNamespace = "authz/image"
    59  )
    60  
    61  type (
    62  	accessTokenResponse struct {
    63  		AccessToken string `json:"access_token"`
    64  	}
    65  
    66  	authHeader struct {
    67  		Realm   string
    68  		Service string
    69  		Scope   string
    70  	}
    71  )
    72  
    73  func getCredString(username, password string) string {
    74  	hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
    75  	if err != nil {
    76  		panic(err)
    77  	}
    78  
    79  	usernameAndHash := fmt.Sprintf("%s:%s", username, string(hash))
    80  
    81  	return usernameAndHash
    82  }
    83  
    84  func skipIt(t *testing.T) {
    85  	if os.Getenv("S3MOCK_ENDPOINT") == "" {
    86  		t.Skip("Skipping testing without AWS S3 mock server")
    87  	}
    88  }
    89  
    90  func TestNew(t *testing.T) {
    91  	Convey("Make a new controller", t, func() {
    92  		conf := config.New()
    93  		So(conf, ShouldNotBeNil)
    94  		So(api.NewController(conf), ShouldNotBeNil)
    95  	})
    96  }
    97  
    98  func TestObjectStorageController(t *testing.T) {
    99  	skipIt(t)
   100  	Convey("Negative make a new object storage controller", t, func() {
   101  		port := GetFreePort()
   102  		conf := config.New()
   103  		conf.HTTP.Port = port
   104  		storageDriverParams := map[string]interface{}{
   105  			"rootDir": "zot",
   106  			"name":    storage.S3StorageDriverName,
   107  		}
   108  		conf.Storage.StorageDriver = storageDriverParams
   109  		c := api.NewController(conf)
   110  		So(c, ShouldNotBeNil)
   111  
   112  		c.Config.Storage.RootDirectory = "zot"
   113  
   114  		err := c.Run()
   115  		So(err, ShouldNotBeNil)
   116  	})
   117  
   118  	Convey("Make a new object storage controller", t, func() {
   119  		port := GetFreePort()
   120  		baseURL := GetBaseURL(port)
   121  		conf := config.New()
   122  		conf.HTTP.Port = port
   123  
   124  		bucket := "zot-storage-test"
   125  		endpoint := os.Getenv("S3MOCK_ENDPOINT")
   126  
   127  		storageDriverParams := map[string]interface{}{
   128  			"rootDir":        "zot",
   129  			"name":           storage.S3StorageDriverName,
   130  			"region":         "us-east-2",
   131  			"bucket":         bucket,
   132  			"regionendpoint": endpoint,
   133  			"secure":         false,
   134  			"skipverify":     false,
   135  		}
   136  		conf.Storage.StorageDriver = storageDriverParams
   137  		c := api.NewController(conf)
   138  		So(c, ShouldNotBeNil)
   139  
   140  		c.Config.Storage.RootDirectory = "/"
   141  
   142  		go startServer(c)
   143  		defer stopServer(c)
   144  		WaitTillServerReady(baseURL)
   145  	})
   146  }
   147  
   148  func TestObjectStorageControllerSubPaths(t *testing.T) {
   149  	skipIt(t)
   150  	Convey("Make a new object storage controller", t, func() {
   151  		port := GetFreePort()
   152  		baseURL := GetBaseURL(port)
   153  		conf := config.New()
   154  		conf.HTTP.Port = port
   155  
   156  		bucket := "zot-storage-test"
   157  		endpoint := os.Getenv("S3MOCK_ENDPOINT")
   158  
   159  		storageDriverParams := map[string]interface{}{
   160  			"rootDir":        "zot",
   161  			"name":           storage.S3StorageDriverName,
   162  			"region":         "us-east-2",
   163  			"bucket":         bucket,
   164  			"regionendpoint": endpoint,
   165  			"secure":         false,
   166  			"skipverify":     false,
   167  		}
   168  		conf.Storage.StorageDriver = storageDriverParams
   169  		c := api.NewController(conf)
   170  		So(c, ShouldNotBeNil)
   171  
   172  		c.Config.Storage.RootDirectory = "zot"
   173  		subPathMap := make(map[string]config.StorageConfig)
   174  		subPathMap["/a"] = config.StorageConfig{
   175  			RootDirectory: "/a",
   176  			StorageDriver: storageDriverParams,
   177  		}
   178  		c.Config.Storage.SubPaths = subPathMap
   179  
   180  		go startServer(c)
   181  		defer stopServer(c)
   182  		WaitTillServerReady(baseURL)
   183  	})
   184  }
   185  
   186  func TestHtpasswdSingleCred(t *testing.T) {
   187  	Convey("Single cred", t, func() {
   188  		port := GetFreePort()
   189  		baseURL := GetBaseURL(port)
   190  		singleCredtests := []string{}
   191  		user := ALICE
   192  		password := ALICE
   193  		singleCredtests = append(singleCredtests, getCredString(user, password))
   194  		singleCredtests = append(singleCredtests, getCredString(user, password)+"\n")
   195  
   196  		for _, testString := range singleCredtests {
   197  			func() {
   198  				conf := config.New()
   199  				conf.HTTP.Port = port
   200  
   201  				htpasswdPath := MakeHtpasswdFileFromString(testString)
   202  				defer os.Remove(htpasswdPath)
   203  				conf.HTTP.Auth = &config.AuthConfig{
   204  					HTPasswd: config.AuthHTPasswd{
   205  						Path: htpasswdPath,
   206  					},
   207  				}
   208  				c := api.NewController(conf)
   209  				dir, err := ioutil.TempDir("", "oci-repo-test")
   210  				if err != nil {
   211  					panic(err)
   212  				}
   213  				defer os.RemoveAll(dir)
   214  				c.Config.Storage.RootDirectory = dir
   215  
   216  				go startServer(c)
   217  				defer stopServer(c)
   218  				WaitTillServerReady(baseURL)
   219  
   220  				// with creds, should get expected status code
   221  				resp, _ := resty.R().SetBasicAuth(user, password).Get(baseURL + "/v2/")
   222  				So(resp, ShouldNotBeNil)
   223  				So(resp.StatusCode(), ShouldEqual, 200)
   224  
   225  				//with invalid creds, it should fail
   226  				resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
   227  				So(resp, ShouldNotBeNil)
   228  				So(resp.StatusCode(), ShouldEqual, 401)
   229  			}()
   230  		}
   231  	})
   232  }
   233  
   234  func TestHtpasswdTwoCreds(t *testing.T) {
   235  	Convey("Two creds", t, func() {
   236  		twoCredTests := []string{}
   237  		user1 := "alicia"
   238  		password1 := "aliciapassword"
   239  		user2 := "bob"
   240  		password2 := "robert"
   241  		twoCredTests = append(twoCredTests, getCredString(user1, password1)+"\n"+
   242  			getCredString(user2, password2))
   243  
   244  		twoCredTests = append(twoCredTests, getCredString(user1, password1)+"\n"+
   245  			getCredString(user2, password2)+"\n")
   246  
   247  		twoCredTests = append(twoCredTests, getCredString(user1, password1)+"\n\n"+
   248  			getCredString(user2, password2)+"\n\n")
   249  
   250  		for _, testString := range twoCredTests {
   251  			func() {
   252  				port := GetFreePort()
   253  				baseURL := GetBaseURL(port)
   254  				conf := config.New()
   255  				conf.HTTP.Port = port
   256  				htpasswdPath := MakeHtpasswdFileFromString(testString)
   257  				defer os.Remove(htpasswdPath)
   258  				conf.HTTP.Auth = &config.AuthConfig{
   259  					HTPasswd: config.AuthHTPasswd{
   260  						Path: htpasswdPath,
   261  					},
   262  				}
   263  				c := api.NewController(conf)
   264  				dir, err := ioutil.TempDir("", "oci-repo-test")
   265  				if err != nil {
   266  					panic(err)
   267  				}
   268  				defer os.RemoveAll(dir)
   269  				c.Config.Storage.RootDirectory = dir
   270  
   271  				go startServer(c)
   272  				defer stopServer(c)
   273  				WaitTillServerReady(baseURL)
   274  
   275  				// with creds, should get expected status code
   276  				resp, _ := resty.R().SetBasicAuth(user1, password1).Get(baseURL + "/v2/")
   277  				So(resp, ShouldNotBeNil)
   278  				So(resp.StatusCode(), ShouldEqual, 200)
   279  
   280  				resp, _ = resty.R().SetBasicAuth(user2, password2).Get(baseURL + "/v2/")
   281  				So(resp, ShouldNotBeNil)
   282  				So(resp.StatusCode(), ShouldEqual, 200)
   283  
   284  				//with invalid creds, it should fail
   285  				resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
   286  				So(resp, ShouldNotBeNil)
   287  				So(resp.StatusCode(), ShouldEqual, 401)
   288  			}()
   289  		}
   290  	})
   291  }
   292  func TestHtpasswdFiveCreds(t *testing.T) {
   293  	Convey("Five creds", t, func() {
   294  		tests := map[string]string{
   295  			"michael": "scott",
   296  			"jim":     "halpert",
   297  			"dwight":  "shrute",
   298  			"pam":     "bessley",
   299  			"creed":   "bratton",
   300  		}
   301  		credString := strings.Builder{}
   302  		for key, val := range tests {
   303  			credString.WriteString(getCredString(key, val) + "\n")
   304  		}
   305  
   306  		func() {
   307  			port := GetFreePort()
   308  			baseURL := GetBaseURL(port)
   309  			conf := config.New()
   310  			conf.HTTP.Port = port
   311  			htpasswdPath := MakeHtpasswdFileFromString(credString.String())
   312  			defer os.Remove(htpasswdPath)
   313  			conf.HTTP.Auth = &config.AuthConfig{
   314  				HTPasswd: config.AuthHTPasswd{
   315  					Path: htpasswdPath,
   316  				},
   317  			}
   318  			c := api.NewController(conf)
   319  			dir, err := ioutil.TempDir("", "oci-repo-test")
   320  			if err != nil {
   321  				panic(err)
   322  			}
   323  			defer os.RemoveAll(dir)
   324  			c.Config.Storage.RootDirectory = dir
   325  
   326  			go startServer(c)
   327  			defer stopServer(c)
   328  			WaitTillServerReady(baseURL)
   329  
   330  			// with creds, should get expected status code
   331  			for key, val := range tests {
   332  				resp, _ := resty.R().SetBasicAuth(key, val).Get(baseURL + "/v2/")
   333  				So(resp, ShouldNotBeNil)
   334  				So(resp.StatusCode(), ShouldEqual, 200)
   335  			}
   336  
   337  			//with invalid creds, it should fail
   338  			resp, _ := resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
   339  			So(resp, ShouldNotBeNil)
   340  			So(resp.StatusCode(), ShouldEqual, 401)
   341  		}()
   342  	})
   343  }
   344  func TestBasicAuth(t *testing.T) {
   345  	Convey("Make a new controller", t, func() {
   346  		port := GetFreePort()
   347  		baseURL := GetBaseURL(port)
   348  		conf := config.New()
   349  		conf.HTTP.Port = port
   350  		htpasswdPath := MakeHtpasswdFile()
   351  		defer os.Remove(htpasswdPath)
   352  
   353  		conf.HTTP.Auth = &config.AuthConfig{
   354  			HTPasswd: config.AuthHTPasswd{
   355  				Path: htpasswdPath,
   356  			},
   357  		}
   358  		c := api.NewController(conf)
   359  		dir, err := ioutil.TempDir("", "oci-repo-test")
   360  		if err != nil {
   361  			panic(err)
   362  		}
   363  		defer os.RemoveAll(dir)
   364  		c.Config.Storage.RootDirectory = dir
   365  
   366  		go startServer(c)
   367  		defer stopServer(c)
   368  		WaitTillServerReady(baseURL)
   369  
   370  		// without creds, should get access error
   371  		resp, err := resty.R().Get(baseURL + "/v2/")
   372  		So(err, ShouldBeNil)
   373  		So(resp, ShouldNotBeNil)
   374  		So(resp.StatusCode(), ShouldEqual, 401)
   375  		var e api.Error
   376  		err = json.Unmarshal(resp.Body(), &e)
   377  		So(err, ShouldBeNil)
   378  
   379  		// with creds, should get expected status code
   380  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL)
   381  		So(resp, ShouldNotBeNil)
   382  		So(resp.StatusCode(), ShouldEqual, 404)
   383  
   384  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/")
   385  		So(resp, ShouldNotBeNil)
   386  		So(resp.StatusCode(), ShouldEqual, 200)
   387  	})
   388  }
   389  
   390  func TestInterruptedBlobUpload(t *testing.T) {
   391  	Convey("Successfully cleaning interrupted blob uploads", t, func() {
   392  		port := GetFreePort()
   393  		baseURL := GetBaseURL(port)
   394  		conf := config.New()
   395  		conf.HTTP.Port = port
   396  
   397  		c := api.NewController(conf)
   398  		dir, err := ioutil.TempDir("", "oci-repo-test")
   399  		if err != nil {
   400  			panic(err)
   401  		}
   402  
   403  		defer os.RemoveAll(dir)
   404  		c.Config.Storage.RootDirectory = dir
   405  
   406  		go startServer(c)
   407  		defer stopServer(c)
   408  		WaitTillServerReady(baseURL)
   409  
   410  		client := resty.New()
   411  		blob := make([]byte, 50*1024*1024)
   412  		digest := godigest.FromBytes(blob).String()
   413  
   414  		// nolint: dupl
   415  		Convey("Test interrupt PATCH blob upload", func() {
   416  			resp, err := client.R().Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
   417  			So(err, ShouldBeNil)
   418  			So(resp, ShouldNotBeNil)
   419  			So(resp.StatusCode(), ShouldEqual, 202)
   420  
   421  			loc := resp.Header().Get("Location")
   422  			splittedLoc := strings.Split(loc, "/")
   423  			sessionID := splittedLoc[len(splittedLoc)-1]
   424  
   425  			ctx := context.Background()
   426  			ctx, cancel := context.WithCancel(ctx)
   427  
   428  			// patch blob
   429  			go func(ctx context.Context) {
   430  				for i := 0; i < 3; i++ {
   431  					_, _ = client.R().
   432  						SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
   433  						SetHeader("Content-Type", "application/octet-stream").
   434  						SetQueryParam("digest", digest).
   435  						SetBody(blob).
   436  						SetContext(ctx).
   437  						Patch(baseURL + loc)
   438  
   439  					time.Sleep(500 * time.Millisecond)
   440  				}
   441  			}(ctx)
   442  
   443  			// if the blob upload has started then interrupt by running cancel()
   444  			for {
   445  				n, err := c.StoreController.DefaultStore.GetBlobUpload(AuthorizedNamespace, sessionID)
   446  				if n > 0 && err == nil {
   447  					cancel()
   448  					break
   449  				}
   450  				time.Sleep(100 * time.Millisecond)
   451  			}
   452  
   453  			// wait for zot to remove blobUpload
   454  			time.Sleep(1 * time.Second)
   455  
   456  			resp, err = client.R().Get(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/" + sessionID)
   457  			So(err, ShouldBeNil)
   458  			So(resp, ShouldNotBeNil)
   459  			So(resp.StatusCode(), ShouldEqual, 404)
   460  		})
   461  
   462  		Convey("Test negative interrupt PATCH blob upload", func() {
   463  			resp, err := client.R().Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
   464  			So(err, ShouldBeNil)
   465  			So(resp, ShouldNotBeNil)
   466  			So(resp.StatusCode(), ShouldEqual, 202)
   467  
   468  			loc := resp.Header().Get("Location")
   469  			splittedLoc := strings.Split(loc, "/")
   470  			sessionID := splittedLoc[len(splittedLoc)-1]
   471  
   472  			ctx := context.Background()
   473  			ctx, cancel := context.WithCancel(ctx)
   474  
   475  			// patch blob
   476  			go func(ctx context.Context) {
   477  				for i := 0; i < 3; i++ {
   478  					_, _ = client.R().
   479  						SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
   480  						SetHeader("Content-Type", "application/octet-stream").
   481  						SetQueryParam("digest", digest).
   482  						SetBody(blob).
   483  						SetContext(ctx).
   484  						Patch(baseURL + loc)
   485  
   486  					time.Sleep(500 * time.Millisecond)
   487  				}
   488  			}(ctx)
   489  
   490  			// if the blob upload has started then interrupt by running cancel()
   491  			for {
   492  				n, err := c.StoreController.DefaultStore.GetBlobUpload(AuthorizedNamespace, sessionID)
   493  				if n > 0 && err == nil {
   494  					// cleaning blob uploads, so that zot fails to clean up, +code coverage
   495  					err = c.StoreController.DefaultStore.DeleteBlobUpload(AuthorizedNamespace, sessionID)
   496  					So(err, ShouldBeNil)
   497  					cancel()
   498  					break
   499  				}
   500  				time.Sleep(100 * time.Millisecond)
   501  			}
   502  
   503  			// wait for zot to remove blobUpload
   504  			time.Sleep(1 * time.Second)
   505  
   506  			resp, err = client.R().Get(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/" + sessionID)
   507  			So(err, ShouldBeNil)
   508  			So(resp, ShouldNotBeNil)
   509  			So(resp.StatusCode(), ShouldEqual, 404)
   510  		})
   511  
   512  		// nolint: dupl
   513  		Convey("Test interrupt PUT blob upload", func() {
   514  			resp, err := client.R().Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
   515  			So(err, ShouldBeNil)
   516  			So(resp, ShouldNotBeNil)
   517  			So(resp.StatusCode(), ShouldEqual, 202)
   518  
   519  			loc := resp.Header().Get("Location")
   520  			splittedLoc := strings.Split(loc, "/")
   521  			sessionID := splittedLoc[len(splittedLoc)-1]
   522  
   523  			ctx := context.Background()
   524  			ctx, cancel := context.WithCancel(ctx)
   525  
   526  			// put blob
   527  			go func(ctx context.Context) {
   528  				for i := 0; i < 3; i++ {
   529  					_, _ = client.R().
   530  						SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
   531  						SetHeader("Content-Type", "application/octet-stream").
   532  						SetQueryParam("digest", digest).
   533  						SetBody(blob).
   534  						SetContext(ctx).
   535  						Put(baseURL + loc)
   536  
   537  					time.Sleep(500 * time.Millisecond)
   538  				}
   539  			}(ctx)
   540  
   541  			// if the blob upload has started then interrupt by running cancel()
   542  			for {
   543  				n, err := c.StoreController.DefaultStore.GetBlobUpload(AuthorizedNamespace, sessionID)
   544  				if n > 0 && err == nil {
   545  					cancel()
   546  					break
   547  				}
   548  				time.Sleep(100 * time.Millisecond)
   549  			}
   550  
   551  			// wait for zot to try to remove blobUpload
   552  			time.Sleep(1 * time.Second)
   553  
   554  			resp, err = client.R().Get(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/" + sessionID)
   555  			So(err, ShouldBeNil)
   556  			So(resp, ShouldNotBeNil)
   557  			So(resp.StatusCode(), ShouldEqual, 404)
   558  		})
   559  
   560  		Convey("Test negative interrupt PUT blob upload", func() {
   561  			resp, err := client.R().Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
   562  			So(err, ShouldBeNil)
   563  			So(resp, ShouldNotBeNil)
   564  			So(resp.StatusCode(), ShouldEqual, 202)
   565  
   566  			loc := resp.Header().Get("Location")
   567  			splittedLoc := strings.Split(loc, "/")
   568  			sessionID := splittedLoc[len(splittedLoc)-1]
   569  
   570  			ctx := context.Background()
   571  			ctx, cancel := context.WithCancel(ctx)
   572  
   573  			// push blob
   574  			go func(ctx context.Context) {
   575  				for i := 0; i < 3; i++ {
   576  					_, _ = client.R().
   577  						SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
   578  						SetHeader("Content-Type", "application/octet-stream").
   579  						SetQueryParam("digest", digest).
   580  						SetBody(blob).
   581  						SetContext(ctx).
   582  						Put(baseURL + loc)
   583  
   584  					time.Sleep(500 * time.Millisecond)
   585  				}
   586  			}(ctx)
   587  
   588  			// if the blob upload has started then interrupt by running cancel()
   589  			for {
   590  				n, err := c.StoreController.DefaultStore.GetBlobUpload(AuthorizedNamespace, sessionID)
   591  				if n > 0 && err == nil {
   592  					// cleaning blob uploads, so that zot fails to clean up, +code coverage
   593  					err = c.StoreController.DefaultStore.DeleteBlobUpload(AuthorizedNamespace, sessionID)
   594  					So(err, ShouldBeNil)
   595  					cancel()
   596  					break
   597  				}
   598  				time.Sleep(100 * time.Millisecond)
   599  			}
   600  
   601  			// wait for zot to try to remove blobUpload
   602  			time.Sleep(1 * time.Second)
   603  
   604  			resp, err = client.R().Get(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/" + sessionID)
   605  			So(err, ShouldBeNil)
   606  			So(resp, ShouldNotBeNil)
   607  			So(resp.StatusCode(), ShouldEqual, 404)
   608  		})
   609  	})
   610  }
   611  
   612  func TestMultipleInstance(t *testing.T) {
   613  	Convey("Negative test zot multiple instance", t, func() {
   614  		port := GetFreePort()
   615  		baseURL := GetBaseURL(port)
   616  		conf := config.New()
   617  		conf.HTTP.Port = port
   618  		htpasswdPath := MakeHtpasswdFile()
   619  		defer os.Remove(htpasswdPath)
   620  
   621  		conf.HTTP.Auth = &config.AuthConfig{
   622  			HTPasswd: config.AuthHTPasswd{
   623  				Path: htpasswdPath,
   624  			},
   625  		}
   626  		c := api.NewController(conf)
   627  		err := c.Run()
   628  		So(err, ShouldEqual, errors.ErrImgStoreNotFound)
   629  
   630  		globalDir, err := ioutil.TempDir("", "oci-repo-test")
   631  		if err != nil {
   632  			panic(err)
   633  		}
   634  		defer os.RemoveAll(globalDir)
   635  
   636  		subDir, err := ioutil.TempDir("", "oci-sub-test")
   637  		if err != nil {
   638  			panic(err)
   639  		}
   640  		defer os.RemoveAll(subDir)
   641  
   642  		c.Config.Storage.RootDirectory = globalDir
   643  		subPathMap := make(map[string]config.StorageConfig)
   644  
   645  		subPathMap["/a"] = config.StorageConfig{RootDirectory: subDir}
   646  
   647  		go startServer(c)
   648  		defer stopServer(c)
   649  		WaitTillServerReady(baseURL)
   650  
   651  		client := resty.New()
   652  
   653  		tagResponse, err := client.R().SetBasicAuth(username, passphrase).
   654  			Get(baseURL + "/v2/zot-test/tags/list")
   655  		So(err, ShouldBeNil)
   656  		So(tagResponse.StatusCode(), ShouldEqual, 404)
   657  	})
   658  
   659  	Convey("Test zot multiple instance", t, func() {
   660  		port := GetFreePort()
   661  		baseURL := GetBaseURL(port)
   662  		conf := config.New()
   663  		conf.HTTP.Port = port
   664  		htpasswdPath := MakeHtpasswdFile()
   665  		defer os.Remove(htpasswdPath)
   666  
   667  		conf.HTTP.Auth = &config.AuthConfig{
   668  			HTPasswd: config.AuthHTPasswd{
   669  				Path: htpasswdPath,
   670  			},
   671  		}
   672  		c := api.NewController(conf)
   673  		globalDir, err := ioutil.TempDir("", "oci-repo-test")
   674  		if err != nil {
   675  			panic(err)
   676  		}
   677  		defer os.RemoveAll(globalDir)
   678  
   679  		subDir, err := ioutil.TempDir("", "oci-sub-test")
   680  		if err != nil {
   681  			panic(err)
   682  		}
   683  		defer os.RemoveAll(subDir)
   684  
   685  		c.Config.Storage.RootDirectory = globalDir
   686  		subPathMap := make(map[string]config.StorageConfig)
   687  		subPathMap["/a"] = config.StorageConfig{RootDirectory: subDir}
   688  
   689  		go startServer(c)
   690  		defer stopServer(c)
   691  		WaitTillServerReady(baseURL)
   692  
   693  		// without creds, should get access error
   694  		resp, err := resty.R().Get(baseURL + "/v2/")
   695  		So(err, ShouldBeNil)
   696  		So(resp, ShouldNotBeNil)
   697  		So(resp.StatusCode(), ShouldEqual, 401)
   698  		var e api.Error
   699  		err = json.Unmarshal(resp.Body(), &e)
   700  		So(err, ShouldBeNil)
   701  
   702  		// with creds, should get expected status code
   703  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL)
   704  		So(resp, ShouldNotBeNil)
   705  		So(resp.StatusCode(), ShouldEqual, 404)
   706  
   707  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/")
   708  		So(resp, ShouldNotBeNil)
   709  		So(resp.StatusCode(), ShouldEqual, 200)
   710  	})
   711  }
   712  
   713  func TestTLSWithBasicAuth(t *testing.T) {
   714  	Convey("Make a new controller", t, func() {
   715  		caCert, err := ioutil.ReadFile(CACert)
   716  		So(err, ShouldBeNil)
   717  		caCertPool := x509.NewCertPool()
   718  		caCertPool.AppendCertsFromPEM(caCert)
   719  		htpasswdPath := MakeHtpasswdFile()
   720  		defer os.Remove(htpasswdPath)
   721  
   722  		port := GetFreePort()
   723  		baseURL := GetBaseURL(port)
   724  		secureBaseURL := GetSecureBaseURL(port)
   725  
   726  		resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
   727  		defer func() { resty.SetTLSClientConfig(nil) }()
   728  		conf := config.New()
   729  		conf.HTTP.Port = port
   730  		conf.HTTP.TLS = &config.TLSConfig{
   731  			Cert: ServerCert,
   732  			Key:  ServerKey,
   733  		}
   734  		conf.HTTP.Auth = &config.AuthConfig{
   735  			HTPasswd: config.AuthHTPasswd{
   736  				Path: htpasswdPath,
   737  			},
   738  		}
   739  
   740  		c := api.NewController(conf)
   741  		dir, err := ioutil.TempDir("", "oci-repo-test")
   742  		if err != nil {
   743  			panic(err)
   744  		}
   745  		defer os.RemoveAll(dir)
   746  		c.Config.Storage.RootDirectory = dir
   747  
   748  		go startServer(c)
   749  		defer stopServer(c)
   750  		WaitTillServerReady(baseURL)
   751  
   752  		// accessing insecure HTTP site should fail
   753  		resp, err := resty.R().Get(baseURL)
   754  		So(err, ShouldBeNil)
   755  		So(resp, ShouldNotBeNil)
   756  		So(resp.StatusCode(), ShouldEqual, 400)
   757  
   758  		// without creds, should get access error
   759  		resp, err = resty.R().Get(secureBaseURL + "/v2/")
   760  		So(err, ShouldBeNil)
   761  		So(resp, ShouldNotBeNil)
   762  		So(resp.StatusCode(), ShouldEqual, 401)
   763  		var e api.Error
   764  		err = json.Unmarshal(resp.Body(), &e)
   765  		So(err, ShouldBeNil)
   766  
   767  		// with creds, should get expected status code
   768  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
   769  		So(resp, ShouldNotBeNil)
   770  		So(resp.StatusCode(), ShouldEqual, 404)
   771  
   772  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
   773  		So(resp, ShouldNotBeNil)
   774  		So(resp.StatusCode(), ShouldEqual, 200)
   775  	})
   776  }
   777  
   778  func TestTLSWithBasicAuthAllowReadAccess(t *testing.T) {
   779  	Convey("Make a new controller", t, func() {
   780  		caCert, err := ioutil.ReadFile(CACert)
   781  		So(err, ShouldBeNil)
   782  		caCertPool := x509.NewCertPool()
   783  		caCertPool.AppendCertsFromPEM(caCert)
   784  		htpasswdPath := MakeHtpasswdFile()
   785  		defer os.Remove(htpasswdPath)
   786  
   787  		port := GetFreePort()
   788  		baseURL := GetBaseURL(port)
   789  		secureBaseURL := GetSecureBaseURL(port)
   790  
   791  		resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
   792  		defer func() { resty.SetTLSClientConfig(nil) }()
   793  		conf := config.New()
   794  		conf.HTTP.Port = port
   795  		conf.HTTP.Auth = &config.AuthConfig{
   796  			HTPasswd: config.AuthHTPasswd{
   797  				Path: htpasswdPath,
   798  			},
   799  		}
   800  		conf.HTTP.TLS = &config.TLSConfig{
   801  			Cert: ServerCert,
   802  			Key:  ServerKey,
   803  		}
   804  		conf.HTTP.AllowReadAccess = true
   805  
   806  		c := api.NewController(conf)
   807  		dir, err := ioutil.TempDir("", "oci-repo-test")
   808  		if err != nil {
   809  			panic(err)
   810  		}
   811  		defer os.RemoveAll(dir)
   812  		c.Config.Storage.RootDirectory = dir
   813  
   814  		go startServer(c)
   815  		defer stopServer(c)
   816  		WaitTillServerReady(baseURL)
   817  
   818  		// accessing insecure HTTP site should fail
   819  		resp, err := resty.R().Get(baseURL)
   820  		So(err, ShouldBeNil)
   821  		So(resp, ShouldNotBeNil)
   822  		So(resp.StatusCode(), ShouldEqual, 400)
   823  
   824  		// without creds, should still be allowed to access
   825  		resp, err = resty.R().Get(secureBaseURL + "/v2/")
   826  		So(err, ShouldBeNil)
   827  		So(resp.StatusCode(), ShouldEqual, 200)
   828  
   829  		// with creds, should get expected status code
   830  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
   831  		So(resp, ShouldNotBeNil)
   832  		So(resp.StatusCode(), ShouldEqual, 404)
   833  
   834  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
   835  		So(resp, ShouldNotBeNil)
   836  		So(resp.StatusCode(), ShouldEqual, 200)
   837  
   838  		// without creds, writes should fail
   839  		resp, err = resty.R().Post(secureBaseURL + "/v2/repo/blobs/uploads/")
   840  		So(err, ShouldBeNil)
   841  		So(resp.StatusCode(), ShouldEqual, 401)
   842  	})
   843  }
   844  
   845  func TestTLSMutualAuth(t *testing.T) {
   846  	Convey("Make a new controller", t, func() {
   847  		caCert, err := ioutil.ReadFile(CACert)
   848  		So(err, ShouldBeNil)
   849  		caCertPool := x509.NewCertPool()
   850  		caCertPool.AppendCertsFromPEM(caCert)
   851  
   852  		port := GetFreePort()
   853  		baseURL := GetBaseURL(port)
   854  		secureBaseURL := GetSecureBaseURL(port)
   855  
   856  		resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
   857  		defer func() { resty.SetTLSClientConfig(nil) }()
   858  		conf := config.New()
   859  		conf.HTTP.Port = port
   860  		conf.HTTP.TLS = &config.TLSConfig{
   861  			Cert:   ServerCert,
   862  			Key:    ServerKey,
   863  			CACert: CACert,
   864  		}
   865  
   866  		c := api.NewController(conf)
   867  		dir, err := ioutil.TempDir("", "oci-repo-test")
   868  		if err != nil {
   869  			panic(err)
   870  		}
   871  		defer os.RemoveAll(dir)
   872  		c.Config.Storage.RootDirectory = dir
   873  
   874  		go startServer(c)
   875  		defer stopServer(c)
   876  		WaitTillServerReady(baseURL)
   877  
   878  		// accessing insecure HTTP site should fail
   879  		resp, err := resty.R().Get(baseURL)
   880  		So(err, ShouldBeNil)
   881  		So(resp, ShouldNotBeNil)
   882  		So(resp.StatusCode(), ShouldEqual, 400)
   883  
   884  		// without client certs and creds, should get conn error
   885  		_, err = resty.R().Get(secureBaseURL)
   886  		So(err, ShouldNotBeNil)
   887  
   888  		// with creds but without certs, should get conn error
   889  		_, err = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
   890  		So(err, ShouldNotBeNil)
   891  
   892  		// setup TLS mutual auth
   893  		cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key")
   894  		So(err, ShouldBeNil)
   895  
   896  		resty.SetCertificates(cert)
   897  		defer func() { resty.SetCertificates(tls.Certificate{}) }()
   898  
   899  		// with client certs but without creds, should succeed
   900  		resp, err = resty.R().Get(secureBaseURL + "/v2/")
   901  		So(err, ShouldBeNil)
   902  		So(resp, ShouldNotBeNil)
   903  		So(resp.StatusCode(), ShouldEqual, 200)
   904  
   905  		// with client certs and creds, should get expected status code
   906  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
   907  		So(resp, ShouldNotBeNil)
   908  		So(resp.StatusCode(), ShouldEqual, 404)
   909  
   910  		// with client certs, creds shouldn't matter
   911  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
   912  		So(resp, ShouldNotBeNil)
   913  		So(resp.StatusCode(), ShouldEqual, 200)
   914  	})
   915  }
   916  
   917  func TestTLSMutualAuthAllowReadAccess(t *testing.T) {
   918  	Convey("Make a new controller", t, func() {
   919  		caCert, err := ioutil.ReadFile(CACert)
   920  		So(err, ShouldBeNil)
   921  		caCertPool := x509.NewCertPool()
   922  		caCertPool.AppendCertsFromPEM(caCert)
   923  
   924  		port := GetFreePort()
   925  		baseURL := GetBaseURL(port)
   926  		secureBaseURL := GetSecureBaseURL(port)
   927  
   928  		resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
   929  		defer func() { resty.SetTLSClientConfig(nil) }()
   930  		conf := config.New()
   931  		conf.HTTP.Port = port
   932  		conf.HTTP.TLS = &config.TLSConfig{
   933  			Cert:   ServerCert,
   934  			Key:    ServerKey,
   935  			CACert: CACert,
   936  		}
   937  		conf.HTTP.AllowReadAccess = true
   938  
   939  		c := api.NewController(conf)
   940  		dir, err := ioutil.TempDir("", "oci-repo-test")
   941  		if err != nil {
   942  			panic(err)
   943  		}
   944  		defer os.RemoveAll(dir)
   945  		c.Config.Storage.RootDirectory = dir
   946  
   947  		go startServer(c)
   948  		defer stopServer(c)
   949  		WaitTillServerReady(baseURL)
   950  
   951  		// accessing insecure HTTP site should fail
   952  		resp, err := resty.R().Get(baseURL)
   953  		So(err, ShouldBeNil)
   954  		So(resp, ShouldNotBeNil)
   955  		So(resp.StatusCode(), ShouldEqual, 400)
   956  
   957  		// without client certs and creds, reads are allowed
   958  		resp, err = resty.R().Get(secureBaseURL + "/v2/")
   959  		So(err, ShouldBeNil)
   960  		So(resp.StatusCode(), ShouldEqual, 200)
   961  
   962  		// with creds but without certs, reads are allowed
   963  		resp, err = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
   964  		So(err, ShouldBeNil)
   965  		So(resp.StatusCode(), ShouldEqual, 200)
   966  
   967  		// without creds, writes should fail
   968  		resp, err = resty.R().Post(secureBaseURL + "/v2/repo/blobs/uploads/")
   969  		So(err, ShouldBeNil)
   970  		So(resp.StatusCode(), ShouldEqual, 401)
   971  
   972  		// setup TLS mutual auth
   973  		cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key")
   974  		So(err, ShouldBeNil)
   975  
   976  		resty.SetCertificates(cert)
   977  		defer func() { resty.SetCertificates(tls.Certificate{}) }()
   978  
   979  		// with client certs but without creds, should succeed
   980  		resp, err = resty.R().Get(secureBaseURL + "/v2/")
   981  		So(err, ShouldBeNil)
   982  		So(resp, ShouldNotBeNil)
   983  		So(resp.StatusCode(), ShouldEqual, 200)
   984  
   985  		// with client certs and creds, should get expected status code
   986  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
   987  		So(resp, ShouldNotBeNil)
   988  		So(resp.StatusCode(), ShouldEqual, 404)
   989  
   990  		// with client certs, creds shouldn't matter
   991  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
   992  		So(resp, ShouldNotBeNil)
   993  		So(resp.StatusCode(), ShouldEqual, 200)
   994  	})
   995  }
   996  
   997  func TestTLSMutualAndBasicAuth(t *testing.T) {
   998  	Convey("Make a new controller", t, func() {
   999  		caCert, err := ioutil.ReadFile(CACert)
  1000  		So(err, ShouldBeNil)
  1001  		caCertPool := x509.NewCertPool()
  1002  		caCertPool.AppendCertsFromPEM(caCert)
  1003  		htpasswdPath := MakeHtpasswdFile()
  1004  		defer os.Remove(htpasswdPath)
  1005  
  1006  		port := GetFreePort()
  1007  		baseURL := GetBaseURL(port)
  1008  		secureBaseURL := GetSecureBaseURL(port)
  1009  
  1010  		resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
  1011  		defer func() { resty.SetTLSClientConfig(nil) }()
  1012  		conf := config.New()
  1013  		conf.HTTP.Port = port
  1014  		conf.HTTP.Auth = &config.AuthConfig{
  1015  			HTPasswd: config.AuthHTPasswd{
  1016  				Path: htpasswdPath,
  1017  			},
  1018  		}
  1019  		conf.HTTP.TLS = &config.TLSConfig{
  1020  			Cert:   ServerCert,
  1021  			Key:    ServerKey,
  1022  			CACert: CACert,
  1023  		}
  1024  
  1025  		c := api.NewController(conf)
  1026  		dir, err := ioutil.TempDir("", "oci-repo-test")
  1027  		if err != nil {
  1028  			panic(err)
  1029  		}
  1030  		defer os.RemoveAll(dir)
  1031  		c.Config.Storage.RootDirectory = dir
  1032  
  1033  		go startServer(c)
  1034  		defer stopServer(c)
  1035  		WaitTillServerReady(baseURL)
  1036  
  1037  		// accessing insecure HTTP site should fail
  1038  		resp, err := resty.R().Get(baseURL)
  1039  		So(err, ShouldBeNil)
  1040  		So(resp, ShouldNotBeNil)
  1041  		So(resp.StatusCode(), ShouldEqual, 400)
  1042  
  1043  		// without client certs and creds, should fail
  1044  		_, err = resty.R().Get(secureBaseURL)
  1045  		So(err, ShouldBeNil)
  1046  		So(resp, ShouldNotBeNil)
  1047  		So(resp.StatusCode(), ShouldEqual, 400)
  1048  
  1049  		// with creds but without certs, should succeed
  1050  		_, err = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
  1051  		So(err, ShouldBeNil)
  1052  		So(resp, ShouldNotBeNil)
  1053  		So(resp.StatusCode(), ShouldEqual, 400)
  1054  
  1055  		// setup TLS mutual auth
  1056  		cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key")
  1057  		So(err, ShouldBeNil)
  1058  
  1059  		resty.SetCertificates(cert)
  1060  		defer func() { resty.SetCertificates(tls.Certificate{}) }()
  1061  
  1062  		// with client certs but without creds, should get access error
  1063  		resp, err = resty.R().Get(secureBaseURL + "/v2/")
  1064  		So(err, ShouldBeNil)
  1065  		So(resp, ShouldNotBeNil)
  1066  		So(resp.StatusCode(), ShouldEqual, 401)
  1067  
  1068  		// with client certs and creds, should get expected status code
  1069  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
  1070  		So(resp, ShouldNotBeNil)
  1071  		So(resp.StatusCode(), ShouldEqual, 404)
  1072  
  1073  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
  1074  		So(resp, ShouldNotBeNil)
  1075  		So(resp.StatusCode(), ShouldEqual, 200)
  1076  	})
  1077  }
  1078  
  1079  func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) {
  1080  	Convey("Make a new controller", t, func() {
  1081  		caCert, err := ioutil.ReadFile(CACert)
  1082  		So(err, ShouldBeNil)
  1083  		caCertPool := x509.NewCertPool()
  1084  		caCertPool.AppendCertsFromPEM(caCert)
  1085  		htpasswdPath := MakeHtpasswdFile()
  1086  		defer os.Remove(htpasswdPath)
  1087  
  1088  		port := GetFreePort()
  1089  		baseURL := GetBaseURL(port)
  1090  		secureBaseURL := GetSecureBaseURL(port)
  1091  
  1092  		resty.SetTLSClientConfig(&tls.Config{RootCAs: caCertPool})
  1093  		defer func() { resty.SetTLSClientConfig(nil) }()
  1094  		conf := config.New()
  1095  		conf.HTTP.Port = port
  1096  		conf.HTTP.Auth = &config.AuthConfig{
  1097  			HTPasswd: config.AuthHTPasswd{
  1098  				Path: htpasswdPath,
  1099  			},
  1100  		}
  1101  		conf.HTTP.TLS = &config.TLSConfig{
  1102  			Cert:   ServerCert,
  1103  			Key:    ServerKey,
  1104  			CACert: CACert,
  1105  		}
  1106  		conf.HTTP.AllowReadAccess = true
  1107  
  1108  		c := api.NewController(conf)
  1109  		dir, err := ioutil.TempDir("", "oci-repo-test")
  1110  		if err != nil {
  1111  			panic(err)
  1112  		}
  1113  		defer os.RemoveAll(dir)
  1114  		c.Config.Storage.RootDirectory = dir
  1115  
  1116  		go startServer(c)
  1117  		defer stopServer(c)
  1118  		WaitTillServerReady(baseURL)
  1119  
  1120  		// accessing insecure HTTP site should fail
  1121  		resp, err := resty.R().Get(baseURL)
  1122  		So(err, ShouldBeNil)
  1123  		So(resp, ShouldNotBeNil)
  1124  		So(resp.StatusCode(), ShouldEqual, 400)
  1125  
  1126  		// without client certs and creds, should fail
  1127  		_, err = resty.R().Get(secureBaseURL)
  1128  		So(err, ShouldBeNil)
  1129  		So(resp, ShouldNotBeNil)
  1130  		So(resp.StatusCode(), ShouldEqual, 400)
  1131  
  1132  		// with creds but without certs, should succeed
  1133  		_, err = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
  1134  		So(err, ShouldBeNil)
  1135  		So(resp, ShouldNotBeNil)
  1136  		So(resp.StatusCode(), ShouldEqual, 400)
  1137  
  1138  		// setup TLS mutual auth
  1139  		cert, err := tls.LoadX509KeyPair("../../test/data/client.cert", "../../test/data/client.key")
  1140  		So(err, ShouldBeNil)
  1141  
  1142  		resty.SetCertificates(cert)
  1143  		defer func() { resty.SetCertificates(tls.Certificate{}) }()
  1144  
  1145  		// with client certs but without creds, reads should succeed
  1146  		resp, err = resty.R().Get(secureBaseURL + "/v2/")
  1147  		So(err, ShouldBeNil)
  1148  		So(resp.StatusCode(), ShouldEqual, 200)
  1149  
  1150  		// with only client certs, writes should fail
  1151  		resp, err = resty.R().Post(secureBaseURL + "/v2/repo/blobs/uploads/")
  1152  		So(err, ShouldBeNil)
  1153  		So(resp.StatusCode(), ShouldEqual, 401)
  1154  
  1155  		// with client certs and creds, should get expected status code
  1156  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL)
  1157  		So(resp, ShouldNotBeNil)
  1158  		So(resp.StatusCode(), ShouldEqual, 404)
  1159  
  1160  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(secureBaseURL + "/v2/")
  1161  		So(resp, ShouldNotBeNil)
  1162  		So(resp.StatusCode(), ShouldEqual, 200)
  1163  	})
  1164  }
  1165  
  1166  const (
  1167  	LDAPAddress      = "127.0.0.1"
  1168  	LDAPPort         = 9636
  1169  	LDAPBaseDN       = "ou=test"
  1170  	LDAPBindDN       = "cn=reader," + LDAPBaseDN
  1171  	LDAPBindPassword = "bindPassword"
  1172  )
  1173  
  1174  type testLDAPServer struct {
  1175  	server *vldap.Server
  1176  	quitCh chan bool
  1177  }
  1178  
  1179  func newTestLDAPServer() *testLDAPServer {
  1180  	l := &testLDAPServer{}
  1181  	quitCh := make(chan bool)
  1182  	server := vldap.NewServer()
  1183  	server.QuitChannel(quitCh)
  1184  	server.BindFunc("", l)
  1185  	server.SearchFunc("", l)
  1186  	l.server = server
  1187  	l.quitCh = quitCh
  1188  
  1189  	return l
  1190  }
  1191  
  1192  func (l *testLDAPServer) Start() {
  1193  	addr := fmt.Sprintf("%s:%d", LDAPAddress, LDAPPort)
  1194  
  1195  	go func() {
  1196  		if err := l.server.ListenAndServe(addr); err != nil {
  1197  			panic(err)
  1198  		}
  1199  	}()
  1200  
  1201  	for {
  1202  		_, err := net.Dial("tcp", addr)
  1203  		if err == nil {
  1204  			break
  1205  		}
  1206  
  1207  		time.Sleep(10 * time.Millisecond)
  1208  	}
  1209  }
  1210  
  1211  func (l *testLDAPServer) Stop() {
  1212  	l.quitCh <- true
  1213  }
  1214  
  1215  func (l *testLDAPServer) Bind(bindDN, bindSimplePw string, conn net.Conn) (vldap.LDAPResultCode, error) {
  1216  	if bindDN == "" || bindSimplePw == "" {
  1217  		return vldap.LDAPResultInappropriateAuthentication, errors.ErrRequireCred
  1218  	}
  1219  
  1220  	if (bindDN == LDAPBindDN && bindSimplePw == LDAPBindPassword) ||
  1221  		(bindDN == fmt.Sprintf("cn=%s,%s", username, LDAPBaseDN) && bindSimplePw == passphrase) {
  1222  		return vldap.LDAPResultSuccess, nil
  1223  	}
  1224  
  1225  	return vldap.LDAPResultInvalidCredentials, errors.ErrInvalidCred
  1226  }
  1227  
  1228  func (l *testLDAPServer) Search(boundDN string, req vldap.SearchRequest,
  1229  	conn net.Conn) (vldap.ServerSearchResult, error) {
  1230  	check := fmt.Sprintf("(uid=%s)", username)
  1231  	if check == req.Filter {
  1232  		return vldap.ServerSearchResult{
  1233  			Entries: []*vldap.Entry{
  1234  				{DN: fmt.Sprintf("cn=%s,%s", username, LDAPBaseDN)},
  1235  			},
  1236  			ResultCode: vldap.LDAPResultSuccess,
  1237  		}, nil
  1238  	}
  1239  
  1240  	return vldap.ServerSearchResult{}, nil
  1241  }
  1242  
  1243  func TestBasicAuthWithLDAP(t *testing.T) {
  1244  	Convey("Make a new controller", t, func() {
  1245  		l := newTestLDAPServer()
  1246  		l.Start()
  1247  		defer l.Stop()
  1248  
  1249  		port := GetFreePort()
  1250  		baseURL := GetBaseURL(port)
  1251  
  1252  		conf := config.New()
  1253  		conf.HTTP.Port = port
  1254  		conf.HTTP.Auth = &config.AuthConfig{
  1255  			LDAP: &config.LDAPConfig{
  1256  				Insecure:      true,
  1257  				Address:       LDAPAddress,
  1258  				Port:          LDAPPort,
  1259  				BindDN:        LDAPBindDN,
  1260  				BindPassword:  LDAPBindPassword,
  1261  				BaseDN:        LDAPBaseDN,
  1262  				UserAttribute: "uid",
  1263  			},
  1264  		}
  1265  		c := api.NewController(conf)
  1266  		dir, err := ioutil.TempDir("", "oci-repo-test")
  1267  		if err != nil {
  1268  			panic(err)
  1269  		}
  1270  		defer os.RemoveAll(dir)
  1271  		c.Config.Storage.RootDirectory = dir
  1272  
  1273  		go startServer(c)
  1274  		defer stopServer(c)
  1275  		WaitTillServerReady(baseURL)
  1276  
  1277  		// without creds, should get access error
  1278  		resp, err := resty.R().Get(baseURL + "/v2/")
  1279  		So(err, ShouldBeNil)
  1280  		So(resp, ShouldNotBeNil)
  1281  		So(resp.StatusCode(), ShouldEqual, 401)
  1282  		var e api.Error
  1283  		err = json.Unmarshal(resp.Body(), &e)
  1284  		So(err, ShouldBeNil)
  1285  
  1286  		// with creds, should get expected status code
  1287  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL)
  1288  		So(resp, ShouldNotBeNil)
  1289  		So(resp.StatusCode(), ShouldEqual, 404)
  1290  
  1291  		resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/")
  1292  		So(resp, ShouldNotBeNil)
  1293  		So(resp.StatusCode(), ShouldEqual, 200)
  1294  	})
  1295  }
  1296  
  1297  func TestBearerAuth(t *testing.T) {
  1298  	Convey("Make a new controller", t, func() {
  1299  		authTestServer := makeAuthTestServer()
  1300  		defer authTestServer.Close()
  1301  
  1302  		port := GetFreePort()
  1303  		baseURL := GetBaseURL(port)
  1304  
  1305  		conf := config.New()
  1306  		conf.HTTP.Port = port
  1307  
  1308  		u, err := url.Parse(authTestServer.URL)
  1309  		So(err, ShouldBeNil)
  1310  
  1311  		conf.HTTP.Auth = &config.AuthConfig{
  1312  			Bearer: &config.BearerConfig{
  1313  				Cert:    ServerCert,
  1314  				Realm:   authTestServer.URL + "/auth/token",
  1315  				Service: u.Host,
  1316  			},
  1317  		}
  1318  		c := api.NewController(conf)
  1319  		dir, err := ioutil.TempDir("", "oci-repo-test")
  1320  		So(err, ShouldBeNil)
  1321  		defer os.RemoveAll(dir)
  1322  		c.Config.Storage.RootDirectory = dir
  1323  
  1324  		go startServer(c)
  1325  		defer stopServer(c)
  1326  		WaitTillServerReady(baseURL)
  1327  
  1328  		blob := []byte("hello, blob!")
  1329  		digest := godigest.FromBytes(blob).String()
  1330  
  1331  		resp, err := resty.R().Get(baseURL + "/v2/")
  1332  		So(err, ShouldBeNil)
  1333  		So(resp, ShouldNotBeNil)
  1334  		So(resp.StatusCode(), ShouldEqual, 401)
  1335  
  1336  		authorizationHeader := parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1337  		resp, err = resty.R().
  1338  			SetQueryParam("service", authorizationHeader.Service).
  1339  			SetQueryParam("scope", authorizationHeader.Scope).
  1340  			Get(authorizationHeader.Realm)
  1341  		So(err, ShouldBeNil)
  1342  		So(resp, ShouldNotBeNil)
  1343  		So(resp.StatusCode(), ShouldEqual, 200)
  1344  		var goodToken accessTokenResponse
  1345  		err = json.Unmarshal(resp.Body(), &goodToken)
  1346  		So(err, ShouldBeNil)
  1347  
  1348  		resp, err = resty.R().
  1349  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1350  			Get(baseURL + "/v2/")
  1351  		So(err, ShouldBeNil)
  1352  		So(resp, ShouldNotBeNil)
  1353  		So(resp.StatusCode(), ShouldEqual, 200)
  1354  
  1355  		resp, err = resty.R().Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
  1356  		So(err, ShouldBeNil)
  1357  		So(resp, ShouldNotBeNil)
  1358  		So(resp.StatusCode(), ShouldEqual, 401)
  1359  
  1360  		authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1361  		resp, err = resty.R().
  1362  			SetQueryParam("service", authorizationHeader.Service).
  1363  			SetQueryParam("scope", authorizationHeader.Scope).
  1364  			Get(authorizationHeader.Realm)
  1365  		So(err, ShouldBeNil)
  1366  		So(resp, ShouldNotBeNil)
  1367  		So(resp.StatusCode(), ShouldEqual, 200)
  1368  		err = json.Unmarshal(resp.Body(), &goodToken)
  1369  		So(err, ShouldBeNil)
  1370  
  1371  		resp, err = resty.R().
  1372  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1373  			Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
  1374  		So(err, ShouldBeNil)
  1375  		So(resp, ShouldNotBeNil)
  1376  		So(resp.StatusCode(), ShouldEqual, 202)
  1377  		loc := resp.Header().Get("Location")
  1378  
  1379  		resp, err = resty.R().
  1380  			SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
  1381  			SetHeader("Content-Type", "application/octet-stream").
  1382  			SetQueryParam("digest", digest).
  1383  			SetBody(blob).
  1384  			Put(baseURL + loc)
  1385  		So(err, ShouldBeNil)
  1386  		So(resp, ShouldNotBeNil)
  1387  		So(resp.StatusCode(), ShouldEqual, 401)
  1388  
  1389  		authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1390  		resp, err = resty.R().
  1391  			SetQueryParam("service", authorizationHeader.Service).
  1392  			SetQueryParam("scope", authorizationHeader.Scope).
  1393  			Get(authorizationHeader.Realm)
  1394  		So(err, ShouldBeNil)
  1395  		So(resp, ShouldNotBeNil)
  1396  		So(resp.StatusCode(), ShouldEqual, 200)
  1397  		err = json.Unmarshal(resp.Body(), &goodToken)
  1398  		So(err, ShouldBeNil)
  1399  
  1400  		resp, err = resty.R().
  1401  			SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
  1402  			SetHeader("Content-Type", "application/octet-stream").
  1403  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1404  			SetQueryParam("digest", digest).
  1405  			SetBody(blob).
  1406  			Put(baseURL + loc)
  1407  		So(err, ShouldBeNil)
  1408  		So(resp, ShouldNotBeNil)
  1409  		So(resp.StatusCode(), ShouldEqual, 201)
  1410  
  1411  		resp, err = resty.R().
  1412  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1413  			Get(baseURL + "/v2/" + AuthorizedNamespace + "/tags/list")
  1414  		So(err, ShouldBeNil)
  1415  		So(resp, ShouldNotBeNil)
  1416  		So(resp.StatusCode(), ShouldEqual, 401)
  1417  
  1418  		authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1419  		resp, err = resty.R().
  1420  			SetQueryParam("service", authorizationHeader.Service).
  1421  			SetQueryParam("scope", authorizationHeader.Scope).
  1422  			Get(authorizationHeader.Realm)
  1423  		So(err, ShouldBeNil)
  1424  		So(resp, ShouldNotBeNil)
  1425  		So(resp.StatusCode(), ShouldEqual, 200)
  1426  		err = json.Unmarshal(resp.Body(), &goodToken)
  1427  		So(err, ShouldBeNil)
  1428  
  1429  		resp, err = resty.R().
  1430  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1431  			Get(baseURL + "/v2/" + AuthorizedNamespace + "/tags/list")
  1432  		So(err, ShouldBeNil)
  1433  		So(resp, ShouldNotBeNil)
  1434  		So(resp.StatusCode(), ShouldEqual, 200)
  1435  
  1436  		resp, err = resty.R().
  1437  			Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
  1438  		So(err, ShouldBeNil)
  1439  		So(resp, ShouldNotBeNil)
  1440  		So(resp.StatusCode(), ShouldEqual, 401)
  1441  
  1442  		authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1443  		resp, err = resty.R().
  1444  			SetQueryParam("service", authorizationHeader.Service).
  1445  			SetQueryParam("scope", authorizationHeader.Scope).
  1446  			Get(authorizationHeader.Realm)
  1447  		So(err, ShouldBeNil)
  1448  		So(resp, ShouldNotBeNil)
  1449  		So(resp.StatusCode(), ShouldEqual, 200)
  1450  		var badToken accessTokenResponse
  1451  		err = json.Unmarshal(resp.Body(), &badToken)
  1452  		So(err, ShouldBeNil)
  1453  
  1454  		resp, err = resty.R().
  1455  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", badToken.AccessToken)).
  1456  			Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
  1457  		So(err, ShouldBeNil)
  1458  		So(resp, ShouldNotBeNil)
  1459  		So(resp.StatusCode(), ShouldEqual, 401)
  1460  	})
  1461  }
  1462  
  1463  func TestBearerAuthWithAllowReadAccess(t *testing.T) {
  1464  	Convey("Make a new controller", t, func() {
  1465  		authTestServer := makeAuthTestServer()
  1466  		defer authTestServer.Close()
  1467  
  1468  		port := GetFreePort()
  1469  		baseURL := GetBaseURL(port)
  1470  
  1471  		conf := config.New()
  1472  		conf.HTTP.Port = port
  1473  
  1474  		u, err := url.Parse(authTestServer.URL)
  1475  		So(err, ShouldBeNil)
  1476  
  1477  		conf.HTTP.Auth = &config.AuthConfig{
  1478  			Bearer: &config.BearerConfig{
  1479  				Cert:    ServerCert,
  1480  				Realm:   authTestServer.URL + "/auth/token",
  1481  				Service: u.Host,
  1482  			},
  1483  		}
  1484  		conf.HTTP.AllowReadAccess = true
  1485  		c := api.NewController(conf)
  1486  		dir, err := ioutil.TempDir("", "oci-repo-test")
  1487  		So(err, ShouldBeNil)
  1488  		defer os.RemoveAll(dir)
  1489  		c.Config.Storage.RootDirectory = dir
  1490  
  1491  		go startServer(c)
  1492  		defer stopServer(c)
  1493  		WaitTillServerReady(baseURL)
  1494  
  1495  		blob := []byte("hello, blob!")
  1496  		digest := godigest.FromBytes(blob).String()
  1497  
  1498  		resp, err := resty.R().Get(baseURL + "/v2/")
  1499  		So(err, ShouldBeNil)
  1500  		So(resp, ShouldNotBeNil)
  1501  		So(resp.StatusCode(), ShouldEqual, 401)
  1502  
  1503  		authorizationHeader := parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1504  		resp, err = resty.R().
  1505  			SetQueryParam("service", authorizationHeader.Service).
  1506  			SetQueryParam("scope", authorizationHeader.Scope).
  1507  			Get(authorizationHeader.Realm)
  1508  		So(err, ShouldBeNil)
  1509  		So(resp, ShouldNotBeNil)
  1510  		So(resp.StatusCode(), ShouldEqual, 200)
  1511  		var goodToken accessTokenResponse
  1512  		err = json.Unmarshal(resp.Body(), &goodToken)
  1513  		So(err, ShouldBeNil)
  1514  
  1515  		resp, err = resty.R().
  1516  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1517  			Get(baseURL + "/v2/")
  1518  		So(err, ShouldBeNil)
  1519  		So(resp, ShouldNotBeNil)
  1520  		So(resp.StatusCode(), ShouldEqual, 200)
  1521  
  1522  		resp, err = resty.R().Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
  1523  		So(err, ShouldBeNil)
  1524  		So(resp, ShouldNotBeNil)
  1525  		So(resp.StatusCode(), ShouldEqual, 401)
  1526  
  1527  		authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1528  		resp, err = resty.R().
  1529  			SetQueryParam("service", authorizationHeader.Service).
  1530  			SetQueryParam("scope", authorizationHeader.Scope).
  1531  			Get(authorizationHeader.Realm)
  1532  		So(err, ShouldBeNil)
  1533  		So(resp, ShouldNotBeNil)
  1534  		So(resp.StatusCode(), ShouldEqual, 200)
  1535  		err = json.Unmarshal(resp.Body(), &goodToken)
  1536  		So(err, ShouldBeNil)
  1537  
  1538  		resp, err = resty.R().
  1539  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1540  			Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
  1541  		So(err, ShouldBeNil)
  1542  		So(resp, ShouldNotBeNil)
  1543  		So(resp.StatusCode(), ShouldEqual, 202)
  1544  		loc := resp.Header().Get("Location")
  1545  
  1546  		resp, err = resty.R().
  1547  			SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
  1548  			SetHeader("Content-Type", "application/octet-stream").
  1549  			SetQueryParam("digest", digest).
  1550  			SetBody(blob).
  1551  			Put(baseURL + loc)
  1552  		So(err, ShouldBeNil)
  1553  		So(resp, ShouldNotBeNil)
  1554  		So(resp.StatusCode(), ShouldEqual, 401)
  1555  
  1556  		authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1557  		resp, err = resty.R().
  1558  			SetQueryParam("service", authorizationHeader.Service).
  1559  			SetQueryParam("scope", authorizationHeader.Scope).
  1560  			Get(authorizationHeader.Realm)
  1561  		So(err, ShouldBeNil)
  1562  		So(resp, ShouldNotBeNil)
  1563  		So(resp.StatusCode(), ShouldEqual, 200)
  1564  		err = json.Unmarshal(resp.Body(), &goodToken)
  1565  		So(err, ShouldBeNil)
  1566  
  1567  		resp, err = resty.R().
  1568  			SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
  1569  			SetHeader("Content-Type", "application/octet-stream").
  1570  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1571  			SetQueryParam("digest", digest).
  1572  			SetBody(blob).
  1573  			Put(baseURL + loc)
  1574  		So(err, ShouldBeNil)
  1575  		So(resp, ShouldNotBeNil)
  1576  		So(resp.StatusCode(), ShouldEqual, 201)
  1577  
  1578  		resp, err = resty.R().
  1579  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1580  			Get(baseURL + "/v2/" + AuthorizedNamespace + "/tags/list")
  1581  		So(err, ShouldBeNil)
  1582  		So(resp, ShouldNotBeNil)
  1583  		So(resp.StatusCode(), ShouldEqual, 401)
  1584  
  1585  		authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1586  		resp, err = resty.R().
  1587  			SetQueryParam("service", authorizationHeader.Service).
  1588  			SetQueryParam("scope", authorizationHeader.Scope).
  1589  			Get(authorizationHeader.Realm)
  1590  		So(err, ShouldBeNil)
  1591  		So(resp, ShouldNotBeNil)
  1592  		So(resp.StatusCode(), ShouldEqual, 200)
  1593  		err = json.Unmarshal(resp.Body(), &goodToken)
  1594  		So(err, ShouldBeNil)
  1595  
  1596  		resp, err = resty.R().
  1597  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", goodToken.AccessToken)).
  1598  			Get(baseURL + "/v2/" + AuthorizedNamespace + "/tags/list")
  1599  		So(err, ShouldBeNil)
  1600  		So(resp, ShouldNotBeNil)
  1601  		So(resp.StatusCode(), ShouldEqual, 200)
  1602  
  1603  		resp, err = resty.R().
  1604  			Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
  1605  		So(err, ShouldBeNil)
  1606  		So(resp, ShouldNotBeNil)
  1607  		So(resp.StatusCode(), ShouldEqual, 401)
  1608  
  1609  		authorizationHeader = parseBearerAuthHeader(resp.Header().Get("Www-Authenticate"))
  1610  		resp, err = resty.R().
  1611  			SetQueryParam("service", authorizationHeader.Service).
  1612  			SetQueryParam("scope", authorizationHeader.Scope).
  1613  			Get(authorizationHeader.Realm)
  1614  		So(err, ShouldBeNil)
  1615  		So(resp, ShouldNotBeNil)
  1616  		So(resp.StatusCode(), ShouldEqual, 200)
  1617  		var badToken accessTokenResponse
  1618  		err = json.Unmarshal(resp.Body(), &badToken)
  1619  		So(err, ShouldBeNil)
  1620  
  1621  		resp, err = resty.R().
  1622  			SetHeader("Authorization", fmt.Sprintf("Bearer %s", badToken.AccessToken)).
  1623  			Post(baseURL + "/v2/" + UnauthorizedNamespace + "/blobs/uploads/")
  1624  		So(err, ShouldBeNil)
  1625  		So(resp, ShouldNotBeNil)
  1626  		So(resp.StatusCode(), ShouldEqual, 401)
  1627  	})
  1628  }
  1629  
  1630  func makeAuthTestServer() *httptest.Server {
  1631  	cmTokenGenerator, err := auth.NewTokenGenerator(&auth.TokenGeneratorOptions{
  1632  		PrivateKeyPath: ServerKey,
  1633  		Audience:       "Zot Registry",
  1634  		Issuer:         "Zot",
  1635  		AddKIDHeader:   true,
  1636  	})
  1637  	if err != nil {
  1638  		panic(err)
  1639  	}
  1640  
  1641  	authTestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1642  		scope := r.URL.Query().Get("scope")
  1643  		parts := strings.Split(scope, ":")
  1644  		name := parts[1]
  1645  		actions := strings.Split(parts[2], ",")
  1646  		if name == UnauthorizedNamespace {
  1647  			actions = []string{}
  1648  		}
  1649  		access := []auth.AccessEntry{
  1650  			{
  1651  				Name:    name,
  1652  				Type:    "repository",
  1653  				Actions: actions,
  1654  			},
  1655  		}
  1656  		token, err := cmTokenGenerator.GenerateToken(access, time.Minute*1)
  1657  		if err != nil {
  1658  			panic(err)
  1659  		}
  1660  		w.Header().Set("Content-Type", "application/json")
  1661  		fmt.Fprintf(w, `{"access_token": "%s"}`, token)
  1662  	}))
  1663  
  1664  	return authTestServer
  1665  }
  1666  
  1667  func parseBearerAuthHeader(authHeaderRaw string) *authHeader {
  1668  	re := regexp.MustCompile(`([a-zA-z]+)="(.+?)"`)
  1669  	matches := re.FindAllStringSubmatch(authHeaderRaw, -1)
  1670  	m := make(map[string]string)
  1671  
  1672  	for i := 0; i < len(matches); i++ {
  1673  		m[matches[i][1]] = matches[i][2]
  1674  	}
  1675  
  1676  	var h authHeader
  1677  	if err := mapstructure.Decode(m, &h); err != nil {
  1678  		panic(err)
  1679  	}
  1680  
  1681  	return &h
  1682  }
  1683  
  1684  func TestAuthorizationWithBasicAuth(t *testing.T) {
  1685  	Convey("Make a new controller", t, func() {
  1686  		port := GetFreePort()
  1687  		baseURL := GetBaseURL(port)
  1688  
  1689  		conf := config.New()
  1690  		conf.HTTP.Port = port
  1691  		htpasswdPath := MakeHtpasswdFile()
  1692  		defer os.Remove(htpasswdPath)
  1693  
  1694  		conf.HTTP.Auth = &config.AuthConfig{
  1695  			HTPasswd: config.AuthHTPasswd{
  1696  				Path: htpasswdPath,
  1697  			},
  1698  		}
  1699  		conf.AccessControl = &config.AccessControlConfig{
  1700  			Repositories: config.Repositories{
  1701  				AuthorizationNamespace: config.PolicyGroup{
  1702  					Policies: []config.Policy{
  1703  						{
  1704  							Users:   []string{},
  1705  							Actions: []string{},
  1706  						},
  1707  					},
  1708  					DefaultPolicy: []string{},
  1709  				},
  1710  			},
  1711  			AdminPolicy: config.Policy{
  1712  				Users:   []string{},
  1713  				Actions: []string{},
  1714  			},
  1715  		}
  1716  
  1717  		c := api.NewController(conf)
  1718  		dir, err := ioutil.TempDir("", "oci-repo-test")
  1719  		if err != nil {
  1720  			panic(err)
  1721  		}
  1722  		defer os.RemoveAll(dir)
  1723  		err = CopyFiles("../../test/data", dir)
  1724  		if err != nil {
  1725  			panic(err)
  1726  		}
  1727  		c.Config.Storage.RootDirectory = dir
  1728  
  1729  		go startServer(c)
  1730  		defer stopServer(c)
  1731  		WaitTillServerReady(baseURL)
  1732  
  1733  		blob := []byte("hello, blob!")
  1734  		digest := godigest.FromBytes(blob).String()
  1735  
  1736  		// everybody should have access to /v2/
  1737  		resp, err := resty.R().SetBasicAuth(username, passphrase).
  1738  			Get(baseURL + "/v2/")
  1739  		So(err, ShouldBeNil)
  1740  		So(resp, ShouldNotBeNil)
  1741  		So(resp.StatusCode(), ShouldEqual, 200)
  1742  
  1743  		// everybody should have access to /v2/_catalog
  1744  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1745  			Get(baseURL + "/v2/_catalog")
  1746  		So(err, ShouldBeNil)
  1747  		So(resp, ShouldNotBeNil)
  1748  		So(resp.StatusCode(), ShouldEqual, 200)
  1749  		var e api.Error
  1750  		err = json.Unmarshal(resp.Body(), &e)
  1751  		So(err, ShouldBeNil)
  1752  
  1753  		// first let's use only repositories based policies
  1754  		// should get 403 without create
  1755  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1756  			Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
  1757  		So(err, ShouldBeNil)
  1758  		So(resp, ShouldNotBeNil)
  1759  		So(resp.StatusCode(), ShouldEqual, 403)
  1760  
  1761  		// add test user to repo's policy with create perm
  1762  		conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Users =
  1763  			append(conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Users, "test")
  1764  		conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions =
  1765  			append(conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions, "create")
  1766  
  1767  		// now it should get 202
  1768  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1769  			Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
  1770  		So(err, ShouldBeNil)
  1771  		So(resp, ShouldNotBeNil)
  1772  		So(resp.StatusCode(), ShouldEqual, 202)
  1773  		loc := resp.Header().Get("Location")
  1774  
  1775  		// uploading blob should get 201
  1776  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1777  			SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
  1778  			SetHeader("Content-Type", "application/octet-stream").
  1779  			SetQueryParam("digest", digest).
  1780  			SetBody(blob).
  1781  			Put(baseURL + loc)
  1782  		So(err, ShouldBeNil)
  1783  		So(resp, ShouldNotBeNil)
  1784  		So(resp.StatusCode(), ShouldEqual, 201)
  1785  
  1786  		// head blob should get 403 with read perm
  1787  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1788  			Head(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
  1789  		So(err, ShouldBeNil)
  1790  		So(resp, ShouldNotBeNil)
  1791  		So(resp.StatusCode(), ShouldEqual, 403)
  1792  
  1793  		// get blob should get 403 without read perm
  1794  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1795  			Get(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
  1796  		So(err, ShouldBeNil)
  1797  		So(resp, ShouldNotBeNil)
  1798  		So(resp.StatusCode(), ShouldEqual, 403)
  1799  
  1800  		// get tags without read access should get 403
  1801  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1802  			Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
  1803  		So(err, ShouldBeNil)
  1804  		So(resp, ShouldNotBeNil)
  1805  		So(resp.StatusCode(), ShouldEqual, 403)
  1806  
  1807  		// get tags with read access should get 200
  1808  		conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions =
  1809  			append(conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions, "read")
  1810  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1811  			Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
  1812  		So(err, ShouldBeNil)
  1813  		So(resp, ShouldNotBeNil)
  1814  		So(resp.StatusCode(), ShouldEqual, 200)
  1815  
  1816  		// head blob should get 200 now
  1817  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1818  			Head(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
  1819  		So(err, ShouldBeNil)
  1820  		So(resp, ShouldNotBeNil)
  1821  		So(resp.StatusCode(), ShouldEqual, 200)
  1822  
  1823  		// get blob should get 200 now
  1824  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1825  			Get(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
  1826  		So(err, ShouldBeNil)
  1827  		So(resp, ShouldNotBeNil)
  1828  		So(resp.StatusCode(), ShouldEqual, 200)
  1829  
  1830  		// delete blob should get 403 without delete perm
  1831  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1832  			Delete(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
  1833  		So(err, ShouldBeNil)
  1834  		So(resp, ShouldNotBeNil)
  1835  		So(resp.StatusCode(), ShouldEqual, 403)
  1836  
  1837  		// add delete perm on repo
  1838  		conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions =
  1839  			append(conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions, "delete")
  1840  
  1841  		// delete blob should get 202
  1842  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1843  			Delete(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
  1844  		So(err, ShouldBeNil)
  1845  		So(resp, ShouldNotBeNil)
  1846  		So(resp.StatusCode(), ShouldEqual, 202)
  1847  
  1848  		// get manifest should get 403, we don't have perm at all on this repo
  1849  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1850  			Get(baseURL + "/v2/zot-test/manifests/0.0.1")
  1851  		So(err, ShouldBeNil)
  1852  		So(resp, ShouldNotBeNil)
  1853  		So(resp.StatusCode(), ShouldEqual, 403)
  1854  
  1855  		// add read perm on repo
  1856  		conf.AccessControl.Repositories["zot-test"] = config.PolicyGroup{Policies: []config.Policy{
  1857  			{
  1858  				Users:   []string{"test"},
  1859  				Actions: []string{"read"},
  1860  			},
  1861  		}, DefaultPolicy: []string{}}
  1862  
  1863  		// get manifest should get 200 now
  1864  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1865  			Get(baseURL + "/v2/zot-test/manifests/0.0.1")
  1866  		So(err, ShouldBeNil)
  1867  		So(resp, ShouldNotBeNil)
  1868  		So(resp.StatusCode(), ShouldEqual, 200)
  1869  
  1870  		manifestBlob := resp.Body()
  1871  
  1872  		// put manifest should get 403 without create perm
  1873  		resp, err = resty.R().SetBasicAuth(username, passphrase).SetBody(manifestBlob).
  1874  			Put(baseURL + "/v2/zot-test/manifests/0.0.2")
  1875  		So(err, ShouldBeNil)
  1876  		So(resp, ShouldNotBeNil)
  1877  		So(resp.StatusCode(), ShouldEqual, 403)
  1878  
  1879  		// add create perm on repo
  1880  		conf.AccessControl.Repositories["zot-test"].Policies[0].Actions =
  1881  			append(conf.AccessControl.Repositories["zot-test"].Policies[0].Actions, "create")
  1882  
  1883  		// should get 201 with create perm
  1884  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1885  			SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
  1886  			SetBody(manifestBlob).
  1887  			Put(baseURL + "/v2/zot-test/manifests/0.0.2")
  1888  		So(err, ShouldBeNil)
  1889  		So(resp, ShouldNotBeNil)
  1890  		So(resp.StatusCode(), ShouldEqual, 201)
  1891  
  1892  		// update manifest should get 403 without update perm
  1893  		resp, err = resty.R().SetBasicAuth(username, passphrase).SetBody(manifestBlob).
  1894  			Put(baseURL + "/v2/zot-test/manifests/0.0.2")
  1895  		So(err, ShouldBeNil)
  1896  		So(resp, ShouldNotBeNil)
  1897  		So(resp.StatusCode(), ShouldEqual, 403)
  1898  
  1899  		// add update perm on repo
  1900  		conf.AccessControl.Repositories["zot-test"].Policies[0].Actions =
  1901  			append(conf.AccessControl.Repositories["zot-test"].Policies[0].Actions, "update")
  1902  
  1903  		// update manifest should get 201 with update perm
  1904  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1905  			SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
  1906  			SetBody(manifestBlob).
  1907  			Put(baseURL + "/v2/zot-test/manifests/0.0.2")
  1908  		So(err, ShouldBeNil)
  1909  		So(resp, ShouldNotBeNil)
  1910  		So(resp.StatusCode(), ShouldEqual, 201)
  1911  
  1912  		// now use default repo policy
  1913  		conf.AccessControl.Repositories["zot-test"].Policies[0].Actions = []string{}
  1914  		repoPolicy := conf.AccessControl.Repositories["zot-test"]
  1915  		repoPolicy.DefaultPolicy = []string{"update"}
  1916  		conf.AccessControl.Repositories["zot-test"] = repoPolicy
  1917  
  1918  		// update manifest should get 201 with update perm on repo's default policy
  1919  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1920  			SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
  1921  			SetBody(manifestBlob).
  1922  			Put(baseURL + "/v2/zot-test/manifests/0.0.2")
  1923  		So(err, ShouldBeNil)
  1924  		So(resp, ShouldNotBeNil)
  1925  		So(resp.StatusCode(), ShouldEqual, 201)
  1926  
  1927  		// with default read on repo should still get 200
  1928  		conf.AccessControl.Repositories[AuthorizationNamespace].Policies[0].Actions = []string{}
  1929  		repoPolicy = conf.AccessControl.Repositories[AuthorizationNamespace]
  1930  		repoPolicy.DefaultPolicy = []string{"read"}
  1931  		conf.AccessControl.Repositories[AuthorizationNamespace] = repoPolicy
  1932  
  1933  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1934  			Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
  1935  		So(err, ShouldBeNil)
  1936  		So(resp, ShouldNotBeNil)
  1937  		So(resp.StatusCode(), ShouldEqual, 200)
  1938  
  1939  		// upload blob without user create but with default create should get 200
  1940  		repoPolicy.DefaultPolicy = append(repoPolicy.DefaultPolicy, "create")
  1941  		conf.AccessControl.Repositories[AuthorizationNamespace] = repoPolicy
  1942  
  1943  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1944  			Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
  1945  		So(err, ShouldBeNil)
  1946  		So(resp, ShouldNotBeNil)
  1947  		So(resp.StatusCode(), ShouldEqual, 202)
  1948  
  1949  		//remove per repo policy
  1950  		repoPolicy = conf.AccessControl.Repositories[AuthorizationNamespace]
  1951  		repoPolicy.Policies = []config.Policy{}
  1952  		repoPolicy.DefaultPolicy = []string{}
  1953  		conf.AccessControl.Repositories[AuthorizationNamespace] = repoPolicy
  1954  
  1955  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1956  			Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
  1957  		So(err, ShouldBeNil)
  1958  		So(resp, ShouldNotBeNil)
  1959  		So(resp.StatusCode(), ShouldEqual, 403)
  1960  
  1961  		// let's use admin policy
  1962  		// remove all repo based policy
  1963  		delete(conf.AccessControl.Repositories, AuthorizationNamespace)
  1964  		delete(conf.AccessControl.Repositories, "zot-test")
  1965  
  1966  		// whithout any perm should get 403
  1967  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1968  			Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
  1969  		So(err, ShouldBeNil)
  1970  		So(resp, ShouldNotBeNil)
  1971  		So(resp.StatusCode(), ShouldEqual, 403)
  1972  
  1973  		// add read perm
  1974  		conf.AccessControl.AdminPolicy.Users = append(conf.AccessControl.AdminPolicy.Users, "test")
  1975  		conf.AccessControl.AdminPolicy.Actions = append(conf.AccessControl.AdminPolicy.Actions, "read")
  1976  		// with read perm should get 200
  1977  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1978  			Get(baseURL + "/v2/" + AuthorizationNamespace + "/tags/list")
  1979  		So(err, ShouldBeNil)
  1980  		So(resp, ShouldNotBeNil)
  1981  		So(resp.StatusCode(), ShouldEqual, 200)
  1982  
  1983  		// without create perm should 403
  1984  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1985  			Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
  1986  		So(err, ShouldBeNil)
  1987  		So(resp, ShouldNotBeNil)
  1988  		So(resp.StatusCode(), ShouldEqual, 403)
  1989  
  1990  		// add create perm
  1991  		conf.AccessControl.AdminPolicy.Actions = append(conf.AccessControl.AdminPolicy.Actions, "create")
  1992  		// with create perm should get 202
  1993  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  1994  			Post(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/uploads/")
  1995  		So(err, ShouldBeNil)
  1996  		So(resp, ShouldNotBeNil)
  1997  		So(resp.StatusCode(), ShouldEqual, 202)
  1998  		loc = resp.Header().Get("Location")
  1999  
  2000  		// uploading blob should get 201
  2001  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  2002  			SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
  2003  			SetHeader("Content-Type", "application/octet-stream").
  2004  			SetQueryParam("digest", digest).
  2005  			SetBody(blob).
  2006  			Put(baseURL + loc)
  2007  		So(err, ShouldBeNil)
  2008  		So(resp, ShouldNotBeNil)
  2009  		So(resp.StatusCode(), ShouldEqual, 201)
  2010  
  2011  		// without delete perm should 403
  2012  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  2013  			Delete(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
  2014  		So(err, ShouldBeNil)
  2015  		So(resp, ShouldNotBeNil)
  2016  		So(resp.StatusCode(), ShouldEqual, 403)
  2017  
  2018  		// add delete perm
  2019  		conf.AccessControl.AdminPolicy.Actions = append(conf.AccessControl.AdminPolicy.Actions, "delete")
  2020  		// with delete perm should get 202
  2021  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  2022  			Delete(baseURL + "/v2/" + AuthorizationNamespace + "/blobs/" + digest)
  2023  		So(err, ShouldBeNil)
  2024  		So(resp, ShouldNotBeNil)
  2025  		So(resp.StatusCode(), ShouldEqual, 202)
  2026  
  2027  		// without update perm should 403
  2028  		resp, err = resty.R().SetBasicAuth(username, passphrase).SetBody(manifestBlob).
  2029  			Put(baseURL + "/v2/zot-test/manifests/0.0.2")
  2030  		So(err, ShouldBeNil)
  2031  		So(resp, ShouldNotBeNil)
  2032  		So(resp.StatusCode(), ShouldEqual, 403)
  2033  
  2034  		// add update perm
  2035  		conf.AccessControl.AdminPolicy.Actions = append(conf.AccessControl.AdminPolicy.Actions, "update")
  2036  		// update manifest should get 201 with update perm
  2037  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  2038  			SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
  2039  			SetBody(manifestBlob).
  2040  			Put(baseURL + "/v2/zot-test/manifests/0.0.2")
  2041  		So(err, ShouldBeNil)
  2042  		So(resp, ShouldNotBeNil)
  2043  		So(resp.StatusCode(), ShouldEqual, 201)
  2044  
  2045  		conf.AccessControl = &config.AccessControlConfig{}
  2046  
  2047  		resp, err = resty.R().SetBasicAuth(username, passphrase).
  2048  			SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
  2049  			SetBody(manifestBlob).
  2050  			Put(baseURL + "/v2/zot-test/manifests/0.0.2")
  2051  		So(err, ShouldBeNil)
  2052  		So(resp, ShouldNotBeNil)
  2053  		So(resp.StatusCode(), ShouldEqual, 403)
  2054  	})
  2055  }
  2056  
  2057  func TestInvalidCases(t *testing.T) {
  2058  	Convey("Invalid repo dir", t, func() {
  2059  		port := GetFreePort()
  2060  		baseURL := GetBaseURL(port)
  2061  
  2062  		conf := config.New()
  2063  		conf.HTTP.Port = port
  2064  		htpasswdPath := MakeHtpasswdFileFromString(getCredString(username, passphrase))
  2065  
  2066  		defer os.Remove(htpasswdPath)
  2067  
  2068  		conf.HTTP.Auth = &config.AuthConfig{
  2069  			HTPasswd: config.AuthHTPasswd{
  2070  				Path: htpasswdPath,
  2071  			},
  2072  		}
  2073  
  2074  		c := api.NewController(conf)
  2075  
  2076  		err := os.Mkdir("oci-repo-test", 0000)
  2077  		if err != nil {
  2078  			panic(err)
  2079  		}
  2080  
  2081  		c.Config.Storage.RootDirectory = "oci-repo-test"
  2082  
  2083  		go startServer(c)
  2084  		defer func(ctrl *api.Controller) {
  2085  			err := ctrl.Server.Shutdown(context.Background())
  2086  			if err != nil {
  2087  				panic(err)
  2088  			}
  2089  
  2090  			err = os.RemoveAll(ctrl.Config.Storage.RootDirectory)
  2091  			if err != nil {
  2092  				panic(err)
  2093  			}
  2094  		}(c)
  2095  		WaitTillServerReady(baseURL)
  2096  
  2097  		digest := "sha256:8dd57e171a61368ffcfde38045ddb6ed74a32950c271c1da93eaddfb66a77e78"
  2098  		name := "zot-c-test"
  2099  
  2100  		client := resty.New()
  2101  
  2102  		params := make(map[string]string)
  2103  		params["from"] = "zot-cveid-test"
  2104  		params["mount"] = digest
  2105  
  2106  		postResponse, err := client.R().
  2107  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2108  			Post(fmt.Sprintf("%s/v2/%s/blobs/uploads/", baseURL, name))
  2109  		So(err, ShouldBeNil)
  2110  		So(postResponse.StatusCode(), ShouldEqual, 500)
  2111  	})
  2112  }
  2113  func TestHTTPReadOnly(t *testing.T) {
  2114  	Convey("Single cred", t, func() {
  2115  		singleCredtests := []string{}
  2116  		user := ALICE
  2117  		password := ALICE
  2118  		singleCredtests = append(singleCredtests, getCredString(user, password))
  2119  		singleCredtests = append(singleCredtests, getCredString(user, password)+"\n")
  2120  
  2121  		port := GetFreePort()
  2122  		baseURL := GetBaseURL(port)
  2123  
  2124  		for _, testString := range singleCredtests {
  2125  			func() {
  2126  				conf := config.New()
  2127  				conf.HTTP.Port = port
  2128  				// enable read-only mode
  2129  				conf.HTTP.ReadOnly = true
  2130  
  2131  				htpasswdPath := MakeHtpasswdFileFromString(testString)
  2132  				defer os.Remove(htpasswdPath)
  2133  				conf.HTTP.Auth = &config.AuthConfig{
  2134  					HTPasswd: config.AuthHTPasswd{
  2135  						Path: htpasswdPath,
  2136  					},
  2137  				}
  2138  				c := api.NewController(conf)
  2139  				dir, err := ioutil.TempDir("", "oci-repo-test")
  2140  				if err != nil {
  2141  					panic(err)
  2142  				}
  2143  				defer os.RemoveAll(dir)
  2144  				c.Config.Storage.RootDirectory = dir
  2145  
  2146  				go startServer(c)
  2147  				defer stopServer(c)
  2148  				WaitTillServerReady(baseURL)
  2149  
  2150  				// with creds, should get expected status code
  2151  				resp, _ := resty.R().SetBasicAuth(user, password).Get(baseURL + "/v2/")
  2152  				So(resp, ShouldNotBeNil)
  2153  				So(resp.StatusCode(), ShouldEqual, 200)
  2154  
  2155  				// with creds, any modifications should still fail on read-only mode
  2156  				resp, err = resty.R().SetBasicAuth(user, password).
  2157  					Post(baseURL + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
  2158  				So(err, ShouldBeNil)
  2159  				So(resp, ShouldNotBeNil)
  2160  				So(resp.StatusCode(), ShouldEqual, 405)
  2161  
  2162  				//with invalid creds, it should fail
  2163  				resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(baseURL + "/v2/")
  2164  				So(resp, ShouldNotBeNil)
  2165  				So(resp.StatusCode(), ShouldEqual, 401)
  2166  			}()
  2167  		}
  2168  	})
  2169  }
  2170  
  2171  func TestCrossRepoMount(t *testing.T) {
  2172  	Convey("Cross Repo Mount", t, func() {
  2173  		port := GetFreePort()
  2174  		baseURL := GetBaseURL(port)
  2175  
  2176  		conf := config.New()
  2177  		conf.HTTP.Port = port
  2178  		htpasswdPath := MakeHtpasswdFileFromString(getCredString(username, passphrase))
  2179  
  2180  		defer os.Remove(htpasswdPath)
  2181  
  2182  		conf.HTTP.Auth = &config.AuthConfig{
  2183  			HTPasswd: config.AuthHTPasswd{
  2184  				Path: htpasswdPath,
  2185  			},
  2186  		}
  2187  
  2188  		c := api.NewController(conf)
  2189  
  2190  		dir, err := ioutil.TempDir("", "oci-repo-test")
  2191  		if err != nil {
  2192  			panic(err)
  2193  		}
  2194  
  2195  		err = CopyFiles("../../test/data", dir)
  2196  		if err != nil {
  2197  			panic(err)
  2198  		}
  2199  		defer os.RemoveAll(dir)
  2200  		c.Config.Storage.RootDirectory = dir
  2201  
  2202  		go startServer(c)
  2203  		defer stopServer(c)
  2204  		WaitTillServerReady(baseURL)
  2205  
  2206  		params := make(map[string]string)
  2207  		digest := "sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29"
  2208  		d := godigest.Digest(digest)
  2209  		name := "zot-cve-test"
  2210  		params["mount"] = digest
  2211  		params["from"] = name
  2212  
  2213  		client := resty.New()
  2214  		headResponse, err := client.R().SetBasicAuth(username, passphrase).
  2215  			Head(fmt.Sprintf("%s/v2/%s/blobs/%s", baseURL, name, digest))
  2216  		So(err, ShouldBeNil)
  2217  		So(headResponse.StatusCode(), ShouldEqual, 200)
  2218  
  2219  		// All invalid request of mount should return 202.
  2220  		params["mount"] = "sha:"
  2221  
  2222  		postResponse, err := client.R().
  2223  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2224  			Post(baseURL + "/v2/zot-c-test/blobs/uploads/")
  2225  		So(err, ShouldBeNil)
  2226  		So(postResponse.StatusCode(), ShouldEqual, 202)
  2227  
  2228  		incorrectParams := make(map[string]string)
  2229  		incorrectParams["mount"] = "sha256:63a795ca90aa6e7dda60941e826810a4cd0a2e73ea02bf458241df2a5c973e29"
  2230  		incorrectParams["from"] = "zot-x-test"
  2231  
  2232  		postResponse, err = client.R().
  2233  			SetBasicAuth(username, passphrase).SetQueryParams(incorrectParams).
  2234  			Post(baseURL + "/v2/zot-y-test/blobs/uploads/")
  2235  		So(err, ShouldBeNil)
  2236  		So(postResponse.StatusCode(), ShouldEqual, 202)
  2237  
  2238  		// Use correct request
  2239  		// This is correct request but it will return 202 because blob is not present in cache.
  2240  		params["mount"] = digest
  2241  		postResponse, err = client.R().
  2242  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2243  			Post(baseURL + "/v2/zot-c-test/blobs/uploads/")
  2244  		So(err, ShouldBeNil)
  2245  		So(postResponse.StatusCode(), ShouldEqual, 202)
  2246  
  2247  		// Send same request again
  2248  		postResponse, err = client.R().
  2249  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2250  			Post(baseURL + "/v2/zot-c-test/blobs/uploads/")
  2251  		So(err, ShouldBeNil)
  2252  		So(postResponse.StatusCode(), ShouldEqual, 202)
  2253  
  2254  		// Valid requests
  2255  		postResponse, err = client.R().
  2256  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2257  			Post(baseURL + "/v2/zot-d-test/blobs/uploads/")
  2258  		So(err, ShouldBeNil)
  2259  		So(postResponse.StatusCode(), ShouldEqual, 202)
  2260  
  2261  		headResponse, err = client.R().SetBasicAuth(username, passphrase).
  2262  			Head(fmt.Sprintf("%s/v2/zot-cv-test/blobs/%s", baseURL, digest))
  2263  		So(err, ShouldBeNil)
  2264  		So(headResponse.StatusCode(), ShouldEqual, 404)
  2265  
  2266  		postResponse, err = client.R().
  2267  			SetBasicAuth(username, passphrase).SetQueryParams(params).Post(baseURL + "/v2/zot-c-test/blobs/uploads/")
  2268  		So(err, ShouldBeNil)
  2269  		So(postResponse.StatusCode(), ShouldEqual, 202)
  2270  
  2271  		postResponse, err = client.R().
  2272  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2273  			Post(baseURL + "/v2/ /blobs/uploads/")
  2274  		So(err, ShouldBeNil)
  2275  		So(postResponse.StatusCode(), ShouldEqual, 404)
  2276  
  2277  		digest = "sha256:63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29"
  2278  
  2279  		blob := "63a795ca90aa6e7cca60941e826810a4cd0a2e73ea02bf458241df2a5c973e29"
  2280  
  2281  		buf, err := ioutil.ReadFile(path.Join(c.Config.Storage.RootDirectory, "zot-cve-test/blobs/sha256/"+blob))
  2282  		if err != nil {
  2283  			panic(err)
  2284  		}
  2285  
  2286  		postResponse, err = client.R().SetHeader("Content-type", "application/octet-stream").
  2287  			SetBasicAuth(username, passphrase).SetQueryParam("digest", "sha256:"+blob).
  2288  			SetBody(buf).Post(baseURL + "/v2/zot-d-test/blobs/uploads/")
  2289  		So(err, ShouldBeNil)
  2290  		So(postResponse.StatusCode(), ShouldEqual, 201)
  2291  
  2292  		// We have uploaded a blob and since we have provided digest it should be full blob upload and there should be entry
  2293  		// in cache, now try mount blob request status and it should be 201 because now blob is present in cache
  2294  		// and it should do hard link.
  2295  
  2296  		params["mount"] = digest
  2297  		postResponse, err = client.R().
  2298  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2299  			Post(baseURL + "/v2/zot-mount-test/blobs/uploads/")
  2300  		So(err, ShouldBeNil)
  2301  		So(postResponse.StatusCode(), ShouldEqual, 201)
  2302  
  2303  		// Check os.SameFile here
  2304  		cachePath := path.Join(c.Config.Storage.RootDirectory, "zot-d-test", "blobs/sha256", d.Hex())
  2305  
  2306  		cacheFi, err := os.Stat(cachePath)
  2307  		So(err, ShouldBeNil)
  2308  
  2309  		linkPath := path.Join(c.Config.Storage.RootDirectory, "zot-mount-test", "blobs/sha256", d.Hex())
  2310  
  2311  		linkFi, err := os.Stat(linkPath)
  2312  		So(err, ShouldBeNil)
  2313  
  2314  		So(os.SameFile(cacheFi, linkFi), ShouldEqual, true)
  2315  
  2316  		// Now try another mount request and this time it should be from above uploaded repo i.e zot-mount-test
  2317  		// mount request should pass and should return 201.
  2318  		params["mount"] = digest
  2319  		params["from"] = "zot-mount-test"
  2320  		postResponse, err = client.R().
  2321  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2322  			Post(baseURL + "/v2/zot-mount1-test/blobs/uploads/")
  2323  		So(err, ShouldBeNil)
  2324  		So(postResponse.StatusCode(), ShouldEqual, 201)
  2325  
  2326  		linkPath = path.Join(c.Config.Storage.RootDirectory, "zot-mount1-test", "blobs/sha256", d.Hex())
  2327  
  2328  		linkFi, err = os.Stat(linkPath)
  2329  		So(err, ShouldBeNil)
  2330  
  2331  		So(os.SameFile(cacheFi, linkFi), ShouldEqual, true)
  2332  
  2333  		headResponse, err = client.R().SetBasicAuth(username, passphrase).
  2334  			Head(fmt.Sprintf("%s/v2/zot-cv-test/blobs/%s", baseURL, digest))
  2335  		So(err, ShouldBeNil)
  2336  		So(headResponse.StatusCode(), ShouldEqual, 200)
  2337  
  2338  		// Invalid request
  2339  		params = make(map[string]string)
  2340  		params["mount"] = "sha256:"
  2341  		postResponse, err = client.R().
  2342  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2343  			Post(baseURL + "/v2/zot-mount-test/blobs/uploads/")
  2344  		So(err, ShouldBeNil)
  2345  		So(postResponse.StatusCode(), ShouldEqual, 405)
  2346  
  2347  		params = make(map[string]string)
  2348  		params["from"] = "zot-cve-test"
  2349  		postResponse, err = client.R().
  2350  			SetBasicAuth(username, passphrase).SetQueryParams(params).
  2351  			Post(baseURL + "/v2/zot-mount-test/blobs/uploads/")
  2352  		So(err, ShouldBeNil)
  2353  		So(postResponse.StatusCode(), ShouldEqual, 405)
  2354  	})
  2355  
  2356  	Convey("Disable dedupe and cache", t, func() {
  2357  		port := GetFreePort()
  2358  		baseURL := GetBaseURL(port)
  2359  
  2360  		conf := config.New()
  2361  		conf.HTTP.Port = port
  2362  		htpasswdPath := MakeHtpasswdFileFromString(getCredString(username, passphrase))
  2363  
  2364  		defer os.Remove(htpasswdPath)
  2365  
  2366  		conf.HTTP.Auth = &config.AuthConfig{
  2367  			HTPasswd: config.AuthHTPasswd{
  2368  				Path: htpasswdPath,
  2369  			},
  2370  		}
  2371  
  2372  		c := api.NewController(conf)
  2373  
  2374  		dir, err := ioutil.TempDir("", "oci-repo-test")
  2375  		if err != nil {
  2376  			panic(err)
  2377  		}
  2378  
  2379  		err = CopyFiles("../../test/data", dir)
  2380  		if err != nil {
  2381  			panic(err)
  2382  		}
  2383  		defer os.RemoveAll(dir)
  2384  
  2385  		c.Config.Storage.RootDirectory = dir
  2386  		c.Config.Storage.Dedupe = false
  2387  		c.Config.Storage.GC = false
  2388  
  2389  		go startServer(c)
  2390  		defer stopServer(c)
  2391  		WaitTillServerReady(baseURL)
  2392  
  2393  		digest := "sha256:7a0437f04f83f084b7ed68ad9c4a4947e12fc4e1b006b38129bac89114ec3621"
  2394  		name := "zot-c-test"
  2395  		client := resty.New()
  2396  		headResponse, err := client.R().SetBasicAuth(username, passphrase).
  2397  			Head(fmt.Sprintf("%s/v2/%s/blobs/%s", baseURL, name, digest))
  2398  		So(err, ShouldBeNil)
  2399  		So(headResponse.StatusCode(), ShouldEqual, 404)
  2400  	})
  2401  }
  2402  
  2403  func TestParallelRequests(t *testing.T) {
  2404  	testCases := []struct {
  2405  		srcImageName  string
  2406  		srcImageTag   string
  2407  		destImageName string
  2408  		destImageTag  string
  2409  		testCaseName  string
  2410  	}{
  2411  		{
  2412  			srcImageName:  "zot-test",
  2413  			srcImageTag:   "0.0.1",
  2414  			destImageName: "zot-1-test",
  2415  			destImageTag:  "0.0.1",
  2416  			testCaseName:  "Request-1",
  2417  		},
  2418  		{
  2419  			srcImageName:  "zot-test",
  2420  			srcImageTag:   "0.0.1",
  2421  			destImageName: "zot-2-test",
  2422  			testCaseName:  "Request-2",
  2423  		},
  2424  		{
  2425  			srcImageName:  "zot-cve-test",
  2426  			srcImageTag:   "0.0.1",
  2427  			destImageName: "a/zot-3-test",
  2428  			testCaseName:  "Request-3",
  2429  		},
  2430  		{
  2431  			srcImageName:  "zot-cve-test",
  2432  			srcImageTag:   "0.0.1",
  2433  			destImageName: "b/zot-4-test",
  2434  			testCaseName:  "Request-4",
  2435  		},
  2436  		{
  2437  			srcImageName:  "zot-cve-test",
  2438  			srcImageTag:   "0.0.1",
  2439  			destImageName: "zot-5-test",
  2440  			testCaseName:  "Request-5",
  2441  		},
  2442  		{
  2443  			srcImageName:  "zot-cve-test",
  2444  			srcImageTag:   "0.0.1",
  2445  			destImageName: "zot-1-test",
  2446  			testCaseName:  "Request-6",
  2447  		},
  2448  		{
  2449  			srcImageName:  "zot-cve-test",
  2450  			srcImageTag:   "0.0.1",
  2451  			destImageName: "zot-2-test",
  2452  			testCaseName:  "Request-7",
  2453  		},
  2454  		{
  2455  			srcImageName:  "zot-cve-test",
  2456  			srcImageTag:   "0.0.1",
  2457  			destImageName: "zot-3-test",
  2458  			testCaseName:  "Request-8",
  2459  		},
  2460  		{
  2461  			srcImageName:  "zot-cve-test",
  2462  			srcImageTag:   "0.0.1",
  2463  			destImageName: "zot-4-test",
  2464  			testCaseName:  "Request-9",
  2465  		},
  2466  		{
  2467  			srcImageName:  "zot-cve-test",
  2468  			srcImageTag:   "0.0.1",
  2469  			destImageName: "zot-5-test",
  2470  			testCaseName:  "Request-10",
  2471  		},
  2472  		{
  2473  			srcImageName:  "zot-test",
  2474  			srcImageTag:   "0.0.1",
  2475  			destImageName: "zot-1-test",
  2476  			destImageTag:  "0.0.1",
  2477  			testCaseName:  "Request-11",
  2478  		},
  2479  		{
  2480  			srcImageName:  "zot-test",
  2481  			srcImageTag:   "0.0.1",
  2482  			destImageName: "zot-2-test",
  2483  			testCaseName:  "Request-12",
  2484  		},
  2485  		{
  2486  			srcImageName:  "zot-cve-test",
  2487  			srcImageTag:   "0.0.1",
  2488  			destImageName: "a/zot-3-test",
  2489  			testCaseName:  "Request-13",
  2490  		},
  2491  		{
  2492  			srcImageName:  "zot-cve-test",
  2493  			srcImageTag:   "0.0.1",
  2494  			destImageName: "b/zot-4-test",
  2495  			testCaseName:  "Request-14",
  2496  		},
  2497  	}
  2498  
  2499  	port := GetFreePort()
  2500  	baseURL := GetBaseURL(port)
  2501  
  2502  	conf := config.New()
  2503  	conf.HTTP.Port = port
  2504  	htpasswdPath := MakeHtpasswdFileFromString(getCredString(username, passphrase))
  2505  
  2506  	conf.HTTP.Auth = &config.AuthConfig{
  2507  		HTPasswd: config.AuthHTPasswd{
  2508  			Path: htpasswdPath,
  2509  		},
  2510  	}
  2511  
  2512  	c := api.NewController(conf)
  2513  
  2514  	dir, err := ioutil.TempDir("", "oci-repo-test")
  2515  	if err != nil {
  2516  		panic(err)
  2517  	}
  2518  
  2519  	firstSubDir, err := ioutil.TempDir("", "oci-sub-dir")
  2520  	if err != nil {
  2521  		panic(err)
  2522  	}
  2523  
  2524  	secondSubDir, err := ioutil.TempDir("", "oci-sub-dir")
  2525  	if err != nil {
  2526  		panic(err)
  2527  	}
  2528  
  2529  	subPaths := make(map[string]config.StorageConfig)
  2530  
  2531  	subPaths["/a"] = config.StorageConfig{RootDirectory: firstSubDir}
  2532  	subPaths["/b"] = config.StorageConfig{RootDirectory: secondSubDir}
  2533  
  2534  	c.Config.Storage.SubPaths = subPaths
  2535  	c.Config.Storage.RootDirectory = dir
  2536  
  2537  	go startServer(c)
  2538  	WaitTillServerReady(baseURL)
  2539  
  2540  	// without creds, should get access error
  2541  	for i, testcase := range testCases {
  2542  		testcase := testcase
  2543  		j := i
  2544  
  2545  		t.Run(testcase.testCaseName, func(t *testing.T) {
  2546  			t.Parallel()
  2547  			client := resty.New()
  2548  
  2549  			tagResponse, err := client.R().SetBasicAuth(username, passphrase).
  2550  				Get(baseURL + "/v2/" + testcase.destImageName + "/tags/list")
  2551  			assert.Equal(t, err, nil, "Error should be nil")
  2552  			assert.NotEqual(t, tagResponse.StatusCode(), 400, "bad request")
  2553  
  2554  			manifestList := getAllManifests(path.Join("../../test/data", testcase.srcImageName))
  2555  
  2556  			for _, manifest := range manifestList {
  2557  				headResponse, err := client.R().SetBasicAuth(username, passphrase).
  2558  					Head(baseURL + "/v2/" + testcase.destImageName + "/manifests/" + manifest)
  2559  				assert.Equal(t, err, nil, "Error should be nil")
  2560  				assert.Equal(t, headResponse.StatusCode(), 404, "response status code should return 404")
  2561  
  2562  				getResponse, err := client.R().SetBasicAuth(username, passphrase).
  2563  					Get(baseURL + "/v2/" + testcase.destImageName + "/manifests/" + manifest)
  2564  				assert.Equal(t, err, nil, "Error should be nil")
  2565  				assert.Equal(t, getResponse.StatusCode(), 404, "response status code should return 404")
  2566  			}
  2567  
  2568  			blobList := getAllBlobs(path.Join("../../test/data", testcase.srcImageName))
  2569  
  2570  			for _, blob := range blobList {
  2571  				// Get request of blob
  2572  				headResponse, err := client.R().
  2573  					SetBasicAuth(username, passphrase).
  2574  					Head(baseURL + "/v2/" + testcase.destImageName + "/blobs/sha256:" + blob)
  2575  
  2576  				assert.Equal(t, err, nil, "Should not be nil")
  2577  				assert.NotEqual(t, headResponse.StatusCode(), 500, "internal server error should not occurred")
  2578  
  2579  				getResponse, err := client.R().
  2580  					SetBasicAuth(username, passphrase).
  2581  					Get(baseURL + "/v2/" + testcase.destImageName + "/blobs/sha256:" + blob)
  2582  
  2583  				assert.Equal(t, err, nil, "Should not be nil")
  2584  				assert.NotEqual(t, getResponse.StatusCode(), 500, "internal server error should not occurred")
  2585  
  2586  				blobPath := path.Join("../../test/data", testcase.srcImageName, "blobs/sha256", blob)
  2587  
  2588  				buf, err := ioutil.ReadFile(blobPath)
  2589  				if err != nil {
  2590  					panic(err)
  2591  				}
  2592  
  2593  				// Post request of blob
  2594  				postResponse, err := client.R().
  2595  					SetHeader("Content-type", "application/octet-stream").
  2596  					SetBasicAuth(username, passphrase).
  2597  					SetBody(buf).Post(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/")
  2598  
  2599  				assert.Equal(t, err, nil, "Error should be nil")
  2600  				assert.NotEqual(t, postResponse.StatusCode(), 500, "response status code should not return 500")
  2601  
  2602  				// Post request with query parameter
  2603  
  2604  				if j%2 == 0 {
  2605  					postResponse, err = client.R().
  2606  						SetHeader("Content-type", "application/octet-stream").
  2607  						SetBasicAuth(username, passphrase).
  2608  						SetBody(buf).
  2609  						Post(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/")
  2610  
  2611  					assert.Equal(t, err, nil, "Error should be nil")
  2612  					assert.NotEqual(t, postResponse.StatusCode(), 500, "response status code should not return 500")
  2613  
  2614  					var sessionID string
  2615  					sessionIDList := postResponse.Header().Values("Blob-Upload-UUID")
  2616  					if len(sessionIDList) == 0 {
  2617  						location := postResponse.Header().Values("Location")
  2618  						firstLocation := location[0]
  2619  						splitLocation := strings.Split(firstLocation, "/")
  2620  						sessionID = splitLocation[len(splitLocation)-1]
  2621  					} else {
  2622  						sessionID = sessionIDList[0]
  2623  					}
  2624  
  2625  					file, err := os.Open(blobPath)
  2626  					if err != nil {
  2627  						panic(err)
  2628  					}
  2629  
  2630  					defer file.Close()
  2631  
  2632  					reader := bufio.NewReader(file)
  2633  
  2634  					b := make([]byte, 5*1024*1024)
  2635  
  2636  					if j%4 == 0 {
  2637  						readContent := 0
  2638  						for {
  2639  							n, err := reader.Read(b)
  2640  							if err != nil {
  2641  								if err == io.EOF {
  2642  									break
  2643  								}
  2644  								panic(err)
  2645  							}
  2646  							// Patch request of blob
  2647  
  2648  							patchResponse, err := client.R().
  2649  								SetBody(b[0:n]).
  2650  								SetHeader("Content-Type", "application/octet-stream").
  2651  								SetHeader("Content-Length", fmt.Sprintf("%d", n)).
  2652  								SetHeader("Content-Range", fmt.Sprintf("%d", readContent)+"-"+fmt.Sprintf("%d", readContent+n-1)).
  2653  								SetBasicAuth(username, passphrase).
  2654  								Patch(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/" + sessionID)
  2655  
  2656  							assert.Equal(t, err, nil, "Error should be nil")
  2657  							assert.NotEqual(t, patchResponse.StatusCode(), 500, "response status code should not return 500")
  2658  
  2659  							readContent += n
  2660  						}
  2661  					} else {
  2662  						for {
  2663  							n, err := reader.Read(b)
  2664  							if err != nil {
  2665  								if err == io.EOF {
  2666  									break
  2667  								}
  2668  								panic(err)
  2669  							}
  2670  							// Patch request of blob
  2671  
  2672  							patchResponse, err := client.R().SetBody(b[0:n]).SetHeader("Content-type", "application/octet-stream").
  2673  								SetBasicAuth(username, passphrase).
  2674  								Patch(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/" + sessionID)
  2675  
  2676  							if err != nil {
  2677  								panic(err)
  2678  							}
  2679  
  2680  							assert.Equal(t, err, nil, "Error should be nil")
  2681  							assert.NotEqual(t, patchResponse.StatusCode(), 500, "response status code should not return 500")
  2682  						}
  2683  					}
  2684  				} else {
  2685  					postResponse, err = client.R().
  2686  						SetHeader("Content-type", "application/octet-stream").
  2687  						SetBasicAuth(username, passphrase).
  2688  						SetBody(buf).SetQueryParam("digest", "sha256:"+blob).
  2689  						Post(baseURL + "/v2/" + testcase.destImageName + "/blobs/uploads/")
  2690  
  2691  					assert.Equal(t, err, nil, "Error should be nil")
  2692  					assert.NotEqual(t, postResponse.StatusCode(), 500, "response status code should not return 500")
  2693  				}
  2694  
  2695  				headResponse, err = client.R().
  2696  					SetBasicAuth(username, passphrase).
  2697  					Head(baseURL + "/v2/" + testcase.destImageName + "/blobs/sha256:" + blob)
  2698  
  2699  				assert.Equal(t, err, nil, "Should not be nil")
  2700  				assert.NotEqual(t, headResponse.StatusCode(), 500, "response should return success code")
  2701  
  2702  				getResponse, err = client.R().
  2703  					SetBasicAuth(username, passphrase).
  2704  					Get(baseURL + "/v2/" + testcase.destImageName + "/blobs/sha256:" + blob)
  2705  
  2706  				assert.Equal(t, err, nil, "Should not be nil")
  2707  				assert.NotEqual(t, getResponse.StatusCode(), 500, "response should return success code")
  2708  			}
  2709  
  2710  			tagResponse, err = client.R().SetBasicAuth(username, passphrase).
  2711  				Get(baseURL + "/v2/" + testcase.destImageName + "/tags/list")
  2712  			assert.Equal(t, err, nil, "Error should be nil")
  2713  			assert.Equal(t, tagResponse.StatusCode(), 200, "response status code should return success code")
  2714  
  2715  			repoResponse, err := client.R().SetBasicAuth(username, passphrase).
  2716  				Get(baseURL + "/v2/_catalog")
  2717  			assert.Equal(t, err, nil, "Error should be nil")
  2718  			assert.Equal(t, repoResponse.StatusCode(), 200, "response status code should return success code")
  2719  		})
  2720  	}
  2721  }
  2722  
  2723  func TestHardLink(t *testing.T) {
  2724  	Convey("Validate hard link", t, func() {
  2725  		port := GetFreePort()
  2726  		baseURL := GetBaseURL(port)
  2727  
  2728  		conf := config.New()
  2729  		conf.HTTP.Port = port
  2730  		htpasswdPath := MakeHtpasswdFileFromString(getCredString(username, passphrase))
  2731  
  2732  		conf.HTTP.Auth = &config.AuthConfig{
  2733  			HTPasswd: config.AuthHTPasswd{
  2734  				Path: htpasswdPath,
  2735  			},
  2736  		}
  2737  
  2738  		c := api.NewController(conf)
  2739  
  2740  		dir, err := ioutil.TempDir("", "hard-link-test")
  2741  		if err != nil {
  2742  			panic(err)
  2743  		}
  2744  		defer os.RemoveAll(dir)
  2745  
  2746  		err = os.Chmod(dir, 0400)
  2747  		if err != nil {
  2748  			panic(err)
  2749  		}
  2750  
  2751  		subDir, err := ioutil.TempDir("", "sub-hardlink-test")
  2752  		if err != nil {
  2753  			panic(err)
  2754  		}
  2755  		defer os.RemoveAll(subDir)
  2756  
  2757  		err = os.Chmod(subDir, 0400)
  2758  		if err != nil {
  2759  			panic(err)
  2760  		}
  2761  
  2762  		c.Config.Storage.RootDirectory = dir
  2763  		subPaths := make(map[string]config.StorageConfig)
  2764  
  2765  		subPaths["/a"] = config.StorageConfig{RootDirectory: subDir, Dedupe: true}
  2766  		c.Config.Storage.SubPaths = subPaths
  2767  
  2768  		go startServer(c)
  2769  		defer stopServer(c)
  2770  		WaitTillServerReady(baseURL)
  2771  
  2772  		err = os.Chmod(dir, 0644)
  2773  		if err != nil {
  2774  			panic(err)
  2775  		}
  2776  
  2777  		err = os.Chmod(subDir, 0644)
  2778  		if err != nil {
  2779  			panic(err)
  2780  		}
  2781  
  2782  		So(c.Config.Storage.Dedupe, ShouldEqual, false)
  2783  	})
  2784  }
  2785  
  2786  func TestImageSignatures(t *testing.T) {
  2787  	Convey("Validate signatures", t, func() {
  2788  		// start a new server
  2789  		port := GetFreePort()
  2790  		baseURL := GetBaseURL(port)
  2791  
  2792  		conf := config.New()
  2793  		conf.HTTP.Port = port
  2794  
  2795  		c := api.NewController(conf)
  2796  		dir, err := ioutil.TempDir("", "oci-repo-test")
  2797  		if err != nil {
  2798  			panic(err)
  2799  		}
  2800  		defer os.RemoveAll(dir)
  2801  		c.Config.Storage.RootDirectory = dir
  2802  		go func(controller *api.Controller) {
  2803  			// this blocks
  2804  			if err := controller.Run(); err != nil {
  2805  				return
  2806  			}
  2807  		}(c)
  2808  		// wait till ready
  2809  		for {
  2810  			_, err := resty.R().Get(baseURL)
  2811  			if err == nil {
  2812  				break
  2813  			}
  2814  			time.Sleep(100 * time.Millisecond)
  2815  		}
  2816  		defer func(controller *api.Controller) {
  2817  			ctx := context.Background()
  2818  			_ = controller.Server.Shutdown(ctx)
  2819  		}(c)
  2820  
  2821  		repoName := "signed-repo"
  2822  
  2823  		// create a blob/layer
  2824  		resp, err := resty.R().Post(baseURL + fmt.Sprintf("/v2/%s/blobs/uploads/", repoName))
  2825  		So(err, ShouldBeNil)
  2826  		So(resp.StatusCode(), ShouldEqual, 202)
  2827  		loc := Location(baseURL, resp)
  2828  		So(loc, ShouldNotBeEmpty)
  2829  
  2830  		resp, err = resty.R().Get(loc)
  2831  		So(err, ShouldBeNil)
  2832  		So(resp.StatusCode(), ShouldEqual, 204)
  2833  		content := []byte("this is a blob")
  2834  		digest := godigest.FromBytes(content)
  2835  		So(digest, ShouldNotBeNil)
  2836  		// monolithic blob upload: success
  2837  		resp, err = resty.R().SetQueryParam("digest", digest.String()).
  2838  			SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc)
  2839  		So(err, ShouldBeNil)
  2840  		So(resp.StatusCode(), ShouldEqual, 201)
  2841  		blobLoc := resp.Header().Get("Location")
  2842  		So(blobLoc, ShouldNotBeEmpty)
  2843  		So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
  2844  		So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
  2845  
  2846  		// create a manifest
  2847  		m := ispec.Manifest{
  2848  			Config: ispec.Descriptor{
  2849  				Digest: digest,
  2850  				Size:   int64(len(content)),
  2851  			},
  2852  			Layers: []ispec.Descriptor{
  2853  				{
  2854  					MediaType: "application/vnd.oci.image.layer.v1.tar",
  2855  					Digest:    digest,
  2856  					Size:      int64(len(content)),
  2857  				},
  2858  			},
  2859  		}
  2860  		m.SchemaVersion = 2
  2861  		content, err = json.Marshal(m)
  2862  		So(err, ShouldBeNil)
  2863  		digest = godigest.FromBytes(content)
  2864  		So(digest, ShouldNotBeNil)
  2865  		resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
  2866  			SetBody(content).Put(baseURL + fmt.Sprintf("/v2/%s/manifests/1.0", repoName))
  2867  		So(err, ShouldBeNil)
  2868  		So(resp.StatusCode(), ShouldEqual, 201)
  2869  		d := resp.Header().Get(api.DistContentDigestKey)
  2870  		So(d, ShouldNotBeEmpty)
  2871  		So(d, ShouldEqual, digest.String())
  2872  
  2873  		Convey("Validate cosign signatures", func() {
  2874  			cwd, err := os.Getwd()
  2875  			So(err, ShouldBeNil)
  2876  			defer func() { _ = os.Chdir(cwd) }()
  2877  			tdir, err := ioutil.TempDir("", "cosign")
  2878  			So(err, ShouldBeNil)
  2879  			defer os.RemoveAll(tdir)
  2880  			_ = os.Chdir(tdir)
  2881  
  2882  			// generate a keypair
  2883  			os.Setenv("COSIGN_PASSWORD", "")
  2884  			err = generate.GenerateKeyPairCmd(context.TODO(), "", nil)
  2885  			So(err, ShouldBeNil)
  2886  
  2887  			// sign the image
  2888  			err = sign.SignCmd(context.TODO(),
  2889  				sign.KeyOpts{KeyRef: path.Join(tdir, "cosign.key"), PassFunc: generate.GetPass},
  2890  				options.RegistryOptions{AllowInsecure: true},
  2891  				map[string]interface{}{"tag": "1.0"},
  2892  				[]string{fmt.Sprintf("localhost:%s/%s@%s", port, repoName, digest.String())},
  2893  				"", true, "", false, false, "")
  2894  			So(err, ShouldBeNil)
  2895  
  2896  			// verify the image
  2897  			a := &options.AnnotationOptions{Annotations: []string{"tag=1.0"}}
  2898  			amap, err := a.AnnotationsMap()
  2899  			So(err, ShouldBeNil)
  2900  			v := verify.VerifyCommand{
  2901  				RegistryOptions: options.RegistryOptions{AllowInsecure: true},
  2902  				CheckClaims:     true,
  2903  				KeyRef:          path.Join(tdir, "cosign.pub"),
  2904  				Annotations:     amap,
  2905  			}
  2906  			err = v.Exec(context.TODO(), []string{fmt.Sprintf("localhost:%s/%s:%s", port, repoName, "1.0")})
  2907  			So(err, ShouldBeNil)
  2908  
  2909  			// verify the image with incorrect tag
  2910  			a = &options.AnnotationOptions{Annotations: []string{"tag=2.0"}}
  2911  			amap, err = a.AnnotationsMap()
  2912  			So(err, ShouldBeNil)
  2913  			v = verify.VerifyCommand{
  2914  				RegistryOptions: options.RegistryOptions{AllowInsecure: true},
  2915  				CheckClaims:     true,
  2916  				KeyRef:          path.Join(tdir, "cosign.pub"),
  2917  				Annotations:     amap,
  2918  			}
  2919  			err = v.Exec(context.TODO(), []string{fmt.Sprintf("localhost:%s/%s:%s", port, repoName, "1.0")})
  2920  			So(err, ShouldNotBeNil)
  2921  
  2922  			// verify the image with incorrect key
  2923  			a = &options.AnnotationOptions{Annotations: []string{"tag=1.0"}}
  2924  			amap, err = a.AnnotationsMap()
  2925  			So(err, ShouldBeNil)
  2926  			v = verify.VerifyCommand{
  2927  				CheckClaims:     true,
  2928  				RegistryOptions: options.RegistryOptions{AllowInsecure: true},
  2929  				KeyRef:          path.Join(tdir, "cosign.key"),
  2930  				Annotations:     amap,
  2931  			}
  2932  			err = v.Exec(context.TODO(), []string{fmt.Sprintf("localhost:%s/%s:%s", port, repoName, "1.0")})
  2933  			So(err, ShouldNotBeNil)
  2934  
  2935  			// generate another keypair
  2936  			err = os.Remove(path.Join(tdir, "cosign.pub"))
  2937  			So(err, ShouldBeNil)
  2938  			err = os.Remove(path.Join(tdir, "cosign.key"))
  2939  			So(err, ShouldBeNil)
  2940  
  2941  			os.Setenv("COSIGN_PASSWORD", "")
  2942  			err = generate.GenerateKeyPairCmd(context.TODO(), "", nil)
  2943  			So(err, ShouldBeNil)
  2944  
  2945  			// verify the image with incorrect key
  2946  			a = &options.AnnotationOptions{Annotations: []string{"tag=1.0"}}
  2947  			amap, err = a.AnnotationsMap()
  2948  			So(err, ShouldBeNil)
  2949  			v = verify.VerifyCommand{
  2950  				CheckClaims:     true,
  2951  				RegistryOptions: options.RegistryOptions{AllowInsecure: true},
  2952  				KeyRef:          path.Join(tdir, "cosign.pub"),
  2953  				Annotations:     amap,
  2954  			}
  2955  			err = v.Exec(context.TODO(), []string{fmt.Sprintf("localhost:%s/%s:%s", port, repoName, "1.0")})
  2956  			So(err, ShouldNotBeNil)
  2957  		})
  2958  
  2959  		Convey("Validate notation signatures", func() {
  2960  			cwd, err := os.Getwd()
  2961  			So(err, ShouldBeNil)
  2962  			defer func() { _ = os.Chdir(cwd) }()
  2963  			tdir, err := ioutil.TempDir("", "notation")
  2964  			So(err, ShouldBeNil)
  2965  			defer os.RemoveAll(tdir)
  2966  			_ = os.Chdir(tdir)
  2967  
  2968  			// "notation" (notaryv2) doesn't yet support exported apis, so use the binary instead
  2969  			notPath, err := exec.LookPath("notation")
  2970  			So(notPath, ShouldNotBeNil)
  2971  			So(err, ShouldBeNil)
  2972  
  2973  			os.Setenv("XDG_CONFIG_HOME", tdir)
  2974  
  2975  			// generate a keypair
  2976  			cmd := exec.Command("notation", "cert", "generate-test", "--trust", "good")
  2977  			err = cmd.Run()
  2978  			So(err, ShouldBeNil)
  2979  
  2980  			// generate another keypair
  2981  			cmd = exec.Command("notation", "cert", "generate-test", "--trust", "bad")
  2982  			err = cmd.Run()
  2983  			So(err, ShouldBeNil)
  2984  
  2985  			// sign the image
  2986  			image := fmt.Sprintf("localhost:%s/%s:%s", port, repoName, "1.0")
  2987  			cmd = exec.Command("notation", "sign", "--key", "good", "--plain-http", image)
  2988  			err = cmd.Run()
  2989  			So(err, ShouldBeNil)
  2990  
  2991  			// verify the image
  2992  			cmd = exec.Command("notation", "verify", "--cert", "good", "--plain-http", image)
  2993  			out, err := cmd.CombinedOutput()
  2994  			So(err, ShouldBeNil)
  2995  			msg := string(out)
  2996  			So(msg, ShouldNotBeEmpty)
  2997  			So(strings.Contains(msg, "verification failure"), ShouldBeFalse)
  2998  
  2999  			// verify the image with incorrect key
  3000  			cmd = exec.Command("notation", "verify", "--cert", "bad", "--plain-http", image)
  3001  			out, err = cmd.CombinedOutput()
  3002  			So(err, ShouldNotBeNil)
  3003  			msg = string(out)
  3004  			So(msg, ShouldNotBeEmpty)
  3005  			So(strings.Contains(msg, "verification failure"), ShouldBeTrue)
  3006  
  3007  			// check unsupported manifest media type
  3008  			resp, err = resty.R().SetHeader("Content-Type", "application/vnd.unsupported.image.manifest.v1+json").
  3009  				SetBody(content).Put(baseURL + fmt.Sprintf("/v2/%s/manifests/1.0", repoName))
  3010  			So(err, ShouldBeNil)
  3011  			So(resp.StatusCode(), ShouldEqual, 415)
  3012  
  3013  			// check invalid content with artifact media type
  3014  			resp, err = resty.R().SetHeader("Content-Type", artifactspec.MediaTypeArtifactManifest).
  3015  				SetBody([]byte("bogus")).Put(baseURL + fmt.Sprintf("/v2/%s/manifests/1.0", repoName))
  3016  			So(err, ShouldBeNil)
  3017  			So(resp.StatusCode(), ShouldEqual, 400)
  3018  
  3019  			Convey("Validate corrupted signature", func() {
  3020  				// verify with corrupted signature
  3021  				resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
  3022  					fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
  3023  				So(err, ShouldBeNil)
  3024  				So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  3025  				var refs api.ReferenceList
  3026  				err = json.Unmarshal(resp.Body(), &refs)
  3027  				So(err, ShouldBeNil)
  3028  				So(len(refs.References), ShouldEqual, 1)
  3029  				err = ioutil.WriteFile(path.Join(dir, repoName, "blobs",
  3030  					strings.ReplaceAll(refs.References[0].Digest.String(), ":", "/")), []byte("corrupt"), 0600)
  3031  				So(err, ShouldBeNil)
  3032  				resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
  3033  					fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
  3034  				So(err, ShouldBeNil)
  3035  				So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
  3036  				cmd = exec.Command("notation", "verify", "--cert", "good", "--plain-http", image)
  3037  				out, err = cmd.CombinedOutput()
  3038  				So(err, ShouldNotBeNil)
  3039  				msg = string(out)
  3040  				So(msg, ShouldNotBeEmpty)
  3041  			})
  3042  
  3043  			Convey("Validate deleted signature", func() {
  3044  				// verify with corrupted signature
  3045  				resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
  3046  					fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
  3047  				So(err, ShouldBeNil)
  3048  				So(resp.StatusCode(), ShouldEqual, http.StatusOK)
  3049  				var refs api.ReferenceList
  3050  				err = json.Unmarshal(resp.Body(), &refs)
  3051  				So(err, ShouldBeNil)
  3052  				So(len(refs.References), ShouldEqual, 1)
  3053  				err = os.Remove(path.Join(dir, repoName, "blobs",
  3054  					strings.ReplaceAll(refs.References[0].Digest.String(), ":", "/")))
  3055  				So(err, ShouldBeNil)
  3056  				resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
  3057  					fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
  3058  				So(err, ShouldBeNil)
  3059  				So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
  3060  				cmd = exec.Command("notation", "verify", "--cert", "good", "--plain-http", image)
  3061  				out, err = cmd.CombinedOutput()
  3062  				So(err, ShouldNotBeNil)
  3063  				msg = string(out)
  3064  				So(msg, ShouldNotBeEmpty)
  3065  			})
  3066  		})
  3067  
  3068  		Convey("GetReferrers", func() {
  3069  			// cover error paths
  3070  			resp, err := resty.R().Get(
  3071  				fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, "badRepo", "badDigest"))
  3072  			So(err, ShouldBeNil)
  3073  			So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
  3074  
  3075  			resp, err = resty.R().Get(
  3076  				fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, "badDigest"))
  3077  			So(err, ShouldBeNil)
  3078  			So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
  3079  
  3080  			resp, err = resty.R().Get(
  3081  				fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
  3082  			So(err, ShouldBeNil)
  3083  			So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
  3084  
  3085  			resp, err = resty.R().SetQueryParam("artifactType", "badArtifact").Get(
  3086  				fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, repoName, digest.String()))
  3087  			So(err, ShouldBeNil)
  3088  			So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
  3089  
  3090  			resp, err = resty.R().SetQueryParam("artifactType", notreg.ArtifactTypeNotation).Get(
  3091  				fmt.Sprintf("%s/oras/artifacts/v1/%s/manifests/%s/referrers", baseURL, "badRepo", digest.String()))
  3092  			So(err, ShouldBeNil)
  3093  			So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
  3094  		})
  3095  	})
  3096  }
  3097  
  3098  func getAllBlobs(imagePath string) []string {
  3099  	blobList := make([]string, 0)
  3100  
  3101  	if !storage.DirExists(imagePath) {
  3102  		return []string{}
  3103  	}
  3104  
  3105  	buf, err := ioutil.ReadFile(path.Join(imagePath, "index.json"))
  3106  
  3107  	if err != nil {
  3108  		panic(err)
  3109  	}
  3110  
  3111  	var index ispec.Index
  3112  	if err := json.Unmarshal(buf, &index); err != nil {
  3113  		panic(err)
  3114  	}
  3115  
  3116  	var digest godigest.Digest
  3117  
  3118  	for _, m := range index.Manifests {
  3119  		digest = m.Digest
  3120  		blobList = append(blobList, digest.Encoded())
  3121  		p := path.Join(imagePath, "blobs", digest.Algorithm().String(), digest.Encoded())
  3122  
  3123  		buf, err = ioutil.ReadFile(p)
  3124  
  3125  		if err != nil {
  3126  			panic(err)
  3127  		}
  3128  
  3129  		var manifest ispec.Manifest
  3130  		if err := json.Unmarshal(buf, &manifest); err != nil {
  3131  			panic(err)
  3132  		}
  3133  
  3134  		blobList = append(blobList, manifest.Config.Digest.Encoded())
  3135  
  3136  		for _, layer := range manifest.Layers {
  3137  			blobList = append(blobList, layer.Digest.Encoded())
  3138  		}
  3139  	}
  3140  
  3141  	return blobList
  3142  }
  3143  
  3144  func getAllManifests(imagePath string) []string {
  3145  	manifestList := make([]string, 0)
  3146  
  3147  	if !storage.DirExists(imagePath) {
  3148  		return []string{}
  3149  	}
  3150  
  3151  	buf, err := ioutil.ReadFile(path.Join(imagePath, "index.json"))
  3152  
  3153  	if err != nil {
  3154  		panic(err)
  3155  	}
  3156  
  3157  	var index ispec.Index
  3158  	if err := json.Unmarshal(buf, &index); err != nil {
  3159  		panic(err)
  3160  	}
  3161  
  3162  	var digest godigest.Digest
  3163  
  3164  	for _, m := range index.Manifests {
  3165  		digest = m.Digest
  3166  		manifestList = append(manifestList, digest.Encoded())
  3167  	}
  3168  
  3169  	return manifestList
  3170  }
  3171  
  3172  func startServer(c *api.Controller) {
  3173  	// this blocks
  3174  	if err := c.Run(); err != nil {
  3175  		return
  3176  	}
  3177  }
  3178  
  3179  func stopServer(c *api.Controller) {
  3180  	ctx := context.Background()
  3181  	_ = c.Server.Shutdown(ctx)
  3182  }