Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check ORAS-Api-Version for referrers API #227

Merged
merged 4 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions registry/remote/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"io"
"mime"
"net/http"
"regexp"
"strconv"
"strings"

Expand All @@ -40,6 +41,10 @@ import (
"oras.land/oras-go/v2/registry/remote/internal/errutil"
)

// referrersApiRegex checks referrers API version.
// Reference: https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md#versioning
var referrersApiRegex = regexp.MustCompile(`^oras/1\.(0|[1-9]\d*)$`)

// Client is an interface for a HTTP client.
type Client interface {
// Do sends an HTTP request and returns an HTTP response.
Expand Down Expand Up @@ -407,6 +412,9 @@ func (r *Repository) referrers(ctx context.Context, artifactType string, fn func
if resp.StatusCode != http.StatusOK {
return "", errutil.ParseErrorResponse(resp)
}
if err := verifyOrasApiVersion(resp); err != nil {
return "", err
}

var page struct {
References []artifactspec.Descriptor `json:"references"`
Expand Down Expand Up @@ -978,3 +986,13 @@ func verifyContentDigest(resp *http.Response, expected digest.Digest) error {
}
return nil
}

// verifyOrasApiVersion verifies "ORAS-Api-Version" header if present.
// Reference: https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md#versioning
func verifyOrasApiVersion(resp *http.Response) error {
versionStr := resp.Header.Get("ORAS-Api-Version")
if !referrersApiRegex.MatchString(versionStr) {
return fmt.Errorf("%w: Unsupported ORAS-Api-Version: %q", errdef.ErrUnsupportedVersion, versionStr)
}
return nil
}
100 changes: 99 additions & 1 deletion registry/remote/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,7 @@ func TestRepository_Predecessors(t *testing.T) {
w.WriteHeader(http.StatusBadRequest)
return
}
w.Header().Set("ORAS-Api-Version", "oras/1.0")
var referrers []artifactspec.Descriptor
switch q.Get("test") {
case "foo":
Expand Down Expand Up @@ -985,6 +986,7 @@ func TestRepository_Referrers(t *testing.T) {
w.WriteHeader(http.StatusBadRequest)
return
}
w.Header().Set("ORAS-Api-Version", "oras/1.0")
var referrers []artifactspec.Descriptor
switch q.Get("test") {
case "foo":
Expand Down Expand Up @@ -1040,6 +1042,100 @@ func TestRepository_Referrers(t *testing.T) {
}
}

func TestRepository_Referrers_Incompatible(t *testing.T) {
manifest := []byte(`{"layers":[]}`)
manifestDesc := ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageManifest,
Digest: digest.FromBytes(manifest),
Size: int64(len(manifest)),
}
var ts *httptest.Server
ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := "/v2/test/_oras/artifacts/referrers"
if r.Method != http.MethodGet || r.URL.Path != path {
t.Errorf("unexpected access: %s %q", r.Method, r.URL)
w.WriteHeader(http.StatusNotFound)
return
}
w.Header().Set("ORAS-Api-Version", "oras/2.0")
}))
defer ts.Close()
uri, err := url.Parse(ts.URL)
if err != nil {
t.Fatalf("invalid test http server: %v", err)
}

repo, err := NewRepository(uri.Host + "/test")
if err != nil {
t.Fatalf("NewRepository() error = %v", err)
}
repo.PlainHTTP = true

ctx := context.Background()
if err := repo.Referrers(ctx, manifestDesc, "", func(got []artifactspec.Descriptor) error {
return nil
}); err == nil {
t.Error("Repository.Referrers() incompatible version not rejected")
}
}

func Test_verifyOrasApiVersion(t *testing.T) {
params := []struct {
name string
version string
compatible bool
}{
{
name: "exact",
version: "oras/1.0",
compatible: true,
},
{
name: "major same, minor different",
version: "oras/1.11",
compatible: true,
},
{
name: "major different",
version: "oras/2.0",
compatible: false,
},
{
name: "invalid prefix",
version: "*oras/1.0",
compatible: false,
},
{
name: "invalid minor version",
version: "oras/1.01",
compatible: false,
},
{
name: "not dot",
version: "oras/1#0",
compatible: false,
},
{
name: "no version",
version: "",
compatible: false,
},
}

for _, tt := range params {
t.Run(tt.name, func(t *testing.T) {
resp := &http.Response{Header: http.Header{}}
if tt.version != "" {
resp.Header.Set("ORAS-Api-Version", tt.version)
}
err := verifyOrasApiVersion(resp)
if (err == nil) != tt.compatible {
t.Errorf("verifyOrasApiVersion() compatible = %v, want = %v", err == nil, tt.compatible)
}
})
}
}

func TestRepository_Referrers_Fallback(t *testing.T) {
manifest := []byte(`{"layers":[]}`)
manifestDesc := ocispec.Descriptor{
Expand Down Expand Up @@ -1099,6 +1195,7 @@ func TestRepository_Referrers_Fallback(t *testing.T) {
w.WriteHeader(http.StatusBadRequest)
return
}
w.Header().Set("ORAS-Api-Version", "oras/1.0")
var referrers []artifactspec.Descriptor
switch q.Get("test") {
case "foo":
Expand Down Expand Up @@ -1147,7 +1244,6 @@ func TestRepository_Referrers_Fallback(t *testing.T) {
}); err != nil {
t.Errorf("Repository.Referrers() error = %v", err)
}

}

func TestRepository_Referrers_ServerFiltering(t *testing.T) {
Expand Down Expand Up @@ -1210,6 +1306,7 @@ func TestRepository_Referrers_ServerFiltering(t *testing.T) {
w.WriteHeader(http.StatusBadRequest)
return
}
w.Header().Set("ORAS-Api-Version", "oras/1.0")
var referrers []artifactspec.Descriptor
switch q.Get("test") {
case "foo":
Expand Down Expand Up @@ -1346,6 +1443,7 @@ func TestRepository_Referrers_ClientFiltering(t *testing.T) {
w.WriteHeader(http.StatusBadRequest)
return
}
w.Header().Set("ORAS-Api-Version", "oras/1.0")
var referrers []artifactspec.Descriptor
switch q.Get("test") {
case "foo":
Expand Down