2014-03-10 20:16:58 -04:00
package registry
2013-03-14 20:43:59 -04:00
import (
"fmt"
"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
2015-03-26 18:22:04 -04:00
"github.com/Sirupsen/logrus"
2016-03-01 02:07:41 -05:00
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
2016-01-04 19:05:26 -05:00
"github.com/docker/engine-api/types"
registrytypes "github.com/docker/engine-api/types/registry"
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 ) {
2016-03-01 02:07:41 -05:00
registryEndpoint , err := apiEndpoint . ToV1Endpoint ( userAgent , nil )
if err != nil {
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
serverAddress := registryEndpoint . String ( )
2013-09-03 14:45:49 -04:00
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "attempting v1 login to registry endpoint %s" , registryEndpoint )
2014-12-11 20:55:15 -05:00
2013-09-03 14:45:49 -04:00
if serverAddress == "" {
2016-02-23 18:18:04 -05:00
return "" , "" , fmt . Errorf ( "Server Error: Server Address not set." )
2013-09-03 14:45:49 -04:00
}
2015-07-21 15:40:36 -04:00
loginAgainstOfficialIndex := serverAddress == IndexServer
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 {
2016-02-23 18:18:04 -05:00
return "" , "" , err
2013-03-14 20:43:59 -04:00
}
2016-02-29 20:51:36 -05:00
if resp . StatusCode == http . StatusOK {
2016-02-23 18:18:04 -05:00
return "Login Succeeded" , "" , nil
2016-02-29 20:51:36 -05:00
} else if resp . StatusCode == http . StatusUnauthorized {
2013-09-03 14:45:49 -04:00
if loginAgainstOfficialIndex {
2016-02-23 18:18:04 -05: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 14:45:49 -04:00
}
2016-02-23 18:18:04 -05:00
return "" , "" , fmt . Errorf ( "Wrong login/password, please try again" )
2016-02-29 20:51:36 -05:00
} else if resp . StatusCode == http . StatusForbidden {
if loginAgainstOfficialIndex {
2016-02-23 18:18:04 -05:00
return "" , "" , fmt . Errorf ( "Login: Account is not active. Please check your e-mail for a confirmation link." )
2013-11-07 13:36:02 -05:00
}
2016-02-29 20:51:36 -05:00
// *TODO: Use registry configuration to determine what this says, if anything?
2016-02-23 18:18:04 -05: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-02-29 20:51:36 -05: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 18:18:04 -05:00
return "" , "" , fmt . Errorf ( "Internal Server Error" )
2013-03-14 20:43:59 -04:00
}
2016-02-23 18:18:04 -05:00
return "" , "" , fmt . 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-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-01 02:07:41 -05:00
logrus . Debugf ( "attempting v2 login to registry endpoint %s" , endpoint )
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
modifiers := DockerHeaders ( userAgent , nil )
authTransport := transport . NewTransport ( NewTransport ( endpoint . TLSConfig ) , modifiers ... )
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
challengeManager , foundV2 , err := PingV2Registry ( endpoint , authTransport )
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-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-02-23 18:18:04 -05:00
tokenHandlerOptions := auth . TokenHandlerOptions {
Transport : authTransport ,
Credentials : creds ,
OfflineAccess : true ,
ClientID : AuthClientID ,
}
tokenHandler := auth . NewTokenHandlerWithOptions ( tokenHandlerOptions )
2016-03-01 02:07:41 -05:00
basicHandler := auth . NewBasicHandler ( creds )
modifiers = append ( modifiers , auth . NewAuthorizer ( challengeManager , tokenHandler , basicHandler ) )
tr := transport . NewTransport ( authTransport , modifiers ... )
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
loginClient := & http . Client {
Transport : tr ,
Timeout : 15 * time . Second ,
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 {
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
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
2016-03-01 02:07:41 -05: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 18:18:04 -05:00
return "" , "" , err
2014-12-11 20:55:15 -05:00
}
2016-02-23 18:18:04 -05:00
return "Login Succeeded" , credentialAuthConfig . IdentityToken , nil
2016-03-01 02:07:41 -05:00
2014-12-11 20:55:15 -05:00
}
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
convertToHostname := func ( url string ) string {
stripped := url
if strings . HasPrefix ( url , "http://" ) {
stripped = strings . Replace ( url , "http://" , "" , 1 )
} else if strings . HasPrefix ( url , "https://" ) {
stripped = strings . Replace ( url , "https://" , "" , 1 )
2013-09-03 14:45:49 -04:00
}
2014-02-20 17:57:58 -05:00
nameParts := strings . SplitN ( stripped , "/" , 2 )
return nameParts [ 0 ]
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 {
2014-10-06 21:54:52 -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 {
return err . Error ( )
}
// 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.
func PingV2Registry ( endpoint APIEndpoint , transport http . RoundTripper ) ( auth . ChallengeManager , bool , error ) {
var (
foundV2 = false
v2Version = auth . APIVersion {
Type : "registry" ,
Version : "2.0" ,
}
)
pingClient := & http . Client {
Transport : transport ,
Timeout : 15 * time . Second ,
}
endpointStr := strings . TrimRight ( endpoint . URL . String ( ) , "/" ) + "/v2/"
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
}
}
challengeManager := auth . NewSimpleChallengeManager ( )
if err := challengeManager . AddResponse ( resp ) ; err != nil {
return nil , foundV2 , PingResponseError {
Err : err ,
}
}
return challengeManager , foundV2 , nil
}