mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Add TLS support for discovery backend
This leverages recent additions to libkv enabling client
authentication via TLS so the discovery back-end can be locked
down with mutual TLS.  Example usage:
    docker daemon [other args] \
        --cluster-advertise 192.168.122.168:2376 \
        --cluster-store etcd://192.168.122.168:2379 \
        --cluster-store-opt kv.cacertfile=/path/to/ca.pem \
        --cluster-store-opt kv.certfile=/path/to/cert.pem \
        --cluster-store-opt kv.keyfile=/path/to/key.pem
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
			
			
This commit is contained in:
		
							parent
							
								
									ce0457a2c9
								
							
						
					
					
						commit
						124792a871
					
				
					 13 changed files with 215 additions and 21 deletions
				
			
		| 
						 | 
				
			
			@ -8,6 +8,7 @@ import (
 | 
			
		|||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/docker/docker/pkg/discovery"
 | 
			
		||||
	"github.com/docker/docker/pkg/tlsconfig"
 | 
			
		||||
	"github.com/docker/libkv"
 | 
			
		||||
	"github.com/docker/libkv/store"
 | 
			
		||||
	"github.com/docker/libkv/store/consul"
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +48,7 @@ func Init() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Initialize is exported
 | 
			
		||||
func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Duration) error {
 | 
			
		||||
func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) error {
 | 
			
		||||
	var (
 | 
			
		||||
		parts = strings.SplitN(uris, "/", 2)
 | 
			
		||||
		addrs = strings.Split(parts[0], ",")
 | 
			
		||||
| 
						 | 
				
			
			@ -63,9 +64,34 @@ func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Du
 | 
			
		|||
	s.ttl = ttl
 | 
			
		||||
	s.path = path.Join(s.prefix, discoveryPath)
 | 
			
		||||
 | 
			
		||||
	var config *store.Config
 | 
			
		||||
	if clusterOpts["kv.cacertfile"] != "" && clusterOpts["kv.certfile"] != "" && clusterOpts["kv.keyfile"] != "" {
 | 
			
		||||
		log.Info("Initializing discovery with TLS")
 | 
			
		||||
		tlsConfig, err := tlsconfig.Client(tlsconfig.Options{
 | 
			
		||||
			CAFile:   clusterOpts["kv.cacertfile"],
 | 
			
		||||
			CertFile: clusterOpts["kv.certfile"],
 | 
			
		||||
			KeyFile:  clusterOpts["kv.keyfile"],
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		config = &store.Config{
 | 
			
		||||
			// Set ClientTLS to trigger https (bug in libkv/etcd)
 | 
			
		||||
			ClientTLS: &store.ClientTLSConfig{
 | 
			
		||||
				CACertFile: clusterOpts["kv.cacertfile"],
 | 
			
		||||
				CertFile:   clusterOpts["kv.certfile"],
 | 
			
		||||
				KeyFile:    clusterOpts["kv.keyfile"],
 | 
			
		||||
			},
 | 
			
		||||
			// The actual TLS config that will be used
 | 
			
		||||
			TLS: tlsConfig,
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Info("Initializing discovery without TLS")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Creates a new store, will ignore options given
 | 
			
		||||
	// if not supported by the chosen store
 | 
			
		||||
	s.store, err = libkv.NewStore(s.backend, addrs, nil)
 | 
			
		||||
	s.store, err = libkv.NewStore(s.backend, addrs, config)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,11 +2,14 @@ package kv
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/discovery"
 | 
			
		||||
	"github.com/docker/libkv"
 | 
			
		||||
	"github.com/docker/libkv/store"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-check/check"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +27,7 @@ func (ds *DiscoverySuite) TestInitialize(c *check.C) {
 | 
			
		|||
		Endpoints: []string{"127.0.0.1"},
 | 
			
		||||
	}
 | 
			
		||||
	d := &Discovery{backend: store.CONSUL}
 | 
			
		||||
	d.Initialize("127.0.0.1", 0, 0)
 | 
			
		||||
	d.Initialize("127.0.0.1", 0, 0, nil)
 | 
			
		||||
	d.store = storeMock
 | 
			
		||||
 | 
			
		||||
	s := d.store.(*FakeStore)
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +39,7 @@ func (ds *DiscoverySuite) TestInitialize(c *check.C) {
 | 
			
		|||
		Endpoints: []string{"127.0.0.1:1234"},
 | 
			
		||||
	}
 | 
			
		||||
	d = &Discovery{backend: store.CONSUL}
 | 
			
		||||
	d.Initialize("127.0.0.1:1234/path", 0, 0)
 | 
			
		||||
	d.Initialize("127.0.0.1:1234/path", 0, 0, nil)
 | 
			
		||||
	d.store = storeMock
 | 
			
		||||
 | 
			
		||||
	s = d.store.(*FakeStore)
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +51,7 @@ func (ds *DiscoverySuite) TestInitialize(c *check.C) {
 | 
			
		|||
		Endpoints: []string{"127.0.0.1:1234", "127.0.0.2:1234", "127.0.0.3:1234"},
 | 
			
		||||
	}
 | 
			
		||||
	d = &Discovery{backend: store.CONSUL}
 | 
			
		||||
	d.Initialize("127.0.0.1:1234,127.0.0.2:1234,127.0.0.3:1234/path", 0, 0)
 | 
			
		||||
	d.Initialize("127.0.0.1:1234,127.0.0.2:1234,127.0.0.3:1234/path", 0, 0, nil)
 | 
			
		||||
	d.store = storeMock
 | 
			
		||||
 | 
			
		||||
	s = d.store.(*FakeStore)
 | 
			
		||||
| 
						 | 
				
			
			@ -60,6 +63,150 @@ func (ds *DiscoverySuite) TestInitialize(c *check.C) {
 | 
			
		|||
	c.Assert(d.path, check.Equals, "path/"+discoveryPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Extremely limited mock store so we can test initialization
 | 
			
		||||
type Mock struct {
 | 
			
		||||
	// Endpoints passed to InitializeMock
 | 
			
		||||
	Endpoints []string
 | 
			
		||||
 | 
			
		||||
	// Options passed to InitializeMock
 | 
			
		||||
	Options *store.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMock(endpoints []string, options *store.Config) (store.Store, error) {
 | 
			
		||||
	s := &Mock{}
 | 
			
		||||
	s.Endpoints = endpoints
 | 
			
		||||
	s.Options = options
 | 
			
		||||
	return s, nil
 | 
			
		||||
}
 | 
			
		||||
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
 | 
			
		||||
	return errors.New("Put not supported")
 | 
			
		||||
}
 | 
			
		||||
func (s *Mock) Get(key string) (*store.KVPair, error) {
 | 
			
		||||
	return nil, errors.New("Get not supported")
 | 
			
		||||
}
 | 
			
		||||
func (s *Mock) Delete(key string) error {
 | 
			
		||||
	return errors.New("Delete not supported")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Exists mock
 | 
			
		||||
func (s *Mock) Exists(key string) (bool, error) {
 | 
			
		||||
	return false, errors.New("Exists not supported")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Watch mock
 | 
			
		||||
func (s *Mock) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair, error) {
 | 
			
		||||
	return nil, errors.New("Watch not supported")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WatchTree mock
 | 
			
		||||
func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
 | 
			
		||||
	return nil, errors.New("WatchTree not supported")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLock mock
 | 
			
		||||
func (s *Mock) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
 | 
			
		||||
	return nil, errors.New("NewLock not supported")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List mock
 | 
			
		||||
func (s *Mock) List(prefix string) ([]*store.KVPair, error) {
 | 
			
		||||
	return nil, errors.New("List not supported")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteTree mock
 | 
			
		||||
func (s *Mock) DeleteTree(prefix string) error {
 | 
			
		||||
	return errors.New("DeleteTree not supported")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AtomicPut mock
 | 
			
		||||
func (s *Mock) AtomicPut(key string, value []byte, previous *store.KVPair, opts *store.WriteOptions) (bool, *store.KVPair, error) {
 | 
			
		||||
	return false, nil, errors.New("AtomicPut not supported")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AtomicDelete mock
 | 
			
		||||
func (s *Mock) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
 | 
			
		||||
	return false, errors.New("AtomicDelete not supported")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close mock
 | 
			
		||||
func (s *Mock) Close() {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ds *DiscoverySuite) TestInitializeWithCerts(c *check.C) {
 | 
			
		||||
	cert := `-----BEGIN CERTIFICATE-----
 | 
			
		||||
MIIDCDCCAfKgAwIBAgIICifG7YeiQOEwCwYJKoZIhvcNAQELMBIxEDAOBgNVBAMT
 | 
			
		||||
B1Rlc3QgQ0EwHhcNMTUxMDAxMjMwMDAwWhcNMjAwOTI5MjMwMDAwWjASMRAwDgYD
 | 
			
		||||
VQQDEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wRC
 | 
			
		||||
O+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4+zE9h80aC4hz+6caRpds
 | 
			
		||||
+J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhRSoSi3nY+B7F2E8cuz14q
 | 
			
		||||
V2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZrpXUyXxAvzXfpFXo1RhSb
 | 
			
		||||
UywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUerVYrCPq8vqfn//01qz55
 | 
			
		||||
Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHojxOpXTBepUCIJLbtNnWFT
 | 
			
		||||
V44t9gh5IqIWtoBReQIDAQABo2YwZDAOBgNVHQ8BAf8EBAMCAAYwEgYDVR0TAQH/
 | 
			
		||||
BAgwBgEB/wIBAjAdBgNVHQ4EFgQUZKUI8IIjIww7X/6hvwggQK4bD24wHwYDVR0j
 | 
			
		||||
BBgwFoAUZKUI8IIjIww7X/6hvwggQK4bD24wCwYJKoZIhvcNAQELA4IBAQDES2cz
 | 
			
		||||
7sCQfDCxCIWH7X8kpi/JWExzUyQEJ0rBzN1m3/x8ySRxtXyGekimBqQwQdFqlwMI
 | 
			
		||||
xzAQKkh3ue8tNSzRbwqMSyH14N1KrSxYS9e9szJHfUasoTpQGPmDmGIoRJuq1h6M
 | 
			
		||||
ej5x1SCJ7GWCR6xEXKUIE9OftXm9TdFzWa7Ja3OHz/mXteii8VXDuZ5ACq6EE5bY
 | 
			
		||||
8sP4gcICfJ5fTrpTlk9FIqEWWQrCGa5wk95PGEj+GJpNogjXQ97wVoo/Y3p1brEn
 | 
			
		||||
t5zjN9PAq4H1fuCMdNNA+p1DHNwd+ELTxcMAnb2ajwHvV6lKPXutrTFc4umJToBX
 | 
			
		||||
FpTxDmJHEV4bzUzh
 | 
			
		||||
-----END CERTIFICATE-----
 | 
			
		||||
`
 | 
			
		||||
	key := `-----BEGIN RSA PRIVATE KEY-----
 | 
			
		||||
MIIEpQIBAAKCAQEA1wRCO+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4
 | 
			
		||||
+zE9h80aC4hz+6caRpds+J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhR
 | 
			
		||||
SoSi3nY+B7F2E8cuz14qV2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZr
 | 
			
		||||
pXUyXxAvzXfpFXo1RhSbUywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUe
 | 
			
		||||
rVYrCPq8vqfn//01qz55Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHoj
 | 
			
		||||
xOpXTBepUCIJLbtNnWFTV44t9gh5IqIWtoBReQIDAQABAoIBAHSWipORGp/uKFXj
 | 
			
		||||
i/mut776x8ofsAxhnLBARQr93ID+i49W8H7EJGkOfaDjTICYC1dbpGrri61qk8sx
 | 
			
		||||
qX7p3v/5NzKwOIfEpirgwVIqSNYe/ncbxnhxkx6tXtUtFKmEx40JskvSpSYAhmmO
 | 
			
		||||
1XSx0E/PWaEN/nLgX/f1eWJIlxlQkk3QeqL+FGbCXI48DEtlJ9+MzMu4pAwZTpj5
 | 
			
		||||
5qtXo5JJ0jRGfJVPAOznRsYqv864AhMdMIWguzk6EGnbaCWwPcfcn+h9a5LMdony
 | 
			
		||||
MDHfBS7bb5tkF3+AfnVY3IBMVx7YlsD9eAyajlgiKu4zLbwTRHjXgShy+4Oussz0
 | 
			
		||||
ugNGnkECgYEA/hi+McrZC8C4gg6XqK8+9joD8tnyDZDz88BQB7CZqABUSwvjDqlP
 | 
			
		||||
L8hcwo/lzvjBNYGkqaFPUICGWKjeCtd8pPS2DCVXxDQX4aHF1vUur0uYNncJiV3N
 | 
			
		||||
XQz4Iemsa6wnKf6M67b5vMXICw7dw0HZCdIHD1hnhdtDz0uVpeevLZ8CgYEA2KCT
 | 
			
		||||
Y43lorjrbCgMqtlefkr3GJA9dey+hTzCiWEOOqn9RqGoEGUday0sKhiLofOgmN2B
 | 
			
		||||
LEukpKIey8s+Q/cb6lReajDVPDsMweX8i7hz3Wa4Ugp4Xa5BpHqu8qIAE2JUZ7bU
 | 
			
		||||
t88aQAYE58pUF+/Lq1QzAQdrjjzQBx6SrBxieecCgYEAvukoPZEC8mmiN1VvbTX+
 | 
			
		||||
QFHmlZha3QaDxChB+QUe7bMRojEUL/fVnzkTOLuVFqSfxevaI/km9n0ac5KtAchV
 | 
			
		||||
xjp2bTnBb5EUQFqjopYktWA+xO07JRJtMfSEmjZPbbay1kKC7rdTfBm961EIHaRj
 | 
			
		||||
xZUf6M+rOE8964oGrdgdLlECgYEA046GQmx6fh7/82FtdZDRQp9tj3SWQUtSiQZc
 | 
			
		||||
qhO59Lq8mjUXz+MgBuJXxkiwXRpzlbaFB0Bca1fUoYw8o915SrDYf/Zu2OKGQ/qa
 | 
			
		||||
V81sgiVmDuEgycR7YOlbX6OsVUHrUlpwhY3hgfMe6UtkMvhBvHF/WhroBEIJm1pV
 | 
			
		||||
PXZ/CbMCgYEApNWVktFBjOaYfY6SNn4iSts1jgsQbbpglg3kT7PLKjCAhI6lNsbk
 | 
			
		||||
dyT7ut01PL6RaW4SeQWtrJIVQaM6vF3pprMKqlc5XihOGAmVqH7rQx9rtQB5TicL
 | 
			
		||||
BFrwkQE4HQtQBV60hYQUzzlSk44VFDz+jxIEtacRHaomDRh2FtOTz+I=
 | 
			
		||||
-----END RSA PRIVATE KEY-----
 | 
			
		||||
`
 | 
			
		||||
	certFile, err := ioutil.TempFile("", "cert")
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
	defer os.Remove(certFile.Name())
 | 
			
		||||
	certFile.Write([]byte(cert))
 | 
			
		||||
	certFile.Close()
 | 
			
		||||
	keyFile, err := ioutil.TempFile("", "key")
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
	defer os.Remove(keyFile.Name())
 | 
			
		||||
	keyFile.Write([]byte(key))
 | 
			
		||||
	keyFile.Close()
 | 
			
		||||
 | 
			
		||||
	libkv.AddStore("mock", NewMock)
 | 
			
		||||
	d := &Discovery{backend: "mock"}
 | 
			
		||||
	err = d.Initialize("127.0.0.3:1234", 0, 0, map[string]string{
 | 
			
		||||
		"kv.cacertfile": certFile.Name(),
 | 
			
		||||
		"kv.certfile":   certFile.Name(),
 | 
			
		||||
		"kv.keyfile":    keyFile.Name(),
 | 
			
		||||
	})
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
	s := d.store.(*Mock)
 | 
			
		||||
	c.Assert(s.Options.TLS, check.NotNil)
 | 
			
		||||
	c.Assert(s.Options.TLS.RootCAs, check.NotNil)
 | 
			
		||||
	c.Assert(s.Options.TLS.Certificates, check.HasLen, 1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ds *DiscoverySuite) TestWatch(c *check.C) {
 | 
			
		||||
	mockCh := make(chan []*store.KVPair)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +216,7 @@ func (ds *DiscoverySuite) TestWatch(c *check.C) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	d := &Discovery{backend: store.CONSUL}
 | 
			
		||||
	d.Initialize("127.0.0.1:1234/path", 0, 0)
 | 
			
		||||
	d.Initialize("127.0.0.1:1234/path", 0, 0, nil)
 | 
			
		||||
	d.store = storeMock
 | 
			
		||||
 | 
			
		||||
	expected := discovery.Entries{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue