dotcloud/docker

View on GitHub
registry/registry.go

Summary

Maintainability
A
2 hrs
Test Coverage
// Package registry contains client primitives to interact with a remote Docker registry.
package registry // import "github.com/docker/docker/registry"

import (
    "context"
    "crypto/tls"
    "net"
    "net/http"
    "os"
    "path/filepath"
    "strings"
    "time"

    "github.com/containerd/log"
    "github.com/docker/distribution/registry/client/transport"
    "github.com/docker/go-connections/tlsconfig"
)

// HostCertsDir returns the config directory for a specific host.
func HostCertsDir(hostname string) string {
    return filepath.Join(CertsDir(), cleanPath(hostname))
}

// newTLSConfig constructs a client TLS configuration based on server defaults
func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
    // PreferredServerCipherSuites should have no effect
    tlsConfig := tlsconfig.ServerDefault()

    tlsConfig.InsecureSkipVerify = !isSecure

    if isSecure && CertsDir() != "" {
        hostDir := HostCertsDir(hostname)
        log.G(context.TODO()).Debugf("hostDir: %s", hostDir)
        if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
            return nil, err
        }
    }

    return tlsConfig, nil
}

func hasFile(files []os.DirEntry, name string) bool {
    for _, f := range files {
        if f.Name() == name {
            return true
        }
    }
    return false
}

// ReadCertsDirectory reads the directory for TLS certificates
// including roots and certificate pairs and updates the
// provided TLS configuration.
func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
    fs, err := os.ReadDir(directory)
    if err != nil && !os.IsNotExist(err) {
        return invalidParam(err)
    }

    for _, f := range fs {
        if strings.HasSuffix(f.Name(), ".crt") {
            if tlsConfig.RootCAs == nil {
                systemPool, err := tlsconfig.SystemCertPool()
                if err != nil {
                    return invalidParamWrapf(err, "unable to get system cert pool")
                }
                tlsConfig.RootCAs = systemPool
            }
            log.G(context.TODO()).Debugf("crt: %s", filepath.Join(directory, f.Name()))
            data, err := os.ReadFile(filepath.Join(directory, f.Name()))
            if err != nil {
                return err
            }
            tlsConfig.RootCAs.AppendCertsFromPEM(data)
        }
        if strings.HasSuffix(f.Name(), ".cert") {
            certName := f.Name()
            keyName := certName[:len(certName)-5] + ".key"
            log.G(context.TODO()).Debugf("cert: %s", filepath.Join(directory, f.Name()))
            if !hasFile(fs, keyName) {
                return invalidParamf("missing key %s for client certificate %s. CA certificates must use the extension .crt", keyName, certName)
            }
            cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
            if err != nil {
                return err
            }
            tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
        }
        if strings.HasSuffix(f.Name(), ".key") {
            keyName := f.Name()
            certName := keyName[:len(keyName)-4] + ".cert"
            log.G(context.TODO()).Debugf("key: %s", filepath.Join(directory, f.Name()))
            if !hasFile(fs, certName) {
                return invalidParamf("missing client certificate %s for key %s", certName, keyName)
            }
        }
    }

    return nil
}

// Headers returns request modifiers with a User-Agent and metaHeaders
func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModifier {
    modifiers := []transport.RequestModifier{}
    if userAgent != "" {
        modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{
            "User-Agent": []string{userAgent},
        }))
    }
    if metaHeaders != nil {
        modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
    }
    return modifiers
}

// newTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
// default TLS configuration.
func newTransport(tlsConfig *tls.Config) *http.Transport {
    if tlsConfig == nil {
        tlsConfig = tlsconfig.ServerDefault()
    }

    direct := &net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
    }

    return &http.Transport{
        Proxy:               http.ProxyFromEnvironment,
        DialContext:         direct.DialContext,
        TLSHandshakeTimeout: 10 * time.Second,
        TLSClientConfig:     tlsConfig,
        // TODO(dmcgowan): Call close idle connections when complete and use keep alive
        DisableKeepAlives: true,
    }
}