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  }