Skip to content

Commit af23c1b

Browse files
committed
BACKPORT: use an encrypted client certificate to connect to a docker daemon
Fix https://bugzilla.redhat.com/show_bug.cgi?id=1510170 Signed-off-by: Antonio Murdaca <runcom@redhat.com>
1 parent ec8512b commit af23c1b

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

  • api/client
  • vendor/src/github.com/docker/go-connections/tlsconfig

api/client/cli.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"github.com/docker/engine-api/client"
2020
"github.com/docker/go-connections/sockets"
2121
"github.com/docker/go-connections/tlsconfig"
22+
"github.com/docker/notary/passphrase"
23+
pkgerrors "github.com/pkg/errors"
2224
)
2325

2426
// DockerCli represents the docker command line client.
@@ -179,6 +181,24 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.Cl
179181
cli.configFile = LoadDefaultConfigFile(err)
180182

181183
client, err := NewAPIClientFromFlags(clientFlags, cli.configFile)
184+
if tlsconfig.IsErrEncryptedKey(err) {
185+
var (
186+
passwd string
187+
giveup bool
188+
)
189+
passRetriever := passphrase.PromptRetrieverWithInOut(cli.In(), cli.Out(), nil)
190+
for attempts := 0; tlsconfig.IsErrEncryptedKey(err); attempts++ {
191+
// some code and comments borrowed from notary/trustmanager/keystore.go
192+
passwd, giveup, err = passRetriever("private", "encrypted TLS private", false, attempts)
193+
// Check if the passphrase retriever got an error or if it is telling us to give up
194+
if giveup || err != nil {
195+
return pkgerrors.Wrap(err, "private key is encrypted, but could not get passphrase")
196+
}
197+
198+
clientFlags.Common.TLSOptions.Passphrase = passwd
199+
client, err = NewAPIClientFromFlags(clientFlags, cli.configFile)
200+
}
201+
}
182202
if err != nil {
183203
return err
184204
}

vendor/src/github.com/docker/go-connections/tlsconfig/config.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ package tlsconfig
88
import (
99
"crypto/tls"
1010
"crypto/x509"
11+
"encoding/pem"
1112
"fmt"
1213
"io/ioutil"
1314
"os"
1415

1516
"github.com/Sirupsen/logrus"
17+
"github.com/pkg/errors"
1618
)
1719

1820
// Options represents the information needed to create client and server TLS configurations.
@@ -29,6 +31,10 @@ type Options struct {
2931
InsecureSkipVerify bool
3032
// server-only option
3133
ClientAuth tls.ClientAuthType
34+
35+
// If Passphrase is set, it will be used to decrypt a TLS private key
36+
// if the key is encrypted
37+
Passphrase string
3238
}
3339

3440
// Extra (server-side) accepted CBC cipher suites - will phase out in the future
@@ -80,6 +86,67 @@ func certPool(caFile string) (*x509.CertPool, error) {
8086
return certPool, nil
8187
}
8288

89+
// IsErrEncryptedKey returns true if the 'err' is an error of incorrect
90+
// password when tryin to decrypt a TLS private key
91+
func IsErrEncryptedKey(err error) bool {
92+
return errors.Cause(err) == x509.IncorrectPasswordError
93+
}
94+
95+
// getCert returns a Certificate from the CertFile and KeyFile in 'options',
96+
// if the key is encrypted, the Passphrase in 'options' will be used to
97+
// decrypt it.
98+
func getCert(options Options) ([]tls.Certificate, error) {
99+
if options.CertFile == "" && options.KeyFile == "" {
100+
return nil, nil
101+
}
102+
103+
errMessage := "Could not load X509 key pair"
104+
105+
cert, err := ioutil.ReadFile(options.CertFile)
106+
if err != nil {
107+
return nil, errors.Wrap(err, errMessage)
108+
}
109+
110+
prKeyBytes, err := ioutil.ReadFile(options.KeyFile)
111+
if err != nil {
112+
return nil, errors.Wrap(err, errMessage)
113+
}
114+
115+
prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase)
116+
if err != nil {
117+
return nil, errors.Wrap(err, errMessage)
118+
}
119+
120+
tlsCert, err := tls.X509KeyPair(cert, prKeyBytes)
121+
if err != nil {
122+
return nil, errors.Wrap(err, errMessage)
123+
}
124+
125+
return []tls.Certificate{tlsCert}, nil
126+
}
127+
128+
// getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format.
129+
// If the private key is encrypted, 'passphrase' is used to decrypted the
130+
// private key.
131+
func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) {
132+
// this section makes some small changes to code from notary/tuf/utils/x509.go
133+
pemBlock, _ := pem.Decode(keyBytes)
134+
if pemBlock == nil {
135+
return nil, fmt.Errorf("no valid private key found")
136+
}
137+
138+
var err error
139+
if x509.IsEncryptedPEMBlock(pemBlock) {
140+
keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase))
141+
if err != nil {
142+
return nil, errors.Wrap(err, "private key is encrypted, but could not decrypt it")
143+
}
144+
keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
145+
}
146+
147+
return keyBytes, nil
148+
}
149+
83150
// Client returns a TLS configuration meant to be used by a client.
84151
func Client(options Options) (*tls.Config, error) {
85152
tlsConfig := ClientDefault
@@ -99,6 +166,11 @@ func Client(options Options) (*tls.Config, error) {
99166
}
100167
tlsConfig.Certificates = []tls.Certificate{tlsCert}
101168
}
169+
tlsCerts, err := getCert(options)
170+
if err != nil {
171+
return nil, err
172+
}
173+
tlsConfig.Certificates = tlsCerts
102174

103175
return &tlsConfig, nil
104176
}

0 commit comments

Comments
 (0)