Skip to content

Commit 5e685a0

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 5e685a0

File tree

2 files changed

+69
-12
lines changed

2 files changed

+69
-12
lines changed

conn.go

+18
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

+51-12
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.
@@ -59,20 +81,34 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
5981
return nil, err
6082
}
6183

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

91+
data := &ssldata{&tlsConf, verifyCaOnly}
92+
configMapMu.Lock()
93+
configMap[hash] = data
94+
configMapMu.Unlock()
95+
return data, nil
96+
}
97+
98+
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
99+
// related settings. The function is nil when no upgrade should take place.
100+
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
101+
data, err := getTLSConf(o)
102+
if data == nil && err == nil {
103+
return nil, nil
104+
}
105+
if err != nil {
106+
return nil, err
107+
}
72108
return func(conn net.Conn) (net.Conn, error) {
73-
client := tls.Client(conn, &tlsConf)
74-
if verifyCaOnly {
75-
err := sslVerifyCertificateAuthority(client, &tlsConf)
109+
client := tls.Client(conn, data.Conf)
110+
if data.VerifyCAOnly {
111+
err := sslVerifyCertificateAuthority(client, data.Conf)
76112
if err != nil {
77113
return nil, err
78114
}
@@ -86,8 +122,7 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
86122
// in the user's home directory. The configured files must exist and have
87123
// the correct permissions.
88124
func sslClientCertificates(tlsConf *tls.Config, o values) error {
89-
sslinline := o["sslinline"]
90-
if sslinline == "true" {
125+
if o["sslinline"] == "true" {
91126
cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"]))
92127
// Clear out these params, in case they were to be sent to the PostgreSQL server by mistake
93128
o["sslcert"] = ""
@@ -98,7 +133,6 @@ func sslClientCertificates(tlsConf *tls.Config, o values) error {
98133
tlsConf.Certificates = []tls.Certificate{cert}
99134
return nil
100135
}
101-
102136
// user.Current() might fail when cross-compiling. We have to ignore the
103137
// error and continue without home directory defaults, since we wouldn't
104138
// know from where to load them.
@@ -168,6 +202,11 @@ func sslCertificateAuthority(tlsConf *tls.Config, o values) error {
168202
}
169203
}
170204

205+
cert, err := ioutil.ReadFile(sslrootcert)
206+
if err != nil {
207+
return err
208+
}
209+
171210
if !tlsConf.RootCAs.AppendCertsFromPEM(cert) {
172211
return fmterrorf("couldn't parse pem in sslrootcert")
173212
}

0 commit comments

Comments
 (0)