redact passwords in parse config errors

Redact passwords when printing the parseConfigError in a best effort manner.
This prevents people from leaking the password into logs, if they just print the error in logs.
query-exec-mode
Lukas Vogel 2020-06-12 13:01:57 +02:00
parent a1b9eb4d4e
commit f27e874d55
3 changed files with 83 additions and 2 deletions

View File

@ -4,6 +4,8 @@ import (
"context"
"fmt"
"net"
"net/url"
"regexp"
"strings"
errors "golang.org/x/xerrors"
@ -98,10 +100,11 @@ type parseConfigError struct {
}
func (e *parseConfigError) Error() string {
connString := redactPW(e.connString)
if e.err == nil {
return fmt.Sprintf("cannot parse `%s`: %s", e.connString, e.msg)
return fmt.Sprintf("cannot parse `%s`: %s", connString, e.msg)
}
return fmt.Sprintf("cannot parse `%s`: %s (%s)", e.connString, e.msg, e.err.Error())
return fmt.Sprintf("cannot parse `%s`: %s (%s)", connString, e.msg, e.err.Error())
}
func (e *parseConfigError) Unwrap() error {
@ -164,3 +167,26 @@ func (e *writeError) SafeToRetry() bool {
func (e *writeError) Unwrap() error {
return e.err
}
func redactPW(connString string) string {
if strings.HasPrefix(connString, "postgres://") || strings.HasPrefix(connString, "postgresql://") {
if u, err := url.Parse(connString); err == nil {
return redactURL(u)
}
}
quotedDSN := regexp.MustCompile(`password='[^']*'`)
connString = quotedDSN.ReplaceAllLiteralString(connString, "password=xxxxx")
plainDSN := regexp.MustCompile(`password=[^ ]*`)
connString = plainDSN.ReplaceAllLiteralString(connString, "password=xxxxx")
return connString
}
func redactURL(u *url.URL) string {
if u == nil {
return ""
}
if _, pwSet := u.User.Password(); pwSet {
u.User = url.UserPassword(u.User.Username(), "xxxxx")
}
return u.String()
}

44
errors_test.go Normal file
View File

@ -0,0 +1,44 @@
package pgconn_test
import (
"testing"
"github.com/jackc/pgconn"
"github.com/stretchr/testify/assert"
)
func TestConfigError(t *testing.T) {
tests := []struct {
name string
err error
expectedMsg string
}{
{
name: "url with password",
err: pgconn.NewParseConfigError("postgresql://foo:password@host", "msg", nil),
expectedMsg: "cannot parse `postgresql://foo:xxxxx@host`: msg",
},
{
name: "dsn with password unquoted",
err: pgconn.NewParseConfigError("host=host password=password user=user", "msg", nil),
expectedMsg: "cannot parse `host=host password=xxxxx user=user`: msg",
},
{
name: "dsn with password quoted",
err: pgconn.NewParseConfigError("host=host password='pass word' user=user", "msg", nil),
expectedMsg: "cannot parse `host=host password=xxxxx user=user`: msg",
},
{
name: "weird url",
err: pgconn.NewParseConfigError("postgresql://foo::pasword@host:1:", "msg", nil),
expectedMsg: "cannot parse `postgresql://foo:xxxxx@host:1:`: msg",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.EqualError(t, tt.err, tt.expectedMsg)
})
}
}

11
export_test.go Normal file
View File

@ -0,0 +1,11 @@
// File export_test exports some methods for better testing.
package pgconn
func NewParseConfigError(conn, msg string, err error) error {
return &parseConfigError{
connString: conn,
msg: msg,
err: err,
}
}