cuelabs.dev/go/oci/ociregistry@v0.0.0-20240906074133-82eb438dd565/ociclient/auth_test.go (about)

     1  package ociclient
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"net/url"
     8  	"strings"
     9  	"testing"
    10  
    11  	"cuelabs.dev/go/oci/ociregistry"
    12  	"cuelabs.dev/go/oci/ociregistry/ociauth"
    13  	"cuelabs.dev/go/oci/ociregistry/ocimem"
    14  	"cuelabs.dev/go/oci/ociregistry/ociserver"
    15  	"github.com/go-quicktest/qt"
    16  	"github.com/opencontainers/go-digest"
    17  )
    18  
    19  func TestAuthScopes(t *testing.T) {
    20  
    21  	// Test that we're passing the expected authorization scopes to the various parts of the API.
    22  	// All the call semantics themselves are tested elsewhere, but we want to be
    23  	// sure that we're passing the right required auth scopes to the authorizer.
    24  
    25  	srv := httptest.NewServer(ociserver.New(ocimem.New(), nil))
    26  	defer srv.Close()
    27  	srvURL, _ := url.Parse(srv.URL)
    28  
    29  	assertScope := func(scope string, f func(ctx context.Context, r ociregistry.Interface)) {
    30  		assertAuthScope(t, srvURL.Host, scope, f)
    31  	}
    32  
    33  	assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
    34  		r.GetBlob(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    35  	})
    36  	assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
    37  		r.GetBlobRange(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 100, 200)
    38  	})
    39  	assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
    40  		r.GetManifest(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    41  	})
    42  	assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
    43  		r.GetTag(ctx, "foo/bar", "sometag")
    44  	})
    45  	assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
    46  		r.ResolveBlob(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    47  	})
    48  	assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
    49  		r.ResolveManifest(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    50  	})
    51  	assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
    52  		r.ResolveTag(ctx, "foo/bar", "sometag")
    53  	})
    54  	assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
    55  		r.PushBlob(ctx, "foo/bar", ociregistry.Descriptor{
    56  			MediaType: "application/json",
    57  			Digest:    "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    58  			Size:      3,
    59  		}, strings.NewReader("foo"))
    60  	})
    61  	assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
    62  		w, err := r.PushBlobChunked(ctx, "foo/bar", 0)
    63  		qt.Assert(t, qt.IsNil(err))
    64  		w.Write([]byte("foo"))
    65  		w.Close()
    66  
    67  		id := w.ID()
    68  		w, err = r.PushBlobChunkedResume(ctx, "foo/bar", id, 3, 0)
    69  		qt.Assert(t, qt.IsNil(err))
    70  		w.Write([]byte("bar"))
    71  		_, err = w.Commit(digest.FromString("foobar"))
    72  		qt.Assert(t, qt.IsNil(err))
    73  	})
    74  	assertScope("repository:x/y:pull repository:z/w:push", func(ctx context.Context, r ociregistry.Interface) {
    75  		r.MountBlob(ctx, "x/y", "z/w", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    76  	})
    77  	assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
    78  		r.PushManifest(ctx, "foo/bar", "sometag", []byte("something"), "application/json")
    79  	})
    80  	assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
    81  		r.DeleteBlob(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    82  	})
    83  	assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
    84  		r.DeleteManifest(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    85  	})
    86  	assertScope("repository:foo/bar:push", func(ctx context.Context, r ociregistry.Interface) {
    87  		r.DeleteTag(ctx, "foo/bar", "sometag")
    88  	})
    89  	assertScope("registry:catalog:*", func(ctx context.Context, r ociregistry.Interface) {
    90  		ociregistry.All(r.Repositories(ctx, ""))
    91  	})
    92  	assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
    93  		ociregistry.All(r.Tags(ctx, "foo/bar", ""))
    94  	})
    95  	assertScope("repository:foo/bar:pull", func(ctx context.Context, r ociregistry.Interface) {
    96  		ociregistry.All(r.Referrers(ctx, "foo/bar", "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ""))
    97  	})
    98  }
    99  
   100  // assertAuthScope asserts that the given function makes a client request with the
   101  // given scope to the given URL.
   102  func assertAuthScope(t *testing.T, host string, scope string, f func(ctx context.Context, r ociregistry.Interface)) {
   103  	requestedScopes := make(map[string]bool)
   104  
   105  	// Check that the context is passed through with values intact.
   106  	type foo struct{}
   107  	ctx := context.WithValue(context.Background(), foo{}, true)
   108  
   109  	client, err := New(host, &Options{
   110  		Insecure: true,
   111  		Transport: transportFunc(func(req *http.Request) (*http.Response, error) {
   112  			ctx := req.Context()
   113  			qt.Check(t, qt.Equals(ctx.Value(foo{}), true))
   114  			scope := ociauth.RequestInfoFromContext(ctx).RequiredScope
   115  			requestedScopes[scope.Canonical().String()] = true
   116  			return http.DefaultTransport.RoundTrip(req)
   117  		}),
   118  	})
   119  	qt.Assert(t, qt.IsNil(err))
   120  	f(ctx, client)
   121  	qt.Assert(t, qt.HasLen(requestedScopes, 1))
   122  	t.Logf("requested scopes: %v", requestedScopes)
   123  	qt.Assert(t, qt.Equals(mapsKeys(requestedScopes)[0], scope))
   124  }
   125  
   126  type transportFunc func(req *http.Request) (*http.Response, error)
   127  
   128  func (f transportFunc) RoundTrip(req *http.Request) (*http.Response, error) {
   129  	return f(req)
   130  }
   131  
   132  // TODO: replace with maps.Keys once Go adds it
   133  func mapsKeys[M ~map[K]V, K comparable, V any](m M) []K {
   134  	r := make([]K, 0, len(m))
   135  	for k := range m {
   136  		r = append(r, k)
   137  	}
   138  	return r
   139  }