github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli/context/docker/load.go (about)

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