Skip to content

Commit 9449587

Browse files
committed
ssl: reuse *tls.Config if connection settings are identical
Previously, we would reload and re-parse a certificate from disk every single time we initialized a connection and the sslrootcert setting was enabled. This results in a lot of allocations that can be avoided. Instead, save the *tls.Config for a given configuration hash, and reuse it when we see it again. Fixes lib#1032.
1 parent 072e83d commit 9449587

File tree

2 files changed

+64
-35
lines changed

2 files changed

+64
-35
lines changed

conn.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"os/user"
1717
"path"
1818
"path/filepath"
19+
"sort"
1920
"strconv"
2021
"strings"
2122
"sync/atomic"
@@ -394,6 +395,23 @@ func network(o values) (string, string) {
394395

395396
type values map[string]string
396397

398+
// Hash returns a deterministic hash of values.
399+
func (v values) Hash() []byte {
400+
keys := make([]string, len(v))
401+
i := 0
402+
for key := range v {
403+
keys[i] = key
404+
i++
405+
}
406+
sort.Strings(keys)
407+
h := sha256.New()
408+
for _, key := range keys {
409+
h.Write([]byte(key))
410+
h.Write([]byte(v[key]))
411+
}
412+
return h.Sum(nil)
413+
}
414+
397415
// scanner implements a tokenizer for libpq-style option strings.
398416
type scanner struct {
399417
s []rune

ssl.go

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,36 @@ package pq
33
import (
44
"crypto/tls"
55
"crypto/x509"
6+
"fmt"
67
"io/ioutil"
78
"net"
89
"os"
910
"os/user"
1011
"path/filepath"
12+
"sync"
1113
)
1214

13-
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
14-
// related settings. The function is nil when no upgrade should take place.
15-
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
15+
// To avoid allocating the map if we never use ssl
16+
var configMapOnce sync.Once
17+
var configMapMu sync.Mutex
18+
var configMap map[string]*ssldata
19+
20+
type ssldata struct {
21+
Conf *tls.Config
22+
VerifyCAOnly bool
23+
}
24+
25+
func getTLSConf(o values) (*ssldata, error) {
1626
verifyCaOnly := false
27+
configMapOnce.Do(func() {
28+
configMap = make(map[string]*ssldata)
29+
})
30+
configMapMu.Lock()
31+
conf, ok := configMap[string(o.Hash())]
32+
configMapMu.Unlock()
33+
if ok {
34+
return conf, nil
35+
}
1736
tlsConf := tls.Config{}
1837
switch mode := o["sslmode"]; mode {
1938
// "require" is the default.
@@ -59,20 +78,35 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
5978
return nil, err
6079
}
6180

62-
// This pseudo-parameter is not recognized by the PostgreSQL server, so let's delete it after use.
63-
delete(o, "sslinline")
64-
6581
// Accept renegotiation requests initiated by the backend.
6682
//
6783
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
6884
// the default configuration of older versions has it enabled. Redshift
6985
// also initiates renegotiations and cannot be reconfigured.
7086
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
7187

88+
data := &ssldata{&tlsConf, verifyCaOnly}
89+
configMapMu.Lock()
90+
configMap[string(o.Hash())] = data
91+
fmt.Printf("o: %#v\n", string(o.Hash()))
92+
configMapMu.Unlock()
93+
return data, nil
94+
}
95+
96+
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
97+
// related settings. The function is nil when no upgrade should take place.
98+
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
99+
data, err := getTLSConf(o)
100+
if data == nil && err == nil {
101+
return nil, nil
102+
}
103+
if err != nil {
104+
return nil, err
105+
}
72106
return func(conn net.Conn) (net.Conn, error) {
73-
client := tls.Client(conn, &tlsConf)
74-
if verifyCaOnly {
75-
err := sslVerifyCertificateAuthority(client, &tlsConf)
107+
client := tls.Client(conn, data.Conf)
108+
if data.VerifyCAOnly {
109+
err := sslVerifyCertificateAuthority(client, data.Conf)
76110
if err != nil {
77111
return nil, err
78112
}
@@ -86,19 +120,6 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
86120
// in the user's home directory. The configured files must exist and have
87121
// the correct permissions.
88122
func sslClientCertificates(tlsConf *tls.Config, o values) error {
89-
sslinline := o["sslinline"]
90-
if sslinline == "true" {
91-
cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"]))
92-
// Clear out these params, in case they were to be sent to the PostgreSQL server by mistake
93-
o["sslcert"] = ""
94-
o["sslkey"] = ""
95-
if err != nil {
96-
return err
97-
}
98-
tlsConf.Certificates = []tls.Certificate{cert}
99-
return nil
100-
}
101-
102123
// user.Current() might fail when cross-compiling. We have to ignore the
103124
// error and continue without home directory defaults, since we wouldn't
104125
// know from where to load them.
@@ -153,19 +174,9 @@ func sslCertificateAuthority(tlsConf *tls.Config, o values) error {
153174
if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 {
154175
tlsConf.RootCAs = x509.NewCertPool()
155176

156-
sslinline := o["sslinline"]
157-
158-
var cert []byte
159-
if sslinline == "true" {
160-
// // Clear out this param, in case it were to be sent to the PostgreSQL server by mistake
161-
o["sslrootcert"] = ""
162-
cert = []byte(sslrootcert)
163-
} else {
164-
var err error
165-
cert, err = ioutil.ReadFile(sslrootcert)
166-
if err != nil {
167-
return err
168-
}
177+
cert, err := ioutil.ReadFile(sslrootcert)
178+
if err != nil {
179+
return err
169180
}
170181

171182
if !tlsConf.RootCAs.AppendCertsFromPEM(cert) {

0 commit comments

Comments
 (0)