Skip to content

Commit

Permalink
Fix MySQL Plugin password special character escape bug (#8040)
Browse files Browse the repository at this point in the history
* Fix MySQL password escape bug

* Add test

* Add debug output

* Add debug line

* Added debug output

* Debug

* Debug

* Update vendor

* Remove debug comments
  • Loading branch information
michelvocks committed Jan 7, 2020
1 parent 31bbb81 commit f813caa
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 11 deletions.
47 changes: 38 additions & 9 deletions plugins/database/mysql/mysql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

var _ dbplugin.Database = (*MySQL)(nil)

func prepareMySQLTestContainer(t *testing.T, legacy bool) (cleanup func(), retURL string) {
func prepareMySQLTestContainer(t *testing.T, legacy bool, pw string) (cleanup func(), retURL string) {
if os.Getenv("MYSQL_URL") != "" {
return func() {}, os.Getenv("MYSQL_URL")
}
Expand All @@ -35,7 +35,7 @@ func prepareMySQLTestContainer(t *testing.T, legacy bool) (cleanup func(), retUR
imageVersion = "5.6"
}

resource, err := pool.Run("mysql", imageVersion, []string{"MYSQL_ROOT_PASSWORD=secret"})
resource, err := pool.Run("mysql", imageVersion, []string{"MYSQL_ROOT_PASSWORD=" + pw})
if err != nil {
t.Fatalf("Could not start local MySQL docker container: %s", err)
}
Expand All @@ -44,7 +44,7 @@ func prepareMySQLTestContainer(t *testing.T, legacy bool) (cleanup func(), retUR
docker.CleanupResource(t, pool, resource)
}

retURL = fmt.Sprintf("root:secret@(localhost:%s)/mysql?parseTime=true", resource.GetPort("3306/tcp"))
retURL = fmt.Sprintf("root:%s@(localhost:%s)/mysql?parseTime=true", pw, resource.GetPort("3306/tcp"))

// exponential backoff-retry
if err = pool.Retry(func() error {
Expand All @@ -65,7 +65,7 @@ func prepareMySQLTestContainer(t *testing.T, legacy bool) (cleanup func(), retUR
}

func TestMySQL_Initialize(t *testing.T) {
cleanup, connURL := prepareMySQLTestContainer(t, false)
cleanup, connURL := prepareMySQLTestContainer(t, false, "secret")
defer cleanup()

connectionDetails := map[string]interface{}{
Expand Down Expand Up @@ -100,7 +100,7 @@ func TestMySQL_Initialize(t *testing.T) {
}

func TestMySQL_CreateUser(t *testing.T) {
cleanup, connURL := prepareMySQLTestContainer(t, false)
cleanup, connURL := prepareMySQLTestContainer(t, false, "secret")
defer cleanup()

connectionDetails := map[string]interface{}{
Expand Down Expand Up @@ -162,7 +162,7 @@ func TestMySQL_CreateUser(t *testing.T) {
}

func TestMySQL_CreateUser_Legacy(t *testing.T) {
cleanup, connURL := prepareMySQLTestContainer(t, true)
cleanup, connURL := prepareMySQLTestContainer(t, true, "secret")
defer cleanup()

connectionDetails := map[string]interface{}{
Expand Down Expand Up @@ -211,7 +211,7 @@ func TestMySQL_CreateUser_Legacy(t *testing.T) {
}

func TestMySQL_RotateRootCredentials(t *testing.T) {
cleanup, connURL := prepareMySQLTestContainer(t, false)
cleanup, connURL := prepareMySQLTestContainer(t, false, "secret")
defer cleanup()

connURL = strings.Replace(connURL, "root:secret", `{{username}}:{{password}}`, -1)
Expand Down Expand Up @@ -247,7 +247,7 @@ func TestMySQL_RotateRootCredentials(t *testing.T) {
}

func TestMySQL_RevokeUser(t *testing.T) {
cleanup, connURL := prepareMySQLTestContainer(t, false)
cleanup, connURL := prepareMySQLTestContainer(t, false, "secret")
defer cleanup()

connectionDetails := map[string]interface{}{
Expand Down Expand Up @@ -311,7 +311,7 @@ func TestMySQL_RevokeUser(t *testing.T) {
}

func TestMySQL_SetCredentials(t *testing.T) {
cleanup, connURL := prepareMySQLTestContainer(t, false)
cleanup, connURL := prepareMySQLTestContainer(t, false, "secret")
defer cleanup()

// create the database user and verify we can access
Expand Down Expand Up @@ -368,6 +368,35 @@ func TestMySQL_SetCredentials(t *testing.T) {
}
}

func TestMySQL_Initialize_ReservedChars(t *testing.T) {
pw := "#secret!%25#{@}"
cleanup, connURL := prepareMySQLTestContainer(t, false, pw)
defer cleanup()

// Revert password set to test replacement by db.Init
connURL = strings.ReplaceAll(connURL, pw, "{{password}}")

connectionDetails := map[string]interface{}{
"connection_url": connURL,
"password": pw,
}

db := new(MetadataLen, MetadataLen, UsernameLen)
_, err := db.Init(context.Background(), connectionDetails, true)
if err != nil {
t.Fatalf("err: %s", err)
}

if !db.Initialized {
t.Fatal("Database should be initialized")
}

err = db.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
}

func testCredsExist(t testing.TB, connURL, username, password string) error {
// Log in with the new creds
connURL = strings.Replace(connURL, "root:secret", fmt.Sprintf("%s:%s", username, password), 1)
Expand Down
8 changes: 7 additions & 1 deletion sdk/database/helper/connutil/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,17 @@ func (c *SQLConnectionProducer) Init(ctx context.Context, conf map[string]interf
return nil, fmt.Errorf("connection_url cannot be empty")
}

// Don't escape special characters for MySQL password
password := c.Password
if c.Type != "mysql" {
password = url.PathEscape(c.Password)
}

// QueryHelper doesn't do any SQL escaping, but if it starts to do so
// then maybe we won't be able to use it to do URL substitution any more.
c.ConnectionURL = dbutil.QueryHelper(c.ConnectionURL, map[string]string{
"username": url.PathEscape(c.Username),
"password": url.PathEscape(c.Password),
"password": password,
})

if c.MaxOpenConnections == 0 {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f813caa

Please sign in to comment.