@@ -35,6 +35,7 @@ import (
3535 "github.com/containerd/containerd/errdefs"
3636 "github.com/containerd/containerd/remotes"
3737 "github.com/containerd/containerd/remotes/docker/auth"
38+ remoteerrors "github.com/containerd/containerd/remotes/errors"
3839 digest "github.com/opencontainers/go-digest"
3940 specs "github.com/opencontainers/image-spec/specs-go"
4041 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -215,6 +216,14 @@ func TestPostBasicAuthTokenResolver(t *testing.T) {
215216 runBasicTest (t , "testname" , withTokenServer (th , creds ))
216217}
217218
219+ func TestBasicAuthResolver (t * testing.T ) {
220+ creds := func (string ) (string , string , error ) {
221+ return "totallyvaliduser" , "totallyvalidpassword" , nil
222+ }
223+
224+ runBasicTest (t , "testname" , withBasicAuthServer (creds ))
225+ }
226+
218227func TestBadTokenResolver (t * testing.T ) {
219228 th := http .HandlerFunc (func (rw http.ResponseWriter , r * http.Request ) {
220229 if r .Method != http .MethodPost {
@@ -236,7 +245,7 @@ func TestBadTokenResolver(t *testing.T) {
236245 defer close ()
237246
238247 resolver := NewResolver (ro )
239- image := fmt .Sprintf ("%s/doesntmatter:sometatg " , base )
248+ image := fmt .Sprintf ("%s/doesntmatter:sometag " , base )
240249
241250 _ , _ , err := resolver .Resolve (ctx , image )
242251 if err == nil {
@@ -247,6 +256,59 @@ func TestBadTokenResolver(t *testing.T) {
247256 }
248257}
249258
259+ func TestMissingBasicAuthResolver (t * testing.T ) {
260+ creds := func (string ) (string , string , error ) {
261+ return "" , "" , nil
262+ }
263+
264+ ctx := context .Background ()
265+ h := newContent (ocispec .MediaTypeImageManifest , []byte ("not anything parse-able" ))
266+
267+ base , ro , close := withBasicAuthServer (creds )(logHandler {t , h })
268+ defer close ()
269+
270+ resolver := NewResolver (ro )
271+ image := fmt .Sprintf ("%s/doesntmatter:sometag" , base )
272+
273+ _ , _ , err := resolver .Resolve (ctx , image )
274+ if err == nil {
275+ t .Fatal ("Expected error getting token with inssufficient scope" )
276+ }
277+ if ! errors .Is (err , ErrInvalidAuthorization ) {
278+ t .Fatal (err )
279+ }
280+ if ! strings .Contains (err .Error (), "no basic auth credentials" ) {
281+ t .Fatalf ("expected \" no basic auth credentials\" message, got %s" , err .Error ())
282+ }
283+ }
284+
285+ func TestWrongBasicAuthResolver (t * testing.T ) {
286+ creds := func (string ) (string , string , error ) {
287+ return "totallyvaliduser" , "definitelythewrongpassword" , nil
288+ }
289+
290+ ctx := context .Background ()
291+ h := newContent (ocispec .MediaTypeImageManifest , []byte ("not anything parse-able" ))
292+
293+ base , ro , close := withBasicAuthServer (creds )(logHandler {t , h })
294+ defer close ()
295+
296+ resolver := NewResolver (ro )
297+ image := fmt .Sprintf ("%s/doesntmatter:sometag" , base )
298+
299+ _ , _ , err := resolver .Resolve (ctx , image )
300+ if err == nil {
301+ t .Fatal ("Expected error getting token with inssufficient scope" )
302+ }
303+ var rerr remoteerrors.ErrUnexpectedStatus
304+ if ! errors .As (err , & rerr ) {
305+ t .Fatal (err )
306+ }
307+ if rerr .StatusCode != 403 {
308+ t .Fatalf ("expected 403 status code, got %d" , rerr .StatusCode )
309+ }
310+ }
311+
250312func TestHostFailureFallbackResolver (t * testing.T ) {
251313 sf := func (h http.Handler ) (string , ResolverOptions , func ()) {
252314 s := httptest .NewServer (h )
@@ -549,6 +611,37 @@ func withTokenServer(th http.Handler, creds func(string) (string, string, error)
549611 }
550612}
551613
614+ func withBasicAuthServer (creds func (string ) (string , string , error )) func (h http.Handler ) (string , ResolverOptions , func ()) {
615+ return func (h http.Handler ) (string , ResolverOptions , func ()) {
616+ // Wrap with basic auth
617+ wrapped := http .HandlerFunc (func (rw http.ResponseWriter , r * http.Request ) {
618+ user , password , ok := r .BasicAuth ()
619+ if ok {
620+ if user != "totallyvaliduser" || password != "totallyvalidpassword" {
621+ rw .WriteHeader (http .StatusForbidden )
622+ rw .Write ([]byte (`{"errors":[{"code":"DENIED"}]}` ))
623+ return
624+ }
625+ } else {
626+ authHeader := "Basic realm=\" testserver\" "
627+ rw .Header ().Set ("WWW-Authenticate" , authHeader )
628+ rw .WriteHeader (http .StatusUnauthorized )
629+ return
630+ }
631+ h .ServeHTTP (rw , r )
632+ })
633+
634+ base , options , close := tlsServer (wrapped )
635+ options .Hosts = ConfigureDefaultRegistries (
636+ WithClient (options .Client ),
637+ WithAuthorizer (NewDockerAuthorizer (
638+ WithAuthCreds (creds ),
639+ )),
640+ )
641+ return base , options , close
642+ }
643+ }
644+
552645func tlsServer (h http.Handler ) (string , ResolverOptions , func ()) {
553646 s := httptest .NewUnstartedServer (h )
554647 s .StartTLS ()
@@ -564,6 +657,7 @@ func tlsServer(h http.Handler) (string, ResolverOptions, func()) {
564657 },
565658 },
566659 }
660+
567661 options := ResolverOptions {
568662 Hosts : ConfigureDefaultRegistries (WithClient (client )),
569663 // Set deprecated field for tests to use for configuration
0 commit comments