Skip to content

Commit d598478

Browse files
committed
MDEV-10112: mysql_secure_installation: use DDL instead of DML for Galera compatibility
The script previously used direct DML statements (UPDATE, DELETE, INSERT) on MyISAM system tables (mysql.global_priv, mysql.db) which are not replicated by Galera clusters. Running the script on one node would leave other cluster nodes unsecured. Replace all such DML with Galera-safe DDL equivalents: 1. set_root_password: Before: UPDATE mysql.global_priv SET priv=json_set(...) WHERE User='root' After: ALTER USER ... IDENTIFIED BY '...' Uses PREPARE/EXECUTE to handle all root accounts across all hosts. 2. enable unix_socket authentication: Before: UPDATE mysql.global_priv SET priv=json_set(...) WHERE User='root' After: INSTALL SONAME 'auth_socket' (ensure plugin is loaded) ALTER USER ... IDENTIFIED VIA mysql_native_password USING 'invalid' OR unix_socket Uses PREPARE/EXECUTE to handle all root accounts across all hosts. 3. remove_anonymous_users: Before: DELETE FROM mysql.global_priv WHERE User='' After: DROP USER IF EXISTS ''@'host1', ''@'host2', ... Uses SELECT/GROUP_CONCAT to build the list of anonymous accounts dynamically. DROP USER automatically removes all privilege table entries for the dropped users, including mysql.db rows. 4. remove_remote_root: Before: DELETE FROM mysql.global_priv WHERE User='root' AND Host NOT IN (...) After: DROP USER IF EXISTS 'root'@'remotehost', ... Uses SELECT/GROUP_CONCAT to build the list of remote root accounts. 5. remove_test_database privileges: Before: DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%' After: REVOKE ALL PRIVILEGES ON test.* FROM non-anonymous users in mysql.db, followed by FLUSH PRIVILEGES to purge stale cache entries. Anonymous user entries are already cleaned up in step 3, since DROP USER automatically removes all mysql.db rows for the dropped user. All five changes use PREPARE/EXECUTE with GROUP_CONCAT-generated DDL to handle cases where multiple rows need to be acted on in a single statement. Also added: - MTR test case 'main.mysql_secure_installation' to verify the script works as expected and that the new DDL statements are correctly generated and executed. - Support in 'mariadb-test-run.pl' to locate the 'mysql_secure_installation' script during tests.
1 parent e0edd07 commit d598478

4 files changed

Lines changed: 143 additions & 15 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# restart: --datadir=MYSQLTEST_VARDIR/tmp/msi_datadir
2+
# Verify: anonymous users removed
3+
SELECT user, host FROM mysql.global_priv WHERE user = '' ORDER BY host;
4+
user host
5+
# Verify: only local root accounts remain
6+
SELECT user, host FROM mysql.global_priv
7+
WHERE user = 'root' ORDER BY host;
8+
user host
9+
root 127.0.0.1
10+
root ::1
11+
root localhost
12+
# Verify: test database removed
13+
SHOW DATABASES LIKE 'test';
14+
Database (test)
15+
# Verify: no test DB privileges remain in mysql.db
16+
SELECT user, host, db FROM mysql.db
17+
WHERE db = 'test' OR db LIKE 'test\\_%'
18+
ORDER BY user, host, db;
19+
user host db
20+
# restart
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# MDEV-10112: mysql_secure_installation should use DDL (GRANT, REVOKE, etc)
2+
# instead of DML for Galera compatibility
3+
#
4+
# This test verifies that mysql_secure_installation works correctly
5+
# after being converted from DML (UPDATE/DELETE on system tables) to
6+
# DDL (ALTER USER, DROP USER, REVOKE, etc).
7+
8+
--source include/not_windows.inc
9+
--source include/not_embedded.inc
10+
11+
# To avoid affecting other tests, we run this test on a temporary datadir
12+
# using MTR's official restart mechanism.
13+
--let $MSI_DATADIR= $MYSQLTEST_VARDIR/tmp/msi_datadir
14+
--mkdir $MSI_DATADIR
15+
--exec cp -R $MYSQLTEST_VARDIR/install.db/. $MSI_DATADIR/
16+
--exec chmod -R 700 $MSI_DATADIR
17+
18+
--let $old_restart_parameters= $restart_parameters
19+
--let $restart_parameters= --datadir=$MSI_DATADIR
20+
--source include/restart_mysqld.inc
21+
22+
#
23+
# Scripted answers for mariadb-secure-installation
24+
#
25+
--write_file $MYSQLTEST_VARDIR/tmp/msi_input.txt
26+
27+
n
28+
n
29+
Y
30+
Y
31+
Y
32+
Y
33+
EOF
34+
35+
# --basedir is needed so the script can find my_print_defaults and mariadb
36+
# from the build tree (extra/ and client/ directories).
37+
# Output redirected to log file to keep result file clean.
38+
--exec $MYSQL_SECURE_INSTALLATION --basedir=$MYSQL_BINDIR -S $MASTER_MYSOCK < $MYSQLTEST_VARDIR/tmp/msi_input.txt > $MYSQLTEST_VARDIR/tmp/msi.log 2>&1
39+
40+
--remove_file $MYSQLTEST_VARDIR/tmp/msi_input.txt
41+
--remove_file $MYSQLTEST_VARDIR/tmp/msi.log
42+
43+
#
44+
# Verify the results
45+
#
46+
--echo # Verify: anonymous users removed
47+
SELECT user, host FROM mysql.global_priv WHERE user = '' ORDER BY host;
48+
49+
--echo # Verify: only local root accounts remain
50+
SELECT user, host FROM mysql.global_priv
51+
WHERE user = 'root' ORDER BY host;
52+
53+
--echo # Verify: test database removed
54+
SHOW DATABASES LIKE 'test';
55+
56+
--echo # Verify: no test DB privileges remain in mysql.db
57+
SELECT user, host, db FROM mysql.db
58+
WHERE db = 'test' OR db LIKE 'test\\_%'
59+
ORDER BY user, host, db;
60+
61+
#
62+
# Clean up: restore the original server state
63+
#
64+
--let $restart_parameters= $old_restart_parameters
65+
--source include/restart_mysqld.inc
66+
--rmdir $MSI_DATADIR

mysql-test/mariadb-test-run.pl

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2233,12 +2233,13 @@ sub environment_setup {
22332233
$ENV{'MYSQL_PLUGIN'}= $exe_mysql_plugin;
22342234
$ENV{'MYSQL_EMBEDDED'}= $exe_mysql_embedded;
22352235
$ENV{'MARIADB_CONV'}= "$exe_mariadb_conv --character-sets-dir=$path_charsetsdir";
2236+
$ENV{'MYSQL_INSTALL_DB_EXE'}= mtr_exe_maybe_exists("$bindir/sql/mariadb-install-db",
2237+
"$bindir/bin/mariadb-install-db",
2238+
"$bindir/scripts/mariadb-install-db");
22362239
if(IS_WINDOWS)
22372240
{
2238-
$ENV{'MYSQL_INSTALL_DB_EXE'}= mtr_exe_exists("$bindir/sql$multiconfig/mariadb-install-db",
2239-
"$bindir/bin/mariadb-install-db");
22402241
$ENV{'MARIADB_UPGRADE_SERVICE_EXE'}= mtr_exe_exists("$bindir/sql$multiconfig/mariadb-upgrade-service",
2241-
"$bindir/bin/mariadb-upgrade-service");
2242+
"$bindir/bin/mariadb-upgrade-service");
22422243
$ENV{'MARIADB_UPGRADE_EXE'}= mtr_exe_exists("$path_client_bindir/mariadb-upgrade");
22432244
}
22442245

@@ -2312,6 +2313,19 @@ sub environment_setup {
23122313
$ENV{'MYSQLHOTCOPY'}= $mysqlhotcopy;
23132314
}
23142315

2316+
# ----------------------------------------------------
2317+
# mysql_secure_installation
2318+
# ----------------------------------------------------
2319+
my $mysql_secure_installation=
2320+
mtr_pl_maybe_exists("$bindir/scripts/mariadb-secure-installation") ||
2321+
mtr_pl_maybe_exists("$bindir/scripts/mysql_secure_installation") ||
2322+
mtr_pl_maybe_exists("$path_client_bindir/mariadb-secure-installation") ||
2323+
mtr_pl_maybe_exists("$path_client_bindir/mysql_secure_installation");
2324+
if ($mysql_secure_installation)
2325+
{
2326+
$ENV{'MYSQL_SECURE_INSTALLATION'}= $mysql_secure_installation;
2327+
}
2328+
23152329
# ----------------------------------------------------
23162330
# perror
23172331
# ----------------------------------------------------

scripts/mysql_secure_installation.sh

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,10 @@ then
167167
cannot_find_file my_print_defaults $basedir/bin $basedir/extra
168168
exit 1
169169
fi
170-
mysql_command=`find_in_basedir mariadb bin`
170+
mysql_command=`find_in_basedir mariadb bin client`
171171
if test -z "$mysql_command"
172172
then
173-
cannot_find_file mariadb $basedir/bin
173+
cannot_find_file mariadb $basedir/bin $basedir/client
174174
exit 1
175175
fi
176176
else
@@ -316,7 +316,13 @@ set_root_password() {
316316
fi
317317

318318
esc_pass=`basic_single_escape "$password1"`
319-
do_query "UPDATE mysql.global_priv SET priv=json_set(priv, '$.plugin', 'mysql_native_password', '$.authentication_string', PASSWORD('$esc_pass')) WHERE User='root';"
319+
query="
320+
SET @str = IFNULL((SELECT GROUP_CONCAT(CONCAT('\'', User, '\'@\'', Host, '\'')) FROM mysql.global_priv WHERE User='root'), 'root@localhost');
321+
SET @str = CONCAT('ALTER USER ', @str, ' IDENTIFIED BY \'$esc_pass\'');
322+
PREPARE stmt FROM @str;
323+
EXECUTE stmt;
324+
DEALLOCATE PREPARE stmt;"
325+
do_query "$query"
320326
if [ $? -eq 0 ]; then
321327
echo "Password updated successfully!"
322328
echo "Reloading privilege tables.."
@@ -336,7 +342,13 @@ set_root_password() {
336342
}
337343

338344
remove_anonymous_users() {
339-
do_query "DELETE FROM mysql.global_priv WHERE User='';"
345+
query="
346+
SET @str = (SELECT IFNULL(CONCAT('DROP USER IF EXISTS ', GROUP_CONCAT(CONCAT('\'', User, '\'@\'', Host, '\''))), '') FROM mysql.global_priv WHERE User='');
347+
SET @str = IF(@str = '', 'DO 1', @str);
348+
PREPARE stmt FROM @str;
349+
EXECUTE stmt;
350+
DEALLOCATE PREPARE stmt;"
351+
do_query "$query"
340352
if [ $? -eq 0 ]; then
341353
echo " ... Success!"
342354
else
@@ -348,7 +360,13 @@ remove_anonymous_users() {
348360
}
349361

350362
remove_remote_root() {
351-
do_query "DELETE FROM mysql.global_priv WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');"
363+
query="
364+
SET @str = (SELECT IFNULL(CONCAT('DROP USER IF EXISTS ', GROUP_CONCAT(CONCAT('\'', User, '\'@\'', Host, '\''))), '') FROM mysql.global_priv WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1'));
365+
SET @str = IF(@str = '', 'DO 1', @str);
366+
PREPARE stmt FROM @str;
367+
EXECUTE stmt;
368+
DEALLOCATE PREPARE stmt;"
369+
do_query "$query"
352370
if [ $? -eq 0 ]; then
353371
echo " ... Success!"
354372
else
@@ -366,12 +384,16 @@ remove_test_database() {
366384
fi
367385

368386
echo " - Removing privileges on test database..."
369-
do_query "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'"
370-
if [ $? -eq 0 ]; then
371-
echo " ... Success!"
372-
else
373-
echo " ... Failed! Not critical, keep moving..."
374-
fi
387+
# REVOKE explicit grants on 'test' from any remaining named users.
388+
# Anonymous users (User='') are already removed by remove_anonymous_users()
389+
# via DROP USER, which also removes their mysql.db rows automatically.
390+
# FLUSH PRIVILEGES below clears any remaining stale cache entries.
391+
query="
392+
SET @str = (SELECT IFNULL(CONCAT('REVOKE ALL PRIVILEGES ON \`test\`.* FROM ', GROUP_CONCAT(CONCAT('\'', User, '\'@\'', Host, '\''))), 'DO 1') FROM mysql.db WHERE Db='test' AND User <> '');
393+
PREPARE stmt FROM @str;
394+
EXECUTE stmt;
395+
DEALLOCATE PREPARE stmt;"
396+
do_query "$query"
375397

376398
return 0
377399
}
@@ -448,7 +470,13 @@ if [ "$reply" = "n" ]; then
448470
echo " ... skipping."
449471
else
450472
emptypass=0
451-
do_query "UPDATE mysql.global_priv SET priv=json_set(priv, '$.password_last_changed', UNIX_TIMESTAMP(), '$.plugin', 'mysql_native_password', '$.authentication_string', 'invalid', '$.auth_or', json_array(json_object(), json_object('plugin', 'unix_socket'))) WHERE User='root';"
473+
query="
474+
SET @str = IFNULL((SELECT GROUP_CONCAT(CONCAT('\'', User, '\'@\'', Host, '\'')) FROM mysql.global_priv WHERE User='root'), 'root@localhost');
475+
SET @str = CONCAT('ALTER USER ', @str, ' IDENTIFIED VIA mysql_native_password USING \'invalid\' OR unix_socket');
476+
PREPARE stmt FROM @str;
477+
EXECUTE stmt;
478+
DEALLOCATE PREPARE stmt;"
479+
do_query "$query"
452480
if [ $? -eq 0 ]; then
453481
echo "Enabled successfully!"
454482
echo "Reloading privilege tables.."

0 commit comments

Comments
 (0)