Skip to content

Commit ee52475

Browse files
Nelson Goncalvesrennox
authored andcommitted
BUG#26979375 : ADMIN API COMMANDS FAILING WHEN THE USER CONNECTED TO ROOT@%
This patch fixes an issue with the account validation being done on the check_instance_configuration method that did not work correctly unless the session account existed. With this patch, that validation is now done using the account that was authenticated by the server, i.e., the account returned by the SELECT CURRENT_USER() SQL query. Furthermore, it now also makes sure that the correct account is used by the MP backend on the check command. New tests were added to verify the fix.
1 parent c92b07b commit ee52475

19 files changed

+219
-139
lines changed

modules/adminapi/mod_dba.cc

Lines changed: 100 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919

2020
#include <string>
2121
#include <random>
22+
#include <mysqld_error.h>
23+
24+
2225
#ifndef WIN32
2326
#include <sys/un.h>
2427
#endif
@@ -1588,6 +1591,12 @@ shcore::Value::Map_type_ref Dba::_check_instance_configuration(const shcore::Arg
15881591
shcore::Value::Map_type_ref validate_options;
15891592

15901593
std::string cnfpath, cluster_admin, cluster_admin_password;
1594+
std::string admin_user, admin_user_host, validate_user, validate_host;
1595+
std::string current_user, current_host, account_type, error_msg_extra;
1596+
std::string uri_user = instance_def->get_string(
1597+
instance_def->has_key("user") ? "user" : "dbUser");
1598+
std::string uri_host = instance_def->get_string("host");
1599+
bool cluster_admin_user_exists = false;
15911600
bool clear_read_only = false;
15921601

15931602
if (args.size() == 2) {
@@ -1632,18 +1641,80 @@ shcore::Value::Map_type_ref Dba::_check_instance_configuration(const shcore::Arg
16321641
new_args.push_back(shcore::Value(instance_def));
16331642
auto session = Dba::get_session(new_args);
16341643

1644+
// get the current user
1645+
auto result = session->execute_sql("SELECT CURRENT_USER()");
1646+
auto row = result->fetch_one();
1647+
std::string current_account = row->get_value(0).as_string();
1648+
split_account(current_account, &current_user, &current_host);
1649+
1650+
// if this is a configureLocalInstance operation and the clusterAdmin
1651+
// option was used and that user exists, validate its privileges, otherwise
1652+
// validate the privileges of the current user
1653+
if (allow_update && !cluster_admin.empty()) {
1654+
shcore::split_account(cluster_admin, &admin_user, &admin_user_host);
1655+
// Host '%' is used by default if not provided in the user account.
1656+
if (admin_user_host.empty())
1657+
admin_user_host = "%";
1658+
// Check if the cluster_admin user exists
1659+
shcore::sqlstring query = shcore::sqlstring(
1660+
"SELECT COUNT(*) from mysql.user WHERE "
1661+
"User = ? AND Host = ?", 0);
1662+
query << admin_user << admin_user_host;
1663+
query.done();
1664+
try {
1665+
auto result = session->execute_sql(query);
1666+
cluster_admin_user_exists =
1667+
result->fetch_one()->get_value(0).as_int() != 0;
1668+
} catch (shcore::Exception &err) {
1669+
if (err.code() == ER_TABLEACCESS_DENIED_ERROR) {
1670+
// the current_account doesn't have enough privileges to execute
1671+
// the query
1672+
std::string error_msg;
1673+
if (uri_host != current_host || uri_user != current_user)
1674+
error_msg =
1675+
"Session account '" + current_user + "'@'" + current_host +
1676+
"'(used to authenticate '" + uri_user + "'@'" + uri_host +
1677+
"') does not have all the required privileges to execute this "
1678+
"operation. For more information, see the online documentation.";
1679+
else
1680+
error_msg =
1681+
"Session account '" + current_user + "'@'" + current_host +
1682+
"' does not have all the required privileges to execute this "
1683+
"operation. For more information, see the online documentation.";
1684+
log_error("%s", error_msg.c_str());
1685+
throw std::runtime_error(error_msg);
1686+
} else {
1687+
throw;
1688+
}
1689+
}
1690+
}
1691+
if (cluster_admin_user_exists) {
1692+
// cluster admin account exists, so we will validate its privileges
1693+
validate_user = admin_user;
1694+
validate_host = admin_user_host;
1695+
account_type = "Cluster Admin";
1696+
} else {
1697+
// cluster admin doesn't exist, so we validate the privileges of the
1698+
// current user
1699+
validate_user = current_user;
1700+
validate_host = current_host;
1701+
account_type = "Session";
1702+
if (uri_host != current_host || uri_user != current_user)
1703+
error_msg_extra =
1704+
" (used to authenticate '" + uri_user + "'@'" + uri_host + "')";
1705+
}
16351706
// Validate the permissions of the user running the operation.
1636-
if (!validate_cluster_admin_user_privileges(session, session->get_user(),
1637-
session->get_host())) {
1707+
if (!validate_cluster_admin_user_privileges(session, validate_user,
1708+
validate_host)) {
16381709
std::string error_msg =
1639-
"Account '" + session->get_user() + "'@'" +
1640-
session->get_host() +
1641-
"' does not have all the required privileges to execute this "
1642-
"operation. For more information, see the online documentation.";
1710+
account_type + " account '" + validate_user + "'@'" + validate_host +
1711+
"'" + error_msg_extra +
1712+
" does not have all the required privileges to "
1713+
"execute this operation. For more information, see the online "
1714+
"documentation.";
16431715
log_error("%s", error_msg.c_str());
16441716
throw std::runtime_error(error_msg);
16451717
}
1646-
16471718
// Now validates the instance GR status itself
16481719
std::string uri = session->uri();
16491720

@@ -1658,8 +1729,7 @@ shcore::Value::Map_type_ref Dba::_check_instance_configuration(const shcore::Arg
16581729
else if (type == GRInstanceType::InnoDBCluster && !allow_update) {
16591730
session->close(shcore::Argument_list());
16601731
throw shcore::Exception::runtime_error("The instance '" + uri + "' is already part of an InnoDB Cluster");
1661-
}
1662-
else {
1732+
} else {
16631733
if (!cluster_admin.empty() && allow_update) {
16641734
auto classic =
16651735
dynamic_cast<mysqlsh::mysql::ClassicSession*>(session.get());
@@ -1669,39 +1739,16 @@ shcore::Value::Map_type_ref Dba::_check_instance_configuration(const shcore::Arg
16691739
// and right before some execution failure of the command leaving the
16701740
// instance in an incorrect state
16711741
bool super_read_only = validate_super_read_only(classic, clear_read_only);
1672-
1673-
try {
1674-
create_cluster_admin_user(session,
1675-
cluster_admin,
1676-
cluster_admin_password);
1677-
} catch (shcore::Exception &err) {
1678-
// Catch ER_CANNOT_USER (1396) if the user already exists, and skip it
1679-
// if the user has the needed privileges.
1680-
if (err.code() == ER_CANNOT_USER) {
1681-
std::string admin_user, admin_user_host;
1682-
shcore::split_account(cluster_admin, &admin_user, &admin_user_host);
1683-
// Host '%' is used by default if not provided in the user account.
1684-
if (admin_user_host.empty())
1685-
admin_user_host = "%";
1686-
if (!validate_cluster_admin_user_privileges(session, admin_user,
1687-
admin_user_host)) {
1688-
std::string error_msg =
1689-
"User " + cluster_admin + " already exists but it does not "
1690-
"have all the privileges for managing an InnoDB cluster. "
1691-
"Please provide a non-existing user to be created or a "
1692-
"different one with all the required privileges.";
1693-
errors->push_back(shcore::Value(error_msg));
1694-
log_error("%s", error_msg.c_str());
1695-
} else {
1696-
log_warning("User %s already exists.", cluster_admin.c_str());
1697-
}
1698-
} else {
1742+
if (!cluster_admin_user_exists) {
1743+
try {
1744+
create_cluster_admin_user(session, cluster_admin,
1745+
cluster_admin_password);
1746+
} catch (shcore::Exception &err) {
16991747
std::string error_msg = "Unexpected error creating " + cluster_admin +
17001748
" user: " + err.what();
17011749
errors->push_back(shcore::Value(error_msg));
17021750
log_error("%s", error_msg.c_str());
17031751
}
1704-
17051752
// If we disabled super_read_only we must enable it back
17061753
// also confirm that the initial status was 1/ON
17071754
if (clear_read_only && super_read_only) {
@@ -1710,12 +1757,12 @@ shcore::Value::Map_type_ref Dba::_check_instance_configuration(const shcore::Arg
17101757
session_address.c_str());
17111758
set_global_variable(classic->connection(), "super_read_only", "ON");
17121759
}
1760+
} else {
1761+
log_warning("User %s already exists.", cluster_admin.c_str());
17131762
}
17141763
}
1715-
1716-
std::string host = instance_def->get_string("host");
17171764
int port = instance_def->get_int("port");
1718-
std::string endpoint = host + ":" + std::to_string(port);
1765+
std::string endpoint = uri_host + ":" + std::to_string(port);
17191766

17201767
if (type == GRInstanceType::InnoDBCluster) {
17211768
auto seeds = get_peer_seeds(session->connection(), endpoint);
@@ -1725,8 +1772,16 @@ shcore::Value::Map_type_ref Dba::_check_instance_configuration(const shcore::Arg
17251772

17261773
std::string user;
17271774
std::string password;
1728-
user = instance_def->get_string(instance_def->has_key("user") ? "user" : "dbUser");
1729-
password = instance_def->get_string(instance_def->has_key("password") ? "password" : "dbPassword");
1775+
// If a clusterAdmin account was specified, use it, otherwise user the
1776+
// user of the instance.
1777+
if (!cluster_admin.empty()) {
1778+
user = admin_user;
1779+
password = cluster_admin_password;
1780+
} else {
1781+
user = uri_user;
1782+
password = instance_def->get_string(
1783+
instance_def->has_key("password") ? "password" : "dbPassword");
1784+
}
17301785

17311786
// Get SSL values to connect to instance
17321787
Value::Map_type_ref instance_ssl_opts(new shcore::Value::Map_type);
@@ -1740,7 +1795,9 @@ shcore::Value::Map_type_ref Dba::_check_instance_configuration(const shcore::Arg
17401795
// Verbose is mandatory for checkInstanceConfiguration
17411796
shcore::Value::Array_type_ref mp_errors;
17421797

1743-
if ((_provisioning_interface->check(user, host, port, password, instance_ssl_opts, cnfpath, allow_update, mp_errors) == 0)) {
1798+
if ((_provisioning_interface->check(user, uri_host, port, password,
1799+
instance_ssl_opts, cnfpath,
1800+
allow_update, mp_errors) == 0)) {
17441801
// Only return status "ok" if no previous errors were found.
17451802
if (errors->size() == 0) {
17461803
(*ret_val)["status"] = shcore::Value("ok");

modules/adminapi/mod_dba.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
#include "mod_dba_provisioning_interface.h"
3232
#include "modules/adminapi/mod_dba_common.h"
3333

34-
#define ER_CANNOT_USER 1396
35-
3634
namespace mysqlsh {
3735
namespace dba {
3836
/**

modules/adminapi/mod_dba_common.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ static const std::set<std::string> k_metadata_schema_privileges{
534534
// list of (schema, [privilege]) pairs, with the required privileges on each schema
535535
static const std::map<std::string, std::set<std::string>> k_schema_grants{
536536
{"mysql_innodb_cluster_metadata", k_metadata_schema_privileges},
537-
{"mysql", {"SELECT", "INSERT", "UPDATE", "DELETE"}} // need for mysql.plugin, mysql.user others
537+
{"mysql", {"INSERT", "UPDATE", "DELETE"}} // need for mysql.plugin, mysql.user others
538538
};
539539

540540
/** Check that the provided account has privileges to manage a cluster.
@@ -628,7 +628,7 @@ static const char *k_admin_user_grants[] = {
628628
"GRANT RELOAD, SHUTDOWN, PROCESS, FILE, SUPER, REPLICATION SLAVE, REPLICATION CLIENT, CREATE USER ON *.*",
629629
"GRANT ALL PRIVILEGES ON mysql_innodb_cluster_metadata.*",
630630
"GRANT SELECT ON *.*",
631-
"GRANT SELECT, INSERT, UPDATE, DELETE ON mysql.*"
631+
"GRANT INSERT, UPDATE, DELETE ON mysql.*"
632632
};
633633

634634
void create_cluster_admin_user(std::shared_ptr<mysqlsh::mysql::ClassicSession> session,

unittest/scripts/js_devapi/scripts/dba_check_instance_configuration_session.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,27 @@ session.runSql("GRANT ALL PRIVILEGES ON *.* to 'test_user'@'%' WITH GRANT OPTION
1818
session.runSql("REVOKE SELECT on *.* FROM 'test_user'@'%'");
1919
session.runSql("SET sql_log_bin = 1");
2020
dba.checkInstanceConfiguration({host: localhost, port: __mysql_sandbox_port1, user: 'test_user', password:''});
21+
session.close()
22+
23+
//@ Check instance configuration using a non existing user that authenticates as another user that does not have enough privileges (BUG#26979375)
24+
shell.connect({host: hostname, port: __mysql_sandbox_port1, user: 'test_user', password: ''});
25+
dba.checkInstanceConfiguration({host: hostname, port: __mysql_sandbox_port1, user: 'test_user', password:''});
26+
session.close()
27+
28+
//@ Check instance configuration using a non existing user that authenticates as another user that has all privileges (BUG#26979375)
29+
// Grant the missing privilege again
30+
shell.connect({host: localhost, port: __mysql_sandbox_port1, user: 'root', password: 'root'});
31+
session.runSql("SET sql_log_bin = 0");
32+
session.runSql("GRANT SELECT on *.* TO 'test_user'@'%'");
33+
session.runSql("SET sql_log_bin = 1");
34+
session.close()
35+
36+
shell.connect({host: hostname, port: __mysql_sandbox_port1, user: 'test_user', password: ''});
37+
dba.checkInstanceConfiguration({host: hostname, port: __mysql_sandbox_port1, user: 'test_user', password:''});
38+
session.close()
39+
2140
// Drop user
41+
shell.connect({host: localhost, port: __mysql_sandbox_port1, user: 'root', password: 'root'});
2242
session.runSql("SET sql_log_bin = 0");
2343
session.runSql("DROP user 'test_user'@'%'");
2444
session.runSql("SET sql_log_bin = 1");

unittest/scripts/js_devapi/scripts/dba_configure_local_instance.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ session.runSql("SET sql_log_bin = 0");
1111
session.runSql("REVOKE SELECT on *.* FROM 'gr_user'@'%'");
1212
session.runSql("SET sql_log_bin = 1");
1313
dba.configureLocalInstance("gr_user@localhost:"+__mysql_sandbox_port1, {mycnfPath: cnfPath1, dbPassword:'root'});
14+
//@ Error: session user has privileges to run the configure command but we pass it an existing clusterAdmin user that doesn't have enough privileges (BUG#26979375)
15+
dba.configureLocalInstance("root@localhost:"+__mysql_sandbox_port1, {mycnfPath: cnfPath1, dbPassword:'root', clusterAdmin: 'gr_user', clusterAdminPassword: "root"});
16+
17+
//@ Session user has privileges to run the configure command and we pass it an non existing clusterAdmin user(BUG#26979375)
18+
dba.configureLocalInstance("root@localhost:"+__mysql_sandbox_port1, {mycnfPath: cnfPath1, dbPassword:'root', clusterAdmin: 'gr_user2', clusterAdminPassword: "root"});
19+
1420
// restore select privilege to gr_user
1521
session.runSql("SET sql_log_bin = 0");
1622
session.runSql("GRANT SELECT on *.* TO 'gr_user'@'%'");

unittest/scripts/js_devapi/scripts/dba_interactive.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,31 @@ var restored_sql_mode = row[0];
6767
var was_restored = restored_sql_mode == original_sql_mode;
6868
print("Original SQL_MODE has been restored: "+ was_restored + "\n");
6969

70+
//@ Dba: create cluster using a non existing user that authenticates as another user (BUG#26979375)
71+
// Clear super read_only
72+
session.runSql("set GLOBAL super_read_only = 0");
73+
session.runSql("SET sql_log_bin = 0");
74+
session.runSql("CREATE USER 'test_user'@'%'");
75+
session.runSql("GRANT ALL PRIVILEGES ON *.* to 'test_user'@'%' WITH GRANT OPTION");
76+
session.runSql("SET sql_log_bin = 1");
77+
session.close()
78+
79+
shell.connect({host: hostname, port: __mysql_sandbox_port1, user: 'test_user', password: ''});
80+
c1 = dba.createCluster("devCluster", {clearReadOnly: true});
81+
c1
82+
83+
//@ Dba: dissolve cluster created using a non existing user that authenticates as another user (BUG#26979375)
84+
c1.dissolve({force:true});
85+
session.close()
86+
shell.connect({host: localhost, port: __mysql_sandbox_port1, user: 'root', password: 'root'});
87+
88+
// drop created test_user
89+
// Clear super read_only
90+
session.runSql("set GLOBAL super_read_only = 0");
91+
session.runSql("SET sql_log_bin = 0");
92+
session.runSql("DROP user 'test_user'@'%'");
93+
session.runSql("SET sql_log_bin = 1");
94+
7095
//@<OUT> Dba: createCluster with interaction
7196
if (__have_ssl)
7297
var c1 = dba.createCluster('devCluster', {memberSslMode: 'REQUIRED', clearReadOnly: true});
@@ -184,7 +209,7 @@ session.runSql("REVOKE REPLICATION SLAVE ON *.* FROM 'dba_test'@'%'");
184209
session.runSql("SET SQL_LOG_BIN=1");
185210
session.close();
186211

187-
//@<OUT> Dba: configureLocalInstance create existing invalid admin user
212+
//@ Dba: configureLocalInstance create existing invalid admin user
188213
// Regression for BUG#25519190 : CONFIGURELOCALINSTANCE() FAILS UNGRACEFUL IF CALLED TWICE
189214
dba.configureLocalInstance('mydba@localhost:' + __mysql_sandbox_port2);
190215

unittest/scripts/js_devapi/scripts/dba_no_interactive.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ session.runSql("REVOKE REPLICATION SLAVE ON *.* FROM 'dba_test'@'%'");
198198
session.runSql("SET SQL_LOG_BIN=1");
199199
session.close();
200200

201-
//@<OUT> Dba: configureLocalInstance create existing invalid admin user
201+
//@ Dba: configureLocalInstance create existing invalid admin user
202202
// Regression for BUG#25519190 : CONFIGURELOCALINSTANCE() FAILS UNGRACEFUL IF CALLED TWICE
203203
dba.configureLocalInstance('mydba:@localhost:' + __mysql_sandbox_port2,
204204
{clusterAdmin: "dba_test", clusterAdminPassword:"",

unittest/scripts/js_devapi/validation/dba_check_instance_configuration_session.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@
88
||
99

1010
//@ Error: user has no privileges to run the checkInstanceConfiguration command (BUG#26609909)
11-
||Dba.checkInstanceConfiguration: Account 'test_user'@'localhost' does not have all the required privileges to execute this operation. For more information, see the online documentation.
11+
||Dba.checkInstanceConfiguration: Session account 'test_user'@'%' (used to authenticate 'test_user'@'<<<localhost>>>') does not have all the required privileges to execute this operation. For more information, see the online documentation.
12+
13+
//@ Check instance configuration using a non existing user that authenticates as another user that does not have enough privileges (BUG#26979375)
14+
||Dba.checkInstanceConfiguration: Session account 'test_user'@'%' (used to authenticate 'test_user'@'<<<hostname>>>') does not have all the required privileges to execute this operation. For more information, see the online documentation.
15+
16+
//@ Check instance configuration using a non existing user that authenticates as another user that has all privileges (BUG#26979375)
17+
||

unittest/scripts/js_devapi/validation/dba_configure_local_instance.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
}
55

66
//@ Error: user has no privileges to run the configure command (BUG#26609909)
7-
||Dba.configureLocalInstance: Account 'gr_user'@'localhost' does not have all the required privileges to execute this operation. For more information, see the online documentation.
7+
||Dba.configureLocalInstance: Session account 'gr_user'@'%' (used to authenticate 'gr_user'@'<<<localhost>>>') does not have all the required privileges to execute this operation. For more information, see the online documentation.
8+
9+
//@ Error: session user has privileges to run the configure command but we pass it an existing clusterAdmin user that doesn't have enough privileges (BUG#26979375)
10+
||Dba.configureLocalInstance: Cluster Admin account 'gr_user'@'%' does not have all the required privileges to execute this operation. For more information, see the online documentation.
11+
12+
//@ Session user has privileges to run the configure command and we pass it an non existing clusterAdmin user(BUG#26979375)
13+
||
814

915
//@ create cluster using cluster admin account (BUG#26523629)
1016
||

0 commit comments

Comments
 (0)