Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
24
plugins/common/tls/client.conf
Normal file
24
plugins/common/tls/client.conf
Normal file
|
@ -0,0 +1,24 @@
|
|||
## Set to true/false to enforce TLS being enabled/disabled. If not set,
|
||||
## enable TLS only if any of the other options are specified.
|
||||
# tls_enable =
|
||||
## Trusted root certificates for server
|
||||
# tls_ca = "/path/to/cafile"
|
||||
## Used for TLS client certificate authentication
|
||||
# tls_cert = "/path/to/certfile"
|
||||
## Used for TLS client certificate authentication
|
||||
# tls_key = "/path/to/keyfile"
|
||||
## Password for the key file if it is encrypted
|
||||
# tls_key_pwd = ""
|
||||
## Send the specified TLS server name via SNI
|
||||
# tls_server_name = "kubernetes.example.com"
|
||||
## Minimal TLS version to accept by the client
|
||||
# tls_min_version = "TLS12"
|
||||
## List of ciphers to accept, by default all secure ciphers will be accepted
|
||||
## See https://pkg.go.dev/crypto/tls#pkg-constants for supported values.
|
||||
## Use "all", "secure" and "insecure" to add all support ciphers, secure
|
||||
## suites or insecure suites respectively.
|
||||
# tls_cipher_suites = ["secure"]
|
||||
## Renegotiation method, "never", "once" or "freely"
|
||||
# tls_renegotiation_method = "never"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
34
plugins/common/tls/common.go
Normal file
34
plugins/common/tls/common.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var tlsVersionMap = map[string]uint16{
|
||||
"TLS10": tls.VersionTLS10,
|
||||
"TLS11": tls.VersionTLS11,
|
||||
"TLS12": tls.VersionTLS12,
|
||||
"TLS13": tls.VersionTLS13,
|
||||
}
|
||||
|
||||
var tlsCipherMapInit sync.Once
|
||||
var tlsCipherMapSecure map[string]uint16
|
||||
var tlsCipherMapInsecure map[string]uint16
|
||||
|
||||
func init() {
|
||||
tlsCipherMapInit.Do(func() {
|
||||
// Initialize the secure suites
|
||||
suites := tls.CipherSuites()
|
||||
tlsCipherMapSecure = make(map[string]uint16, len(suites))
|
||||
for _, s := range suites {
|
||||
tlsCipherMapSecure[s.Name] = s.ID
|
||||
}
|
||||
|
||||
suites = tls.InsecureCipherSuites()
|
||||
tlsCipherMapInsecure = make(map[string]uint16, len(suites))
|
||||
for _, s := range suites {
|
||||
tlsCipherMapInsecure[s.Name] = s.ID
|
||||
}
|
||||
})
|
||||
}
|
298
plugins/common/tls/config.go
Normal file
298
plugins/common/tls/config.go
Normal file
|
@ -0,0 +1,298 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
||||
"github.com/influxdata/telegraf/internal/choice"
|
||||
)
|
||||
|
||||
const TLSMinVersionDefault = tls.VersionTLS12
|
||||
|
||||
// ClientConfig represents the standard client TLS config.
|
||||
type ClientConfig struct {
|
||||
TLSCA string `toml:"tls_ca"`
|
||||
TLSCert string `toml:"tls_cert"`
|
||||
TLSKey string `toml:"tls_key"`
|
||||
TLSKeyPwd string `toml:"tls_key_pwd"`
|
||||
TLSMinVersion string `toml:"tls_min_version"`
|
||||
TLSCipherSuites []string `toml:"tls_cipher_suites"`
|
||||
InsecureSkipVerify bool `toml:"insecure_skip_verify"`
|
||||
ServerName string `toml:"tls_server_name"`
|
||||
RenegotiationMethod string `toml:"tls_renegotiation_method"`
|
||||
Enable *bool `toml:"tls_enable"`
|
||||
|
||||
SSLCA string `toml:"ssl_ca" deprecated:"1.7.0;1.35.0;use 'tls_ca' instead"`
|
||||
SSLCert string `toml:"ssl_cert" deprecated:"1.7.0;1.35.0;use 'tls_cert' instead"`
|
||||
SSLKey string `toml:"ssl_key" deprecated:"1.7.0;1.35.0;use 'tls_key' instead"`
|
||||
}
|
||||
|
||||
// ServerConfig represents the standard server TLS config.
|
||||
type ServerConfig struct {
|
||||
TLSCert string `toml:"tls_cert"`
|
||||
TLSKey string `toml:"tls_key"`
|
||||
TLSKeyPwd string `toml:"tls_key_pwd"`
|
||||
TLSAllowedCACerts []string `toml:"tls_allowed_cacerts"`
|
||||
TLSCipherSuites []string `toml:"tls_cipher_suites"`
|
||||
TLSMinVersion string `toml:"tls_min_version"`
|
||||
TLSMaxVersion string `toml:"tls_max_version"`
|
||||
TLSAllowedDNSNames []string `toml:"tls_allowed_dns_names"`
|
||||
}
|
||||
|
||||
// TLSConfig returns a tls.Config, may be nil without error if TLS is not
|
||||
// configured.
|
||||
func (c *ClientConfig) TLSConfig() (*tls.Config, error) {
|
||||
// Check if TLS config is forcefully disabled
|
||||
if c.Enable != nil && !*c.Enable {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Support deprecated variable names
|
||||
if c.TLSCA == "" && c.SSLCA != "" {
|
||||
c.TLSCA = c.SSLCA
|
||||
}
|
||||
if c.TLSCert == "" && c.SSLCert != "" {
|
||||
c.TLSCert = c.SSLCert
|
||||
}
|
||||
if c.TLSKey == "" && c.SSLKey != "" {
|
||||
c.TLSKey = c.SSLKey
|
||||
}
|
||||
|
||||
// This check returns a nil (aka "disabled") or an empty config
|
||||
// (aka, "use the default") if no field is set that would have an effect on
|
||||
// a TLS connection. That is, any of:
|
||||
// * client certificate settings,
|
||||
// * peer certificate authorities,
|
||||
// * disabled security,
|
||||
// * an SNI server name, or
|
||||
// * empty/never renegotiation method
|
||||
empty := c.TLSCA == "" && c.TLSKey == "" && c.TLSCert == ""
|
||||
empty = empty && !c.InsecureSkipVerify && c.ServerName == ""
|
||||
empty = empty && (c.RenegotiationMethod == "" || c.RenegotiationMethod == "never")
|
||||
|
||||
if empty {
|
||||
// Check if TLS config is forcefully enabled and supposed to
|
||||
// use the system defaults.
|
||||
if c.Enable != nil && *c.Enable {
|
||||
return &tls.Config{}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var renegotiationMethod tls.RenegotiationSupport
|
||||
switch c.RenegotiationMethod {
|
||||
case "", "never":
|
||||
renegotiationMethod = tls.RenegotiateNever
|
||||
case "once":
|
||||
renegotiationMethod = tls.RenegotiateOnceAsClient
|
||||
case "freely":
|
||||
renegotiationMethod = tls.RenegotiateFreelyAsClient
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized renegotiation method %q, choose from: 'never', 'once', 'freely'", c.RenegotiationMethod)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||
Renegotiation: renegotiationMethod,
|
||||
}
|
||||
|
||||
if c.TLSCA != "" {
|
||||
pool, err := makeCertPool([]string{c.TLSCA})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.RootCAs = pool
|
||||
}
|
||||
|
||||
if c.TLSCert != "" && c.TLSKey != "" {
|
||||
err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey, c.TLSKeyPwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly and consistently set the minimal accepted version using the
|
||||
// defined default. We use this setting for both clients and servers
|
||||
// instead of relying on Golang's default that is different for clients
|
||||
// and servers and might change over time.
|
||||
tlsConfig.MinVersion = TLSMinVersionDefault
|
||||
if c.TLSMinVersion != "" {
|
||||
version, err := ParseTLSVersion(c.TLSMinVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse tls min version %q: %w", c.TLSMinVersion, err)
|
||||
}
|
||||
tlsConfig.MinVersion = version
|
||||
}
|
||||
|
||||
if c.ServerName != "" {
|
||||
tlsConfig.ServerName = c.ServerName
|
||||
}
|
||||
|
||||
if len(c.TLSCipherSuites) != 0 {
|
||||
cipherSuites, err := ParseCiphers(c.TLSCipherSuites)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse client cipher suites: %w", err)
|
||||
}
|
||||
tlsConfig.CipherSuites = cipherSuites
|
||||
}
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
// TLSConfig returns a tls.Config, may be nil without error if TLS is not
|
||||
// configured.
|
||||
func (c *ServerConfig) TLSConfig() (*tls.Config, error) {
|
||||
if c.TLSCert == "" && c.TLSKey == "" && len(c.TLSAllowedCACerts) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{}
|
||||
|
||||
if len(c.TLSAllowedCACerts) != 0 {
|
||||
pool, err := makeCertPool(c.TLSAllowedCACerts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ClientCAs = pool
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
if c.TLSCert != "" && c.TLSKey != "" {
|
||||
err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey, c.TLSKeyPwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.TLSCipherSuites) != 0 {
|
||||
cipherSuites, err := ParseCiphers(c.TLSCipherSuites)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse server cipher suites: %w", 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: %w", c.TLSMaxVersion, err)
|
||||
}
|
||||
tlsConfig.MaxVersion = version
|
||||
}
|
||||
|
||||
// Explicitly and consistently set the minimal accepted version using the
|
||||
// defined default. We use this setting for both clients and servers
|
||||
// instead of relying on Golang's default that is different for clients
|
||||
// and servers and might change over time.
|
||||
tlsConfig.MinVersion = TLSMinVersionDefault
|
||||
if c.TLSMinVersion != "" {
|
||||
version, err := ParseTLSVersion(c.TLSMinVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse tls min version %q: %w", 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 than tls max version %q", tlsConfig.MinVersion, tlsConfig.MaxVersion)
|
||||
}
|
||||
|
||||
// Since clientAuth is tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
// there must be certs to validate.
|
||||
if len(c.TLSAllowedCACerts) > 0 && len(c.TLSAllowedDNSNames) > 0 {
|
||||
tlsConfig.VerifyPeerCertificate = c.verifyPeerCertificate
|
||||
}
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
func makeCertPool(certFiles []string) (*x509.CertPool, error) {
|
||||
pool := x509.NewCertPool()
|
||||
for _, certFile := range certFiles {
|
||||
cert, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read certificate %q: %w", certFile, err)
|
||||
}
|
||||
if !pool.AppendCertsFromPEM(cert) {
|
||||
return nil, fmt.Errorf("could not parse any PEM certificates %q: %w", certFile, err)
|
||||
}
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func loadCertificate(config *tls.Config, certFile, keyFile, privateKeyPassphrase string) error {
|
||||
certBytes, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load certificate %q: %w", certFile, err)
|
||||
}
|
||||
|
||||
keyBytes, err := os.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load private key %q: %w", keyFile, err)
|
||||
}
|
||||
|
||||
keyPEMBlock, _ := pem.Decode(keyBytes)
|
||||
if keyPEMBlock == nil {
|
||||
return errors.New("failed to decode private key: no PEM data found")
|
||||
}
|
||||
|
||||
var cert tls.Certificate
|
||||
if keyPEMBlock.Type == "ENCRYPTED PRIVATE KEY" {
|
||||
if privateKeyPassphrase == "" {
|
||||
return errors.New("missing password for PKCS#8 encrypted private key")
|
||||
}
|
||||
rawDecryptedKey, err := pemutil.DecryptPKCS8PrivateKey(keyPEMBlock.Bytes, []byte(privateKeyPassphrase))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt PKCS#8 private key: %w", err)
|
||||
}
|
||||
decryptedKey, err := x509.ParsePKCS8PrivateKey(rawDecryptedKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse decrypted PKCS#8 private key: %w", err)
|
||||
}
|
||||
privateKey, ok := decryptedKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("decrypted key is not a RSA private key: %T", decryptedKey)
|
||||
}
|
||||
cert, err = tls.X509KeyPair(certBytes, pem.EncodeToMemory(&pem.Block{Type: keyPEMBlock.Type, Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load cert/key pair: %w", err)
|
||||
}
|
||||
} else if keyPEMBlock.Headers["Proc-Type"] == "4,ENCRYPTED" {
|
||||
// The key is an encrypted private key with the DEK-Info header.
|
||||
// This is currently unsupported because of the deprecation of x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock.
|
||||
return errors.New("password-protected keys in pkcs#1 format are not supported")
|
||||
} else {
|
||||
cert, err = tls.X509KeyPair(certBytes, keyBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load cert/key pair: %w", err)
|
||||
}
|
||||
}
|
||||
config.Certificates = []tls.Certificate{cert}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ServerConfig) verifyPeerCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
// The certificate chain is client + intermediate + root.
|
||||
// Let's review the client certificate.
|
||||
cert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not validate peer certificate: %w", err)
|
||||
}
|
||||
|
||||
for _, name := range cert.DNSNames {
|
||||
if choice.Contains(name, c.TLSAllowedDNSNames) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("peer certificate not in allowed DNS Name list: %v", cert.DNSNames)
|
||||
}
|
614
plugins/common/tls/config_test.go
Normal file
614
plugins/common/tls/config_test.go
Normal file
|
@ -0,0 +1,614 @@
|
|||
package tls_test
|
||||
|
||||
import (
|
||||
cryptotls "crypto/tls"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
var pki = testutil.NewPKI("../../../testutil/pki")
|
||||
|
||||
func TestClientConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
client tls.ClientConfig
|
||||
expNil bool
|
||||
expErr bool
|
||||
}{
|
||||
{
|
||||
name: "unset",
|
||||
client: tls.ClientConfig{},
|
||||
expNil: true,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success with tls key password set",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
TLSKeyPwd: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success with unencrypted pkcs#8 key",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientPKCS8KeyPath(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "encrypted pkcs#8 key but missing password",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientEncPKCS8KeyPath(),
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "encrypted pkcs#8 key and incorrect password",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientEncPKCS8KeyPath(),
|
||||
TLSKeyPwd: "incorrect",
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "success with encrypted pkcs#8 key and password set",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientEncPKCS8KeyPath(),
|
||||
TLSKeyPwd: "changeme",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error with encrypted pkcs#1 key and password set",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientEncKeyPath(),
|
||||
TLSKeyPwd: "changeme",
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "invalid ca",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.ClientKeyPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing ca is okay",
|
||||
client: tls.ClientConfig{
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid cert",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientKeyPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing cert skips client keypair",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
},
|
||||
expNil: false,
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "missing key skips client keypair",
|
||||
client: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
},
|
||||
expNil: false,
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "support deprecated ssl field names",
|
||||
client: tls.ClientConfig{
|
||||
SSLCA: pki.CACertPath(),
|
||||
SSLCert: pki.ClientCertPath(),
|
||||
SSLKey: pki.ClientKeyPath(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set SNI server name",
|
||||
client: tls.ClientConfig{
|
||||
ServerName: "foo.example.com",
|
||||
},
|
||||
expNil: false,
|
||||
expErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tlsConfig, err := tt.client.TLSConfig()
|
||||
if !tt.expNil {
|
||||
require.NotNil(t, tlsConfig)
|
||||
} else {
|
||||
require.Nil(t, tlsConfig)
|
||||
}
|
||||
|
||||
if !tt.expErr {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
server tls.ServerConfig
|
||||
expNil bool
|
||||
expErr bool
|
||||
}{
|
||||
{
|
||||
name: "unset",
|
||||
server: tls.ServerConfig{},
|
||||
expNil: true,
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CipherSuite()},
|
||||
TLSAllowedDNSNames: []string{"localhost", "127.0.0.1"},
|
||||
TLSMinVersion: pki.TLSMinVersion(),
|
||||
TLSMaxVersion: pki.TLSMaxVersion(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success with tls key password set",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSKeyPwd: "",
|
||||
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()},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid ca",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.ServerKeyPath()},
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing allowed ca is okay",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid cert",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerKeyPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing cert",
|
||||
server: tls.ServerConfig{
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing key",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
},
|
||||
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 than 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) {
|
||||
tlsConfig, err := tt.server.TLSConfig()
|
||||
if !tt.expNil {
|
||||
require.NotNil(t, tlsConfig)
|
||||
}
|
||||
if !tt.expErr {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
clientConfig := tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
}
|
||||
|
||||
serverConfig := tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSAllowedDNSNames: []string{"localhost", "127.0.0.1"},
|
||||
}
|
||||
|
||||
serverTLSConfig, err := serverConfig.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
ts.TLS = serverTLSConfig
|
||||
|
||||
ts.StartTLS()
|
||||
defer ts.Close()
|
||||
|
||||
clientTLSConfig, err := clientConfig.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: clientTLSConfig,
|
||||
},
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(ts.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestConnectClientMinTLSVersion(t *testing.T) {
|
||||
serverConfig := tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSAllowedDNSNames: []string{"localhost", "127.0.0.1"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg tls.ClientConfig
|
||||
}{
|
||||
{
|
||||
name: "TLS version default",
|
||||
cfg: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TLS version 1.0",
|
||||
cfg: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
TLSMinVersion: "TLS10",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TLS version 1.1",
|
||||
cfg: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
TLSMinVersion: "TLS11",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TLS version 1.2",
|
||||
cfg: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
TLSMinVersion: "TLS12",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TLS version 1.3",
|
||||
cfg: tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
TLSMinVersion: "TLS13",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tlsVersions := []uint16{
|
||||
cryptotls.VersionTLS10,
|
||||
cryptotls.VersionTLS11,
|
||||
cryptotls.VersionTLS12,
|
||||
cryptotls.VersionTLS13,
|
||||
}
|
||||
|
||||
tlsVersionNames := []string{
|
||||
"TLS 1.0",
|
||||
"TLS 1.1",
|
||||
"TLS 1.2",
|
||||
"TLS 1.3",
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
clientTLSConfig, err := tt.cfg.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: clientTLSConfig,
|
||||
},
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
clientMinVersion := clientTLSConfig.MinVersion
|
||||
if tt.cfg.TLSMinVersion == "" {
|
||||
clientMinVersion = tls.TLSMinVersionDefault
|
||||
}
|
||||
|
||||
for i, serverTLSMaxVersion := range tlsVersions {
|
||||
serverVersionName := tlsVersionNames[i]
|
||||
t.Run(tt.name+" vs "+serverVersionName, func(t *testing.T) {
|
||||
// Constrain the server's maximum TLS version
|
||||
serverTLSConfig, err := serverConfig.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
serverTLSConfig.MinVersion = cryptotls.VersionTLS10
|
||||
serverTLSConfig.MaxVersion = serverTLSMaxVersion
|
||||
|
||||
// Start the server
|
||||
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
ts.TLS = serverTLSConfig
|
||||
ts.StartTLS()
|
||||
|
||||
// Do the connection and cleanup
|
||||
resp, err := client.Get(ts.URL)
|
||||
ts.Close()
|
||||
|
||||
// Things should fail if the currently tested "serverTLSMaxVersion"
|
||||
// is below the client minimum version.
|
||||
if serverTLSMaxVersion < clientMinVersion {
|
||||
require.ErrorContains(t, err, "tls: protocol version not supported")
|
||||
} else {
|
||||
require.NoErrorf(t, err, "server=%v client=%v", serverTLSMaxVersion, clientMinVersion)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
resp.Body.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectClientInvalidMinTLSVersion(t *testing.T) {
|
||||
clientConfig := tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
TLSMinVersion: "garbage",
|
||||
}
|
||||
|
||||
_, err := clientConfig.TLSConfig()
|
||||
expected := `could not parse tls min version "garbage": unsupported version "garbage" (available: TLS10,TLS11,TLS12,TLS13)`
|
||||
require.EqualError(t, err, expected)
|
||||
}
|
||||
|
||||
func TestConnectWrongDNS(t *testing.T) {
|
||||
clientConfig := tls.ClientConfig{
|
||||
TLSCA: pki.CACertPath(),
|
||||
TLSCert: pki.ClientCertPath(),
|
||||
TLSKey: pki.ClientKeyPath(),
|
||||
}
|
||||
|
||||
serverConfig := tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSAllowedDNSNames: []string{"localhos", "127.0.0.2"},
|
||||
}
|
||||
|
||||
serverTLSConfig, err := serverConfig.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
ts.TLS = serverTLSConfig
|
||||
|
||||
ts.StartTLS()
|
||||
defer ts.Close()
|
||||
|
||||
clientTLSConfig, err := clientConfig.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: clientTLSConfig,
|
||||
},
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(ts.URL)
|
||||
require.Error(t, err)
|
||||
if resp != nil {
|
||||
err = resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableFlagAuto(t *testing.T) {
|
||||
cfgEmpty := tls.ClientConfig{}
|
||||
cfg, err := cfgEmpty.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, cfg)
|
||||
|
||||
cfgSet := tls.ClientConfig{InsecureSkipVerify: true}
|
||||
cfg, err = cfgSet.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cfg)
|
||||
}
|
||||
|
||||
func TestEnableFlagDisabled(t *testing.T) {
|
||||
enabled := false
|
||||
cfgSet := tls.ClientConfig{
|
||||
InsecureSkipVerify: true,
|
||||
Enable: &enabled,
|
||||
}
|
||||
cfg, err := cfgSet.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, cfg)
|
||||
}
|
||||
|
||||
func TestEnableFlagEnabled(t *testing.T) {
|
||||
enabled := true
|
||||
cfgSet := tls.ClientConfig{Enable: &enabled}
|
||||
cfg, err := cfgSet.TLSConfig()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cfg)
|
||||
|
||||
expected := &cryptotls.Config{}
|
||||
require.Equal(t, expected, cfg)
|
||||
}
|
112
plugins/common/tls/utils.go
Normal file
112
plugins/common/tls/utils.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrCipherUnsupported = errors.New("unsupported cipher")
|
||||
|
||||
// InsecureCiphers returns the list of insecure ciphers among the list of given ciphers
|
||||
func InsecureCiphers(ciphers []string) []string {
|
||||
var insecure []string
|
||||
|
||||
for _, c := range ciphers {
|
||||
cipher := strings.ToUpper(c)
|
||||
if _, ok := tlsCipherMapInsecure[cipher]; ok {
|
||||
insecure = append(insecure, c)
|
||||
}
|
||||
}
|
||||
|
||||
return insecure
|
||||
}
|
||||
|
||||
// Ciphers returns the list of supported ciphers
|
||||
func Ciphers() (secure, insecure []string) {
|
||||
for c := range tlsCipherMapSecure {
|
||||
secure = append(secure, c)
|
||||
}
|
||||
|
||||
for c := range tlsCipherMapInsecure {
|
||||
insecure = append(insecure, c)
|
||||
}
|
||||
|
||||
return secure, insecure
|
||||
}
|
||||
|
||||
// ParseCiphers returns a `[]uint16` by received `[]string` key that represents ciphers from crypto/tls.
|
||||
// If some of ciphers in received list doesn't exists ParseCiphers returns nil with error
|
||||
func ParseCiphers(ciphers []string) ([]uint16, error) {
|
||||
suites := make([]uint16, 0)
|
||||
added := make(map[uint16]bool, len(ciphers))
|
||||
for _, c := range ciphers {
|
||||
// Handle meta-keywords
|
||||
switch c {
|
||||
case "all":
|
||||
for _, id := range tlsCipherMapInsecure {
|
||||
if added[id] {
|
||||
continue
|
||||
}
|
||||
suites = append(suites, id)
|
||||
added[id] = true
|
||||
}
|
||||
for _, id := range tlsCipherMapSecure {
|
||||
if added[id] {
|
||||
continue
|
||||
}
|
||||
suites = append(suites, id)
|
||||
added[id] = true
|
||||
}
|
||||
case "insecure":
|
||||
for _, id := range tlsCipherMapInsecure {
|
||||
if added[id] {
|
||||
continue
|
||||
}
|
||||
suites = append(suites, id)
|
||||
added[id] = true
|
||||
}
|
||||
case "secure":
|
||||
for _, id := range tlsCipherMapSecure {
|
||||
if added[id] {
|
||||
continue
|
||||
}
|
||||
suites = append(suites, id)
|
||||
added[id] = true
|
||||
}
|
||||
default:
|
||||
cipher := strings.ToUpper(c)
|
||||
id, ok := tlsCipherMapSecure[cipher]
|
||||
if !ok {
|
||||
idInsecure, ok := tlsCipherMapInsecure[cipher]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%q %w", cipher, ErrCipherUnsupported)
|
||||
}
|
||||
id = idInsecure
|
||||
}
|
||||
if added[id] {
|
||||
continue
|
||||
}
|
||||
suites = append(suites, id)
|
||||
added[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
return suites, nil
|
||||
}
|
||||
|
||||
// ParseTLSVersion returns a `uint16` by received version string key that represents tls version from crypto/tls.
|
||||
// If version isn't supported ParseTLSVersion returns 0 with error
|
||||
func ParseTLSVersion(version string) (uint16, error) {
|
||||
if v, ok := tlsVersionMap[version]; ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
available := make([]string, 0, len(tlsVersionMap))
|
||||
for n := range tlsVersionMap {
|
||||
available = append(available, n)
|
||||
}
|
||||
sort.Strings(available)
|
||||
return 0, fmt.Errorf("unsupported version %q (available: %s)", version, strings.Join(available, ","))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue