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