1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View 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

View 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
}
})
}

View 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)
}

View 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
View 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, ","))
}