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. When linting on Go 1.16: cli/context/docker/load.go:69:6: SA1019: x509.IsEncryptedPEMBlock is deprecated: 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. (staticcheck) if x509.IsEncryptedPEMBlock(pemBlock) { ^ cli/context/docker/load.go:70:20: SA1019: x509.DecryptPEMBlock is deprecated: 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. (staticcheck) keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(c.TLSPassword)) ^ Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
170 lines
4.8 KiB
Go
170 lines
4.8 KiB
Go
package docker
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"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
|
|
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, fmt.Errorf("no valid private key found")
|
|
}
|
|
|
|
var err error
|
|
// TODO should we follow Golang, and deprecate RFC 1423 encryption, and produce a warning (or just error)? see https://github.com/docker/cli/issues/3212
|
|
if x509.IsEncryptedPEMBlock(pemBlock) { //nolint: staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design
|
|
keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(c.TLSPassword)) //nolint: staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "private key is encrypted, but could not decrypt it")
|
|
}
|
|
keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
|
|
}
|
|
|
|
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
|
|
}
|