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 }