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))