Older versions of Go do not format these comments, so we can already
reformat them ahead of time to prevent gofmt linting failing once
we update to Go 1.19 or up.
Result of:
gofmt -s -w $(find . -type f -name '*.go' | grep -v "/vendor/")
With some manual adjusting.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
161 lines
4.6 KiB
Go
161 lines
4.6 KiB
Go
package docker
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/docker/cli/cli/connhelper"
|
|
"github.com/docker/cli/cli/context"
|
|
"github.com/docker/cli/cli/context/store"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/go-connections/tlsconfig"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// EndpointMeta is a typed wrapper around a context-store generic endpoint describing
|
|
// a Docker Engine endpoint, without its tls config
|
|
type EndpointMeta = context.EndpointMetaBase
|
|
|
|
// Endpoint is a typed wrapper around a context-store generic endpoint describing
|
|
// a Docker Engine endpoint, with its tls data
|
|
type Endpoint struct {
|
|
EndpointMeta
|
|
TLSData *context.TLSData
|
|
|
|
// Deprecated: Use of encrypted TLS private keys has been deprecated, and
|
|
// will be removed in a future release. Golang has deprecated support for
|
|
// legacy PEM encryption (as specified in RFC 1423), as it is insecure by
|
|
// design (see https://go-review.googlesource.com/c/go/+/264159).
|
|
TLSPassword string
|
|
}
|
|
|
|
// WithTLSData loads TLS materials for the endpoint
|
|
func WithTLSData(s store.Reader, contextName string, m EndpointMeta) (Endpoint, error) {
|
|
tlsData, err := context.LoadTLSData(s, contextName, DockerEndpoint)
|
|
if err != nil {
|
|
return Endpoint{}, err
|
|
}
|
|
return Endpoint{
|
|
EndpointMeta: m,
|
|
TLSData: tlsData,
|
|
}, nil
|
|
}
|
|
|
|
// tlsConfig extracts a context docker endpoint TLS config
|
|
func (c *Endpoint) tlsConfig() (*tls.Config, error) {
|
|
if c.TLSData == nil && !c.SkipTLSVerify {
|
|
// there is no specific tls config
|
|
return nil, nil
|
|
}
|
|
var tlsOpts []func(*tls.Config)
|
|
if c.TLSData != nil && c.TLSData.CA != nil {
|
|
certPool := x509.NewCertPool()
|
|
if !certPool.AppendCertsFromPEM(c.TLSData.CA) {
|
|
return nil, errors.New("failed to retrieve context tls info: ca.pem seems invalid")
|
|
}
|
|
tlsOpts = append(tlsOpts, func(cfg *tls.Config) {
|
|
cfg.RootCAs = certPool
|
|
})
|
|
}
|
|
if c.TLSData != nil && c.TLSData.Key != nil && c.TLSData.Cert != nil {
|
|
keyBytes := c.TLSData.Key
|
|
pemBlock, _ := pem.Decode(keyBytes)
|
|
if pemBlock == nil {
|
|
return nil, errors.New("no valid private key found")
|
|
}
|
|
if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design
|
|
return nil, errors.New("private key is encrypted - support for encrypted private keys has been removed, see https://docs.docker.com/go/deprecated/")
|
|
}
|
|
|
|
x509cert, err := tls.X509KeyPair(c.TLSData.Cert, keyBytes)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to retrieve context tls info")
|
|
}
|
|
tlsOpts = append(tlsOpts, func(cfg *tls.Config) {
|
|
cfg.Certificates = []tls.Certificate{x509cert}
|
|
})
|
|
}
|
|
if c.SkipTLSVerify {
|
|
tlsOpts = append(tlsOpts, func(cfg *tls.Config) {
|
|
cfg.InsecureSkipVerify = true
|
|
})
|
|
}
|
|
return tlsconfig.ClientDefault(tlsOpts...), nil
|
|
}
|
|
|
|
// ClientOpts returns a slice of Client options to configure an API client with this endpoint
|
|
func (c *Endpoint) ClientOpts() ([]client.Opt, error) {
|
|
var result []client.Opt
|
|
if c.Host != "" {
|
|
helper, err := connhelper.GetConnectionHelper(c.Host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if helper == nil {
|
|
tlsConfig, err := c.tlsConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result,
|
|
withHTTPClient(tlsConfig),
|
|
client.WithHost(c.Host),
|
|
)
|
|
|
|
} else {
|
|
httpClient := &http.Client{
|
|
// No tls
|
|
// No proxy
|
|
Transport: &http.Transport{
|
|
DialContext: helper.Dialer,
|
|
},
|
|
}
|
|
result = append(result,
|
|
client.WithHTTPClient(httpClient),
|
|
client.WithHost(helper.Host),
|
|
client.WithDialContext(helper.Dialer),
|
|
)
|
|
}
|
|
}
|
|
|
|
result = append(result, client.WithVersionFromEnv(), client.WithAPIVersionNegotiation())
|
|
return result, nil
|
|
}
|
|
|
|
func withHTTPClient(tlsConfig *tls.Config) func(*client.Client) error {
|
|
return func(c *client.Client) error {
|
|
if tlsConfig == nil {
|
|
// Use the default HTTPClient
|
|
return nil
|
|
}
|
|
|
|
httpClient := &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: tlsConfig,
|
|
DialContext: (&net.Dialer{
|
|
KeepAlive: 30 * time.Second,
|
|
Timeout: 30 * time.Second,
|
|
}).DialContext,
|
|
},
|
|
CheckRedirect: client.CheckRedirect,
|
|
}
|
|
return client.WithHTTPClient(httpClient)(c)
|
|
}
|
|
}
|
|
|
|
// EndpointFromContext parses a context docker endpoint metadata into a typed EndpointMeta structure
|
|
func EndpointFromContext(metadata store.Metadata) (EndpointMeta, error) {
|
|
ep, ok := metadata.Endpoints[DockerEndpoint]
|
|
if !ok {
|
|
return EndpointMeta{}, errors.New("cannot find docker endpoint in context")
|
|
}
|
|
typed, ok := ep.(EndpointMeta)
|
|
if !ok {
|
|
return EndpointMeta{}, errors.Errorf("endpoint %q is not of type EndpointMeta", DockerEndpoint)
|
|
}
|
|
return typed, nil
|
|
}
|