Skip to content

Commit

Permalink
Support of SSL protocol
Browse files Browse the repository at this point in the history
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
  • Loading branch information
oleg-jukovec committed May 11, 2022
1 parent de95e31 commit 0d72eae
Show file tree
Hide file tree
Showing 17 changed files with 931 additions and 11 deletions.
59 changes: 55 additions & 4 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
workflow_dispatch:

jobs:
linux:
run-tests-ce:
# We want to run on external PRs, but not on our own internal
# PRs as they'll be run by the push to the branch.
#
Expand All @@ -26,9 +26,6 @@ jobs:
- '2.9'
- '2.x-latest'
coveralls: [false]
include:
- tarantool: '2.x-latest'
coveralls: true

steps:
- name: Clone the connector
Expand Down Expand Up @@ -63,3 +60,57 @@ jobs:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
make coveralls
run-tests-ee:
if: github.event_name == 'push' ||
github.event.pull_request.head.repo.full_name != github.repository

runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
sdk-version:
- '1.10.11-0-gf0b0e7ecf-r470'
- '2.8.3-21-g7d35cd2be-r470'
coveralls: [false]
ssl: [false]
include:
- sdk-version: '2.10.0-beta2-59-gc51e2ba67-r469'
coveralls: true
ssl: true

steps:
- name: Clone the connector
uses: actions/checkout@v2

- name: Setup Tarantool ${{ matrix.sdk-version }}
run: |
ARCHIVE_NAME=tarantool-enterprise-bundle-${{ matrix.sdk-version }}.tar.gz
curl -O -L https://${{ secrets.SDK_DOWNLOAD_TOKEN }}@download.tarantool.io/enterprise/${ARCHIVE_NAME}
tar -xzf ${ARCHIVE_NAME}
rm -f ${ARCHIVE_NAME}
- name: Setup golang for the connector and tests
uses: actions/setup-go@v2
with:
go-version: 1.13

- name: Install test dependencies
run: |
source tarantool-enterprise/env.sh
make deps
- name: Run tests
run: |
source tarantool-enterprise/env.sh
make test
env:
TEST_TNT_SSL: ${{matrix.ssl}}

- name: Run tests, collect code coverage data and send to Coveralls
if: ${{ matrix.coveralls }}
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
source tarantool-enterprise/env.sh
make coveralls
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
- Go modules support (#91)
- queue-utube handling (#85)
- Master discovery (#113)
- SSL support (#155)

### Fixed

Expand Down
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ make test
The tests set up all required `tarantool` processes before run and clean up
afterwards.

If you have Tarantool Enterprise Edition 2.10 or newer, you can run additional
SSL tests. To do this, you need to set an environment variable 'TEST_TNT_SSL':

```bash
TEST_TNT_SSL=true make test
```

If you want to run the tests for a specific package:
```bash
make test-<SUBDIR>
Expand Down
49 changes: 44 additions & 5 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ const (
connClosed = 2
)

const (
connTransportNone = ""
connTransportSsl = "ssl"
)

type ConnEventKind int
type ConnLogKind int

Expand Down Expand Up @@ -207,6 +212,32 @@ type Opts struct {
Handle interface{}
// Logger is user specified logger used for error messages.
Logger Logger
// Transport is the connection type, by default the connection is unencrypted.
Transport string
// SslOpts is used only if the Transport == 'ssl' is set.
Ssl SslOpts
}

// SslOpts is a way to configure ssl transport.
type SslOpts struct {
// KeyFile is a path to a private SSL key file.
KeyFile string
// CertFile is a path to an SSL sertificate file.
CertFile string
// CaFile is a path to a trusted certificate authorities (CA) file.
CaFile string
// Ciphers is a colon-separated (:) list of SSL cipher suites the connection
// can use.
//
// We don't provide a list of supported ciphers. This is what OpenSSL
// does. The only limitation is usage of TLSv1.2 (because other protocol
// versions don't seem to support the GOST cipher). To add additional
// ciphers (GOST cipher), you must configure OpenSSL.
//
// See also
//
// * https://www.openssl.org/docs/man1.1.1/man1/ciphers.html
Ciphers string
}

// Connect creates and configures a new Connection.
Expand Down Expand Up @@ -358,8 +389,10 @@ func (conn *Connection) Handle() interface{} {
func (conn *Connection) dial() (err error) {
var connection net.Conn
network := "tcp"
opts := conn.opts
address := conn.addr
timeout := conn.opts.Reconnect / 2
timeout := opts.Reconnect / 2
transport := opts.Transport
if timeout == 0 {
timeout = 500 * time.Millisecond
} else if timeout > 5*time.Second {
Expand All @@ -383,11 +416,17 @@ func (conn *Connection) dial() (err error) {
} else if addrLen >= 4 && address[0:4] == "tcp:" {
address = address[4:]
}
connection, err = net.DialTimeout(network, address, timeout)
if transport == connTransportNone {
connection, err = net.DialTimeout(network, address, timeout)
} else if transport == connTransportSsl {
connection, err = sslDialTimeout(network, address, timeout, opts.Ssl)
} else {
err = errors.New("An unsupported transport type: " + transport)
}
if err != nil {
return
}
dc := &DeadlineIO{to: conn.opts.Timeout, c: connection}
dc := &DeadlineIO{to: opts.Timeout, c: connection}
r := bufio.NewReaderSize(dc, 128*1024)
w := bufio.NewWriterSize(dc, 128*1024)
greeting := make([]byte, 128)
Expand All @@ -400,8 +439,8 @@ func (conn *Connection) dial() (err error) {
conn.Greeting.auth = bytes.NewBuffer(greeting[64:108]).String()

// Auth
if conn.opts.User != "" {
scr, err := scramble(conn.Greeting.auth, conn.opts.Pass)
if opts.User != "" {
scr, err := scramble(conn.Greeting.auth, opts.Pass)
if err != nil {
err = errors.New("auth: scrambling failure " + err.Error())
connection.Close()
Expand Down
20 changes: 19 additions & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,29 @@ type Tuple struct {
func example_connect() *tarantool.Connection {
conn, err := tarantool.Connect(server, opts)
if err != nil {
panic("Connection is not established")
panic("Connection is not established: " + err.Error())
}
return conn
}

// Example demonstrates how to use SSL transport.
func ExampleSslOpts() {
var opts = tarantool.Opts{
User: "test",
Pass: "test",
Transport: "ssl",
Ssl: tarantool.SslOpts{
KeyFile: "testdata/localhost.key",
CertFile: "testdata/localhost.crt",
CaFile: "testdata/ca.crt",
},
}
_, err := tarantool.Connect("127.0.0.1:3013", opts)
if err != nil {
panic("Connection is not established: " + err.Error())
}
}

func ExampleConnection_Select() {
conn := example_connect()
defer conn.Close()
Expand Down
14 changes: 14 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
package tarantool

import (
"net"
"time"
)

func (schema *Schema) ResolveSpaceIndex(s interface{}, i interface{}) (spaceNo, indexNo uint32, err error) {
return schema.resolveSpaceIndex(s, i)
}

func SslDialTimeout(network, address string, timeout time.Duration,
opts SslOpts) (connection net.Conn, err error) {
return sslDialTimeout(network, address, timeout, opts)
}

func SslCreateContext(opts SslOpts) (ctx interface{}, err error) {
return sslCreateContext(opts)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/google/uuid v1.3.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/stretchr/testify v1.7.1 // indirect
github.com/tarantool/go-openssl v0.0.8-0.20220419150948-be4921aa2f87
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/vmihailenco/msgpack.v2 v2.9.2
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,25 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tarantool/go-openssl v0.0.8-0.20220419150948-be4921aa2f87 h1:JGzuBxNBq5saVtPUcuu5Y4+kbJON6H02//OT+RNqGts=
github.com/tarantool/go-openssl v0.0.8-0.20220419150948-be4921aa2f87/go.mod h1:M7H4xYSbzqpW/ZRBMyH0eyqQBsnhAMfsYk5mv0yid7A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
110 changes: 110 additions & 0 deletions ssl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//go:build !go_tarantool_ssl_disable
// +build !go_tarantool_ssl_disable

package tarantool

import (
"errors"
"io/ioutil"
"net"
"time"

"github.com/tarantool/go-openssl"
)

func sslDialTimeout(network, address string, timeout time.Duration,
opts SslOpts) (connection net.Conn, err error) {
var ctx interface{}
if ctx, err = sslCreateContext(opts); err != nil {
return
}

return openssl.DialTimeout(network, address, timeout, ctx.(*openssl.Ctx), 0)
}

// interface{} is a hack. It helps to avoid dependency of go-openssl in build
// of tests with the tag 'go_tarantool_ssl_disable'.
func sslCreateContext(opts SslOpts) (ctx interface{}, err error) {
var sslCtx *openssl.Ctx

// Require TLSv1.2, because other protocol versions don't seem to
// support the GOST cipher.
if sslCtx, err = openssl.NewCtxWithVersion(openssl.TLSv1_2); err != nil {
return
}
ctx = sslCtx
sslCtx.SetMaxProtoVersion(openssl.TLS1_2_VERSION)
sslCtx.SetMinProtoVersion(openssl.TLS1_2_VERSION)

if opts.CertFile != "" {
if err = sslLoadCert(sslCtx, opts.CertFile); err != nil {
return
}
}

if opts.KeyFile != "" {
if err = sslLoadKey(sslCtx, opts.KeyFile); err != nil {
return
}
}

if opts.CaFile != "" {
if err = sslCtx.LoadVerifyLocations(opts.CaFile, ""); err != nil {
return
}
verifyFlags := openssl.VerifyPeer | openssl.VerifyFailIfNoPeerCert
sslCtx.SetVerify(verifyFlags, nil)
}

if opts.Ciphers != "" {
sslCtx.SetCipherList(opts.Ciphers)
}

return
}

func sslLoadCert(ctx *openssl.Ctx, certFile string) (err error) {
var certBytes []byte
if certBytes, err = ioutil.ReadFile(certFile); err != nil {
return
}

certs := openssl.SplitPEM(certBytes)
if len(certs) == 0 {
err = errors.New("No PEM certificate found in " + certFile)
return
}
first, certs := certs[0], certs[1:]

var cert *openssl.Certificate
if cert, err = openssl.LoadCertificateFromPEM(first); err != nil {
return
}
if err = ctx.UseCertificate(cert); err != nil {
return
}

for _, pem := range certs {
if cert, err = openssl.LoadCertificateFromPEM(pem); err != nil {
break
}
if err = ctx.AddChainCertificate(cert); err != nil {
break
}
}
return
}

func sslLoadKey(ctx *openssl.Ctx, keyFile string) (err error) {
var keyBytes []byte
if keyBytes, err = ioutil.ReadFile(keyFile); err != nil {
return
}

var key openssl.PrivateKey
if key, err = openssl.LoadPrivateKeyFromPEM(keyBytes); err != nil {
return
}

return ctx.UsePrivateKey(key)
}
Loading

0 comments on commit 0d72eae

Please sign in to comment.