Skip to content

Commit 653b89e

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 653b89e

4 files changed

Lines changed: 217 additions & 7 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
SELECT user, host FROM mysql.global_priv
2+
WHERE user NOT IN ('mariadb.sys')
3+
ORDER BY user, host;
4+
user host
5+
root 127.0.0.1
6+
root ::1
7+
root HOSTNAME
8+
root localhost
9+
SHOW DATABASES;
10+
Database
11+
information_schema
12+
mtr
13+
mysql
14+
performance_schema
15+
sys
16+
test
17+
BINDIR/scripts/mysql_secure_installation: Deprecated program name. It will be removed in a future release, use 'mariadb-secure-installation' instead
18+
print: BINDIR/extra/my_print_defaults
19+
20+
NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
21+
SERVERS IN PRODUCTION USE! PLEASE READ EACH STEP CAREFULLY!
22+
23+
In order to log into MariaDB to secure it, we'll need the current
24+
password for the root user. If you've just installed MariaDB, and
25+
haven't set the root password yet, you should just press enter here.
26+
27+
stty: stdin isn't a terminal
28+
Enter current password for root (enter for none):
29+
stty: stdin isn't a terminal
30+
OK, successfully used password, moving on...
31+
32+
Setting the root password or using the unix_socket ensures that nobody
33+
can log into the MariaDB root user without the proper authorisation.
34+
35+
Enable unix_socket authentication? [Y/n] ... skipping.
36+
37+
Set root password? [Y/n] ... skipping.
38+
39+
By default, a MariaDB installation has an anonymous user, allowing anyone
40+
to log into MariaDB without having to have a user account created for
41+
them. This is intended only for testing, and to make the installation
42+
go a bit smoother. You should remove them before moving into a
43+
production environment.
44+
45+
Remove anonymous users? [Y/n] ... Success!
46+
47+
Normally, root should only be allowed to connect from 'localhost'. This
48+
ensures that someone cannot guess at the root password from the network.
49+
50+
Disallow root login remotely? [Y/n] ... Success!
51+
52+
By default, MariaDB comes with a database named 'test' that anyone can
53+
access. This is also intended only for testing, and should be removed
54+
before moving into a production environment.
55+
56+
Remove test database and access to it? [Y/n] - Dropping test database...
57+
... Success!
58+
- Removing privileges on test database...
59+
... Success!
60+
61+
Reloading the privilege tables will ensure that all changes made so far
62+
will take effect immediately.
63+
64+
Reload privilege tables now? [Y/n] ... Success!
65+
66+
Cleaning up...
67+
68+
All done! If you've completed all of the above steps, your MariaDB
69+
installation should now be secure.
70+
71+
Thanks for using MariaDB!
72+
# Verify: anonymous users removed
73+
SELECT user, host FROM mysql.global_priv WHERE user = '' ORDER BY host;
74+
user host
75+
# Verify: only local root accounts remain
76+
SELECT user, host FROM mysql.global_priv
77+
WHERE user = 'root' ORDER BY host;
78+
user host
79+
root 127.0.0.1
80+
root ::1
81+
root localhost
82+
# Verify: test database removed
83+
SHOW DATABASES;
84+
Database
85+
information_schema
86+
mtr
87+
mysql
88+
performance_schema
89+
sys
90+
# Verify: no test DB privileges remain in mysql.db
91+
SELECT user, host, db FROM mysql.db
92+
WHERE db = 'test' OR db LIKE 'test\\_%'
93+
ORDER BY user, host, db;
94+
user host db
95+
# Kill the server
96+
# restart
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
# Capture hostname for use in --replace_result (it varies per machine)
12+
--let $hostname= `SELECT LOWER(@@hostname)`
13+
14+
#
15+
# Record initial state: verify root accounts and databases exist before
16+
# running the script.
17+
#
18+
--replace_result $hostname HOSTNAME
19+
SELECT user, host FROM mysql.global_priv
20+
WHERE user NOT IN ('mariadb.sys')
21+
ORDER BY user, host;
22+
SHOW DATABASES;
23+
24+
#
25+
# Scripted answers for mysql_secure_installation (current 11.4 prompt order):
26+
#
27+
# 1. Enter current password for root (enter for none): -> (empty, just press enter)
28+
# 2. Enable unix_socket authentication? [Y/n] -> n (skip to simplify test)
29+
# 3. Set root password? [Y/n] -> n (skip: changing root
30+
# password would lock us out)
31+
# 4. Remove anonymous users? [Y/n] -> Y
32+
# 5. Disallow root login remotely? [Y/n] -> Y
33+
# 6. Remove test database and access to it? [Y/n] -> Y
34+
# 7. Reload privilege tables now? [Y/n] -> Y
35+
#
36+
--write_file $MYSQLTEST_VARDIR/tmp/mysql_secure_installation_input.txt
37+
38+
n
39+
n
40+
Y
41+
Y
42+
Y
43+
Y
44+
EOF
45+
46+
# --basedir is needed so the script can find my_print_defaults and mariadb
47+
# from the build tree (extra/ and client/ directories).
48+
--replace_result $MYSQL_BINDIR BINDIR
49+
--exec $MYSQL_SECURE_INSTALLATION --basedir=$MYSQL_BINDIR -S $MASTER_MYSOCK < $MYSQLTEST_VARDIR/tmp/mysql_secure_installation_input.txt 2>&1
50+
51+
--remove_file $MYSQLTEST_VARDIR/tmp/mysql_secure_installation_input.txt
52+
53+
#
54+
# Verify the results of the DDL-based secure installation:
55+
#
56+
# 1. Anonymous users should be removed (DROP USER used instead of DELETE)
57+
# 2. test database should be dropped
58+
# 3. Remote root accounts should be removed (DROP USER used instead of DELETE)
59+
# 4. No test DB privileges remain (REVOKE used instead of DELETE FROM mysql.db)
60+
#
61+
62+
--echo # Verify: anonymous users removed
63+
SELECT user, host FROM mysql.global_priv WHERE user = '' ORDER BY host;
64+
65+
--echo # Verify: only local root accounts remain
66+
SELECT user, host FROM mysql.global_priv
67+
WHERE user = 'root' ORDER BY host;
68+
69+
--echo # Verify: test database removed
70+
SHOW DATABASES;
71+
72+
--echo # Verify: no test DB privileges remain in mysql.db
73+
SELECT user, host, db FROM mysql.db
74+
WHERE db = 'test' OR db LIKE 'test\\_%'
75+
ORDER BY user, host, db;
76+
77+
#
78+
# Clean up: restore the original datadir so subsequent tests are unaffected.
79+
# This approach (kill server, replace datadir, restart) is adapted from
80+
# commit 8977ad6 -- the previous attempt at this MDEV.
81+
#
82+
--let MYSQLD_DATADIR= `select @@datadir`
83+
--source include/kill_mysqld.inc
84+
--rmdir $MYSQLD_DATADIR
85+
86+
perl;
87+
use lib "lib";
88+
use My::Handles { suppress_init_messages => 1 };
89+
use My::File::Path;
90+
my $install_db_dir = ($ENV{MTR_PARALLEL} == 1) ?
91+
"$ENV{'MYSQLTEST_VARDIR'}/install.db" :
92+
"$ENV{'MYSQLTEST_VARDIR'}/../install.db";
93+
copytree($install_db_dir, $ENV{'MYSQLD_DATADIR'});
94+
EOF
95+
96+
--let $restart_parameters= $old_restart_parameters
97+
--source include/start_mysqld.inc

mysql-test/mariadb-test-run.pl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2312,6 +2312,17 @@ sub environment_setup {
23122312
$ENV{'MYSQLHOTCOPY'}= $mysqlhotcopy;
23132313
}
23142314

2315+
# ----------------------------------------------------
2316+
# mysql_secure_installation
2317+
# ----------------------------------------------------
2318+
my $mysql_secure_installation=
2319+
mtr_pl_maybe_exists("$bindir/scripts/mysql_secure_installation") ||
2320+
mtr_pl_maybe_exists("$path_client_bindir/mysql_secure_installation");
2321+
if ($mysql_secure_installation)
2322+
{
2323+
$ENV{'MYSQL_SECURE_INSTALLATION'}= $mysql_secure_installation;
2324+
}
2325+
23152326
# ----------------------------------------------------
23162327
# perror
23172328
# ----------------------------------------------------

scripts/mysql_secure_installation.sh

Lines changed: 13 additions & 7 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,7 @@ 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+
do_query "SET @str = IFNULL((SELECT GROUP_CONCAT(CONCAT('\'', User, '\'@\'', Host, '\'')) FROM mysql.global_priv WHERE User='root'), 'root@localhost'); SET @str = CONCAT('ALTER USER ', @str, ' IDENTIFIED BY \'$esc_pass\''); PREPARE stmt FROM @str; EXECUTE stmt; DEALLOCATE PREPARE stmt;"
320320
if [ $? -eq 0 ]; then
321321
echo "Password updated successfully!"
322322
echo "Reloading privilege tables.."
@@ -336,7 +336,7 @@ set_root_password() {
336336
}
337337

338338
remove_anonymous_users() {
339-
do_query "DELETE FROM mysql.global_priv WHERE User='';"
339+
do_query "SET @str = (SELECT IFNULL(CONCAT('DROP USER IF EXISTS ', GROUP_CONCAT(CONCAT('\'', User, '\'@\'', Host, '\''))), '') FROM mysql.global_priv WHERE User=''); SET @str = IF(@str = '', 'DO 1', @str); PREPARE stmt FROM @str; EXECUTE stmt; DEALLOCATE PREPARE stmt;"
340340
if [ $? -eq 0 ]; then
341341
echo " ... Success!"
342342
else
@@ -348,7 +348,7 @@ remove_anonymous_users() {
348348
}
349349

350350
remove_remote_root() {
351-
do_query "DELETE FROM mysql.global_priv WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');"
351+
do_query "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')); SET @str = IF(@str = '', 'DO 1', @str); PREPARE stmt FROM @str; EXECUTE stmt; DEALLOCATE PREPARE stmt;"
352352
if [ $? -eq 0 ]; then
353353
echo " ... Success!"
354354
else
@@ -366,12 +366,17 @@ remove_test_database() {
366366
fi
367367

368368
echo " - Removing privileges on test database..."
369-
do_query "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'"
369+
# REVOKE explicit grants on 'test' from any remaining named users.
370+
# Anonymous users (User='') are already removed by remove_anonymous_users()
371+
# via DROP USER, which also removes their mysql.db rows automatically.
372+
# FLUSH PRIVILEGES below clears any remaining stale cache entries.
373+
do_query "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 <> ''); PREPARE stmt FROM @str; EXECUTE stmt; DEALLOCATE PREPARE stmt;"
370374
if [ $? -eq 0 ]; then
371375
echo " ... Success!"
372376
else
373377
echo " ... Failed! Not critical, keep moving..."
374378
fi
379+
do_query "FLUSH PRIVILEGES;"
375380

376381
return 0
377382
}
@@ -448,7 +453,8 @@ if [ "$reply" = "n" ]; then
448453
echo " ... skipping."
449454
else
450455
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';"
456+
do_query "INSTALL SONAME 'auth_socket';"
457+
do_query "SET @str = IFNULL((SELECT GROUP_CONCAT(CONCAT('\'', User, '\'@\'', Host, '\'')) FROM mysql.global_priv WHERE User='root'), 'root@localhost'); SET @str = CONCAT('ALTER USER ', @str, ' IDENTIFIED VIA mysql_native_password USING \'invalid\' OR unix_socket'); PREPARE stmt FROM @str; EXECUTE stmt; DEALLOCATE PREPARE stmt;"
452458
if [ $? -eq 0 ]; then
453459
echo "Enabled successfully!"
454460
echo "Reloading privilege tables.."

0 commit comments

Comments
 (0)