diff --git a/mysql-test/suite/galera/r/galera_sst_rsync_encrypt_with_key_server.result b/mysql-test/suite/galera/r/galera_sst_rsync_encrypt_with_key_server.result new file mode 100644 index 0000000000000..48b296a96f56d --- /dev/null +++ b/mysql-test/suite/galera/r/galera_sst_rsync_encrypt_with_key_server.result @@ -0,0 +1,27 @@ +connection node_2; +connection node_1; +SELECT 1; +1 +1 +connection node_2; +FOUND 1 /wsrep_sst_rsync/ in mysqld.1.err +connection node_1; +call mtr.add_suppression('Invalid value for WSREP_SST_OPT_REMOTE_USER'); +call mtr.add_suppression('Failed to read from: wsrep_sst_rsync'); +call mtr.add_suppression('Process completed with error: wsrep_sst_rsync'); +call mtr.add_suppression('Command did not run: wsrep_sst_rsync'); +call mtr.add_suppression('State transfer to .* failed'); +call mtr.add_suppression('Will never receive state. Need to abort'); +call mtr.add_suppression('Error while getting data from donor node'); +call mtr.add_suppression('Cleanup after exit with status'); +call mtr.add_suppression('Removing .*/sst_in_progress'); +call mtr.add_suppression('Parent mysqld process .* terminated unexpectedly'); +connection node_2; +connection node_1; +FOUND 1 /Invalid value for WSREP_SST_OPT_REMOTE_USER/ in mysqld.1.err +connection node_2; +# restart +call mtr.add_suppression('Will never receive state. Need to abort'); +call mtr.add_suppression('Parent mysqld process .* terminated unexpectedly'); +call mtr.add_suppression('Cleanup after exit with status'); +call mtr.add_suppression('State transfer to .* failed'); diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key_server.cnf b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key_server.cnf new file mode 100644 index 0000000000000..9b919145a316d --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key_server.cnf @@ -0,0 +1,13 @@ +!include ../galera_2nodes.cnf + +[mysqld] +wsrep_sst_method=rsync +wsrep_sst_auth="root:" +wsrep_debug=1 + +ssl-cert=@ENV.MYSQL_TEST_DIR/std_data/server-cert.pem +ssl-key=@ENV.MYSQL_TEST_DIR/std_data/server-key.pem +ssl-ca=@ENV.MYSQL_TEST_DIR/std_data/cacert.pem + +[sst] +ssl-mode=VERIFY_CA diff --git a/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key_server.test b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key_server.test new file mode 100644 index 0000000000000..f0458e1f62fd2 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_sst_rsync_encrypt_with_key_server.test @@ -0,0 +1,99 @@ +# +# Verifies that wsrep_sst_rsync.sh rejects a joiner-supplied certificate +# whose CN contains shell-unsafe characters. +# +# Brings up a 2-node cluster with rsync SST and ssl-mode=VERIFY_CA, then +# forces a fresh SST on node_2 using std_data/server-new-cert.pem -- a +# cert whose CN intentionally contains shell metacharacters. Confirms +# that the donor (node_1) logs +# "Invalid value for WSREP_SST_OPT_REMOTE_USER" +# i.e. the rsync SST script refuses the value rather than interpolating +# it into stunnel.conf or the rsync magic file. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc + +SELECT 1; + +--connection node_2 +--let $wait_condition = SELECT VARIABLE_VALUE = 'Synced' FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'WSREP_LOCAL_STATE_COMMENT' +--source include/wait_condition.inc + +# Confirm the initial SST went via rsync + stunnel (sanity check for the +# test configuration). +--let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.1.err +--let SEARCH_PATTERN = wsrep_sst_rsync +--source include/search_pattern_in_file.inc + + +# Reject shell-unsafe joiner-supplied auth (rsync) + +# Suppressions are per-server. node_1 will log the donor-side rejection +# ("Invalid value for WSREP_SST_OPT_REMOTE_USER"); node_2 will log the +# joiner-side "Will never receive state" abort. Add to both. +--connection node_1 +call mtr.add_suppression('Invalid value for WSREP_SST_OPT_REMOTE_USER'); +call mtr.add_suppression('Failed to read from: wsrep_sst_rsync'); +call mtr.add_suppression('Process completed with error: wsrep_sst_rsync'); +call mtr.add_suppression('Command did not run: wsrep_sst_rsync'); +call mtr.add_suppression('State transfer to .* failed'); +call mtr.add_suppression('Will never receive state. Need to abort'); +call mtr.add_suppression('Error while getting data from donor node'); +call mtr.add_suppression('Cleanup after exit with status'); +call mtr.add_suppression('Removing .*/sst_in_progress'); +call mtr.add_suppression('Parent mysqld process .* terminated unexpectedly'); + +--connection node_2 +--source include/shutdown_mysqld.inc + +# force SST again +--remove_file $MYSQLTEST_VARDIR/mysqld.2/data/grastate.dat +# using a cert with shell-unsafe CN +--exec echo '[mysqld.2]' >> $MYSQLTEST_VARDIR/my.cnf +--exec echo ssl-cert=$MYSQL_TEST_DIR/std_data/server-new-cert.pem >> $MYSQLTEST_VARDIR/my.cnf +--exec echo ssl-key=$MYSQL_TEST_DIR/std_data/server-new-key.pem >> $MYSQLTEST_VARDIR/my.cnf + +# start the server +# Joiner mariadbd exits when SST is aborted; the exit code varies by +# platform (clean 0 on some systems, signalled 134 / 1 on others). +--error 0,1,134 +--exec $MYSQLD_LAST_CMD +# the donor refused the SST request + +--connection node_1 +# safe() in wsrep_sst_common.sh logs this when it rejects the joiner CN; +# wsrep_sst_rsync.sh wraps the joiner-supplied REMOTE_USER with $(safe ..) +# at line 249 so the value never reaches the stunnel.conf heredoc. +--let SEARCH_PATTERN = Invalid value for WSREP_SST_OPT_REMOTE_USER +--source include/search_pattern_in_file.inc + +# cleanup +# Kill joiner's stunnel / rsync that may linger after the aborted SST. +# Use a perl block because --exec with pkill -f matches the mtr cmdline +# itself (which contains the pattern) and tears down the wrong process. +perl; + open(my $fh, '-|', 'ps', '-eo', 'pid,args') or die "ps: $!"; + while (<$fh>) { + next unless /server-new-cert/; + next unless /^\s*(\d+)\s+(?:.*\/)?(stunnel|socat|rsync)\b/; + kill 'TERM', $1; + } + close $fh; +EOF +--exec echo ssl-cert=$MYSQL_TEST_DIR/std_data/server-cert.pem >> $MYSQLTEST_VARDIR/my.cnf +--exec echo ssl-key=$MYSQL_TEST_DIR/std_data/server-key.pem >> $MYSQLTEST_VARDIR/my.cnf + +# Switch back to node_2 before restarting it; the connection associates +# with the soon-to-be-restarted server so mtr auto-reconnects and the +# wait_condition + late suppressions land on the new instance. +--connection node_2 +--source $MYSQL_TEST_DIR/include/start_mysqld.inc + +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size' +--source include/wait_condition.inc + +call mtr.add_suppression('Will never receive state. Need to abort'); +call mtr.add_suppression('Parent mysqld process .* terminated unexpectedly'); +call mtr.add_suppression('Cleanup after exit with status'); +call mtr.add_suppression('State transfer to .* failed'); diff --git a/scripts/wsrep_sst_rsync.sh b/scripts/wsrep_sst_rsync.sh index 90d439b9316bf..2f4cb84fe619a 100644 --- a/scripts/wsrep_sst_rsync.sh +++ b/scripts/wsrep_sst_rsync.sh @@ -246,7 +246,7 @@ if [ "${SSLMODE#VERIFY}" != "$SSLMODE" ]; then exit 22 # EINVAL fi if [ -n "$WSREP_SST_OPT_REMOTE_USER" ]; then - CHECK_OPT="checkHost = $WSREP_SST_OPT_REMOTE_USER" + CHECK_OPT="checkHost = $(safe WSREP_SST_OPT_REMOTE_USER)" elif [ "$WSREP_SST_OPT_ROLE" = 'donor' ]; then # check if the address is an ip-address (v4 or v6): if echo "$WSREP_SST_OPT_HOST_UNESCAPED" | \ @@ -640,7 +640,8 @@ FILTER="-f '- /lost+found' echo "$STATE" > "$MAGIC_FILE" if [ -n "$WSREP_SST_OPT_REMOTE_PSWD" ]; then - # Let joiner know that we know its secret + # Let joiner know that we know its secret. + WSREP_SST_OPT_REMOTE_PSWD=$(safe WSREP_SST_OPT_REMOTE_PSWD) echo "$SECRET_TAG $WSREP_SST_OPT_REMOTE_PSWD" >> "$MAGIC_FILE" fi