From 056e0fe2d94425ec781ef0a3a23bd04e7e351bc3 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 24 Jun 2016 09:58:42 +0200 Subject: [PATCH 1/6] Use KvStores as global config source --- configuration.go | 4 +- glide.lock | 8 ++- glide.yaml | 2 +- integration/consul_test.go | 117 ++++++++++++++++++++++++++++++++++++- integration/etcd_test.go | 117 ++++++++++++++++++++++++++++++++++++- provider/boltdb.go | 2 +- provider/consul.go | 2 +- provider/consul_catalog.go | 10 ++-- provider/docker.go | 2 +- provider/etcd.go | 2 +- provider/file.go | 2 +- provider/kubernetes.go | 2 +- provider/kv.go | 12 ++-- provider/zk.go | 2 +- traefik.go | 72 +++++++++++++++++++++++ 15 files changed, 330 insertions(+), 26 deletions(-) diff --git a/configuration.go b/configuration.go index a055bba0f..21e95d475 100644 --- a/configuration.go +++ b/configuration.go @@ -13,8 +13,8 @@ import ( // TraefikConfiguration holds GlobalConfiguration and other stuff type TraefikConfiguration struct { - GlobalConfiguration - ConfigFile string `short:"c" description:"Configuration file to use (TOML)."` + GlobalConfiguration `mapstructure:",squash"` + ConfigFile string `short:"c" description:"Configuration file to use (TOML)."` } // GlobalConfiguration holds global configuration (with providers, etc.). diff --git a/glide.lock b/glide.lock index e7694a0e2..d2ba4c3c5 100644 --- a/glide.lock +++ b/glide.lock @@ -22,13 +22,13 @@ imports: - name: github.com/containous/mux version: a819b77bba13f0c0cbe36e437bc2e948411b3996 - name: github.com/containous/staert - version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602 + version: 436a2f21e5e28c7c761130720befe05381db0765 - name: github.com/coreos/etcd version: c400d05d0aa73e21e431c16145e558d624098018 subpackages: + - client - Godeps/_workspace/src/github.com/ugorji/go/codec - Godeps/_workspace/src/golang.org/x/net/context - - client - pkg/pathutil - pkg/types - name: github.com/davecgh/go-spew @@ -134,7 +134,9 @@ imports: - name: github.com/Microsoft/go-winio version: ce2922f643c8fd76b46cadc7f404a06282678b34 - name: github.com/miekg/dns - version: 5d001d020961ae1c184f9f8152fdc73810481677 + version: 48ab6605c66ac797e07f615101c3e9e10e932b66 +- name: github.com/mitchellh/mapstructure + version: d2dd0262208475919e1a362f675cfc0e7c10e905 - name: github.com/moul/http2curl version: b1479103caacaa39319f75e7f57fc545287fca0d - name: github.com/ogier/pflag diff --git a/glide.yaml b/glide.yaml index 36e73fa4b..5235bcb23 100644 --- a/glide.yaml +++ b/glide.yaml @@ -21,7 +21,7 @@ import: - stream - utils - package: github.com/containous/staert - version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602 + version: 436a2f21e5e28c7c761130720befe05381db0765 - package: github.com/docker/engine-api version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb subpackages: diff --git a/integration/consul_test.go b/integration/consul_test.go index 0c2c97058..9002a778c 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -24,7 +24,7 @@ type ConsulSuite struct { kv store.Store } -func (s *ConsulSuite) SetUpSuite(c *check.C) { +func (s *ConsulSuite) SetUpTest(c *check.C) { s.createComposeProject(c, "consul") s.composeProject.Start(c) @@ -52,6 +52,15 @@ func (s *ConsulSuite) SetUpSuite(c *check.C) { c.Assert(err, checker.IsNil) } +func (s *ConsulSuite) TearDownTest(c *check.C) { + // shutdown and delete compose project + if s.composeProject != nil { + s.composeProject.Stop(c) + } +} + +func (s *ConsulSuite) TearDownSuite(c *check.C) {} + func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost}) @@ -190,3 +199,109 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(resp.StatusCode, checker.Equals, 404) } + +func (s *ConsulSuite) TestGlobalConfiguration(c *check.C) { + consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + err := s.kv.Put("traefik/entrypoints/http/address", []byte(":8001"), nil) + c.Assert(err, checker.IsNil) + + // wait for consul + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("traefik/entrypoints/http/address") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + // start traefik + cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--consul", "--consul.endpoint="+consulHost+":8500") + // cmd.Stdout = os.Stdout + // cmd.Stderr = os.Stderr + + err = cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + whoami1 := s.composeProject.Container(c, "whoami1") + whoami2 := s.composeProject.Container(c, "whoami2") + whoami3 := s.composeProject.Container(c, "whoami3") + whoami4 := s.composeProject.Container(c, "whoami4") + + backend1 := map[string]string{ + "traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5", + "traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80", + "traefik/backends/backend1/servers/server1/weight": "10", + "traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80", + "traefik/backends/backend1/servers/server2/weight": "1", + } + backend2 := map[string]string{ + "traefik/backends/backend2/loadbalancer/method": "drr", + "traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80", + "traefik/backends/backend2/servers/server1/weight": "1", + "traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80", + "traefik/backends/backend2/servers/server2/weight": "2", + } + frontend1 := map[string]string{ + "traefik/frontends/frontend1/backend": "backend2", + "traefik/frontends/frontend1/entrypoints": "http", + "traefik/frontends/frontend1/priority": "1", + "traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost", + } + frontend2 := map[string]string{ + "traefik/frontends/frontend2/backend": "backend1", + "traefik/frontends/frontend2/entrypoints": "http", + "traefik/frontends/frontend2/priority": "10", + "traefik/frontends/frontend2/routes/test_2/rule": "Path:/test", + } + for key, value := range backend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range backend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + + // wait for consul + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("traefik/frontends/frontend2/routes/test_2/rule") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + // wait for traefik + err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error { + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if !strings.Contains(string(body), "Path:/test") { + return errors.New("Incorrect traefik config") + } + return nil + }) + c.Assert(err, checker.IsNil) + + //check + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8001/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.localhost" + response, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(response.StatusCode, checker.Equals, 200) +} diff --git a/integration/etcd_test.go b/integration/etcd_test.go index 316d989b3..09a329e37 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -25,7 +25,7 @@ type EtcdSuite struct { kv store.Store } -func (s *EtcdSuite) SetUpSuite(c *check.C) { +func (s *EtcdSuite) SetUpTest(c *check.C) { s.createComposeProject(c, "etcd") s.composeProject.Start(c) @@ -54,6 +54,15 @@ func (s *EtcdSuite) SetUpSuite(c *check.C) { c.Assert(err, checker.IsNil) } +func (s *EtcdSuite) TearDownTest(c *check.C) { + // shutdown and delete compose project + if s.composeProject != nil { + s.composeProject.Stop(c) + } +} + +func (s *EtcdSuite) TearDownSuite(c *check.C) {} + func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) { etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost}) @@ -193,3 +202,109 @@ func (s *EtcdSuite) TestNominalConfiguration(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(resp.StatusCode, checker.Equals, 404) } + +func (s *EtcdSuite) TestGlobalConfiguration(c *check.C) { + etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + err := s.kv.Put("/traefik/entrypoints/http/address", []byte(":8001"), nil) + c.Assert(err, checker.IsNil) + + // wait for etcd + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("/traefik/entrypoints/http/address") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + // start traefik + cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--etcd", "--etcd.endpoint="+etcdHost+":4001") + // cmd.Stdout = os.Stdout + // cmd.Stderr = os.Stderr + + err = cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + whoami1 := s.composeProject.Container(c, "whoami1") + whoami2 := s.composeProject.Container(c, "whoami2") + whoami3 := s.composeProject.Container(c, "whoami3") + whoami4 := s.composeProject.Container(c, "whoami4") + + backend1 := map[string]string{ + "/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5", + "/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend1/servers/server1/weight": "10", + "/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend1/servers/server2/weight": "1", + } + backend2 := map[string]string{ + "/traefik/backends/backend2/loadbalancer/method": "drr", + "/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend2/servers/server1/weight": "1", + "/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend2/servers/server2/weight": "2", + } + frontend1 := map[string]string{ + "/traefik/frontends/frontend1/backend": "backend2", + "/traefik/frontends/frontend1/entrypoints": "http", + "/traefik/frontends/frontend1/priority": "1", + "/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost", + } + frontend2 := map[string]string{ + "/traefik/frontends/frontend2/backend": "backend1", + "/traefik/frontends/frontend2/entrypoints": "http", + "/traefik/frontends/frontend2/priority": "10", + "/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test", + } + for key, value := range backend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range backend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + + // wait for etcd + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + // wait for traefik + err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error { + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if !strings.Contains(string(body), "Path:/test") { + return errors.New("Incorrect traefik config") + } + return nil + }) + c.Assert(err, checker.IsNil) + + //check + client := &http.Client{} + req, err := http.NewRequest("GET", "http://127.0.0.1:8001/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.localhost" + response, err := client.Do(req) + + c.Assert(err, checker.IsNil) + c.Assert(response.StatusCode, checker.Equals, 200) +} diff --git a/provider/boltdb.go b/provider/boltdb.go index 574956ace..285206955 100644 --- a/provider/boltdb.go +++ b/provider/boltdb.go @@ -9,7 +9,7 @@ import ( // BoltDb holds configurations of the BoltDb provider. type BoltDb struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/consul.go b/provider/consul.go index f936e79f3..b8967e61c 100644 --- a/provider/consul.go +++ b/provider/consul.go @@ -9,7 +9,7 @@ import ( // Consul holds configurations of the Consul provider. type Consul struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/consul_catalog.go b/provider/consul_catalog.go index 8622b66e0..4ff332517 100644 --- a/provider/consul_catalog.go +++ b/provider/consul_catalog.go @@ -25,11 +25,11 @@ const ( // ConsulCatalog holds configurations of the Consul catalog provider. type ConsulCatalog struct { - BaseProvider - Endpoint string `description:"Consul server endpoint"` - Domain string `description:"Default domain used"` - client *api.Client - Prefix string + BaseProvider `mapstructure:",squash"` + Endpoint string `description:"Consul server endpoint"` + Domain string `description:"Default domain used"` + client *api.Client + Prefix string } type serviceUpdate struct { diff --git a/provider/docker.go b/provider/docker.go index f71aa8892..a8655c8ed 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -29,7 +29,7 @@ const DockerAPIVersion string = "1.21" // Docker holds configurations of the Docker provider. type Docker struct { - BaseProvider + BaseProvider `mapstructure:",squash"` Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"` Domain string `description:"Default domain used"` TLS *DockerTLS `description:"Enable Docker TLS support"` diff --git a/provider/etcd.go b/provider/etcd.go index 934e0f245..6c41757fa 100644 --- a/provider/etcd.go +++ b/provider/etcd.go @@ -9,7 +9,7 @@ import ( // Etcd holds configurations of the Etcd provider. type Etcd struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/file.go b/provider/file.go index 07bcbd02f..05cb89eee 100644 --- a/provider/file.go +++ b/provider/file.go @@ -14,7 +14,7 @@ import ( // File holds configurations of the File provider. type File struct { - BaseProvider + BaseProvider `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 0535b9570..b4de0a1fd 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -51,7 +51,7 @@ func (ns *Namespaces) SetValue(val interface{}) { // Kubernetes holds configurations of the Kubernetes provider. type Kubernetes struct { - BaseProvider + BaseProvider `mapstructure:",squash"` Endpoint string `description:"Kubernetes server endpoint"` DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"` Namespaces Namespaces `description:"Kubernetes namespaces"` diff --git a/provider/kv.go b/provider/kv.go index 728e8a2f3..3b7f6418c 100644 --- a/provider/kv.go +++ b/provider/kv.go @@ -22,12 +22,12 @@ import ( // Kv holds common configurations of key-value providers. type Kv struct { - BaseProvider - Endpoint string `description:"Comma sepparated server endpoints"` - Prefix string `description:"Prefix used for KV store"` - TLS *KvTLS `description:"Enable TLS support"` - storeType store.Backend - kvclient store.Store + BaseProvider `mapstructure:",squash"` + Endpoint string `description:"Comma sepparated server endpoints"` + Prefix string `description:"Prefix used for KV store"` + TLS *KvTLS `description:"Enable TLS support"` + storeType store.Backend + kvclient store.Store } // KvTLS holds TLS specific configurations diff --git a/provider/zk.go b/provider/zk.go index 06eb65000..416d62844 100644 --- a/provider/zk.go +++ b/provider/zk.go @@ -9,7 +9,7 @@ import ( // Zookepper holds configurations of the Zookepper provider. type Zookepper struct { - Kv + Kv `mapstructure:",squash"` } // Provide allows the provider to provide configurations to traefik diff --git a/traefik.go b/traefik.go index a471796ff..89c9960fb 100644 --- a/traefik.go +++ b/traefik.go @@ -10,6 +10,11 @@ import ( "github.com/containous/traefik/middlewares" "github.com/containous/traefik/provider" "github.com/containous/traefik/types" + "github.com/docker/libkv/store" + "github.com/docker/libkv/store/boltdb" + "github.com/docker/libkv/store/consul" + "github.com/docker/libkv/store/etcd" + "github.com/docker/libkv/store/zookeeper" fmtlog "log" "net/http" "os" @@ -111,6 +116,73 @@ Complete documentation is available at https://traefik.io`, traefikConfiguration.ConfigFile = toml.ConfigFileUsed() + var kv *staert.KvSource + var err error + if traefikConfiguration.Consul != nil { + //init KvSource + consul.Register() + kv, err = staert.NewKvSource( + store.CONSUL, + strings.Split(traefikConfiguration.Consul.Endpoint, ","), + nil, + strings.TrimPrefix(traefikConfiguration.Consul.Prefix, "/"), // TrimPrefix should be done in https://github.com/docker/libkv/blob/master/store/consul/consul.go#L113 : IDK why it doen't work + ) + } else if traefikConfiguration.Etcd != nil { + //init KvSource + etcd.Register() + kv, err = staert.NewKvSource( + store.ETCD, + strings.Split(traefikConfiguration.Etcd.Endpoint, ","), + nil, + traefikConfiguration.Etcd.Prefix, + ) + } else if traefikConfiguration.Zookeeper != nil { + //init KvSource + zookeeper.Register() + kv, err = staert.NewKvSource( + store.ZK, + strings.Split(traefikConfiguration.Zookeeper.Endpoint, ","), + nil, + traefikConfiguration.Zookeeper.Prefix, + ) + } else if traefikConfiguration.Boltdb != nil { + //init KvSource + boltdb.Register() + kv, err = staert.NewKvSource( + store.BOLTDB, + strings.Split(traefikConfiguration.Boltdb.Endpoint, ","), + nil, + traefikConfiguration.Boltdb.Prefix, + ) + } + if err != nil { + fmtlog.Println(err) + os.Exit(-1) + } + + // TO DELETE : Used once to fill the kv store + // if kv != nil { + // fmtlog.Println("Try to store global configuration in consul store") + // if err := kv.StoreConfig(traefikConfiguration); err != nil { + // fmtlog.Println(fmt.Errorf("Error : %s", err)) + // os.Exit(-1) + // } else { + // fmtlog.Println("It seems okay :)") + // jsonConf, _ := json.Marshal(traefikConfiguration) + // fmtlog.Printf("Global configuration loaded %s", string(jsonConf)) + // os.Exit(0) + // } + // } + + //TODO : log warning if many KvStore or set priority + if kv != nil { + fmtlog.Println("KV Store found") + s.AddSource(kv) + if _, err := s.LoadConfig(); err != nil { + fmtlog.Println(fmt.Errorf("Error : %s", err)) + } + } + if err := s.Run(); err != nil { fmtlog.Println(err) os.Exit(-1) From 7ada80b61903551076ec25f8e7e20c89ad7983a8 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 27 Jun 2016 12:19:14 +0200 Subject: [PATCH 2/6] Certificate can contain path or file contents Signed-off-by: Martin --- configuration.go | 40 +++++++++++++ glide.lock | 10 ++-- integration/etcd_test.go | 124 +++++++++++++++++++++++++++++++++++++++ server.go | 11 +--- traefik.go | 1 - 5 files changed, 172 insertions(+), 14 deletions(-) diff --git a/configuration.go b/configuration.go index 21e95d475..cdbdca5da 100644 --- a/configuration.go +++ b/configuration.go @@ -1,11 +1,13 @@ package main import ( + "crypto/tls" "errors" "fmt" "github.com/containous/traefik/acme" "github.com/containous/traefik/provider" "github.com/containous/traefik/types" + "os" "regexp" "strings" "time" @@ -179,6 +181,43 @@ type TLS struct { // Certificates defines traefik certificates type type Certificates []Certificate +//CreateTLSConfig creates a TLS config from Certificate structures +func (certs *Certificates) CreateTLSConfig() (*tls.Config, error) { + config := &tls.Config{} + config.Certificates = []tls.Certificate{} + certsSlice := []Certificate(*certs) + for _, v := range certsSlice { + isAPath := false + _, errCert := os.Stat(v.CertFile) + _, errKey := os.Stat(v.KeyFile) + if errCert == nil { + if errKey == nil { + isAPath = true + } else { + return nil, fmt.Errorf("Bad TLS Certificate KeyFile format. Expected a path.") + } + } else if errKey == nil { + return nil, fmt.Errorf("Bad TLS Certificate KeyFile format. Expected a path.") + } + + cert := tls.Certificate{} + var err error + if isAPath { + cert, err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile) + if err != nil { + return nil, err + } + } else { + cert, err = tls.X509KeyPair([]byte(v.CertFile), []byte(v.KeyFile)) + if err != nil { + return nil, err + } + } + config.Certificates = append(config.Certificates, cert) + } + return config, nil +} + // String is the method to format the flag's value, part of the flag.Value interface. // The String method's output will be used in diagnostics. func (certs *Certificates) String() string { @@ -209,6 +248,7 @@ func (certs *Certificates) Type() string { } // Certificate holds a SSL cert/key pair +// May can contain either path or file contents type Certificate struct { CertFile string KeyFile string diff --git a/glide.lock b/glide.lock index d2ba4c3c5..ee4e37f06 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 22c20a7d7419e9624267d7f0041cd8ad87afc876d2738fa559527c74f9917c3a -updated: 2016-07-05T14:48:30.023831407+02:00 +hash: cf8f42aae193bd26b7670157094e6b11590443bc500f0a78243ee21bd3ed314e +updated: 2016-06-23T17:46:42.381770076+02:00 imports: - name: github.com/boltdb/bolt version: 3f7947a25d970e1e5f512276c14d5dcf731ccd5e @@ -36,7 +36,7 @@ imports: subpackages: - spew - name: github.com/docker/distribution - version: 4e17ab5d319ac5b70b2769442947567a83386fbc + version: edd7cb5249d0a45262b20bb58b838ecf4fb368bd subpackages: - reference - digest @@ -142,7 +142,7 @@ imports: - name: github.com/ogier/pflag version: 45c278ab3607870051a2ea9040bb85fcb8557481 - name: github.com/opencontainers/runc - version: 7221e387826c9918fa9fd6e7975baf4d30c8fa54 + version: 5dc3f3576efb5262bf582217e93f86c93944374d subpackages: - libcontainer/user - name: github.com/parnurzeal/gorequest @@ -219,7 +219,7 @@ imports: subpackages: - acme - name: golang.org/x/crypto - version: 0c565bf13221fb55497d7ae2bb95694db1fd1bff + version: f3241ce8505855877cc8a9717bd61a0f7c4ea83c subpackages: - ocsp - name: golang.org/x/net diff --git a/integration/etcd_test.go b/integration/etcd_test.go index 09a329e37..51d27a2ef 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -8,6 +8,7 @@ import ( checker "github.com/vdemeester/shakers" + "crypto/tls" "errors" "fmt" "github.com/containous/traefik/integration/utils" @@ -308,3 +309,126 @@ func (s *EtcdSuite) TestGlobalConfiguration(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(response.StatusCode, checker.Equals, 200) } + +//TODO : TestCertificatesContents +func (s *EtcdSuite) TestCertificatesContentstWithSNIConfigHandshake(c *check.C) { + etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + // start traefik + cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--etcd", "--etcd.endpoint="+etcdHost+":4001") + // cmd.Stdout = os.Stdout + // cmd.Stderr = os.Stderr + + whoami1 := s.composeProject.Container(c, "whoami1") + whoami2 := s.composeProject.Container(c, "whoami2") + whoami3 := s.composeProject.Container(c, "whoami3") + whoami4 := s.composeProject.Container(c, "whoami4") + + //Copy the contents of the certificate files into ETCD + snitestComCert, err := ioutil.ReadFile("fixtures/https/snitest.com.cert") + c.Assert(err, checker.IsNil) + snitestComKey, err := ioutil.ReadFile("fixtures/https/snitest.com.key") + c.Assert(err, checker.IsNil) + snitestOrgCert, err := ioutil.ReadFile("fixtures/https/snitest.org.cert") + c.Assert(err, checker.IsNil) + snitestOrgKey, err := ioutil.ReadFile("fixtures/https/snitest.org.key") + c.Assert(err, checker.IsNil) + + globalConfig := map[string]string{ + "/traefik/entrypoints/https/address": ":4443", + "/traefik/entrypoints/https/tls/certificates/0/certfile": string(snitestComCert), + "/traefik/entrypoints/https/tls/certificates/0/keyfile": string(snitestComKey), + "/traefik/entrypoints/https/tls/certificates/1/certfile": string(snitestOrgCert), + "/traefik/entrypoints/https/tls/certificates/1/keyfile": string(snitestOrgKey), + "/traefik/defaultentrypoints/0": "https", + } + + backend1 := map[string]string{ + "/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5", + "/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend1/servers/server1/weight": "10", + "/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend1/servers/server2/weight": "1", + } + backend2 := map[string]string{ + "/traefik/backends/backend2/loadbalancer/method": "drr", + "/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend2/servers/server1/weight": "1", + "/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80", + "/traefik/backends/backend2/servers/server2/weight": "2", + } + frontend1 := map[string]string{ + "/traefik/frontends/frontend1/backend": "backend2", + "/traefik/frontends/frontend1/entrypoints": "http", + "/traefik/frontends/frontend1/priority": "1", + "/traefik/frontends/frontend1/routes/test_1/rule": "Host:snitest.com", + } + frontend2 := map[string]string{ + "/traefik/frontends/frontend2/backend": "backend1", + "/traefik/frontends/frontend2/entrypoints": "http", + "/traefik/frontends/frontend2/priority": "10", + "/traefik/frontends/frontend2/routes/test_2/rule": "Host:snitest.org", + } + for key, value := range globalConfig { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range backend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range backend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend1 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + for key, value := range frontend2 { + err := s.kv.Put(key, []byte(value), nil) + c.Assert(err, checker.IsNil) + } + + // wait for etcd + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + err = cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + // wait for traefik + err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error { + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + if !strings.Contains(string(body), "Host:snitest.org") { + return errors.New("Incorrect traefik config") + } + return nil + }) + c.Assert(err, checker.IsNil) + + //check + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + ServerName: "snitest.com", + } + conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) + c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server")) + + defer conn.Close() + err = conn.Handshake() + c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error")) + + cs := conn.ConnectionState() + err = cs.PeerCertificates[0].VerifyHostname("snitest.com") + c.Assert(err, checker.IsNil, check.Commentf("certificate did not match SNI servername")) +} diff --git a/server.go b/server.go index 1a7be3d6c..4e83c0ac8 100644 --- a/server.go +++ b/server.go @@ -290,14 +290,9 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou return nil, nil } - config := &tls.Config{} - config.Certificates = []tls.Certificate{} - for _, v := range tlsOption.Certificates { - cert, err := tls.LoadX509KeyPair(v.CertFile, v.KeyFile) - if err != nil { - return nil, err - } - config.Certificates = append(config.Certificates, cert) + config, err := tlsOption.Certificates.CreateTLSConfig() + if err != nil { + return nil, err } if len(tlsOption.ClientCAFiles) > 0 { diff --git a/traefik.go b/traefik.go index 89c9960fb..49af4e1a4 100644 --- a/traefik.go +++ b/traefik.go @@ -176,7 +176,6 @@ Complete documentation is available at https://traefik.io`, //TODO : log warning if many KvStore or set priority if kv != nil { - fmtlog.Println("KV Store found") s.AddSource(kv) if _, err := s.LoadConfig(); err != nil { fmtlog.Println(fmt.Errorf("Error : %s", err)) From e26e0955b355bd6ab7c62afdc22b3a8975916a81 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 27 Jun 2016 16:14:56 +0200 Subject: [PATCH 3/6] add struct ClientTLS : supports either a paths to a file or directly the certificate --- integration/etcd_test.go | 1 - provider/docker.go | 27 +++++++---------- provider/kv.go | 41 ++++---------------------- provider/marathon.go | 19 +++++++----- provider/provider.go | 63 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 61 deletions(-) diff --git a/integration/etcd_test.go b/integration/etcd_test.go index 51d27a2ef..0b6b2d077 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -310,7 +310,6 @@ func (s *EtcdSuite) TestGlobalConfiguration(c *check.C) { c.Assert(response.StatusCode, checker.Equals, 200) } -//TODO : TestCertificatesContents func (s *EtcdSuite) TestCertificatesContentstWithSNIConfigHandshake(c *check.C) { etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress // start traefik diff --git a/provider/docker.go b/provider/docker.go index a8655c8ed..52f70ae28 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -10,6 +10,7 @@ import ( "golang.org/x/net/context" + "crypto/tls" "github.com/BurntSushi/ty/fun" log "github.com/Sirupsen/logrus" "github.com/cenkalti/backoff" @@ -20,7 +21,6 @@ import ( eventtypes "github.com/docker/engine-api/types/events" "github.com/docker/engine-api/types/filters" "github.com/docker/go-connections/sockets" - "github.com/docker/go-connections/tlsconfig" "github.com/vdemeester/docker-events" ) @@ -32,18 +32,10 @@ type Docker struct { BaseProvider `mapstructure:",squash"` Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"` Domain string `description:"Default domain used"` - TLS *DockerTLS `description:"Enable Docker TLS support"` + TLS *ClientTLS `description:"Enable Docker TLS support"` ExposedByDefault bool `description:"Expose containers by default"` } -// DockerTLS holds TLS specific configurations -type DockerTLS struct { - CA string `description:"TLS CA"` - Cert string `description:"TLS cert"` - Key string `description:"TLS key"` - InsecureSkipVerify bool `description:"TLS insecure skip verify"` -} - func (provider *Docker) createClient() (client.APIClient, error) { var httpClient *http.Client httpHeaders := map[string]string{ @@ -51,16 +43,16 @@ func (provider *Docker) createClient() (client.APIClient, error) { "User-Agent": "Traefik", } if provider.TLS != nil { - tlsOptions := tlsconfig.Options{ - CAFile: provider.TLS.CA, - CertFile: provider.TLS.Cert, - KeyFile: provider.TLS.Key, - InsecureSkipVerify: provider.TLS.InsecureSkipVerify, - } - config, err := tlsconfig.Client(tlsOptions) + config, err := provider.TLS.CreateTLSConfig() if err != nil { return nil, err } + // TO DELETE IF USELESS : default docker TLS Client config + config.MaxVersion = tls.VersionTLS12 + config.CipherSuites = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + } tr := &http.Transport{ TLSClientConfig: config, } @@ -74,6 +66,7 @@ func (provider *Docker) createClient() (client.APIClient, error) { httpClient = &http.Client{ Transport: tr, } + } return client.NewClient(provider.Endpoint, DockerAPIVersion, httpClient, httpHeaders) } diff --git a/provider/kv.go b/provider/kv.go index 3b7f6418c..f27a085fe 100644 --- a/provider/kv.go +++ b/provider/kv.go @@ -2,10 +2,7 @@ package provider import ( - "crypto/tls" - "crypto/x509" "fmt" - "io/ioutil" "strings" "text/template" "time" @@ -23,21 +20,13 @@ import ( // Kv holds common configurations of key-value providers. type Kv struct { BaseProvider `mapstructure:",squash"` - Endpoint string `description:"Comma sepparated server endpoints"` - Prefix string `description:"Prefix used for KV store"` - TLS *KvTLS `description:"Enable TLS support"` + Endpoint string `description:"Comma sepparated server endpoints"` + Prefix string `description:"Prefix used for KV store"` + TLS *ClientTLS `description:"Enable TLS support"` storeType store.Backend kvclient store.Store } -// KvTLS holds TLS specific configurations -type KvTLS struct { - CA string `description:"TLS CA"` - Cert string `description:"TLS cert"` - Key string `description:"TLS key"` - InsecureSkipVerify bool `description:"TLS insecure skip verify"` -} - func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error { operation := func() error { events, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{})) @@ -80,28 +69,10 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool * } if provider.TLS != nil { - caPool := x509.NewCertPool() - - if provider.TLS.CA != "" { - ca, err := ioutil.ReadFile(provider.TLS.CA) - - if err != nil { - return fmt.Errorf("Failed to read CA. %s", err) - } - - caPool.AppendCertsFromPEM(ca) - } - - cert, err := tls.LoadX509KeyPair(provider.TLS.Cert, provider.TLS.Key) - + var err error + storeConfig.TLS, err = provider.TLS.CreateTLSConfig() if err != nil { - return fmt.Errorf("Failed to load TLS keypair: %v", err) - } - - storeConfig.TLS = &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: caPool, - InsecureSkipVerify: provider.TLS.InsecureSkipVerify, + return err } } diff --git a/provider/marathon.go b/provider/marathon.go index 628dd69ad..52a7f7522 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -8,7 +8,6 @@ import ( "strings" "text/template" - "crypto/tls" "github.com/BurntSushi/ty/fun" log "github.com/Sirupsen/logrus" "github.com/cenkalti/backoff" @@ -22,13 +21,13 @@ import ( // Marathon holds configuration of the Marathon provider. type Marathon struct { BaseProvider - Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` - Domain string `description:"Default domain used"` - ExposedByDefault bool `description:"Expose Marathon apps by default"` - GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"` - DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"` + Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` + Domain string `description:"Default domain used"` + ExposedByDefault bool `description:"Expose Marathon apps by default"` + GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"` + DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"` + TLS *ClientTLS `description:"Enable Docker TLS support"` Basic *MarathonBasic - TLS *tls.Config marathonClient marathon.Marathon } @@ -58,9 +57,13 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, if len(provider.DCOSToken) > 0 { config.DCOSToken = provider.DCOSToken } + TLSConfig, err := provider.TLS.CreateTLSConfig() + if err != nil { + return err + } config.HTTPClient = &http.Client{ Transport: &http.Transport{ - TLSClientConfig: provider.TLS, + TLSClientConfig: TLSConfig, }, } client, err := marathon.NewClient(config) diff --git a/provider/provider.go b/provider/provider.go index 5f52988e0..28f75f564 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -7,10 +7,14 @@ import ( "text/template" "unicode" + "crypto/tls" + "crypto/x509" + "fmt" "github.com/BurntSushi/toml" "github.com/containous/traefik/autogen" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" + "os" ) // Provider defines methods of a provider. @@ -92,3 +96,62 @@ func normalize(name string) string { // get function return strings.Join(strings.FieldsFunc(name, fargs), "-") } + +// ClientTLS holds TLS specific configurations as client +// CA, Cert and Key can be either path or file contents +type ClientTLS struct { + CA string `description:"TLS CA"` + Cert string `description:"TLS cert"` + Key string `description:"TLS key"` + InsecureSkipVerify bool `description:"TLS insecure skip verify"` +} + +// CreateTLSConfig creates a TLS config from ClientTLS structures +func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) { + var err error + caPool := x509.NewCertPool() + // TODO : error if CA=="" || Cert=="" || Key=="" + if clientTLS.CA != "" { + var ca []byte + if _, errCA := os.Stat(clientTLS.CA); errCA == nil { + ca, err = ioutil.ReadFile(clientTLS.CA) + if err != nil { + return nil, fmt.Errorf("Failed to read CA. %s", err) + } + } else { + ca = []byte(clientTLS.CA) + } + caPool.AppendCertsFromPEM(ca) + } + + cert := tls.Certificate{} + _, errKeyIsFile := os.Stat(clientTLS.Key) + + if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil { + if errKeyIsFile == nil { + cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key) + if err != nil { + return nil, fmt.Errorf("Failed to load TLS keypair: %v", err) + } + } else { + return nil, fmt.Errorf("tls cert is a file, but tls key is not") + } + } else { + if errKeyIsFile != nil { + cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key)) + if err != nil { + return nil, fmt.Errorf("Failed to load TLS keypair: %v", err) + + } + } else { + return nil, fmt.Errorf("tls key is a file, but tls cert is not") + } + } + + TLSConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caPool, + InsecureSkipVerify: clientTLS.InsecureSkipVerify, + } + return TLSConfig, nil +} From b83fb525a8bca5fa1c950c3d438c4bf4d7646f7f Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 7 Jul 2016 17:25:42 +0200 Subject: [PATCH 4/6] Add TLS support for etcd and consul --- glide.lock | 14 ++-- integration/consul_test.go | 88 +++++++++++++++++++- integration/resources/compose/consul_tls.yml | 14 ++++ integration/resources/tls/ca.cert | 22 +++++ integration/resources/tls/consul.cert | 19 +++++ integration/resources/tls/consul.key | 16 ++++ integration/resources/tls/consul_config.json | 9 ++ traefik.go | 42 +++++----- 8 files changed, 197 insertions(+), 27 deletions(-) create mode 100644 integration/resources/compose/consul_tls.yml create mode 100644 integration/resources/tls/ca.cert create mode 100644 integration/resources/tls/consul.cert create mode 100644 integration/resources/tls/consul.key create mode 100644 integration/resources/tls/consul_config.json diff --git a/glide.lock b/glide.lock index ee4e37f06..ba9b6d010 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: cf8f42aae193bd26b7670157094e6b11590443bc500f0a78243ee21bd3ed314e -updated: 2016-06-23T17:46:42.381770076+02:00 +hash: 70ad4e576bc1fa845512cce6b4ade5c422ba4fb5bb0472b37e1d3a93f13809cd +updated: 2016-07-07T17:33:16.358775373+02:00 imports: - name: github.com/boltdb/bolt version: 3f7947a25d970e1e5f512276c14d5dcf731ccd5e @@ -36,7 +36,7 @@ imports: subpackages: - spew - name: github.com/docker/distribution - version: edd7cb5249d0a45262b20bb58b838ecf4fb368bd + version: 4e17ab5d319ac5b70b2769442947567a83386fbc subpackages: - reference - digest @@ -100,7 +100,7 @@ imports: - name: github.com/gorilla/context version: aed02d124ae4a0e94fea4541c8effd05bf0c8296 - name: github.com/hashicorp/consul - version: 6e061b2d580d80347b7c5c4dfc8730de7403a145 + version: 64e3033d3c7f80af3f925f4665a9bc1af75d6153 subpackages: - api - name: github.com/hashicorp/go-cleanhttp @@ -134,7 +134,7 @@ imports: - name: github.com/Microsoft/go-winio version: ce2922f643c8fd76b46cadc7f404a06282678b34 - name: github.com/miekg/dns - version: 48ab6605c66ac797e07f615101c3e9e10e932b66 + version: 5d001d020961ae1c184f9f8152fdc73810481677 - name: github.com/mitchellh/mapstructure version: d2dd0262208475919e1a362f675cfc0e7c10e905 - name: github.com/moul/http2curl @@ -142,7 +142,7 @@ imports: - name: github.com/ogier/pflag version: 45c278ab3607870051a2ea9040bb85fcb8557481 - name: github.com/opencontainers/runc - version: 5dc3f3576efb5262bf582217e93f86c93944374d + version: 9d7831e41d3ef428b67685eeb27f2b4a22a92391 subpackages: - libcontainer/user - name: github.com/parnurzeal/gorequest @@ -219,7 +219,7 @@ imports: subpackages: - acme - name: golang.org/x/crypto - version: f3241ce8505855877cc8a9717bd61a0f7c4ea83c + version: d81fdb778bf2c40a91b24519d60cdc5767318829 subpackages: - ocsp - name: golang.org/x/net diff --git a/integration/consul_test.go b/integration/consul_test.go index 9002a778c..79a5e6846 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -12,6 +12,7 @@ import ( "errors" "github.com/containous/traefik/integration/utils" + "github.com/containous/traefik/provider" checker "github.com/vdemeester/shakers" "io/ioutil" "os" @@ -24,7 +25,7 @@ type ConsulSuite struct { kv store.Store } -func (s *ConsulSuite) SetUpTest(c *check.C) { +func (s *ConsulSuite) setupConsul(c *check.C) { s.createComposeProject(c, "consul") s.composeProject.Start(c) @@ -52,6 +53,45 @@ func (s *ConsulSuite) SetUpTest(c *check.C) { c.Assert(err, checker.IsNil) } +func (s *ConsulSuite) setupConsulTLS(c *check.C) { + s.createComposeProject(c, "consul_tls") + s.composeProject.Start(c) + + consul.Register() + clientTLS := &provider.ClientTLS{ + CA: "resources/tls/ca.cert", + Cert: "resources/tls/consul.cert", + Key: "resources/tls/consul.key", + InsecureSkipVerify: true, + } + TLSConfig, err := clientTLS.CreateTLSConfig() + c.Assert(err, checker.IsNil) + + kv, err := libkv.NewStore( + store.CONSUL, + []string{s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + ":8585"}, + &store.Config{ + ConnectionTimeout: 10 * time.Second, + TLS: TLSConfig, + }, + ) + + if err != nil { + c.Fatal("Cannot create store consul") + } + s.kv = kv + + // wait for consul + err = utils.Try(60*time.Second, func() error { + _, err := kv.Exists("test") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) +} + func (s *ConsulSuite) TearDownTest(c *check.C) { // shutdown and delete compose project if s.composeProject != nil { @@ -62,6 +102,7 @@ func (s *ConsulSuite) TearDownTest(c *check.C) { func (s *ConsulSuite) TearDownSuite(c *check.C) {} func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { + s.setupConsul(c) consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost}) defer os.Remove(file) @@ -79,6 +120,7 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) { } func (s *ConsulSuite) TestNominalConfiguration(c *check.C) { + s.setupConsul(c) consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost}) defer os.Remove(file) @@ -201,6 +243,7 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) { } func (s *ConsulSuite) TestGlobalConfiguration(c *check.C) { + s.setupConsul(c) consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress err := s.kv.Put("traefik/entrypoints/http/address", []byte(":8001"), nil) c.Assert(err, checker.IsNil) @@ -305,3 +348,46 @@ func (s *ConsulSuite) TestGlobalConfiguration(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(response.StatusCode, checker.Equals, 200) } + +func (s *ConsulSuite) TestGlobalConfigurationWithClientTLS(c *check.C) { + s.setupConsulTLS(c) + consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + + err := s.kv.Put("traefik/web/address", []byte(":8081"), nil) + c.Assert(err, checker.IsNil) + + // wait for consul + err = utils.Try(60*time.Second, func() error { + _, err := s.kv.Exists("traefik/web/address") + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + + // start traefik + cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", + "--consul", "--consul.endpoint="+consulHost+":8585", + "--consul.tls.ca=resources/tls/ca.cert", + "--consul.tls.cert=resources/tls/consul.cert", + "--consul.tls.key=resources/tls/consul.key", + "--consul.tls.insecureskipverify") + // cmd.Stdout = os.Stdout + // cmd.Stderr = os.Stderr + + err = cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + // wait for traefik + err = utils.TryRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, func(res *http.Response) error { + _, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + return nil + }) + c.Assert(err, checker.IsNil) + +} diff --git a/integration/resources/compose/consul_tls.yml b/integration/resources/compose/consul_tls.yml new file mode 100644 index 000000000..41a666799 --- /dev/null +++ b/integration/resources/compose/consul_tls.yml @@ -0,0 +1,14 @@ +consul: + image: progrium/consul + command: -server -bootstrap -log-level debug -ui-dir /ui -config-dir /configs + ports: + - "8500:8500" + - "8585:8585" + expose: + - "8300" + - "8301" + - "8301/udp" + - "8302" + - "8302/udp" + volumes: + - ../tls:/configs \ No newline at end of file diff --git a/integration/resources/tls/ca.cert b/integration/resources/tls/ca.cert new file mode 100644 index 000000000..7d549690f --- /dev/null +++ b/integration/resources/tls/ca.cert @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrTCCApWgAwIBAgIJAO8QudN/gvGqMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNV +BAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxDTALBgNVBAcMBEx5b24xDzANBgNVBAoM +BlplbmlrYTENMAsGA1UEAwwEdGVzdDEeMBwGCSqGSIb3DQEJARYPdGVzdEB6ZW5p +a2EuY29tMB4XDTE2MDcwNjA5MTA1MloXDTI2MDcwNDA5MTA1MlowbTELMAkGA1UE +BhMCRlIxDzANBgNVBAgMBkZyYW5jZTENMAsGA1UEBwwETHlvbjEPMA0GA1UECgwG +WmVuaWthMQ0wCwYDVQQDDAR0ZXN0MR4wHAYJKoZIhvcNAQkBFg90ZXN0QHplbmlr +YS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKwrXlde3J8hFY +hEvH1GH5SfA64/yb7pjXOwI3kvdS2dkbLglOL/jsolARAfFWxhhnnyJUy/BBzZtS +rZN/IukuLCypzjnF6I9koVwILU2EkhPcBUzPZWD6gDU42XZH/lgglZyTyLA/pi24 +eAag5xVuTBMmBGbRsJJEq8MYgzSOAQLu2K8vFPARZdnvOMXVpfrC5+RxDj1AzyxU +5s7olWWG13cWkkh2PUNdb1gCXsz34ALG3EmD2S92tovkKHUZS5zHnOvFl8bF7bKC +MoXBi4bL2cUQXq815uFl0gfRrBgN4U+uT2UjzhIV9ax/xnkGueXi9wGPYP3Yanu8 +dguEtevRAgMBAAGjUDBOMB0GA1UdDgQWBBSxdmZrC6APPhMg73JGRa1sKPB2CDAf +BgNVHSMEGDAWgBSxdmZrC6APPhMg73JGRa1sKPB2CDAMBgNVHRMEBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQBEZNfJxlKr/hv/cyfbJX6yUKDRG/sIFVD4G9uNEKak +N9Dm5/FZ3pzosq/mBuMjXyY/5kYfiBPpyJfUK7CpWfa/U1RP76dDPm+3aaTNK0XS +rWWxP/n5plfb6bt53cfKrnk9ud9ZqY6jX0vQzbVp6F4+jN3ZZfl4SEwlbK0jnrYV +pbjPKbDS5o0RNgLuk/KN9x/KLb9FdgTYxVrB4orDUzpxx54sjfHRGodUAO9VIlbZ +WteavUhCqbVWvYBB64vxKY695PeX79nmwCMVmsy8luquJYgIn27Czexuei3+2mxX +f1rPZL+iCzi8cuShXqhrxH2dNyxsmYFjiPwFHSVgYtL2 +-----END CERTIFICATE----- diff --git a/integration/resources/tls/consul.cert b/integration/resources/tls/consul.cert new file mode 100644 index 000000000..786e1b991 --- /dev/null +++ b/integration/resources/tls/consul.cert @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAeygAwIBAgIBEDANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJGUjEP +MA0GA1UECAwGRnJhbmNlMQ0wCwYDVQQHDARMeW9uMQ8wDQYDVQQKDAZaZW5pa2Ex +DTALBgNVBAMMBHRlc3QxHjAcBgkqhkiG9w0BCQEWD3Rlc3RAemVuaWthLmNvbTAe +Fw0xNjA3MDYxMzMzMTJaFw0yNjA3MDQxMzMzMTJaMBQxEjAQBgNVBAMMCWxvY2Fs +aG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyOvmLF7FLDur27bX7OYo +lh3RF/9Bo0Eq1+uFbqs7/KNVp66njn4QYT+OLcRTovoCIbTqaFT7jeqIKxpJ+DWL +n61BENZvsfSPkxTyF/zekarMHhvrMSpPqEP+NFnfmEVQ4kUELAyREmq6qkZloavV +8X8obRjGbNGuWpNLAlO0g68CAwEAAaOBizCBiDAJBgNVHRMEAjAAMB0GA1UdDgQW +BBQPlr+xQCpVYfksoxb+tsnNkL63EjAfBgNVHSMEGDAWgBSxdmZrC6APPhMg73JG +Ra1sKPB2CDALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF +BwMCMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEFBQADggEBAEUgwhGh48yJ +JDlR7lT+uWQkPGXuXuATYKOt2fnqYFQ3z3jzP2Og8mR/iZJm85GvsN7CyfVi8hZL +sNJOJ1KPRrGVrOFGD4fd8e1sYYw1wowyEiBQii9f/BGy8khw7rl5RrZotuffTulx +PWXF9EyO+vLhpkPzCXG7CkJdakWfJX/83C7xfC+wOpyeGG89IW2l4W6yofLV88hL +LqBLfuL3J9ZknplSmHDB4W4TFr9aHd2zXdgAUgGd+b0+JfxZtxClivn8RoIkR6Dr +JKhxFO9i714+0MKQMEWAvAFcTw8I9ddkQ4cWs9YoYKYdF/cxowAxGYuykI+H92PO +ABAA2aH8ilE= +-----END CERTIFICATE----- diff --git a/integration/resources/tls/consul.key b/integration/resources/tls/consul.key new file mode 100644 index 000000000..182a186b6 --- /dev/null +++ b/integration/resources/tls/consul.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMjr5ixexSw7q9u2 +1+zmKJYd0Rf/QaNBKtfrhW6rO/yjVaeup45+EGE/ji3EU6L6AiG06mhU+43qiCsa +Sfg1i5+tQRDWb7H0j5MU8hf83pGqzB4b6zEqT6hD/jRZ35hFUOJFBCwMkRJquqpG +ZaGr1fF/KG0YxmzRrlqTSwJTtIOvAgMBAAECgYB2kymK4/8vRKP/DeBOkeI//abJ +p73/79SuCvP7RRko1ugVBrEiGenmypBJGEVXuH4LkG6KViUDMvdboK8oycj0zL6y +4naKuWct6EOcxSLhdFyCFLPY+0ggl3F9oG92D02H/3oU7ORBNFBaigSYRSP8EieT +5LxCkM2L1cElMJ/6cQJBAP901eNQZSvikAPma+9oPySD01e9yr0AE06wBxGNMkHH +OS07WknvIdJAMDKng5Umbp4EG/3UbV5ED/y3NoO22YkCQQDJWVrP5nEx53EXCENb +LWDA7SBxjBX60pqvuguDZSjsONQJUlMlqebZSzf/ezLGRUhkzRek8uOwz8MGKnTV +sf13AkAGvE3ncHc6cP7bG3g9F8KSc+deqOJvmVDpAjste0uX8GjRiH8Y8/UwVgDv +VPtjM2A3SmRyjOdVVPYW8728O1YBAkEAl4aPOPYLKasrCFJHnk5ACfBqAgmSYPgt +QSGZmICAk4UQzRMPT8DU4aIhujpUs7FgEbvml1PS1jUEZ5d75XXVcQJAZI50y14r +LJ4H+Q2NvvmeyuI8csX+63IGGd/Zt9/EYj4TQnKISnTV3cr/vkmsdoCevC4dT8rS +0d1rqCvfNzBUPA== +-----END PRIVATE KEY----- diff --git a/integration/resources/tls/consul_config.json b/integration/resources/tls/consul_config.json new file mode 100644 index 000000000..005d381a7 --- /dev/null +++ b/integration/resources/tls/consul_config.json @@ -0,0 +1,9 @@ +{ + "ports": { + "https": 8585 + }, + "ca_file": "/configs/ca.cert", + "cert_file": "/configs/consul.cert", + "key_file": "/configs/consul.key", + "verify_outgoing": true +} \ No newline at end of file diff --git a/traefik.go b/traefik.go index 49af4e1a4..5574ee977 100644 --- a/traefik.go +++ b/traefik.go @@ -22,6 +22,7 @@ import ( "runtime" "strings" "text/template" + "time" ) var versionTemplate = `Version: {{.Version}} @@ -118,22 +119,40 @@ Complete documentation is available at https://traefik.io`, var kv *staert.KvSource var err error + storeConfig := &store.Config{ + ConnectionTimeout: 30 * time.Second, + Bucket: "traefik", + } if traefikConfiguration.Consul != nil { //init KvSource + if traefikConfiguration.Consul.TLS != nil { + storeConfig.TLS, err = traefikConfiguration.Consul.TLS.CreateTLSConfig() + if err != nil { + fmtlog.Println(err) + os.Exit(-1) + } + } consul.Register() kv, err = staert.NewKvSource( store.CONSUL, strings.Split(traefikConfiguration.Consul.Endpoint, ","), - nil, - strings.TrimPrefix(traefikConfiguration.Consul.Prefix, "/"), // TrimPrefix should be done in https://github.com/docker/libkv/blob/master/store/consul/consul.go#L113 : IDK why it doen't work + storeConfig, + strings.TrimPrefix(traefikConfiguration.Consul.Prefix, "/"), ) } else if traefikConfiguration.Etcd != nil { //init KvSource + if traefikConfiguration.Etcd.TLS != nil { + storeConfig.TLS, err = traefikConfiguration.Etcd.TLS.CreateTLSConfig() + if err != nil { + fmtlog.Println(err) + os.Exit(-1) + } + } etcd.Register() kv, err = staert.NewKvSource( store.ETCD, strings.Split(traefikConfiguration.Etcd.Endpoint, ","), - nil, + storeConfig, traefikConfiguration.Etcd.Prefix, ) } else if traefikConfiguration.Zookeeper != nil { @@ -160,25 +179,10 @@ Complete documentation is available at https://traefik.io`, os.Exit(-1) } - // TO DELETE : Used once to fill the kv store - // if kv != nil { - // fmtlog.Println("Try to store global configuration in consul store") - // if err := kv.StoreConfig(traefikConfiguration); err != nil { - // fmtlog.Println(fmt.Errorf("Error : %s", err)) - // os.Exit(-1) - // } else { - // fmtlog.Println("It seems okay :)") - // jsonConf, _ := json.Marshal(traefikConfiguration) - // fmtlog.Printf("Global configuration loaded %s", string(jsonConf)) - // os.Exit(0) - // } - // } - - //TODO : log warning if many KvStore or set priority if kv != nil { s.AddSource(kv) if _, err := s.LoadConfig(); err != nil { - fmtlog.Println(fmt.Errorf("Error : %s", err)) + fmtlog.Println(err) } } From 38cc36980f25582cf4b64aca1453de34ca9fcb4a Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 11 Jul 2016 17:32:28 +0200 Subject: [PATCH 5/6] update doc --- docs/basics.md | 55 ++++++- docs/toml.md | 89 +---------- docs/user-guide/examples.md | 1 + docs/user-guide/kv-config.md | 298 +++++++++++++++++++++++++++++++++++ mkdocs.yml | 2 +- 5 files changed, 352 insertions(+), 93 deletions(-) create mode 100644 docs/user-guide/kv-config.md diff --git a/docs/basics.md b/docs/basics.md index c6a4145ee..41966e3b3 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -253,9 +253,30 @@ Here is an example of backends and servers definition: - `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy. - a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window -# Launch +# Configuration + +Træfɪk's configuration has two parts: + +- The [static Træfɪk configuration](/basics#static-trfk-configuration) which is loaded only at the begining. +- The [dynamic Træfɪk configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process). + + +## Static Træfɪk configuration + +The static configuration is the global configuration which setting up connections to configuration backends and entrypoints. + +Træfɪk can be configured using many configuration sources with the following precedence order. +Each item takes precedence over the item below it: + +- [Key-value Store](/basics/#key-value-stores) +- [Arguments](/basics/#arguments) +- [Configuration file](/basics/#configuration-file) +- Default + +It means that arguments overrides configuration file, and Key-value Store overrides arguments. + +### Configuration file -Træfɪk can be configured using a TOML file configuration, arguments, or both. By default, Træfɪk will try to find a `traefik.toml` in the following places: - `/etc/traefik/` @@ -268,15 +289,35 @@ You can override this by setting a `configFile` argument: $ traefik --configFile=foo/bar/myconfigfile.toml ``` -Træfɪk uses the following precedence order. Each item takes precedence over the item below it: +Please refer to the [global configuration](/toml/#global-configuration) section to get documentation on it. -- arguments -- configuration file -- default +### Arguments -It means that arguments overrides configuration file. Each argument is described in the help section: ```bash $ traefik --help ``` + +Note that all default values will be displayed as well. + +### Key-value stores + +Træfɪk supports several Key-value stores: + +- [Consul](https://consul.io) +- [etcd](https://coreos.com/etcd/) +- [ZooKeeper](https://zookeeper.apache.org/) +- [boltdb](https://github.com/boltdb/bolt) + +Please refer to the [User Guide Key-value store configuration](/user-guide/kv-config/) section to get documentation on it. + +## Dynamic Træfɪk configuration + +Træfɪk can hot-reload its configuration. + +The dynamic configuration concern route rules which could be provided by [multiple configuration backends](/toml/#configuration-backends). +We only need to enable `watch` option to make Træfɪk watch configuration backend changes and generate its configuration automatically. +Routes to services will be created and updated instantly at any changes. + +Please refer to the [configuration backends](/toml/#configuration-backends) section to get documentation on it. diff --git a/docs/toml.md b/docs/toml.md index 8028206ea..8cfded1ea 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -756,7 +756,7 @@ prefix = "traefik" # insecureskipverify = true ``` -Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure. +Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure. ## Consul catalog backend @@ -857,7 +857,7 @@ prefix = "/traefik" # insecureskipverify = true ``` -Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure. +Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure. ## Zookeeper backend @@ -900,7 +900,7 @@ prefix = "/traefik" # filename = "zookeeper.tmpl" ``` -Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure. +Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure. ## BoltDB backend @@ -942,85 +942,4 @@ prefix = "/traefik" # filename = "boltdb.tmpl" ``` -Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure. - -## Key-value storage structure - -The Keys-Values structure should look (using `prefix = "/traefik"`): - -- backend 1 - -| Key | Value | -|--------------------------------------------------------|-----------------------------| -| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` | -| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | -| `/traefik/backends/backend1/servers/server1/weight` | `10` | -| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` | -| `/traefik/backends/backend1/servers/server2/weight` | `1` | - -- backend 2 - -| Key | Value | -|-----------------------------------------------------|------------------------| -| `/traefik/backends/backend2/maxconn/amount` | `10` | -| `/traefik/backends/backend2/maxconn/extractorfunc` | `request.host` | -| `/traefik/backends/backend2/loadbalancer/method` | `drr` | -| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` | -| `/traefik/backends/backend2/servers/server1/weight` | `1` | -| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` | -| `/traefik/backends/backend2/servers/server2/weight` | `2` | - -- frontend 1 - -| Key | Value | -|---------------------------------------------------|-----------------------| -| `/traefik/frontends/frontend1/backend` | `backend2` | -| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` | - -- frontend 2 - -| Key | Value | -|----------------------------------------------------|--------------------| -| `/traefik/frontends/frontend2/backend` | `backend1` | -| `/traefik/frontends/frontend2/passHostHeader` | `true` | -| `/traefik/frontends/frontend2/priority` | `10` | -| `/traefik/frontends/frontend2/entrypoints` | `http,https` | -| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` | - -## Atomic configuration changes - -The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix. - -Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity). - -| Key | Value | -|-------------------------------------------------------------------------|-----------------------------| -| `/traefik/alias` | `/traefik_configurations/1` | -| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | -| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` | - -When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`: - -| Key | Value | -|-------------------------------------------------------------------------|-----------------------------| -| `/traefik/alias` | `/traefik_configurations/1` | -| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | -| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` | -| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | -| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` | -| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` | -| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | - -Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host: - -| Key | Value | -|-------------------------------------------------------------------------|-----------------------------| -| `/traefik/alias` | `/traefik_configurations/2` | -| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | -| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` | -| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` | -| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` | -| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` | -| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | - -Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored. +Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure. diff --git a/docs/user-guide/examples.md b/docs/user-guide/examples.md index cf164755c..ad4d9fa52 100644 --- a/docs/user-guide/examples.md +++ b/docs/user-guide/examples.md @@ -29,6 +29,7 @@ defaultEntryPoints = ["http", "https"] CertFile = "integration/fixtures/https/snitest.org.cert" KeyFile = "integration/fixtures/https/snitest.org.key" ``` +Note that we can either give path to certificate file or directly the file content itself ([like in this TOML example](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store)). ## HTTP redirect on HTTPS diff --git a/docs/user-guide/kv-config.md b/docs/user-guide/kv-config.md new file mode 100644 index 000000000..264722e1d --- /dev/null +++ b/docs/user-guide/kv-config.md @@ -0,0 +1,298 @@ + +# Key-value store configuration + +Both [static global configuration](/user-guide/kv-config/#static-configuration-in-key-value-store) and [dynamic](/user-guide/kv-config/#dynamic-configuration-in-key-value-store) configuration can be sorted in a Key-value store. + +This section explains how to launch Træfɪk using a configuration loaded from a Key-value store. + +Træfɪk supports several Key-value stores: + +- [Consul](https://consul.io) +- [etcd](https://coreos.com/etcd/) +- [ZooKeeper](https://zookeeper.apache.org/) +- [boltdb](https://github.com/boltdb/bolt) + +# Static configuration in Key-value store + +We will see the steps to set it up with an easy example. +Note that we could do the same with any other Key-value Store. + +## docker-compose file for Consul + +The Træfɪk global configuration will be getted from a [Consul](https://consul.io) store. + +First we have to launch Consul in a container. +The [docker-compose file](https://docs.docker.com/compose/compose-file/) allows us to launch Consul and four instances of the trivial app [emilevauge/whoamI](https://github.com/emilevauge/whoamI) : + +```yml +consul: + image: progrium/consul + command: -server -bootstrap -log-level debug -ui-dir /ui + ports: + - "8400:8400" + - "8500:8500" + - "8600:53/udp" + expose: + - "8300" + - "8301" + - "8301/udp" + - "8302" + - "8302/udp" + +whoami1: + image: emilevauge/whoami + +whoami2: + image: emilevauge/whoami + +whoami3: + image: emilevauge/whoami + +whoami4: + image: emilevauge/whoami +``` + +## Upload the configuration in the Key-value store + +We should now fill the store with the Træfɪk global configuration, as we do with a [TOML file configuration](/toml). +To do that, we can send the Key-value pairs via [curl commands](https://www.consul.io/intro/getting-started/kv.html) or via the [Web UI](https://www.consul.io/intro/getting-started/ui.html) + +Here the toml configuration we would like to store in the Key-value Store : + +```toml +logLevel = "DEBUG" + +defaultEntryPoints = ["http", "https"] + +[entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.https] + address = ":443" + [entryPoints.https.tls] + [[entryPoints.https.tls.certificates]] + CertFile = "integration/fixtures/https/snitest.com.cert" + KeyFile = "integration/fixtures/https/snitest.com.key" + [[entryPoints.https.tls.certificates]] + CertFile = """-----BEGIN CERTIFICATE----- + + -----END CERTIFICATE-----""" + KeyFile = """-----BEGIN CERTIFICATE----- + + -----END CERTIFICATE-----""" + + +[consul] + endpoint = "127.0.0.1:8500" + watch = true + prefix = "traefik" + +[web] + address = ":8081" +``` + +And there, the same global configuration in the Key-value Store (using `prefix = "traefik"`): + +| Key | Value | +|-----------------------------------------------------------|---------------------------------------------------------------| +| `/traefik/loglevel` | `DEBUG` | +| `/traefik/defaultentrypoints/0` | `http` | +| `/traefik/defaultentrypoints/1` | `https` | +| `/traefik/entrypoints/http/address` | `:80` | +| `/traefik/entrypoints/https/address` | `:443` | +| `/traefik/entrypoints/https/tls/certificates/0/certfile` | `integration/fixtures/https/snitest.com.cert` | +| `/traefik/entrypoints/https/tls/certificates/0/keyfile` | `integration/fixtures/https/snitest.com.key` | +| `/traefik/entrypoints/https/tls/certificates/1/certfile` | `--BEGIN CERTIFICATE----END CERTIFICATE--` | +| `/traefik/entrypoints/https/tls/certificates/1/keyfile` | `--BEGIN CERTIFICATE----END CERTIFICATE--` | +| `/traefik/consul/endpoint` | `127.0.0.1:8500` | +| `/traefik/consul/watch` | `true` | +| `/traefik/consul/prefix` | `traefik` | +| `/traefik/web/address` | `:8081` | + +Remember to specify the indexes (`0`,`1`, `2`, ... ) under prefixes `/traefik/defaultentrypoints/` and `/traefik/entrypoints/https/tls/certificates/` in order to match the global configuration structure. + +Be careful to give the correct IP address and port on the key `/traefik/consul/endpoint`. + +Note that we can either give path to certificate file or directly the file content itself. + +## Launch Træfɪk + +We will now launch Træfɪk in a container. +We use CLI flags to setup the connection between Træfɪk and Consul. +All the rest of the global configuration is stored in Consul. + +Here the [docker-compose file](https://docs.docker.com/compose/compose-file/) : + +```yml +traefik: + image: traefik + command: --consul --consul.endpoint=127.0.0.1:8500 + ports: + - "80:80" + - "8080:8080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock +``` + +NB : Be careful to give the correct IP address and port in the flag `--consul.endpoint`. + +## TLS support + +So far, only [Consul](https://consul.io) and [etcd](https://coreos.com/etcd/) support TLS connections. +To set it up, we should enable [consul security](https://www.consul.io/docs/internals/security.html) (or [etcd security](https://coreos.com/etcd/docs/latest/security.html)). + +Then, we have to provide CA, Cert and Key to Træfɪk using `consul` flags : + +- `--consul.tls` +- `--consul.tls.ca=path/to/the/file` +- `--consul.tls.cert=path/to/the/file` +- `--consul.tls.key=path/to/the/file` + +Or etcd flags : + +- `--etcd.tls` +- `--etcd.tls.ca=path/to/the/file` +- `--etcd.tls.cert=path/to/the/file` +- `--etcd.tls.key=path/to/the/file` + +Note that we can either give directly directly the file content itself (instead of the path to certificate) in a TOML file configuration. + +Remember the command `traefik --help` to display the updated list of flags. + +# Dynamic configuration in Key-value store +Following our example, we will provide backends/frontends rules to Træfɪk. + +Note that this section is independent of the way Træfɪk got its static configuration. +It means that the static configuration can either come from the same Key-value store or from any other sources. + +## Key-value storage structure +Here the toml configuration we would like to store in the store : + +```toml +[file] + +# rules +[backends] + [backends.backend1] + [backends.backend1.circuitbreaker] + expression = "NetworkErrorRatio() > 0.5" + [backends.backend1.servers.server1] + url = "http://172.17.0.2:80" + weight = 10 + [backends.backend1.servers.server2] + url = "http://172.17.0.3:80" + weight = 1 + [backends.backend2] + [backends.backend1.maxconn] + amount = 10 + extractorfunc = "request.host" + [backends.backend2.LoadBalancer] + method = "drr" + [backends.backend2.servers.server1] + url = "http://172.17.0.4:80" + weight = 1 + [backends.backend2.servers.server2] + url = "http://172.17.0.5:80" + weight = 2 + +[frontends] + [frontends.frontend1] + backend = "backend2" + [frontends.frontend1.routes.test_1] + rule = "Host:test.localhost" + [frontends.frontend2] + backend = "backend1" + passHostHeader = true + priority = 10 + entrypoints = ["https"] # overrides defaultEntryPoints + [frontends.frontend2.routes.test_1] + rule = "Host:{subdomain:[a-z]+}.localhost" + [frontends.frontend3] + entrypoints = ["http", "https"] # overrides defaultEntryPoints + backend = "backend2" + rule = "Path:/test" +``` + +And there, the same dynamic configuration in a KV Store (using `prefix = "traefik"`): + +- backend 1 + +| Key | Value | +|--------------------------------------------------------|-----------------------------| +| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` | +| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | +| `/traefik/backends/backend1/servers/server1/weight` | `10` | +| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` | +| `/traefik/backends/backend1/servers/server2/weight` | `1` | + +- backend 2 + +| Key | Value | +|-----------------------------------------------------|------------------------| +| `/traefik/backends/backend2/maxconn/amount` | `10` | +| `/traefik/backends/backend2/maxconn/extractorfunc` | `request.host` | +| `/traefik/backends/backend2/loadbalancer/method` | `drr` | +| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` | +| `/traefik/backends/backend2/servers/server1/weight` | `1` | +| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` | +| `/traefik/backends/backend2/servers/server2/weight` | `2` | + +- frontend 1 + +| Key | Value | +|---------------------------------------------------|-----------------------| +| `/traefik/frontends/frontend1/backend` | `backend2` | +| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` | + +- frontend 2 + +| Key | Value | +|----------------------------------------------------|--------------------| +| `/traefik/frontends/frontend2/backend` | `backend1` | +| `/traefik/frontends/frontend2/passHostHeader` | `true` | +| `/traefik/frontends/frontend2/priority` | `10` | +| `/traefik/frontends/frontend2/entrypoints` | `http,https` | +| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` | + +## Atomic configuration changes + +Træfɪk can watch the backends/frontends configuration changes and generate its configuration automatically. + +Note that only backends/frontends rules are dynamic, the rest of the Træfɪk configuration stay static. + +The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix. + +Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity). + +| Key | Value | +|-------------------------------------------------------------------------|-----------------------------| +| `/traefik/alias` | `/traefik_configurations/1` | +| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | +| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` | + +When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`: + +| Key | Value | +|-------------------------------------------------------------------------|-----------------------------| +| `/traefik/alias` | `/traefik_configurations/1` | +| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | +| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` | +| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | +| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` | +| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` | +| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | + +Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host: + +| Key | Value | +|-------------------------------------------------------------------------|-----------------------------| +| `/traefik/alias` | `/traefik_configurations/2` | +| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` | +| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` | +| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` | +| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` | +| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` | +| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` | + +Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik/alias`. +Further, if the `/traefik/alias` key is set, all other configuration with `/traefik/backends` or `/traefik/frontends` prefix are ignored. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 08191d62b..be6298810 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -50,4 +50,4 @@ pages: - 'Configuration examples': 'user-guide/examples.md' - 'Swarm cluster': 'user-guide/swarm.md' - 'Kubernetes': 'user-guide/kubernetes.md' - - Benchmarks: benchmarks.md + - 'Key-value store configuration': 'user-guide/kv-config.md' From b153e90ec5307d9e0d3a4d943a8f19decaeff50b Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 13 Jul 2016 17:18:55 +0200 Subject: [PATCH 6/6] add createStore() funcs and skip consul TLS test --- configuration.go | 3 +- docs/basics.md | 9 +++- integration/consul_test.go | 3 +- provider/boltdb.go | 15 +++++- provider/consul.go | 15 +++++- provider/docker.go | 7 --- provider/etcd.go | 15 +++++- provider/kv.go | 44 ++++++++--------- provider/provider.go | 1 - provider/zk.go | 15 +++++- traefik.go | 99 ++++++++++++++------------------------ 11 files changed, 121 insertions(+), 105 deletions(-) diff --git a/configuration.go b/configuration.go index cdbdca5da..b1ce08464 100644 --- a/configuration.go +++ b/configuration.go @@ -179,6 +179,7 @@ type TLS struct { } // Certificates defines traefik certificates type +// Certs and Keys could be either a file path, or the file content itself type Certificates []Certificate //CreateTLSConfig creates a TLS config from Certificate structures @@ -248,7 +249,7 @@ func (certs *Certificates) Type() string { } // Certificate holds a SSL cert/key pair -// May can contain either path or file contents +// Certs and Key could be either a file path, or the file content itself type Certificate struct { CertFile string KeyFile string diff --git a/docs/basics.md b/docs/basics.md index 41966e3b3..dcce736e5 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -314,9 +314,14 @@ Please refer to the [User Guide Key-value store configuration](/user-guide/kv-co ## Dynamic Træfɪk configuration -Træfɪk can hot-reload its configuration. +The dynamic configuration concerns : + +- [Frontends](/basics/#frontends) +- [Backends](/basics/#backends) +- [Servers](/basics/#servers) + +Træfɪk can hot-reload those rules which could be provided by [multiple configuration backends](/toml/#configuration-backends). -The dynamic configuration concern route rules which could be provided by [multiple configuration backends](/toml/#configuration-backends). We only need to enable `watch` option to make Træfɪk watch configuration backend changes and generate its configuration automatically. Routes to services will be created and updated instantly at any changes. diff --git a/integration/consul_test.go b/integration/consul_test.go index 79a5e6846..9352bedb1 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -349,7 +349,8 @@ func (s *ConsulSuite) TestGlobalConfiguration(c *check.C) { c.Assert(response.StatusCode, checker.Equals, 200) } -func (s *ConsulSuite) TestGlobalConfigurationWithClientTLS(c *check.C) { +func (s *ConsulSuite) skipTestGlobalConfigurationWithClientTLS(c *check.C) { + c.Skip("wait for relative path issue in the composefile") s.setupConsulTLS(c) consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress diff --git a/provider/boltdb.go b/provider/boltdb.go index 285206955..5220fca98 100644 --- a/provider/boltdb.go +++ b/provider/boltdb.go @@ -1,6 +1,7 @@ package provider import ( + "fmt" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/docker/libkv/store" @@ -15,7 +16,17 @@ type BoltDb struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { - provider.storeType = store.BOLTDB - boltdb.Register() + store, err := provider.CreateStore() + if err != nil { + return fmt.Errorf("Failed to Connect to KV store: %v", err) + } + provider.kvclient = store return provider.provide(configurationChan, pool, constraints) } + +// CreateStore creates the KV store +func (provider *BoltDb) CreateStore() (store.Store, error) { + provider.storeType = store.BOLTDB + boltdb.Register() + return provider.createStore() +} diff --git a/provider/consul.go b/provider/consul.go index b8967e61c..4cc8b2851 100644 --- a/provider/consul.go +++ b/provider/consul.go @@ -1,6 +1,7 @@ package provider import ( + "fmt" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/docker/libkv/store" @@ -15,7 +16,17 @@ type Consul struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { - provider.storeType = store.CONSUL - consul.Register() + store, err := provider.CreateStore() + if err != nil { + return fmt.Errorf("Failed to Connect to KV store: %v", err) + } + provider.kvclient = store return provider.provide(configurationChan, pool, constraints) } + +// CreateStore creates the KV store +func (provider *Consul) CreateStore() (store.Store, error) { + provider.storeType = store.CONSUL + consul.Register() + return provider.createStore() +} diff --git a/provider/docker.go b/provider/docker.go index 52f70ae28..71897f56b 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -10,7 +10,6 @@ import ( "golang.org/x/net/context" - "crypto/tls" "github.com/BurntSushi/ty/fun" log "github.com/Sirupsen/logrus" "github.com/cenkalti/backoff" @@ -47,12 +46,6 @@ func (provider *Docker) createClient() (client.APIClient, error) { if err != nil { return nil, err } - // TO DELETE IF USELESS : default docker TLS Client config - config.MaxVersion = tls.VersionTLS12 - config.CipherSuites = []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - } tr := &http.Transport{ TLSClientConfig: config, } diff --git a/provider/etcd.go b/provider/etcd.go index 6c41757fa..0165bf3c1 100644 --- a/provider/etcd.go +++ b/provider/etcd.go @@ -1,6 +1,7 @@ package provider import ( + "fmt" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/docker/libkv/store" @@ -15,7 +16,17 @@ type Etcd struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { - provider.storeType = store.ETCD - etcd.Register() + store, err := provider.CreateStore() + if err != nil { + return fmt.Errorf("Failed to Connect to KV store: %v", err) + } + provider.kvclient = store return provider.provide(configurationChan, pool, constraints) } + +// CreateStore creates the KV store +func (provider *Etcd) CreateStore() (store.Store, error) { + provider.storeType = store.ETCD + etcd.Register() + return provider.createStore() +} diff --git a/provider/kv.go b/provider/kv.go index f27a085fe..332f761dc 100644 --- a/provider/kv.go +++ b/provider/kv.go @@ -27,6 +27,26 @@ type Kv struct { kvclient store.Store } +func (provider *Kv) createStore() (store.Store, error) { + storeConfig := &store.Config{ + ConnectionTimeout: 30 * time.Second, + Bucket: "traefik", + } + + if provider.TLS != nil { + var err error + storeConfig.TLS, err = provider.TLS.CreateTLSConfig() + if err != nil { + return nil, err + } + } + return libkv.NewStore( + provider.storeType, + strings.Split(provider.Endpoint, ","), + storeConfig, + ) +} + func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error { operation := func() error { events, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{})) @@ -63,32 +83,10 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix } func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { - storeConfig := &store.Config{ - ConnectionTimeout: 30 * time.Second, - Bucket: "traefik", - } - - if provider.TLS != nil { - var err error - storeConfig.TLS, err = provider.TLS.CreateTLSConfig() - if err != nil { - return err - } - } - operation := func() error { - kv, err := libkv.NewStore( - provider.storeType, - strings.Split(provider.Endpoint, ","), - storeConfig, - ) - if err != nil { - return fmt.Errorf("Failed to Connect to KV store: %v", err) - } - if _, err := kv.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil { + if _, err := provider.kvclient.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil { return fmt.Errorf("Failed to test KV store connection: %v", err) } - provider.kvclient = kv if provider.Watch { pool.Go(func(stop chan bool) { err := provider.watchKv(configurationChan, provider.Prefix, stop) diff --git a/provider/provider.go b/provider/provider.go index 28f75f564..b06511b6e 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -110,7 +110,6 @@ type ClientTLS struct { func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) { var err error caPool := x509.NewCertPool() - // TODO : error if CA=="" || Cert=="" || Key=="" if clientTLS.CA != "" { var ca []byte if _, errCA := os.Stat(clientTLS.CA); errCA == nil { diff --git a/provider/zk.go b/provider/zk.go index 416d62844..467f59e2f 100644 --- a/provider/zk.go +++ b/provider/zk.go @@ -1,6 +1,7 @@ package provider import ( + "fmt" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/docker/libkv/store" @@ -15,7 +16,17 @@ type Zookepper struct { // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { - provider.storeType = store.ZK - zookeeper.Register() + store, err := provider.CreateStore() + if err != nil { + return fmt.Errorf("Failed to Connect to KV store: %v", err) + } + provider.kvclient = store return provider.provide(configurationChan, pool, constraints) } + +// CreateStore creates the KV store +func (provider *Zookepper) CreateStore() (store.Store, error) { + provider.storeType = store.ZK + zookeeper.Register() + return provider.createStore() +} diff --git a/traefik.go b/traefik.go index 5574ee977..5fd51a9e4 100644 --- a/traefik.go +++ b/traefik.go @@ -11,10 +11,6 @@ import ( "github.com/containous/traefik/provider" "github.com/containous/traefik/types" "github.com/docker/libkv/store" - "github.com/docker/libkv/store/boltdb" - "github.com/docker/libkv/store/consul" - "github.com/docker/libkv/store/etcd" - "github.com/docker/libkv/store/zookeeper" fmtlog "log" "net/http" "os" @@ -22,7 +18,6 @@ import ( "runtime" "strings" "text/template" - "time" ) var versionTemplate = `Version: {{.Version}} @@ -117,63 +112,7 @@ Complete documentation is available at https://traefik.io`, traefikConfiguration.ConfigFile = toml.ConfigFileUsed() - var kv *staert.KvSource - var err error - storeConfig := &store.Config{ - ConnectionTimeout: 30 * time.Second, - Bucket: "traefik", - } - if traefikConfiguration.Consul != nil { - //init KvSource - if traefikConfiguration.Consul.TLS != nil { - storeConfig.TLS, err = traefikConfiguration.Consul.TLS.CreateTLSConfig() - if err != nil { - fmtlog.Println(err) - os.Exit(-1) - } - } - consul.Register() - kv, err = staert.NewKvSource( - store.CONSUL, - strings.Split(traefikConfiguration.Consul.Endpoint, ","), - storeConfig, - strings.TrimPrefix(traefikConfiguration.Consul.Prefix, "/"), - ) - } else if traefikConfiguration.Etcd != nil { - //init KvSource - if traefikConfiguration.Etcd.TLS != nil { - storeConfig.TLS, err = traefikConfiguration.Etcd.TLS.CreateTLSConfig() - if err != nil { - fmtlog.Println(err) - os.Exit(-1) - } - } - etcd.Register() - kv, err = staert.NewKvSource( - store.ETCD, - strings.Split(traefikConfiguration.Etcd.Endpoint, ","), - storeConfig, - traefikConfiguration.Etcd.Prefix, - ) - } else if traefikConfiguration.Zookeeper != nil { - //init KvSource - zookeeper.Register() - kv, err = staert.NewKvSource( - store.ZK, - strings.Split(traefikConfiguration.Zookeeper.Endpoint, ","), - nil, - traefikConfiguration.Zookeeper.Prefix, - ) - } else if traefikConfiguration.Boltdb != nil { - //init KvSource - boltdb.Register() - kv, err = staert.NewKvSource( - store.BOLTDB, - strings.Split(traefikConfiguration.Boltdb.Endpoint, ","), - nil, - traefikConfiguration.Boltdb.Prefix, - ) - } + kv, err := CreateKvSource(traefikConfiguration) if err != nil { fmtlog.Println(err) os.Exit(-1) @@ -255,3 +194,39 @@ func run(traefikConfiguration *TraefikConfiguration) { defer server.Close() log.Info("Shutting down") } + +// CreateKvSource creates KvSource +// TLS support is enable for Consul and ects backends +func CreateKvSource(traefikConfiguration *TraefikConfiguration) (*staert.KvSource, error) { + var kv *staert.KvSource + var store store.Store + var err error + + switch { + case traefikConfiguration.Consul != nil: + store, err = traefikConfiguration.Consul.CreateStore() + kv = &staert.KvSource{ + Store: store, + Prefix: traefikConfiguration.Consul.Prefix, + } + case traefikConfiguration.Etcd != nil: + store, err = traefikConfiguration.Etcd.CreateStore() + kv = &staert.KvSource{ + Store: store, + Prefix: traefikConfiguration.Etcd.Prefix, + } + case traefikConfiguration.Zookeeper != nil: + store, err = traefikConfiguration.Zookeeper.CreateStore() + kv = &staert.KvSource{ + Store: store, + Prefix: traefikConfiguration.Zookeeper.Prefix, + } + case traefikConfiguration.Boltdb != nil: + store, err = traefikConfiguration.Boltdb.CreateStore() + kv = &staert.KvSource{ + Store: store, + Prefix: traefikConfiguration.Boltdb.Prefix, + } + } + return kv, err +}