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 (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
fmtlog "log"
@ -28,6 +29,7 @@ import (
"github.com/coreos/go-systemd/daemon"
"github.com/docker/libkv/store"
"github.com/satori/go.uuid"
"golang.org/x/net/http2"
)
func main() {
@ -104,6 +106,7 @@ Complete documentation is available at https://traefik.io`,
//add custom parsers
f.AddParser(reflect.TypeOf(server.EntryPoints{}), &server.EntryPoints{})
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(kubernetes.Namespaces{}), &kubernetes.Namespaces{})
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}
}
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 {
// no filename, setting to global config file
if len(traefikConfiguration.ConfigFile) != 0 {

View file

@ -95,6 +95,13 @@
#
# 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.
# 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/http"
"net/http/httptest"
"os"
"time"
"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"))
}
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) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode)

View file

@ -2,8 +2,8 @@ package server
import (
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"os"
"regexp"
"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"`
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"`
RootCAs RootCAs `description:"Add cert file for self-signed certicate"`
Retry *Retry `description:"Enable retry sending request if network error"`
HealthCheck *HealthCheckConfig `description:"Health check parameters"`
Docker *docker.Provider `description:"Enable Docker backend"`
@ -110,7 +111,69 @@ func (dep *DefaultEntryPoints) SetValue(val interface{}) {
// Type is type of the struct
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...)
@ -192,7 +255,7 @@ func (ep *EntryPoints) SetValue(val interface{}) {
// Type is type of the struct
func (ep *EntryPoints) Type() string {
return fmt.Sprint("entrypoints")
return "entrypoints"
}
// 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{}
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, 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{}
var err error
certContent, err := v.CertFile.Read()
if err != nil {
return nil, err
}
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
}
keyContent, err := v.KeyFile.Read()
if err != nil {
return nil, err
}
cert, err = tls.X509KeyPair(certContent, keyContent)
if err != nil {
return nil, err
}
config.Certificates = append(config.Certificates, cert)
}
return config, nil
@ -303,7 +359,7 @@ func (certs *Certificates) String() string {
}
var result []string
for _, certificate := range *certs {
result = append(result, certificate.CertFile+","+certificate.KeyFile)
result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String())
}
return strings.Join(result, ";")
}
@ -319,8 +375,8 @@ func (certs *Certificates) Set(value string) error {
return fmt.Errorf("bad certificates format: %s", value)
}
*certs = append(*certs, Certificate{
CertFile: files[0],
KeyFile: files[1],
CertFile: FileOrContent(files[0]),
KeyFile: FileOrContent(files[1]),
})
}
return nil
@ -328,14 +384,14 @@ func (certs *Certificates) Set(value string) error {
// Type is type of the struct
func (certs *Certificates) Type() string {
return fmt.Sprint("certificates")
return "certificates"
}
// Certificate holds a SSL cert/key pair
// Certs and Key could be either a file path, or the file content itself
type Certificate struct {
CertFile string
KeyFile string
CertFile FileOrContent
KeyFile FileOrContent
}
// Retry contains request retry config

View file

@ -76,6 +76,13 @@
#
# 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.
# Each frontend can specify its own entrypoints.
#