Skip to content

Commit a11fa34

Browse files
authored
feat(valkey): add TLS support for Valkey (testcontainers#3131)
* feat(valkey): add TLS support for Valkey * docs(redis): update missing version markers * fix: mod tidy * docs: add whiteline * fix: variable names
1 parent 3858aec commit a11fa34

File tree

8 files changed

+302
-18
lines changed

8 files changed

+302
-18
lines changed

docs/modules/redis.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,12 @@ In the case you have a custom config file for Redis, it's possible to copy that
7373

7474
#### WithTLS
7575

76+
- Since testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go/releases/tag/v0.37.0"><span class="tc-version">:material-tag: v0.37.0</span></a>
77+
7678
In the case you want to enable TLS for the Redis container, you can use the `WithTLS()` option. This options enables TLS on the `6379/tcp` port and uses a secure URL (e.g. `rediss://host:port`).
7779

7880
!!!info
79-
In case you want to use Non-mutual TLS (i.e. client authentication is not required), you can customize the CMD arguments by using the `WithCmdArgs` option. E.g. `WithCmdArgs("--tls-auth-clients no")`.
81+
In case you want to use Non-mutual TLS (i.e. client authentication is not required), you can customize the CMD arguments by using the `WithCmdArgs` option. E.g. `WithCmdArgs("--tls-auth-clients", "no")`.
8082

8183
The module automatically generates three certificates, a CA certificate, a client certificate and a Redis certificate. Please use the `TLSConfig()` container method to get the TLS configuration and use it to configure the Redis client. See more details in the [TLSConfig](#tlsconfig) section.
8284

@@ -94,6 +96,8 @@ If the container is started with TLS enabled, the connection string is `rediss:/
9496

9597
#### TLSConfig
9698

99+
- Since testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go/releases/tag/v0.37.0"><span class="tc-version">:material-tag: v0.37.0</span></a>
100+
97101
This method returns the TLS configuration for the Redis container, nil if TLS is not enabled.
98102

99103
<!--codeinclude-->

docs/modules/valkey.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ You can easily set the valkey logging level. E.g. `WithLogLevel(LogLevelDebug)`.
6666

6767
In the case you have a custom config file for Valkey, it's possible to copy that file into the container before it's started. E.g. `WithConfigFile(filepath.Join("testdata", "valkey.conf"))`.
6868

69+
#### WithTLS
70+
71+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
72+
73+
In the case you want to enable TLS for the Valkey container, you can use the `WithTLS()` option. This options enables TLS on the `6379/tcp` port and uses a secure URL (e.g. `rediss://host:port`).
74+
75+
!!!info
76+
In case you want to use Non-mutual TLS (i.e. client authentication is not required), you can customize the CMD arguments by using the `WithCmdArgs` option. E.g. `WithCmdArgs("--tls-auth-clients", "no")`.
77+
78+
The module automatically generates three certificates, a CA certificate, a client certificate and a Valkey certificate. Please use the `TLSConfig()` container method to get the TLS configuration and use it to configure the Valkey client. See more details in the [TLSConfig](#tlsconfig) section.
79+
6980
### Container Methods
7081

7182
The Valkey container exposes the following methods:
@@ -78,4 +89,16 @@ This method returns the connection string to connect to the Valkey container, us
7889

7990
<!--codeinclude-->
8091
[Get connection string](../../modules/valkey/valkey_test.go) inside_block:connectionString
81-
<!--/codeinclude-->
92+
<!--/codeinclude-->
93+
94+
#### TLSConfig
95+
96+
- Not available until the next release of testcontainers-go <a href="https://github.com/testcontainers/testcontainers-go"><span class="tc-version">:material-tag: main</span></a>
97+
98+
This method returns the TLS configuration for the Valkey container, nil if TLS is not enabled.
99+
100+
<!--codeinclude-->
101+
[Get TLS config](../../modules/valkey/valkey_test.go) inside_block:tlsConfig
102+
<!--/codeinclude-->
103+
104+
In the above example, the options are used to configure a Valkey client with TLS enabled.

modules/valkey/examples_test.go

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@ import (
66
"log"
77
"path/filepath"
88

9+
"github.com/valkey-io/valkey-go"
10+
911
"github.com/testcontainers/testcontainers-go"
10-
"github.com/testcontainers/testcontainers-go/modules/valkey"
12+
tcvalkey "github.com/testcontainers/testcontainers-go/modules/valkey"
1113
)
1214

1315
func ExampleRun() {
1416
// runValkeyContainer {
1517
ctx := context.Background()
1618

17-
valkeyContainer, err := valkey.Run(ctx,
19+
valkeyContainer, err := tcvalkey.Run(ctx,
1820
"valkey/valkey:7.2.5",
19-
valkey.WithSnapshotting(10, 1),
20-
valkey.WithLogLevel(valkey.LogLevelVerbose),
21-
valkey.WithConfigFile(filepath.Join("testdata", "valkey7.conf")),
21+
tcvalkey.WithSnapshotting(10, 1),
22+
tcvalkey.WithLogLevel(tcvalkey.LogLevelVerbose),
23+
tcvalkey.WithConfigFile(filepath.Join("testdata", "valkey7.conf")),
2224
)
2325
defer func() {
2426
if err := testcontainers.TerminateContainer(valkeyContainer); err != nil {
@@ -42,3 +44,63 @@ func ExampleRun() {
4244
// Output:
4345
// true
4446
}
47+
48+
func ExampleRun_withTLS() {
49+
ctx := context.Background()
50+
51+
valkeyContainer, err := tcvalkey.Run(ctx,
52+
"valkey/valkey:7.2.5",
53+
tcvalkey.WithSnapshotting(10, 1),
54+
tcvalkey.WithLogLevel(tcvalkey.LogLevelVerbose),
55+
tcvalkey.WithTLS(),
56+
)
57+
defer func() {
58+
if err := testcontainers.TerminateContainer(valkeyContainer); err != nil {
59+
log.Printf("failed to terminate container: %s", err)
60+
}
61+
}()
62+
if err != nil {
63+
log.Printf("failed to start container: %s", err)
64+
return
65+
}
66+
67+
if valkeyContainer.TLSConfig() == nil {
68+
log.Println("TLS is not enabled")
69+
return
70+
}
71+
72+
uri, err := valkeyContainer.ConnectionString(ctx)
73+
if err != nil {
74+
log.Printf("failed to get connection string: %s", err)
75+
return
76+
}
77+
78+
// You will likely want to wrap your Valkey package of choice in an
79+
// interface to aid in unit testing and limit lock-in throughout your
80+
// codebase but that's out of scope for this example
81+
options, err := valkey.ParseURL(uri)
82+
if err != nil {
83+
log.Printf("failed to parse connection string: %s", err)
84+
return
85+
}
86+
87+
options.TLSConfig = valkeyContainer.TLSConfig()
88+
89+
client, err := valkey.NewClient(options)
90+
if err != nil {
91+
log.Printf("failed to create valkey client: %s", err)
92+
return
93+
}
94+
defer func() {
95+
err := flushValkey(ctx, client)
96+
if err != nil {
97+
log.Printf("failed to flush valkey: %s", err)
98+
}
99+
}()
100+
101+
resp := client.Do(ctx, client.B().Ping().Build().Pin())
102+
fmt.Println(resp.String())
103+
104+
// Output:
105+
// {"Message":{"Value":"PONG","Type":"simple string"}}
106+
}

modules/valkey/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ toolchain go1.23.6
66

77
require (
88
github.com/google/uuid v1.6.0
9+
github.com/mdelapenya/tlscert v0.2.0
910
github.com/stretchr/testify v1.10.0
1011
github.com/testcontainers/testcontainers-go v0.37.0
11-
github.com/valkey-io/valkey-go v1.0.41
12+
github.com/valkey-io/valkey-go v1.0.59
1213
)
1314

1415
replace github.com/testcontainers/testcontainers-go => ../..
@@ -60,7 +61,6 @@ require (
6061
go.opentelemetry.io/otel/trace v1.35.0 // indirect
6162
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
6263
golang.org/x/crypto v0.37.0 // indirect
63-
golang.org/x/net v0.38.0 // indirect
6464
golang.org/x/sys v0.32.0 // indirect
6565
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect
6666
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect

modules/valkey/go.sum

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tA
6060
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
6161
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
6262
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
63+
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
64+
github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o=
6365
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
6466
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
6567
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
@@ -74,8 +76,8 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
7476
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
7577
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
7678
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
77-
github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
78-
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
79+
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
80+
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
7981
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
8082
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
8183
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -102,8 +104,8 @@ github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZ
102104
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
103105
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
104106
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
105-
github.com/valkey-io/valkey-go v1.0.41 h1:pWgh9MP24Vl0ANZ0KxEMwB/LHvTUKwlm2SPuWIrSlFw=
106-
github.com/valkey-io/valkey-go v1.0.41/go.mod h1:LXqAbjygRuA1YRocojTslAGx2dQB4p8feaseGviWka4=
107+
github.com/valkey-io/valkey-go v1.0.59 h1:W67Z0UY+Qqk3k8NKkFCFlM3X4yQUniixl7dSJAch2Qo=
108+
github.com/valkey-io/valkey-go v1.0.59/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY=
107109
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
108110
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
109111
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=

modules/valkey/options.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package valkey
2+
3+
import (
4+
"crypto/tls"
5+
"fmt"
6+
"net"
7+
8+
"github.com/mdelapenya/tlscert"
9+
10+
"github.com/testcontainers/testcontainers-go"
11+
)
12+
13+
type options struct {
14+
tlsEnabled bool
15+
tlsConfig *tls.Config
16+
}
17+
18+
// Compiler check to ensure that Option implements the testcontainers.ContainerCustomizer interface.
19+
var _ testcontainers.ContainerCustomizer = (Option)(nil)
20+
21+
// Option is an option for the Redpanda container.
22+
type Option func(*options) error
23+
24+
// Customize is a NOOP. It's defined to satisfy the testcontainers.ContainerCustomizer interface.
25+
func (o Option) Customize(*testcontainers.GenericContainerRequest) error {
26+
// NOOP to satisfy interface.
27+
return nil
28+
}
29+
30+
// WithTLS sets the TLS configuration for the redis container, setting
31+
// the 6380/tcp port to listen on for TLS connections and using a secure URL (rediss://).
32+
func WithTLS() Option {
33+
return func(o *options) error {
34+
o.tlsEnabled = true
35+
return nil
36+
}
37+
}
38+
39+
// createTLSCerts creates a CA certificate, a client certificate and a Valkey certificate.
40+
func createTLSCerts() (caCert *tlscert.Certificate, clientCert *tlscert.Certificate, valkeyCert *tlscert.Certificate, err error) {
41+
// ips is the extra list of IPs to include in the certificates.
42+
// It's used to allow the client and Valkey certificates to be used in the same host
43+
// when the tests are run using a remote docker daemon.
44+
ips := []net.IP{net.ParseIP("127.0.0.1")}
45+
46+
// Generate CA certificate
47+
caCert, err = tlscert.SelfSignedFromRequestE(tlscert.Request{
48+
Host: "localhost",
49+
IPAddresses: ips,
50+
Name: "ca",
51+
SubjectCommonName: "ca",
52+
IsCA: true,
53+
})
54+
if err != nil {
55+
return nil, nil, nil, fmt.Errorf("generate CA certificate: %w", err)
56+
}
57+
58+
// Generate client certificate
59+
clientCert, err = tlscert.SelfSignedFromRequestE(tlscert.Request{
60+
Host: "localhost",
61+
Name: "Redis Client",
62+
SubjectCommonName: "localhost",
63+
IPAddresses: ips,
64+
Parent: caCert,
65+
})
66+
if err != nil {
67+
return nil, nil, nil, fmt.Errorf("generate client certificate: %w", err)
68+
}
69+
70+
// Generate Valkey certificate
71+
valkeyCert, err = tlscert.SelfSignedFromRequestE(tlscert.Request{
72+
Host: "localhost",
73+
IPAddresses: ips,
74+
Name: "Valkey Server",
75+
Parent: caCert,
76+
})
77+
if err != nil {
78+
return nil, nil, nil, fmt.Errorf("generate Valkey certificate: %w", err)
79+
}
80+
81+
return caCert, clientCert, valkeyCert, nil
82+
}

0 commit comments

Comments
 (0)