2014-03-11 00:16:58 +00:00
package registry
import (
"fmt"
"io/ioutil"
"net/http"
2016-03-01 07:07:41 +00:00
"net/url"
2014-03-11 00:16:58 +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"
"github.com/docker/distribution/registry/client/transport"
2016-01-05 00:05:26 +00:00
"github.com/docker/engine-api/types"
registrytypes "github.com/docker/engine-api/types/registry"
2014-03-11 00:16:58 +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 ( )
2014-03-11 00:16:58 +00:00
2015-03-26 22:22:04 +00:00
logrus . Debugf ( "attempting v1 login to registry endpoint %s" , registryEndpoint )
2014-12-12 01:55:15 +00:00
2014-03-11 00:16:58 +00:00
if serverAddress == "" {
2016-02-23 23:18:04 +00:00
return "" , "" , fmt . Errorf ( "Server Error: Server Address not set." )
2014-03-11 00:16:58 +00:00
}
2015-07-21 19:40:36 +00:00
loginAgainstOfficialIndex := serverAddress == IndexServer
2014-03-11 00:16:58 +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 )
2014-03-11 00:16:58 +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 ,
}
2014-03-11 00:16:58 +00:00
}
2016-03-01 01:51:36 +00:00
defer resp . Body . Close ( )
body , err := ioutil . ReadAll ( resp . Body )
2014-03-11 00:16:58 +00:00
if err != nil {
2016-02-23 23:18:04 +00:00
return "" , "" , err
2014-03-11 00:16:58 +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 {
2014-03-11 00:16:58 +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" )
2014-03-11 00:16:58 +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." )
2014-03-11 00:16:58 +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" )
2014-03-11 00:16:58 +00:00
}
2016-02-23 23:18:04 +00:00
return "" , "" , fmt . Errorf ( "Login: %s (Code: %d; Headers: %s)" , body ,
resp . StatusCode , resp . Header )
2014-03-11 00:16:58 +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-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-01 07:07:41 +00:00
logrus . Debugf ( "attempting v2 login to registry endpoint %s" , endpoint )
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-03-01 07:07:41 +00:00
challengeManager , foundV2 , err := PingV2Registry ( endpoint , authTransport )
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-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-02-23 23:18:04 +00:00
tokenHandlerOptions := auth . TokenHandlerOptions {
Transport : authTransport ,
Credentials : creds ,
OfflineAccess : true ,
ClientID : AuthClientID ,
}
tokenHandler := auth . NewTokenHandlerWithOptions ( tokenHandlerOptions )
2016-03-01 07:07:41 +00:00
basicHandler := auth . NewBasicHandler ( creds )
modifiers = append ( modifiers , auth . NewAuthorizer ( challengeManager , tokenHandler , basicHandler ) )
tr := transport . NewTransport ( authTransport , modifiers ... )
2014-12-12 01:55:15 +00:00
2016-03-01 07:07:41 +00:00
loginClient := & http . Client {
Transport : tr ,
Timeout : 15 * time . Second ,
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
}
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-03-11 00:16: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-03-11 00:16:58 +00:00
return c
}
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 )
}
nameParts := strings . SplitN ( stripped , "/" , 2 )
return nameParts [ 0 ]
}
// 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 {
2014-10-07 01:54:52 +00:00
if configKey == convertToHostname ( registry ) {
2015-04-22 12:06:58 +00:00
return ac
2014-03-11 00:16: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 {
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
}