Skip to content

Commit

Permalink
Merge pull request moby#47380 from dmcgowan/registry-http-fallback
Browse files Browse the repository at this point in the history
Registry host configuration cleanup
  • Loading branch information
vvoland authored Oct 30, 2024
2 parents 3e96728 + 2aaae08 commit dc22579
Show file tree
Hide file tree
Showing 9 changed files with 1,026 additions and 78 deletions.
38 changes: 5 additions & 33 deletions daemon/containerd/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package containerd

import (
"context"
"crypto/tls"
"errors"
"net/http"

"github.com/containerd/containerd/remotes"
Expand Down Expand Up @@ -33,25 +31,20 @@ func (i *ImageService) newResolverFromAuthConfig(ctx context.Context, authConfig
}

func hostsWrapper(hostsFn docker.RegistryHosts, optAuthConfig *registrytypes.AuthConfig, ref reference.Named, regService registryResolver) docker.RegistryHosts {
var authorizer docker.Authorizer
if optAuthConfig != nil {
authorizer = authorizerFromAuthConfig(*optAuthConfig, ref)
if optAuthConfig == nil {
return hostsFn
}

authorizer := authorizerFromAuthConfig(*optAuthConfig, ref)

return func(n string) ([]docker.RegistryHost, error) {
hosts, err := hostsFn(n)
if err != nil {
return nil, err
}

for i := range hosts {
if hosts[i].Authorizer == nil {
hosts[i].Authorizer = authorizer
isInsecure := regService.IsInsecureRegistry(hosts[i].Host)
if hosts[i].Client.Transport != nil && isInsecure {
hosts[i].Client.Transport = httpFallback{super: hosts[i].Client.Transport}
}
}
hosts[i].Authorizer = authorizer
}
return hosts, nil
}
Expand Down Expand Up @@ -111,24 +104,3 @@ func (a *bearerAuthorizer) AddResponses(context.Context, []*http.Response) error
// Return not implemented to prevent retry of the request when bearer did not succeed
return cerrdefs.ErrNotImplemented
}

type httpFallback struct {
super http.RoundTripper
}

func (f httpFallback) RoundTrip(r *http.Request) (*http.Response, error) {
resp, err := f.super.RoundTrip(r)
var tlsErr tls.RecordHeaderError
if errors.As(err, &tlsErr) && string(tlsErr.RecordHeader[:]) == "HTTP/" {
// server gave HTTP response to HTTPS client
plainHttpUrl := *r.URL
plainHttpUrl.Scheme = "http"

plainHttpRequest := *r
plainHttpRequest.URL = &plainHttpUrl

return http.DefaultTransport.RoundTrip(&plainHttpRequest)
}

return resp, err
}
45 changes: 0 additions & 45 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
"github.com/containerd/containerd"
"github.com/containerd/containerd/defaults"
"github.com/containerd/containerd/pkg/dialer"
"github.com/containerd/containerd/remotes/docker"
"github.com/containerd/log"
"github.com/distribution/reference"
dist "github.com/docker/distribution"
Expand Down Expand Up @@ -75,8 +74,6 @@ import (
"github.com/docker/docker/registry"
volumesservice "github.com/docker/docker/volume/service"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/moby/buildkit/util/resolver"
resolverconfig "github.com/moby/buildkit/util/resolver/config"
"github.com/moby/buildkit/util/tracing"
"github.com/moby/locker"
"github.com/moby/sys/userns"
Expand Down Expand Up @@ -207,48 +204,6 @@ func (daemon *Daemon) UsesSnapshotter() bool {
return daemon.usesSnapshotter
}

// RegistryHosts returns the registry hosts configuration for the host component
// of a distribution image reference.
func (daemon *Daemon) RegistryHosts(host string) ([]docker.RegistryHost, error) {
m := map[string]resolverconfig.RegistryConfig{
"docker.io": {Mirrors: daemon.registryService.ServiceConfig().Mirrors},
}
conf := daemon.registryService.ServiceConfig().IndexConfigs
for k, v := range conf {
c := m[k]
if !v.Secure {
t := true
c.PlainHTTP = &t
c.Insecure = &t
}
m[k] = c
}
if c, ok := m[host]; !ok && daemon.registryService.IsInsecureRegistry(host) {
t := true
c.PlainHTTP = &t
c.Insecure = &t
m[host] = c
}

for k, v := range m {
v.TLSConfigDir = []string{registry.HostCertsDir(k)}
m[k] = v
}

certsDir := registry.CertsDir()
if fis, err := os.ReadDir(certsDir); err == nil {
for _, fi := range fis {
if _, ok := m[fi.Name()]; !ok {
m[fi.Name()] = resolverconfig.RegistryConfig{
TLSConfigDir: []string{filepath.Join(certsDir, fi.Name())},
}
}
}
}

return resolver.NewRegistryConfig(m)(host)
}

// layerAccessor may be implemented by ImageService
type layerAccessor interface {
GetLayerByID(cid string) (layer.RWLayer, error)
Expand Down
174 changes: 174 additions & 0 deletions daemon/hosts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package daemon // import "github.com/docker/docker/daemon"

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"strings"

"github.com/containerd/containerd/remotes/docker"
hostconfig "github.com/containerd/containerd/remotes/docker/config"
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
)

const (
defaultPath = "/v2"
)

// RegistryHosts returns the registry hosts configuration for the host component
// of a distribution image reference.
func (daemon *Daemon) RegistryHosts(host string) ([]docker.RegistryHost, error) {
hosts, err := hostconfig.ConfigureHosts(context.Background(), hostconfig.HostOptions{
// TODO: Also check containerd path when updating containerd to use multiple host directories
HostDir: hostconfig.HostDirFromRoot(registry.CertsDir()),
})(host)
if err == nil {
// Merge in legacy configuration if provided
if cfg := daemon.Config(); len(cfg.Mirrors) > 0 || len(cfg.InsecureRegistries) > 0 {
hosts, err = daemon.mergeLegacyConfig(host, hosts)
}
}

return hosts, err
}

func (daemon *Daemon) mergeLegacyConfig(host string, hosts []docker.RegistryHost) ([]docker.RegistryHost, error) {
// If no hosts provided, nothing to do.
// If multiple hosts provided, then a mirror configuration is already provided and
// should not overwrite with legacy config.
if len(hosts) == 0 || len(hosts) > 1 {
return hosts, nil
}
sc := daemon.registryService.ServiceConfig()
if host == "docker.io" && len(sc.Mirrors) > 0 {
hosts = mirrorsToRegistryHosts(sc.Mirrors, hosts[0])
}
hostDir := hostconfig.HostDirFromRoot(registry.CertsDir())
for i := range hosts {
t, ok := hosts[i].Client.Transport.(*http.Transport)
if !ok {
continue
}
if t.TLSClientConfig == nil {
certsDir, err := hostDir(host)
if err != nil && !cerrdefs.IsNotFound(err) {
return nil, err
} else if err == nil {
c, err := loadTLSConfig(certsDir)
if err != nil {
return nil, err
}
t.TLSClientConfig = c
}
}
if daemon.registryService.IsInsecureRegistry(hosts[i].Host) {
if t.TLSClientConfig == nil {
t.TLSClientConfig = &tls.Config{} //nolint: gosec // G402: TLS MinVersion too low.
}
t.TLSClientConfig.InsecureSkipVerify = true

hosts[i].Client.Transport = docker.NewHTTPFallback(hosts[i].Client.Transport)
}
}
return hosts, nil
}

func mirrorsToRegistryHosts(mirrors []string, dHost docker.RegistryHost) []docker.RegistryHost {
var mirrorHosts []docker.RegistryHost
for _, mirror := range mirrors {
h := dHost
h.Capabilities = docker.HostCapabilityPull | docker.HostCapabilityResolve

u, err := url.Parse(mirror)
if err != nil || u.Host == "" {
u, err = url.Parse(fmt.Sprintf("dummy://%s", mirror))
}
if err == nil && u.Host != "" {
h.Host = u.Host
h.Path = strings.TrimSuffix(u.Path, "/")

// For compatibility with legacy mirrors, ensure ends with /v2
// NOTE: Use newer configuration to completely override the path
if !strings.HasSuffix(h.Path, defaultPath) {
h.Path = path.Join(h.Path, defaultPath)
}
if u.Scheme != "dummy" {
h.Scheme = u.Scheme
}
} else {
h.Host = mirror
h.Path = defaultPath
}

mirrorHosts = append(mirrorHosts, h)
}
return append(mirrorHosts, dHost)

}

func loadTLSConfig(d string) (*tls.Config, error) {
fs, err := os.ReadDir(d)
if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) {
return nil, errors.WithStack(err)
}
type keyPair struct {
Certificate string
Key string
}
var (
rootCAs []string
keyPairs []keyPair
)
for _, f := range fs {
switch filepath.Ext(f.Name()) {
case ".crt":
rootCAs = append(rootCAs, filepath.Join(d, f.Name()))
case ".cert":
keyPairs = append(keyPairs, keyPair{
Certificate: filepath.Join(d, f.Name()),
Key: filepath.Join(d, strings.TrimSuffix(f.Name(), ".cert")+".key"),
})
}
}

tc := &tls.Config{
MinVersion: tls.VersionTLS12,
}
if len(rootCAs) > 0 {
systemPool, err := x509.SystemCertPool()
if err != nil {
if runtime.GOOS == "windows" {
systemPool = x509.NewCertPool()
} else {
return nil, errors.Wrapf(err, "unable to get system cert pool")
}
}
tc.RootCAs = systemPool
}

for _, p := range rootCAs {
dt, err := os.ReadFile(p)
if err != nil {
return nil, err
}
tc.RootCAs.AppendCertsFromPEM(dt)
}

for _, kp := range keyPairs {
cert, err := tls.LoadX509KeyPair(kp.Certificate, kp.Key)
if err != nil {
return nil, errors.Wrapf(err, "failed to load keypair for %s", kp.Certificate)
}
tc.Certificates = append(tc.Certificates, cert)
}
return tc, nil
}
Loading

0 comments on commit dc22579

Please sign in to comment.