github.com/xeptore/docker-cli@v20.10.14+incompatible/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 31 // Deprecated: Use of encrypted TLS private keys has been deprecated, and 32 // will be removed in a future release. Golang has deprecated support for 33 // legacy PEM encryption (as specified in RFC 1423), as it is insecure by 34 // design (see https://go-review.googlesource.com/c/go/+/264159). 35 TLSPassword string 36 } 37 38 // WithTLSData loads TLS materials for the endpoint 39 func WithTLSData(s store.Reader, contextName string, m EndpointMeta) (Endpoint, error) { 40 tlsData, err := context.LoadTLSData(s, contextName, DockerEndpoint) 41 if err != nil { 42 return Endpoint{}, err 43 } 44 return Endpoint{ 45 EndpointMeta: m, 46 TLSData: tlsData, 47 }, nil 48 } 49 50 // tlsConfig extracts a context docker endpoint TLS config 51 func (c *Endpoint) tlsConfig() (*tls.Config, error) { 52 if c.TLSData == nil && !c.SkipTLSVerify { 53 // there is no specific tls config 54 return nil, nil 55 } 56 var tlsOpts []func(*tls.Config) 57 if c.TLSData != nil && c.TLSData.CA != nil { 58 certPool := x509.NewCertPool() 59 if !certPool.AppendCertsFromPEM(c.TLSData.CA) { 60 return nil, errors.New("failed to retrieve context tls info: ca.pem seems invalid") 61 } 62 tlsOpts = append(tlsOpts, func(cfg *tls.Config) { 63 cfg.RootCAs = certPool 64 }) 65 } 66 if c.TLSData != nil && c.TLSData.Key != nil && c.TLSData.Cert != nil { 67 keyBytes := c.TLSData.Key 68 pemBlock, _ := pem.Decode(keyBytes) 69 if pemBlock == nil { 70 return nil, fmt.Errorf("no valid private key found") 71 } 72 73 var err error 74 // TODO should we follow Golang, and deprecate RFC 1423 encryption, and produce a warning (or just error)? see https://github.com/docker/cli/issues/3212 75 if x509.IsEncryptedPEMBlock(pemBlock) { //nolint: staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design 76 keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(c.TLSPassword)) //nolint: staticcheck // SA1019: x509.IsEncryptedPEMBlock is deprecated, and insecure by design 77 if err != nil { 78 return nil, errors.Wrap(err, "private key is encrypted, but could not decrypt it") 79 } 80 keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes}) 81 } 82 83 x509cert, err := tls.X509KeyPair(c.TLSData.Cert, keyBytes) 84 if err != nil { 85 return nil, errors.Wrap(err, "failed to retrieve context tls info") 86 } 87 tlsOpts = append(tlsOpts, func(cfg *tls.Config) { 88 cfg.Certificates = []tls.Certificate{x509cert} 89 }) 90 } 91 if c.SkipTLSVerify { 92 tlsOpts = append(tlsOpts, func(cfg *tls.Config) { 93 cfg.InsecureSkipVerify = true 94 }) 95 } 96 return tlsconfig.ClientDefault(tlsOpts...), nil 97 } 98 99 // ClientOpts returns a slice of Client options to configure an API client with this endpoint 100 func (c *Endpoint) ClientOpts() ([]client.Opt, error) { 101 var result []client.Opt 102 if c.Host != "" { 103 helper, err := connhelper.GetConnectionHelper(c.Host) 104 if err != nil { 105 return nil, err 106 } 107 if helper == nil { 108 tlsConfig, err := c.tlsConfig() 109 if err != nil { 110 return nil, err 111 } 112 result = append(result, 113 withHTTPClient(tlsConfig), 114 client.WithHost(c.Host), 115 ) 116 117 } else { 118 httpClient := &http.Client{ 119 // No tls 120 // No proxy 121 Transport: &http.Transport{ 122 DialContext: helper.Dialer, 123 }, 124 } 125 result = append(result, 126 client.WithHTTPClient(httpClient), 127 client.WithHost(helper.Host), 128 client.WithDialContext(helper.Dialer), 129 ) 130 } 131 } 132 133 version := os.Getenv("DOCKER_API_VERSION") 134 if version != "" { 135 result = append(result, client.WithVersion(version)) 136 } else { 137 result = append(result, client.WithAPIVersionNegotiation()) 138 } 139 return result, nil 140 } 141 142 func withHTTPClient(tlsConfig *tls.Config) func(*client.Client) error { 143 return func(c *client.Client) error { 144 if tlsConfig == nil { 145 // Use the default HTTPClient 146 return nil 147 } 148 149 httpClient := &http.Client{ 150 Transport: &http.Transport{ 151 TLSClientConfig: tlsConfig, 152 DialContext: (&net.Dialer{ 153 KeepAlive: 30 * time.Second, 154 Timeout: 30 * time.Second, 155 }).DialContext, 156 }, 157 CheckRedirect: client.CheckRedirect, 158 } 159 return client.WithHTTPClient(httpClient)(c) 160 } 161 } 162 163 // EndpointFromContext parses a context docker endpoint metadata into a typed EndpointMeta structure 164 func EndpointFromContext(metadata store.Metadata) (EndpointMeta, error) { 165 ep, ok := metadata.Endpoints[DockerEndpoint] 166 if !ok { 167 return EndpointMeta{}, errors.New("cannot find docker endpoint in context") 168 } 169 typed, ok := ep.(EndpointMeta) 170 if !ok { 171 return EndpointMeta{}, errors.Errorf("endpoint %q is not of type EndpointMeta", DockerEndpoint) 172 } 173 return typed, nil 174 }