Skip to content

Commit 11ecdbf

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 #1032.
1 parent 072e83d commit 11ecdbf

File tree

2 files changed

+64
-9
lines changed

2 files changed

+64
-9
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 & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,34 @@ import (
88
"os"
99
"os/user"
1010
"path/filepath"
11+
"sync"
1112
)
1213

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) {
14+
// To avoid allocating the map if we never use ssl
15+
var configMapOnce sync.Once
16+
var configMapMu sync.Mutex
17+
var configMap map[string]*ssldata
18+
19+
type ssldata struct {
20+
Conf *tls.Config
21+
VerifyCAOnly bool
22+
}
23+
24+
func getTLSConf(o values) (*ssldata, error) {
1625
verifyCaOnly := false
26+
configMapOnce.Do(func() {
27+
configMap = make(map[string]*ssldata)
28+
})
29+
// this function modifies o, so take the hash before any modifications are
30+
// made
31+
hash := string(o.Hash())
32+
// This pseudo-parameter is not recognized by the PostgreSQL server, so let's delete it after use.
33+
configMapMu.Lock()
34+
conf, ok := configMap[hash]
35+
configMapMu.Unlock()
36+
if ok {
37+
return conf, nil
38+
}
1739
tlsConf := tls.Config{}
1840
switch mode := o["sslmode"]; mode {
1941
// "require" is the default.
@@ -69,10 +91,27 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
6991
// also initiates renegotiations and cannot be reconfigured.
7092
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
7193

94+
data := &ssldata{&tlsConf, verifyCaOnly}
95+
configMapMu.Lock()
96+
configMap[hash] = data
97+
configMapMu.Unlock()
98+
return data, nil
99+
}
100+
101+
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
102+
// related settings. The function is nil when no upgrade should take place.
103+
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
104+
data, err := getTLSConf(o)
105+
if data == nil && err == nil {
106+
return nil, nil
107+
}
108+
if err != nil {
109+
return nil, err
110+
}
72111
return func(conn net.Conn) (net.Conn, error) {
73-
client := tls.Client(conn, &tlsConf)
74-
if verifyCaOnly {
75-
err := sslVerifyCertificateAuthority(client, &tlsConf)
112+
client := tls.Client(conn, data.Conf)
113+
if data.VerifyCAOnly {
114+
err := sslVerifyCertificateAuthority(client, data.Conf)
76115
if err != nil {
77116
return nil, err
78117
}
@@ -86,8 +125,7 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
86125
// in the user's home directory. The configured files must exist and have
87126
// the correct permissions.
88127
func sslClientCertificates(tlsConf *tls.Config, o values) error {
89-
sslinline := o["sslinline"]
90-
if sslinline == "true" {
128+
if o["sslinline"] == "true" {
91129
cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"]))
92130
// Clear out these params, in case they were to be sent to the PostgreSQL server by mistake
93131
o["sslcert"] = ""
@@ -98,7 +136,6 @@ func sslClientCertificates(tlsConf *tls.Config, o values) error {
98136
tlsConf.Certificates = []tls.Certificate{cert}
99137
return nil
100138
}
101-
102139
// user.Current() might fail when cross-compiling. We have to ignore the
103140
// error and continue without home directory defaults, since we wouldn't
104141
// know from where to load them.

0 commit comments

Comments
 (0)