github.com/tompao/docker@v1.9.1/registry/registry.go (about) 1 // Package registry contains client primitives to interact with a remote Docker registry. 2 package registry 3 4 import ( 5 "crypto/tls" 6 "crypto/x509" 7 "errors" 8 "fmt" 9 "io/ioutil" 10 "net" 11 "net/http" 12 "os" 13 "path/filepath" 14 "runtime" 15 "strings" 16 "time" 17 18 "github.com/Sirupsen/logrus" 19 "github.com/docker/distribution/registry/api/errcode" 20 "github.com/docker/distribution/registry/api/v2" 21 "github.com/docker/distribution/registry/client" 22 "github.com/docker/distribution/registry/client/transport" 23 "github.com/docker/docker/autogen/dockerversion" 24 "github.com/docker/docker/pkg/parsers/kernel" 25 "github.com/docker/docker/pkg/tlsconfig" 26 "github.com/docker/docker/pkg/useragent" 27 ) 28 29 var ( 30 // ErrAlreadyExists is an error returned if an image being pushed 31 // already exists on the remote side 32 ErrAlreadyExists = errors.New("Image already exists") 33 errLoginRequired = errors.New("Authentication is required.") 34 ) 35 36 // dockerUserAgent is the User-Agent the Docker client uses to identify itself. 37 // It is populated on init(), comprising version information of different components. 38 var dockerUserAgent string 39 40 func init() { 41 httpVersion := make([]useragent.VersionInfo, 0, 6) 42 httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION}) 43 httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()}) 44 httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT}) 45 if kernelVersion, err := kernel.GetKernelVersion(); err == nil { 46 httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()}) 47 } 48 httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS}) 49 httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH}) 50 51 dockerUserAgent = useragent.AppendVersions("", httpVersion...) 52 53 if runtime.GOOS != "linux" { 54 V2Only = true 55 } 56 } 57 58 func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) { 59 // PreferredServerCipherSuites should have no effect 60 tlsConfig := tlsconfig.ServerDefault 61 62 tlsConfig.InsecureSkipVerify = !isSecure 63 64 if isSecure { 65 hostDir := filepath.Join(CertsDir, cleanPath(hostname)) 66 logrus.Debugf("hostDir: %s", hostDir) 67 if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil { 68 return nil, err 69 } 70 } 71 72 return &tlsConfig, nil 73 } 74 75 func hasFile(files []os.FileInfo, name string) bool { 76 for _, f := range files { 77 if f.Name() == name { 78 return true 79 } 80 } 81 return false 82 } 83 84 // ReadCertsDirectory reads the directory for TLS certificates 85 // including roots and certificate pairs and updates the 86 // provided TLS configuration. 87 func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error { 88 fs, err := ioutil.ReadDir(directory) 89 if err != nil && !os.IsNotExist(err) { 90 return err 91 } 92 93 for _, f := range fs { 94 if strings.HasSuffix(f.Name(), ".crt") { 95 if tlsConfig.RootCAs == nil { 96 // TODO(dmcgowan): Copy system pool 97 tlsConfig.RootCAs = x509.NewCertPool() 98 } 99 logrus.Debugf("crt: %s", filepath.Join(directory, f.Name())) 100 data, err := ioutil.ReadFile(filepath.Join(directory, f.Name())) 101 if err != nil { 102 return err 103 } 104 tlsConfig.RootCAs.AppendCertsFromPEM(data) 105 } 106 if strings.HasSuffix(f.Name(), ".cert") { 107 certName := f.Name() 108 keyName := certName[:len(certName)-5] + ".key" 109 logrus.Debugf("cert: %s", filepath.Join(directory, f.Name())) 110 if !hasFile(fs, keyName) { 111 return fmt.Errorf("Missing key %s for certificate %s", keyName, certName) 112 } 113 cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName)) 114 if err != nil { 115 return err 116 } 117 tlsConfig.Certificates = append(tlsConfig.Certificates, cert) 118 } 119 if strings.HasSuffix(f.Name(), ".key") { 120 keyName := f.Name() 121 certName := keyName[:len(keyName)-4] + ".cert" 122 logrus.Debugf("key: %s", filepath.Join(directory, f.Name())) 123 if !hasFile(fs, certName) { 124 return fmt.Errorf("Missing certificate %s for key %s", certName, keyName) 125 } 126 } 127 } 128 129 return nil 130 } 131 132 // DockerHeaders returns request modifiers that ensure requests have 133 // the User-Agent header set to dockerUserAgent and that metaHeaders 134 // are added. 135 func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier { 136 modifiers := []transport.RequestModifier{ 137 transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}), 138 } 139 if metaHeaders != nil { 140 modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders)) 141 } 142 return modifiers 143 } 144 145 // HTTPClient returns a HTTP client structure which uses the given transport 146 // and contains the necessary headers for redirected requests 147 func HTTPClient(transport http.RoundTripper) *http.Client { 148 return &http.Client{ 149 Transport: transport, 150 CheckRedirect: addRequiredHeadersToRedirectedRequests, 151 } 152 } 153 154 func trustedLocation(req *http.Request) bool { 155 var ( 156 trusteds = []string{"docker.com", "docker.io"} 157 hostname = strings.SplitN(req.Host, ":", 2)[0] 158 ) 159 if req.URL.Scheme != "https" { 160 return false 161 } 162 163 for _, trusted := range trusteds { 164 if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) { 165 return true 166 } 167 } 168 return false 169 } 170 171 // addRequiredHeadersToRedirectedRequests adds the necessary redirection headers 172 // for redirected requests 173 func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { 174 if via != nil && via[0] != nil { 175 if trustedLocation(req) && trustedLocation(via[0]) { 176 req.Header = via[0].Header 177 return nil 178 } 179 for k, v := range via[0].Header { 180 if k != "Authorization" { 181 for _, vv := range v { 182 req.Header.Add(k, vv) 183 } 184 } 185 } 186 } 187 return nil 188 } 189 190 func shouldV2Fallback(err errcode.Error) bool { 191 logrus.Debugf("v2 error: %T %v", err, err) 192 switch err.Code { 193 case v2.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown: 194 return true 195 } 196 return false 197 } 198 199 // ErrNoSupport is an error type used for errors indicating that an operation 200 // is not supported. It encapsulates a more specific error. 201 type ErrNoSupport struct{ Err error } 202 203 func (e ErrNoSupport) Error() string { 204 if e.Err == nil { 205 return "not supported" 206 } 207 return e.Err.Error() 208 } 209 210 // ContinueOnError returns true if we should fallback to the next endpoint 211 // as a result of this error. 212 func ContinueOnError(err error) bool { 213 switch v := err.(type) { 214 case errcode.Errors: 215 return ContinueOnError(v[0]) 216 case ErrNoSupport: 217 return ContinueOnError(v.Err) 218 case errcode.Error: 219 return shouldV2Fallback(v) 220 case *client.UnexpectedHTTPResponseError: 221 return true 222 } 223 // let's be nice and fallback if the error is a completely 224 // unexpected one. 225 // If new errors have to be handled in some way, please 226 // add them to the switch above. 227 return true 228 } 229 230 // NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the 231 // default TLS configuration. 232 func NewTransport(tlsConfig *tls.Config) *http.Transport { 233 if tlsConfig == nil { 234 var cfg = tlsconfig.ServerDefault 235 tlsConfig = &cfg 236 } 237 return &http.Transport{ 238 Proxy: http.ProxyFromEnvironment, 239 Dial: (&net.Dialer{ 240 Timeout: 30 * time.Second, 241 KeepAlive: 30 * time.Second, 242 DualStack: true, 243 }).Dial, 244 TLSHandshakeTimeout: 10 * time.Second, 245 TLSClientConfig: tlsConfig, 246 // TODO(dmcgowan): Call close idle connections when complete and use keep alive 247 DisableKeepAlives: true, 248 } 249 }