Files
go/tls_test.go
zeekayandHanzo Dev ee3ef1a41a fix: delete the stale compose.yml that was shadowing the real one
The tree carried two compose files. Docker Compose v2 resolves by a fixed
precedence -- compose.yaml, compose.yml, docker-compose.yaml,
docker-compose.yml -- so compose.yml won every time and docker-compose.yml,
the one upstream merges actually keep current, was never read:

  warning  Found multiple config files with supported names:
           .../compose.yml, .../docker-compose.yml
  warning  Using /Users/z/work/hanzo/kv-go/compose.yml

compose.yml was a 106-line snapshot taken before several upstream updates.
It pinned CLIENT_LIBS_TEST_IMAGE at 8.4.0 while the Makefile asks for 8.8.0,
and it was missing three services and a whole profile. Two things were
therefore broken and quiet about it:

  * `make docker.e2e.start` runs `docker compose --profile e2e up`. There is
    no e2e profile in the stale file, so it started nothing;
    .github/workflows/test-e2e.yml then sat waiting for cae-resp-proxy on
    :18100 until the timeout.
  * `make docker.start` (--profile all) never started osscluster-tls, so
    tls_cluster_test.go -- whose own comment says the ports come from the
    compose file -- had no server on 5430.

Deleting the stale copy and giving the live one the canonical name (this
workspace uses compose.yml, never docker-compose.yml) leaves exactly one
compose file. `docker compose --profile e2e config --services` now returns
redis, cae-resp-proxy and proxy-fault-injector; --profile all also brings up
osscluster-tls. The three comments that named the old path now name the new
one. go build ./... clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 14:56:26 -07:00

293 lines
7.6 KiB
Go

package redis_test
import (
"context"
"crypto/tls"
"crypto/x509"
"os"
"path/filepath"
. "github.com/bsm/ginkgo/v2"
. "github.com/bsm/gomega"
"github.com/hanzoai/kv-go/v9"
)
// loadTLSConfig loads TLS certificates from the docker test environment
func loadTLSConfig(certDir string) (*tls.Config, error) {
// Load CA cert
caCert, err := os.ReadFile(filepath.Join(certDir, "ca.crt"))
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// Load client cert and key
cert, err := tls.LoadX509KeyPair(
filepath.Join(certDir, "client.crt"),
filepath.Join(certDir, "client.key"),
)
if err != nil {
return nil, err
}
return &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{cert},
ServerName: "localhost",
InsecureSkipVerify: true,
}, nil
}
var _ = Describe("TLS", Label("NonRedisEnterprise"), func() {
var tlsConfig *tls.Config
var tlsPort = "6666" // TLS port from compose.yml
BeforeEach(func() {
var err error
// Load TLS config from docker test certificates
tlsConfig, err = loadTLSConfig("dockers/standalone/tls")
Expect(err).NotTo(HaveOccurred())
})
Describe("Standalone Redis with TLS", func() {
It("should connect with TLS using Options", func() {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: tlsConfig,
})
defer client.Close()
val, err := client.Ping(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal("PONG"))
})
It("should connect with TLS using rediss:// URL", func() {
ctx := context.Background()
opt, err := redis.ParseURL("rediss://localhost:" + tlsPort)
Expect(err).NotTo(HaveOccurred())
// Override TLS config with our certificates
opt.TLSConfig = tlsConfig
client := redis.NewClient(opt)
defer client.Close()
val, err := client.Ping(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal("PONG"))
})
It("should perform basic operations over TLS", func() {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: tlsConfig,
})
defer client.Close()
// SET
err := client.Set(ctx, "tls_test_key", "tls_test_value", 0).Err()
Expect(err).NotTo(HaveOccurred())
// GET
val, err := client.Get(ctx, "tls_test_key").Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal("tls_test_value"))
// DEL
deleted, err := client.Del(ctx, "tls_test_key").Result()
Expect(err).NotTo(HaveOccurred())
Expect(deleted).To(Equal(int64(1)))
})
It("should support pipelining over TLS", func() {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: tlsConfig,
})
defer client.Close()
pipe := client.Pipeline()
setCmd := pipe.Set(ctx, "tls_pipe_key1", "value1", 0)
getCmd := pipe.Get(ctx, "tls_pipe_key1")
delCmd := pipe.Del(ctx, "tls_pipe_key1")
_, err := pipe.Exec(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(setCmd.Err()).NotTo(HaveOccurred())
Expect(getCmd.Val()).To(Equal("value1"))
Expect(delCmd.Val()).To(Equal(int64(1)))
})
It("should support transactions over TLS", func() {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: tlsConfig,
})
defer client.Close()
err := client.Watch(ctx, func(tx *redis.Tx) error {
_, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.Set(ctx, "tls_tx_key", "tx_value", 0)
pipe.Get(ctx, "tls_tx_key")
return nil
})
return err
})
Expect(err).NotTo(HaveOccurred())
val, err := client.Get(ctx, "tls_tx_key").Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal("tx_value"))
// Cleanup
client.Del(ctx, "tls_tx_key")
})
It("should support pub/sub over TLS", func() {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: tlsConfig,
})
defer client.Close()
pubsub := client.Subscribe(ctx, "tls_test_channel")
defer pubsub.Close()
// Wait for subscription confirmation
_, err := pubsub.Receive(ctx)
Expect(err).NotTo(HaveOccurred())
// Publish a message
err = client.Publish(ctx, "tls_test_channel", "tls_test_message").Err()
Expect(err).NotTo(HaveOccurred())
// Receive the message
msg, err := pubsub.ReceiveMessage(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(msg.Channel).To(Equal("tls_test_channel"))
Expect(msg.Payload).To(Equal("tls_test_message"))
})
It("should fail to connect without TLS to TLS port", func() {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
// No TLS config
})
defer client.Close()
err := client.Ping(ctx).Err()
Expect(err).To(HaveOccurred())
})
It("should fail to connect with invalid certificates", func() {
ctx := context.Background()
invalidTLSConfig := &tls.Config{
InsecureSkipVerify: false,
ServerName: "invalid.example.com",
}
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: invalidTLSConfig,
})
defer client.Close()
err := client.Ping(ctx).Err()
Expect(err).To(HaveOccurred())
})
It("should connect with InsecureSkipVerify", func() {
ctx := context.Background()
insecureTLSConfig := &tls.Config{
InsecureSkipVerify: true,
}
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: insecureTLSConfig,
})
defer client.Close()
val, err := client.Ping(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal("PONG"))
})
It("should support connection pooling over TLS", func() {
ctx := context.Background()
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: tlsConfig,
PoolSize: 10,
})
defer client.Close()
// Perform multiple operations to use different connections
for i := 0; i < 20; i++ {
err := client.Ping(ctx).Err()
Expect(err).NotTo(HaveOccurred())
}
stats := client.PoolStats()
Expect(stats.Hits).To(BeNumerically(">", 0))
})
})
Describe("TLS Configuration Options", func() {
It("should respect MinVersion setting", func() {
ctx := context.Background()
tlsConfigWithMinVersion := &tls.Config{
RootCAs: tlsConfig.RootCAs,
Certificates: tlsConfig.Certificates,
ServerName: "localhost",
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
}
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: tlsConfigWithMinVersion,
})
defer client.Close()
val, err := client.Ping(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal("PONG"))
})
It("should work with custom cipher suites", func() {
ctx := context.Background()
tlsConfigWithCiphers := &tls.Config{
RootCAs: tlsConfig.RootCAs,
Certificates: tlsConfig.Certificates,
ServerName: "localhost",
InsecureSkipVerify: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
},
}
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: tlsConfigWithCiphers,
})
defer client.Close()
val, err := client.Ping(ctx).Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal("PONG"))
})
})
})