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 }