From b9bb726ebf154627a21b50f9ffa4b28c6ed3f4d8 Mon Sep 17 00:00:00 2001 From: Eirik Date: Sun, 16 Dec 2018 22:34:36 +0100 Subject: [PATCH 1/3] Support inline SSL certificates Presently, pq only supports SSL connections by loading PEM certificates from files on disk. There are some situations (for example integration with HashiCorp Vault) where it's not so feasible to load certificates from a file system, but better to store them in-memory. This patch lets you set ?sslinline=true in the connection string, which changes the behavior of the paramters sslrootcert, sslcert and sslkey, so they contain the contents of the certificates directly, instead of file names pointing to the certificates on disk. --- ssl.go | 33 ++++++++++++++++++++++++++++++--- url.go | 4 ++-- url_test.go | 6 +++--- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/ssl.go b/ssl.go index d90208455..5db5af83a 100644 --- a/ssl.go +++ b/ssl.go @@ -59,6 +59,9 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { return nil, err } + // This pseudo-parameter is not recognized by the PostgreSQL server, so let's delete it after use. + delete(o, "sslinline") + // Accept renegotiation requests initiated by the backend. // // Renegotiation was deprecated then removed from PostgreSQL 9.5, but @@ -83,6 +86,20 @@ 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 "true" == sslinline { + 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"] = "" + o["sslkey"] = "" + if err != nil { + return err + } + 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. @@ -137,9 +154,19 @@ func sslCertificateAuthority(tlsConf *tls.Config, o values) error { if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 { tlsConf.RootCAs = x509.NewCertPool() - cert, err := ioutil.ReadFile(sslrootcert) - if err != nil { - return err + sslinline := o["sslinline"] + + var cert []byte + if "true" == sslinline { + // // Clear out this param, in case it were to be sent to the PostgreSQL server by mistake + o["sslrootcert"] = "" + cert = []byte(sslrootcert) + } else { + var err error + cert, err = ioutil.ReadFile(sslrootcert) + if err != nil { + return err + } } if !tlsConf.RootCAs.AppendCertsFromPEM(cert) { diff --git a/url.go b/url.go index f4d8a7c20..aec6e95be 100644 --- a/url.go +++ b/url.go @@ -40,10 +40,10 @@ func ParseURL(url string) (string, error) { } var kvs []string - escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`) + escaper := strings.NewReplacer(`'`, `\'`, `\`, `\\`) accrue := func(k, v string) { if v != "" { - kvs = append(kvs, k+"="+escaper.Replace(v)) + kvs = append(kvs, k+"='"+escaper.Replace(v)+"'") } } diff --git a/url_test.go b/url_test.go index 4ff0ce034..9b6345595 100644 --- a/url_test.go +++ b/url_test.go @@ -5,7 +5,7 @@ import ( ) func TestSimpleParseURL(t *testing.T) { - expected := "host=hostname.remote" + expected := "host='hostname.remote'" str, err := ParseURL("postgres://hostname.remote") if err != nil { t.Fatal(err) @@ -17,7 +17,7 @@ func TestSimpleParseURL(t *testing.T) { } func TestIPv6LoopbackParseURL(t *testing.T) { - expected := "host=::1 port=1234" + expected := "host='::1' port='1234'" str, err := ParseURL("postgres://[::1]:1234") if err != nil { t.Fatal(err) @@ -29,7 +29,7 @@ func TestIPv6LoopbackParseURL(t *testing.T) { } func TestFullParseURL(t *testing.T) { - expected := `dbname=database host=hostname.remote password=top\ secret port=1234 user=username` + expected := `dbname='database' host='hostname.remote' password='top secret' port='1234' user='username'` str, err := ParseURL("postgres://username:top%20secret@hostname.remote:1234/database") if err != nil { t.Fatal(err) From b7c85eeec276e594e29cd6ceec5e007965ff0d51 Mon Sep 17 00:00:00 2001 From: Eirik Date: Sun, 16 Dec 2018 23:22:54 +0100 Subject: [PATCH 2/3] remove empty line --- ssl.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ssl.go b/ssl.go index 5db5af83a..3c1f1fc37 100644 --- a/ssl.go +++ b/ssl.go @@ -99,7 +99,6 @@ func sslClientCertificates(tlsConf *tls.Config, o values) error { 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. From 1467baf0954574fc17008b8c564938ffe3bfaa95 Mon Sep 17 00:00:00 2001 From: Eirik Sletteberg Date: Fri, 12 Feb 2021 12:47:39 +0100 Subject: [PATCH 3/3] Fix code style (no yoda conditions) --- ssl.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssl.go b/ssl.go index 3c1f1fc37..881c2219b 100644 --- a/ssl.go +++ b/ssl.go @@ -87,7 +87,7 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { // the correct permissions. func sslClientCertificates(tlsConf *tls.Config, o values) error { sslinline := o["sslinline"] - if "true" == sslinline { + if 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"] = "" @@ -156,7 +156,7 @@ func sslCertificateAuthority(tlsConf *tls.Config, o values) error { sslinline := o["sslinline"] var cert []byte - if "true" == sslinline { + if sslinline == "true" { // // Clear out this param, in case it were to be sent to the PostgreSQL server by mistake o["sslrootcert"] = "" cert = []byte(sslrootcert)