diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..3bde87e --- /dev/null +++ b/.codespellrc @@ -0,0 +1,5 @@ +[codespell] +skip = */testdata,./LICENSE +ignore-words-list = gost +count = +quiet-level = 3 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..846a442 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,72 @@ +name: Run checks + +on: + push: + pull_request: + +jobs: + luacheck: + runs-on: ubuntu-latest + if: | + github.event_name == 'push' || + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.repository + steps: + - uses: actions/checkout@master + + - name: Setup Tarantool + uses: tarantool/setup-tarantool@v2 + with: + tarantool-version: '2.8' + + - name: Setup tt + run: | + curl -L https://tarantool.io/release/2/installer.sh | sudo bash + sudo apt install -y tt + tt version + + - name: Setup luacheck + run: tt rocks install luacheck 0.25.0 + + - name: Run luacheck + run: ./.rocks/bin/luacheck . + + golangci-lint: + runs-on: ubuntu-latest + if: | + github.event_name == 'push' || + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.repository + steps: + - uses: actions/setup-go@v2 + + - uses: actions/checkout@v2 + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + continue-on-error: true + with: + # The first run is for GitHub Actions error format. + args: --config=.golangci.yaml + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + # The second run is for human-readable error format with a file name + # and a line number. + args: --out-${NO_FUTURE}format colored-line-number --config=.golangci.yaml + + codespell: + runs-on: ubuntu-latest + if: | + github.event_name == 'push' || + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.repository + steps: + - uses: actions/checkout@master + + - name: Install codespell + run: pip3 install codespell + + - name: Run codespell + run: codespell diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 0000000..a7b3e19 --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,233 @@ +name: testing + +on: + push: + pull_request: + pull_request_target: + types: [labeled] + workflow_dispatch: + +jobs: + run-tests-ee: + # Does not run on pull requests from forks and on forks by default. + # Tests from forks will run only when the pull request is labeled with + # `full-ci`. To avoid security problems, the label must be reset manually + # for every run. + # + # We need to use `pull_request_target` because it has access to base + # repository secrets unlike `pull_request`. + if: | + github.repository == 'tarantool/go-tlsdialer' && + (github.event_name == 'push' || + (github.event_name == 'pull_request_target' && + github.event.pull_request.head.repo.full_name != github.repository && + github.event.label.name == 'full-ci')) || + github.event_name == 'workflow_dispatch' + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + sdk-path: + - 'release/linux/x86_64/1.10/' + sdk-version: + - 'sdk-1.10.15-0-r598' + coveralls: [ false ] + fuzzing: [ false ] + ssl: [ false ] + include: + - sdk-path: 'release/linux/x86_64/2.10/' + sdk-version: 'sdk-gc64-2.10.8-0-r598.linux.x86_64' + coveralls: false + ssl: true + - sdk-path: 'release/linux/x86_64/2.11/' + sdk-version: 'sdk-gc64-2.11.1-0-r598.linux.x86_64' + coveralls: true + ssl: true + + steps: + - name: Clone the connector + # `ref` as merge request is needed for pull_request_target because this + # target runs in the context of the base commit of the pull request. + uses: actions/checkout@v3 + if: github.event_name == 'pull_request_target' + with: + ref: refs/pull/${{ github.event.pull_request.number }}/merge + + - name: Clone the connector + if: github.event_name != 'pull_request_target' + uses: actions/checkout@v3 + + - name: Setup Tarantool ${{ matrix.sdk-version }} + run: | + ARCHIVE_NAME=tarantool-enterprise-${{ matrix.sdk-version }}.tar.gz + curl -O -L https://${{ secrets.SDK_DOWNLOAD_TOKEN }}@download.tarantool.io/enterprise/${{ matrix.sdk-path }}${ARCHIVE_NAME} + tar -xzf ${ARCHIVE_NAME} + rm -f ${ARCHIVE_NAME} + + - name: Setup golang for the connector and tests + uses: actions/setup-go@v3 + with: + go-version: 1.20 + + - name: Run regression tests + run: | + source tarantool-enterprise/env.sh + go test -v -shuffle=on -coverprofile=module-coverage.txt -coverpkg=./... ./... + env: + TEST_TNT_SSL: ${{matrix.ssl}} + + - name: Collect coverage files + shell: bash + run: echo "COVERAGES=$(find . -type f -name 'module-coverage.txt' | tr -s '\n' ',' | sed 's/,$//')" >> $GITHUB_ENV + - name: Upload coverage to Codecov + uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0 + with: + files: '${{ env.COVERAGES }}' + env_vars: OS=${{ matrix.os }}, GO=${{ matrix.go }} + + testing_mac_os: + # 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. + # + # The main trick is described here: + # https://github.com/Dart-Code/Dart-Code/pull/2375 + if: (github.event_name == 'push') || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.repository) || + (github.event_name == 'workflow_dispatch') + + strategy: + fail-fast: false + matrix: + golang: + - 1.20 + runs-on: + - macos-11 + - macos-12 + tarantool: + - brew + - 1.10.15 + + env: + # Make sense only for non-brew jobs. + # + # Set as absolute paths to avoid any possible confusion + # after changing a current directory. + T_VERSION: ${{ matrix.tarantool }} + T_SRCDIR: ${{ format('{0}/tarantool-{1}', github.workspace, matrix.tarantool) }} + T_TARDIR: ${{ format('{0}/tarantool-{1}-build', github.workspace, matrix.tarantool) }} + SRCDIR: ${{ format('{0}/{1}', github.workspace, github.repository) }} + + runs-on: ${{ matrix.runs-on }} + steps: + - name: Clone the connector + uses: actions/checkout@v3 + with: + path: ${{ env.SRCDIR }} + + - name: Restore cache of tarantool ${{ env.T_VERSION }} + uses: actions/cache@v3 + id: cache + with: + path: ${{ env.T_TARDIR }} + key: ${{ matrix.runs-on }}-${{ matrix.tarantool }} + if: matrix.tarantool != 'brew' + + - name: Install latest tarantool from brew + run: brew install tarantool + if: matrix.tarantool == 'brew' + + - name: Clone tarantool ${{ env.T_VERSION }} + uses: actions/checkout@v3 + with: + repository: tarantool/tarantool + ref: ${{ env.T_VERSION }} + path: ${{ env.T_TARDIR }} + submodules: true + # fetch-depth is 1 by default and it is okay for + # building from a tag. However we have master in + # the version list. + fetch-depth: 0 + if: matrix.tarantool != 'brew' && steps.cache.outputs.cache-hit != 'true' + + - name: Build tarantool ${{ env.T_VERSION }} from sources + run: | + cd "${T_TARDIR}" + # Set RelWithDebInfo just to disable -Werror. + # + # There are tarantool releases on which AppleClang + # complains about the problem that was fixed later in + # https://github.com/tarantool/tarantool/commit/7e8688ff8885cc7813d12225e03694eb8886de29 + # + # Set OpenSSL root directory for linking tarantool with OpenSSL of version 1.1 + # This is related to #49. There are too much deprecations which affect the build and tests. + # Must be revisited after fixing https://github.com/tarantool/tarantool/issues/6477 + cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_DIST=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 -DOPENSSL_LIBRARIES=/usr/local/opt/openssl@1.1/lib + # {{{ Workaround Mac OS build failure (gh-6076) + # + # https://github.com/tarantool/tarantool/issues/6076 + # + # In brief: when "src/lib/small" is in include paths, + # `#include ` from inside Mac OS SDK headers + # attempts to include "src/lib/small/VERSION" as a + # header file that leads to a syntax error. + # + # It was fixed in the following commits: + # + # * 1.10.10-24-g7bce4abd1 + # * 2.7.2-44-gbb1d32903 + # * 2.8.1-56-ga6c29c5af + # * 2.9.0-84-gc5ae543f3 + # + # However applying the workaround for all versions looks + # harmless. + # + # Added -f just in case: I guess we'll drop this useless + # obsoleted VERSION file from the git repository sooner + # or later. + rm -f src/lib/small/VERSION + # The same as above, but for the VERSION file generated + # by tarantool's CMake script. + rm VERSION + # }}} Workaround Mac OS build failure (gh-6076) + # Continue the build. + make -j$(sysctl -n hw.logicalcpu) + make install + if: matrix.tarantool != 'brew' && steps.cache.outputs.cache-hit != 'true' + + - name: Install tarantool + run: | + cd "${T_TARDIR}" + make install + if: matrix.tarantool != 'brew' && steps.cache.outputs.cache-hit == 'true' + + - name: Verify tarantool version + run: | + # Workaround https://github.com/tarantool/tarantool/issues/4983 + # Workaround https://github.com/tarantool/tarantool/issues/5040 + tarantool -e "require('fiber').sleep(0) assert(_TARANTOOL:startswith('${T_VERSION}'), _TARANTOOL) os.exit()" + if: matrix.tarantool != 'brew' && matrix.tarantool != 'master' + + - name: Setup golang for the connector and tests + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.golang }} + + # Workaround for Mac OS 12 testrace failure + # https://github.com/golang/go/issues/49138 + - name: disable MallocNanoZone for macos-12 + run: echo "MallocNanoZone=0" >> $GITHUB_ENV + if: matrix.runs-on == 'macos-12' + + # Workaround issue https://github.com/tarantool/tt/issues/640 + - name: Fix tt rocks + if: matrix.tarantool == 'brew' + run: | + brew ls --verbose tarantool | grep macosx.lua | xargs rm -f + + - name: Run regression tests + run: | + cd "${SRCDIR}" + go test -v -coverprofile=module-coverage.txt -coverpkg=./... ./... diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..32f87cb --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,30 @@ +run: + timeout: 3m + +linters: + disable: + - errcheck + enable: + - forbidigo + - gocritic + - goimports + - lll + - reassign + - stylecheck + - unconvert + +linters-settings: + gocritic: + disabled-checks: + - ifElseChain + lll: + line-length: 100 + tab-width: 4 + stylecheck: + checks: ["all", "-ST1003"] + +issues: + exclude-rules: + - linters: + - lll + source: "\t?// *(see )?https://" diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..4e89983 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,28 @@ +redefined = false + +globals = { + 'box', + 'utf8', + 'checkers', + '_TARANTOOL' +} + +include_files = { + '**/*.lua', + '*.luacheckrc', + '*.rockspec' +} + +exclude_files = { + '**/*.rocks/' +} + +max_line_length = 120 + +ignore = { + "212/self", -- Unused argument . + "411", -- Redefining a local variable. + "421", -- Shadowing a local variable. + "431", -- Shadowing an upvalue. + "432", -- Shadowing an upvalue argument. +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/connection.go b/connection.go new file mode 100644 index 0000000..5e19b0a --- /dev/null +++ b/connection.go @@ -0,0 +1,62 @@ +package tlsdialer + +import ( + "errors" + "io" + "net" + + "github.com/tarantool/go-tarantool/v2" +) + +type tntConn struct { + net net.Conn + reader io.Reader + writer writeFlusher +} + +// writeFlusher is the interface that groups the basic Write and Flush methods. +type writeFlusher interface { + io.Writer + Flush() error +} + +// Addr makes tntConn satisfy the Conn interface. +func (c *tntConn) Addr() net.Addr { + return c.net.RemoteAddr() +} + +// Read makes tntConn satisfy the Conn interface. +func (c *tntConn) Read(p []byte) (int, error) { + return c.reader.Read(p) +} + +// Write makes tntConn satisfy the Conn interface. +func (c *tntConn) Write(p []byte) (int, error) { + if l, err := c.writer.Write(p); err != nil { + return l, err + } else if l != len(p) { + return l, errors.New("wrong length written") + } else { + return l, nil + } +} + +// Flush makes tntConn satisfy the Conn interface. +func (c *tntConn) Flush() error { + return c.writer.Flush() +} + +// Close makes tntConn satisfy the Conn interface. +func (c *tntConn) Close() error { + return c.net.Close() +} + +// Greeting makes tntConn satisfy the Conn interface. +func (c *tntConn) Greeting() tarantool.Greeting { + return tarantool.Greeting{} +} + +// ProtocolInfo makes tntConn satisfy the Conn interface. +func (c *tntConn) ProtocolInfo() tarantool.ProtocolInfo { + return tarantool.ProtocolInfo{} +} diff --git a/deadline_io.go b/deadline_io.go new file mode 100644 index 0000000..fed4364 --- /dev/null +++ b/deadline_io.go @@ -0,0 +1,27 @@ +package tlsdialer + +import ( + "net" + "time" +) + +type deadlineIO struct { + to time.Duration + c net.Conn +} + +func (d *deadlineIO) Write(b []byte) (n int, err error) { + if d.to > 0 { + d.c.SetWriteDeadline(time.Now().Add(d.to)) + } + n, err = d.c.Write(b) + return +} + +func (d *deadlineIO) Read(b []byte) (n int, err error) { + if d.to > 0 { + d.c.SetReadDeadline(time.Now().Add(d.to)) + } + n, err = d.c.Read(b) + return +} diff --git a/dialer.go b/dialer.go new file mode 100644 index 0000000..6f15467 --- /dev/null +++ b/dialer.go @@ -0,0 +1,149 @@ +package tlsdialer + +import ( + "bufio" + "context" + "fmt" + + "github.com/tarantool/go-tarantool/v2" +) + +const bufSize = 128 * 1024 + +type openSslDialer struct { + address string + sslKeyFile string + sslCertFile string + sslCaFile string + sslCiphers string + sslPassword string + sslPasswordFile string +} + +func (d openSslDialer) Dial(ctx context.Context, + opts tarantool.DialOpts) (tarantool.Conn, error) { + var err error + conn := new(tntConn) + + network, address := parseAddress(d.address) + conn.net, err = sslDialContext(ctx, network, address, sslOpts{ + KeyFile: d.sslKeyFile, + CertFile: d.sslCertFile, + CaFile: d.sslCaFile, + Ciphers: d.sslCiphers, + Password: d.sslPassword, + PasswordFile: d.sslPasswordFile, + }) + if err != nil { + return nil, fmt.Errorf("failed to dial: %w", err) + } + + dc := &deadlineIO{to: opts.IoTimeout, c: conn.net} + conn.reader = bufio.NewReaderSize(dc, bufSize) + conn.writer = bufio.NewWriterSize(dc, bufSize) + + return conn, nil +} + +// OpenSslDialer allows to use SSL transport for connection. +type OpenSslDialer struct { + // Address is an address to connect. + // It could be specified in following ways: + // + // - TCP connections (tcp://192.168.1.1:3013, tcp://my.host:3013, + // tcp:192.168.1.1:3013, tcp:my.host:3013, 192.168.1.1:3013, my.host:3013) + // + // - Unix socket, first '/' or '.' indicates Unix socket + // (unix:///abs/path/tnt.sock, unix:path/tnt.sock, /abs/path/tnt.sock, + // ./rel/path/tnt.sock, unix/:path/tnt.sock) + Address string + // Auth is an authentication method. + Auth tarantool.Auth + // Username for logging in to Tarantool. + User string + // User password for logging in to Tarantool. + Password string + // RequiredProtocol contains minimal protocol version and + // list of protocol features that should be supported by + // Tarantool server. By default, there are no restrictions. + RequiredProtocolInfo tarantool.ProtocolInfo + // SslKeyFile is a path to a private SSL key file. + SslKeyFile string + // SslCertFile is a path to an SSL certificate file. + SslCertFile string + // SslCaFile is a path to a trusted certificate authorities (CA) file. + SslCaFile string + // SslCiphers 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 + SslCiphers string + // SslPassword is a password for decrypting the private SSL key file. + // The priority is as follows: try to decrypt with SslPassword, then + // try SslPasswordFile. + SslPassword string + // SslPasswordFile is a path to the list of passwords for decrypting + // the private SSL key file. The connection tries every line from the + // file as a password. + SslPasswordFile string +} + +// Dial makes OpenSslDialer satisfy the Dialer interface. +func (d OpenSslDialer) Dial(ctx context.Context, + opts tarantool.DialOpts) (tarantool.Conn, error) { + if d.Auth != tarantool.AutoAuth { + d.RequiredProtocolInfo.Auth = d.Auth + } + + dialer := tarantool.AuthDialer{ + Dialer: tarantool.ProtocolDialer{ + Dialer: openSslDialer{ + address: d.Address, + sslKeyFile: d.SslKeyFile, + sslCertFile: d.SslCertFile, + sslCaFile: d.SslCaFile, + sslCiphers: d.SslCiphers, + sslPassword: d.SslPassword, + sslPasswordFile: d.SslPasswordFile, + }, + RequiredProtocolInfo: d.RequiredProtocolInfo, + }, + Auth: d.Auth, + Username: d.User, + Password: d.Password, + } + + return dialer.Dial(ctx, opts) +} + +// parseAddress split address into network and address parts. +func parseAddress(address string) (string, string) { + network := "tcp" + addrLen := len(address) + + if addrLen > 0 && (address[0] == '.' || address[0] == '/') { + network = "unix" + } else if addrLen >= 7 && address[0:7] == "unix://" { + network = "unix" + address = address[7:] + } else if addrLen >= 5 && address[0:5] == "unix:" { + network = "unix" + address = address[5:] + } else if addrLen >= 6 && address[0:6] == "unix/:" { + network = "unix" + address = address[6:] + } else if addrLen >= 6 && address[0:6] == "tcp://" { + address = address[6:] + } else if addrLen >= 4 && address[0:4] == "tcp:" { + address = address[4:] + } + + return network, address +} diff --git a/export_test.go b/export_test.go new file mode 100644 index 0000000..d38c24b --- /dev/null +++ b/export_test.go @@ -0,0 +1,14 @@ +package tlsdialer + +type SslTestOpts struct { + KeyFile string + CertFile string + CaFile string + Ciphers string + Password string + PasswordFile string +} + +func SslCreateContext(opts SslTestOpts) (ctx interface{}, err error) { + return sslCreateContext(sslOpts(opts)) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3b848b0 --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module github.com/tarantool/go-tlsdialer + +require ( + github.com/stretchr/testify v1.7.1 + github.com/tarantool/go-iproto v1.0.0 + github.com/tarantool/go-openssl v0.0.8-0.20231004103608-336ca939d2ca + github.com/tarantool/go-tarantool/v2 v2.0.0-20240131003940-1a49b281a97e +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/mattn/go-pointer v0.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +go 1.20 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b95a324 --- /dev/null +++ b/go.sum @@ -0,0 +1,39 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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-iproto v1.0.0 h1:quC4hdFhCuFYaCqOFgUxH2foRkhAy+TlEy7gQLhdVjw= +github.com/tarantool/go-iproto v1.0.0/go.mod h1:LNCtdyZxojUed8SbOiYHoc3v9NvaZTB7p96hUySMlIo= +github.com/tarantool/go-openssl v0.0.8-0.20231004103608-336ca939d2ca h1:oOrBh73tDDyooIXajfr+0pfnM+89404ClAhJpTTHI7E= +github.com/tarantool/go-openssl v0.0.8-0.20231004103608-336ca939d2ca/go.mod h1:M7H4xYSbzqpW/ZRBMyH0eyqQBsnhAMfsYk5mv0yid7A= +github.com/tarantool/go-tarantool/v2 v2.0.0-20240131003940-1a49b281a97e h1:M8wYDUTrchX2/7cVf2xtzjxW5QesxCe5OmCvGyZ4ybE= +github.com/tarantool/go-tarantool/v2 v2.0.0-20240131003940-1a49b281a97e/go.mod h1:fGJBRxbkZmNQap9VxQ7xQHlDhRiHFnqP3gK4ghtlro0= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ssl_dial.go b/ssl_dial.go new file mode 100644 index 0000000..d2e8f3e --- /dev/null +++ b/ssl_dial.go @@ -0,0 +1,140 @@ +package tlsdialer + +import ( + "bufio" + "context" + "errors" + "net" + "os" + "strings" + + "github.com/tarantool/go-openssl" +) + +func sslDialContext(ctx context.Context, network, address string, + opts sslOpts) (connection net.Conn, err error) { + var sslCtx interface{} + if sslCtx, err = sslCreateContext(opts); err != nil { + return + } + + return openssl.DialContext(ctx, network, address, sslCtx.(*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, opts.Password, opts.PasswordFile); 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 = os.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, password string, + passwordFile string) error { + var keyBytes []byte + var err, firstDecryptErr error + + if keyBytes, err = os.ReadFile(keyFile); err != nil { + return err + } + + // If the key is encrypted and password is not provided, + // openssl.LoadPrivateKeyFromPEM(keyBytes) asks to enter PEM pass phrase + // interactively. On the other hand, + // openssl.LoadPrivateKeyFromPEMWithPassword(keyBytes, password) works fine + // for non-encrypted key with any password, including empty string. If + // the key is encrypted, we fast fail with password error instead of + // requesting the pass phrase interactively. + passwords := []string{password} + if passwordFile != "" { + file, err := os.Open(passwordFile) + if err == nil { + defer file.Close() + + scanner := bufio.NewScanner(file) + // Tarantool itself tries each password file line. + for scanner.Scan() { + password = strings.TrimSpace(scanner.Text()) + passwords = append(passwords, password) + } + } else { + firstDecryptErr = err + } + } + + for _, password := range passwords { + key, err := openssl.LoadPrivateKeyFromPEMWithPassword(keyBytes, password) + if err == nil { + return ctx.UsePrivateKey(key) + } else if firstDecryptErr == nil { + firstDecryptErr = err + } + } + + return firstDecryptErr +} diff --git a/ssl_opts.go b/ssl_opts.go new file mode 100644 index 0000000..ee715cf --- /dev/null +++ b/ssl_opts.go @@ -0,0 +1,30 @@ +package tlsdialer + +type sslOpts struct { + // KeyFile is a path to a private SSL key file. + KeyFile string + // CertFile is a path to an SSL certificate 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 + // Password is a password for decrypting the private SSL key file. + // The priority is as follows: try to decrypt with Password, then + // try PasswordFile. + Password string + // PasswordFile is a path to the list of passwords for decrypting + // the private SSL key file. The connection tries every line from the + // file as a password. + PasswordFile string +} diff --git a/ssl_test.go b/ssl_test.go new file mode 100644 index 0000000..3db8af2 --- /dev/null +++ b/ssl_test.go @@ -0,0 +1,1001 @@ +package tlsdialer_test + +import ( + "context" + "fmt" + "net" + "os" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/tarantool/go-iproto" + "github.com/tarantool/go-openssl" + . "github.com/tarantool/go-tlsdialer" + + "github.com/tarantool/go-tarantool/v2" + "github.com/tarantool/go-tarantool/v2/test_helpers" +) + +const tntHost = "127.0.0.1:3014" + +func genSalt() [64]byte { + salt := [64]byte{} + for i := 0; i < 44; i++ { + salt[i] = 'a' + } + return salt +} + +var ( + opts = tarantool.Opts{ + Timeout: 5 * time.Second, + //Concurrency: 32, + //RateLimit: 4*1024, + } + + testDialUser = "test" + testDialPass = "test" + testDialVersion = [64]byte{'t', 'e', 's', 't'} + + // Salt with end zeros. + testDialSalt = genSalt() + + idRequestExpected = []byte{ + 0xce, 0x00, 0x00, 0x00, 29, // Length. + 0x82, // Header map. + 0x00, 0x49, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + + 0x82, // Data map. + 0x54, + 0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, // Version. + 0x55, + 0x97, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // Features. + } + + idResponseTyped = tarantool.ProtocolInfo{ + Version: 6, + Features: []iproto.Feature{iproto.Feature(1), iproto.Feature(21)}, + Auth: tarantool.ChapSha1Auth, + } + + idResponse = []byte{ + 0xce, 0x00, 0x00, 0x00, 37, // Length. + 0x83, // Header map. + 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x05, 0xce, 0x00, 0x00, 0x00, 0x61, + + 0x83, // Data map. + 0x54, + 0x06, // Version. + 0x55, + 0x92, 0x01, 0x15, // Features. + 0x5b, + 0xa9, 'c', 'h', 'a', 'p', '-', 's', 'h', 'a', '1', + } + + idResponseNotSupported = []byte{ + 0xce, 0x00, 0x00, 0x00, 25, // Length. + 0x83, // Header map. + 0x00, 0xce, 0x00, 0x00, 0x80, 0x30, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x05, 0xce, 0x00, 0x00, 0x00, 0x61, + 0x81, + 0x31, + 0xa3, 'e', 'r', 'r', + } + + authRequestExpectedChapSha1 = []byte{ + 0xce, 0x00, 0x00, 0x00, 57, // Length. + 0x82, // Header map. + 0x00, 0x07, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + + 0x82, // Data map. + 0xce, 0x00, 0x00, 0x00, 0x23, + 0xa4, 't', 'e', 's', 't', // Login. + 0xce, 0x00, 0x00, 0x00, 0x21, + 0x92, // Tuple. + 0xa9, 'c', 'h', 'a', 'p', '-', 's', 'h', 'a', '1', + + // Scramble. + 0xb4, 0x1b, 0xd4, 0x20, 0x45, 0x73, 0x22, + 0xcf, 0xab, 0x05, 0x03, 0xf3, 0x89, 0x4b, + 0xfe, 0xc7, 0x24, 0x5a, 0xe6, 0xe8, 0x31, + } + + authRequestExpectedPapSha256 = []byte{ + 0xce, 0x00, 0x00, 0x00, 0x2a, // Length. + 0x82, // Header map. + 0x00, 0x07, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + + 0x82, // Data map. + 0xce, 0x00, 0x00, 0x00, 0x23, + 0xa4, 't', 'e', 's', 't', // Login. + 0xce, 0x00, 0x00, 0x00, 0x21, + 0x92, // Tuple. + 0xaa, 'p', 'a', 'p', '-', 's', 'h', 'a', '2', '5', '6', + 0xa4, 't', 'e', 's', 't', + } + + okResponse = []byte{ + 0xce, 0x00, 0x00, 0x00, 19, // Length. + 0x83, // Header map. + 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xce, 0x00, 0x00, 0x00, 0x00, + 0x05, 0xce, 0x00, 0x00, 0x00, 0x61, + } + + errResponse = []byte{0xce} +) + +type testDialOpts struct { + name string + wantErr bool + expectedErr string + expectedProtocolInfo tarantool.ProtocolInfo + + // These options configure the behavior of the server. + isErrGreeting bool + isErrId bool + isIdUnsupported bool + isPapSha256Auth bool + isErrAuth bool + isEmptyAuth bool +} + +type dialServerActual struct { + IdRequest []byte + AuthRequest []byte +} + +func testDialAccept(opts testDialOpts, l net.Listener) chan dialServerActual { + ch := make(chan dialServerActual, 1) + + go func() { + client, err := l.Accept() + if err != nil { + return + } + defer client.Close() + if opts.isErrGreeting { + client.Write(errResponse) + return + } else { + // Write greeting. + client.Write(testDialVersion[:]) + client.Write(testDialSalt[:]) + } + + // Read Id request. + idRequestActual := make([]byte, len(idRequestExpected)) + client.Read(idRequestActual) + + // Make Id response. + if opts.isErrId { + client.Write(errResponse) + } else if opts.isIdUnsupported { + client.Write(idResponseNotSupported) + } else { + client.Write(idResponse) + } + + // Read Auth request. + authRequestExpected := authRequestExpectedChapSha1 + if opts.isPapSha256Auth { + authRequestExpected = authRequestExpectedPapSha256 + } else if opts.isEmptyAuth { + authRequestExpected = []byte{} + } + authRequestActual := make([]byte, len(authRequestExpected)) + client.Read(authRequestActual) + + // Make Auth response. + if opts.isErrAuth { + client.Write(errResponse) + } else { + client.Write(okResponse) + } + ch <- dialServerActual{ + IdRequest: idRequestActual, + AuthRequest: authRequestActual, + } + }() + + return ch +} + +func testDialer(t *testing.T, l net.Listener, dialer tarantool.Dialer, + opts testDialOpts) { + ctx, cancel := test_helpers.GetConnectContext() + defer cancel() + ch := testDialAccept(opts, l) + conn, err := dialer.Dial(ctx, tarantool.DialOpts{ + IoTimeout: time.Second * 2, + }) + if opts.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), opts.expectedErr) + return + } + require.NoError(t, err) + require.Equal(t, opts.expectedProtocolInfo, conn.ProtocolInfo()) + require.Equal(t, testDialVersion[:], []byte(conn.Greeting().Version)) + require.Equal(t, testDialSalt[:44], []byte(conn.Greeting().Salt)) + + actual := <-ch + require.Equal(t, idRequestExpected, actual.IdRequest) + + authRequestExpected := authRequestExpectedChapSha1 + if opts.isPapSha256Auth { + authRequestExpected = authRequestExpectedPapSha256 + } else if opts.isEmptyAuth { + authRequestExpected = []byte{} + } + require.Equal(t, authRequestExpected, actual.AuthRequest) + conn.Close() +} + +func serverTnt(serverOpts SslTestOpts, + auth tarantool.Auth) (test_helpers.TarantoolInstance, error) { + listen := tntHost + "?transport=ssl&" + + key := serverOpts.KeyFile + if key != "" { + listen += fmt.Sprintf("ssl_key_file=%s&", key) + } + + cert := serverOpts.CertFile + if cert != "" { + listen += fmt.Sprintf("ssl_cert_file=%s&", cert) + } + + ca := serverOpts.CaFile + if ca != "" { + listen += fmt.Sprintf("ssl_ca_file=%s&", ca) + } + + ciphers := serverOpts.Ciphers + if ciphers != "" { + listen += fmt.Sprintf("ssl_ciphers=%s&", ciphers) + } + + password := serverOpts.Password + if password != "" { + listen += fmt.Sprintf("ssl_password=%s&", password) + } + + passwordFile := serverOpts.PasswordFile + if passwordFile != "" { + listen += fmt.Sprintf("ssl_password_file=%s&", passwordFile) + } + + listen = listen[:len(listen)-1] + + return test_helpers.StartTarantool( + test_helpers.StartOpts{ + Dialer: OpenSslDialer{ + Address: tntHost, + Auth: auth, + User: "test", + Password: "test", + SslKeyFile: serverOpts.KeyFile, + SslCertFile: serverOpts.CertFile, + SslCaFile: serverOpts.CaFile, + SslCiphers: serverOpts.Ciphers, + SslPassword: serverOpts.Password, + SslPasswordFile: serverOpts.PasswordFile, + }, + Auth: auth, + InitScript: "config.lua", + Listen: listen, + SslCertsDir: "testdata", + WaitStart: 100 * time.Millisecond, + ConnectRetry: 10, + RetryTimeout: 500 * time.Millisecond, + }, + ) +} + +func serverTntStop(inst test_helpers.TarantoolInstance) { + test_helpers.StopTarantoolWithCleanup(inst) +} + +func checkTntConn(dialer tarantool.Dialer) error { + ctx, cancel := test_helpers.GetConnectContext() + defer cancel() + conn, err := tarantool.Connect(ctx, dialer, tarantool.Opts{ + Timeout: 500 * time.Millisecond, + SkipSchema: true, + }) + if err != nil { + return err + } + conn.Close() + return nil +} + +func assertConnectionTntFail(t testing.TB, serverOpts SslTestOpts, + dialer OpenSslDialer) { + t.Helper() + + inst, err := serverTnt(serverOpts, tarantool.AutoAuth) + defer serverTntStop(inst) + if err != nil { + t.Fatalf("An unexpected server error %q", err.Error()) + } + + err = checkTntConn(dialer) + if err == nil { + t.Errorf("An unexpected connection to the server") + } +} + +func assertConnectionTntOk(t testing.TB, serverOpts SslTestOpts, + dialer OpenSslDialer) { + t.Helper() + + inst, err := serverTnt(serverOpts, tarantool.AutoAuth) + defer serverTntStop(inst) + if err != nil { + t.Fatalf("An unexpected server error %q", err.Error()) + } + + err = checkTntConn(dialer) + if err != nil { + t.Errorf("An unexpected connection error %q", err.Error()) + } +} + +type sslTest struct { + name string + ok bool + serverOpts SslTestOpts + clientOpts SslTestOpts +} + +/* +Requirements from Tarantool Enterprise Edition manual: +https://www.tarantool.io/ru/enterprise_doc/security/#configuration + +For a server: +KeyFile - mandatory +CertFile - mandatory +CaFile - optional +Ciphers - optional + +For a client: +KeyFile - optional, mandatory if server.CaFile set +CertFile - optional, mandatory if server.CaFile set +CaFile - optional, +Ciphers - optional +*/ +var sslTests = []sslTest{ + { + "key_crt_server", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }, + SslTestOpts{}, + }, + { + "key_crt_server_and_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }, + }, + { + "key_crt_ca_server", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{}, + }, + { + "key_crt_ca_server_key_crt_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }, + }, + { + "key_crt_ca_server_and_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + }, + { + "key_crt_ca_server_and_client_invalid_path_key", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "any_invalid_path", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + }, + { + "key_crt_ca_server_and_client_invalid_path_crt", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "any_invalid_path", + CaFile: "testdata/ca.crt", + }, + }, + { + "key_crt_ca_server_and_client_invalid_path_ca", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "any_invalid_path", + }, + }, + { + "key_crt_ca_server_and_client_empty_key", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/empty", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + }, + { + "key_crt_ca_server_and_client_empty_crt", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/empty", + CaFile: "testdata/ca.crt", + }, + }, + { + "key_crt_ca_server_and_client_empty_ca", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/empty", + }, + }, + { + "key_crt_server_and_key_crt_ca_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + }, + { + "key_crt_ca_ciphers_server_key_crt_ca_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + }, + { + "key_crt_ca_ciphers_server_and_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", + }, + }, + { + "non_equal_ciphers_client", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + Ciphers: "TLS_AES_128_GCM_SHA256", + }, + }, + { + "pass_key_encrypt_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.enc.key", + CertFile: "testdata/localhost.crt", + Password: "mysslpassword", + }, + }, + { + "passfile_key_encrypt_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.enc.key", + CertFile: "testdata/localhost.crt", + PasswordFile: "testdata/passwords", + }, + }, + { + "pass_and_passfile_key_encrypt_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.enc.key", + CertFile: "testdata/localhost.crt", + Password: "mysslpassword", + PasswordFile: "testdata/passwords", + }, + }, + { + "inv_pass_and_passfile_key_encrypt_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.enc.key", + CertFile: "testdata/localhost.crt", + Password: "invalidpassword", + PasswordFile: "testdata/passwords", + }, + }, + { + "pass_and_inv_passfile_key_encrypt_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.enc.key", + CertFile: "testdata/localhost.crt", + Password: "mysslpassword", + PasswordFile: "testdata/invalidpasswords", + }, + }, + { + "pass_and_not_existing_passfile_key_encrypt_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.enc.key", + CertFile: "testdata/localhost.crt", + Password: "mysslpassword", + PasswordFile: "testdata/notafile", + }, + }, + { + "inv_pass_and_inv_passfile_key_encrypt_client", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.enc.key", + CertFile: "testdata/localhost.crt", + Password: "invalidpassword", + PasswordFile: "testdata/invalidpasswords", + }, + }, + { + "not_existing_passfile_key_encrypt_client", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.enc.key", + CertFile: "testdata/localhost.crt", + PasswordFile: "testdata/notafile", + }, + }, + { + "no_pass_key_encrypt_client", + false, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.enc.key", + CertFile: "testdata/localhost.crt", + }, + }, + { + "pass_key_non_encrypt_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + Password: "invalidpassword", + }, + }, + { + "passfile_key_non_encrypt_client", + true, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + }, + SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + PasswordFile: "testdata/invalidpasswords", + }, + }, +} + +func isTestTntSsl() bool { + testTntSsl, exists := os.LookupEnv("TEST_TNT_SSL") + return exists && + (testTntSsl == "1" || strings.ToUpper(testTntSsl) == "TRUE") +} + +func makeOpenSslDialer(opts SslTestOpts) OpenSslDialer { + return OpenSslDialer{ + Address: tntHost, + User: "test", + Password: "test", + SslKeyFile: opts.KeyFile, + SslCertFile: opts.CertFile, + SslCaFile: opts.CaFile, + SslCiphers: opts.Ciphers, + SslPassword: opts.Password, + SslPasswordFile: opts.PasswordFile, + } +} + +func TestSslOpts(t *testing.T) { + isTntSsl := isTestTntSsl() + + for _, test := range sslTests { + if !isTntSsl { + continue + } + dialer := makeOpenSslDialer(test.clientOpts) + if test.ok { + t.Run("ok_tnt_"+test.name, func(t *testing.T) { + assertConnectionTntOk(t, test.serverOpts, dialer) + }) + } else { + t.Run("fail_tnt_"+test.name, func(t *testing.T) { + assertConnectionTntFail(t, test.serverOpts, dialer) + }) + } + } +} + +func TestOpts_PapSha256Auth(t *testing.T) { + isTntSsl := isTestTntSsl() + if !isTntSsl { + t.Skip("TEST_TNT_SSL is not set") + } + + isLess, err := test_helpers.IsTarantoolVersionLess(2, 11, 0) + if err != nil { + t.Fatalf("Could not check Tarantool version: %s", err) + } + if isLess { + t.Skip("Skipping test for Tarantool without pap-sha256 support") + } + + sslOpts := SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + } + + inst, err := serverTnt(sslOpts, tarantool.PapSha256Auth) + defer serverTntStop(inst) + if err != nil { + t.Fatalf("An unexpected server error %q", err.Error()) + } + + client := OpenSslDialer{ + Address: tntHost, + Auth: tarantool.PapSha256Auth, + User: "test", + Password: "test", + RequiredProtocolInfo: tarantool.ProtocolInfo{}, + SslKeyFile: sslOpts.KeyFile, + SslCertFile: sslOpts.CertFile, + } + + conn := test_helpers.ConnectWithValidation(t, client, opts) + conn.Close() + + client.Auth = tarantool.AutoAuth + conn = test_helpers.ConnectWithValidation(t, client, opts) + conn.Close() +} + +func createSslListener(t *testing.T, opts SslTestOpts) net.Listener { + ctx, err := SslCreateContext(opts) + require.NoError(t, err) + l, err := openssl.Listen("tcp", "127.0.0.1:0", ctx.(*openssl.Ctx)) + require.NoError(t, err) + return l +} + +func TestOpenSslDialer_Dial_opts(t *testing.T) { + for _, test := range sslTests { + t.Run(test.name, func(t *testing.T) { + l := createSslListener(t, test.serverOpts) + defer l.Close() + addr := l.Addr().String() + + dialer := OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + SslKeyFile: test.clientOpts.KeyFile, + SslCertFile: test.clientOpts.CertFile, + SslCaFile: test.clientOpts.CaFile, + SslCiphers: test.clientOpts.Ciphers, + SslPassword: test.clientOpts.Password, + SslPasswordFile: test.clientOpts.PasswordFile, + } + testDialer(t, l, dialer, testDialOpts{ + wantErr: !test.ok, + expectedProtocolInfo: idResponseTyped.Clone(), + }) + }) + } +} + +func TestOpenSslDialer_Dial_basic(t *testing.T) { + l := createSslListener(t, SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }) + + defer l.Close() + addr := l.Addr().String() + + dialer := OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + } + + cases := []testDialOpts{ + { + name: "all is ok", + expectedProtocolInfo: idResponseTyped.Clone(), + }, + { + name: "id request unsupported", + expectedProtocolInfo: tarantool.ProtocolInfo{}, + isIdUnsupported: true, + }, + { + name: "greeting response error", + wantErr: true, + expectedErr: "failed to read greeting", + isErrGreeting: true, + }, + { + name: "id response error", + wantErr: true, + expectedErr: "failed to identify", + isErrId: true, + }, + { + name: "auth response error", + wantErr: true, + expectedErr: "failed to authenticate", + isErrAuth: true, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + testDialer(t, l, dialer, tc) + }) + } +} + +func TestOpenSslDialer_Dial_requirements(t *testing.T) { + l := createSslListener(t, SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }) + + defer l.Close() + addr := l.Addr().String() + + dialer := OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + RequiredProtocolInfo: tarantool.ProtocolInfo{ + Features: []iproto.Feature{42}, + }, + } + + testDialAccept(testDialOpts{}, l) + ctx, cancel := test_helpers.GetConnectContext() + defer cancel() + conn, err := dialer.Dial(ctx, tarantool.DialOpts{}) + if err == nil { + conn.Close() + } + require.Error(t, err) + require.Contains(t, err.Error(), "invalid server protocol") +} + +func TestOpenSslDialer_Dial_papSha256Auth(t *testing.T) { + l := createSslListener(t, SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + }) + + defer l.Close() + addr := l.Addr().String() + + dialer := OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + Auth: tarantool.PapSha256Auth, + } + + // Response from the server. + protocol := idResponseTyped.Clone() + protocol.Auth = tarantool.ChapSha1Auth + + testDialer(t, l, dialer, testDialOpts{ + expectedProtocolInfo: protocol, + isPapSha256Auth: true, + }) +} + +func TestOpenSslDialer_Dial_ctx_cancel(t *testing.T) { + serverOpts := SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", + } + clientOpts := SslTestOpts{ + KeyFile: "testdata/localhost.key", + CertFile: "testdata/localhost.crt", + CaFile: "testdata/ca.crt", + Ciphers: "ECDHE-RSA-AES256-GCM-SHA384", + } + + l := createSslListener(t, serverOpts) + defer l.Close() + addr := l.Addr().String() + testDialAccept(testDialOpts{}, l) + + dialer := OpenSslDialer{ + Address: addr, + User: testDialUser, + Password: testDialPass, + SslKeyFile: clientOpts.KeyFile, + SslCertFile: clientOpts.CertFile, + SslCaFile: clientOpts.CaFile, + SslCiphers: clientOpts.Ciphers, + SslPassword: clientOpts.Password, + SslPasswordFile: clientOpts.PasswordFile, + } + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + conn, err := dialer.Dial(ctx, tarantool.DialOpts{}) + if err == nil { + conn.Close() + } + require.Error(t, err) +} diff --git a/testdata/ca.crt b/testdata/ca.crt new file mode 100644 index 0000000..2fa1a12 --- /dev/null +++ b/testdata/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLzCCAhegAwIBAgIUMMZTmNkhr4qOfSwInVk2dAJvoBEwDQYJKoZIhvcNAQEL +BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y +MjA1MjYwNjE3NDBaFw00NDEwMjkwNjE3NDBaMCcxCzAJBgNVBAYTAlVTMRgwFgYD +VQQDDA9FeGFtcGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCRq/eaA3I6CB8t770H2XDdzcp1yuC/+TZOxV5o0LuRkogTvL2kYULBrfx1 +rVZu8zQJTx1fmSRj1cN8j+IrmXN5goZ3mYFTnnIOgkyi+hJysVlo5s0Kp0qtLLGM +OuaVbxw2oAy75if5X3pFpiDaMvFBtJKsh8+SkncBIC5bbKC5AoLdFANLmPiH0CGr +Mv3rL3ycnbciI6J4uKHcWnYGGiMjBomaZ7jd/cOjcjmGfpI5d0nq13G11omkyEyR +wNX0eJRL02W+93Xu7tD+FEFMxFvak+70GvX+XWomwYw/Pjlio8KbTAlJxhfK2Lh6 +H798k17VfxIrOk0KjzZS7+a20hZ/AgMBAAGjUzBRMB0GA1UdDgQWBBT2f5o8r75C +PWST36akpkKRRTbhvjAfBgNVHSMEGDAWgBT2f5o8r75CPWST36akpkKRRTbhvjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA9pb75p6mnqp2MQHSr +5SKRf2UV4wQIUtXgF6V9vNfvVzJii+Lzrqir1YMk5QgavCzD96KlJcqJCcH559RY +5743AxI3tdWfA3wajBctoy35oYnT4M30qbkryYLTUlv7PmeNWvrksTURchyyDt5/ +3T73yj5ZalmzKN6+xLfUDdnudspfWlUMutKU50MU1iuQESf4Fwd53vOg9jMcWJ2E +vAgfVI0XAvYdU3ybJrUvBq5zokYR2RzGv14uHxwVPnLBjrBEHRnbrXvLZJhuIS2b +xZ3CqwWi+9bvNqHz09HvhkU2b6fCGweKaAUGSo8OfQ5FRkjTUomMI/ZLs/qtJ6JR +zzVt +-----END CERTIFICATE----- diff --git a/testdata/empty b/testdata/empty new file mode 100644 index 0000000..e69de29 diff --git a/testdata/generate.sh b/testdata/generate.sh new file mode 100755 index 0000000..4b8cf36 --- /dev/null +++ b/testdata/generate.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -xeuo pipefail +# An example how-to re-generate testing certificates (because usually +# TLS certificates have expiration dates and some day they will expire). +# +# The instruction is valid for: +# +# $ openssl version +# OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022) + +cat < domains.ext +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +subjectAltName = @alt_names +[alt_names] +DNS.1 = localhost +IP.1 = 127.0.0.1 +EOF + +openssl req -x509 -nodes -new -sha256 -days 8192 -newkey rsa:2048 -keyout ca.key -out ca.pem -subj "/C=US/CN=Example-Root-CA" +openssl x509 -outform pem -in ca.pem -out ca.crt + +openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=Example-Certificates/CN=localhost" +openssl x509 -req -sha256 -days 8192 -in localhost.csr -CA ca.pem -CAkey ca.key -CAcreateserial -extfile domains.ext -out localhost.crt +password=mysslpassword + +# Tarantool tries every line from the password file. +cat < passwords +unusedpassword +$password +EOF + +cat < invalidpasswords +unusedpassword1 +EOF + +openssl rsa -aes256 -passout "pass:${password}" -in localhost.key -out localhost.enc.key diff --git a/testdata/invalidpasswords b/testdata/invalidpasswords new file mode 100644 index 0000000..b09d795 --- /dev/null +++ b/testdata/invalidpasswords @@ -0,0 +1 @@ +unusedpassword1 diff --git a/testdata/localhost.crt b/testdata/localhost.crt new file mode 100644 index 0000000..fd04b99 --- /dev/null +++ b/testdata/localhost.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIUAvSBJ3nSv7kdKw1IQ7AjchzI7T8wDQYJKoZIhvcNAQEL +BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y +MjA1MjYwNjE3NDBaFw00NDEwMjkwNjE3NDBaMGcxCzAJBgNVBAYTAlVTMRIwEAYD +VQQIDAlZb3VyU3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFt +cGxlLUNlcnRpZmljYXRlczESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnbFY+BMqlddktbitgaZICws4Zyj8LFy9QzO+ +AYSQyqFuTCI+cGqbP5r6Qf4f3xHNGykHJGn18brpiFWhNMaVkkgU3dycU8fFayVN +hLEJAXd4acWP1h5/aH4cOZgl+xJlmU2iLHtP/TLYEDDiVkfqL/MgUIMxbndIaiU0 +/e81v+2gi8ydyI6aElN8KbAaFPzXCZ28/RmO/0m36YzF+FSMVD1Hx8xO5V+Q9N1q +dsyrMdh0nCxDDXGdBgDrKt5+U1uJkDpTHfjMAkf7oBoRd8DJ8O74bpue03W5WxKQ +NjNfvHSgkBaQSdnxR93FSCr/Gs6WcUd50Y8z+ZCTNkup0KROTwIDAQABo3YwdDAf +BgNVHSMEGDAWgBT2f5o8r75CPWST36akpkKRRTbhvjAJBgNVHRMEAjAAMAsGA1Ud +DwQEAwIE8DAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwHQYDVR0OBBYEFOwH +aHK6QrEfltP7wwldUWrQJ9viMA0GCSqGSIb3DQEBCwUAA4IBAQAGHGuloGJqLoPZ +2iRnb/NaiArowLnUz4Z3ENKMB2KbZFGijMJSXO9i9ZLCFL+O93vCTspKGtHqVX2o +dxcrF7EZ9EaHIijWjKGEp1PszunBIca+Te+zyRg9Z+F9gwRsJYB8ctBGjIhe4qEv +ZSlRY489UVNKLTcHcl6nlUled9hciBJHpXuitiiNhzUareP38hROyiUhrAy8L21L +t7Ww5YGRuSTxM5LQfPZcCy40++TlyvXs0DCQ8ZuUbqZv64bNHbaLOyxIqKfPypXa +nS3AYZzUJjHj7vZwHoL1SyvBjx/DQAsWaEv137d8FlMqCsWLXfCsuNpKeQYZOyDS +7ploP9Gl +-----END CERTIFICATE----- diff --git a/testdata/localhost.enc.key b/testdata/localhost.enc.key new file mode 100644 index 0000000..b881820 --- /dev/null +++ b/testdata/localhost.enc.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIm+0WC9xe38cCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBNOE4KD+yauMfsnOiNAaaZBIIE +0DtXaHGpacJ8MjjL6zciYhgJOD9SJHE4vwPxpNDWuS9mf6wk/cdBNFMqnYwJmlYw +J/eQ+Z8MsZUqjnhDQz9YXXd8JftexAAa1bHnmfv2N/czJCx57dAHVdmJzgibfp18 +GCpqR23tklEO2Nj2HCbR59rh7IsnW9mD6jh+mVtkOix5HMCUSxwc3bEUutIQE80P +JHG2BsEfAeeHZa+QgG3Y15c6uSXD6wY73ldPPOgZ3NFOqcw/RDqYf1zsohx7auxi +Y6zHA7LdYtQjbNJ5slIfxPhAh75Fws0g4QvWbAwqqdEOVmlamYYjAOdVBBxTvcRs +/63ZN55VTQ8rYhShNA3BVFOLHaRD4mnlKE5Xh7gJXltCED7EHdpHdT9K3uM9U7nW +b2JSylt2RzY+LDsio2U0xsQp9jHzRRw81p8P1jmo5alP8jPACMsE8nnNNSDF4p43 +fG7hNNBq/dhq80iOnaArY05TIBMsD079tB0VKrYyyfaL0RbsAdgtCEmF9bCpnsTM +y9ExcJGQQJx9WNAHkSyjdzJd0jR6Zc0MrgRuj26nJ3Ahq58zaQKdfFO9RfGWd38n +MH3jshEtAuF+jXFbMcM4rVdIBPSuhYgHzYIC6yteziy7+6hittpWeNGLKpC5oZ8R +oEwH3MVsjCbd6Pp3vdcR412vLMgy1ZUOraDoY08FXC82RBJViVX6LLltIJu96kiX +WWUcRZAwzlJsTvh1EGmDcNNKCgmvWQaojqTNgTjxjJ3SzD2/TV6uQrSLgZ6ulyNl +7vKWt/YMTvIgoJA9JeH8Aik/XNd4bRXL+VXfUHpLTgn+WKiq2irVYd9R/yITDunP +a/kzqxitjU4OGdf/LOtYxfxfoGvFw5ym4KikoHKVg4ILcIQ+W4roOQQlu4/yezAK +fwYCrMVJWq4ESuQh3rn7eFR+eyBV6YcNBLm4iUcQTMhnXMMYxQ3TnDNga5eYhmV1 +ByYx+nFQDrbDolXo5JfXs3x6kXhoT/7wMHgsXtmRSd5PSBbaeJTrbMGA0Op6YgWr +EpvX3Yt863s4h+JgDpg9ouH+OJGgn7LGGye+TjjuDds8CStFdcFDDOayBS3EH4Cr +jgJwzvTdTZl+1YLYJXB67M4zmVPRRs5H88+fZYYA9bhZACL/rQBj2wDq/sIxvrIM +SCjOhSJ4z5Sm3XaBKnRG2GBBt67MeHB0+T3HR3VHKR+zStbCnsbOLythsE/CIA8L +fBNXMvnWa5bLgaCaEcK6Q3LOamJiKaigbmhI+3U3NUdb9cT1GhE0rtx6/IO9eapz +IUDOrtX9U+1o6iW2dahezxwLo9ftRwQ7qwG4qOk/Co/1c2WuuQ+d4YPpj/JOO5mf +LanA35mQjQrr2MZII91psznx05ffb5xMp2pqNbC6DVuZq8ZlhvVHGk+wM9RK3kYP +/ITwpbUvLmmN892kvZgLAXadSupBV8R/L5ZjDUO9U2all9p4eGfWZBk/yiivOLmh +VQxKCqAmThTO1hRa56+AjgzRJO6cY85ra+4Mm3FhhdR4gYvap2QTq0o2Vn0WlCHh +1SIeaDKfw9v4aGBbhqyQU2mPlXO5JiLktO+lZ5styVq9Qm+b0ROZxHzL1lRUNbRA +VfQO4fRnINKPgyzgH3tNxJTzw4pLkrkBD/g+zxDZVqkx +-----END ENCRYPTED PRIVATE KEY----- diff --git a/testdata/localhost.key b/testdata/localhost.key new file mode 100644 index 0000000..ed0f558 --- /dev/null +++ b/testdata/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCdsVj4EyqV12S1 +uK2BpkgLCzhnKPwsXL1DM74BhJDKoW5MIj5waps/mvpB/h/fEc0bKQckafXxuumI +VaE0xpWSSBTd3JxTx8VrJU2EsQkBd3hpxY/WHn9ofhw5mCX7EmWZTaIse0/9MtgQ +MOJWR+ov8yBQgzFud0hqJTT97zW/7aCLzJ3IjpoSU3wpsBoU/NcJnbz9GY7/Sbfp +jMX4VIxUPUfHzE7lX5D03Wp2zKsx2HScLEMNcZ0GAOsq3n5TW4mQOlMd+MwCR/ug +GhF3wMnw7vhum57TdblbEpA2M1+8dKCQFpBJ2fFH3cVIKv8azpZxR3nRjzP5kJM2 +S6nQpE5PAgMBAAECggEAFv81l9wHsll6pOu9VfJ/gCjPPXAjMn8F1OaXV5ZTHVHk +iXLXA0LwyBpcU8JxOHFapZLaqUtQpEObahf+zfkF+BLOBDr3i1pPZpxGjUraIt4e +7+HxY4sIDp+Rky6mn1JkAbLqKy2CkUzYaKgQYf/T3dFJjaRMUa1QoLYzX7MCdi5B +GnBICzi2UVsn3HU934l/gJKV+SlprdbrGJ+fRklP2AxLey3EOrwooUViy+k3+w5E +dzBH2HpLL0XuIHaBXQ01J6Mu3ud9ApFLC+Rh+2UFTW/WPnNe+B6BO5CGNN52Pfdr +Q5l+VzmRkXXo2fio+w4z/az8axT/DdhKGT2oBlp35QKBgQDZVGdKjkalH3QH2pdy +CWJIiybzY1R0CpimfgDLIdqEsps9qqgLXsUFB5yTcCRmg8RSWWHvhMVMyJtBcsdY +xGhmHxsFBxuray60UljxBcRQTwqvAX7mP8WEv8t80kbhyaxvOfkg8JD1n2hS7NjL +dOIG9Mh8L0YSOCRkbfv90OnYXQKBgQC5wGs35Ksnqi2swX5MLYfcBaImzoNde86n +cXJ0yyF82O1pk8DkmU2EDcUoQfkKxr3ANvVDG4vYaguIhYsJqPg/t8XQt/epDz/O +WZhqogn0ysaTv2FHrWcgPAkq82hpNII5NfPP8aRaYh8OUSfh4WHkW84m6+usqwjI +wbOq36qmmwKBgGMFFdreYEmzvwYlDoOiyukKncCfLUeB3HNfTbU/w3RafGjobJBh +qZrVEP4MRkl/F9/9YaXj9JE7haGYTkOfmYGOAp2T04OS3kDClEucuQluOgvqvorh +23jUej5xAGK3pJ046M2dTi7bZokB6PUqWCGbPg127JI4ijxH8FyA50rxAoGAQO2d +jMAFg6vco1JPT1lq7+GYOHBfQsIQDj99fo2yeu1or0rSVhWwHsShcdz9rGKj2Rhc +ysRKMa9/sIzdeNbzT3JxVu+3RgTqjLqMqFlTmZl3qBVxb5iRP5c8rSLAEGYmTtEp +FDqm9GDv8hU0F6SsjyH4AWrdylFOlL4Ai237PJkCgYBDC1wAwBD8WXJqRrYVGj7X +l4TQQ0hO7La/zgbasSgLNaJcYu32nut6D0O8IlmcQ2nO0BGPjQmJFGp6xawjViRu +np7fEkJQEf1pK0yeA8A3urjXccuUXEA9kKeqaSZYDzICPFaOlezPPPpW0hbkhnPe +dQn3DcoY6e6o0K5ltt1RvQ== +-----END PRIVATE KEY----- diff --git a/testdata/passwords b/testdata/passwords new file mode 100644 index 0000000..5853004 --- /dev/null +++ b/testdata/passwords @@ -0,0 +1,2 @@ +unusedpassword +mysslpassword diff --git a/testdata/sidecar/main b/testdata/sidecar/main new file mode 100755 index 0000000..cde97dd Binary files /dev/null and b/testdata/sidecar/main differ diff --git a/testdata/sidecar/main.go b/testdata/sidecar/main.go new file mode 100644 index 0000000..971b869 --- /dev/null +++ b/testdata/sidecar/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "os" + "strconv" + + "github.com/tarantool/go-tarantool/v2" +) + +func main() { + fd, err := strconv.Atoi(os.Getenv("SOCKET_FD")) + if err != nil { + panic(err) + } + dialer := tarantool.FdDialer{ + Fd: uintptr(fd), + } + conn, err := tarantool.Connect(context.Background(), dialer, tarantool.Opts{}) + if err != nil { + panic(err) + } + if _, err := conn.Do(tarantool.NewPingRequest()).Get(); err != nil { + panic(err) + } + // Insert new tuple. + if _, err := conn.Do(tarantool.NewInsertRequest("test"). + Tuple([]interface{}{239})).Get(); err != nil { + panic(err) + } + // Delete inserted tuple. + if _, err := conn.Do(tarantool.NewDeleteRequest("test"). + Index("primary"). + Key([]interface{}{239})).Get(); err != nil { + panic(err) + } +}