diff --git a/config/database.go b/config/database.go index 15a8b64134..4c61375007 100644 --- a/config/database.go +++ b/config/database.go @@ -8,6 +8,7 @@ import ( "database/sql" "io/ioutil" "net/url" + "regexp" "strings" "github.com/jmoiron/sqlx" @@ -22,6 +23,8 @@ import ( _ "github.com/lib/pq" ) +var tcpStripper = regexp.MustCompile(`@tcp\((.*)\)`) + // DatabaseStore is a config store backed by a database. type DatabaseStore struct { commonStore @@ -101,23 +104,22 @@ func initializeConfigurationsTable(db *sqlx.DB) error { // By contrast, a Postgres DSN is returned unmodified. func parseDSN(dsn string) (string, string, error) { // Treat the DSN as the URL that it is. - u, err := url.Parse(dsn) - if err != nil { - return "", "", errors.Wrap(err, "failed to parse DSN as URL") + s := strings.SplitN(dsn, "://", 2) + if len(s) != 2 { + errors.New("failed to parse DSN as URL") } - scheme := u.Scheme + scheme := s[0] switch scheme { case "mysql": // Strip off the mysql:// for the dsn with which to connect. - u.Scheme = "" - dsn = strings.TrimPrefix(u.String(), "//") + dsn = s[1] case "postgres": // No changes required default: - return "", "", errors.Wrapf(err, "unsupported scheme %s", scheme) + return "", "", errors.Errorf("unsupported scheme %s", scheme) } return scheme, dsn, nil @@ -293,7 +295,11 @@ func (ds *DatabaseStore) RemoveFile(name string) error { // String returns the path to the database backing the config, masking the password. func (ds *DatabaseStore) String() string { - u, _ := url.Parse(ds.originalDsn) + // Remove @tcp and the parentheses from the host and parse the rest as a URL + u, err := url.Parse(tcpStripper.ReplaceAllString(ds.originalDsn, `@$1`)) + if err != nil { + return "(omitted due to error parsing the DSN)" + } // Strip out the password to avoid leaking in logs. u.User = url.User(u.User.Username()) diff --git a/config/database_test.go b/config/database_test.go index 106152e889..6119fc2305 100644 --- a/config/database_test.go +++ b/config/database_test.go @@ -6,7 +6,6 @@ package config_test import ( "bytes" "fmt" - "net/url" "os" "strings" "testing" @@ -137,6 +136,11 @@ func TestDatabaseStoreNew(t *testing.T) { _, err := config.NewDatabaseStore("invalid") require.Error(t, err) }) + + t.Run("unsupported scheme with valid data source", func(t *testing.T) { + _, err := config.NewDatabaseStore(fmt.Sprintf("invalid://%s", *sqlSettings.DataSource)) + require.Error(t, err) + }) } func TestDatabaseStoreGet(t *testing.T) { @@ -930,14 +934,11 @@ func TestDatabaseStoreString(t *testing.T) { sqlSettings := mainHelper.GetSqlSettings() ds, err := config.NewDatabaseStore(fmt.Sprintf("%s://%s", *sqlSettings.DriverName, *sqlSettings.DataSource)) require.NoError(t, err) + require.NotNil(t, ds) defer ds.Close() - actualStringURL, err := url.Parse(ds.String()) - require.NoError(t, err) - - assert.Equal(t, *sqlSettings.DriverName, actualStringURL.Scheme) - actualUsername := actualStringURL.User.Username() - actualPassword, _ := actualStringURL.User.Password() - assert.NotEmpty(t, actualUsername) - assert.Empty(t, actualPassword, "should mask password") + maskedDSN := ds.String() + assert.True(t, strings.HasPrefix(maskedDSN, "mysql://")) + assert.True(t, strings.Contains(maskedDSN, "mmuser")) + assert.False(t, strings.Contains(maskedDSN, "mostest")) }