Skip to content

Commit dabffea

Browse files
committed
Support of SSL protocol
The patch adds support for using SSL to encrypt the client-server communications [1]. It uses a wrapper around the OpenSSL library for full compatibility with Tarantool Enterprise (GOST cryptographic algorithms [2] are not supported by Golang's crypto/tls). The feature can be disabled using a build tag [3] 'go_tarantool_ssl_disable'. 1. https://www.tarantool.io/en/enterprise_doc/security/#enterprise-iproto-encryption 2. https://github.com/gost-engine/engine 3. https://pkg.go.dev/go/build#hdr-Build_Constraints Closes #155
1 parent d44ffa0 commit dabffea

18 files changed

+939
-11
lines changed

.github/workflows/testing.yml

+58-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
workflow_dispatch:
77

88
jobs:
9-
linux:
9+
run-tests-ce:
1010
# We want to run on external PRs, but not on our own internal
1111
# PRs as they'll be run by the push to the branch.
1212
#
@@ -26,9 +26,6 @@ jobs:
2626
- '2.9'
2727
- '2.x-latest'
2828
coveralls: [false]
29-
include:
30-
- tarantool: '2.x-latest'
31-
coveralls: true
3229

3330
steps:
3431
- name: Clone the connector
@@ -66,3 +63,60 @@ jobs:
6663
6764
- name: Check workability of benchmark tests
6865
run: make bench-deps bench DURATION=1x COUNT=1
66+
run-tests-ee:
67+
if: github.event_name == 'push' ||
68+
github.event.pull_request.head.repo.full_name != github.repository
69+
70+
runs-on: ubuntu-latest
71+
72+
strategy:
73+
fail-fast: false
74+
matrix:
75+
sdk-version:
76+
- '1.10.11-0-gf0b0e7ecf-r470'
77+
- '2.8.3-21-g7d35cd2be-r470'
78+
coveralls: [false]
79+
ssl: [false]
80+
include:
81+
- sdk-version: '2.10.0-beta2-59-gc51e2ba67-r469'
82+
coveralls: true
83+
ssl: true
84+
85+
steps:
86+
- name: Clone the connector
87+
uses: actions/checkout@v2
88+
89+
- name: Setup Tarantool ${{ matrix.sdk-version }}
90+
run: |
91+
ARCHIVE_NAME=tarantool-enterprise-bundle-${{ matrix.sdk-version }}.tar.gz
92+
curl -O -L https://${{ secrets.SDK_DOWNLOAD_TOKEN }}@download.tarantool.io/enterprise/${ARCHIVE_NAME}
93+
tar -xzf ${ARCHIVE_NAME}
94+
rm -f ${ARCHIVE_NAME}
95+
96+
- name: Setup golang for the connector and tests
97+
uses: actions/setup-go@v2
98+
with:
99+
go-version: 1.13
100+
101+
- name: Install test dependencies
102+
run: |
103+
source tarantool-enterprise/env.sh
104+
make deps
105+
106+
- name: Run tests
107+
run: |
108+
source tarantool-enterprise/env.sh
109+
make test
110+
env:
111+
TEST_TNT_SSL: ${{matrix.ssl}}
112+
113+
- name: Run tests, collect code coverage data and send to Coveralls
114+
if: ${{ matrix.coveralls }}
115+
env:
116+
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
117+
run: |
118+
source tarantool-enterprise/env.sh
119+
make coveralls
120+
121+
- name: Check workability of benchmark tests
122+
run: make bench-deps bench DURATION=1x COUNT=1

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1818
- queue-utube handling (#85)
1919
- Master discovery (#113)
2020
- SQL support (#62)
21+
- SSL support (#155)
2122

2223
### Fixed
2324

CONTRIBUTING.md

+7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ make test
2929
The tests set up all required `tarantool` processes before run and clean up
3030
afterwards.
3131

32+
If you have Tarantool Enterprise Edition 2.10 or newer, you can run additional
33+
SSL tests. To do this, you need to set an environment variable 'TEST_TNT_SSL':
34+
35+
```bash
36+
TEST_TNT_SSL=true make test
37+
```
38+
3239
If you want to run the tests for a specific package:
3340
```bash
3441
make test-<SUBDIR>

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ clean:
2323
deps: clean
2424
( cd ./queue; tarantoolctl rocks install queue 1.1.0 )
2525

26+
.PHONY: testdata
27+
testdata:
28+
(cd ./testdata; ./generate.sh)
29+
2630
.PHONY: test
2731
test:
2832
go test ./... -v -p 1

connection.go

+44-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ const (
2525
connClosed = 2
2626
)
2727

28+
const (
29+
connTransportNone = ""
30+
connTransportSsl = "ssl"
31+
)
32+
2833
type ConnEventKind int
2934
type ConnLogKind int
3035

@@ -207,6 +212,32 @@ type Opts struct {
207212
Handle interface{}
208213
// Logger is user specified logger used for error messages.
209214
Logger Logger
215+
// Transport is the connection type, by default the connection is unencrypted.
216+
Transport string
217+
// SslOpts is used only if the Transport == 'ssl' is set.
218+
Ssl SslOpts
219+
}
220+
221+
// SslOpts is a way to configure ssl transport.
222+
type SslOpts struct {
223+
// KeyFile is a path to a private SSL key file.
224+
KeyFile string
225+
// CertFile is a path to an SSL sertificate file.
226+
CertFile string
227+
// CaFile is a path to a trusted certificate authorities (CA) file.
228+
CaFile string
229+
// Ciphers is a colon-separated (:) list of SSL cipher suites the connection
230+
// can use.
231+
//
232+
// We don't provide a list of supported ciphers. This is what OpenSSL
233+
// does. The only limitation is usage of TLSv1.2 (because other protocol
234+
// versions don't seem to support the GOST cipher). To add additional
235+
// ciphers (GOST cipher), you must configure OpenSSL.
236+
//
237+
// See also
238+
//
239+
// * https://www.openssl.org/docs/man1.1.1/man1/ciphers.html
240+
Ciphers string
210241
}
211242

212243
// Connect creates and configures a new Connection.
@@ -358,8 +389,10 @@ func (conn *Connection) Handle() interface{} {
358389
func (conn *Connection) dial() (err error) {
359390
var connection net.Conn
360391
network := "tcp"
392+
opts := conn.opts
361393
address := conn.addr
362-
timeout := conn.opts.Reconnect / 2
394+
timeout := opts.Reconnect / 2
395+
transport := opts.Transport
363396
if timeout == 0 {
364397
timeout = 500 * time.Millisecond
365398
} else if timeout > 5*time.Second {
@@ -383,11 +416,17 @@ func (conn *Connection) dial() (err error) {
383416
} else if addrLen >= 4 && address[0:4] == "tcp:" {
384417
address = address[4:]
385418
}
386-
connection, err = net.DialTimeout(network, address, timeout)
419+
if transport == connTransportNone {
420+
connection, err = net.DialTimeout(network, address, timeout)
421+
} else if transport == connTransportSsl {
422+
connection, err = sslDialTimeout(network, address, timeout, opts.Ssl)
423+
} else {
424+
err = errors.New("An unsupported transport type: " + transport)
425+
}
387426
if err != nil {
388427
return
389428
}
390-
dc := &DeadlineIO{to: conn.opts.Timeout, c: connection}
429+
dc := &DeadlineIO{to: opts.Timeout, c: connection}
391430
r := bufio.NewReaderSize(dc, 128*1024)
392431
w := bufio.NewWriterSize(dc, 128*1024)
393432
greeting := make([]byte, 128)
@@ -400,8 +439,8 @@ func (conn *Connection) dial() (err error) {
400439
conn.Greeting.auth = bytes.NewBuffer(greeting[64:108]).String()
401440

402441
// Auth
403-
if conn.opts.User != "" {
404-
scr, err := scramble(conn.Greeting.auth, conn.opts.Pass)
442+
if opts.User != "" {
443+
scr, err := scramble(conn.Greeting.auth, opts.Pass)
405444
if err != nil {
406445
err = errors.New("auth: scrambling failure " + err.Error())
407446
connection.Close()

example_test.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,29 @@ type Tuple struct {
2020
func example_connect() *tarantool.Connection {
2121
conn, err := tarantool.Connect(server, opts)
2222
if err != nil {
23-
panic("Connection is not established")
23+
panic("Connection is not established: " + err.Error())
2424
}
2525
return conn
2626
}
2727

28+
// Example demonstrates how to use SSL transport.
29+
func ExampleSslOpts() {
30+
var opts = tarantool.Opts{
31+
User: "test",
32+
Pass: "test",
33+
Transport: "ssl",
34+
Ssl: tarantool.SslOpts{
35+
KeyFile: "testdata/localhost.key",
36+
CertFile: "testdata/localhost.crt",
37+
CaFile: "testdata/ca.crt",
38+
},
39+
}
40+
_, err := tarantool.Connect("127.0.0.1:3013", opts)
41+
if err != nil {
42+
panic("Connection is not established: " + err.Error())
43+
}
44+
}
45+
2846
func ExampleConnection_Select() {
2947
conn := example_connect()
3048
defer conn.Close()

export_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
package tarantool
22

3+
import (
4+
"net"
5+
"time"
6+
)
7+
38
func (schema *Schema) ResolveSpaceIndex(s interface{}, i interface{}) (spaceNo, indexNo uint32, err error) {
49
return schema.resolveSpaceIndex(s, i)
510
}
11+
12+
func SslDialTimeout(network, address string, timeout time.Duration,
13+
opts SslOpts) (connection net.Conn, err error) {
14+
return sslDialTimeout(network, address, timeout, opts)
15+
}
16+
17+
func SslCreateContext(opts SslOpts) (ctx interface{}, err error) {
18+
return sslCreateContext(opts)
19+
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/google/uuid v1.3.0
77
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
88
github.com/stretchr/testify v1.7.1 // indirect
9+
github.com/tarantool/go-openssl v0.0.8-0.20220419150948-be4921aa2f87
910
google.golang.org/appengine v1.6.7 // indirect
1011
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
1112
gopkg.in/vmihailenco/msgpack.v2 v2.9.2

go.sum

+8
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,25 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
77
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
88
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
99
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
10+
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
11+
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
1012
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
1113
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
1214
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1315
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
16+
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
17+
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc=
1418
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1519
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
1620
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
21+
github.com/tarantool/go-openssl v0.0.8-0.20220419150948-be4921aa2f87 h1:JGzuBxNBq5saVtPUcuu5Y4+kbJON6H02//OT+RNqGts=
22+
github.com/tarantool/go-openssl v0.0.8-0.20220419150948-be4921aa2f87/go.mod h1:M7H4xYSbzqpW/ZRBMyH0eyqQBsnhAMfsYk5mv0yid7A=
1723
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
1824
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
1925
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
2026
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
27+
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
28+
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
2129
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
2230
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
2331
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

ssl.go

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//go:build !go_tarantool_ssl_disable
2+
// +build !go_tarantool_ssl_disable
3+
4+
package tarantool
5+
6+
import (
7+
"errors"
8+
"io/ioutil"
9+
"net"
10+
"time"
11+
12+
"github.com/tarantool/go-openssl"
13+
)
14+
15+
func sslDialTimeout(network, address string, timeout time.Duration,
16+
opts SslOpts) (connection net.Conn, err error) {
17+
var ctx interface{}
18+
if ctx, err = sslCreateContext(opts); err != nil {
19+
return
20+
}
21+
22+
return openssl.DialTimeout(network, address, timeout, ctx.(*openssl.Ctx), 0)
23+
}
24+
25+
// interface{} is a hack. It helps to avoid dependency of go-openssl in build
26+
// of tests with the tag 'go_tarantool_ssl_disable'.
27+
func sslCreateContext(opts SslOpts) (ctx interface{}, err error) {
28+
var sslCtx *openssl.Ctx
29+
30+
// Require TLSv1.2, because other protocol versions don't seem to
31+
// support the GOST cipher.
32+
if sslCtx, err = openssl.NewCtxWithVersion(openssl.TLSv1_2); err != nil {
33+
return
34+
}
35+
ctx = sslCtx
36+
sslCtx.SetMaxProtoVersion(openssl.TLS1_2_VERSION)
37+
sslCtx.SetMinProtoVersion(openssl.TLS1_2_VERSION)
38+
39+
if opts.CertFile != "" {
40+
if err = sslLoadCert(sslCtx, opts.CertFile); err != nil {
41+
return
42+
}
43+
}
44+
45+
if opts.KeyFile != "" {
46+
if err = sslLoadKey(sslCtx, opts.KeyFile); err != nil {
47+
return
48+
}
49+
}
50+
51+
if opts.CaFile != "" {
52+
if err = sslCtx.LoadVerifyLocations(opts.CaFile, ""); err != nil {
53+
return
54+
}
55+
verifyFlags := openssl.VerifyPeer | openssl.VerifyFailIfNoPeerCert
56+
sslCtx.SetVerify(verifyFlags, nil)
57+
}
58+
59+
if opts.Ciphers != "" {
60+
sslCtx.SetCipherList(opts.Ciphers)
61+
}
62+
63+
return
64+
}
65+
66+
func sslLoadCert(ctx *openssl.Ctx, certFile string) (err error) {
67+
var certBytes []byte
68+
if certBytes, err = ioutil.ReadFile(certFile); err != nil {
69+
return
70+
}
71+
72+
certs := openssl.SplitPEM(certBytes)
73+
if len(certs) == 0 {
74+
err = errors.New("No PEM certificate found in " + certFile)
75+
return
76+
}
77+
first, certs := certs[0], certs[1:]
78+
79+
var cert *openssl.Certificate
80+
if cert, err = openssl.LoadCertificateFromPEM(first); err != nil {
81+
return
82+
}
83+
if err = ctx.UseCertificate(cert); err != nil {
84+
return
85+
}
86+
87+
for _, pem := range certs {
88+
if cert, err = openssl.LoadCertificateFromPEM(pem); err != nil {
89+
break
90+
}
91+
if err = ctx.AddChainCertificate(cert); err != nil {
92+
break
93+
}
94+
}
95+
return
96+
}
97+
98+
func sslLoadKey(ctx *openssl.Ctx, keyFile string) (err error) {
99+
var keyBytes []byte
100+
if keyBytes, err = ioutil.ReadFile(keyFile); err != nil {
101+
return
102+
}
103+
104+
var key openssl.PrivateKey
105+
if key, err = openssl.LoadPrivateKeyFromPEM(keyBytes); err != nil {
106+
return
107+
}
108+
109+
return ctx.UsePrivateKey(key)
110+
}

0 commit comments

Comments
 (0)