| package daemon |
| |
| import ( |
| "context" |
| "crypto/tls" |
| "crypto/x509" |
| "fmt" |
| "net/http" |
| "net/url" |
| "os" |
| "path" |
| "path/filepath" |
| "runtime" |
| "strings" |
| |
| "github.com/containerd/containerd/v2/core/remotes/docker" |
| hostconfig "github.com/containerd/containerd/v2/core/remotes/docker/config" |
| cerrdefs "github.com/containerd/errdefs" |
| "github.com/moby/moby/v2/daemon/pkg/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().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 |
| } |