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