github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/context/docker/load.go (about) 1 package docker 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 "encoding/pem" 7 "net" 8 "net/http" 9 "time" 10 11 "github.com/docker/cli/cli/connhelper" 12 "github.com/docker/cli/cli/context" 13 "github.com/docker/cli/cli/context/store" 14 "github.com/docker/docker/client" 15 "github.com/docker/go-connections/tlsconfig" 16 "github.com/pkg/errors" 17 ) 18 19 // EndpointMeta is a typed wrapper around a context-store generic endpoint describing 20 // a Docker Engine endpoint, without its tls config 21 type EndpointMeta = context.EndpointMetaBase 22 23 // Endpoint is a typed wrapper around a context-store generic endpoint describing 24 // a Docker Engine endpoint, with its tls data 25 type Endpoint struct { 26 EndpointMeta 27 TLSData *context.TLSData 28 29 // Deprecated: Use of encrypted TLS private keys has been deprecated, and 30 // will be removed in a future release. Golang has deprecated support for 31 // legacy PEM encryption (as specified in RFC 1423), as it is insecure by 32 // design (see https://go-review.googlesource.com/c/go/+/264159). 33 TLSPassword string 34 } 35 36 // WithTLSData loads TLS materials for the endpoint 37 func WithTLSData(s store.Reader, contextName string, m EndpointMeta) (Endpoint, error) { 38 tlsData, err := context.LoadTLSData(s, contextName, DockerEndpoint) 39 if err != nil { 40 return Endpoint{}, err 41 } 42 return Endpoint{ 43 EndpointMeta: m, 44 TLSData: tlsData, 45 }, nil 46 } 47 48 // tlsConfig extracts a context docker endpoint TLS config 49 func (c *Endpoint) tlsConfig() (*tls.Config, error) { 50 if c.TLSData == nil && !c.SkipTLSVerify { 51 // there is no specific tls config 52 return nil, nil 53 } 54 var tlsOpts []func(*tls.Config) 55 if c.TLSData != nil && c.TLSData.CA != nil { 56 certPool := x509.NewCertPool() 57 if !certPool.AppendCertsFromPEM(c.TLSData.CA) { 58 return nil, errors.New("failed to retrieve context tls info: ca.pem seems invalid") 59 } 60 tlsOpts = append(tlsOpts, func(cfg *tls.Config) { 61 cfg.RootCAs = certPool 62 }) 63 } 64 if c.TLSData != nil && c.TLSData.Key != nil && c.TLSData.Cert != nil { 65 keyBytes := c.TLSData.Key 66 pemBlock, _ := pem.Decode(keyBytes) 67 if pemBlock == nil { 68 return nil, errors.New("no valid private key found") 69 } 70 if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design 71 return nil, errors.New("private key is encrypted - support for encrypted private keys has been removed, see https://docs.docker.com/go/deprecated/") 72 } 73 74 x509cert, err := tls.X509KeyPair(c.TLSData.Cert, keyBytes) 75 if err != nil { 76 return nil, errors.Wrap(err, "failed to retrieve context tls info") 77 } 78 tlsOpts = append(tlsOpts, func(cfg *tls.Config) { 79 cfg.Certificates = []tls.Certificate{x509cert} 80 }) 81 } 82 if c.SkipTLSVerify { 83 tlsOpts = append(tlsOpts, func(cfg *tls.Config) { 84 cfg.InsecureSkipVerify = true 85 }) 86 } 87 return tlsconfig.ClientDefault(tlsOpts...), nil 88 } 89 90 // ClientOpts returns a slice of Client options to configure an API client with this endpoint 91 func (c *Endpoint) ClientOpts() ([]client.Opt, error) { 92 var result []client.Opt 93 if c.Host != "" { 94 helper, err := connhelper.GetConnectionHelper(c.Host) 95 if err != nil { 96 return nil, err 97 } 98 if helper == nil { 99 tlsConfig, err := c.tlsConfig() 100 if err != nil { 101 return nil, err 102 } 103 result = append(result, 104 withHTTPClient(tlsConfig), 105 client.WithHost(c.Host), 106 ) 107 108 } else { 109 httpClient := &http.Client{ 110 // No tls 111 // No proxy 112 Transport: &http.Transport{ 113 DialContext: helper.Dialer, 114 }, 115 } 116 result = append(result, 117 client.WithHTTPClient(httpClient), 118 client.WithHost(helper.Host), 119 client.WithDialContext(helper.Dialer), 120 ) 121 } 122 } 123 124 result = append(result, client.WithVersionFromEnv(), client.WithAPIVersionNegotiation()) 125 return result, nil 126 } 127 128 func withHTTPClient(tlsConfig *tls.Config) func(*client.Client) error { 129 return func(c *client.Client) error { 130 if tlsConfig == nil { 131 // Use the default HTTPClient 132 return nil 133 } 134 135 httpClient := &http.Client{ 136 Transport: &http.Transport{ 137 TLSClientConfig: tlsConfig, 138 DialContext: (&net.Dialer{ 139 KeepAlive: 30 * time.Second, 140 Timeout: 30 * time.Second, 141 }).DialContext, 142 }, 143 CheckRedirect: client.CheckRedirect, 144 } 145 return client.WithHTTPClient(httpClient)(c) 146 } 147 } 148 149 // EndpointFromContext parses a context docker endpoint metadata into a typed EndpointMeta structure 150 func EndpointFromContext(metadata store.Metadata) (EndpointMeta, error) { 151 ep, ok := metadata.Endpoints[DockerEndpoint] 152 if !ok { 153 return EndpointMeta{}, errors.New("cannot find docker endpoint in context") 154 } 155 typed, ok := ep.(EndpointMeta) 156 if !ok { 157 return EndpointMeta{}, errors.Errorf("endpoint %q is not of type EndpointMeta", DockerEndpoint) 158 } 159 return typed, nil 160 }