2016-07-20 09:46:01 +00:00
package docker
import (
2017-08-05 11:40:46 +00:00
"context"
2016-07-20 09:46:01 +00:00
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
2018-02-19 17:53:21 +00:00
"net/url"
"os"
2016-07-20 09:46:01 +00:00
"path/filepath"
2018-02-19 17:53:21 +00:00
"strconv"
2016-07-20 09:46:01 +00:00
"strings"
"time"
2017-03-13 16:33:17 +00:00
"github.com/containers/image/docker/reference"
2017-08-29 14:54:45 +00:00
"github.com/containers/image/pkg/docker/config"
2017-10-10 14:11:06 +00:00
"github.com/containers/image/pkg/tlsclientconfig"
2016-09-17 13:50:35 +00:00
"github.com/containers/image/types"
2017-04-03 07:22:44 +00:00
"github.com/docker/distribution/registry/client"
2016-11-22 19:32:10 +00:00
"github.com/docker/go-connections/tlsconfig"
2017-04-03 07:22:44 +00:00
"github.com/opencontainers/go-digest"
2016-10-17 13:53:40 +00:00
"github.com/pkg/errors"
2017-08-05 11:40:46 +00:00
"github.com/sirupsen/logrus"
2016-07-20 09:46:01 +00:00
)
const (
2018-02-19 17:53:21 +00:00
dockerHostname = "docker.io"
dockerV1Hostname = "index.docker.io"
dockerRegistry = "registry-1.docker.io"
2017-06-06 07:19:04 +00:00
2017-04-03 07:22:44 +00:00
resolvedPingV2URL = "%s://%s/v2/"
resolvedPingV1URL = "%s://%s/v1/_ping"
tagsPath = "/v2/%s/tags/list"
manifestPath = "/v2/%s/manifests/%s"
blobsPath = "/v2/%s/blobs/%s"
blobUploadPath = "/v2/%s/blobs/uploads/"
extensionsSignaturePath = "/extensions/v2/%s/signatures/%s"
2017-02-03 19:15:09 +00:00
minimumTokenLifetimeSeconds = 60
2017-04-03 07:22:44 +00:00
extensionSignatureSchemaVersion = 2 // extensionSignature.Version
extensionSignatureTypeAtomic = "atomic" // extensionSignature.Type
2016-07-20 09:46:01 +00:00
)
2017-08-29 14:54:45 +00:00
var (
// ErrV1NotSupported is returned when we're trying to talk to a
// docker V1 registry.
ErrV1NotSupported = errors . New ( "can't talk to a V1 docker registry" )
// ErrUnauthorizedForCredentials is returned when the status code returned is 401
ErrUnauthorizedForCredentials = errors . New ( "unable to retrieve auth token: invalid username/password" )
2018-02-19 17:53:21 +00:00
systemPerHostCertDirPaths = [ 2 ] string { "/etc/containers/certs.d" , "/etc/docker/certs.d" }
2017-08-29 14:54:45 +00:00
)
2016-11-22 19:32:10 +00:00
2017-04-03 07:22:44 +00:00
// extensionSignature and extensionSignatureList come from github.com/openshift/origin/pkg/dockerregistry/server/signaturedispatcher.go:
// signature represents a Docker image signature.
type extensionSignature struct {
Version int ` json:"schemaVersion" ` // Version specifies the schema version
Name string ` json:"name" ` // Name must be in "sha256:<digest>@signatureName" format
Type string ` json:"type" ` // Type is optional, of not set it will be defaulted to "AtomicImageV1"
Content [ ] byte ` json:"content" ` // Content contains the signature
}
// signatureList represents list of Docker image signatures.
type extensionSignatureList struct {
Signatures [ ] extensionSignature ` json:"signatures" `
}
2017-02-03 19:15:09 +00:00
type bearerToken struct {
2018-02-19 17:53:21 +00:00
Token string ` json:"token" `
AccessToken string ` json:"access_token" `
ExpiresIn int ` json:"expires_in" `
IssuedAt time . Time ` json:"issued_at" `
2017-02-03 19:15:09 +00:00
}
2016-07-20 09:46:01 +00:00
// dockerClient is configuration for dealing with a single Docker registry.
type dockerClient struct {
2017-04-03 07:22:44 +00:00
// The following members are set by newDockerClient and do not change afterwards.
ctx * types . SystemContext
registry string
username string
password string
client * http . Client
signatureBase signatureStorageBase
scope authScope
// The following members are detected registry properties:
// They are set after a successful detectProperties(), and never change afterwards.
scheme string // Empty value also used to indicate detectProperties() has not yet succeeded.
challenges [ ] challenge
supportsSignatures bool
// The following members are private state for setupRequestAuth, both are valid if token != nil.
2017-02-03 19:15:09 +00:00
token * bearerToken
tokenExpiration time . Time
2017-02-01 00:45:59 +00:00
}
type authScope struct {
remoteName string
actions string
2016-07-20 09:46:01 +00:00
}
2018-02-19 17:53:21 +00:00
func newBearerTokenFromJSONBlob ( blob [ ] byte ) ( * bearerToken , error ) {
token := new ( bearerToken )
if err := json . Unmarshal ( blob , & token ) ; err != nil {
return nil , err
}
if token . Token == "" {
token . Token = token . AccessToken
}
if token . ExpiresIn < minimumTokenLifetimeSeconds {
token . ExpiresIn = minimumTokenLifetimeSeconds
logrus . Debugf ( "Increasing token expiration to: %d seconds" , token . ExpiresIn )
}
if token . IssuedAt . IsZero ( ) {
token . IssuedAt = time . Now ( ) . UTC ( )
}
return token , nil
}
2016-11-22 19:32:10 +00:00
// this is cloned from docker/go-connections because upstream docker has changed
// it and make deps here fails otherwise.
// We'll drop this once we upgrade to docker 1.13.x deps.
func serverDefault ( ) * tls . Config {
return & tls . Config {
// Avoid fallback to SSL protocols < TLS1.0
MinVersion : tls . VersionTLS10 ,
PreferServerCipherSuites : true ,
CipherSuites : tlsconfig . DefaultServerAcceptedCiphers ,
}
}
2017-10-10 14:11:06 +00:00
// dockerCertDir returns a path to a directory to be consumed by tlsclientconfig.SetupCertificates() depending on ctx and hostPort.
2018-02-19 17:53:21 +00:00
func dockerCertDir ( ctx * types . SystemContext , hostPort string ) ( string , error ) {
2017-06-06 07:19:04 +00:00
if ctx != nil && ctx . DockerCertPath != "" {
2018-02-19 17:53:21 +00:00
return ctx . DockerCertPath , nil
2017-06-06 07:19:04 +00:00
}
if ctx != nil && ctx . DockerPerHostCertDirPath != "" {
2018-02-19 17:53:21 +00:00
return filepath . Join ( ctx . DockerPerHostCertDirPath , hostPort ) , nil
}
var (
hostCertDir string
fullCertDirPath string
)
for _ , systemPerHostCertDirPath := range systemPerHostCertDirPaths {
if ctx != nil && ctx . RootForImplicitAbsolutePaths != "" {
hostCertDir = filepath . Join ( ctx . RootForImplicitAbsolutePaths , systemPerHostCertDirPath )
} else {
hostCertDir = systemPerHostCertDirPath
}
fullCertDirPath = filepath . Join ( hostCertDir , hostPort )
_ , err := os . Stat ( fullCertDirPath )
if err == nil {
break
}
if os . IsNotExist ( err ) {
continue
}
if os . IsPermission ( err ) {
logrus . Debugf ( "error accessing certs directory due to permissions: %v" , err )
continue
}
if err != nil {
return "" , err
}
2016-11-22 19:32:10 +00:00
}
2018-02-19 17:53:21 +00:00
return fullCertDirPath , nil
2017-06-06 07:19:04 +00:00
}
2017-08-29 14:54:45 +00:00
// newDockerClientFromRef returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
2016-09-17 13:50:35 +00:00
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
2017-08-29 14:54:45 +00:00
func newDockerClientFromRef ( ctx * types . SystemContext , ref dockerReference , write bool , actions string ) ( * dockerClient , error ) {
2017-03-13 16:33:17 +00:00
registry := reference . Domain ( ref . ref )
2017-08-29 14:54:45 +00:00
username , password , err := config . GetAuthentication ( ctx , reference . Domain ( ref . ref ) )
if err != nil {
return nil , errors . Wrapf ( err , "error getting username and password" )
2016-07-20 09:46:01 +00:00
}
2017-08-29 14:54:45 +00:00
sigBase , err := configuredSignatureStorageBase ( ctx , ref , write )
2016-07-20 09:46:01 +00:00
if err != nil {
return nil , err
}
2017-08-29 14:54:45 +00:00
remoteName := reference . Path ( ref . ref )
return newDockerClientWithDetails ( ctx , registry , username , password , actions , sigBase , remoteName )
}
// newDockerClientWithDetails returns a new dockerClient instance for the given parameters
func newDockerClientWithDetails ( ctx * types . SystemContext , registry , username , password , actions string , sigBase signatureStorageBase , remoteName string ) ( * dockerClient , error ) {
hostName := registry
if registry == dockerHostname {
registry = dockerRegistry
}
2017-10-10 14:11:06 +00:00
tr := tlsclientconfig . NewTransport ( )
2017-06-06 07:19:04 +00:00
tr . TLSClientConfig = serverDefault ( )
2017-08-29 14:54:45 +00:00
2017-06-06 07:19:04 +00:00
// It is undefined whether the host[:port] string for dockerHostname should be dockerHostname or dockerRegistry,
// because docker/docker does not read the certs.d subdirectory at all in that case. We use the user-visible
// dockerHostname here, because it is more symmetrical to read the configuration in that case as well, and because
// generally the UI hides the existence of the different dockerRegistry. But note that this behavior is
// undocumented and may change if docker/docker changes.
2018-02-19 17:53:21 +00:00
certDir , err := dockerCertDir ( ctx , hostName )
if err != nil {
return nil , err
}
2017-10-10 14:11:06 +00:00
if err := tlsclientconfig . SetupCertificates ( certDir , tr . TLSClientConfig ) ; err != nil {
2017-06-06 07:19:04 +00:00
return nil , err
2016-07-20 09:46:01 +00:00
}
2017-08-29 14:54:45 +00:00
2017-06-06 07:19:04 +00:00
if ctx != nil && ctx . DockerInsecureSkipTLSVerify {
tr . TLSClientConfig . InsecureSkipVerify = true
2016-07-20 09:46:01 +00:00
}
2016-09-17 13:50:35 +00:00
2016-07-20 09:46:01 +00:00
return & dockerClient {
2016-09-17 13:50:35 +00:00
ctx : ctx ,
registry : registry ,
username : username ,
password : password ,
2017-08-29 14:54:45 +00:00
client : & http . Client { Transport : tr } ,
2016-09-17 13:50:35 +00:00
signatureBase : sigBase ,
2017-02-01 00:45:59 +00:00
scope : authScope {
actions : actions ,
2017-08-29 14:54:45 +00:00
remoteName : remoteName ,
2017-02-01 00:45:59 +00:00
} ,
2016-07-20 09:46:01 +00:00
} , nil
}
2017-08-29 14:54:45 +00:00
// CheckAuth validates the credentials by attempting to log into the registry
// returns an error if an error occcured while making the http request or the status code received was 401
func CheckAuth ( ctx context . Context , sCtx * types . SystemContext , username , password , registry string ) error {
newLoginClient , err := newDockerClientWithDetails ( sCtx , registry , username , password , "" , nil , "" )
if err != nil {
return errors . Wrapf ( err , "error creating new docker client" )
}
resp , err := newLoginClient . makeRequest ( ctx , "GET" , "/v2/" , nil , nil )
if err != nil {
return err
}
defer resp . Body . Close ( )
switch resp . StatusCode {
case http . StatusOK :
return nil
case http . StatusUnauthorized :
return ErrUnauthorizedForCredentials
default :
return errors . Errorf ( "error occured with status code %q" , resp . StatusCode )
}
}
2018-02-19 17:53:21 +00:00
// SearchResult holds the information of each matching image
// It matches the output returned by the v1 endpoint
type SearchResult struct {
Name string ` json:"name" `
Description string ` json:"description" `
// StarCount states the number of stars the image has
StarCount int ` json:"star_count" `
IsTrusted bool ` json:"is_trusted" `
// IsAutomated states whether the image is an automated build
IsAutomated bool ` json:"is_automated" `
// IsOfficial states whether the image is an official build
IsOfficial bool ` json:"is_official" `
}
// SearchRegistry queries a registry for images that contain "image" in their name
// The limit is the max number of results desired
// Note: The limit value doesn't work with all registries
// for example registry.access.redhat.com returns all the results without limiting it to the limit value
func SearchRegistry ( ctx context . Context , sCtx * types . SystemContext , registry , image string , limit int ) ( [ ] SearchResult , error ) {
type V2Results struct {
// Repositories holds the results returned by the /v2/_catalog endpoint
Repositories [ ] string ` json:"repositories" `
}
type V1Results struct {
// Results holds the results returned by the /v1/search endpoint
Results [ ] SearchResult ` json:"results" `
}
v2Res := & V2Results { }
v1Res := & V1Results { }
// The /v2/_catalog endpoint has been disabled for docker.io therefore the call made to that endpoint will fail
// So using the v1 hostname for docker.io for simplicity of implementation and the fact that it returns search results
if registry == dockerHostname {
registry = dockerV1Hostname
}
client , err := newDockerClientWithDetails ( sCtx , registry , "" , "" , "" , nil , "" )
if err != nil {
return nil , errors . Wrapf ( err , "error creating new docker client" )
}
logrus . Debugf ( "trying to talk to v2 search endpoint\n" )
resp , err := client . makeRequest ( ctx , "GET" , "/v2/_catalog" , nil , nil )
if err != nil {
logrus . Debugf ( "error getting search results from v2 endpoint %q: %v" , registry , err )
} else {
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
logrus . Debugf ( "error getting search results from v2 endpoint %q, status code %q" , registry , resp . StatusCode )
} else {
if err := json . NewDecoder ( resp . Body ) . Decode ( v2Res ) ; err != nil {
return nil , err
}
searchRes := [ ] SearchResult { }
for _ , repo := range v2Res . Repositories {
if strings . Contains ( repo , image ) {
res := SearchResult {
Name : repo ,
}
searchRes = append ( searchRes , res )
}
}
return searchRes , nil
}
}
// set up the query values for the v1 endpoint
u := url . URL {
Path : "/v1/search" ,
}
q := u . Query ( )
q . Set ( "q" , image )
q . Set ( "n" , strconv . Itoa ( limit ) )
u . RawQuery = q . Encode ( )
logrus . Debugf ( "trying to talk to v1 search endpoint\n" )
resp , err = client . makeRequest ( ctx , "GET" , u . String ( ) , nil , nil )
if err != nil {
logrus . Debugf ( "error getting search results from v1 endpoint %q: %v" , registry , err )
} else {
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
logrus . Debugf ( "error getting search results from v1 endpoint %q, status code %q" , registry , resp . StatusCode )
} else {
if err := json . NewDecoder ( resp . Body ) . Decode ( v1Res ) ; err != nil {
return nil , err
}
return v1Res . Results , nil
}
}
return nil , errors . Wrapf ( err , "couldn't search registry %q" , registry )
}
2016-07-20 09:46:01 +00:00
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
2017-04-03 07:22:44 +00:00
// The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/.
2017-08-05 11:40:46 +00:00
func ( c * dockerClient ) makeRequest ( ctx context . Context , method , path string , headers map [ string ] [ ] string , stream io . Reader ) ( * http . Response , error ) {
if err := c . detectProperties ( ctx ) ; err != nil {
2017-04-03 07:22:44 +00:00
return nil , err
2016-07-20 09:46:01 +00:00
}
2017-04-03 07:22:44 +00:00
url := fmt . Sprintf ( "%s://%s%s" , c . scheme , c . registry , path )
2017-08-05 11:40:46 +00:00
return c . makeRequestToResolvedURL ( ctx , method , url , headers , stream , - 1 , true )
2016-07-20 09:46:01 +00:00
}
// makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
2016-09-17 13:50:35 +00:00
// streamLen, if not -1, specifies the length of the data expected on stream.
2016-07-20 09:46:01 +00:00
// makeRequest should generally be preferred.
2016-11-22 19:32:10 +00:00
// TODO(runcom): too many arguments here, use a struct
2017-08-05 11:40:46 +00:00
func ( c * dockerClient ) makeRequestToResolvedURL ( ctx context . Context , method , url string , headers map [ string ] [ ] string , stream io . Reader , streamLen int64 , sendAuth bool ) ( * http . Response , error ) {
2016-07-20 09:46:01 +00:00
req , err := http . NewRequest ( method , url , stream )
if err != nil {
return nil , err
}
2017-08-05 11:40:46 +00:00
req = req . WithContext ( ctx )
2016-09-17 13:50:35 +00:00
if streamLen != - 1 { // Do not blindly overwrite if streamLen == -1, http.NewRequest above can figure out the length of bytes.Reader and similar objects without us having to compute it.
req . ContentLength = streamLen
}
2016-07-20 09:46:01 +00:00
req . Header . Set ( "Docker-Distribution-API-Version" , "registry/2.0" )
for n , h := range headers {
for _ , hh := range h {
req . Header . Add ( n , hh )
}
}
2016-11-22 19:32:10 +00:00
if c . ctx != nil && c . ctx . DockerRegistryUserAgent != "" {
req . Header . Add ( "User-Agent" , c . ctx . DockerRegistryUserAgent )
}
2017-02-01 00:45:59 +00:00
if sendAuth {
2016-07-20 09:46:01 +00:00
if err := c . setupRequestAuth ( req ) ; err != nil {
return nil , err
}
}
logrus . Debugf ( "%s %s" , method , url )
res , err := c . client . Do ( req )
if err != nil {
return nil , err
}
return res , nil
}
2017-02-01 00:45:59 +00:00
// we're using the challenges from the /v2/ ping response and not the one from the destination
// URL in this request because:
//
// 1) docker does that as well
// 2) gcr.io is sending 401 without a WWW-Authenticate header in the real request
//
// debugging: https://github.com/containers/image/pull/211#issuecomment-273426236 and follows up
2016-07-20 09:46:01 +00:00
func ( c * dockerClient ) setupRequestAuth ( req * http . Request ) error {
2017-02-01 00:45:59 +00:00
if len ( c . challenges ) == 0 {
return nil
2016-07-20 09:46:01 +00:00
}
2017-07-20 20:31:51 +00:00
schemeNames := make ( [ ] string , 0 , len ( c . challenges ) )
for _ , challenge := range c . challenges {
schemeNames = append ( schemeNames , challenge . Scheme )
switch challenge . Scheme {
case "basic" :
req . SetBasicAuth ( c . username , c . password )
return nil
case "bearer" :
if c . token == nil || time . Now ( ) . After ( c . tokenExpiration ) {
realm , ok := challenge . Parameters [ "realm" ]
if ! ok {
return errors . Errorf ( "missing realm in bearer auth challenge" )
}
service , _ := challenge . Parameters [ "service" ] // Will be "" if not present
2017-08-29 14:54:45 +00:00
var scope string
if c . scope . remoteName != "" && c . scope . actions != "" {
scope = fmt . Sprintf ( "repository:%s:%s" , c . scope . remoteName , c . scope . actions )
}
2017-08-05 11:40:46 +00:00
token , err := c . getBearerToken ( req . Context ( ) , realm , service , scope )
2017-07-20 20:31:51 +00:00
if err != nil {
return err
}
c . token = token
c . tokenExpiration = token . IssuedAt . Add ( time . Duration ( token . ExpiresIn ) * time . Second )
2017-02-03 19:15:09 +00:00
}
2017-07-20 20:31:51 +00:00
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , c . token . Token ) )
return nil
default :
logrus . Debugf ( "no handler for %s authentication" , challenge . Scheme )
2016-07-20 09:46:01 +00:00
}
}
2017-07-20 20:31:51 +00:00
logrus . Infof ( "None of the challenges sent by server (%s) are supported, trying an unauthenticated request anyway" , strings . Join ( schemeNames , ", " ) )
return nil
2016-07-20 09:46:01 +00:00
}
2017-08-05 11:40:46 +00:00
func ( c * dockerClient ) getBearerToken ( ctx context . Context , realm , service , scope string ) ( * bearerToken , error ) {
2016-07-20 09:46:01 +00:00
authReq , err := http . NewRequest ( "GET" , realm , nil )
if err != nil {
2017-02-03 19:15:09 +00:00
return nil , err
2016-07-20 09:46:01 +00:00
}
2017-08-05 11:40:46 +00:00
authReq = authReq . WithContext ( ctx )
2016-07-20 09:46:01 +00:00
getParams := authReq . URL . Query ( )
2016-09-17 13:50:35 +00:00
if service != "" {
getParams . Add ( "service" , service )
}
2016-07-20 09:46:01 +00:00
if scope != "" {
getParams . Add ( "scope" , scope )
}
authReq . URL . RawQuery = getParams . Encode ( )
if c . username != "" && c . password != "" {
authReq . SetBasicAuth ( c . username , c . password )
}
2017-10-10 14:11:06 +00:00
tr := tlsclientconfig . NewTransport ( )
2016-11-22 19:32:10 +00:00
// TODO(runcom): insecure for now to contact the external token service
tr . TLSClientConfig = & tls . Config { InsecureSkipVerify : true }
2016-07-20 09:46:01 +00:00
client := & http . Client { Transport : tr }
res , err := client . Do ( authReq )
if err != nil {
2017-02-03 19:15:09 +00:00
return nil , err
2016-07-20 09:46:01 +00:00
}
defer res . Body . Close ( )
switch res . StatusCode {
case http . StatusUnauthorized :
2017-08-29 14:54:45 +00:00
return nil , ErrUnauthorizedForCredentials
2016-07-20 09:46:01 +00:00
case http . StatusOK :
break
default :
2017-02-03 19:15:09 +00:00
return nil , errors . Errorf ( "unexpected http code: %d, URL: %s" , res . StatusCode , authReq . URL )
2016-07-20 09:46:01 +00:00
}
tokenBlob , err := ioutil . ReadAll ( res . Body )
if err != nil {
2017-02-03 19:15:09 +00:00
return nil , err
}
2018-02-19 17:53:21 +00:00
return newBearerTokenFromJSONBlob ( tokenBlob )
2016-07-20 09:46:01 +00:00
}
2017-04-03 07:22:44 +00:00
// detectProperties detects various properties of the registry.
// See the dockerClient documentation for members which are affected by this.
2017-08-05 11:40:46 +00:00
func ( c * dockerClient ) detectProperties ( ctx context . Context ) error {
2017-04-03 07:22:44 +00:00
if c . scheme != "" {
return nil
}
2017-02-01 00:45:59 +00:00
ping := func ( scheme string ) error {
2017-04-03 07:22:44 +00:00
url := fmt . Sprintf ( resolvedPingV2URL , scheme , c . registry )
2017-08-05 11:40:46 +00:00
resp , err := c . makeRequestToResolvedURL ( ctx , "GET" , url , nil , nil , - 1 , true )
2016-07-20 09:46:01 +00:00
logrus . Debugf ( "Ping %s err %#v" , url , err )
if err != nil {
2017-02-01 00:45:59 +00:00
return err
2016-07-20 09:46:01 +00:00
}
defer resp . Body . Close ( )
2017-04-03 07:22:44 +00:00
logrus . Debugf ( "Ping %s status %d" , url , resp . StatusCode )
2016-07-20 09:46:01 +00:00
if resp . StatusCode != http . StatusOK && resp . StatusCode != http . StatusUnauthorized {
2017-02-01 00:45:59 +00:00
return errors . Errorf ( "error pinging repository, response code %d" , resp . StatusCode )
2016-07-20 09:46:01 +00:00
}
2017-02-01 00:45:59 +00:00
c . challenges = parseAuthHeader ( resp . Header )
c . scheme = scheme
2017-04-03 07:22:44 +00:00
c . supportsSignatures = resp . Header . Get ( "X-Registry-Supports-Signatures" ) == "1"
2017-02-01 00:45:59 +00:00
return nil
2016-07-20 09:46:01 +00:00
}
2017-02-01 00:45:59 +00:00
err := ping ( "https" )
2016-11-22 19:32:10 +00:00
if err != nil && c . ctx != nil && c . ctx . DockerInsecureSkipTLSVerify {
2017-02-01 00:45:59 +00:00
err = ping ( "http" )
2016-07-20 09:46:01 +00:00
}
2016-11-22 19:32:10 +00:00
if err != nil {
2016-10-17 13:53:40 +00:00
err = errors . Wrap ( err , "pinging docker registry returned" )
2016-11-22 19:32:10 +00:00
if c . ctx != nil && c . ctx . DockerDisableV1Ping {
2017-02-01 00:45:59 +00:00
return err
2016-11-22 19:32:10 +00:00
}
// best effort to understand if we're talking to a V1 registry
pingV1 := func ( scheme string ) bool {
2017-04-03 07:22:44 +00:00
url := fmt . Sprintf ( resolvedPingV1URL , scheme , c . registry )
2017-08-05 11:40:46 +00:00
resp , err := c . makeRequestToResolvedURL ( ctx , "GET" , url , nil , nil , - 1 , true )
2016-11-22 19:32:10 +00:00
logrus . Debugf ( "Ping %s err %#v" , url , err )
if err != nil {
return false
}
defer resp . Body . Close ( )
2017-04-03 07:22:44 +00:00
logrus . Debugf ( "Ping %s status %d" , url , resp . StatusCode )
2016-11-22 19:32:10 +00:00
if resp . StatusCode != http . StatusOK && resp . StatusCode != http . StatusUnauthorized {
return false
}
return true
}
isV1 := pingV1 ( "https" )
if ! isV1 && c . ctx != nil && c . ctx . DockerInsecureSkipTLSVerify {
isV1 = pingV1 ( "http" )
}
if isV1 {
err = ErrV1NotSupported
}
}
2017-02-01 00:45:59 +00:00
return err
2016-07-20 09:46:01 +00:00
}
2017-04-03 07:22:44 +00:00
// getExtensionsSignatures returns signatures from the X-Registry-Supports-Signatures API extension,
// using the original data structures.
2017-08-05 11:40:46 +00:00
func ( c * dockerClient ) getExtensionsSignatures ( ctx context . Context , ref dockerReference , manifestDigest digest . Digest ) ( * extensionSignatureList , error ) {
2017-04-03 07:22:44 +00:00
path := fmt . Sprintf ( extensionsSignaturePath , reference . Path ( ref . ref ) , manifestDigest )
2017-08-05 11:40:46 +00:00
res , err := c . makeRequest ( ctx , "GET" , path , nil , nil )
2017-04-03 07:22:44 +00:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
if res . StatusCode != http . StatusOK {
return nil , client . HandleErrorResponse ( res )
}
body , err := ioutil . ReadAll ( res . Body )
if err != nil {
return nil , err
}
var parsedBody extensionSignatureList
if err := json . Unmarshal ( body , & parsedBody ) ; err != nil {
return nil , errors . Wrapf ( err , "Error decoding signature list" )
}
return & parsedBody , nil
}