mirror of https://github.com/docker/docs.git
Merge pull request #1254 from dhiltgen/tls_kv
Add TLS support for libkv
This commit is contained in:
commit
1d008a7ddd
|
|
@ -35,6 +35,10 @@
|
||||||
"Comment": "v1.4.1-3245-g443437f",
|
"Comment": "v1.4.1-3245-g443437f",
|
||||||
"Rev": "443437f5ea04da9d62bf3e05d7951f7d30e77d96"
|
"Rev": "443437f5ea04da9d62bf3e05d7951f7d30e77d96"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/pkg/tlsconfig",
|
||||||
|
"Rev": "c7a04fda2ad804601385f054c19b69cf43fcfe46"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/pkg/parsers/filters",
|
"ImportPath": "github.com/docker/docker/pkg/parsers/filters",
|
||||||
"Comment": "v1.4.1-3245-g443437f",
|
"Comment": "v1.4.1-3245-g443437f",
|
||||||
|
|
|
||||||
132
Godeps/_workspace/src/github.com/docker/docker/pkg/tlsconfig/config.go
generated
vendored
Normal file
132
Godeps/_workspace/src/github.com/docker/docker/pkg/tlsconfig/config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers.
|
||||||
|
//
|
||||||
|
// As a reminder from https://golang.org/pkg/crypto/tls/#Config:
|
||||||
|
// A Config structure is used to configure a TLS client or server. After one has been passed to a TLS function it must not be modified.
|
||||||
|
// A Config may be reused; the tls package will also not modify it.
|
||||||
|
package tlsconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options represents the information needed to create client and server TLS configurations.
|
||||||
|
type Options struct {
|
||||||
|
CAFile string
|
||||||
|
|
||||||
|
// If either CertFile or KeyFile is empty, Client() will not load them
|
||||||
|
// preventing the client from authenticating to the server.
|
||||||
|
// However, Server() requires them and will error out if they are empty.
|
||||||
|
CertFile string
|
||||||
|
KeyFile string
|
||||||
|
|
||||||
|
// client-only option
|
||||||
|
InsecureSkipVerify bool
|
||||||
|
// server-only option
|
||||||
|
ClientAuth tls.ClientAuthType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra (server-side) accepted CBC cipher suites - will phase out in the future
|
||||||
|
var acceptedCBCCiphers = []uint16{
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set)
|
||||||
|
var clientCipherSuites = []uint16{
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
}
|
||||||
|
|
||||||
|
// For use by code which already has a crypto/tls options struct but wants to
|
||||||
|
// use a commonly accepted set of TLS cipher suites, with known weak algorithms removed
|
||||||
|
var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...)
|
||||||
|
|
||||||
|
// ServerDefault is a secure-enough TLS configuration for the server TLS configuration.
|
||||||
|
var ServerDefault = tls.Config{
|
||||||
|
// Avoid fallback to SSL protocols < TLS1.0
|
||||||
|
MinVersion: tls.VersionTLS10,
|
||||||
|
PreferServerCipherSuites: true,
|
||||||
|
CipherSuites: DefaultServerAcceptedCiphers,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientDefault is a secure-enough TLS configuration for the client TLS configuration.
|
||||||
|
var ClientDefault = tls.Config{
|
||||||
|
// Prefer TLS1.2 as the client minimum
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
CipherSuites: clientCipherSuites,
|
||||||
|
}
|
||||||
|
|
||||||
|
// certPool returns an X.509 certificate pool from `caFile`, the certificate file.
|
||||||
|
func certPool(caFile string) (*x509.CertPool, error) {
|
||||||
|
// If we should verify the server, we need to load a trusted ca
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
pem, err := ioutil.ReadFile(caFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not read CA certificate %q: %v", caFile, err)
|
||||||
|
}
|
||||||
|
if !certPool.AppendCertsFromPEM(pem) {
|
||||||
|
return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile)
|
||||||
|
}
|
||||||
|
s := certPool.Subjects()
|
||||||
|
subjects := make([]string, len(s))
|
||||||
|
for i, subject := range s {
|
||||||
|
subjects[i] = string(subject)
|
||||||
|
}
|
||||||
|
logrus.Debugf("Trusting certs with subjects: %v", subjects)
|
||||||
|
return certPool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns a TLS configuration meant to be used by a client.
|
||||||
|
func Client(options Options) (*tls.Config, error) {
|
||||||
|
tlsConfig := ClientDefault
|
||||||
|
tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
|
||||||
|
if !options.InsecureSkipVerify {
|
||||||
|
CAs, err := certPool(options.CAFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tlsConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server returns a TLS configuration meant to be used by a server.
|
||||||
|
func Server(options Options) (*tls.Config, error) {
|
||||||
|
tlsConfig := ServerDefault
|
||||||
|
tlsConfig.ClientAuth = options.ClientAuth
|
||||||
|
tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("Could not load X509 key pair (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Error reading X509 key pair (cert: %q, key: %q): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err)
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
||||||
|
if options.ClientAuth >= tls.VerifyClientCertIfGiven {
|
||||||
|
CAs, err := certPool(options.CAFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig.ClientCAs = CAs
|
||||||
|
}
|
||||||
|
return &tlsConfig, nil
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ var (
|
||||||
Name: "list",
|
Name: "list",
|
||||||
ShortName: "l",
|
ShortName: "l",
|
||||||
Usage: "List nodes in a cluster",
|
Usage: "List nodes in a cluster",
|
||||||
Flags: []cli.Flag{flTimeout},
|
Flags: []cli.Flag{flTimeout, flDiscoveryOpt},
|
||||||
Action: list,
|
Action: list,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -28,14 +28,14 @@ var (
|
||||||
flTLS, flTLSCaCert, flTLSCert, flTLSKey, flTLSVerify,
|
flTLS, flTLSCaCert, flTLSCert, flTLSKey, flTLSVerify,
|
||||||
flHeartBeat,
|
flHeartBeat,
|
||||||
flEnableCors,
|
flEnableCors,
|
||||||
flCluster, flClusterOpt},
|
flCluster, flDiscoveryOpt, flClusterOpt},
|
||||||
Action: manage,
|
Action: manage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "join",
|
Name: "join",
|
||||||
ShortName: "j",
|
ShortName: "j",
|
||||||
Usage: "join a docker cluster",
|
Usage: "join a docker cluster",
|
||||||
Flags: []cli.Flag{flJoinAdvertise, flHeartBeat, flTTL},
|
Flags: []cli.Flag{flJoinAdvertise, flHeartBeat, flTTL, flDiscoveryOpt},
|
||||||
Action: join,
|
Action: join,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ func create(c *cli.Context) {
|
||||||
log.Fatalf("the `create` command takes no arguments. See '%s create --help'.", c.App.Name)
|
log.Fatalf("the `create` command takes no arguments. See '%s create --help'.", c.App.Name)
|
||||||
}
|
}
|
||||||
discovery := &token.Discovery{}
|
discovery := &token.Discovery{}
|
||||||
discovery.Initialize("", 0, 0)
|
discovery.Initialize("", 0, 0, nil)
|
||||||
token, err := discovery.CreateCluster()
|
token, err := discovery.CreateCluster()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,11 @@ var (
|
||||||
Usage: "cluster driver options",
|
Usage: "cluster driver options",
|
||||||
Value: &cli.StringSlice{},
|
Value: &cli.StringSlice{},
|
||||||
}
|
}
|
||||||
|
flDiscoveryOpt = cli.StringSliceFlag{
|
||||||
|
Name: "discovery-opt",
|
||||||
|
Usage: "discovery options",
|
||||||
|
Value: &cli.StringSlice{},
|
||||||
|
}
|
||||||
|
|
||||||
flLeaderElection = cli.BoolFlag{
|
flLeaderElection = cli.BoolFlag{
|
||||||
Name: "replication",
|
Name: "replication",
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ func join(c *cli.Context) {
|
||||||
if ttl <= hb {
|
if ttl <= hb {
|
||||||
log.Fatal("--ttl must be strictly superior to the heartbeat value")
|
log.Fatal("--ttl must be strictly superior to the heartbeat value")
|
||||||
}
|
}
|
||||||
d, err := discovery.New(dflag, hb, ttl)
|
|
||||||
|
d, err := discovery.New(dflag, hb, ttl, getDiscoveryOpt(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ func list(c *cli.Context) {
|
||||||
log.Fatalf("invalid --timeout: %v", err)
|
log.Fatalf("invalid --timeout: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := discovery.New(dflag, timeout, 0)
|
d, err := discovery.New(dflag, timeout, 0, getDiscoveryOpt(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
|
@ -97,7 +98,7 @@ func loadTLSConfig(ca, cert, key string, verify bool) (*tls.Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the discovery service.
|
// Initialize the discovery service.
|
||||||
func createDiscovery(uri string, c *cli.Context) discovery.Discovery {
|
func createDiscovery(uri string, c *cli.Context, discoveryOpt []string) discovery.Discovery {
|
||||||
hb, err := time.ParseDuration(c.String("heartbeat"))
|
hb, err := time.ParseDuration(c.String("heartbeat"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("invalid --heartbeat: %v", err)
|
log.Fatalf("invalid --heartbeat: %v", err)
|
||||||
|
|
@ -107,7 +108,7 @@ func createDiscovery(uri string, c *cli.Context) discovery.Discovery {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up discovery.
|
// Set up discovery.
|
||||||
discovery, err := discovery.New(uri, hb, 0)
|
discovery, err := discovery.New(uri, hb, 0, getDiscoveryOpt(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -115,6 +116,19 @@ func createDiscovery(uri string, c *cli.Context) discovery.Discovery {
|
||||||
return discovery
|
return discovery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDiscoveryOpt(c *cli.Context) map[string]string {
|
||||||
|
// Process the store options
|
||||||
|
options := map[string]string{}
|
||||||
|
for _, option := range c.StringSlice("discovery-opt") {
|
||||||
|
if !strings.Contains(option, "=") {
|
||||||
|
log.Fatal("--discovery-opt must contain key=value strings")
|
||||||
|
}
|
||||||
|
kvpair := strings.SplitN(option, "=", 2)
|
||||||
|
options[kvpair[0]] = kvpair[1]
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
func setupReplication(c *cli.Context, cluster cluster.Cluster, server *api.Server, discovery discovery.Discovery, addr string, leaderTTL time.Duration, tlsConfig *tls.Config) {
|
func setupReplication(c *cli.Context, cluster cluster.Cluster, server *api.Server, discovery discovery.Discovery, addr string, leaderTTL time.Duration, tlsConfig *tls.Config) {
|
||||||
kvDiscovery, ok := discovery.(*kvdiscovery.Discovery)
|
kvDiscovery, ok := discovery.(*kvdiscovery.Discovery)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -222,7 +236,7 @@ func manage(c *cli.Context) {
|
||||||
if uri == "" {
|
if uri == "" {
|
||||||
log.Fatalf("discovery required to manage a cluster. See '%s manage --help'.", c.App.Name)
|
log.Fatalf("discovery required to manage a cluster. See '%s manage --help'.", c.App.Name)
|
||||||
}
|
}
|
||||||
discovery := createDiscovery(uri, c)
|
discovery := createDiscovery(uri, c, c.StringSlice("discovery-opt"))
|
||||||
s, err := strategy.New(c.String("strategy"))
|
s, err := strategy.New(c.String("strategy"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,8 @@ func (e Entries) Diff(cmp Entries) (Entries, Entries) {
|
||||||
// The Discovery interface is implemented by Discovery backends which
|
// The Discovery interface is implemented by Discovery backends which
|
||||||
// manage swarm host entries.
|
// manage swarm host entries.
|
||||||
type Discovery interface {
|
type Discovery interface {
|
||||||
// Initialize the discovery with URIs, a heartbeat and a ttl.
|
// Initialize the discovery with URIs, a heartbeat, a ttl and optional settings.
|
||||||
Initialize(string, time.Duration, time.Duration) error
|
Initialize(string, time.Duration, time.Duration, map[string]string) error
|
||||||
|
|
||||||
// Watch the discovery for entry changes.
|
// Watch the discovery for entry changes.
|
||||||
// Returns a channel that will receive changes or an error.
|
// Returns a channel that will receive changes or an error.
|
||||||
|
|
@ -133,12 +133,12 @@ func parse(rawurl string) (string, string) {
|
||||||
|
|
||||||
// New returns a new Discovery given a URL, heartbeat and ttl settings.
|
// New returns a new Discovery given a URL, heartbeat and ttl settings.
|
||||||
// Returns an error if the URL scheme is not supported.
|
// Returns an error if the URL scheme is not supported.
|
||||||
func New(rawurl string, heartbeat time.Duration, ttl time.Duration) (Discovery, error) {
|
func New(rawurl string, heartbeat time.Duration, ttl time.Duration, discoveryOpt map[string]string) (Discovery, error) {
|
||||||
scheme, uri := parse(rawurl)
|
scheme, uri := parse(rawurl)
|
||||||
|
|
||||||
if discovery, exists := discoveries[scheme]; exists {
|
if discovery, exists := discoveries[scheme]; exists {
|
||||||
log.WithFields(log.Fields{"name": scheme, "uri": uri}).Debug("Initializing discovery service")
|
log.WithFields(log.Fields{"name": scheme, "uri": uri}).Debug("Initializing discovery service")
|
||||||
err := discovery.Initialize(uri, heartbeat, ttl)
|
err := discovery.Initialize(uri, heartbeat, ttl, discoveryOpt)
|
||||||
return discovery, err
|
return discovery, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func Init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize is exported
|
// Initialize is exported
|
||||||
func (s *Discovery) Initialize(path string, heartbeat time.Duration, ttl time.Duration) error {
|
func (s *Discovery) Initialize(path string, heartbeat time.Duration, ttl time.Duration, _ map[string]string) error {
|
||||||
s.path = path
|
s.path = path
|
||||||
s.heartbeat = heartbeat
|
s.heartbeat = heartbeat
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ import (
|
||||||
|
|
||||||
func TestInitialize(t *testing.T) {
|
func TestInitialize(t *testing.T) {
|
||||||
d := &Discovery{}
|
d := &Discovery{}
|
||||||
d.Initialize("/path/to/file", 1000, 0)
|
d.Initialize("/path/to/file", 1000, 0, nil)
|
||||||
assert.Equal(t, d.path, "/path/to/file")
|
assert.Equal(t, d.path, "/path/to/file")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
d, err := discovery.New("file:///path/to/file", 0, 0)
|
d, err := discovery.New("file:///path/to/file", 0, 0, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, d.(*Discovery).path, "/path/to/file")
|
assert.Equal(t, d.(*Discovery).path, "/path/to/file")
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +73,7 @@ func TestWatch(t *testing.T) {
|
||||||
|
|
||||||
// Set up file discovery.
|
// Set up file discovery.
|
||||||
d := &Discovery{}
|
d := &Discovery{}
|
||||||
d.Initialize(tmp.Name(), 1000, 0)
|
d.Initialize(tmp.Name(), 1000, 0, nil)
|
||||||
stopCh := make(chan struct{})
|
stopCh := make(chan struct{})
|
||||||
ch, errCh := d.Watch(stopCh)
|
ch, errCh := d.Watch(stopCh)
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ func TestWatch(t *testing.T) {
|
||||||
assert.Error(t, <-errCh)
|
assert.Error(t, <-errCh)
|
||||||
// We have to drain the error channel otherwise Watch will get stuck.
|
// We have to drain the error channel otherwise Watch will get stuck.
|
||||||
go func() {
|
go func() {
|
||||||
for _ = range errCh {
|
for range errCh {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/pkg/tlsconfig"
|
||||||
"github.com/docker/libkv"
|
"github.com/docker/libkv"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
"github.com/docker/libkv/store/consul"
|
"github.com/docker/libkv/store/consul"
|
||||||
|
|
@ -47,7 +48,7 @@ func Init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize is exported
|
// 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, discoveryOpt map[string]string) error {
|
||||||
var (
|
var (
|
||||||
parts = strings.SplitN(uris, "/", 2)
|
parts = strings.SplitN(uris, "/", 2)
|
||||||
addrs = strings.Split(parts[0], ",")
|
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.ttl = ttl
|
||||||
s.path = path.Join(s.prefix, discoveryPath)
|
s.path = path.Join(s.prefix, discoveryPath)
|
||||||
|
|
||||||
|
var config *store.Config
|
||||||
|
if discoveryOpt["kv.cacertfile"] != "" && discoveryOpt["kv.certfile"] != "" && discoveryOpt["kv.keyfile"] != "" {
|
||||||
|
log.Debug("Initializing discovery with TLS")
|
||||||
|
tlsConfig, err := tlsconfig.Client(tlsconfig.Options{
|
||||||
|
CAFile: discoveryOpt["kv.cacertfile"],
|
||||||
|
CertFile: discoveryOpt["kv.certfile"],
|
||||||
|
KeyFile: discoveryOpt["kv.keyfile"],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config = &store.Config{
|
||||||
|
// Set ClientTLS to trigger https (bug in libkv/etcd)
|
||||||
|
ClientTLS: &store.ClientTLSConfig{
|
||||||
|
CACertFile: discoveryOpt["kv.cacertfile"],
|
||||||
|
CertFile: discoveryOpt["kv.certfile"],
|
||||||
|
KeyFile: discoveryOpt["kv.keyfile"],
|
||||||
|
},
|
||||||
|
// The actual TLS config that will be used
|
||||||
|
TLS: tlsConfig,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debug("Initializing discovery without TLS")
|
||||||
|
}
|
||||||
|
|
||||||
// Creates a new store, will ignore options given
|
// Creates a new store, will ignore options given
|
||||||
// if not supported by the chosen store
|
// if not supported by the chosen store
|
||||||
s.store, err = libkv.NewStore(s.backend, addrs, &store.Config{})
|
s.store, err = libkv.NewStore(s.backend, addrs, config)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@ package kv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/libkv"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
libkvmock "github.com/docker/libkv/store/mock"
|
libkvmock "github.com/docker/libkv/store/mock"
|
||||||
"github.com/docker/swarm/discovery"
|
"github.com/docker/swarm/discovery"
|
||||||
|
|
@ -19,7 +22,7 @@ func TestInitialize(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
d := &Discovery{backend: store.CONSUL}
|
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
|
d.store = storeMock
|
||||||
|
|
||||||
s := d.store.(*libkvmock.Mock)
|
s := d.store.(*libkvmock.Mock)
|
||||||
|
|
@ -32,7 +35,7 @@ func TestInitialize(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
d = &Discovery{backend: store.CONSUL}
|
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
|
d.store = storeMock
|
||||||
|
|
||||||
s = d.store.(*libkvmock.Mock)
|
s = d.store.(*libkvmock.Mock)
|
||||||
|
|
@ -45,7 +48,7 @@ func TestInitialize(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
d = &Discovery{backend: store.CONSUL}
|
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
|
d.store = storeMock
|
||||||
|
|
||||||
s = d.store.(*libkvmock.Mock)
|
s = d.store.(*libkvmock.Mock)
|
||||||
|
|
@ -57,13 +60,87 @@ func TestInitialize(t *testing.T) {
|
||||||
assert.Equal(t, d.path, "path/"+discoveryPath)
|
assert.Equal(t, d.path, "path/"+discoveryPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInitializeWithCerts(t *testing.T) {
|
||||||
|
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")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(certFile.Name())
|
||||||
|
certFile.Write([]byte(cert))
|
||||||
|
certFile.Close()
|
||||||
|
keyFile, err := ioutil.TempFile("", "key")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(keyFile.Name())
|
||||||
|
keyFile.Write([]byte(key))
|
||||||
|
keyFile.Close()
|
||||||
|
|
||||||
|
libkv.AddStore("mock", libkvmock.New)
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
s := d.store.(*libkvmock.Mock)
|
||||||
|
assert.Equal(t, s.Options.ClientTLS.CACertFile, certFile.Name())
|
||||||
|
assert.Equal(t, s.Options.ClientTLS.CertFile, certFile.Name())
|
||||||
|
assert.Equal(t, s.Options.ClientTLS.KeyFile, keyFile.Name())
|
||||||
|
}
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
func TestWatch(t *testing.T) {
|
||||||
storeMock, err := libkvmock.New([]string{"127.0.0.1:1234"}, nil)
|
storeMock, err := libkvmock.New([]string{"127.0.0.1:1234"}, nil)
|
||||||
assert.NotNil(t, storeMock)
|
assert.NotNil(t, storeMock)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
d := &Discovery{backend: store.CONSUL}
|
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
|
d.store = storeMock
|
||||||
|
|
||||||
s := d.store.(*libkvmock.Mock)
|
s := d.store.(*libkvmock.Mock)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ func Init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize is exported
|
// Initialize is exported
|
||||||
func (s *Discovery) Initialize(uris string, _ time.Duration, _ time.Duration) error {
|
func (s *Discovery) Initialize(uris string, _ time.Duration, _ time.Duration, _ map[string]string) error {
|
||||||
for _, input := range strings.Split(uris, ",") {
|
for _, input := range strings.Split(uris, ",") {
|
||||||
for _, ip := range discovery.Generate(input) {
|
for _, ip := range discovery.Generate(input) {
|
||||||
entry, err := discovery.NewEntry(ip)
|
entry, err := discovery.NewEntry(ip)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
func TestInitialize(t *testing.T) {
|
func TestInitialize(t *testing.T) {
|
||||||
d := &Discovery{}
|
d := &Discovery{}
|
||||||
d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0)
|
d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0, nil)
|
||||||
assert.Equal(t, len(d.entries), 2)
|
assert.Equal(t, len(d.entries), 2)
|
||||||
assert.Equal(t, d.entries[0].String(), "1.1.1.1:1111")
|
assert.Equal(t, d.entries[0].String(), "1.1.1.1:1111")
|
||||||
assert.Equal(t, d.entries[1].String(), "2.2.2.2:2222")
|
assert.Equal(t, d.entries[1].String(), "2.2.2.2:2222")
|
||||||
|
|
@ -17,7 +17,7 @@ func TestInitialize(t *testing.T) {
|
||||||
|
|
||||||
func TestInitializeWithPattern(t *testing.T) {
|
func TestInitializeWithPattern(t *testing.T) {
|
||||||
d := &Discovery{}
|
d := &Discovery{}
|
||||||
d.Initialize("1.1.1.[1:2]:1111,2.2.2.[2:4]:2222", 0, 0)
|
d.Initialize("1.1.1.[1:2]:1111,2.2.2.[2:4]:2222", 0, 0, nil)
|
||||||
assert.Equal(t, len(d.entries), 5)
|
assert.Equal(t, len(d.entries), 5)
|
||||||
assert.Equal(t, d.entries[0].String(), "1.1.1.1:1111")
|
assert.Equal(t, d.entries[0].String(), "1.1.1.1:1111")
|
||||||
assert.Equal(t, d.entries[1].String(), "1.1.1.2:1111")
|
assert.Equal(t, d.entries[1].String(), "1.1.1.2:1111")
|
||||||
|
|
@ -28,7 +28,7 @@ func TestInitializeWithPattern(t *testing.T) {
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
func TestWatch(t *testing.T) {
|
||||||
d := &Discovery{}
|
d := &Discovery{}
|
||||||
d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0)
|
d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0, nil)
|
||||||
expected := discovery.Entries{
|
expected := discovery.Entries{
|
||||||
&discovery.Entry{Host: "1.1.1.1", Port: "1111"},
|
&discovery.Entry{Host: "1.1.1.1", Port: "1111"},
|
||||||
&discovery.Entry{Host: "2.2.2.2", Port: "2222"},
|
&discovery.Entry{Host: "2.2.2.2", Port: "2222"},
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func Init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize is exported
|
// Initialize is exported
|
||||||
func (s *Discovery) Initialize(urltoken string, heartbeat time.Duration, ttl time.Duration) error {
|
func (s *Discovery) Initialize(urltoken string, heartbeat time.Duration, ttl time.Duration, _ map[string]string) error {
|
||||||
if i := strings.LastIndex(urltoken, "/"); i != -1 {
|
if i := strings.LastIndex(urltoken, "/"); i != -1 {
|
||||||
s.url = "https://" + urltoken[:i]
|
s.url = "https://" + urltoken[:i]
|
||||||
s.token = urltoken[i+1:]
|
s.token = urltoken[i+1:]
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,17 @@ import (
|
||||||
|
|
||||||
func TestInitialize(t *testing.T) {
|
func TestInitialize(t *testing.T) {
|
||||||
discovery := &Discovery{}
|
discovery := &Discovery{}
|
||||||
err := discovery.Initialize("token", 0, 0)
|
err := discovery.Initialize("token", 0, 0, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, discovery.token, "token")
|
assert.Equal(t, discovery.token, "token")
|
||||||
assert.Equal(t, discovery.url, DiscoveryURL)
|
assert.Equal(t, discovery.url, DiscoveryURL)
|
||||||
|
|
||||||
err = discovery.Initialize("custom/path/token", 0, 0)
|
err = discovery.Initialize("custom/path/token", 0, 0, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, discovery.token, "token")
|
assert.Equal(t, discovery.token, "token")
|
||||||
assert.Equal(t, discovery.url, "https://custom/path")
|
assert.Equal(t, discovery.url, "https://custom/path")
|
||||||
|
|
||||||
err = discovery.Initialize("", 0, 0)
|
err = discovery.Initialize("", 0, 0, nil)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue