2018-12-17 05:27:07 -05:00
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
2018-11-09 09:10:41 -05:00
type EndpointMeta = context . EndpointMetaBase
2018-12-17 05:27:07 -05:00
// Endpoint is a typed wrapper around a context-store generic endpoint describing
// a Docker Engine endpoint, with its tls data
type Endpoint struct {
EndpointMeta
2021-07-28 06:45:09 -04:00
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).
2018-12-17 05:27:07 -05:00
TLSPassword string
}
// WithTLSData loads TLS materials for the endpoint
2019-04-15 06:03:03 -04:00
func WithTLSData ( s store . Reader , contextName string , m EndpointMeta ) ( Endpoint , error ) {
2018-12-17 05:27:07 -05:00
tlsData , err := context . LoadTLSData ( s , contextName , DockerEndpoint )
if err != nil {
return Endpoint { } , err
}
return Endpoint {
2018-11-09 09:10:41 -05:00
EndpointMeta : m ,
2018-12-17 05:27:07 -05:00
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
2021-07-26 11:29:52 -04:00
// 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
2022-07-13 06:29:49 -04:00
if x509 . IsEncryptedPEMBlock ( pemBlock ) { //nolint:staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design
2021-07-26 11:29:52 -04:00
keyBytes , err = x509 . DecryptPEMBlock ( pemBlock , [ ] byte ( c . TLSPassword ) ) //nolint: staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design
2018-12-17 05:27:07 -05:00
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
2019-04-12 18:54:49 -04:00
func ( c * Endpoint ) ClientOpts ( ) ( [ ] client . Opt , error ) {
var result [ ] client . Opt
2018-12-17 05:27:07 -05:00
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 ) ,
2019-08-22 10:39:21 -04:00
client . WithHost ( c . Host ) ,
2018-12-17 05:27:07 -05:00
)
} 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 ) )
2019-04-23 18:23:32 -04:00
} else {
result = append ( result , client . WithAPIVersionNegotiation ( ) )
2018-12-17 05:27:07 -05:00
}
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
2019-04-18 09:12:30 -04:00
func EndpointFromContext ( metadata store . Metadata ) ( EndpointMeta , error ) {
2018-12-17 05:27:07 -05:00
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
}