Skip to content

Commit

Permalink
Add possibility to setting up tls security levels
Browse files Browse the repository at this point in the history
Feature request: #5914

- tls_cipher_suites support was added for http server configuration
- tls_min_version/tls_max_version support was added for http server configuration
  • Loading branch information
Stan Putrya committed Aug 12, 2019
1 parent 23b8655 commit 41a7d73
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 1 deletion.
36 changes: 36 additions & 0 deletions internal/tls/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/x509"
"fmt"
"io/ioutil"
"strings"
)

// ClientConfig represents the standard client TLS config.
Expand All @@ -25,6 +26,9 @@ type ServerConfig struct {
TLSCert string `toml:"tls_cert"`
TLSKey string `toml:"tls_key"`
TLSAllowedCACerts []string `toml:"tls_allowed_cacerts"`
TLSCipherSuites []string `toml:"tls_cipher_suites"`
TLSMinVersion string `toml:"tls_min_version"`
TLSMaxVersion string `toml:"tls_max_version"`
}

// TLSConfig returns a tls.Config, may be nil without error if TLS is not
Expand Down Expand Up @@ -97,6 +101,38 @@ func (c *ServerConfig) TLSConfig() (*tls.Config, error) {
}
}

if len(c.TLSCipherSuites) != 0 {
cipherSuites, err := ParseCiphers(c.TLSCipherSuites)
if err != nil {
return nil, fmt.Errorf(
"could not parse server cipher suites %s: %v", strings.Join(c.TLSCipherSuites, ","), err)
}
tlsConfig.CipherSuites = cipherSuites
}

if c.TLSMaxVersion != "" {
version, err := ParseTLSVersion(c.TLSMaxVersion)
if err != nil {
return nil, fmt.Errorf(
"could not parse tls max version %q: %v", c.TLSMaxVersion, err)
}
tlsConfig.MaxVersion = version
}

if c.TLSMinVersion != "" {
version, err := ParseTLSVersion(c.TLSMinVersion)
if err != nil {
return nil, fmt.Errorf(
"could not parse tls min version %q: %v", c.TLSMinVersion, err)
}
tlsConfig.MinVersion = version
}

if tlsConfig.MinVersion != 0 && tlsConfig.MaxVersion != 0 && tlsConfig.MinVersion > tlsConfig.MaxVersion {
return nil, fmt.Errorf(
"tls min version %q can't be greater then tls max version %q", tlsConfig.MinVersion, tlsConfig.MaxVersion)
}

return tlsConfig, nil
}

Expand Down
91 changes: 91 additions & 0 deletions internal/tls/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,47 @@ func TestServerConfig(t *testing.T) {
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CipherSuite()},
TLSMinVersion: pki.TLSMinVersion(),
TLSMaxVersion: pki.TLSMaxVersion(),
},
},
{
name: "missing tls cipher suites is okay",
server: tls.ServerConfig{
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CipherSuite()},
},
},
{
name: "missing tls max version is okay",
server: tls.ServerConfig{
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CipherSuite()},
TLSMaxVersion: pki.TLSMaxVersion(),
},
},
{
name: "missing tls min version is okay",
server: tls.ServerConfig{
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CipherSuite()},
TLSMinVersion: pki.TLSMinVersion(),
},
},
{
name: "missing tls min/max versions is okay",
server: tls.ServerConfig{
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CipherSuite()},
},
},
{
Expand Down Expand Up @@ -172,6 +213,56 @@ func TestServerConfig(t *testing.T) {
expNil: true,
expErr: true,
},
{
name: "invalid cipher suites",
server: tls.ServerConfig{
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CACertPath()},
},
expNil: true,
expErr: true,
},
{
name: "TLS Max Version less then TLS Min version",
server: tls.ServerConfig{
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CACertPath()},
TLSMinVersion: pki.TLSMaxVersion(),
TLSMaxVersion: pki.TLSMinVersion(),
},
expNil: true,
expErr: true,
},
{
name: "invalid tls min version",
server: tls.ServerConfig{
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CipherSuite()},
TLSMinVersion: pki.ServerKeyPath(),
TLSMaxVersion: pki.TLSMaxVersion(),
},
expNil: true,
expErr: true,
},
{
name: "invalid tls max version",
server: tls.ServerConfig{
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CACertPath()},
TLSMinVersion: pki.TLSMinVersion(),
TLSMaxVersion: pki.ServerCertPath(),
},
expNil: true,
expErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
59 changes: 59 additions & 0 deletions internal/tls/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package tls

import (
"crypto/tls"
"fmt"
)

func ParseCiphers(ciphers []string) ([]uint16, error) {
suites := []uint16{}

cipherMap := map[string]uint16{
"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_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_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_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_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"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_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"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_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
}
for _, cipher := range ciphers {
if v, ok := cipherMap[cipher]; ok {
suites = append(suites, v)
} else {
return suites, fmt.Errorf("unsupported cipher %q", cipher)
}
}

return suites, nil
}

func ParseTLSVersion(version string) (uint16, error) {
versionMap := map[string]uint16{
"SSL30": tls.VersionSSL30,
"TLS10": tls.VersionTLS10,
"TLS11": tls.VersionTLS11,
"TLS12": tls.VersionTLS12,
// for go >= 1.12
//"TLS13": tls.VersionTLS13,
}
if v, ok := versionMap[version]; ok {
return v, nil
}
return 0, fmt.Errorf("unsupported version %q", version)
}
36 changes: 36 additions & 0 deletions plugins/outputs/prometheus_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,42 @@ This plugin starts a [Prometheus](https://prometheus.io/) Client, it exposes all
## enable mutually authenticated TLS connections
# tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]

## Use only tls ciphers defined in this list
## Possible values:
## TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
## TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
## TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
## TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
## TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
## TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
## TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
## TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
## TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
## TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
## TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
## TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
## TLS_RSA_WITH_AES_128_GCM_SHA256
## TLS_RSA_WITH_AES_256_GCM_SHA384
## TLS_RSA_WITH_AES_128_CBC_SHA256
## TLS_RSA_WITH_AES_128_CBC_SHA
## TLS_RSA_WITH_AES_256_CBC_SHA
## TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
## TLS_RSA_WITH_3DES_EDE_CBC_SHA
## TLS_RSA_WITH_RC4_128_SHA
## TLS_ECDHE_RSA_WITH_RC4_128_SHA
## TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
## If value wasn't defined default will be used
# tls_cipher_suites = ["TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"]

## contains the minimum SSL/TLS version that is acceptable.
## If not set, then TLS 1.0 is taken as the minimum.
# tls_min_version = "TLS11"

## contains the maximum SSL/TLS version that is acceptable.
## If not set, then the maximum version supported by this package is used,
## which is currently TLS 1.2 (for go < 1.12) or TLS 1.3 (for go >= 1.12).
# tls_max_version = "TLS12"

## Export metric collection time.
# export_timestamp = false
```
2 changes: 2 additions & 0 deletions plugins/outputs/prometheus_client/prometheus_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ var sampleConfig = `
## enable mutually authenticated TLS connections
# tls_allowed_cacerts = ["/etc/telegraf/clientca.pem"]
# tls_cipher_suites = ["TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"]
## Export metric collection time.
# export_timestamp = false
`
Expand Down
15 changes: 14 additions & 1 deletion plugins/outputs/prometheus_client/prometheus_client_tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"testing"

inttls "github.com/influxdata/telegraf/internal/tls"
"github.com/influxdata/telegraf/plugins/outputs/prometheus_client"
"github.com/influxdata/telegraf/testutil"
"github.com/influxdata/toml"
Expand All @@ -19,7 +20,9 @@ var configWithTLS = fmt.Sprintf(`
tls_allowed_cacerts = ["%s"]
tls_cert = "%s"
tls_key = "%s"
`, pki.TLSServerConfig().TLSAllowedCACerts[0], pki.TLSServerConfig().TLSCert, pki.TLSServerConfig().TLSKey)
tls_cipher_suites = ["%s"]
tls_min_version = "%s"
`, pki.TLSServerConfig().TLSAllowedCACerts[0], pki.TLSServerConfig().TLSCert, pki.TLSServerConfig().TLSKey, pki.CipherSuite(), pki.TLSMaxVersion())

var configWithoutTLS = `
listen = "127.0.0.1:0"
Expand Down Expand Up @@ -50,12 +53,22 @@ func TestWorksWithTLS(t *testing.T) {
require.NoError(t, err)
defer tc.Output.Close()

serverCiphers, err := inttls.ParseCiphers(tc.Output.ServerConfig.TLSCipherSuites)
require.NoError(t, err)
require.Equal(t, 1, len(serverCiphers))

tlsVersion, err := inttls.ParseTLSVersion(tc.Output.ServerConfig.TLSMinVersion)
require.NoError(t, err)

response, err := tc.Client.Get(tc.Output.URL())
require.NoError(t, err)

require.NoError(t, err)
require.Equal(t, response.StatusCode, http.StatusOK)

require.Equal(t, response.TLS.CipherSuite, serverCiphers[0])
require.Equal(t, response.TLS.Version, tlsVersion)

tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
Expand Down
15 changes: 15 additions & 0 deletions testutil/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ func (p *pki) TLSServerConfig() *tls.ServerConfig {
TLSAllowedCACerts: []string{p.CACertPath()},
TLSCert: p.ServerCertPath(),
TLSKey: p.ServerKeyPath(),
TLSCipherSuites: []string{p.CipherSuite()},
TLSMinVersion: p.TLSMinVersion(),
TLSMaxVersion: p.TLSMaxVersion(),
}
}

Expand All @@ -41,6 +44,18 @@ func (p *pki) CACertPath() string {
return path.Join(p.path, "cacert.pem")
}

func (p *pki) CipherSuite() string {
return "TLS_RSA_WITH_3DES_EDE_CBC_SHA"
}

func (p *pki) TLSMinVersion() string {
return "TLS11"
}

func (p *pki) TLSMaxVersion() string {
return "TLS12"
}

func (p *pki) ReadClientCert() string {
return readCertificate(p.ClientCertPath())
}
Expand Down

0 comments on commit 41a7d73

Please sign in to comment.