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 }