Files
docker-cli/cli/context/docker/load.go
Sebastiaan van Stijn 08a1ccc60a Remove support for encrypted TLS private keys
> Legacy PEM encryption as specified in RFC 1423 is insecure by design. Since
> it does not authenticate the ciphertext, it is vulnerable to padding oracle
> attacks that can let an attacker recover the plaintext

From https://go-review.googlesource.com/c/go/+/264159

> It's unfortunate that we don't implement PKCS#8 encryption so we can't
> recommend an alternative but PEM encryption is so broken that it's worth
> deprecating outright.

This feature allowed using an encrypted private key with a supplied password,
but did not provide additional security as the encryption is known to be broken,
and the key is sitting next to the password in the filesystem. Users are recommended
to decrypt the private key, and store it un-encrypted to continue using it.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-01-27 14:26:32 +01:00

167 lines
4.7 KiB
Go

package docker
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"net"
"net/http"
"os"
"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),
)
}
}
version := os.Getenv("DOCKER_API_VERSION")
if version != "" {
result = append(result, client.WithVersion(version))
} else {
result = append(result, 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
}