Files
moby/registry/registry.go
Sebastiaan van Stijn 5862b926f5 registry: remove deprecated SetCertsDir and unify CertsDir code
This was deprecated in b633c4cc33, which was
in v28, and no longer has any consumer, so we can remove it.

Now that we no longer have to synchronise `CertsDir` with `SetCertsDir`
we can also remove the synchronization (`homedir.GetConfigHome()` does
some additional lookups, but those usually are just looking up env-vars,
and `user.Current()` already has a `sync.Once` or equivalent). Also
unifying the platform-specific code to remove some abstraction and put
the logic in plain sight.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-10 22:47:17 +02:00

156 lines
4.6 KiB
Go

// Package registry contains client primitives to interact with a remote Docker registry.
package registry
import (
"context"
"crypto/tls"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/containerd/log"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/go-connections/tlsconfig"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
// hostCertsDir returns the config directory for a specific host.
func hostCertsDir(hostnameAndPort string) string {
if runtime.GOOS == "windows" {
// Ensure that a directory name is valid; hostnameAndPort may contain
// a colon (:) if a port is included, and Windows does not allow colons
// in directory names.
hostnameAndPort = filepath.FromSlash(strings.ReplaceAll(hostnameAndPort, ":", ""))
}
return filepath.Join(CertsDir(), hostnameAndPort)
}
// newTLSConfig constructs a client TLS configuration based on server defaults
func newTLSConfig(ctx context.Context, hostname string, isSecure bool) (*tls.Config, error) {
// PreferredServerCipherSuites should have no effect
tlsConfig := tlsconfig.ServerDefault()
tlsConfig.InsecureSkipVerify = !isSecure
if isSecure {
hostDir := hostCertsDir(hostname)
log.G(ctx).Debugf("hostDir: %s", hostDir)
if err := loadTLSConfig(ctx, hostDir, tlsConfig); 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 {
return loadTLSConfig(context.TODO(), directory, tlsConfig)
}
// loadTLSConfig reads the directory for TLS certificates including roots and
// certificate pairs, and updates the provided TLS configuration.
func loadTLSConfig(ctx context.Context, directory string, tlsConfig *tls.Config) error {
fs, err := os.ReadDir(directory)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return invalidParam(err)
}
for _, f := range fs {
if ctx.Err() != nil {
return ctx.Err()
}
switch filepath.Ext(f.Name()) {
case ".crt":
if tlsConfig.RootCAs == nil {
systemPool, err := tlsconfig.SystemCertPool()
if err != nil {
return invalidParamWrapf(err, "unable to get system cert pool")
}
tlsConfig.RootCAs = systemPool
}
fileName := filepath.Join(directory, f.Name())
log.G(ctx).Debugf("crt: %s", fileName)
data, err := os.ReadFile(fileName)
if err != nil {
return err
}
tlsConfig.RootCAs.AppendCertsFromPEM(data)
case ".cert":
certName := f.Name()
keyName := certName[:len(certName)-5] + ".key"
log.G(ctx).Debugf("cert: %s", filepath.Join(directory, certName))
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)
case ".key":
keyName := f.Name()
certName := keyName[:len(keyName)-4] + ".cert"
log.G(ctx).Debugf("key: %s", filepath.Join(directory, keyName))
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.RoundTripper {
if tlsConfig == nil {
tlsConfig = tlsconfig.ServerDefault()
}
return otelhttp.NewTransport(
&http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: tlsConfig,
// TODO(dmcgowan): Call close idle connections when complete and use keep alive
DisableKeepAlives: true,
},
)
}