Skip to content

Commit c760caa

Browse files
committed
go-tlsdialer: initial repository setup
To disable SSL by default we want to transfer OpenSslDialer and any other ssl logic to the new go-tlsdialer repository. go-tlsdialer serves as an interlayer between go-tarantool and go-openssl. All ssl logic from go-tarantool is moved to the go-tlsdialer. go-tlsdialer still uses tarantool connection, but also types and methods from go-openssl. This way we are removing the direct go-openssl dependency from go-tarantool, without creating a tarantool dependency in go-openssl. Moved all ssl code from go-tarantool, some test helpers. Part of tarantool/go-tarantool#301
1 parent c4bdfec commit c760caa

23 files changed

+1929
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1010

1111
### Added
1212

13+
* `OpenSslDialer` type (#1).
14+
1315
### Changed
1416

1517
### Removed

README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,103 @@ To run a default set of tests:
1818
go test -v ./...
1919
```
2020

21+
## OpenSslDialer
22+
23+
User can create a dialer by filling the struct:
24+
```go
25+
// OpenSslDialer allows to use SSL transport for connection.
26+
type OpenSslDialer struct {
27+
// Address is an address to connect.
28+
// It could be specified in following ways:
29+
//
30+
// - TCP connections (tcp://192.168.1.1:3013, tcp://my.host:3013,
31+
// tcp:192.168.1.1:3013, tcp:my.host:3013, 192.168.1.1:3013, my.host:3013)
32+
//
33+
// - Unix socket, first '/' or '.' indicates Unix socket
34+
// (unix:///abs/path/tnt.sock, unix:path/tnt.sock, /abs/path/tnt.sock,
35+
// ./rel/path/tnt.sock, unix/:path/tnt.sock)
36+
Address string
37+
// Auth is an authentication method.
38+
Auth tarantool.Auth
39+
// Username for logging in to Tarantool.
40+
User string
41+
// User password for logging in to Tarantool.
42+
Password string
43+
// RequiredProtocol contains minimal protocol version and
44+
// list of protocol features that should be supported by
45+
// Tarantool server. By default, there are no restrictions.
46+
RequiredProtocolInfo tarantool.ProtocolInfo
47+
// SslKeyFile is a path to a private SSL key file.
48+
SslKeyFile string
49+
// SslCertFile is a path to an SSL certificate file.
50+
SslCertFile string
51+
// SslCaFile is a path to a trusted certificate authorities (CA) file.
52+
SslCaFile string
53+
// SslCiphers is a colon-separated (:) list of SSL cipher suites the connection
54+
// can use.
55+
//
56+
// We don't provide a list of supported ciphers. This is what OpenSSL
57+
// does. The only limitation is usage of TLSv1.2 (because other protocol
58+
// versions don't seem to support the GOST cipher). To add additional
59+
// ciphers (GOST cipher), you must configure OpenSSL.
60+
//
61+
// See also
62+
//
63+
// * https://www.openssl.org/docs/man1.1.1/man1/ciphers.html
64+
SslCiphers string
65+
// SslPassword is a password for decrypting the private SSL key file.
66+
// The priority is as follows: try to decrypt with SslPassword, then
67+
// try SslPasswordFile.
68+
SslPassword string
69+
// SslPasswordFile is a path to the list of passwords for decrypting
70+
// the private SSL key file. The connection tries every line from the
71+
// file as a password.
72+
SslPasswordFile string
73+
}
74+
```
75+
To create a connection from the created dialer a `Dial` function could be used:
76+
```go
77+
package tarantool
78+
79+
import (
80+
"context"
81+
"fmt"
82+
"time"
83+
84+
"github.com/tarantool/go-tarantool/v2"
85+
"github.com/tarantool/go-tlsdialer"
86+
)
87+
88+
func main() {
89+
dialer := tlsdialer.OpenSslDialer{
90+
Address: "127.0.0.1:3301",
91+
User: "guest",
92+
}
93+
opts := tarantool.Opts{
94+
Timeout: 5 * time.Second,
95+
}
96+
97+
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
98+
defer cancel()
99+
100+
conn, err := tarantool.Connect(ctx, dialer, opts)
101+
if err != nil {
102+
fmt.Printf("Failed to create an example connection: %s", err)
103+
return
104+
}
105+
106+
// Use the connection.
107+
data, err := conn.Do(tarantool.NewInsertRequest(999).
108+
Tuple([]interface{}{99999, "BB"}),
109+
).Get()
110+
if err != nil {
111+
fmt.Println("Error", err)
112+
} else {
113+
fmt.Printf("Data: %v", data)
114+
}
115+
}
116+
```
117+
21118
[godoc-badge]: https://pkg.go.dev/badge/github.com/tarantool/go-tlsdialer.svg
22119
[godoc-url]: https://pkg.go.dev/github.com/tarantool/go-tlsdialer
23120
[coverage-badge]: https://coveralls.io/repos/github/tarantool/go-tlsdialer/badge.svg?branch=master

conn.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package tlsdialer
2+
3+
import (
4+
"errors"
5+
"io"
6+
"net"
7+
8+
"github.com/tarantool/go-tarantool/v2"
9+
)
10+
11+
type tntConn struct {
12+
net net.Conn
13+
reader io.Reader
14+
writer writeFlusher
15+
}
16+
17+
// writeFlusher is the interface that groups the basic Write and Flush methods.
18+
type writeFlusher interface {
19+
io.Writer
20+
Flush() error
21+
}
22+
23+
// Addr makes tntConn satisfy the Conn interface.
24+
func (c *tntConn) Addr() net.Addr {
25+
return c.net.RemoteAddr()
26+
}
27+
28+
// Read makes tntConn satisfy the Conn interface.
29+
func (c *tntConn) Read(p []byte) (int, error) {
30+
return c.reader.Read(p)
31+
}
32+
33+
// Write makes tntConn satisfy the Conn interface.
34+
func (c *tntConn) Write(p []byte) (int, error) {
35+
if l, err := c.writer.Write(p); err != nil {
36+
return l, err
37+
} else if l != len(p) {
38+
return l, errors.New("wrong length written")
39+
} else {
40+
return l, nil
41+
}
42+
}
43+
44+
// Flush makes tntConn satisfy the Conn interface.
45+
func (c *tntConn) Flush() error {
46+
return c.writer.Flush()
47+
}
48+
49+
// Close makes tntConn satisfy the Conn interface.
50+
func (c *tntConn) Close() error {
51+
return c.net.Close()
52+
}
53+
54+
// Greeting makes tntConn satisfy the Conn interface.
55+
func (c *tntConn) Greeting() tarantool.Greeting {
56+
return tarantool.Greeting{}
57+
}
58+
59+
// ProtocolInfo makes tntConn satisfy the Conn interface.
60+
func (c *tntConn) ProtocolInfo() tarantool.ProtocolInfo {
61+
return tarantool.ProtocolInfo{}
62+
}

deadline_io.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package tlsdialer
2+
3+
import (
4+
"net"
5+
"time"
6+
)
7+
8+
type deadlineIO struct {
9+
to time.Duration
10+
c net.Conn
11+
}
12+
13+
func (d *deadlineIO) Write(b []byte) (n int, err error) {
14+
if d.to > 0 {
15+
if err := d.c.SetWriteDeadline(time.Now().Add(d.to)); err != nil {
16+
return 0, err
17+
}
18+
}
19+
n, err = d.c.Write(b)
20+
return
21+
}
22+
23+
func (d *deadlineIO) Read(b []byte) (n int, err error) {
24+
if d.to > 0 {
25+
if err := d.c.SetReadDeadline(time.Now().Add(d.to)); err != nil {
26+
return 0, err
27+
}
28+
}
29+
n, err = d.c.Read(b)
30+
return
31+
}

dial.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package tlsdialer
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"errors"
7+
"net"
8+
"os"
9+
"strings"
10+
11+
"github.com/tarantool/go-openssl"
12+
)
13+
14+
func sslDialContext(ctx context.Context, network, address string,
15+
sslOpts opts) (connection net.Conn, err error) {
16+
var sslCtx interface{}
17+
if sslCtx, err = sslCreateContext(sslOpts); err != nil {
18+
return
19+
}
20+
21+
return openssl.DialContext(ctx, network, address, sslCtx.(*openssl.Ctx), 0)
22+
}
23+
24+
// interface{} is a hack. It helps to avoid dependency of go-openssl in build
25+
// of tests with the tag 'go_tarantool_ssl_disable'.
26+
func sslCreateContext(sslOpts opts) (ctx interface{}, err error) {
27+
var sslCtx *openssl.Ctx
28+
29+
// Require TLSv1.2, because other protocol versions don't seem to
30+
// support the GOST cipher.
31+
if sslCtx, err = openssl.NewCtxWithVersion(openssl.TLSv1_2); err != nil {
32+
return
33+
}
34+
ctx = sslCtx
35+
sslCtx.SetMaxProtoVersion(openssl.TLS1_2_VERSION)
36+
sslCtx.SetMinProtoVersion(openssl.TLS1_2_VERSION)
37+
38+
if sslOpts.CertFile != "" {
39+
if err = sslLoadCert(sslCtx, sslOpts.CertFile); err != nil {
40+
return
41+
}
42+
}
43+
44+
if sslOpts.KeyFile != "" {
45+
if err = sslLoadKey(sslCtx, sslOpts.KeyFile, sslOpts.Password,
46+
sslOpts.PasswordFile); err != nil {
47+
return
48+
}
49+
}
50+
51+
if sslOpts.CaFile != "" {
52+
if err = sslCtx.LoadVerifyLocations(sslOpts.CaFile, ""); err != nil {
53+
return
54+
}
55+
verifyFlags := openssl.VerifyPeer | openssl.VerifyFailIfNoPeerCert
56+
sslCtx.SetVerify(verifyFlags, nil)
57+
}
58+
59+
if sslOpts.Ciphers != "" {
60+
if err = sslCtx.SetCipherList(sslOpts.Ciphers); err != nil {
61+
return
62+
}
63+
}
64+
65+
return
66+
}
67+
68+
func sslLoadCert(ctx *openssl.Ctx, certFile string) (err error) {
69+
var certBytes []byte
70+
if certBytes, err = os.ReadFile(certFile); err != nil {
71+
return
72+
}
73+
74+
certs := openssl.SplitPEM(certBytes)
75+
if len(certs) == 0 {
76+
err = errors.New("No PEM certificate found in " + certFile)
77+
return
78+
}
79+
first, certs := certs[0], certs[1:]
80+
81+
var cert *openssl.Certificate
82+
if cert, err = openssl.LoadCertificateFromPEM(first); err != nil {
83+
return
84+
}
85+
if err = ctx.UseCertificate(cert); err != nil {
86+
return
87+
}
88+
89+
for _, pem := range certs {
90+
if cert, err = openssl.LoadCertificateFromPEM(pem); err != nil {
91+
break
92+
}
93+
if err = ctx.AddChainCertificate(cert); err != nil {
94+
break
95+
}
96+
}
97+
return
98+
}
99+
100+
func sslLoadKey(ctx *openssl.Ctx, keyFile string, password string,
101+
passwordFile string) error {
102+
var keyBytes []byte
103+
var err, firstDecryptErr error
104+
105+
if keyBytes, err = os.ReadFile(keyFile); err != nil {
106+
return err
107+
}
108+
109+
// If the key is encrypted and password is not provided,
110+
// openssl.LoadPrivateKeyFromPEM(keyBytes) asks to enter PEM pass phrase
111+
// interactively. On the other hand,
112+
// openssl.LoadPrivateKeyFromPEMWithPassword(keyBytes, password) works fine
113+
// for non-encrypted key with any password, including empty string. If
114+
// the key is encrypted, we fast fail with password error instead of
115+
// requesting the pass phrase interactively.
116+
passwords := []string{password}
117+
if passwordFile != "" {
118+
file, err := os.Open(passwordFile)
119+
if err == nil {
120+
defer file.Close()
121+
122+
scanner := bufio.NewScanner(file)
123+
// Tarantool itself tries each password file line.
124+
for scanner.Scan() {
125+
password = strings.TrimSpace(scanner.Text())
126+
passwords = append(passwords, password)
127+
}
128+
} else {
129+
firstDecryptErr = err
130+
}
131+
}
132+
133+
for _, password := range passwords {
134+
key, err := openssl.LoadPrivateKeyFromPEMWithPassword(keyBytes, password)
135+
if err == nil {
136+
return ctx.UsePrivateKey(key)
137+
} else if firstDecryptErr == nil {
138+
firstDecryptErr = err
139+
}
140+
}
141+
142+
return firstDecryptErr
143+
}

0 commit comments

Comments
 (0)