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