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