Handle RootCAs Certificate

This commit is contained in:
Julien Salleyron 2017-06-23 15:15:07 +02:00 committed by Ludovic Fernandez
parent 3776e58041
commit 81d011e57d
8 changed files with 245 additions and 32 deletions

View file

@ -2,6 +2,7 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
fmtlog "log" fmtlog "log"
@ -28,6 +29,7 @@ import (
"github.com/coreos/go-systemd/daemon" "github.com/coreos/go-systemd/daemon"
"github.com/docker/libkv/store" "github.com/docker/libkv/store"
"github.com/satori/go.uuid" "github.com/satori/go.uuid"
"golang.org/x/net/http2"
) )
func main() { func main() {
@ -104,6 +106,7 @@ Complete documentation is available at https://traefik.io`,
//add custom parsers //add custom parsers
f.AddParser(reflect.TypeOf(server.EntryPoints{}), &server.EntryPoints{}) f.AddParser(reflect.TypeOf(server.EntryPoints{}), &server.EntryPoints{})
f.AddParser(reflect.TypeOf(server.DefaultEntryPoints{}), &server.DefaultEntryPoints{}) f.AddParser(reflect.TypeOf(server.DefaultEntryPoints{}), &server.DefaultEntryPoints{})
f.AddParser(reflect.TypeOf(server.RootCAs{}), &server.RootCAs{})
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{}) f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{}) f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{})
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{}) f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
@ -180,6 +183,23 @@ func run(traefikConfiguration *server.TraefikConfiguration) {
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
} }
if len(globalConfiguration.RootCAs) > 0 {
roots := x509.NewCertPool()
for _, cert := range globalConfiguration.RootCAs {
certContent, err := cert.Read()
if err != nil {
log.Error("Error while read RootCAs", err)
continue
}
roots.AppendCertsFromPEM(certContent)
}
tr := http.DefaultTransport.(*http.Transport)
tr.TLSClientConfig = &tls.Config{RootCAs: roots}
http2.ConfigureTransport(tr)
}
if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 { if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 {
// no filename, setting to global config file // no filename, setting to global config file
if len(traefikConfiguration.ConfigFile) != 0 { if len(traefikConfiguration.ConfigFile) != 0 {

View file

@ -95,6 +95,13 @@
# #
# InsecureSkipVerify = true # InsecureSkipVerify = true
# Register Certificates in the RootCA. This certificates will be use for backends calls.
# Note: You can use file path or cert content directly
# Optional
# Default: []
#
# RootCAs = [ "/mycert.cert" ]
# Entrypoints to be used by frontends that do not specify any entrypoint. # Entrypoints to be used by frontends that do not specify any entrypoint.
# Each frontend can specify its own entrypoints. # Each frontend can specify its own entrypoints.
# #

View file

@ -0,0 +1,41 @@
logLevel = "DEBUG"
defaultEntryPoints = ["http"]
# Use certificate in net/internal/testcert.go
RootCAs = [ """
-----BEGIN CERTIFICATE-----
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4
iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul
rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA
AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9
tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs
h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM
fblo6RBxUQ==
-----END CERTIFICATE-----
"""]
[entryPoints]
[entryPoints.http]
address = ":8081"
[web]
address = ":8080"
[file]
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "{{ .BackendHost }}"
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Path: /ping"

View file

@ -0,0 +1,25 @@
logLevel = "DEBUG"
defaultEntryPoints = ["http"]
# Use certificate in net/internal/testcert.go
RootCAs = [ "fixtures/https/rootcas/local.crt"]
[entryPoints]
[entryPoints.http]
address = ":8081"
[web]
address = ":8080"
[file]
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "{{ .BackendHost }}"
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Path: /ping"

View file

@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4
iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul
rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA
AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9
tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs
h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM
fblo6RBxUQ==
-----END CERTIFICATE-----

View file

@ -5,6 +5,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"time" "time"
"github.com/containous/traefik/integration/try" "github.com/containous/traefik/integration/try"
@ -271,6 +272,48 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAsMultipleFi
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server")) c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
} }
func (s *HTTPSSuite) TestWithRootCAsContentForHTTPSOnBackend(c *check.C) {
backend := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))
defer backend.Close()
file := s.adaptFile(c, "fixtures/https/rootcas/https.toml", struct{ BackendHost string }{backend.URL})
defer os.Remove(file)
cmd, _ := s.cmdTraefikWithConfigFile(file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1000*time.Millisecond, try.BodyContains(backend.URL))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8081/ping", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
}
func (s *HTTPSSuite) TestWithRootCAsFileForHTTPSOnBackend(c *check.C) {
backend := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))
defer backend.Close()
file := s.adaptFile(c, "fixtures/https/rootcas/https_with_file.toml", struct{ BackendHost string }{backend.URL})
defer os.Remove(file)
cmd, _ := s.cmdTraefikWithConfigFile(file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1000*time.Millisecond, try.BodyContains(backend.URL))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8081/ping", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
}
func startTestServer(port string, statusCode int) (ts *httptest.Server) { func startTestServer(port string, statusCode int) (ts *httptest.Server) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode) w.WriteHeader(statusCode)

View file

@ -2,8 +2,8 @@ package server
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"regexp" "regexp"
"strings" "strings"
@ -56,6 +56,7 @@ type GlobalConfiguration struct {
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"` MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
IdleTimeout flaeg.Duration `description:"maximum amount of time an idle (keep-alive) connection will remain idle before closing itself."` IdleTimeout flaeg.Duration `description:"maximum amount of time an idle (keep-alive) connection will remain idle before closing itself."`
InsecureSkipVerify bool `description:"Disable SSL certificate verification"` InsecureSkipVerify bool `description:"Disable SSL certificate verification"`
RootCAs RootCAs `description:"Add cert file for self-signed certicate"`
Retry *Retry `description:"Enable retry sending request if network error"` Retry *Retry `description:"Enable retry sending request if network error"`
HealthCheck *HealthCheckConfig `description:"Health check parameters"` HealthCheck *HealthCheckConfig `description:"Health check parameters"`
Docker *docker.Provider `description:"Enable Docker backend"` Docker *docker.Provider `description:"Enable Docker backend"`
@ -110,7 +111,69 @@ func (dep *DefaultEntryPoints) SetValue(val interface{}) {
// Type is type of the struct // Type is type of the struct
func (dep *DefaultEntryPoints) Type() string { func (dep *DefaultEntryPoints) Type() string {
return fmt.Sprint("defaultentrypoints") return "defaultentrypoints"
}
// RootCAs hold the CA we want to have in root
type RootCAs []FileOrContent
// FileOrContent hold a file path or content
type FileOrContent string
func (f FileOrContent) String() string {
return string(f)
}
func (f FileOrContent) Read() ([]byte, error) {
var content []byte
if _, err := os.Stat(f.String()); err == nil {
content, err = ioutil.ReadFile(f.String())
if err != nil {
return nil, err
}
} else {
content = []byte(f)
}
return content, 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 (r *RootCAs) String() string {
sliceOfString := make([]string, len([]FileOrContent(*r)))
for key, value := range *r {
sliceOfString[key] = value.String()
}
return strings.Join(sliceOfString, ",")
}
// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it.
func (r *RootCAs) Set(value string) error {
rootCAs := strings.Split(value, ",")
if len(rootCAs) == 0 {
return fmt.Errorf("bad RootCAs format: %s", value)
}
for _, rootCA := range rootCAs {
*r = append(*r, FileOrContent(rootCA))
}
return nil
}
// Get return the EntryPoints map
func (r *RootCAs) Get() interface{} {
return RootCAs(*r)
}
// SetValue sets the EntryPoints map with val
func (r *RootCAs) SetValue(val interface{}) {
*r = RootCAs(val.(RootCAs))
}
// Type is type of the struct
func (r *RootCAs) Type() string {
return "rootcas"
} }
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...) // EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
@ -192,7 +255,7 @@ func (ep *EntryPoints) SetValue(val interface{}) {
// Type is type of the struct // Type is type of the struct
func (ep *EntryPoints) Type() string { func (ep *EntryPoints) Type() string {
return fmt.Sprint("entrypoints") return "entrypoints"
} }
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...) // EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
@ -264,32 +327,25 @@ func (certs *Certificates) CreateTLSConfig() (*tls.Config, error) {
config.Certificates = []tls.Certificate{} config.Certificates = []tls.Certificate{}
certsSlice := []Certificate(*certs) certsSlice := []Certificate(*certs)
for _, v := range certsSlice { for _, v := range certsSlice {
isAPath := false cert := tls.Certificate{}
_, errCert := os.Stat(v.CertFile)
_, errKey := os.Stat(v.KeyFile) var err error
if errCert == nil {
if errKey == nil { certContent, err := v.CertFile.Read()
isAPath = true if err != nil {
} else { return nil, err
return nil, errors.New("bad TLS Certificate KeyFile format, expected a path")
}
} else if errKey == nil {
return nil, errors.New("bad TLS Certificate KeyFile format, expected a path")
} }
cert := tls.Certificate{} keyContent, err := v.KeyFile.Read()
var err error if err != nil {
if isAPath { return nil, err
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
}
} }
cert, err = tls.X509KeyPair(certContent, keyContent)
if err != nil {
return nil, err
}
config.Certificates = append(config.Certificates, cert) config.Certificates = append(config.Certificates, cert)
} }
return config, nil return config, nil
@ -303,7 +359,7 @@ func (certs *Certificates) String() string {
} }
var result []string var result []string
for _, certificate := range *certs { for _, certificate := range *certs {
result = append(result, certificate.CertFile+","+certificate.KeyFile) result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String())
} }
return strings.Join(result, ";") return strings.Join(result, ";")
} }
@ -319,8 +375,8 @@ func (certs *Certificates) Set(value string) error {
return fmt.Errorf("bad certificates format: %s", value) return fmt.Errorf("bad certificates format: %s", value)
} }
*certs = append(*certs, Certificate{ *certs = append(*certs, Certificate{
CertFile: files[0], CertFile: FileOrContent(files[0]),
KeyFile: files[1], KeyFile: FileOrContent(files[1]),
}) })
} }
return nil return nil
@ -328,14 +384,14 @@ func (certs *Certificates) Set(value string) error {
// Type is type of the struct // Type is type of the struct
func (certs *Certificates) Type() string { func (certs *Certificates) Type() string {
return fmt.Sprint("certificates") return "certificates"
} }
// Certificate holds a SSL cert/key pair // Certificate holds a SSL cert/key pair
// Certs and Key could be either a file path, or the file content itself // Certs and Key could be either a file path, or the file content itself
type Certificate struct { type Certificate struct {
CertFile string CertFile FileOrContent
KeyFile string KeyFile FileOrContent
} }
// Retry contains request retry config // Retry contains request retry config

View file

@ -76,6 +76,13 @@
# #
# InsecureSkipVerify = true # InsecureSkipVerify = true
# Register Certificates in the RootCA. This certificates will be use for backends calls.
# Note: You can use file path or cert content directly
# Optional
# Default: []
#
# RootCAs = [ "/mycert.cert" ]
# Entrypoints to be used by frontends that do not specify any entrypoint. # Entrypoints to be used by frontends that do not specify any entrypoint.
# Each frontend can specify its own entrypoints. # Each frontend can specify its own entrypoints.
# #