github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/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/docker/client" 12 "github.com/khulnasoft-lab/go-connections/tlsconfig" 13 "github.com/khulnasoft/cli/cli/connhelper" 14 "github.com/khulnasoft/cli/cli/context" 15 "github.com/khulnasoft/cli/cli/context/store" 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 30 // WithTLSData loads TLS materials for the endpoint 31 func WithTLSData(s store.Reader, contextName string, m EndpointMeta) (Endpoint, error) { 32 tlsData, err := context.LoadTLSData(s, contextName, DockerEndpoint) 33 if err != nil { 34 return Endpoint{}, err 35 } 36 return Endpoint{ 37 EndpointMeta: m, 38 TLSData: tlsData, 39 }, nil 40 } 41 42 // tlsConfig extracts a context docker endpoint TLS config 43 func (ep *Endpoint) tlsConfig() (*tls.Config, error) { 44 if ep.TLSData == nil && !ep.SkipTLSVerify { 45 // there is no specific tls config 46 return nil, nil 47 } 48 var tlsOpts []func(*tls.Config) 49 if ep.TLSData != nil && ep.TLSData.CA != nil { 50 certPool := x509.NewCertPool() 51 if !certPool.AppendCertsFromPEM(ep.TLSData.CA) { 52 return nil, errors.New("failed to retrieve context tls info: ca.pem seems invalid") 53 } 54 tlsOpts = append(tlsOpts, func(cfg *tls.Config) { 55 cfg.RootCAs = certPool 56 }) 57 } 58 if ep.TLSData != nil && ep.TLSData.Key != nil && ep.TLSData.Cert != nil { 59 keyBytes := ep.TLSData.Key 60 pemBlock, _ := pem.Decode(keyBytes) 61 if pemBlock == nil { 62 return nil, errors.New("no valid private key found") 63 } 64 if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design 65 return nil, errors.New("private key is encrypted - support for encrypted private keys has been removed, see https://docs.docker.com/go/deprecated/") 66 } 67 68 x509cert, err := tls.X509KeyPair(ep.TLSData.Cert, keyBytes) 69 if err != nil { 70 return nil, errors.Wrap(err, "failed to retrieve context tls info") 71 } 72 tlsOpts = append(tlsOpts, func(cfg *tls.Config) { 73 cfg.Certificates = []tls.Certificate{x509cert} 74 }) 75 } 76 if ep.SkipTLSVerify { 77 tlsOpts = append(tlsOpts, func(cfg *tls.Config) { 78 cfg.InsecureSkipVerify = true 79 }) 80 } 81 return tlsconfig.ClientDefault(tlsOpts...), nil 82 } 83 84 // ClientOpts returns a slice of Client options to configure an API client with this endpoint 85 func (ep *Endpoint) ClientOpts() ([]client.Opt, error) { 86 var result []client.Opt 87 if ep.Host != "" { 88 helper, err := connhelper.GetConnectionHelper(ep.Host) 89 if err != nil { 90 return nil, err 91 } 92 if helper == nil { 93 tlsConfig, err := ep.tlsConfig() 94 if err != nil { 95 return nil, err 96 } 97 result = append(result, 98 withHTTPClient(tlsConfig), 99 client.WithHost(ep.Host), 100 ) 101 } else { 102 result = append(result, 103 client.WithHTTPClient(&http.Client{ 104 // No TLS, and no proxy. 105 Transport: &http.Transport{ 106 DialContext: helper.Dialer, 107 }, 108 }), 109 client.WithHost(helper.Host), 110 client.WithDialContext(helper.Dialer), 111 ) 112 } 113 } 114 115 result = append(result, client.WithVersionFromEnv(), client.WithAPIVersionNegotiation()) 116 return result, nil 117 } 118 119 func withHTTPClient(tlsConfig *tls.Config) func(*client.Client) error { 120 return func(c *client.Client) error { 121 if tlsConfig == nil { 122 // Use the default HTTPClient 123 return nil 124 } 125 return client.WithHTTPClient(&http.Client{ 126 Transport: &http.Transport{ 127 TLSClientConfig: tlsConfig, 128 DialContext: (&net.Dialer{ 129 KeepAlive: 30 * time.Second, 130 Timeout: 30 * time.Second, 131 }).DialContext, 132 }, 133 CheckRedirect: client.CheckRedirect, 134 })(c) 135 } 136 } 137 138 // EndpointFromContext parses a context docker endpoint metadata into a typed EndpointMeta structure 139 func EndpointFromContext(metadata store.Metadata) (EndpointMeta, error) { 140 ep, ok := metadata.Endpoints[DockerEndpoint] 141 if !ok { 142 return EndpointMeta{}, errors.New("cannot find docker endpoint in context") 143 } 144 typed, ok := ep.(EndpointMeta) 145 if !ok { 146 return EndpointMeta{}, errors.Errorf("endpoint %q is not of type EndpointMeta", DockerEndpoint) 147 } 148 return typed, nil 149 }