From 32ec44f726e1376620ad746a226cd691c4b8d528 Mon Sep 17 00:00:00 2001 From: Eric McCormack Date: Tue, 21 Jun 2022 11:19:46 -0400 Subject: [PATCH 1/9] Add support for SslPassword --- .idea/pgconn.iml | 9 ++++++++ config.go | 56 +++++++++++++++++++++++++++++++++++++++++++----- pgconn.go | 2 ++ pgconn_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 .idea/pgconn.iml diff --git a/.idea/pgconn.iml b/.idea/pgconn.iml new file mode 100644 index 00000000..5e764c4f --- /dev/null +++ b/.idea/pgconn.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/config.go b/config.go index 8fd7efbf..12a48288 100644 --- a/config.go +++ b/config.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "encoding/pem" "errors" "fmt" "io" @@ -60,6 +61,9 @@ type Config struct { // OnNotification is a callback function called when a notification from the LISTEN/NOTIFY system is received. OnNotification NotificationHandler + // SslPasswordCallback is a callback function to handle Auth callback for SSL Password + SslPasswordCallback SslPasswordCallbackHandler + createdByParseConfig bool // Used to enforce created by ParseConfig rule. } @@ -132,6 +136,11 @@ func NetworkAddress(host string, port uint16) (network, address string) { return network, address } +// ParseConfig builds a *Config when sslpasswordcallback function is not provided +func ParseConfig(connString string) (*Config, error) { + return ParseConfigWithSslPasswordCallback(connString, nil) +} + // ParseConfig builds a *Config with similar behavior to the PostgreSQL standard C library libpq. It uses the same // defaults as libpq (e.g. port=5432) and understands most PG* environment variables. ParseConfig closely matches // the parsing behavior of libpq. connString may either be in URL format or keyword = value format (DSN style). See @@ -171,6 +180,7 @@ func NetworkAddress(host string, port uint16) (network, address string) { // PGSSLCERT // PGSSLKEY // PGSSLROOTCERT +// PGSSLPASSWORD // PGAPPNAME // PGCONNECT_TIMEOUT // PGTARGETSESSIONATTRS @@ -194,6 +204,7 @@ func NetworkAddress(host string, port uint16) (network, address string) { // which does not use TLS. This can lead to an unexpected unencrypted connection if the main TLS config is manually // changed later but the unencrypted fallback is present. Ensure there are no stale fallbacks when manually setting // TLCConfig. +// sslPasswordCallback function provide a callback function for sslpassword // // Other known differences with libpq: // @@ -207,7 +218,7 @@ func NetworkAddress(host string, port uint16) (network, address string) { // servicefile // libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a // part of the connection string. -func ParseConfig(connString string) (*Config, error) { +func ParseConfigWithSslPasswordCallback(connString string, sslPasswordCallback SslPasswordCallbackHandler) (*Config, error) { defaultSettings := defaultSettings() envSettings := parseEnvSettings() @@ -278,6 +289,7 @@ func ParseConfig(connString string) (*Config, error) { "sslkey": {}, "sslcert": {}, "sslrootcert": {}, + "sslpassword": {}, "krbspn": {}, "krbsrvname": {}, "target_session_attrs": {}, @@ -326,7 +338,7 @@ func ParseConfig(connString string) (*Config, error) { tlsConfigs = append(tlsConfigs, nil) } else { var err error - tlsConfigs, err = configTLS(settings, host) + tlsConfigs, err = configTLS(settings, host, sslPasswordCallback) if err != nil { return nil, &parseConfigError{connString: connString, msg: "failed to configure TLS", err: err} } @@ -406,6 +418,7 @@ func parseEnvSettings() map[string]string { "PGSSLKEY": "sslkey", "PGSSLCERT": "sslcert", "PGSSLROOTCERT": "sslrootcert", + "PGSSLPASSWORD": "sslpassword", "PGTARGETSESSIONATTRS": "target_session_attrs", "PGSERVICE": "service", "PGSERVICEFILE": "servicefile", @@ -592,12 +605,13 @@ func parseServiceSettings(servicefilePath, serviceName string) (map[string]strin // configTLS uses libpq's TLS parameters to construct []*tls.Config. It is // necessary to allow returning multiple TLS configs as sslmode "allow" and // "prefer" allow fallback. -func configTLS(settings map[string]string, thisHost string) ([]*tls.Config, error) { +func configTLS(settings map[string]string, thisHost string, sslPasswordCallback SslPasswordCallbackHandler) ([]*tls.Config, error) { host := thisHost sslmode := settings["sslmode"] sslrootcert := settings["sslrootcert"] sslcert := settings["sslcert"] sslkey := settings["sslkey"] + sslpassword := settings["sslpassword"] // Match libpq default behavior if sslmode == "" { @@ -685,11 +699,43 @@ func configTLS(settings map[string]string, thisHost string) ([]*tls.Config, erro } if sslcert != "" && sslkey != "" { - cert, err := tls.LoadX509KeyPair(sslcert, sslkey) + buf, err := ioutil.ReadFile(sslkey) + if err != nil { + return nil, fmt.Errorf("unable to read sslkey: %w", err) + } + block, _ := pem.Decode(buf) + var pemKey []byte + // If PEM is encrypted, attempt to decrypt using pass phrase + if x509.IsEncryptedPEMBlock(block) { + if sslpassword == "" { + if sslPasswordCallback == nil { + return nil, fmt.Errorf("unable to find sslpassword: %w", err) + } + sslpassword = sslPasswordCallback() + } + // Attempt decryption with pass phrase + // NOTE: only supports RSA (PKCS#1) + decryptedKey, err := x509.DecryptPEMBlock(block, []byte(sslpassword)) + // Should we also provide warning for PKCS#1 needed? + if err != nil { + return nil, fmt.Errorf("unable to decrypt key: %w", err) + } + pemBytes := pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: decryptedKey, + } + pemKey = pem.EncodeToMemory(&pemBytes) + } else { + pemKey = pem.EncodeToMemory(block) + } + certfile, err := ioutil.ReadFile(sslcert) if err != nil { return nil, fmt.Errorf("unable to read cert: %w", err) } - + cert, err := tls.X509KeyPair(certfile, pemKey) + if err != nil { + return nil, fmt.Errorf("unable to load cert: %w", err) + } tlsConfig.Certificates = []tls.Certificate{cert} } diff --git a/pgconn.go b/pgconn.go index 430f4367..67d6af38 100644 --- a/pgconn.go +++ b/pgconn.go @@ -64,6 +64,8 @@ type NoticeHandler func(*PgConn, *Notice) // notice event. type NotificationHandler func(*PgConn, *Notification) +type SslPasswordCallbackHandler func() (string) + // Frontend used to receive messages from backend. type Frontend interface { Receive() (pgproto3.BackendMessage, error) diff --git a/pgconn_test.go b/pgconn_test.go index 32186fc6..d9adda99 100644 --- a/pgconn_test.go +++ b/pgconn_test.go @@ -1,6 +1,7 @@ package pgconn_test import ( + "bufio" "bytes" "compress/gzip" "context" @@ -63,7 +64,59 @@ func TestConnectTLS(t *testing.T) { t.Skipf("Skipping due to missing environment variable %v", "PGX_TEST_TLS_CONN_STRING") } - conn, err := pgconn.Connect(context.Background(), connString) + var conn *pgconn.PgConn + var err error + + isSslPasswrodEmpty := strings.HasSuffix(connString, "sslpassword=") + + if isSslPasswrodEmpty { + config, err := pgconn.ParseConfigWithSslPasswordCallback(connString, GetSslPassword) + require.Nil(t, err) + + conn, err = pgconn.ConnectConfig(context.Background(), config) + require.NoError(t, err) + } else { + conn, err = pgconn.Connect(context.Background(), connString) + require.NoError(t, err) + } + + if _, ok := conn.Conn().(*tls.Conn); !ok { + t.Error("not a TLS connection") + } + + closeConn(t, conn) +} + +func GetSslPassword() string { + readFile, err := os.Open("data.txt") + if err != nil { + fmt.Println(err) + } + fileScanner := bufio.NewScanner(readFile) + fileScanner.Split(bufio.ScanLines) + for fileScanner.Scan() { + line := fileScanner.Text() + if strings.HasPrefix(line, "sslpassword=") { + index := len("sslpassword=") + line := line[index:] + return line + } + } + return "" +} + +func TestConnectTLSCallback(t *testing.T) { + t.Parallel() + + connString := os.Getenv("PGX_TEST_TLS_CONN_STRING") + if connString == "" { + t.Skipf("Skipping due to missing environment variable %v", "PGX_TEST_TLS_CONN_STRING") + } + + config, err := pgconn.ParseConfigWithSslPasswordCallback(connString, GetSslPassword) + require.Nil(t, err) + + conn, err := pgconn.ConnectConfig(context.Background(), config) require.NoError(t, err) if _, ok := conn.Conn().(*tls.Conn); !ok { From c56b38c1f64d21b05026682c3c6a468ac4a998d3 Mon Sep 17 00:00:00 2001 From: Eric McCormack Date: Fri, 8 Jul 2022 14:24:59 -0400 Subject: [PATCH 2/9] SSL password - changes based on community feedback --- config.go | 27 ++++++++++++++++----------- pgconn.go | 2 -- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/config.go b/config.go index 12a48288..fa9e3801 100644 --- a/config.go +++ b/config.go @@ -26,6 +26,7 @@ import ( type AfterConnectFunc func(ctx context.Context, pgconn *PgConn) error type ValidateConnectFunc func(ctx context.Context, pgconn *PgConn) error +type GetSSLPasswordFunc func(ctx context.Context) string // Config is the settings used to establish a connection to a PostgreSQL server. It must be created by ParseConfig. A // manually initialized Config will cause ConnectConfig to panic. @@ -61,12 +62,14 @@ type Config struct { // OnNotification is a callback function called when a notification from the LISTEN/NOTIFY system is received. OnNotification NotificationHandler - // SslPasswordCallback is a callback function to handle Auth callback for SSL Password - SslPasswordCallback SslPasswordCallbackHandler - createdByParseConfig bool // Used to enforce created by ParseConfig rule. } +//Congig Options such as getsslpassword function +type ParseConfigOptions struct { + GetSSLPassword GetSSLPasswordFunc +} + // Copy returns a deep copy of the config that is safe to use and modify. // The only exception is the TLSConfig field: // according to the tls.Config docs it must not be modified after creation. @@ -138,7 +141,8 @@ func NetworkAddress(host string, port uint16) (network, address string) { // ParseConfig builds a *Config when sslpasswordcallback function is not provided func ParseConfig(connString string) (*Config, error) { - return ParseConfigWithSslPasswordCallback(connString, nil) + var parseConfigOptions ParseConfigOptions + return ParseConfigWithOptions(connString, parseConfigOptions) } // ParseConfig builds a *Config with similar behavior to the PostgreSQL standard C library libpq. It uses the same @@ -204,7 +208,7 @@ func ParseConfig(connString string) (*Config, error) { // which does not use TLS. This can lead to an unexpected unencrypted connection if the main TLS config is manually // changed later but the unencrypted fallback is present. Ensure there are no stale fallbacks when manually setting // TLCConfig. -// sslPasswordCallback function provide a callback function for sslpassword +// ParseConfigOptions options for parse config // // Other known differences with libpq: // @@ -218,7 +222,7 @@ func ParseConfig(connString string) (*Config, error) { // servicefile // libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a // part of the connection string. -func ParseConfigWithSslPasswordCallback(connString string, sslPasswordCallback SslPasswordCallbackHandler) (*Config, error) { +func ParseConfigWithOptions(connString string, parseConfigOptions ParseConfigOptions) (*Config, error) { defaultSettings := defaultSettings() envSettings := parseEnvSettings() @@ -338,7 +342,7 @@ func ParseConfigWithSslPasswordCallback(connString string, sslPasswordCallback S tlsConfigs = append(tlsConfigs, nil) } else { var err error - tlsConfigs, err = configTLS(settings, host, sslPasswordCallback) + tlsConfigs, err = configTLS(settings, host, parseConfigOptions) if err != nil { return nil, &parseConfigError{connString: connString, msg: "failed to configure TLS", err: err} } @@ -605,7 +609,7 @@ func parseServiceSettings(servicefilePath, serviceName string) (map[string]strin // configTLS uses libpq's TLS parameters to construct []*tls.Config. It is // necessary to allow returning multiple TLS configs as sslmode "allow" and // "prefer" allow fallback. -func configTLS(settings map[string]string, thisHost string, sslPasswordCallback SslPasswordCallbackHandler) ([]*tls.Config, error) { +func configTLS(settings map[string]string, thisHost string, parseConfigOptions ParseConfigOptions) ([]*tls.Config, error) { host := thisHost sslmode := settings["sslmode"] sslrootcert := settings["sslrootcert"] @@ -708,10 +712,11 @@ func configTLS(settings map[string]string, thisHost string, sslPasswordCallback // If PEM is encrypted, attempt to decrypt using pass phrase if x509.IsEncryptedPEMBlock(block) { if sslpassword == "" { - if sslPasswordCallback == nil { - return nil, fmt.Errorf("unable to find sslpassword: %w", err) + if(parseConfigOptions.GetSSLPassword != nil){ + sslpassword = parseConfigOptions.GetSSLPassword(context.Background()) + }else{ + return nil, fmt.Errorf("unable to find sslpassword") } - sslpassword = sslPasswordCallback() } // Attempt decryption with pass phrase // NOTE: only supports RSA (PKCS#1) diff --git a/pgconn.go b/pgconn.go index 67d6af38..430f4367 100644 --- a/pgconn.go +++ b/pgconn.go @@ -64,8 +64,6 @@ type NoticeHandler func(*PgConn, *Notice) // notice event. type NotificationHandler func(*PgConn, *Notification) -type SslPasswordCallbackHandler func() (string) - // Frontend used to receive messages from backend. type Frontend interface { Receive() (pgproto3.BackendMessage, error) From 7402796e02f1112dd0a3c972c2f8e0580b762988 Mon Sep 17 00:00:00 2001 From: Eric McCormack Date: Mon, 11 Jul 2022 12:20:10 -0400 Subject: [PATCH 3/9] Delete pgconn.iml --- .idea/pgconn.iml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .idea/pgconn.iml diff --git a/.idea/pgconn.iml b/.idea/pgconn.iml deleted file mode 100644 index 5e764c4f..00000000 --- a/.idea/pgconn.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file From cdd2cc41244843d1aaaf47efa89ff1f8dce6e3c1 Mon Sep 17 00:00:00 2001 From: "yun.xu" Date: Tue, 19 Jul 2022 10:36:38 -0400 Subject: [PATCH 4/9] EC-2198 change for sslpassword --- config.go | 21 ++++++++---- pgconn.go | 12 +++++++ pgconn_test.go | 89 ++++++++++++++++++++++---------------------------- 3 files changed, 66 insertions(+), 56 deletions(-) diff --git a/config.go b/config.go index fa9e3801..2e038304 100644 --- a/config.go +++ b/config.go @@ -709,22 +709,31 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P } block, _ := pem.Decode(buf) var pemKey []byte + var decryptedKey []byte + var decryptedError error // If PEM is encrypted, attempt to decrypt using pass phrase if x509.IsEncryptedPEMBlock(block) { - if sslpassword == "" { + // Attempt decryption with pass phrase + // NOTE: only supports RSA (PKCS#1) + if(sslpassword != ""){ + decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) + } + //if sslpassword not provided or has decryption error when use it + //try to find sslpassword with callback function + if (sslpassword == "" || decryptedError!= nil) { if(parseConfigOptions.GetSSLPassword != nil){ sslpassword = parseConfigOptions.GetSSLPassword(context.Background()) - }else{ + } + if(sslpassword == ""){ return nil, fmt.Errorf("unable to find sslpassword") } } - // Attempt decryption with pass phrase - // NOTE: only supports RSA (PKCS#1) - decryptedKey, err := x509.DecryptPEMBlock(block, []byte(sslpassword)) + decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) // Should we also provide warning for PKCS#1 needed? - if err != nil { + if decryptedError != nil { return nil, fmt.Errorf("unable to decrypt key: %w", err) } + pemBytes := pem.Block{ Type: "RSA PRIVATE KEY", Bytes: decryptedKey, diff --git a/pgconn.go b/pgconn.go index 430f4367..f582f5b8 100644 --- a/pgconn.go +++ b/pgconn.go @@ -109,6 +109,18 @@ func Connect(ctx context.Context, connString string) (*PgConn, error) { return ConnectConfig(ctx, config) } +// Connect establishes a connection to a PostgreSQL server using the environment +// and connString (in URL or DSN format) and ParseConfigOptions +// to provide configuration. See documentation for ParseConfig for details. ctx can be used to cancel a connect attempt. +func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptions ParseConfigOptions) (*PgConn, error) { + config, err := ParseConfigWithOptions(connString, parseConfigOptions) + if err != nil { + return nil, err + } + + return ConnectConfig(ctx, config) +} + // Connect establishes a connection to a PostgreSQL server using config. config must have been constructed with // ParseConfig. ctx can be used to cancel a connect attempt. // diff --git a/pgconn_test.go b/pgconn_test.go index d9adda99..9a52abf6 100644 --- a/pgconn_test.go +++ b/pgconn_test.go @@ -1,7 +1,6 @@ package pgconn_test import ( - "bufio" "bytes" "compress/gzip" "context" @@ -54,6 +53,35 @@ func TestConnect(t *testing.T) { } } +func TestConnectWithOption(t *testing.T) { + tests := []struct { + name string + env string + }{ + {"Unix socket", "PGX_TEST_UNIX_SOCKET_CONN_STRING"}, + {"TCP", "PGX_TEST_TCP_CONN_STRING"}, + {"Plain password", "PGX_TEST_PLAIN_PASSWORD_CONN_STRING"}, + {"MD5 password", "PGX_TEST_MD5_PASSWORD_CONN_STRING"}, + {"SCRAM password", "PGX_TEST_SCRAM_PASSWORD_CONN_STRING"}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + connString := os.Getenv(tt.env) + if connString == "" { + t.Skipf("Skipping due to missing environment variable %v", tt.env) + } + var sslOptions pgconn.ParseConfigOptions + sslOptions.GetSSLPassword = GetSSLPassword + conn, err := pgconn.ConnectWithOptions(context.Background(), connString, sslOptions) + require.NoError(t, err) + + closeConn(t, conn) + }) + } +} + // TestConnectTLS is separate from other connect tests because it has an additional test to ensure it really is a secure // connection. func TestConnectTLS(t *testing.T) { @@ -67,58 +95,14 @@ func TestConnectTLS(t *testing.T) { var conn *pgconn.PgConn var err error - isSslPasswrodEmpty := strings.HasSuffix(connString, "sslpassword=") - - if isSslPasswrodEmpty { - config, err := pgconn.ParseConfigWithSslPasswordCallback(connString, GetSslPassword) - require.Nil(t, err) - - conn, err = pgconn.ConnectConfig(context.Background(), config) - require.NoError(t, err) - } else { - conn, err = pgconn.Connect(context.Background(), connString) - require.NoError(t, err) - } - - if _, ok := conn.Conn().(*tls.Conn); !ok { - t.Error("not a TLS connection") - } - - closeConn(t, conn) -} - -func GetSslPassword() string { - readFile, err := os.Open("data.txt") - if err != nil { - fmt.Println(err) - } - fileScanner := bufio.NewScanner(readFile) - fileScanner.Split(bufio.ScanLines) - for fileScanner.Scan() { - line := fileScanner.Text() - if strings.HasPrefix(line, "sslpassword=") { - index := len("sslpassword=") - line := line[index:] - return line - } - } - return "" -} - -func TestConnectTLSCallback(t *testing.T) { - t.Parallel() - - connString := os.Getenv("PGX_TEST_TLS_CONN_STRING") - if connString == "" { - t.Skipf("Skipping due to missing environment variable %v", "PGX_TEST_TLS_CONN_STRING") - } - - config, err := pgconn.ParseConfigWithSslPasswordCallback(connString, GetSslPassword) + var sslOptions pgconn.ParseConfigOptions + sslOptions.GetSSLPassword = GetSSLPassword + config, err := pgconn.ParseConfigWithOptions(connString, sslOptions) require.Nil(t, err) - conn, err := pgconn.ConnectConfig(context.Background(), config) + conn, err = pgconn.ConnectConfig(context.Background(), config) require.NoError(t, err) - + if _, ok := conn.Conn().(*tls.Conn); !ok { t.Error("not a TLS connection") } @@ -2180,3 +2164,8 @@ func Example() { // 3 // SELECT 3 } + +func GetSSLPassword(ctx context.Context) string { + connString := os.Getenv("PGX_SSL_PASSWORD") + return connString +} \ No newline at end of file From 69b99209fb3add083fca198fe026294def5312a5 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Wed, 20 Jul 2022 06:06:54 -0500 Subject: [PATCH 5/9] Run go fmt --- config.go | 20 ++++++++++---------- defaults.go | 1 + pgconn.go | 4 ++-- pgconn_test.go | 14 +++++++------- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/config.go b/config.go index 2e038304..dd023a96 100644 --- a/config.go +++ b/config.go @@ -67,7 +67,7 @@ type Config struct { //Congig Options such as getsslpassword function type ParseConfigOptions struct { - GetSSLPassword GetSSLPasswordFunc + GetSSLPassword GetSSLPasswordFunc } // Copy returns a deep copy of the config that is safe to use and modify. @@ -715,25 +715,25 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P if x509.IsEncryptedPEMBlock(block) { // Attempt decryption with pass phrase // NOTE: only supports RSA (PKCS#1) - if(sslpassword != ""){ - decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) + if sslpassword != "" { + decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) } //if sslpassword not provided or has decryption error when use it //try to find sslpassword with callback function - if (sslpassword == "" || decryptedError!= nil) { - if(parseConfigOptions.GetSSLPassword != nil){ - sslpassword = parseConfigOptions.GetSSLPassword(context.Background()) + if sslpassword == "" || decryptedError != nil { + if parseConfigOptions.GetSSLPassword != nil { + sslpassword = parseConfigOptions.GetSSLPassword(context.Background()) } - if(sslpassword == ""){ - return nil, fmt.Errorf("unable to find sslpassword") + if sslpassword == "" { + return nil, fmt.Errorf("unable to find sslpassword") } } decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) // Should we also provide warning for PKCS#1 needed? - if decryptedError != nil { + if decryptedError != nil { return nil, fmt.Errorf("unable to decrypt key: %w", err) } - + pemBytes := pem.Block{ Type: "RSA PRIVATE KEY", Bytes: decryptedKey, diff --git a/defaults.go b/defaults.go index f69cad31..c7209fdd 100644 --- a/defaults.go +++ b/defaults.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package pgconn diff --git a/pgconn.go b/pgconn.go index f582f5b8..c817de49 100644 --- a/pgconn.go +++ b/pgconn.go @@ -109,8 +109,8 @@ func Connect(ctx context.Context, connString string) (*PgConn, error) { return ConnectConfig(ctx, config) } -// Connect establishes a connection to a PostgreSQL server using the environment -// and connString (in URL or DSN format) and ParseConfigOptions +// Connect establishes a connection to a PostgreSQL server using the environment +// and connString (in URL or DSN format) and ParseConfigOptions // to provide configuration. See documentation for ParseConfig for details. ctx can be used to cancel a connect attempt. func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptions ParseConfigOptions) (*PgConn, error) { config, err := ParseConfigWithOptions(connString, parseConfigOptions) diff --git a/pgconn_test.go b/pgconn_test.go index 9a52abf6..c08fa54a 100644 --- a/pgconn_test.go +++ b/pgconn_test.go @@ -72,8 +72,8 @@ func TestConnectWithOption(t *testing.T) { if connString == "" { t.Skipf("Skipping due to missing environment variable %v", tt.env) } - var sslOptions pgconn.ParseConfigOptions - sslOptions.GetSSLPassword = GetSSLPassword + var sslOptions pgconn.ParseConfigOptions + sslOptions.GetSSLPassword = GetSSLPassword conn, err := pgconn.ConnectWithOptions(context.Background(), connString, sslOptions) require.NoError(t, err) @@ -97,12 +97,12 @@ func TestConnectTLS(t *testing.T) { var sslOptions pgconn.ParseConfigOptions sslOptions.GetSSLPassword = GetSSLPassword - config, err := pgconn.ParseConfigWithOptions(connString, sslOptions) + config, err := pgconn.ParseConfigWithOptions(connString, sslOptions) require.Nil(t, err) - conn, err = pgconn.ConnectConfig(context.Background(), config) + conn, err = pgconn.ConnectConfig(context.Background(), config) require.NoError(t, err) - + if _, ok := conn.Conn().(*tls.Conn); !ok { t.Error("not a TLS connection") } @@ -2167,5 +2167,5 @@ func Example() { func GetSSLPassword(ctx context.Context) string { connString := os.Getenv("PGX_SSL_PASSWORD") - return connString -} \ No newline at end of file + return connString +} From fe0fb3b24dca7c395038dc81bdb84e6706e50979 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Wed, 20 Jul 2022 06:28:08 -0500 Subject: [PATCH 6/9] Clean up docs for new ParseConfigOptions feature --- config.go | 73 ++++++++++++++++++++++++++------------------------ pgconn.go | 6 ++--- pgconn_test.go | 2 +- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/config.go b/config.go index dd023a96..2277dc1d 100644 --- a/config.go +++ b/config.go @@ -65,8 +65,10 @@ type Config struct { createdByParseConfig bool // Used to enforce created by ParseConfig rule. } -//Congig Options such as getsslpassword function +// ParseConfigOptions contains options that control how a config is built such as getsslpassword. type ParseConfigOptions struct { + // GetSSLPassword gets the password to decrypt a SSL client certificate. This is analogous to the the libpq function + // PQsetSSLKeyPassHook_OpenSSL. GetSSLPassword GetSSLPasswordFunc } @@ -139,16 +141,10 @@ func NetworkAddress(host string, port uint16) (network, address string) { return network, address } -// ParseConfig builds a *Config when sslpasswordcallback function is not provided -func ParseConfig(connString string) (*Config, error) { - var parseConfigOptions ParseConfigOptions - return ParseConfigWithOptions(connString, parseConfigOptions) -} - -// ParseConfig builds a *Config with similar behavior to the PostgreSQL standard C library libpq. It uses the same -// defaults as libpq (e.g. port=5432) and understands most PG* environment variables. ParseConfig closely matches -// the parsing behavior of libpq. connString may either be in URL format or keyword = value format (DSN style). See -// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for details. connString also may be +// ParseConfig builds a *Config from connString with similar behavior to the PostgreSQL standard C library libpq. It +// uses the same defaults as libpq (e.g. port=5432) and understands most PG* environment variables. ParseConfig closely +// matches the parsing behavior of libpq. connString may either be in URL format or keyword = value format (DSN style). +// See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for details. connString also may be // empty to only read from the environment. If a password is not supplied it will attempt to read the .pgpass file. // // # Example DSN @@ -172,22 +168,22 @@ func ParseConfig(connString string) (*Config, error) { // ParseConfig currently recognizes the following environment variable and their parameter key word equivalents passed // via database URL or DSN: // -// PGHOST -// PGPORT -// PGDATABASE -// PGUSER -// PGPASSWORD -// PGPASSFILE -// PGSERVICE -// PGSERVICEFILE -// PGSSLMODE -// PGSSLCERT -// PGSSLKEY -// PGSSLROOTCERT +// PGHOST +// PGPORT +// PGDATABASE +// PGUSER +// PGPASSWORD +// PGPASSFILE +// PGSERVICE +// PGSERVICEFILE +// PGSSLMODE +// PGSSLCERT +// PGSSLKEY +// PGSSLROOTCERT // PGSSLPASSWORD -// PGAPPNAME -// PGCONNECT_TIMEOUT -// PGTARGETSESSIONATTRS +// PGAPPNAME +// PGCONNECT_TIMEOUT +// PGTARGETSESSIONATTRS // // See http://www.postgresql.org/docs/11/static/libpq-envars.html for details on the meaning of environment variables. // @@ -207,8 +203,7 @@ func ParseConfig(connString string) (*Config, error) { // sslmode "prefer" this means it will first try the main Config settings which use TLS, then it will try the fallback // which does not use TLS. This can lead to an unexpected unencrypted connection if the main TLS config is manually // changed later but the unencrypted fallback is present. Ensure there are no stale fallbacks when manually setting -// TLCConfig. -// ParseConfigOptions options for parse config +// TLSConfig. // // Other known differences with libpq: // @@ -217,12 +212,20 @@ func ParseConfig(connString string) (*Config, error) { // // In addition, ParseConfig accepts the following options: // -// min_read_buffer_size -// The minimum size of the internal read buffer. Default 8192. -// servicefile -// libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a -// part of the connection string. -func ParseConfigWithOptions(connString string, parseConfigOptions ParseConfigOptions) (*Config, error) { +// min_read_buffer_size +// The minimum size of the internal read buffer. Default 8192. +// servicefile +// libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a +// part of the connection string. +func ParseConfig(connString string) (*Config, error) { + var parseConfigOptions ParseConfigOptions + return ParseConfigWithOptions(connString, parseConfigOptions) +} + +// ParseConfigWithOptions builds a *Config from connString and options with similar behavior to the PostgreSQL standard +// C library libpq. options contains settings that cannot be specified in a connString such as providing a function to +// get the SSL password. +func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Config, error) { defaultSettings := defaultSettings() envSettings := parseEnvSettings() @@ -342,7 +345,7 @@ func ParseConfigWithOptions(connString string, parseConfigOptions ParseConfigOpt tlsConfigs = append(tlsConfigs, nil) } else { var err error - tlsConfigs, err = configTLS(settings, host, parseConfigOptions) + tlsConfigs, err = configTLS(settings, host, options) if err != nil { return nil, &parseConfigError{connString: connString, msg: "failed to configure TLS", err: err} } diff --git a/pgconn.go b/pgconn.go index c817de49..17f19e95 100644 --- a/pgconn.go +++ b/pgconn.go @@ -109,9 +109,9 @@ func Connect(ctx context.Context, connString string) (*PgConn, error) { return ConnectConfig(ctx, config) } -// Connect establishes a connection to a PostgreSQL server using the environment -// and connString (in URL or DSN format) and ParseConfigOptions -// to provide configuration. See documentation for ParseConfig for details. ctx can be used to cancel a connect attempt. +// Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or DSN format) +// and ParseConfigOptions to provide additional configuration. See documentation for ParseConfig for details. ctx can be +// used to cancel a connect attempt. func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptions ParseConfigOptions) (*PgConn, error) { config, err := ParseConfigWithOptions(connString, parseConfigOptions) if err != nil { diff --git a/pgconn_test.go b/pgconn_test.go index c08fa54a..a4f0ec63 100644 --- a/pgconn_test.go +++ b/pgconn_test.go @@ -53,7 +53,7 @@ func TestConnect(t *testing.T) { } } -func TestConnectWithOption(t *testing.T) { +func TestConnectWithOptions(t *testing.T) { tests := []struct { name string env string From 0a539a9d92131c8353435393670f4d10b8b36ebb Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 6 Aug 2022 05:58:55 -0500 Subject: [PATCH 7/9] Upgrade pgproto3 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index aaf7a486..a2eeecff 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/jackc/pgio v1.0.0 github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 github.com/jackc/pgpassfile v1.0.0 - github.com/jackc/pgproto3/v2 v2.3.0 + github.com/jackc/pgproto3/v2 v2.3.1 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 diff --git a/go.sum b/go.sum index a3834fd2..95b6bbca 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/jackc/pgproto3/v2 v2.2.1-0.20220412121321-175856ffd3c8 h1:KxsCQec+1iw github.com/jackc/pgproto3/v2 v2.2.1-0.20220412121321-175856ffd3c8/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y= github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= +github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= From 5192d9acc15feea530a46d397effe01a44d4647c Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 6 Aug 2022 06:00:03 -0500 Subject: [PATCH 8/9] Upgrade 3rd party dependencies --- go.mod | 4 ++-- go.sum | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a2eeecff..a1bec1f4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/jackc/pgpassfile v1.0.0 github.com/jackc/pgproto3/v2 v2.3.1 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b - github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 + github.com/stretchr/testify v1.8.0 + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa golang.org/x/text v0.3.7 ) diff --git a/go.sum b/go.sum index 95b6bbca..e89d701d 100644 --- a/go.sum +++ b/go.sum @@ -75,12 +75,17 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -95,11 +100,14 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -110,6 +118,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -134,3 +143,5 @@ gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:a gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 4c048d40d859bbb702bff9b46cfefd44b89d82c1 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 6 Aug 2022 06:07:40 -0500 Subject: [PATCH 9/9] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3efb7f2..f6a6807f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.13.0 (August 6, 2022) + +* Add sslpassword support (Eric McCormack and yun.xu) +* Add prefer-standby target_session_attrs support (sergey.bashilov) +* Fix GSS ErrorResponse handling (Oliver Tan) + # 1.12.1 (May 7, 2022) * Fix: setting krbspn and krbsrvname in connection string (sireax)