package main import ( "context" "crypto/sha256" "encoding/json" "fmt" "net/http" "net/http/httptest" "net/url" "strings" "testing" ) const childDigest = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" func TestPhysicalPresence(t *testing.T) { t.Parallel() for _, status := range []int{200, 404, 401, 403, 429, 500, 502} { t.Run(fmt.Sprint(status), func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { if request.URL.Path != "/api/v1/repository/candidates/pause/manifest/"+testDigest+"/security" || request.URL.Query().Get("vulnerabilities") != "false" { t.Errorf("unexpected lookup: %s", request.URL) } if request.Header.Get("Authorization") != "Bearer reader-token" { t.Error("lookup did not use reader authority") } response.WriteHeader(status) _, _ = fmt.Fprint(response, `{"status":"queued"}`) })) defer server.Close() api, err := url.Parse(server.URL) if err != nil { t.Fatal(err) } reader := &quayReader{api: api, client: server.Client(), candidates: "reader-token"} present, err := reader.physicallyPresent(context.Background(), "candidates/pause", testDigest) if present != (status == 200) || (err != nil) != (status != 200 && status != 404) { t.Fatalf("HTTP %d: present=%t err=%v", status, present, err) } }) } } func TestQuayScanAndExistence(t *testing.T) { t.Parallel() child := fmt.Sprintf(`{"schemaVersion":2,"layers":[{"digest":%q}]}`, testDigest) childDigest := fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(child))) index := fmt.Sprintf(`{"manifests":[{"digest":%q}]}`, childDigest) testDigest := fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(index))) severity := "Low" var server *httptest.Server server = httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { switch { case request.URL.Path == "/v2/candidates/pause/manifests/"+childDigest: _, _ = fmt.Fprint(response, child) case request.URL.Path == "/v2/auth": username, password, found := request.BasicAuth() if !found || username != oauthUser || (password != "candidate-token" && password != "mirror-token") { http.Error(response, "unauthorized", http.StatusUnauthorized) return } response.Header().Set("Content-Type", "application/json") if _, err := fmt.Fprintf(response, `{"token":%q}`, password+"-registry"); err != nil { t.Errorf("write token response: %v", err) } case request.URL.Path == "/v2/candidates/pause/manifests/"+testDigest || request.URL.Path == "/v2/mirror/pause/manifests/"+testDigest: expected := "Bearer candidate-token-registry" if strings.Contains(request.URL.Path, "/mirror/") { expected = "Bearer mirror-token-registry" } if request.Header.Get("Authorization") != expected { response.Header().Set("WWW-Authenticate", `Bearer realm="`+server.URL+`/v2/auth",service="fixture",scope="repository:test:pull"`) response.WriteHeader(http.StatusUnauthorized) return } if strings.Contains(request.URL.Path, "/mirror/") { response.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json") if _, err := fmt.Fprint(response, `{"schemaVersion":2}`); err != nil { t.Errorf("write manifest response: %v", err) } return } response.Header().Set("Content-Type", "application/vnd.oci.image.index.v1+json") if _, err := fmt.Fprintf(response, `{"manifests":[{"digest":%q}]}`, childDigest); err != nil { t.Errorf("write index response: %v", err) } case strings.HasSuffix(request.URL.Path, "/security"): if request.Header.Get("Authorization") != "Bearer candidate-token" || request.URL.Query().Get("vulnerabilities") != "true" { http.Error(response, "unauthorized", http.StatusUnauthorized) return } data := inventoryFixture() data.Inventory.Hash = childDigest data.Layer.Name = childDigest (*data.Layer.Features)[0].Vulnerabilities = []vulnerability{{Severity: severity}} if err := json.NewEncoder(response).Encode(securityDoc{Status: "scanned", Data: data}); err != nil { t.Errorf("write scan response: %v", err) } default: http.NotFound(response, request) } })) defer server.Close() api, err := url.Parse(server.URL) if err != nil { t.Fatal(err) } reader := &quayReader{ api: api, registry: server.Listener.Addr().String(), client: server.Client(), candidates: "candidate-token", mirror: "mirror-token", } phase, err := reader.scan(context.Background(), "candidates/pause", testDigest, "High") if err != nil { t.Fatal(err) } if phase != scanAccepted { t.Fatalf("unexpected accepted phase %q", phase) } severity = "Critical" phase, err = reader.scan(context.Background(), "candidates/pause", testDigest, "High") if err != nil { t.Fatal(err) } if phase != scanRejected { t.Fatalf("unexpected rejected phase %q", phase) } severity = "Unknown" phase, err = reader.scan(context.Background(), "candidates/pause", testDigest, "Critical") if err != nil || phase != scanRejected { t.Fatalf("unknown severity: %s %v", phase, err) } present, err := reader.exists(context.Background(), "mirror/pause", testDigest) if err != nil { t.Fatal(err) } if !present { t.Fatal("promoted manifest was not observed") } present, err = reader.exists(context.Background(), "candidates/missing", testDigest) if err != nil { t.Fatal(err) } if present { t.Fatal("missing manifest was observed") } } func TestSingleManifestScanDigest(t *testing.T) { t.Parallel() digests, err := scanDigests([]byte(`{"schemaVersion":2}`), testDigest) if err != nil { t.Fatal(err) } if len(digests) != 1 || digests[0] != testDigest { t.Fatalf("unexpected scan digests %#v", digests) } }