2017-11-09 11:16:03 +00:00
|
|
|
package tls
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
2019-08-03 01:58:23 +00:00
|
|
|
"github.com/containous/traefik/v2/pkg/log"
|
|
|
|
"github.com/containous/traefik/v2/pkg/tls/generate"
|
2017-11-09 11:16:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// MinVersion Map of allowed TLS minimum versions
|
|
|
|
MinVersion = map[string]uint16{
|
|
|
|
`VersionTLS10`: tls.VersionTLS10,
|
|
|
|
`VersionTLS11`: tls.VersionTLS11,
|
|
|
|
`VersionTLS12`: tls.VersionTLS12,
|
2019-03-01 10:48:04 +00:00
|
|
|
`VersionTLS13`: tls.VersionTLS13,
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CipherSuites Map of TLS CipherSuites from crypto/tls
|
|
|
|
// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants
|
|
|
|
CipherSuites = map[string]uint16{
|
|
|
|
`TLS_RSA_WITH_RC4_128_SHA`: tls.TLS_RSA_WITH_RC4_128_SHA,
|
|
|
|
`TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
|
|
|
`TLS_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
|
|
|
`TLS_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
|
|
`TLS_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
|
|
|
`TLS_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
|
|
|
`TLS_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
|
|
|
`TLS_ECDHE_ECDSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
|
|
|
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
|
|
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
|
|
`TLS_ECDHE_RSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
|
|
|
`TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
|
|
|
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
|
|
`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
|
|
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
|
|
|
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
|
|
|
`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
|
|
`TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
|
|
`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
|
|
`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
|
|
`TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
|
|
`TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
2019-03-01 10:48:04 +00:00
|
|
|
"TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
|
|
|
|
"TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384,
|
|
|
|
"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
|
|
|
|
"TLS_FALLBACK_SCSV": tls.TLS_FALLBACK_SCSV,
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Certificate holds a SSL cert/key pair
|
|
|
|
// Certs and Key could be either a file path, or the file content itself
|
|
|
|
type Certificate struct {
|
2019-07-01 09:30:05 +00:00
|
|
|
CertFile FileOrContent `json:"certFile,omitempty" toml:"certFile,omitempty" yaml:"certFile,omitempty"`
|
|
|
|
KeyFile FileOrContent `json:"keyFile,omitempty" toml:"keyFile,omitempty" yaml:"keyFile,omitempty"`
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Certificates defines traefik certificates type
|
|
|
|
// Certs and Keys could be either a file path, or the file content itself
|
|
|
|
type Certificates []Certificate
|
|
|
|
|
|
|
|
// FileOrContent hold a file path or content
|
|
|
|
type FileOrContent string
|
|
|
|
|
|
|
|
func (f FileOrContent) String() string {
|
|
|
|
return string(f)
|
|
|
|
}
|
|
|
|
|
2017-12-08 10:02:03 +00:00
|
|
|
// IsPath returns true if the FileOrContent is a file path, otherwise returns false
|
|
|
|
func (f FileOrContent) IsPath() bool {
|
|
|
|
_, err := os.Stat(f.String())
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
2017-11-09 11:16:03 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateTLSConfig creates a TLS config from Certificate structures
|
2018-03-05 19:54:04 +00:00
|
|
|
func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, error) {
|
2017-11-09 11:16:03 +00:00
|
|
|
config := &tls.Config{}
|
2018-03-06 09:12:04 +00:00
|
|
|
domainsCertificates := make(map[string]map[string]*tls.Certificate)
|
2018-04-23 08:54:03 +00:00
|
|
|
|
2017-11-09 11:16:03 +00:00
|
|
|
if c.isEmpty() {
|
2018-03-05 19:54:04 +00:00
|
|
|
config.Certificates = []tls.Certificate{}
|
2018-04-23 08:54:03 +00:00
|
|
|
|
2017-11-09 11:16:03 +00:00
|
|
|
cert, err := generate.DefaultCertificate()
|
|
|
|
if err != nil {
|
2018-03-05 19:54:04 +00:00
|
|
|
return nil, err
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
2018-04-23 08:54:03 +00:00
|
|
|
|
2017-11-09 11:16:03 +00:00
|
|
|
config.Certificates = append(config.Certificates, *cert)
|
|
|
|
} else {
|
|
|
|
for _, certificate := range *c {
|
2019-04-15 10:32:03 +00:00
|
|
|
err := certificate.AppendCertificate(domainsCertificates, entryPointName)
|
2017-11-09 11:16:03 +00:00
|
|
|
if err != nil {
|
2018-02-22 13:38:04 +00:00
|
|
|
log.Errorf("Unable to add a certificate to the entryPoint %q : %v", entryPointName, err)
|
|
|
|
continue
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
2018-04-23 08:54:03 +00:00
|
|
|
|
2017-11-09 11:16:03 +00:00
|
|
|
for _, certDom := range domainsCertificates {
|
2018-04-23 08:54:03 +00:00
|
|
|
for _, cert := range certDom {
|
2017-11-09 11:16:03 +00:00
|
|
|
config.Certificates = append(config.Certificates, *cert)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-05 19:54:04 +00:00
|
|
|
return config, nil
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// isEmpty checks if the certificates list is empty
|
|
|
|
func (c *Certificates) isEmpty() bool {
|
|
|
|
if len(*c) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
var key int
|
|
|
|
for _, cert := range *c {
|
|
|
|
if len(cert.CertFile.String()) != 0 && len(cert.KeyFile.String()) != 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
key++
|
|
|
|
}
|
|
|
|
return key == len(*c)
|
|
|
|
}
|
|
|
|
|
2019-04-15 10:32:03 +00:00
|
|
|
// AppendCertificate appends a Certificate to a certificates map keyed by entrypoint.
|
|
|
|
func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certificate, ep string) error {
|
2017-11-09 11:16:03 +00:00
|
|
|
|
|
|
|
certContent, err := c.CertFile.Read()
|
|
|
|
if err != nil {
|
2018-02-22 13:38:04 +00:00
|
|
|
return fmt.Errorf("unable to read CertFile : %v", err)
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
keyContent, err := c.KeyFile.Read()
|
|
|
|
if err != nil {
|
2018-03-05 19:54:04 +00:00
|
|
|
return fmt.Errorf("unable to read KeyFile : %v", err)
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
|
|
|
tlsCert, err := tls.X509KeyPair(certContent, keyContent)
|
|
|
|
if err != nil {
|
2018-02-22 13:38:04 +00:00
|
|
|
return fmt.Errorf("unable to generate TLS certificate : %v", err)
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0])
|
|
|
|
|
2018-07-06 08:30:03 +00:00
|
|
|
var SANs []string
|
|
|
|
if parsedCert.Subject.CommonName != "" {
|
2018-11-26 09:38:03 +00:00
|
|
|
SANs = append(SANs, strings.ToLower(parsedCert.Subject.CommonName))
|
2018-07-06 08:30:03 +00:00
|
|
|
}
|
2017-11-09 11:16:03 +00:00
|
|
|
if parsedCert.DNSNames != nil {
|
|
|
|
sort.Strings(parsedCert.DNSNames)
|
2018-01-29 09:48:03 +00:00
|
|
|
for _, dnsName := range parsedCert.DNSNames {
|
|
|
|
if dnsName != parsedCert.Subject.CommonName {
|
2018-11-26 09:38:03 +00:00
|
|
|
SANs = append(SANs, strings.ToLower(dnsName))
|
2018-07-06 08:30:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
if parsedCert.IPAddresses != nil {
|
|
|
|
for _, ip := range parsedCert.IPAddresses {
|
|
|
|
if ip.String() != parsedCert.Subject.CommonName {
|
2018-11-26 09:38:03 +00:00
|
|
|
SANs = append(SANs, strings.ToLower(ip.String()))
|
2018-01-29 09:48:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
2018-07-06 08:30:03 +00:00
|
|
|
certKey := strings.Join(SANs, ",")
|
2017-11-09 11:16:03 +00:00
|
|
|
|
|
|
|
certExists := false
|
|
|
|
if certs[ep] == nil {
|
2018-03-06 09:12:04 +00:00
|
|
|
certs[ep] = make(map[string]*tls.Certificate)
|
2017-11-09 11:16:03 +00:00
|
|
|
} else {
|
2018-03-06 09:12:04 +00:00
|
|
|
for domains := range certs[ep] {
|
2017-11-09 11:16:03 +00:00
|
|
|
if domains == certKey {
|
|
|
|
certExists = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if certExists {
|
2019-04-15 10:32:03 +00:00
|
|
|
log.Warnf("Skipping addition of certificate for domain(s) %q, to EntryPoint %s, as it already exists for this Entrypoint.", certKey, ep)
|
2017-11-09 11:16:03 +00:00
|
|
|
} else {
|
2019-04-15 10:32:03 +00:00
|
|
|
log.Debugf("Adding certificate for domain(s) %s", certKey)
|
2018-03-06 09:12:04 +00:00
|
|
|
certs[ep][certKey] = &tlsCert
|
2017-11-09 11:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-03-14 08:30:04 +00:00
|
|
|
// GetTruncatedCertificateName truncates the certificate name
|
|
|
|
func (c *Certificate) GetTruncatedCertificateName() string {
|
2018-10-16 09:00:04 +00:00
|
|
|
certName := c.CertFile.String()
|
|
|
|
|
|
|
|
// Truncate certificate information only if it's a well formed certificate content with more than 50 characters
|
|
|
|
if !c.CertFile.IsPath() && strings.HasPrefix(certName, certificateHeader) && len(certName) > len(certificateHeader)+50 {
|
|
|
|
certName = strings.TrimPrefix(c.CertFile.String(), certificateHeader)[:50]
|
|
|
|
}
|
|
|
|
|
|
|
|
return certName
|
|
|
|
}
|
|
|
|
|
2017-11-09 11:16:03 +00:00
|
|
|
// 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 (c *Certificates) String() string {
|
|
|
|
if len(*c) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
var result []string
|
|
|
|
for _, certificate := range *c {
|
|
|
|
result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String())
|
|
|
|
}
|
|
|
|
return strings.Join(result, ";")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 (c *Certificates) Set(value string) error {
|
|
|
|
certificates := strings.Split(value, ";")
|
|
|
|
for _, certificate := range certificates {
|
|
|
|
files := strings.Split(certificate, ",")
|
|
|
|
if len(files) != 2 {
|
|
|
|
return fmt.Errorf("bad certificates format: %s", value)
|
|
|
|
}
|
|
|
|
*c = append(*c, Certificate{
|
|
|
|
CertFile: FileOrContent(files[0]),
|
|
|
|
KeyFile: FileOrContent(files[1]),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type is type of the struct
|
|
|
|
func (c *Certificates) Type() string {
|
|
|
|
return "certificates"
|
|
|
|
}
|