Skip to content

Commit b9ff7f1

Browse files
committed
connect: add SSL support
The patch adds options to configure traffic encryption[1]: --sslkeyfile - a path to a private SSL key file; --sslcerfile - a path to an SSL certificate file; --sslcafile - a path to a trusted certificate authorities (CA) file; --sslciphers - colon-separated (:) list of SSL cipher suites the connection can use; 1. https://www.tarantool.io/en/enterprise_doc/security/#configuration Part of #308
1 parent 822f26d commit b9ff7f1

File tree

16 files changed

+492
-79
lines changed

16 files changed

+492
-79
lines changed

cli/cmd/connect.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ var (
2828
connectPassword string
2929
connectFile string
3030
connectLanguage string
31+
connectSslKeyFile string
32+
connectSslCertFile string
33+
connectSslCaFile string
34+
connectSslCiphers string
3135
connectInteractive bool
3236
)
3337

@@ -57,6 +61,16 @@ func NewConnectCmd() *cobra.Command {
5761
`file to read the script for evaluation. "-" - read the script from stdin`)
5862
connectCmd.Flags().StringVarP(&connectLanguage, "language", "l",
5963
connect.DefaultLanguage.String(), `language: lua or sql`)
64+
connectCmd.Flags().StringVarP(&connectSslKeyFile, "sslkeyfile", "",
65+
connect.DefaultLanguage.String(), `path to a private SSL key file`)
66+
connectCmd.Flags().StringVarP(&connectSslCertFile, "sslcertfile", "",
67+
connect.DefaultLanguage.String(), `path to a SSL certificate file`)
68+
connectCmd.Flags().StringVarP(&connectSslCaFile, "sslcafile", "",
69+
connect.DefaultLanguage.String(),
70+
`path to a trusted certificate authorities (CA) file`)
71+
connectCmd.Flags().StringVarP(&connectSslCiphers, "sslciphers", "",
72+
connect.DefaultLanguage.String(),
73+
`colon-separated (:) list of SSL cipher suites the connection`)
6074
connectCmd.Flags().BoolVarP(&connectInteractive, "interactive", "i",
6175
false, `enter interactive mode after executing 'FILE'`)
6276

@@ -169,6 +183,10 @@ func internalConnectModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
169183
Username: connectUser,
170184
Password: connectPassword,
171185
SrcFile: connectFile,
186+
SslKeyFile: connectSslKeyFile,
187+
SslCertFile: connectSslCertFile,
188+
SslCaFile: connectSslCaFile,
189+
SslCiphers: connectSslCiphers,
172190
Interactive: connectInteractive,
173191
}
174192

cli/connect/connect.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ type ConnectCtx struct {
2222
SrcFile string
2323
// Language to use for execution.
2424
Language Language
25+
// SslKeyFile is a path to a private SSL key file.
26+
SslKeyFile string
27+
// SslCertFile is a path to an SSL certificate file.
28+
SslCertFile string
29+
// SslCaFile is a path to a trusted certificate authorities (CA) file.
30+
SslCaFile string
31+
// SslCiphers is a colon-separated (:) list of SSL cipher suites the
32+
// connection can use.
33+
SslCiphers string
2534
// Interactive mode is used.
2635
Interactive bool
2736
}
@@ -34,7 +43,13 @@ const (
3443
func getConnOpts(connString string, connCtx ConnectCtx) connector.ConnectOpts {
3544
username := connCtx.Username
3645
password := connCtx.Password
37-
return connector.MakeConnectOpts(connString, username, password)
46+
ssl := connector.SslOpts{
47+
KeyFile: connCtx.SslKeyFile,
48+
CertFile: connCtx.SslCertFile,
49+
CaFile: connCtx.SslCaFile,
50+
Ciphers: connCtx.SslCiphers,
51+
}
52+
return connector.MakeConnectOpts(connString, username, password, ssl)
3853
}
3954

4055
// getEvalCmd returns a command from the input source (file or stdin).

cli/connector/connector.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,17 @@ func Connect(opts ConnectOpts) (Connector, error) {
7474
// Set a deadline for the greeting.
7575
greetingConn.SetReadDeadline(time.Now().Add(greetingOperationTimeout))
7676

77-
// Detect protocol.
77+
// Detect transport and protocol.
78+
transport := ""
7879
protocol, err := GetProtocol(greetingConn)
7980
if err != nil {
80-
return nil, fmt.Errorf("failed to get protocol: %s", err)
81+
if opts.Ssl.KeyFile != "" || opts.Ssl.CertFile != "" ||
82+
opts.Ssl.CaFile != "" || opts.Ssl.Ciphers != "" {
83+
protocol = BinaryProtocol
84+
transport = "ssl"
85+
} else {
86+
return nil, fmt.Errorf("failed to get protocol: %s", err)
87+
}
8188
}
8289

8390
// Reset the deadline. From the SetDeadline doc:
@@ -95,6 +102,8 @@ func Connect(opts ConnectOpts) (Connector, error) {
95102
conn, err := tarantool.Connect(addr, tarantool.Opts{
96103
User: opts.Username,
97104
Pass: opts.Password,
105+
Transport: transport,
106+
Ssl: tarantool.SslOpts(opts.Ssl),
98107
SkipSchema: true, // We don't need a schema for eval requests.
99108
})
100109
if err != nil {

cli/connector/integration_test.go

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build integration
12
// +build integration
23

34
package connector_test
@@ -10,6 +11,7 @@ import (
1011
"time"
1112

1213
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
1315
"github.com/tarantool/go-tarantool"
1416
"github.com/tarantool/go-tarantool/test_helpers"
1517

@@ -18,13 +20,20 @@ import (
1820

1921
const workDir = "work_dir"
2022
const server = "127.0.0.1:3013"
23+
const serverTls = "127.0.0.1:3014"
2124
const console = workDir + "/" + "console.control"
2225

26+
var tarantoolEe bool
2327
var opts = tarantool.Opts{
2428
Timeout: 500 * time.Millisecond,
2529
User: "test",
2630
Pass: "password",
2731
}
32+
var sslOpts = SslOpts{
33+
KeyFile: "testdata/localhost.key",
34+
CertFile: "testdata/localhost.crt",
35+
CaFile: "testdata/ca.crt",
36+
}
2837

2938
func textConnectWithValidation(t *testing.T) *TextConnector {
3039
t.Helper()
@@ -167,12 +176,32 @@ func TestBinaryConnector_Eval_pushCallback(t *testing.T) {
167176

168177
func TestConnect_binary(t *testing.T) {
169178
conn, err := Connect(ConnectOpts{
170-
Network: "tcp",
171-
Address: server,
179+
Network: "tcp",
180+
Address: server,
172181
Username: "test",
173182
Password: "password",
174183
})
184+
require.NoError(t, err)
185+
defer conn.Close()
186+
187+
eval := "return 'hello', 'world'"
188+
ret, err := conn.Eval(eval, []interface{}{}, RequestOpts{})
175189
assert.NoError(t, err)
190+
assert.Equal(t, []interface{}{"hello", "world"}, ret)
191+
}
192+
193+
func TestConnect_binaryTls(t *testing.T) {
194+
if !tarantoolEe {
195+
t.Skip("Only for Tarantool Enterprise.")
196+
}
197+
conn, err := Connect(ConnectOpts{
198+
Network: "tcp",
199+
Address: serverTls,
200+
Username: "test",
201+
Password: "password",
202+
Ssl: sslOpts,
203+
})
204+
require.NoError(t, err)
176205
defer conn.Close()
177206

178207
eval := "return 'hello', 'world'"
@@ -186,7 +215,7 @@ func TestConnect_text(t *testing.T) {
186215
Network: "unix",
187216
Address: console,
188217
})
189-
assert.NoError(t, err)
218+
require.NoError(t, err)
190219
defer conn.Close()
191220

192221
eval := "return 'hello', 'world'"
@@ -197,21 +226,64 @@ func TestConnect_text(t *testing.T) {
197226

198227
func runTestMain(m *testing.M) int {
199228
inst, err := test_helpers.StartTarantool(test_helpers.StartOpts{
200-
InitScript: "testdata/config.lua",
201-
Listen: server,
202-
WorkDir: workDir,
203-
User: opts.User,
204-
Pass: opts.Pass,
205-
WaitStart: 100 * time.Millisecond,
206-
ConnectRetry: 3,
207-
RetryTimeout: 500 * time.Millisecond,
229+
InitScript: "testdata/config.lua",
230+
Listen: server,
231+
WorkDir: workDir,
232+
User: opts.User,
233+
Pass: opts.Pass,
234+
WaitStart: 100 * time.Millisecond,
235+
ConnectRetry: 3,
236+
RetryTimeout: 500 * time.Millisecond,
208237
})
209238
defer test_helpers.StopTarantoolWithCleanup(inst)
210-
211239
if err != nil {
212240
log.Fatalf("Failed to prepare test tarantool: %s", err)
213241
}
214242

243+
conn, err := tarantool.Connect(server, opts)
244+
if err != nil {
245+
log.Fatalf("Failed to check tarantool version: %s", err)
246+
}
247+
req := tarantool.NewEvalRequest("return box.info.package")
248+
resp, err := conn.Do(req).Get()
249+
conn.Close()
250+
251+
if err != nil {
252+
log.Fatalf("Failed to get box.info.package: %s", err)
253+
}
254+
255+
if len(resp.Data) > 0 {
256+
if pack, ok := resp.Data[0].(string); ok {
257+
tarantoolEe = pack == "Tarantool Enterprise"
258+
}
259+
}
260+
261+
if tarantoolEe {
262+
// Try to start Tarantool instance with TLS.
263+
listen := serverTls + "?transport=ssl&" +
264+
"ssl_key_file=testdata/localhost.key&" +
265+
"ssl_cert_file=testdata/localhost.crt&" +
266+
"ssl_ca_file=testdata/ca.crt"
267+
inst, err = test_helpers.StartTarantool(test_helpers.StartOpts{
268+
InitScript: "testdata/config.lua",
269+
Listen: listen,
270+
SslCertsDir: "testdata",
271+
ClientServer: serverTls,
272+
ClientTransport: "ssl",
273+
ClientSsl: tarantool.SslOpts(sslOpts),
274+
WorkDir: workDir,
275+
User: opts.User,
276+
Pass: opts.Pass,
277+
WaitStart: 100 * time.Millisecond,
278+
ConnectRetry: 3,
279+
RetryTimeout: 500 * time.Millisecond,
280+
})
281+
if err != nil {
282+
log.Fatalf("Failed to prepare test tarantool with TLS: %s", err)
283+
}
284+
defer test_helpers.StopTarantoolWithCleanup(inst)
285+
}
286+
215287
return m.Run()
216288
}
217289

cli/connector/opts.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,33 @@ type ConnectOpts struct {
2020
Username string
2121
// Password of the user.
2222
Password string
23+
// Ssl options for a connection.
24+
Ssl SslOpts
25+
}
26+
27+
// SslOpts is a way to configure SSL connection.
28+
type SslOpts struct {
29+
// KeyFile is a path to a private SSL key file.
30+
KeyFile string
31+
// CertFile is a path to an SSL certificate file.
32+
CertFile string
33+
// CaFile is a path to a trusted certificate authorities (CA) file.
34+
CaFile string
35+
// Ciphers is a colon-separated (:) list of SSL cipher suites the
36+
// connection can use.
37+
Ciphers string
2338
}
2439

2540
// MakeConnectOpts creates a new connection options object according to the
2641
// arguments passed. An username and a password values from the connection
2742
// string are used only if the username and password from the arguments are
2843
// empty.
29-
func MakeConnectOpts(connString, username, password string) ConnectOpts {
44+
func MakeConnectOpts(connString, username, password string,
45+
ssl SslOpts) ConnectOpts {
3046
connOpts := ConnectOpts{
3147
Username: username,
3248
Password: password,
49+
Ssl: ssl,
3350
}
3451

3552
connStringParts := strings.SplitN(connString, "@", 2)

0 commit comments

Comments
 (0)