2017-11-09 11:16:03 +00:00
package tls
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"sort"
"strings"
"github.com/containous/traefik/log"
"github.com/containous/traefik/tls/generate"
)
var (
// MinVersion Map of allowed TLS minimum versions
MinVersion = map [ string ] uint16 {
` VersionTLS10 ` : tls . VersionTLS10 ,
` VersionTLS11 ` : tls . VersionTLS11 ,
` VersionTLS12 ` : tls . VersionTLS12 ,
}
// 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 ,
}
)
// 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 FileOrContent
KeyFile FileOrContent
}
// 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 )
2017-11-09 11:16:03 +00:00
if c . isEmpty ( ) {
2018-03-05 19:54:04 +00:00
config . Certificates = [ ] tls . Certificate { }
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
}
config . Certificates = append ( config . Certificates , * cert )
} else {
for _ , certificate := range * c {
err := certificate . AppendCertificates ( domainsCertificates , entryPointName )
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
}
for _ , certDom := range domainsCertificates {
2018-03-06 09:12:04 +00:00
for _ , cert := range map [ string ] * tls . Certificate ( 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 )
}
// AppendCertificates appends a Certificate to a certificates map sorted by entrypoints
2018-03-06 09:12:04 +00:00
func ( c * Certificate ) AppendCertificates ( 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 ] )
certKey := parsedCert . Subject . CommonName
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 {
certKey += fmt . Sprintf ( ",%s" , dnsName )
}
}
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 {
2017-12-08 10:02:03 +00:00
log . Warnf ( "Into EntryPoint %s, try to add certificate for domains which already have this certificate (%s). The new certificate will not be append to the EntryPoint." , ep , certKey )
2017-11-09 11:16:03 +00:00
} else {
log . Debugf ( "Add certificate for domains %s" , certKey )
2018-03-06 09:12:04 +00:00
certs [ ep ] [ certKey ] = & tlsCert
2017-11-09 11:16:03 +00:00
}
return err
}
// 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"
}