Просмотр исходного кода

Merge pull request #31364 from adshmh/30935-use-encrypted-client-certificate-to-connect-to-a-docker-host

use an encrypted client certificate to connect to a docker daemon
Sebastiaan van Stijn 8 лет назад
Родитель
Сommit
2daa2b894c

+ 22 - 0
cli/command/cli.go

@@ -20,6 +20,7 @@ import (
 	dopts "github.com/docker/docker/opts"
 	"github.com/docker/go-connections/sockets"
 	"github.com/docker/go-connections/tlsconfig"
+	"github.com/docker/notary/passphrase"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
@@ -153,9 +154,30 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
 
 	var err error
 	cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
+	if tlsconfig.IsErrEncryptedKey(err) {
+		var (
+			passwd string
+			giveup bool
+		)
+		passRetriever := passphrase.PromptRetrieverWithInOut(cli.In(), cli.Out(), nil)
+
+		for attempts := 0; tlsconfig.IsErrEncryptedKey(err); attempts++ {
+			// some code and comments borrowed from notary/trustmanager/keystore.go
+			passwd, giveup, err = passRetriever("private", "encrypted TLS private", false, attempts)
+			// Check if the passphrase retriever got an error or if it is telling us to give up
+			if giveup || err != nil {
+				return errors.Wrap(err, "private key is encrypted, but could not get passphrase")
+			}
+
+			opts.Common.TLSOptions.Passphrase = passwd
+			cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
+		}
+	}
+
 	if err != nil {
 		return err
 	}
+
 	cli.defaultVersion = cli.client.ClientVersion()
 
 	if opts.Common.TrustKey == "" {

+ 1 - 1
client/client_test.go

@@ -33,7 +33,7 @@ func TestNewEnvClient(t *testing.T) {
 			envs: map[string]string{
 				"DOCKER_CERT_PATH": "invalid/path",
 			},
-			expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory. Make sure the key is not encrypted",
+			expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory",
 		},
 		{
 			envs: map[string]string{

+ 1 - 1
vendor.conf

@@ -17,7 +17,7 @@ github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
 golang.org/x/net c427ad74c6d7a814201695e9ffde0c5d400a7674
 golang.org/x/sys 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
 github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
-github.com/docker/go-connections a2afab9802043837035592f1c24827fb70766de9
+github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5
 golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
 
 github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5

+ 70 - 6
vendor/github.com/docker/go-connections/tlsconfig/config.go

@@ -8,11 +8,13 @@ package tlsconfig
 import (
 	"crypto/tls"
 	"crypto/x509"
+	"encoding/pem"
 	"fmt"
 	"io/ioutil"
 	"os"
 
 	"github.com/Sirupsen/logrus"
+	"github.com/pkg/errors"
 )
 
 // Options represents the information needed to create client and server TLS configurations.
@@ -34,6 +36,9 @@ type Options struct {
 	// the system pool will be used.
 	ExclusiveRootPools bool
 	MinVersion         uint16
+	// If Passphrase is set, it will be used to decrypt a TLS private key
+	// if the key is encrypted
+	Passphrase string
 }
 
 // Extra (server-side) accepted CBC cipher suites - will phase out in the future
@@ -127,6 +132,67 @@ func adjustMinVersion(options Options, config *tls.Config) error {
 	return nil
 }
 
+// IsErrEncryptedKey returns true if the 'err' is an error of incorrect
+// password when tryin to decrypt a TLS private key
+func IsErrEncryptedKey(err error) bool {
+	return errors.Cause(err) == x509.IncorrectPasswordError
+}
+
+// getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format.
+// If the private key is encrypted, 'passphrase' is used to decrypted the
+// private key.
+func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) {
+	// this section makes some small changes to code from notary/tuf/utils/x509.go
+	pemBlock, _ := pem.Decode(keyBytes)
+	if pemBlock == nil {
+		return nil, fmt.Errorf("no valid private key found")
+	}
+
+	var err error
+	if x509.IsEncryptedPEMBlock(pemBlock) {
+		keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase))
+		if err != nil {
+			return nil, errors.Wrap(err, "private key is encrypted, but could not decrypt it")
+		}
+		keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
+	}
+
+	return keyBytes, nil
+}
+
+// getCert returns a Certificate from the CertFile and KeyFile in 'options',
+// if the key is encrypted, the Passphrase in 'options' will be used to
+// decrypt it.
+func getCert(options Options) ([]tls.Certificate, error) {
+	if options.CertFile == "" && options.KeyFile == "" {
+		return nil, nil
+	}
+
+	errMessage := "Could not load X509 key pair"
+
+	cert, err := ioutil.ReadFile(options.CertFile)
+	if err != nil {
+		return nil, errors.Wrap(err, errMessage)
+	}
+
+	prKeyBytes, err := ioutil.ReadFile(options.KeyFile)
+	if err != nil {
+		return nil, errors.Wrap(err, errMessage)
+	}
+
+	prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase)
+	if err != nil {
+		return nil, errors.Wrap(err, errMessage)
+	}
+
+	tlsCert, err := tls.X509KeyPair(cert, prKeyBytes)
+	if err != nil {
+		return nil, errors.Wrap(err, errMessage)
+	}
+
+	return []tls.Certificate{tlsCert}, nil
+}
+
 // Client returns a TLS configuration meant to be used by a client.
 func Client(options Options) (*tls.Config, error) {
 	tlsConfig := ClientDefault()
@@ -139,13 +205,11 @@ func Client(options Options) (*tls.Config, error) {
 		tlsConfig.RootCAs = CAs
 	}
 
-	if options.CertFile != "" || options.KeyFile != "" {
-		tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
-		if err != nil {
-			return nil, fmt.Errorf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err)
-		}
-		tlsConfig.Certificates = []tls.Certificate{tlsCert}
+	tlsCerts, err := getCert(options)
+	if err != nil {
+		return nil, err
 	}
+	tlsConfig.Certificates = tlsCerts
 
 	if err := adjustMinVersion(options, tlsConfig); err != nil {
 		return nil, err