Handle RootCAs Certificate
This commit is contained in:
parent
3776e58041
commit
81d011e57d
8 changed files with 245 additions and 32 deletions
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
#
|
||||
|
|
41
integration/fixtures/https/rootcas/https.toml
Normal file
41
integration/fixtures/https/rootcas/https.toml
Normal 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"
|
25
integration/fixtures/https/rootcas/https_with_file.toml
Normal file
25
integration/fixtures/https/rootcas/https_with_file.toml
Normal 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"
|
14
integration/fixtures/https/rootcas/local.crt
Normal file
14
integration/fixtures/https/rootcas/local.crt
Normal 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-----
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
#
|
||||
|
|
Loading…
Reference in a new issue