Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ssl: reuse *tls.Config if connection settings are identical #1033

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"os/user"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"sync/atomic"
Expand Down Expand Up @@ -394,6 +395,23 @@ func network(o values) (string, string) {

type values map[string]string

// Hash returns a deterministic hash of values.
func (v values) Hash() []byte {
keys := make([]string, len(v))
i := 0
for key := range v {
keys[i] = key
i++
}
sort.Strings(keys)
h := sha256.New()
for _, key := range keys {
h.Write([]byte(key))
h.Write([]byte(v[key]))
}
return h.Sum(nil)
}

// scanner implements a tokenizer for libpq-style option strings.
type scanner struct {
s []rune
Expand Down
55 changes: 46 additions & 9 deletions ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,34 @@ import (
"os"
"os/user"
"path/filepath"
"sync"
)

// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
// related settings. The function is nil when no upgrade should take place.
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
// To avoid allocating the map if we never use ssl
var configMapOnce sync.Once
var configMapMu sync.Mutex
var configMap map[string]*ssldata

type ssldata struct {
Conf *tls.Config
VerifyCAOnly bool
}

func getTLSConf(o values) (*ssldata, error) {
verifyCaOnly := false
configMapOnce.Do(func() {
configMap = make(map[string]*ssldata)
})
// this function modifies o, so take the hash before any modifications are
// made
hash := string(o.Hash())
// This pseudo-parameter is not recognized by the PostgreSQL server, so let's delete it after use.
configMapMu.Lock()
conf, ok := configMap[hash]
Copy link

@Lekensteyn Lekensteyn Apr 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hash can change due to delete(o, "sslrootcert") and delete(o, "sslinline") below.

Edit: Actually, delete(o, "sslinline") got removed in #1035.

configMapMu.Unlock()
if ok {
return conf, nil
}
tlsConf := tls.Config{}
switch mode := o["sslmode"]; mode {
// "require" is the default.
Expand Down Expand Up @@ -69,10 +91,27 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
// also initiates renegotiations and cannot be reconfigured.
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient

data := &ssldata{&tlsConf, verifyCaOnly}
configMapMu.Lock()
configMap[hash] = data
configMapMu.Unlock()
return data, nil
}

// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
// related settings. The function is nil when no upgrade should take place.
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
data, err := getTLSConf(o)
if data == nil && err == nil {
return nil, nil
}
if err != nil {
return nil, err
}
return func(conn net.Conn) (net.Conn, error) {
client := tls.Client(conn, &tlsConf)
if verifyCaOnly {
err := sslVerifyCertificateAuthority(client, &tlsConf)
client := tls.Client(conn, data.Conf)
if data.VerifyCAOnly {
err := sslVerifyCertificateAuthority(client, data.Conf)
if err != nil {
return nil, err
}
Expand All @@ -86,8 +125,7 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
// in the user's home directory. The configured files must exist and have
// the correct permissions.
func sslClientCertificates(tlsConf *tls.Config, o values) error {
sslinline := o["sslinline"]
if sslinline == "true" {
if o["sslinline"] == "true" {
cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"]))
// Clear out these params, in case they were to be sent to the PostgreSQL server by mistake
o["sslcert"] = ""
Expand All @@ -98,7 +136,6 @@ func sslClientCertificates(tlsConf *tls.Config, o values) error {
tlsConf.Certificates = []tls.Certificate{cert}
return nil
}

// user.Current() might fail when cross-compiling. We have to ignore the
// error and continue without home directory defaults, since we wouldn't
// know from where to load them.
Expand Down