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