11package docker
22
33import (
4+ "bytes"
45 "context"
56 "crypto/tls"
67 "encoding/json"
78 "fmt"
89 "io"
10+ "io/ioutil"
911 "net/http"
1012 "net/url"
1113 "os"
@@ -97,8 +99,7 @@ type dockerClient struct {
9799 // by detectProperties(). Callers can edit tlsClientConfig.InsecureSkipVerify in the meantime.
98100 tlsClientConfig * tls.Config
99101 // The following members are not set by newDockerClient and must be set by callers if needed.
100- username string
101- password string
102+ auth types.DockerAuthConfig
102103 registryToken string
103104 signatureBase signatureStorageBase
104105 scope authScope
@@ -210,10 +211,11 @@ func dockerCertDir(sys *types.SystemContext, hostPort string) (string, error) {
210211// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
211212func newDockerClientFromRef (sys * types.SystemContext , ref dockerReference , write bool , actions string ) (* dockerClient , error ) {
212213 registry := reference .Domain (ref .ref )
213- username , password , err := config .GetAuthentication (sys , registry )
214+ auth , err := config .GetCredentials (sys , registry )
214215 if err != nil {
215216 return nil , errors .Wrapf (err , "error getting username and password" )
216217 }
218+
217219 sigBase , err := configuredSignatureStorageBase (sys , ref , write )
218220 if err != nil {
219221 return nil , err
@@ -223,8 +225,7 @@ func newDockerClientFromRef(sys *types.SystemContext, ref dockerReference, write
223225 if err != nil {
224226 return nil , err
225227 }
226- client .username = username
227- client .password = password
228+ client .auth = auth
228229 if sys != nil {
229230 client .registryToken = sys .DockerBearerRegistryToken
230231 }
@@ -289,8 +290,10 @@ func CheckAuth(ctx context.Context, sys *types.SystemContext, username, password
289290 if err != nil {
290291 return errors .Wrapf (err , "error creating new docker client" )
291292 }
292- client .username = username
293- client .password = password
293+ client .auth = types.DockerAuthConfig {
294+ Username : username ,
295+ Password : password ,
296+ }
294297
295298 resp , err := client .makeRequest (ctx , "GET" , "/v2/" , nil , nil , v2Auth , nil )
296299 if err != nil {
@@ -332,7 +335,7 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
332335 v1Res := & V1Results {}
333336
334337 // Get credentials from authfile for the underlying hostname
335- username , password , err := config .GetAuthentication (sys , registry )
338+ auth , err := config .GetCredentials (sys , registry )
336339 if err != nil {
337340 return nil , errors .Wrapf (err , "error getting username and password" )
338341 }
@@ -350,8 +353,7 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
350353 if err != nil {
351354 return nil , errors .Wrapf (err , "error creating new docker client" )
352355 }
353- client .username = username
354- client .password = password
356+ client .auth = auth
355357 if sys != nil {
356358 client .registryToken = sys .DockerBearerRegistryToken
357359 }
@@ -535,7 +537,7 @@ func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope
535537 schemeNames = append (schemeNames , challenge .Scheme )
536538 switch challenge .Scheme {
537539 case "basic" :
538- req .SetBasicAuth (c .username , c .password )
540+ req .SetBasicAuth (c .auth . Username , c .auth . Password )
539541 return nil
540542 case "bearer" :
541543 registryToken := c .registryToken
@@ -553,10 +555,19 @@ func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope
553555 token = t .(bearerToken )
554556 }
555557 if ! inCache || time .Now ().After (token .expirationTime ) {
556- t , err := c .getBearerToken (req .Context (), challenge , scopes )
558+ var (
559+ t * bearerToken
560+ err error
561+ )
562+ if c .auth .IdentityToken != "" {
563+ t , err = c .getBearerTokenOAuth2 (req .Context (), challenge , scopes )
564+ } else {
565+ t , err = c .getBearerToken (req .Context (), challenge , scopes )
566+ }
557567 if err != nil {
558568 return err
559569 }
570+
560571 token = * t
561572 c .tokenCache .Store (cacheKey , token )
562573 }
@@ -572,48 +583,96 @@ func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope
572583 return nil
573584}
574585
575- func (c * dockerClient ) getBearerToken (ctx context.Context , challenge challenge , scopes []authScope ) (* bearerToken , error ) {
586+ func (c * dockerClient ) getBearerTokenOAuth2 (ctx context.Context , challenge challenge ,
587+ scopes []authScope ) (* bearerToken , error ) {
588+ realm , ok := challenge .Parameters ["realm" ]
589+ if ! ok {
590+ return nil , errors .Errorf ("missing realm in bearer auth challenge" )
591+ }
592+
593+ authReq , err := http .NewRequest (http .MethodPost , realm , nil )
594+ if err != nil {
595+ return nil , err
596+ }
597+
598+ authReq = authReq .WithContext (ctx )
599+
600+ // Make the form data required against the oauth2 authentication
601+ // More details here: https://docs.docker.com/registry/spec/auth/oauth/
602+ params := authReq .URL .Query ()
603+ if service , ok := challenge .Parameters ["service" ]; ok && service != "" {
604+ params .Add ("service" , service )
605+ }
606+ for _ , scope := range scopes {
607+ if scope .remoteName != "" && scope .actions != "" {
608+ params .Add ("scope" , fmt .Sprintf ("repository:%s:%s" , scope .remoteName , scope .actions ))
609+ }
610+ }
611+ params .Add ("grant_type" , "refresh_token" )
612+ params .Add ("refresh_token" , c .auth .IdentityToken )
613+
614+ authReq .Body = ioutil .NopCloser (bytes .NewBufferString (params .Encode ()))
615+ authReq .Header .Add ("Content-Type" , "application/x-www-form-urlencoded" )
616+ logrus .Debugf ("%s %s" , authReq .Method , authReq .URL .String ())
617+ res , err := c .client .Do (authReq )
618+ if err != nil {
619+ return nil , err
620+ }
621+ defer res .Body .Close ()
622+ if err := httpResponseToError (res , "Trying to obtain access token" ); err != nil {
623+ return nil , err
624+ }
625+
626+ tokenBlob , err := iolimits .ReadAtMost (res .Body , iolimits .MaxAuthTokenBodySize )
627+ if err != nil {
628+ return nil , err
629+ }
630+
631+ return newBearerTokenFromJSONBlob (tokenBlob )
632+ }
633+
634+ func (c * dockerClient ) getBearerToken (ctx context.Context , challenge challenge ,
635+ scopes []authScope ) (* bearerToken , error ) {
576636 realm , ok := challenge .Parameters ["realm" ]
577637 if ! ok {
578638 return nil , errors .Errorf ("missing realm in bearer auth challenge" )
579639 }
580640
581- authReq , err := http .NewRequest ("GET" , realm , nil )
641+ authReq , err := http .NewRequest (http . MethodGet , realm , nil )
582642 if err != nil {
583643 return nil , err
584644 }
645+
585646 authReq = authReq .WithContext (ctx )
586- getParams := authReq .URL .Query ()
587- if c .username != "" {
588- getParams .Add ("account" , c .username )
647+ params := authReq .URL .Query ()
648+ if c .auth . Username != "" {
649+ params .Add ("account" , c .auth . Username )
589650 }
651+
590652 if service , ok := challenge .Parameters ["service" ]; ok && service != "" {
591- getParams .Add ("service" , service )
653+ params .Add ("service" , service )
592654 }
655+
593656 for _ , scope := range scopes {
594657 if scope .remoteName != "" && scope .actions != "" {
595- getParams .Add ("scope" , fmt .Sprintf ("repository:%s:%s" , scope .remoteName , scope .actions ))
658+ params .Add ("scope" , fmt .Sprintf ("repository:%s:%s" , scope .remoteName , scope .actions ))
596659 }
597660 }
598- authReq .URL .RawQuery = getParams .Encode ()
599- if c .username != "" && c .password != "" {
600- authReq .SetBasicAuth (c .username , c .password )
661+
662+ authReq .URL .RawQuery = params .Encode ()
663+
664+ if c .auth .Username != "" && c .auth .Password != "" {
665+ authReq .SetBasicAuth (c .auth .Username , c .auth .Password )
601666 }
667+
602668 logrus .Debugf ("%s %s" , authReq .Method , authReq .URL .String ())
603669 res , err := c .client .Do (authReq )
604670 if err != nil {
605671 return nil , err
606672 }
607673 defer res .Body .Close ()
608- switch res .StatusCode {
609- case http .StatusUnauthorized :
610- err := clientLib .HandleErrorResponse (res )
611- logrus .Debugf ("Server response when trying to obtain an access token: \n %q" , err .Error ())
612- return nil , ErrUnauthorizedForCredentials {Err : err }
613- case http .StatusOK :
614- break
615- default :
616- return nil , errors .Errorf ("unexpected http code: %d (%s), URL: %s" , res .StatusCode , http .StatusText (res .StatusCode ), authReq .URL )
674+ if err := httpResponseToError (res , "Requesting bear token" ); err != nil {
675+ return nil , err
617676 }
618677 tokenBlob , err := iolimits .ReadAtMost (res .Body , iolimits .MaxAuthTokenBodySize )
619678 if err != nil {
0 commit comments