Skip to content

Commit 10c7254

Browse files
committed
test: Add TLS E2E test
1 parent 15ea3b2 commit 10c7254

1 file changed

Lines changed: 205 additions & 0 deletions

File tree

test/e2e/tls_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package e2e
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"crypto/ecdsa"
7+
"crypto/elliptic"
8+
"crypto/rand"
9+
"crypto/x509"
10+
"crypto/x509/pkix"
11+
"encoding/pem"
12+
"fmt"
13+
"math/big"
14+
"net"
15+
"os"
16+
"os/exec"
17+
"path/filepath"
18+
"strings"
19+
"testing"
20+
"time"
21+
22+
"github.com/stretchr/testify/require"
23+
24+
"github.com/ustclug/rsync-proxy/cmd"
25+
"github.com/ustclug/rsync-proxy/pkg/server"
26+
)
27+
28+
type tlsCertFiles struct {
29+
certPath string
30+
keyPath string
31+
}
32+
33+
func writeTestTLSCert(t *testing.T, dir, name, commonName string) tlsCertFiles {
34+
t.Helper()
35+
36+
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
37+
if err != nil {
38+
t.Fatalf("generate private key: %v", err)
39+
}
40+
41+
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
42+
if err != nil {
43+
t.Fatalf("generate serial number: %v", err)
44+
}
45+
46+
tmpl := &x509.Certificate{
47+
SerialNumber: serial,
48+
Subject: pkix.Name{
49+
CommonName: commonName,
50+
},
51+
NotBefore: time.Now().Add(-time.Minute),
52+
NotAfter: time.Now().Add(time.Hour),
53+
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
54+
ExtKeyUsage: []x509.ExtKeyUsage{
55+
x509.ExtKeyUsageServerAuth,
56+
},
57+
DNSNames: []string{"localhost"},
58+
}
59+
60+
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &privateKey.PublicKey, privateKey)
61+
if err != nil {
62+
t.Fatalf("create certificate: %v", err)
63+
}
64+
65+
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
66+
keyDER, err := x509.MarshalECPrivateKey(privateKey)
67+
if err != nil {
68+
t.Fatalf("marshal private key: %v", err)
69+
}
70+
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
71+
72+
certPath := filepath.Join(dir, fmt.Sprintf("%s.crt", name))
73+
keyPath := filepath.Join(dir, fmt.Sprintf("%s.key", name))
74+
require.NoError(t, os.WriteFile(certPath, certPEM, 0600))
75+
require.NoError(t, os.WriteFile(keyPath, keyPEM, 0600))
76+
77+
return tlsCertFiles{certPath: certPath, keyPath: keyPath}
78+
}
79+
80+
func writeProxyTLSConfig(t *testing.T, configPath string, certFiles tlsCertFiles) {
81+
t.Helper()
82+
83+
configContent := fmt.Sprintf(`
84+
[proxy]
85+
listen = "127.0.0.1:873"
86+
listen_tls = "127.0.0.1:874"
87+
listen_http = "127.0.0.1:9528"
88+
tls_cert_file = %q
89+
tls_key_file = %q
90+
91+
[upstreams.u1]
92+
address = "127.0.0.1:1234"
93+
modules = ["foo"]
94+
95+
[upstreams.u2]
96+
address = "127.0.0.1:1235"
97+
modules = ["bar"]
98+
`, certFiles.certPath, certFiles.keyPath)
99+
require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0600))
100+
}
101+
102+
func newRsyncSSLCommand(certPath string, args ...string) *exec.Cmd {
103+
cmd := exec.Command("rsync-ssl", args...)
104+
cmd.Env = append(os.Environ(),
105+
"RSYNC_SSL_TYPE=openssl",
106+
"RSYNC_SSL_CA_CERT="+certPath,
107+
)
108+
return cmd
109+
}
110+
111+
func normalizeRsyncSSLOutput(output []byte) string {
112+
lines := strings.Split(string(output), "\n")
113+
filtered := lines[:0]
114+
for _, line := range lines {
115+
switch {
116+
case strings.HasPrefix(line, "verify depth is "):
117+
continue
118+
case strings.HasPrefix(line, "Connecting to "):
119+
continue
120+
default:
121+
filtered = append(filtered, line)
122+
}
123+
}
124+
return strings.TrimSuffix(strings.Join(filtered, "\n"), "\n") + "\n"
125+
}
126+
127+
func getRsyncTLSPath(s *server.Server, path string) string {
128+
_, port, err := net.SplitHostPort(s.TLSListenAddr)
129+
if err != nil {
130+
panic(err)
131+
}
132+
return fmt.Sprintf("rsync://localhost:%s%s", port, path)
133+
}
134+
135+
func ensureTLSPortIsReady(t *testing.T, addr string) {
136+
t.Helper()
137+
138+
_, port, err := net.SplitHostPort(addr)
139+
require.NoError(t, err)
140+
141+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
142+
defer cancel()
143+
require.NoError(t, ensureTCPPortIsReady(ctx, port))
144+
}
145+
146+
func TestTLSListModules(t *testing.T) {
147+
r := require.New(t)
148+
dir := t.TempDir()
149+
tlsFiles := writeTestTLSCert(t, dir, "server", "rsync-proxy-e2e")
150+
configPath := filepath.Join(dir, "config.toml")
151+
writeProxyTLSConfig(t, configPath, tlsFiles)
152+
153+
proxy := startProxy(t, func(s *server.Server) {
154+
s.ConfigPath = configPath
155+
s.TLSListenAddr = "127.0.0.1:0"
156+
})
157+
ensureTLSPortIsReady(t, proxy.TLSListenAddr)
158+
159+
outputBytes, err := newRsyncSSLCommand(tlsFiles.certPath, getRsyncTLSPath(proxy, "/")).CombinedOutput()
160+
if err != nil {
161+
t.Log(string(outputBytes))
162+
r.NoError(err)
163+
}
164+
165+
r.Equal("bar\nfoo\n", normalizeRsyncSSLOutput(outputBytes))
166+
}
167+
168+
func TestReloadTLSCertificateE2E(t *testing.T) {
169+
r := require.New(t)
170+
dir := t.TempDir()
171+
firstCert := writeTestTLSCert(t, dir, "first", "first-cert")
172+
secondCert := writeTestTLSCert(t, dir, "second", "second-cert")
173+
configPath := filepath.Join(dir, "config.toml")
174+
writeProxyTLSConfig(t, configPath, firstCert)
175+
176+
proxy := startProxy(t, func(s *server.Server) {
177+
s.ConfigPath = configPath
178+
s.TLSListenAddr = "127.0.0.1:0"
179+
})
180+
ensureTLSPortIsReady(t, proxy.TLSListenAddr)
181+
182+
outputBytes, err := newRsyncSSLCommand(firstCert.certPath, getRsyncTLSPath(proxy, "/")).CombinedOutput()
183+
if err != nil {
184+
t.Log(string(outputBytes))
185+
r.NoError(err)
186+
}
187+
r.Equal("bar\nfoo\n", normalizeRsyncSSLOutput(outputBytes))
188+
189+
writeProxyTLSConfig(t, configPath, secondCert)
190+
191+
var reloadOutput bytes.Buffer
192+
err = cmd.SendReloadRequest(proxy.HTTPListenAddr, &reloadOutput, &reloadOutput)
193+
r.NoError(err)
194+
r.Contains(reloadOutput.String(), "Successfully reloaded")
195+
196+
outputBytes, err = newRsyncSSLCommand(firstCert.certPath, getRsyncTLSPath(proxy, "/")).CombinedOutput()
197+
r.Error(err)
198+
199+
outputBytes, err = newRsyncSSLCommand(secondCert.certPath, getRsyncTLSPath(proxy, "/")).CombinedOutput()
200+
if err != nil {
201+
t.Log(string(outputBytes))
202+
r.NoError(err)
203+
}
204+
r.Equal("bar\nfoo\n", normalizeRsyncSSLOutput(outputBytes))
205+
}

0 commit comments

Comments
 (0)