mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-11 16:57:45 +02:00
The scaffold advertises enable_tls, ca_cert_path, client_cert_path and client_key_path under redis2, redis2_sentinel and redis_cluster2, but only the plain redis2 and redis3 stores ever read them, and under a different name, enable_mtls. Sentinel and cluster setups quietly connected in plaintext. Build the TLS config in one place and use it from all six stores. enable_mtls still works. The CA and the client key pair are optional now, so enable_tls alone verifies against the system roots, and ServerName is left unset so go-redis validates each address the sentinel and cluster clients dial.
168 lines
4.6 KiB
Go
168 lines
4.6 KiB
Go
package redis_tls
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type fakeConfiguration map[string]interface{}
|
|
|
|
func (c fakeConfiguration) GetString(key string) string {
|
|
value, _ := c[key].(string)
|
|
return value
|
|
}
|
|
|
|
func (c fakeConfiguration) GetBool(key string) bool {
|
|
value, _ := c[key].(bool)
|
|
return value
|
|
}
|
|
|
|
func (c fakeConfiguration) GetInt(key string) int {
|
|
value, _ := c[key].(int)
|
|
return value
|
|
}
|
|
|
|
func (c fakeConfiguration) GetStringSlice(key string) []string {
|
|
value, _ := c[key].([]string)
|
|
return value
|
|
}
|
|
|
|
func (c fakeConfiguration) SetDefault(key string, value interface{}) {
|
|
if _, found := c[key]; !found {
|
|
c[key] = value
|
|
}
|
|
}
|
|
|
|
func TestDisabled(t *testing.T) {
|
|
tlsConfig, err := Config(fakeConfiguration{"redis2.ca_cert_path": "/does/not/exist"}, "redis2.")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if tlsConfig != nil {
|
|
t.Fatalf("expected no TLS config when neither enable_tls nor enable_mtls is set")
|
|
}
|
|
}
|
|
|
|
func TestEnabledWithoutCertificates(t *testing.T) {
|
|
for _, key := range []string{"enable_tls", "enable_mtls"} {
|
|
tlsConfig, err := Config(fakeConfiguration{"redis2." + key: true}, "redis2.")
|
|
if err != nil {
|
|
t.Fatalf("%s: unexpected error: %v", key, err)
|
|
}
|
|
if tlsConfig == nil {
|
|
t.Fatalf("%s: expected a TLS config", key)
|
|
}
|
|
if tlsConfig.MinVersion != tls.VersionTLS12 {
|
|
t.Errorf("%s: expected TLS 1.2 minimum, got %x", key, tlsConfig.MinVersion)
|
|
}
|
|
if tlsConfig.RootCAs != nil {
|
|
t.Errorf("%s: expected the system root pool", key)
|
|
}
|
|
if len(tlsConfig.Certificates) != 0 {
|
|
t.Errorf("%s: expected no client certificate", key)
|
|
}
|
|
if tlsConfig.ServerName != "" {
|
|
t.Errorf("%s: expected the server name to come from the dialed address, got %s", key, tlsConfig.ServerName)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMutualTls(t *testing.T) {
|
|
dir := t.TempDir()
|
|
certPath, keyPath := writeCertificate(t, dir, "client")
|
|
|
|
tlsConfig, err := Config(fakeConfiguration{
|
|
"redis2.enable_tls": true,
|
|
"redis2.ca_cert_path": certPath,
|
|
"redis2.client_cert_path": certPath,
|
|
"redis2.client_key_path": keyPath,
|
|
}, "redis2.")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if tlsConfig.RootCAs == nil {
|
|
t.Errorf("expected the CA certificate to be loaded")
|
|
}
|
|
if len(tlsConfig.Certificates) != 1 {
|
|
t.Errorf("expected the client certificate to be loaded, got %d", len(tlsConfig.Certificates))
|
|
}
|
|
}
|
|
|
|
func TestUnreadableCaCertificate(t *testing.T) {
|
|
if _, err := Config(fakeConfiguration{
|
|
"redis2.enable_tls": true,
|
|
"redis2.ca_cert_path": filepath.Join(t.TempDir(), "missing.pem"),
|
|
}, "redis2."); err == nil {
|
|
t.Fatalf("expected an error for a missing CA certificate")
|
|
}
|
|
}
|
|
|
|
func TestCaCertificateWithoutPem(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "ca.pem")
|
|
if err := os.WriteFile(path, []byte("not a certificate"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := Config(fakeConfiguration{
|
|
"redis2.enable_tls": true,
|
|
"redis2.ca_cert_path": path,
|
|
}, "redis2."); err == nil {
|
|
t.Fatalf("expected an error for a CA file without any certificate")
|
|
}
|
|
}
|
|
|
|
func TestClientCertificateWithoutKey(t *testing.T) {
|
|
certPath, _ := writeCertificate(t, t.TempDir(), "client")
|
|
if _, err := Config(fakeConfiguration{
|
|
"redis2.enable_tls": true,
|
|
"redis2.client_cert_path": certPath,
|
|
}, "redis2."); err == nil {
|
|
t.Fatalf("expected an error for a client certificate without a key")
|
|
}
|
|
}
|
|
|
|
func writeCertificate(t *testing.T, dir, name string) (certPath, keyPath string) {
|
|
t.Helper()
|
|
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
template := &x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{CommonName: name},
|
|
NotBefore: time.Unix(0, 0),
|
|
NotAfter: time.Unix(1<<31-1, 0),
|
|
IsCA: true,
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
|
BasicConstraintsValid: true,
|
|
}
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
keyBytes, err := x509.MarshalECPrivateKey(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
certPath = filepath.Join(dir, name+".crt")
|
|
keyPath = filepath.Join(dir, name+".key")
|
|
if err := os.WriteFile(certPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return certPath, keyPath
|
|
}
|