2018-02-05 16:05:59 -05:00
package registry // import "github.com/docker/docker/registry"
2013-03-14 20:43:59 -04:00
import (
"io/ioutil"
"net/http"
2016-03-01 02:07:41 -05:00
"net/url"
2013-03-14 20:43:59 -04:00
"strings"
2016-03-01 02:07:41 -05:00
"time"
2014-06-02 20:46:06 -04:00
2016-03-01 02:07:41 -05:00
"github.com/docker/distribution/registry/client/auth"
2016-11-10 18:59:02 -05:00
"github.com/docker/distribution/registry/client/auth/challenge"
2016-03-01 02:07:41 -05:00
"github.com/docker/distribution/registry/client/transport"
2016-09-06 14:18:12 -04:00
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
2018-01-11 14:53:06 -05:00
"github.com/docker/docker/errdefs"
2017-07-19 10:20:13 -04:00
"github.com/pkg/errors"
2017-07-26 17:42:13 -04:00
"github.com/sirupsen/logrus"
2013-03-14 20:43:59 -04:00
)
2016-02-23 18:18:04 -05:00
const (
// AuthClientID is used the ClientID used for the token server
AuthClientID = "docker"
)
2016-03-01 02:07:41 -05:00
// loginV1 tries to register/login to the v1 registry server.
2016-02-23 18:18:04 -05:00
func loginV1 ( authConfig * types . AuthConfig , apiEndpoint APIEndpoint , userAgent string ) ( string , string , error ) {
2017-07-19 10:20:13 -04:00
registryEndpoint := apiEndpoint . ToV1Endpoint ( userAgent , nil )
2016-03-01 02:07:41 -05:00
serverAddress := registryEndpoint . String ( )
2013-09-03 14:45:49 -04:00
2016-03-28 21:22:24 -04:00
logrus . Debugf ( "attempting v1 login to registry endpoint %s" , serverAddress )
2014-12-11 20:55:15 -05:00
2013-09-03 14:45:49 -04:00
if serverAddress == "" {
2017-11-28 23:09:37 -05:00
return "" , "" , errdefs . System ( errors . New ( "server Error: Server Address not set" ) )
2013-09-03 14:45:49 -04:00
}
2016-02-29 20:51:36 -05:00
req , err := http . NewRequest ( "GET" , serverAddress + "users/" , nil )
2016-03-01 02:07:41 -05:00
if err != nil {
2016-02-23 18:18:04 -05:00
return "" , "" , err
2016-03-01 02:07:41 -05:00
}
2016-02-29 20:51:36 -05:00
req . SetBasicAuth ( authConfig . Username , authConfig . Password )
resp , err := registryEndpoint . client . Do ( req )
2013-03-15 17:41:55 -04:00
if err != nil {
2016-03-01 02:07:41 -05:00
// fallback when request could not be completed
2016-02-23 18:18:04 -05:00
return "" , "" , fallbackError {
2016-03-01 02:07:41 -05:00
err : err ,
}
2013-03-15 17:41:55 -04:00
}
2016-02-29 20:51:36 -05:00
defer resp . Body . Close ( )
body , err := ioutil . ReadAll ( resp . Body )
2013-03-14 21:43:02 -04:00
if err != nil {
2017-11-28 23:09:37 -05:00
return "" , "" , errdefs . System ( err )
2013-03-14 20:43:59 -04:00
}
2017-07-19 10:20:13 -04:00
switch resp . StatusCode {
case http . StatusOK :
2016-02-23 18:18:04 -05:00
return "Login Succeeded" , "" , nil
2017-07-19 10:20:13 -04:00
case http . StatusUnauthorized :
2017-11-28 23:09:37 -05:00
return "" , "" , errdefs . Unauthorized ( errors . New ( "Wrong login/password, please try again" ) )
2017-07-19 10:20:13 -04:00
case http . StatusForbidden :
2016-02-29 20:51:36 -05:00
// *TODO: Use registry configuration to determine what this says, if anything?
2017-11-28 23:09:37 -05:00
return "" , "" , errdefs . Forbidden ( errors . Errorf ( "Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it." , serverAddress ) )
2017-07-19 10:20:13 -04:00
case http . StatusInternalServerError :
2016-02-29 20:51:36 -05:00
logrus . Errorf ( "%s returned status code %d. Response Body :\n%s" , req . URL . String ( ) , resp . StatusCode , body )
2017-11-28 23:09:37 -05:00
return "" , "" , errdefs . System ( errors . New ( "Internal Server Error" ) )
2013-03-14 20:43:59 -04:00
}
2017-11-28 23:09:37 -05:00
return "" , "" , errdefs . System ( errors . Errorf ( "Login: %s (Code: %d; Headers: %s)" , body ,
resp . StatusCode , resp . Header ) )
2013-03-14 20:43:59 -04:00
}
2013-09-03 14:45:49 -04:00
2016-03-01 02:07:41 -05:00
type loginCredentialStore struct {
authConfig * types . AuthConfig
}
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
func ( lcs loginCredentialStore ) Basic ( * url . URL ) ( string , string ) {
return lcs . authConfig . Username , lcs . authConfig . Password
}
2014-12-11 20:55:15 -05:00
2016-02-23 18:18:04 -05:00
func ( lcs loginCredentialStore ) RefreshToken ( * url . URL , string ) string {
return lcs . authConfig . IdentityToken
}
func ( lcs loginCredentialStore ) SetRefreshToken ( u * url . URL , service , token string ) {
lcs . authConfig . IdentityToken = token
}
2016-07-13 16:30:24 -04:00
type staticCredentialStore struct {
auth * types . AuthConfig
}
// NewStaticCredentialStore returns a credential store
// which always returns the same credential values.
func NewStaticCredentialStore ( auth * types . AuthConfig ) auth . CredentialStore {
return staticCredentialStore {
auth : auth ,
}
}
func ( scs staticCredentialStore ) Basic ( * url . URL ) ( string , string ) {
if scs . auth == nil {
return "" , ""
}
return scs . auth . Username , scs . auth . Password
}
func ( scs staticCredentialStore ) RefreshToken ( * url . URL , string ) string {
if scs . auth == nil {
return ""
}
return scs . auth . IdentityToken
}
func ( scs staticCredentialStore ) SetRefreshToken ( * url . URL , string , string ) {
}
2016-03-01 02:07:41 -05:00
type fallbackError struct {
err error
}
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
func ( err fallbackError ) Error ( ) string {
return err . err . Error ( )
2014-12-11 20:55:15 -05:00
}
2016-03-01 02:07:41 -05:00
// loginV2 tries to login to the v2 registry server. The given registry
// endpoint will be pinged to get authorization challenges. These challenges
// will be used to authenticate against the registry to validate credentials.
2016-02-23 18:18:04 -05:00
func loginV2 ( authConfig * types . AuthConfig , endpoint APIEndpoint , userAgent string ) ( string , string , error ) {
2016-03-28 21:22:24 -04:00
logrus . Debugf ( "attempting v2 login to registry endpoint %s" , strings . TrimRight ( endpoint . URL . String ( ) , "/" ) + "/v2/" )
2014-12-11 20:55:15 -05:00
2017-10-25 08:39:51 -04:00
modifiers := Headers ( userAgent , nil )
2016-03-01 02:07:41 -05:00
authTransport := transport . NewTransport ( NewTransport ( endpoint . TLSConfig ) , modifiers ... )
2014-12-11 20:55:15 -05:00
2016-02-23 18:18:04 -05:00
credentialAuthConfig := * authConfig
2016-03-01 02:07:41 -05:00
creds := loginCredentialStore {
2016-02-23 18:18:04 -05:00
authConfig : & credentialAuthConfig ,
2014-12-11 20:55:15 -05:00
}
2016-07-13 16:30:24 -04:00
loginClient , foundV2 , err := v2AuthHTTPClient ( endpoint . URL , authTransport , modifiers , creds , nil )
if err != nil {
return "" , "" , err
2014-12-11 20:55:15 -05:00
}
2016-03-01 02:07:41 -05:00
endpointStr := strings . TrimRight ( endpoint . URL . String ( ) , "/" ) + "/v2/"
req , err := http . NewRequest ( "GET" , endpointStr , nil )
2014-12-11 20:55:15 -05:00
if err != nil {
2016-03-01 02:07:41 -05:00
if ! foundV2 {
err = fallbackError { err : err }
}
2016-02-23 18:18:04 -05:00
return "" , "" , err
2014-12-11 20:55:15 -05:00
}
2016-03-01 02:07:41 -05:00
resp , err := loginClient . Do ( req )
2014-12-11 20:55:15 -05:00
if err != nil {
2017-07-19 10:20:13 -04:00
err = translateV2AuthError ( err )
2016-03-01 02:07:41 -05:00
if ! foundV2 {
err = fallbackError { err : err }
}
2017-07-19 10:20:13 -04:00
2016-02-23 18:18:04 -05:00
return "" , "" , err
2014-12-11 20:55:15 -05:00
}
defer resp . Body . Close ( )
2017-07-19 10:20:13 -04:00
if resp . StatusCode == http . StatusOK {
return "Login Succeeded" , credentialAuthConfig . IdentityToken , nil
2014-12-11 20:55:15 -05:00
}
2017-07-19 10:20:13 -04:00
// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
err = errors . Errorf ( "login attempt to %s failed with status: %d %s" , endpointStr , resp . StatusCode , http . StatusText ( resp . StatusCode ) )
if ! foundV2 {
err = fallbackError { err : err }
}
return "" , "" , err
2014-12-11 20:55:15 -05:00
}
2016-07-13 16:30:24 -04:00
func v2AuthHTTPClient ( endpoint * url . URL , authTransport http . RoundTripper , modifiers [ ] transport . RequestModifier , creds auth . CredentialStore , scopes [ ] auth . Scope ) ( * http . Client , bool , error ) {
challengeManager , foundV2 , err := PingV2Registry ( endpoint , authTransport )
if err != nil {
if ! foundV2 {
err = fallbackError { err : err }
}
return nil , foundV2 , err
}
tokenHandlerOptions := auth . TokenHandlerOptions {
Transport : authTransport ,
Credentials : creds ,
OfflineAccess : true ,
ClientID : AuthClientID ,
Scopes : scopes ,
}
tokenHandler := auth . NewTokenHandlerWithOptions ( tokenHandlerOptions )
basicHandler := auth . NewBasicHandler ( creds )
modifiers = append ( modifiers , auth . NewAuthorizer ( challengeManager , tokenHandler , basicHandler ) )
tr := transport . NewTransport ( authTransport , modifiers ... )
return & http . Client {
Transport : tr ,
Timeout : 15 * time . Second ,
} , foundV2 , nil
}
2016-05-30 06:47:49 -04:00
// ConvertToHostname converts a registry url which has http|https prepended
// to just an hostname.
func ConvertToHostname ( url string ) string {
stripped := url
if strings . HasPrefix ( url , "http://" ) {
stripped = strings . TrimPrefix ( url , "http://" )
} else if strings . HasPrefix ( url , "https://" ) {
stripped = strings . TrimPrefix ( url , "https://" )
}
nameParts := strings . SplitN ( stripped , "/" , 2 )
return nameParts [ 0 ]
}
2015-07-21 15:40:36 -04:00
// ResolveAuthConfig matches an auth configuration to a server address or a URL
2015-12-11 21:14:52 -05:00
func ResolveAuthConfig ( authConfigs map [ string ] types . AuthConfig , index * registrytypes . IndexInfo ) types . AuthConfig {
configKey := GetAuthConfigKey ( index )
2014-02-20 17:57:58 -05:00
// First try the happy case
2015-12-11 22:11:20 -05:00
if c , found := authConfigs [ configKey ] ; found || index . Official {
2014-02-20 17:57:58 -05:00
return c
2013-09-03 14:45:49 -04:00
}
2014-02-20 17:57:58 -05:00
// Maybe they have a legacy config file, we will iterate the keys converting
// them to the new format and testing
2015-12-11 22:11:20 -05:00
for registry , ac := range authConfigs {
2016-05-30 06:47:49 -04:00
if configKey == ConvertToHostname ( registry ) {
2015-04-22 08:06:58 -04:00
return ac
2014-02-20 17:57:58 -05:00
}
2013-09-03 14:45:49 -04:00
}
2014-02-20 17:57:58 -05:00
// When all else fails, return an empty auth config
2015-12-11 23:11:42 -05:00
return types . AuthConfig { }
2015-04-01 18:39:37 -04:00
}
2016-03-01 02:07:41 -05:00
// PingResponseError is used when the response from a ping
// was received but invalid.
type PingResponseError struct {
Err error
}
func ( err PingResponseError ) Error ( ) string {
2016-11-18 07:57:50 -05:00
return err . Err . Error ( )
2016-03-01 02:07:41 -05:00
}
// PingV2Registry attempts to ping a v2 registry and on success return a
// challenge manager for the supported authentication types and
// whether v2 was confirmed by the response. If a response is received but
// cannot be interpreted a PingResponseError will be returned.
2016-11-10 18:59:02 -05:00
func PingV2Registry ( endpoint * url . URL , transport http . RoundTripper ) ( challenge . Manager , bool , error ) {
2016-03-01 02:07:41 -05:00
var (
foundV2 = false
v2Version = auth . APIVersion {
Type : "registry" ,
Version : "2.0" ,
}
)
pingClient := & http . Client {
Transport : transport ,
Timeout : 15 * time . Second ,
}
2016-07-13 16:30:24 -04:00
endpointStr := strings . TrimRight ( endpoint . String ( ) , "/" ) + "/v2/"
2016-03-01 02:07:41 -05:00
req , err := http . NewRequest ( "GET" , endpointStr , nil )
if err != nil {
return nil , false , err
}
resp , err := pingClient . Do ( req )
if err != nil {
return nil , false , err
}
defer resp . Body . Close ( )
versions := auth . APIVersions ( resp , DefaultRegistryVersionHeader )
for _ , pingVersion := range versions {
if pingVersion == v2Version {
// The version header indicates we're definitely
// talking to a v2 registry. So don't allow future
// fallbacks to the v1 protocol.
foundV2 = true
break
}
}
2016-11-10 18:59:02 -05:00
challengeManager := challenge . NewSimpleManager ( )
2016-03-01 02:07:41 -05:00
if err := challengeManager . AddResponse ( resp ) ; err != nil {
return nil , foundV2 , PingResponseError {
Err : err ,
}
}
return challengeManager , foundV2 , nil
}