diff --git a/plugins/database/mysql/mysql.go b/plugins/database/mysql/mysql.go index 297941c94b7c..87289274e0ee 100644 --- a/plugins/database/mysql/mysql.go +++ b/plugins/database/mysql/mysql.go @@ -5,7 +5,7 @@ import ( "strings" "time" - _ "github.com/go-sql-driver/mysql" + stdmysql "github.com/go-sql-driver/mysql" "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/builtin/logical/database/dbplugin" "github.com/hashicorp/vault/helper/strutil" @@ -140,13 +140,28 @@ func (m *MySQL) CreateUser(statements dbplugin.Statements, usernameConfig dbplug if len(query) == 0 { continue } - - stmt, err := tx.Prepare(dbutil.QueryHelper(query, map[string]string{ + query = dbutil.QueryHelper(query, map[string]string{ "name": username, "password": password, "expiration": expirationStr, - })) + }) + + stmt, err := tx.Prepare(query) if err != nil { + // If the error code we get back is Error 1295: This command is not + // supported in the prepared statement protocol yet, we will execute + // the statement without preparing it. This allows the caller to + // manually prepare statements, as well as run other not yet + // prepare supported commands. If there is no error when running we + // will continue to the next statement. + if e, ok := err.(*stdmysql.MySQLError); ok && e.Number == 1295 { + _, err = tx.Exec(query) + if err != nil { + return "", "", err + } + continue + } + return "", "", err } defer stmt.Close() diff --git a/plugins/database/mysql/mysql_test.go b/plugins/database/mysql/mysql_test.go index 851bd02da17c..203158b461a1 100644 --- a/plugins/database/mysql/mysql_test.go +++ b/plugins/database/mysql/mysql_test.go @@ -184,6 +184,19 @@ func TestMySQL_CreateUser(t *testing.T) { if err := testCredsExist(t, connURL, username, password); err != nil { t.Fatalf("Could not connect with new credentials: %s", err) } + + // Test with a manualy prepare statement + statements.CreationStatements = testMySQLRolePreparedStmt + + username, password, err = db.CreateUser(statements, usernameConfig, time.Now().Add(time.Minute)) + if err != nil { + t.Fatalf("err: %s", err) + } + + if err := testCredsExist(t, connURL, username, password); err != nil { + t.Fatalf("Could not connect with new credentials: %s", err) + } + } func TestMySQL_CreateUser_Legacy(t *testing.T) { @@ -316,6 +329,13 @@ func testCredsExist(t testing.TB, connURL, username, password string) error { return db.Ping() } +const testMySQLRolePreparedStmt = ` +CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; +set @grants=CONCAT("GRANT SELECT ON ", "*", ".* TO '{{name}}'@'%'"); +PREPARE grantStmt from @grants; +EXECUTE grantStmt; +DEALLOCATE PREPARE grantStmt; +` const testMySQLRoleWildCard = ` CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON *.* TO '{{name}}'@'%';