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  }