2014-03-11 00:16:58 +00:00
package registry
2013-03-15 00:43:59 +00:00
import (
"fmt"
"io/ioutil"
"net/http"
2016-03-01 07:07:41 +00:00
"net/url"
2013-03-15 00:43:59 +00:00
"strings"
2016-03-01 07:07:41 +00:00
"time"
2014-06-03 00:46:06 +00:00
2015-03-26 22:22:04 +00:00
"github.com/Sirupsen/logrus"
2016-03-01 07:07:41 +00:00
"github.com/docker/distribution/registry/client/auth"
2016-11-10 23:59:02 +00:00
"github.com/docker/distribution/registry/client/auth/challenge"
2016-03-01 07:07:41 +00:00
"github.com/docker/distribution/registry/client/transport"
2016-09-06 18:18:12 +00:00
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
2013-03-15 00:43:59 +00:00
)
2016-02-23 23:18:04 +00:00
const (
// AuthClientID is used the ClientID used for the token server
AuthClientID = "docker"
)
2016-03-01 07:07:41 +00:00
// loginV1 tries to register/login to the v1 registry server.
2016-02-23 23:18:04 +00:00
func loginV1 ( authConfig * types . AuthConfig , apiEndpoint APIEndpoint , userAgent string ) ( string , string , error ) {
2016-03-01 07:07:41 +00:00
registryEndpoint , err := apiEndpoint . ToV1Endpoint ( userAgent , nil )
if err != nil {
2016-02-23 23:18:04 +00:00
return "" , "" , err
2014-12-12 01:55:15 +00:00
}
2016-03-01 07:07:41 +00:00
serverAddress := registryEndpoint . String ( )
2013-09-03 18:45:49 +00:00
2016-03-29 01:22:24 +00:00
logrus . Debugf ( "attempting v1 login to registry endpoint %s" , serverAddress )
2014-12-12 01:55:15 +00:00
2013-09-03 18:45:49 +00:00
if serverAddress == "" {
2016-02-23 23:18:04 +00:00
return "" , "" , fmt . Errorf ( "Server Error: Server Address not set." )
2013-09-03 18:45:49 +00:00
}
2015-07-21 19:40:36 +00:00
loginAgainstOfficialIndex := serverAddress == IndexServer
2013-09-03 18:45:49 +00:00
2016-03-01 01:51:36 +00:00
req , err := http . NewRequest ( "GET" , serverAddress + "users/" , nil )
2016-03-01 07:07:41 +00:00
if err != nil {
2016-02-23 23:18:04 +00:00
return "" , "" , err
2016-03-01 07:07:41 +00:00
}
2016-03-01 01:51:36 +00:00
req . SetBasicAuth ( authConfig . Username , authConfig . Password )
resp , err := registryEndpoint . client . Do ( req )
2013-03-15 21:41:55 +00:00
if err != nil {
2016-03-01 07:07:41 +00:00
// fallback when request could not be completed
2016-02-23 23:18:04 +00:00
return "" , "" , fallbackError {
2016-03-01 07:07:41 +00:00
err : err ,
}
2013-03-15 21:41:55 +00:00
}
2016-03-01 01:51:36 +00:00
defer resp . Body . Close ( )
body , err := ioutil . ReadAll ( resp . Body )
2013-03-15 01:43:02 +00:00
if err != nil {
2016-02-23 23:18:04 +00:00
return "" , "" , err
2013-03-15 00:43:59 +00:00
}
2016-03-01 01:51:36 +00:00
if resp . StatusCode == http . StatusOK {
2016-02-23 23:18:04 +00:00
return "Login Succeeded" , "" , nil
2016-03-01 01:51:36 +00:00
} else if resp . StatusCode == http . StatusUnauthorized {
2013-09-03 18:45:49 +00:00
if loginAgainstOfficialIndex {
2016-02-23 23:18:04 +00:00
return "" , "" , fmt . Errorf ( "Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com" )
2013-09-03 18:45:49 +00:00
}
2016-02-23 23:18:04 +00:00
return "" , "" , fmt . Errorf ( "Wrong login/password, please try again" )
2016-03-01 01:51:36 +00:00
} else if resp . StatusCode == http . StatusForbidden {
if loginAgainstOfficialIndex {
2016-02-23 23:18:04 +00:00
return "" , "" , fmt . Errorf ( "Login: Account is not active. Please check your e-mail for a confirmation link." )
2013-11-07 18:36:02 +00:00
}
2016-03-01 01:51:36 +00:00
// *TODO: Use registry configuration to determine what this says, if anything?
2016-02-23 23:18:04 +00:00
return "" , "" , fmt . Errorf ( "Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it." , serverAddress )
2016-03-01 01:51:36 +00:00
} else if resp . StatusCode == http . StatusInternalServerError { // Issue #14326
logrus . Errorf ( "%s returned status code %d. Response Body :\n%s" , req . URL . String ( ) , resp . StatusCode , body )
2016-02-23 23:18:04 +00:00
return "" , "" , fmt . Errorf ( "Internal Server Error" )
2013-03-15 00:43:59 +00:00
}
2016-02-23 23:18:04 +00:00
return "" , "" , fmt . Errorf ( "Login: %s (Code: %d; Headers: %s)" , body ,
resp . StatusCode , resp . Header )
2013-03-15 00:43:59 +00:00
}
2013-09-03 18:45:49 +00:00
2016-03-01 07:07:41 +00:00
type loginCredentialStore struct {
authConfig * types . AuthConfig
}
2014-12-12 01:55:15 +00:00
2016-03-01 07:07:41 +00:00
func ( lcs loginCredentialStore ) Basic ( * url . URL ) ( string , string ) {
return lcs . authConfig . Username , lcs . authConfig . Password
}
2014-12-12 01:55:15 +00:00
2016-02-23 23:18:04 +00: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 20:30:24 +00: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 07:07:41 +00:00
type fallbackError struct {
err error
}
2014-12-12 01:55:15 +00:00
2016-03-01 07:07:41 +00:00
func ( err fallbackError ) Error ( ) string {
return err . err . Error ( )
2014-12-12 01:55:15 +00:00
}
2016-03-01 07:07:41 +00: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 23:18:04 +00:00
func loginV2 ( authConfig * types . AuthConfig , endpoint APIEndpoint , userAgent string ) ( string , string , error ) {
2016-03-29 01:22:24 +00:00
logrus . Debugf ( "attempting v2 login to registry endpoint %s" , strings . TrimRight ( endpoint . URL . String ( ) , "/" ) + "/v2/" )
2014-12-12 01:55:15 +00:00
2016-03-01 07:07:41 +00:00
modifiers := DockerHeaders ( userAgent , nil )
authTransport := transport . NewTransport ( NewTransport ( endpoint . TLSConfig ) , modifiers ... )
2014-12-12 01:55:15 +00:00
2016-02-23 23:18:04 +00:00
credentialAuthConfig := * authConfig
2016-03-01 07:07:41 +00:00
creds := loginCredentialStore {
2016-02-23 23:18:04 +00:00
authConfig : & credentialAuthConfig ,
2014-12-12 01:55:15 +00:00
}
2016-07-13 20:30:24 +00:00
loginClient , foundV2 , err := v2AuthHTTPClient ( endpoint . URL , authTransport , modifiers , creds , nil )
if err != nil {
return "" , "" , err
2014-12-12 01:55:15 +00:00
}
2016-03-01 07:07:41 +00:00
endpointStr := strings . TrimRight ( endpoint . URL . String ( ) , "/" ) + "/v2/"
req , err := http . NewRequest ( "GET" , endpointStr , nil )
2014-12-12 01:55:15 +00:00
if err != nil {
2016-03-01 07:07:41 +00:00
if ! foundV2 {
err = fallbackError { err : err }
}
2016-02-23 23:18:04 +00:00
return "" , "" , err
2014-12-12 01:55:15 +00:00
}
2016-03-01 07:07:41 +00:00
resp , err := loginClient . Do ( req )
2014-12-12 01:55:15 +00:00
if err != nil {
2016-03-01 07:07:41 +00:00
if ! foundV2 {
err = fallbackError { err : err }
}
2016-02-23 23:18:04 +00:00
return "" , "" , err
2014-12-12 01:55:15 +00:00
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
2016-03-01 07:07:41 +00:00
// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
err := fmt . Errorf ( "login attempt to %s failed with status: %d %s" , endpointStr , resp . StatusCode , http . StatusText ( resp . StatusCode ) )
if ! foundV2 {
err = fallbackError { err : err }
}
2016-02-23 23:18:04 +00:00
return "" , "" , err
2014-12-12 01:55:15 +00:00
}
2016-02-23 23:18:04 +00:00
return "Login Succeeded" , credentialAuthConfig . IdentityToken , nil
2016-03-01 07:07:41 +00:00
2014-12-12 01:55:15 +00:00
}
2016-07-13 20:30:24 +00: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 10:47:49 +00: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 19:40:36 +00:00
// ResolveAuthConfig matches an auth configuration to a server address or a URL
2015-12-12 02:14:52 +00:00
func ResolveAuthConfig ( authConfigs map [ string ] types . AuthConfig , index * registrytypes . IndexInfo ) types . AuthConfig {
configKey := GetAuthConfigKey ( index )
2014-02-20 22:57:58 +00:00
// First try the happy case
2015-12-12 03:11:20 +00:00
if c , found := authConfigs [ configKey ] ; found || index . Official {
2014-02-20 22:57:58 +00:00
return c
2013-09-03 18:45:49 +00:00
}
2014-02-20 22:57:58 +00:00
// Maybe they have a legacy config file, we will iterate the keys converting
// them to the new format and testing
2015-12-12 03:11:20 +00:00
for registry , ac := range authConfigs {
2016-05-30 10:47:49 +00:00
if configKey == ConvertToHostname ( registry ) {
2015-04-22 12:06:58 +00:00
return ac
2014-02-20 22:57:58 +00:00
}
2013-09-03 18:45:49 +00:00
}
2014-02-20 22:57:58 +00:00
// When all else fails, return an empty auth config
2015-12-12 04:11:42 +00:00
return types . AuthConfig { }
2015-04-01 22:39:37 +00:00
}
2016-03-01 07:07:41 +00: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 12:57:50 +00:00
return err . Err . Error ( )
2016-03-01 07:07:41 +00: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 23:59:02 +00:00
func PingV2Registry ( endpoint * url . URL , transport http . RoundTripper ) ( challenge . Manager , bool , error ) {
2016-03-01 07:07:41 +00:00
var (
foundV2 = false
v2Version = auth . APIVersion {
Type : "registry" ,
Version : "2.0" ,
}
)
pingClient := & http . Client {
Transport : transport ,
Timeout : 15 * time . Second ,
}
2016-07-13 20:30:24 +00:00
endpointStr := strings . TrimRight ( endpoint . String ( ) , "/" ) + "/v2/"
2016-03-01 07:07:41 +00: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 23:59:02 +00:00
challengeManager := challenge . NewSimpleManager ( )
2016-03-01 07:07:41 +00:00
if err := challengeManager . AddResponse ( resp ) ; err != nil {
return nil , foundV2 , PingResponseError {
Err : err ,
}
}
return challengeManager , foundV2 , nil
}