Skip to content

Commit 1bf325c

Browse files
authored
TLS support (#159)
* TLS support in the server * test TLS in integration
1 parent ee3ea5c commit 1bf325c

File tree

13 files changed

+346
-8
lines changed

13 files changed

+346
-8
lines changed

integration/ephemeral.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,20 @@ func RedisCluster() (*ephemeral, string) {
3939
return runRedis("cluster-enabled yes\ncluster-config-file nodes.conf")
4040
}
4141

42+
func RedisTLS() (*ephemeral, string) {
43+
port := arbitraryPort()
44+
e, _ := runRedis(fmt.Sprintf(
45+
`
46+
tls-port %d
47+
tls-cert-file ../testdata/server.crt
48+
tls-key-file ../testdata/server.key
49+
tls-ca-cert-file ../testdata/client.crt
50+
`,
51+
port))
52+
addr := fmt.Sprintf("127.0.0.1:%d", port)
53+
return e, addr
54+
}
55+
4256
func runRedis(extraConfig string) (*ephemeral, string) {
4357
port := arbitraryPort()
4458

@@ -67,7 +81,7 @@ func runRedis(extraConfig string) (*ephemeral, string) {
6781
e := ephemeral(*c)
6882
return &e, addr
6983
}
70-
time.Sleep(1 * time.Millisecond)
84+
time.Sleep(3 * time.Millisecond)
7185
}
7286
panic(fmt.Sprintf("No connection on port %d", port))
7387
}

integration/get_redis.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ mkdir -p ./redis_src/
99
cd ./redis_src/
1010
wget http://download.redis.io/releases/redis-${VERSION}.tar.gz -O ./redis.tar.gz
1111
tar -xf ./redis.tar.gz
12-
(cd ./redis-${VERSION}/src/ && make)
12+
(cd ./redis-${VERSION}/src/ && make BUILD_TLS=yes)
1313
cp ./redis-${VERSION}/src/redis-server .

integration/server_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,12 @@ func TestServer(t *testing.T) {
3636
fail("FLUSHALL", "ASYNC", "foo"),
3737
)
3838
}
39+
40+
func TestServerTLS(t *testing.T) {
41+
testCommandsTLS(t,
42+
succ("PING", "foo"),
43+
44+
succ("SET", "foo", "bar"),
45+
succ("GET", "foo"),
46+
)
47+
}

integration/test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,17 @@ func testCommands(t *testing.T, commands ...command) {
139139
runCommands(t, sRealAddr, sMini.Addr(), commands)
140140
}
141141

142+
func testCommandsTLS(t *testing.T, commands ...command) {
143+
t.Helper()
144+
sMini := miniredis.NewMiniRedis()
145+
ok(t, sMini.StartTLS(testServerTLS(t)))
146+
defer sMini.Close()
147+
148+
sReal, sRealAddr := RedisTLS()
149+
defer sReal.Close()
150+
runCommandsTLS(t, sRealAddr, sMini.Addr(), commands)
151+
}
152+
142153
// like testCommands, but multiple connections
143154
func testMultiCommands(t *testing.T, cs ...func(chan<- command, *miniredis.Miniredis)) {
144155
t.Helper()
@@ -265,6 +276,30 @@ func runCommands(t *testing.T, realAddr, miniAddr string, commands []command) {
265276
}
266277
}
267278

279+
func runCommandsTLS(t *testing.T, realAddr, miniAddr string, commands []command) {
280+
t.Helper()
281+
cfg := testClientTLS(t)
282+
cMini, err := redis.Dial(
283+
"tcp",
284+
miniAddr,
285+
redis.DialTLSConfig(cfg),
286+
redis.DialUseTLS(true),
287+
)
288+
ok(t, err)
289+
290+
cReal, err := redis.Dial(
291+
"tcp",
292+
realAddr,
293+
redis.DialTLSConfig(cfg),
294+
redis.DialUseTLS(true),
295+
)
296+
ok(t, err)
297+
298+
for _, c := range commands {
299+
runCommand(t, cMini, cReal, c)
300+
}
301+
}
302+
268303
func runCommand(t *testing.T, cMini, cReal redis.Conn, p command) {
269304
t.Helper()
270305
var (

integration/tls.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// +build int
2+
3+
package main
4+
5+
import (
6+
"crypto/tls"
7+
"crypto/x509"
8+
"io/ioutil"
9+
"testing"
10+
)
11+
12+
func testServerTLS(t *testing.T) *tls.Config {
13+
cert, err := tls.LoadX509KeyPair("../testdata/server.crt", "../testdata/server.key")
14+
if err != nil {
15+
t.Fatal(err)
16+
}
17+
18+
cp := x509.NewCertPool()
19+
rootca, err := ioutil.ReadFile("../testdata/client.crt")
20+
if err != nil {
21+
t.Fatal(err)
22+
}
23+
if !cp.AppendCertsFromPEM(rootca) {
24+
t.Fatal("client cert err")
25+
}
26+
return &tls.Config{
27+
Certificates: []tls.Certificate{cert},
28+
ClientAuth: tls.RequireAndVerifyClientCert,
29+
ServerName: "Server",
30+
ClientCAs: cp,
31+
}
32+
}
33+
34+
func testClientTLS(t *testing.T) *tls.Config {
35+
cert, err := tls.LoadX509KeyPair("../testdata/client.crt", "../testdata/client.key")
36+
if err != nil {
37+
t.Fatal(err)
38+
}
39+
cp := x509.NewCertPool()
40+
rootca, err := ioutil.ReadFile("../testdata/server.crt")
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
if !cp.AppendCertsFromPEM(rootca) {
45+
t.Fatal("server cert err")
46+
}
47+
return &tls.Config{
48+
Certificates: []tls.Certificate{cert},
49+
ServerName: "Server",
50+
RootCAs: cp,
51+
}
52+
}

miniredis.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package miniredis
1616

1717
import (
1818
"context"
19+
"crypto/tls"
1920
"fmt"
2021
"math/rand"
2122
"strconv"
@@ -117,6 +118,12 @@ func Run() (*Miniredis, error) {
117118
return m, m.Start()
118119
}
119120

121+
// Run creates and Start()s a Miniredis, TLS version.
122+
func RunTLS(cfg *tls.Config) (*Miniredis, error) {
123+
m := NewMiniRedis()
124+
return m, m.StartTLS(cfg)
125+
}
126+
120127
// Start starts a server. It listens on a random port on localhost. See also
121128
// Addr().
122129
func (m *Miniredis) Start() error {
@@ -127,6 +134,15 @@ func (m *Miniredis) Start() error {
127134
return m.start(s)
128135
}
129136

137+
// Start starts a server, TLS version.
138+
func (m *Miniredis) StartTLS(cfg *tls.Config) error {
139+
s, err := server.NewServerTLS(fmt.Sprintf("127.0.0.1:%d", m.port), cfg)
140+
if err != nil {
141+
return err
142+
}
143+
return m.start(s)
144+
}
145+
130146
// StartAddr runs miniredis with a given addr. Examples: "127.0.0.1:6379",
131147
// ":6379", or "127.0.0.1:0"
132148
func (m *Miniredis) StartAddr(addr string) error {

server/server.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package server
22

33
import (
44
"bufio"
5+
"crypto/tls"
56
"fmt"
67
"net"
78
"strings"
@@ -42,16 +43,27 @@ type Server struct {
4243

4344
// NewServer makes a server listening on addr. Close with .Close().
4445
func NewServer(addr string) (*Server, error) {
45-
s := Server{
46-
cmds: map[string]Cmd{},
47-
peers: map[net.Conn]struct{}{},
46+
l, err := net.Listen("tcp", addr)
47+
if err != nil {
48+
return nil, err
4849
}
50+
return newServer(l), nil
51+
}
4952

50-
l, err := net.Listen("tcp", addr)
53+
func NewServerTLS(addr string, cfg *tls.Config) (*Server, error) {
54+
l, err := tls.Listen("tcp", addr, cfg)
5155
if err != nil {
5256
return nil, err
5357
}
54-
s.l = l
58+
return newServer(l), nil
59+
}
60+
61+
func newServer(l net.Listener) *Server {
62+
s := Server{
63+
cmds: map[string]Cmd{},
64+
peers: map[net.Conn]struct{}{},
65+
l: l,
66+
}
5567

5668
s.wg.Add(1)
5769
go func() {
@@ -64,7 +76,7 @@ func NewServer(addr string) (*Server, error) {
6476
}
6577
s.mu.Unlock()
6678
}()
67-
return &s, nil
79+
return &s
6880
}
6981

7082
// (un)set a hook which is ran before every call. It returns true if the command is done.

server/server_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package server
22

33
import (
4+
"crypto/tls"
5+
"crypto/x509"
46
"fmt"
7+
"io/ioutil"
58
"reflect"
69
"strconv"
710
"strings"
@@ -157,3 +160,81 @@ func Test(t *testing.T) {
157160
}
158161
}
159162
}
163+
164+
func testServerTLS(t *testing.T) *tls.Config {
165+
cert, err := tls.LoadX509KeyPair("../testdata/server.crt", "../testdata/server.key")
166+
if err != nil {
167+
t.Fatal(err)
168+
}
169+
170+
cp := x509.NewCertPool()
171+
rootca, err := ioutil.ReadFile("../testdata/client.crt")
172+
if err != nil {
173+
t.Fatal(err)
174+
}
175+
if !cp.AppendCertsFromPEM(rootca) {
176+
t.Fatal("client cert err")
177+
}
178+
return &tls.Config{
179+
Certificates: []tls.Certificate{cert},
180+
ClientAuth: tls.RequireAndVerifyClientCert,
181+
ServerName: "Server",
182+
ClientCAs: cp,
183+
}
184+
}
185+
186+
func testClientTLS(t *testing.T) *tls.Config {
187+
cert, err := tls.LoadX509KeyPair("../testdata/client.crt", "../testdata/client.key")
188+
if err != nil {
189+
t.Fatal(err)
190+
}
191+
cp := x509.NewCertPool()
192+
rootca, err := ioutil.ReadFile("../testdata/server.crt")
193+
if err != nil {
194+
t.Fatal(err)
195+
}
196+
if !cp.AppendCertsFromPEM(rootca) {
197+
t.Fatal("server cert err")
198+
}
199+
return &tls.Config{
200+
Certificates: []tls.Certificate{cert},
201+
ServerName: "Server",
202+
RootCAs: cp,
203+
}
204+
}
205+
206+
func TestTLS(t *testing.T) {
207+
s, err := NewServerTLS("127.0.0.1:0", testServerTLS(t))
208+
if err != nil {
209+
t.Fatal(err)
210+
}
211+
defer s.Close()
212+
213+
if have := s.Addr().Port; have <= 0 {
214+
t.Fatalf("have %v, want > 0", have)
215+
}
216+
217+
s.Register("PING", func(c *Peer, cmd string, args []string) {
218+
c.WriteInline("PONG")
219+
})
220+
221+
cfg := testClientTLS(t)
222+
c, err := redis.Dial("tcp",
223+
s.Addr().String(),
224+
redis.DialTLSConfig(cfg),
225+
redis.DialUseTLS(true),
226+
)
227+
if err != nil {
228+
t.Fatal(err)
229+
}
230+
231+
{
232+
res, err := redis.String(c.Do("PING"))
233+
if err != nil {
234+
t.Fatal(err)
235+
}
236+
if have, want := res, "PONG"; have != want {
237+
t.Errorf("have: %s, want: %s", have, want)
238+
}
239+
}
240+
}

testdata/Makefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
gen:
2+
openssl req \
3+
-x509 \
4+
-nodes \
5+
-newkey rsa:2048 \
6+
-keyout server.key \
7+
-out server.crt \
8+
-days 3650 \
9+
-subj "/C=GB/ST=London/L=London/O=Global Security/OU=IT Department/CN=Server"
10+
openssl req \
11+
-x509 \
12+
-nodes \
13+
-newkey rsa:2048 \
14+
-keyout client.key \
15+
-out client.crt \
16+
-days 3650 \
17+
-subj "/C=GB/ST=London/L=London/O=Global Security/OU=IT Department/CN=Client"

testdata/client.crt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDxTCCAq2gAwIBAgIUEqYzLKsTOvgGfJ46o9y4GBy+H+UwDQYJKoZIhvcNAQEL
3+
BQAwcjELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9u
4+
ZG9uMRgwFgYDVQQKDA9HbG9iYWwgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFy
5+
dG1lbnQxDzANBgNVBAMMBkNsaWVudDAeFw0yMDA2MTcxNjMzMDJaFw0zMDA2MTUx
6+
NjMzMDJaMHIxCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcM
7+
BkxvbmRvbjEYMBYGA1UECgwPR2xvYmFsIFNlY3VyaXR5MRYwFAYDVQQLDA1JVCBE
8+
ZXBhcnRtZW50MQ8wDQYDVQQDDAZDbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IB
9+
DwAwggEKAoIBAQDIO3Z3LxdRl8ts+vlNPFzvY0PZ9aiIqWwDA0oaK0wJTC+q669x
10+
gfm9km2apd5sUNkA8X68DrjVAumK5h8XgA09Rz5j8mYLPoiQfuyXKB2eYLtBl/D+
11+
yYMU639znXxXCUOw/ZhJ4qqHwcBw0RynKGg1wELTdbG4OMpAz/EEh+/z2mpuryQF
12+
aFOhVK4AaOBgUdBeyFBOuPtWxzHn0vQPIEM98P3yUFn3IaOSGtChgcy7VbpAH9K2
13+
18YuBxWNYoabeLQuBk/Jfam+8a+gIY8WUSIw9gGUYIVBpl4Hm/xOYCgmU/SyiEHs
14+
aQWAuhVtcS+WyHL5RhFiIWSeyK91p4//ZBKtAgMBAAGjUzBRMB0GA1UdDgQWBBT/
15+
p6iIo0Xy1uXn8FvxPSrzzWKWTzAfBgNVHSMEGDAWgBT/p6iIo0Xy1uXn8FvxPSrz
16+
zWKWTzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCQ7gHmQjPa
17+
FLXfrUY24rxggLkQrntGy4ZdKznd3ffduNDW9vc45/kOBSqoECC3a6jadXZltMbq
18+
G/B3LQH5qNZJf9V0cYCyzSX+M6lENdcakYpHwyz4MOOZRt4nYBe8MUXp3e2YKhvo
19+
AMPBgUckLV+A1amWhHk/r+3K2uAQwGeEGcn0C+dXBR6rQeWOzuMbqaF5KCDF/Tcc
20+
KZ2WJMRBr/z7RZuls2KmAxIDPTfUXmRqwwk2+KBp8PleuZ26t4cREEFmf2z+4Npl
21+
MtVFOElNPMFd+9YUpkOP/LncfFIoLVMy67G/P76fkZ/qT4yA+xx+80MY73LKnOQf
22+
wA11PFVjhAE0
23+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)