github.com/kobeld/docker@v1.12.0-rc1/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 "strings" 15 "time" 16 17 "github.com/Sirupsen/logrus" 18 "github.com/docker/distribution/registry/client/transport" 19 "github.com/docker/go-connections/sockets" 20 "github.com/docker/go-connections/tlsconfig" 21 ) 22 23 var ( 24 // ErrAlreadyExists is an error returned if an image being pushed 25 // already exists on the remote side 26 ErrAlreadyExists = errors.New("Image already exists") 27 ) 28 29 func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) { 30 // PreferredServerCipherSuites should have no effect 31 tlsConfig := tlsconfig.ServerDefault 32 33 tlsConfig.InsecureSkipVerify = !isSecure 34 35 if isSecure && CertsDir != "" { 36 hostDir := filepath.Join(CertsDir, cleanPath(hostname)) 37 logrus.Debugf("hostDir: %s", hostDir) 38 if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil { 39 return nil, err 40 } 41 } 42 43 return &tlsConfig, nil 44 } 45 46 func hasFile(files []os.FileInfo, name string) bool { 47 for _, f := range files { 48 if f.Name() == name { 49 return true 50 } 51 } 52 return false 53 } 54 55 // ReadCertsDirectory reads the directory for TLS certificates 56 // including roots and certificate pairs and updates the 57 // provided TLS configuration. 58 func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error { 59 fs, err := ioutil.ReadDir(directory) 60 if err != nil && !os.IsNotExist(err) { 61 return err 62 } 63 64 for _, f := range fs { 65 if strings.HasSuffix(f.Name(), ".crt") { 66 if tlsConfig.RootCAs == nil { 67 // TODO(dmcgowan): Copy system pool 68 tlsConfig.RootCAs = x509.NewCertPool() 69 } 70 logrus.Debugf("crt: %s", filepath.Join(directory, f.Name())) 71 data, err := ioutil.ReadFile(filepath.Join(directory, f.Name())) 72 if err != nil { 73 return err 74 } 75 tlsConfig.RootCAs.AppendCertsFromPEM(data) 76 } 77 if strings.HasSuffix(f.Name(), ".cert") { 78 certName := f.Name() 79 keyName := certName[:len(certName)-5] + ".key" 80 logrus.Debugf("cert: %s", filepath.Join(directory, f.Name())) 81 if !hasFile(fs, keyName) { 82 return fmt.Errorf("Missing key %s for client certificate %s. Note that CA certificates should use the extension .crt.", keyName, certName) 83 } 84 cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName)) 85 if err != nil { 86 return err 87 } 88 tlsConfig.Certificates = append(tlsConfig.Certificates, cert) 89 } 90 if strings.HasSuffix(f.Name(), ".key") { 91 keyName := f.Name() 92 certName := keyName[:len(keyName)-4] + ".cert" 93 logrus.Debugf("key: %s", filepath.Join(directory, f.Name())) 94 if !hasFile(fs, certName) { 95 return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName) 96 } 97 } 98 } 99 100 return nil 101 } 102 103 // DockerHeaders returns request modifiers with a User-Agent and metaHeaders 104 func DockerHeaders(userAgent string, metaHeaders http.Header) []transport.RequestModifier { 105 modifiers := []transport.RequestModifier{} 106 if userAgent != "" { 107 modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{ 108 "User-Agent": []string{userAgent}, 109 })) 110 } 111 if metaHeaders != nil { 112 modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders)) 113 } 114 return modifiers 115 } 116 117 // HTTPClient returns an HTTP client structure which uses the given transport 118 // and contains the necessary headers for redirected requests 119 func HTTPClient(transport http.RoundTripper) *http.Client { 120 return &http.Client{ 121 Transport: transport, 122 CheckRedirect: addRequiredHeadersToRedirectedRequests, 123 } 124 } 125 126 func trustedLocation(req *http.Request) bool { 127 var ( 128 trusteds = []string{"docker.com", "docker.io"} 129 hostname = strings.SplitN(req.Host, ":", 2)[0] 130 ) 131 if req.URL.Scheme != "https" { 132 return false 133 } 134 135 for _, trusted := range trusteds { 136 if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) { 137 return true 138 } 139 } 140 return false 141 } 142 143 // addRequiredHeadersToRedirectedRequests adds the necessary redirection headers 144 // for redirected requests 145 func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { 146 if via != nil && via[0] != nil { 147 if trustedLocation(req) && trustedLocation(via[0]) { 148 req.Header = via[0].Header 149 return nil 150 } 151 for k, v := range via[0].Header { 152 if k != "Authorization" { 153 for _, vv := range v { 154 req.Header.Add(k, vv) 155 } 156 } 157 } 158 } 159 return nil 160 } 161 162 // NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the 163 // default TLS configuration. 164 func NewTransport(tlsConfig *tls.Config) *http.Transport { 165 if tlsConfig == nil { 166 var cfg = tlsconfig.ServerDefault 167 tlsConfig = &cfg 168 } 169 170 direct := &net.Dialer{ 171 Timeout: 30 * time.Second, 172 KeepAlive: 30 * time.Second, 173 DualStack: true, 174 } 175 176 base := &http.Transport{ 177 Proxy: http.ProxyFromEnvironment, 178 Dial: direct.Dial, 179 TLSHandshakeTimeout: 10 * time.Second, 180 TLSClientConfig: tlsConfig, 181 // TODO(dmcgowan): Call close idle connections when complete and use keep alive 182 DisableKeepAlives: true, 183 } 184 185 proxyDialer, err := sockets.DialerFromEnvironment(direct) 186 if err == nil { 187 base.Dial = proxyDialer.Dial 188 } 189 return base 190 }