163 lines
5.7 KiB
Go
163 lines
5.7 KiB
Go
|
|
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)
|
||
|
|
}
|
||
|
|
}
|