Skip to content

Commit 788e2b6

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/ru/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 f388f6d commit 788e2b6

18 files changed

+931
-11
lines changed

.github/workflows/testing.yml

+52-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
@@ -63,3 +60,54 @@ jobs:
6360
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6461
run: |
6562
make coveralls
63+
run-tests-ee:
64+
if: github.event_name == 'push' ||
65+
github.event.pull_request.head.repo.full_name != github.repository
66+
67+
runs-on: ubuntu-latest
68+
69+
env:
70+
TEST_TNT_EE: 1
71+
72+
strategy:
73+
fail-fast: false
74+
matrix:
75+
sdk-version: ["2.10.0-beta2-59-gc51e2ba67-r469"]
76+
coveralls: [false]
77+
include:
78+
- sdk-version: '2.10.0-beta2-59-gc51e2ba67-r469'
79+
coveralls: true
80+
81+
steps:
82+
- name: Clone the connector
83+
uses: actions/checkout@v2
84+
85+
- name: Setup Tarantool ${{ matrix.sdk-version }}
86+
run: |
87+
ARCHIVE_NAME=tarantool-enterprise-bundle-${{ matrix.sdk-version }}.tar.gz
88+
curl -O -L https://${{ secrets.SDK_DOWNLOAD_TOKEN }}@download.tarantool.io/enterprise/${ARCHIVE_NAME}
89+
tar -xzf ${ARCHIVE_NAME}
90+
rm -f ${ARCHIVE_NAME}
91+
92+
- name: Setup golang for the connector and tests
93+
uses: actions/setup-go@v2
94+
with:
95+
go-version: 1.13
96+
97+
- name: Install test dependencies
98+
run: |
99+
source tarantool-enterprise/env.sh
100+
make deps
101+
102+
- name: Run tests
103+
run: |
104+
source tarantool-enterprise/env.sh
105+
make test
106+
107+
- name: Run tests, collect code coverage data and send to Coveralls
108+
if: ${{ matrix.coveralls }}
109+
env:
110+
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
111+
run: |
112+
source tarantool-enterprise/env.sh
113+
make coveralls

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1616
- Support UUID type in msgpack (#90)
1717
- Go modules support (#91)
1818
- queue-utube handling (#85)
19+
- SSL support (#155)
1920

2021
### Fixed
2122

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, then you can run additional tests.
33+
To do this, you need to set an environment variable 'TEST_TNT_EE':
34+
35+
```bash
36+
TEST_TNT_EE=1 make test
37+
```
38+
3239
If you want to run the tests for a specific package:
3340
```bash
3441
make test-<SUBDIR>

config.lua

+3-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ box.space.test:truncate()
7373
--box.schema.user.revoke('guest', 'read,write,execute', 'universe')
7474

7575
-- Set listen only when every other thing is configured.
76+
-- Try to decode it as YAML first.
77+
local yaml = require('yaml')
7678
box.cfg{
77-
listen = os.getenv("TEST_TNT_LISTEN"),
79+
listen = yaml.decode(os.getenv("TEST_TNT_LISTEN")),
7880
}

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

+18
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,24 @@ func example_connect() *tarantool.Connection {
2424
return conn
2525
}
2626

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

export_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
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+
// Yes, this is an anti-pattern. We will test implementation, not public API.
13+
// But in this case it is important to do it. Because we must to test
14+
// an integration with an SSL library.
15+
func SslDialTimeout(network, address string, timeout time.Duration,
16+
opts SslOpts) (connection net.Conn, err error) {
17+
return sslDialTimeout(network, address, timeout, opts)
18+
}
19+
20+
func SslCreateContext(opts SslOpts) (ctx interface{}, err error) {
21+
return sslCreateContext(opts)
22+
}

go.mod

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

go.sum

+8
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
55
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
66
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
77
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
8+
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
9+
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
810
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
911
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
12+
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
13+
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc=
14+
github.com/tarantool/go-openssl v0.0.8-0.20220419150948-be4921aa2f87 h1:JGzuBxNBq5saVtPUcuu5Y4+kbJON6H02//OT+RNqGts=
15+
github.com/tarantool/go-openssl v0.0.8-0.20220419150948-be4921aa2f87/go.mod h1:M7H4xYSbzqpW/ZRBMyH0eyqQBsnhAMfsYk5mv0yid7A=
1016
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
1117
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
1218
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
1319
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
20+
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
21+
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1422
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1523
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
1624
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 nil, err
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)