github.com/sevki/docker@v1.7.1/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 "net/http/httputil" 12 "os" 13 "path" 14 "path/filepath" 15 "runtime" 16 "strings" 17 "sync" 18 "time" 19 20 "github.com/Sirupsen/logrus" 21 "github.com/docker/docker/autogen/dockerversion" 22 "github.com/docker/docker/pkg/parsers/kernel" 23 "github.com/docker/docker/pkg/timeoutconn" 24 "github.com/docker/docker/pkg/transport" 25 "github.com/docker/docker/pkg/useragent" 26 ) 27 28 var ( 29 ErrAlreadyExists = errors.New("Image already exists") 30 ErrDoesNotExist = errors.New("Image does not exist") 31 errLoginRequired = errors.New("Authentication is required.") 32 ) 33 34 type TimeoutType uint32 35 36 const ( 37 NoTimeout TimeoutType = iota 38 ReceiveTimeout 39 ConnectTimeout 40 ) 41 42 // dockerUserAgent is the User-Agent the Docker client uses to identify itself. 43 // It is populated on init(), comprising version information of different components. 44 var dockerUserAgent string 45 46 func init() { 47 httpVersion := make([]useragent.VersionInfo, 0, 6) 48 httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION}) 49 httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()}) 50 httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT}) 51 if kernelVersion, err := kernel.GetKernelVersion(); err == nil { 52 httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()}) 53 } 54 httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS}) 55 httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH}) 56 57 dockerUserAgent = useragent.AppendVersions("", httpVersion...) 58 } 59 60 type httpsRequestModifier struct { 61 mu sync.Mutex 62 tlsConfig *tls.Config 63 } 64 65 // DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip, 66 // it's because it's so as to match the current behavior in master: we generate the 67 // certpool on every-goddam-request. It's not great, but it allows people to just put 68 // the certs in /etc/docker/certs.d/.../ and let docker "pick it up" immediately. Would 69 // prefer an fsnotify implementation, but that was out of scope of my refactoring. 70 func (m *httpsRequestModifier) ModifyRequest(req *http.Request) error { 71 var ( 72 roots *x509.CertPool 73 certs []tls.Certificate 74 hostDir string 75 ) 76 77 if req.URL.Scheme == "https" { 78 hasFile := func(files []os.FileInfo, name string) bool { 79 for _, f := range files { 80 if f.Name() == name { 81 return true 82 } 83 } 84 return false 85 } 86 87 if runtime.GOOS == "windows" { 88 hostDir = path.Join(os.TempDir(), "/docker/certs.d", req.URL.Host) 89 } else { 90 hostDir = path.Join("/etc/docker/certs.d", req.URL.Host) 91 } 92 logrus.Debugf("hostDir: %s", hostDir) 93 fs, err := ioutil.ReadDir(hostDir) 94 if err != nil && !os.IsNotExist(err) { 95 return nil 96 } 97 98 for _, f := range fs { 99 if strings.HasSuffix(f.Name(), ".crt") { 100 if roots == nil { 101 roots = x509.NewCertPool() 102 } 103 logrus.Debugf("crt: %s", hostDir+"/"+f.Name()) 104 data, err := ioutil.ReadFile(filepath.Join(hostDir, f.Name())) 105 if err != nil { 106 return err 107 } 108 roots.AppendCertsFromPEM(data) 109 } 110 if strings.HasSuffix(f.Name(), ".cert") { 111 certName := f.Name() 112 keyName := certName[:len(certName)-5] + ".key" 113 logrus.Debugf("cert: %s", hostDir+"/"+f.Name()) 114 if !hasFile(fs, keyName) { 115 return fmt.Errorf("Missing key %s for certificate %s", keyName, certName) 116 } 117 cert, err := tls.LoadX509KeyPair(filepath.Join(hostDir, certName), path.Join(hostDir, keyName)) 118 if err != nil { 119 return err 120 } 121 certs = append(certs, cert) 122 } 123 if strings.HasSuffix(f.Name(), ".key") { 124 keyName := f.Name() 125 certName := keyName[:len(keyName)-4] + ".cert" 126 logrus.Debugf("key: %s", hostDir+"/"+f.Name()) 127 if !hasFile(fs, certName) { 128 return fmt.Errorf("Missing certificate %s for key %s", certName, keyName) 129 } 130 } 131 } 132 m.mu.Lock() 133 m.tlsConfig.RootCAs = roots 134 m.tlsConfig.Certificates = certs 135 m.mu.Unlock() 136 } 137 return nil 138 } 139 140 func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper { 141 tlsConfig := &tls.Config{ 142 // Avoid fallback to SSL protocols < TLS1.0 143 MinVersion: tls.VersionTLS10, 144 InsecureSkipVerify: !secure, 145 } 146 147 tr := &http.Transport{ 148 DisableKeepAlives: true, 149 Proxy: http.ProxyFromEnvironment, 150 TLSClientConfig: tlsConfig, 151 } 152 153 switch timeout { 154 case ConnectTimeout: 155 tr.Dial = func(proto string, addr string) (net.Conn, error) { 156 // Set the connect timeout to 30 seconds to allow for slower connection 157 // times... 158 d := net.Dialer{Timeout: 30 * time.Second, DualStack: true} 159 160 conn, err := d.Dial(proto, addr) 161 if err != nil { 162 return nil, err 163 } 164 // Set the recv timeout to 10 seconds 165 conn.SetDeadline(time.Now().Add(10 * time.Second)) 166 return conn, nil 167 } 168 case ReceiveTimeout: 169 tr.Dial = func(proto string, addr string) (net.Conn, error) { 170 d := net.Dialer{DualStack: true} 171 172 conn, err := d.Dial(proto, addr) 173 if err != nil { 174 return nil, err 175 } 176 conn = timeoutconn.New(conn, 1*time.Minute) 177 return conn, nil 178 } 179 } 180 181 if secure { 182 // note: httpsTransport also handles http transport 183 // but for HTTPS, it sets up the certs 184 return transport.NewTransport(tr, &httpsRequestModifier{tlsConfig: tlsConfig}) 185 } 186 187 return tr 188 } 189 190 // DockerHeaders returns request modifiers that ensure requests have 191 // the User-Agent header set to dockerUserAgent and that metaHeaders 192 // are added. 193 func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier { 194 modifiers := []transport.RequestModifier{ 195 transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}), 196 } 197 if metaHeaders != nil { 198 modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders)) 199 } 200 return modifiers 201 } 202 203 type debugTransport struct{ http.RoundTripper } 204 205 func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) { 206 dump, err := httputil.DumpRequestOut(req, false) 207 if err != nil { 208 fmt.Println("could not dump request") 209 } 210 fmt.Println(string(dump)) 211 resp, err := tr.RoundTripper.RoundTrip(req) 212 if err != nil { 213 return nil, err 214 } 215 dump, err = httputil.DumpResponse(resp, false) 216 if err != nil { 217 fmt.Println("could not dump response") 218 } 219 fmt.Println(string(dump)) 220 return resp, err 221 } 222 223 func HTTPClient(transport http.RoundTripper) *http.Client { 224 if transport == nil { 225 transport = NewTransport(ConnectTimeout, true) 226 } 227 228 return &http.Client{ 229 Transport: transport, 230 CheckRedirect: AddRequiredHeadersToRedirectedRequests, 231 } 232 } 233 234 func trustedLocation(req *http.Request) bool { 235 var ( 236 trusteds = []string{"docker.com", "docker.io"} 237 hostname = strings.SplitN(req.Host, ":", 2)[0] 238 ) 239 if req.URL.Scheme != "https" { 240 return false 241 } 242 243 for _, trusted := range trusteds { 244 if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) { 245 return true 246 } 247 } 248 return false 249 } 250 251 func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { 252 if via != nil && via[0] != nil { 253 if trustedLocation(req) && trustedLocation(via[0]) { 254 req.Header = via[0].Header 255 return nil 256 } 257 for k, v := range via[0].Header { 258 if k != "Authorization" { 259 for _, vv := range v { 260 req.Header.Add(k, vv) 261 } 262 } 263 } 264 } 265 return nil 266 }