| package resolver |
| |
| import ( |
| "crypto/tls" |
| "crypto/x509" |
| "io/ioutil" |
| "net" |
| "net/http" |
| "os" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "time" |
| |
| "github.com/containerd/containerd/remotes/docker" |
| "github.com/moby/buildkit/cmd/buildkitd/config" |
| "github.com/moby/buildkit/util/tracing" |
| "github.com/pkg/errors" |
| ) |
| |
| func fillInsecureOpts(host string, c config.RegistryConfig, h docker.RegistryHost) ([]docker.RegistryHost, error) { |
| var hosts []docker.RegistryHost |
| |
| tc, err := loadTLSConfig(c) |
| if err != nil { |
| return nil, err |
| } |
| var isHTTP bool |
| |
| if c.PlainHTTP != nil && *c.PlainHTTP { |
| isHTTP = true |
| } |
| if c.PlainHTTP == nil { |
| if ok, _ := docker.MatchLocalhost(host); ok { |
| isHTTP = true |
| } |
| } |
| |
| if isHTTP { |
| h2 := h |
| h2.Scheme = "http" |
| hosts = append(hosts, h2) |
| } |
| if c.Insecure != nil && *c.Insecure { |
| h2 := h |
| transport := newDefaultTransport() |
| transport.TLSClientConfig = tc |
| h2.Client = &http.Client{ |
| Transport: tracing.NewTransport(transport), |
| } |
| tc.InsecureSkipVerify = true |
| hosts = append(hosts, h2) |
| } |
| |
| if len(hosts) == 0 { |
| transport := newDefaultTransport() |
| transport.TLSClientConfig = tc |
| |
| h.Client = &http.Client{ |
| Transport: tracing.NewTransport(transport), |
| } |
| hosts = append(hosts, h) |
| } |
| |
| return hosts, nil |
| } |
| |
| func loadTLSConfig(c config.RegistryConfig) (*tls.Config, error) { |
| for _, d := range c.TLSConfigDir { |
| fs, err := ioutil.ReadDir(d) |
| if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) { |
| return nil, errors.WithStack(err) |
| } |
| for _, f := range fs { |
| if strings.HasSuffix(f.Name(), ".crt") { |
| c.RootCAs = append(c.RootCAs, filepath.Join(d, f.Name())) |
| } |
| if strings.HasSuffix(f.Name(), ".cert") { |
| c.KeyPairs = append(c.KeyPairs, config.TLSKeyPair{ |
| Certificate: filepath.Join(d, f.Name()), |
| Key: filepath.Join(d, strings.TrimSuffix(f.Name(), ".cert")+".key"), |
| }) |
| } |
| } |
| } |
| |
| tc := &tls.Config{} |
| if len(c.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 c.RootCAs { |
| dt, err := ioutil.ReadFile(p) |
| if err != nil { |
| return nil, errors.Wrapf(err, "failed to read %s", p) |
| } |
| tc.RootCAs.AppendCertsFromPEM(dt) |
| } |
| |
| for _, kp := range c.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 |
| } |
| |
| // NewRegistryConfig converts registry config to docker.RegistryHosts callback |
| func NewRegistryConfig(m map[string]config.RegistryConfig) docker.RegistryHosts { |
| return docker.Registries( |
| func(host string) ([]docker.RegistryHost, error) { |
| c, ok := m[host] |
| if !ok { |
| return nil, nil |
| } |
| |
| var out []docker.RegistryHost |
| |
| for _, mirror := range c.Mirrors { |
| h := docker.RegistryHost{ |
| Scheme: "https", |
| Client: newDefaultClient(), |
| Host: mirror, |
| Path: "/v2", |
| Capabilities: docker.HostCapabilityPull | docker.HostCapabilityResolve, |
| } |
| |
| hosts, err := fillInsecureOpts(mirror, m[mirror], h) |
| if err != nil { |
| return nil, err |
| } |
| |
| out = append(out, hosts...) |
| } |
| |
| if host == "docker.io" { |
| host = "registry-1.docker.io" |
| } |
| |
| h := docker.RegistryHost{ |
| Scheme: "https", |
| Client: newDefaultClient(), |
| Host: host, |
| Path: "/v2", |
| Capabilities: docker.HostCapabilityPush | docker.HostCapabilityPull | docker.HostCapabilityResolve, |
| } |
| |
| hosts, err := fillInsecureOpts(host, c, h) |
| if err != nil { |
| return nil, err |
| } |
| |
| out = append(out, hosts...) |
| return out, nil |
| }, |
| docker.ConfigureDefaultRegistries( |
| docker.WithClient(newDefaultClient()), |
| docker.WithPlainHTTP(docker.MatchLocalhost), |
| ), |
| ) |
| } |
| |
| func newDefaultClient() *http.Client { |
| return &http.Client{ |
| Transport: tracing.NewTransport(newDefaultTransport()), |
| } |
| } |
| |
| // newDefaultTransport is for pull or push client |
| // |
| // NOTE: For push, there must disable http2 for https because the flow control |
| // will limit data transfer. The net/http package doesn't provide http2 tunable |
| // settings which limits push performance. |
| // |
| // REF: https://github.com/golang/go/issues/14077 |
| func newDefaultTransport() *http.Transport { |
| return &http.Transport{ |
| Proxy: http.ProxyFromEnvironment, |
| DialContext: (&net.Dialer{ |
| Timeout: 30 * time.Second, |
| KeepAlive: 60 * time.Second, |
| }).DialContext, |
| MaxIdleConns: 10, |
| IdleConnTimeout: 30 * time.Second, |
| TLSHandshakeTimeout: 10 * time.Second, |
| ExpectContinueTimeout: 5 * time.Second, |
| TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), |
| } |
| } |