From be00ca79c0aec2e5679d0b473f8dbbbdbdc7a487 Mon Sep 17 00:00:00 2001 From: Julien Riou Date: Sat, 13 Apr 2019 16:30:04 +0200 Subject: [PATCH] Add TLS configuration Golang HTTPS server isn't secure by default. This commit introduces TLS minimum version and ciphers list to set up a secure TLS service. Signed-off-by: Julien Riou --- VERSION | 2 +- config.yml.example | 8 ++++ src/config.go | 12 ++--- src/frontend.go | 108 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 113 insertions(+), 17 deletions(-) diff --git a/VERSION b/VERSION index afaf360..1cc5f65 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 \ No newline at end of file +1.1.0 \ No newline at end of file diff --git a/config.yml.example b/config.yml.example index a68a7ec..d3ca27a 100644 --- a/config.yml.example +++ b/config.yml.example @@ -4,6 +4,14 @@ frontend: port: 8443 certfile: /path/to/certificate.pem keyfile: /pat/to/keyfile.key + tls-ciphers: + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 + tls-min-version: TLSv1.1 backend: host: 127.0.0.1 port: 8008 diff --git a/src/config.go b/src/config.go index 6be99bf..c156048 100644 --- a/src/config.go +++ b/src/config.go @@ -17,11 +17,13 @@ type Config struct { // FrontendConfig for storing Frontend settings type FrontendConfig struct { - Host string `yaml:"host"` - Port int `yaml:"port"` - Certfile string `yaml:"certfile"` - Keyfile string `yaml:"keyfile"` - LogFormat string `yaml:"logformat"` + Host string `yaml:"host"` + Port int `yaml:"port"` + Certfile string `yaml:"certfile"` + Keyfile string `yaml:"keyfile"` + TLSMinVersion string `yaml:"tls-min-version"` + TLSCiphers []string `yaml:"tls-ciphers"` + LogFormat string `yaml:"logformat"` } // BackendConfig for storing Backend settings diff --git a/src/frontend.go b/src/frontend.go index 2e2f303..f9944ea 100644 --- a/src/frontend.go +++ b/src/frontend.go @@ -1,6 +1,7 @@ package main import ( + "crypto/tls" "fmt" "io" "net/http" @@ -11,11 +12,13 @@ import ( // Frontend exposes statuses over HTTP(S) type Frontend struct { - backend Backend - host string - port int - certfile string - keyfile string + backend Backend + host string + port int + certfile string + keyfile string + tlsMinVersion string + tlsCiphers []string } var backend Backend @@ -35,10 +38,30 @@ func (f *Frontend) Start() error { Info("listening on %s", f) var err error + server := &http.Server{ + Addr: f.String(), + Handler: r, + } if f.certfile != "" && f.keyfile != "" { - err = http.ListenAndServeTLS(f.String(), f.certfile, f.keyfile, r) + config := &tls.Config{} + if f.tlsMinVersion != "" { + config.MinVersion, err = parseTLSVersion(f.tlsMinVersion) + if err != nil { + return err + } + } + if len(f.tlsCiphers) > 0 { + ciphers, err := parseCiphersSuite(f.tlsCiphers) + if err != nil { + return err + } + config.CipherSuites = ciphers + } + + server.TLSConfig = config + err = server.ListenAndServeTLS(f.certfile, f.keyfile) } else { - err = http.ListenAndServe(f.String(), r) + err = server.ListenAndServe() } if err != nil { @@ -57,10 +80,12 @@ func NewFrontend(config FrontendConfig, b Backend) (*Frontend, error) { backend = b logFormat = config.LogFormat return &Frontend{ - host: config.Host, - port: config.Port, - certfile: config.Certfile, - keyfile: config.Keyfile, + host: config.Host, + port: config.Port, + certfile: config.Certfile, + keyfile: config.Keyfile, + tlsMinVersion: config.TLSMinVersion, + tlsCiphers: config.TLSCiphers, }, nil } @@ -140,3 +165,64 @@ func ReplicaHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(status) io.WriteString(w, message) } + +// Store TLS ciphers map from string to constant +// See full list at https://golang.org/pkg/crypto/tls/#pkg-constants +var tlsCiphers = 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, + "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, +} + +// Convert a list of ciphers from string to TLS constants +func parseCiphersSuite(strings []string) (ciphers []uint16, err error) { + for _, s := range strings { + if cipher, ok := tlsCiphers[s]; ok { + ciphers = append(ciphers, cipher) + } else { + return nil, fmt.Errorf("unknown cipher detected: %s", s) + } + } + return ciphers, nil +} + +// Store TLS versions map from string to constant +// See full list at https://golang.org/pkg/crypto/tls/#pkg-constants +var tlsVersions = map[string]uint16{ + "SSLv3.0": tls.VersionSSL30, + "TLSv1.0": tls.VersionTLS10, + "TLSv1.1": tls.VersionTLS11, + "TLSv1.2": tls.VersionTLS12, + "TLSv1.3": tls.VersionTLS13, +} + +// Convert a list of ciphers from string to TLS constants +func parseTLSVersion(s string) (uint16, error) { + if version, ok := tlsVersions[s]; ok { + return version, nil + } + return 0, fmt.Errorf("unknown TLS version: %s", s) +}