chronograf/server/config/tls_options.go

159 lines
5.3 KiB
Go

package config
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"os"
"sort"
"strings"
)
// TLSOptions specifies several key options that create TLS Configuration
type TLSOptions struct {
// Cert contains path to PEM encoded public key certificate
Cert string
// Key contains Path to private key associated with given certificate.
Key string
// Ciphers is a preference list of supported ciphers, empty list means default
Ciphers []string
// MinVersion of TLS to be negotiated with the client ("1.0", "1.1" ...), no value means no minimum.
MinVersion string
// MaxVersion of TLS to be negotiated with the client, no value means no maximum.
MaxVersion string
// CACerts contains Path to CA certificates
CACerts string
// CertOptional controls whether Cert is optional or not
CertOptional bool
}
var ciphersMap = map[string]uint16{
// TLS 1.0 - 1.2 cipher suites.
"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,
// TLS 1.3 cipher suites
"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,
}
var versionsMap = map[string]uint16{
"1.0": tls.VersionTLS10,
"1.1": tls.VersionTLS11,
"1.2": tls.VersionTLS12,
"1.3": tls.VersionTLS13,
}
// CreateTLSConfig creates TLS configuration out of specific TLS
func CreateTLSConfig(o TLSOptions) (out *tls.Config, err error) {
// load key pair
if o.Cert == "" && !o.CertOptional {
return nil, errors.New("no TLS certificate specified")
}
key := o.Key
if key == "" {
key = o.Cert // If no key is specified, we assume it is in the cert
}
out = new(tls.Config)
if o.Cert != "" {
cert, err := tls.LoadX509KeyPair(o.Cert, key)
if err != nil {
return nil, err
}
out.Certificates = []tls.Certificate{cert}
}
// CA certs
if o.CACerts != "" {
f, err := os.Open(o.CACerts)
if err != nil {
return nil, err
}
defer f.Close()
certPool := x509.NewCertPool()
certs, err := ioutil.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("error reading CA certificates: %s", err.Error())
}
ok := certPool.AppendCertsFromPEM(certs)
if !ok {
return nil, fmt.Errorf("error appending CA certificates from %s", o.CACerts)
}
out.RootCAs = certPool
}
// ciphers
if len(o.Ciphers) > 0 {
for _, name := range o.Ciphers {
cipherName := strings.ToUpper(strings.Trim(name, " "))
if len(cipherName) == 0 {
continue
}
cipher, ok := ciphersMap[cipherName]
if !ok {
if cipherName == "HELP" {
return nil, fmt.Errorf("available ciphers are: %s",
availableKeys(ciphersMap))
}
return nil, fmt.Errorf("unknown cipher suite: %q. available ciphers: %s",
cipherName, availableKeys(ciphersMap))
}
out.CipherSuites = append(out.CipherSuites, cipher)
}
out.PreferServerCipherSuites = true
}
if o.MinVersion != "" {
version, ok := versionsMap[o.MinVersion]
if !ok {
return nil, fmt.Errorf("unknown minimum TLS version: %q. available versions: %s",
o.MinVersion, availableKeys(versionsMap))
}
out.MinVersion = version
}
if o.MaxVersion != "" {
version, ok := versionsMap[o.MaxVersion]
if !ok {
return nil, fmt.Errorf("unknown maximum TLS version: %q. available versions: %s",
o.MaxVersion, availableKeys(versionsMap))
}
out.MaxVersion = version
}
return out, nil
}
func availableKeys(keySource map[string]uint16) string {
available := make([]string, 0, len(keySource))
for name := range keySource {
available = append(available, name)
}
sort.Strings(available)
return strings.Join(available, ", ")
}