From 8a481e55343e677c6e5d025e88fb774fda3b5366 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Fri, 12 Mar 2021 15:28:01 +1100 Subject: [PATCH] SQL escape passwords To correctly SQL escape passwords, escaping \ first is required. Then we need to escape ' in the password to prevent it being treated as a end of SQL statement quote. All escaping needs to use \, so we cannot be in NO_BACKSLASH_ESCAPES sql_mode otherwise no escaping will work. Getting bash to escape the root password for its internal uses and in a form that was recognized by MariaDB reading the configuration file was becoming exceptionally complicated, and unsuccessful. As such we use the MYSQL_PWD environment variable to contain the password when needed. Occasionally the socket wasn't ready on a clean install by the time docker_process_sql was called so allow up to 5 seconds just in case. example logs of this: 2021-03-16 00:41:21+00:00 [Note] [Entrypoint]: Waiting for server startup 2021-03-16 0:41:21 140680894076608 [Note] mysqld (mysqld 10.2.37-MariaDB-1:10.2.37+maria~bionic) starting as process 111 ... 2021-03-16 00:41:21+00:00 [Note] [Entrypoint]: Temporary server started. 2021-03-16 0:41:21 140680894076608 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins 2021-03-16 0:41:21 140680894076608 [Note] InnoDB: Uses event mutexes 2021-03-16 0:41:21 140680894076608 [Note] InnoDB: Compressed tables use zlib 1.2.11 2021-03-16 0:41:21 140680894076608 [Note] InnoDB: Using Linux native AIO 2021-03-16 0:41:21 140680894076608 [Note] InnoDB: Number of pools: 1 2021-03-16 0:41:21 140680894076608 [Note] InnoDB: Using SSE2 crc32 instructions ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2) Closes #183 --- 10.2/docker-entrypoint.sh | 55 +++++++++++++++++++++++++-------------- 10.3/docker-entrypoint.sh | 55 +++++++++++++++++++++++++-------------- 10.4/docker-entrypoint.sh | 55 +++++++++++++++++++++++++-------------- 10.5/docker-entrypoint.sh | 55 +++++++++++++++++++++++++-------------- docker-entrypoint.sh | 55 +++++++++++++++++++++++++-------------- 5 files changed, 180 insertions(+), 95 deletions(-) diff --git a/10.2/docker-entrypoint.sh b/10.2/docker-entrypoint.sh index 2375375d..d420d79f 100755 --- a/10.2/docker-entrypoint.sh +++ b/10.2/docker-entrypoint.sh @@ -139,9 +139,11 @@ docker_temp_server_start() { # Stop the server. When using a local socket file mysqladmin will block until # the shutdown is complete. docker_temp_server_stop() { - if ! mysqladmin --defaults-extra-file=<( _mysql_passfile ) shutdown -uroot --socket="${SOCKET}"; then + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD + if ! mysqladmin shutdown -uroot --socket="${SOCKET}"; then mysql_error "Unable to shut down server." fi + unset MYSQL_PWD } # Verify that the minimally required password settings are set for new databases. @@ -216,15 +218,32 @@ docker_setup_env() { docker_process_sql() { passfileArgs=() if [ '--dont-use-mysql-root-password' = "$1" ]; then - passfileArgs+=( "$1" ) shift + unset MYSQL_PWD + else + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD fi + local count=5 + while [ $count -gt 0 ] && [ ! -S "${SOCKET}" ] + do + count=$(( $count - 1 )) + mysql_note "Waiting for MariaDB to start, $count more seconds" + sleep 1 + done # args sent in can override this db, since they will be later in the command if [ -n "$MYSQL_DATABASE" ]; then set -- --database="$MYSQL_DATABASE" "$@" fi - mysql --defaults-extra-file=<( _mysql_passfile "${passfileArgs[@]}") --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + unset MYSQL_PWD +} + +# SQL escape the string $1 to be placed in a string literal. +# escape, \ followed by ' +docker_sql_escape_string_literal() { + local escaped=${1//\\/\\\\} + echo "${escaped//\'/\\\'}" } # Initializes database with timezone info and root password, plus optional extra db/user @@ -258,24 +277,29 @@ docker_setup_db() { fi # Sets root password and creates root users for non-localhost hosts local rootCreate= + local rootPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_ROOT_PASSWORD}" ) + # default root to listen for connections from anywhere if [ -n "$MARIADB_ROOT_HOST" ] && [ "$MARIADB_ROOT_HOST" != 'localhost' ]; then # no, we don't care if read finds a terminating character in this heredoc # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 read -r -d '' rootCreate <<-EOSQL || true - CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${MARIADB_ROOT_PASSWORD}' ; + CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${rootPasswordEscaped}' ; GRANT ALL ON *.* TO 'root'@'${MARIADB_ROOT_HOST}' WITH GRANT OPTION ; EOSQL fi # tell docker_process_sql to not use MARIADB_ROOT_PASSWORD since it is just now being set - docker_process_sql --dont-use-mysql-root-password --database=mysql <<-EOSQL + # --binary-mode to save us from the semi-mad users go out of their way to confuse the encoding. + docker_process_sql --dont-use-mysql-root-password --database=mysql --binary-mode <<-EOSQL -- What's done in this file shouldn't be replicated -- or products like mysql-fabric won't work SET @@SESSION.SQL_LOG_BIN=0; + -- we need the SQL_MODE NO_BACKSLASH_ESCAPES mode to be clear for the password to be set + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mariadb.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; - SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MARIADB_ROOT_PASSWORD}') ; + SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${rootPasswordEscaped}') ; -- 10.1: https://github.com/MariaDB/server/blob/d925aec1c10cebf6c34825a7de50afe4e630aff4/scripts/mysql_secure_installation.sh#L347-L365 -- 10.5: https://github.com/MariaDB/server/blob/00c3a28820c67c37ebbca72691f4897b57f2eed5/scripts/mysql_secure_installation.sh#L351-L369 DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%' ; @@ -294,7 +318,12 @@ docker_setup_db() { if [ -n "$MARIADB_USER" ] && [ -n "$MARIADB_PASSWORD" ]; then mysql_note "Creating user ${MARIADB_USER}" - docker_process_sql --database=mysql <<<"CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$MARIADB_PASSWORD' ;" + # SQL escape the user password, \ followed by ' + local userPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_PASSWORD}" ) + docker_process_sql --database=mysql --binary-mode <<-EOSQL_USER + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); + CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$userPasswordEscaped'; + EOSQL_USER if [ -n "$MARIADB_DATABASE" ]; then mysql_note "Giving user ${MARIADB_USER} access to schema ${MARIADB_DATABASE}" @@ -303,18 +332,6 @@ docker_setup_db() { fi } -_mysql_passfile() { - # echo the password to the "file" the client uses - # the client command will use process substitution to create a file on the fly - # ie: --defaults-extra-file=<( _mysql_passfile ) - if [ '--dont-use-mysql-root-password' != "$1" ] && [ -n "$MARIADB_ROOT_PASSWORD" ]; then - cat <<-EOF - [client] - password="${MARIADB_ROOT_PASSWORD}" - EOF - fi -} - # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { diff --git a/10.3/docker-entrypoint.sh b/10.3/docker-entrypoint.sh index 2375375d..d420d79f 100755 --- a/10.3/docker-entrypoint.sh +++ b/10.3/docker-entrypoint.sh @@ -139,9 +139,11 @@ docker_temp_server_start() { # Stop the server. When using a local socket file mysqladmin will block until # the shutdown is complete. docker_temp_server_stop() { - if ! mysqladmin --defaults-extra-file=<( _mysql_passfile ) shutdown -uroot --socket="${SOCKET}"; then + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD + if ! mysqladmin shutdown -uroot --socket="${SOCKET}"; then mysql_error "Unable to shut down server." fi + unset MYSQL_PWD } # Verify that the minimally required password settings are set for new databases. @@ -216,15 +218,32 @@ docker_setup_env() { docker_process_sql() { passfileArgs=() if [ '--dont-use-mysql-root-password' = "$1" ]; then - passfileArgs+=( "$1" ) shift + unset MYSQL_PWD + else + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD fi + local count=5 + while [ $count -gt 0 ] && [ ! -S "${SOCKET}" ] + do + count=$(( $count - 1 )) + mysql_note "Waiting for MariaDB to start, $count more seconds" + sleep 1 + done # args sent in can override this db, since they will be later in the command if [ -n "$MYSQL_DATABASE" ]; then set -- --database="$MYSQL_DATABASE" "$@" fi - mysql --defaults-extra-file=<( _mysql_passfile "${passfileArgs[@]}") --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + unset MYSQL_PWD +} + +# SQL escape the string $1 to be placed in a string literal. +# escape, \ followed by ' +docker_sql_escape_string_literal() { + local escaped=${1//\\/\\\\} + echo "${escaped//\'/\\\'}" } # Initializes database with timezone info and root password, plus optional extra db/user @@ -258,24 +277,29 @@ docker_setup_db() { fi # Sets root password and creates root users for non-localhost hosts local rootCreate= + local rootPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_ROOT_PASSWORD}" ) + # default root to listen for connections from anywhere if [ -n "$MARIADB_ROOT_HOST" ] && [ "$MARIADB_ROOT_HOST" != 'localhost' ]; then # no, we don't care if read finds a terminating character in this heredoc # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 read -r -d '' rootCreate <<-EOSQL || true - CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${MARIADB_ROOT_PASSWORD}' ; + CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${rootPasswordEscaped}' ; GRANT ALL ON *.* TO 'root'@'${MARIADB_ROOT_HOST}' WITH GRANT OPTION ; EOSQL fi # tell docker_process_sql to not use MARIADB_ROOT_PASSWORD since it is just now being set - docker_process_sql --dont-use-mysql-root-password --database=mysql <<-EOSQL + # --binary-mode to save us from the semi-mad users go out of their way to confuse the encoding. + docker_process_sql --dont-use-mysql-root-password --database=mysql --binary-mode <<-EOSQL -- What's done in this file shouldn't be replicated -- or products like mysql-fabric won't work SET @@SESSION.SQL_LOG_BIN=0; + -- we need the SQL_MODE NO_BACKSLASH_ESCAPES mode to be clear for the password to be set + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mariadb.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; - SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MARIADB_ROOT_PASSWORD}') ; + SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${rootPasswordEscaped}') ; -- 10.1: https://github.com/MariaDB/server/blob/d925aec1c10cebf6c34825a7de50afe4e630aff4/scripts/mysql_secure_installation.sh#L347-L365 -- 10.5: https://github.com/MariaDB/server/blob/00c3a28820c67c37ebbca72691f4897b57f2eed5/scripts/mysql_secure_installation.sh#L351-L369 DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%' ; @@ -294,7 +318,12 @@ docker_setup_db() { if [ -n "$MARIADB_USER" ] && [ -n "$MARIADB_PASSWORD" ]; then mysql_note "Creating user ${MARIADB_USER}" - docker_process_sql --database=mysql <<<"CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$MARIADB_PASSWORD' ;" + # SQL escape the user password, \ followed by ' + local userPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_PASSWORD}" ) + docker_process_sql --database=mysql --binary-mode <<-EOSQL_USER + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); + CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$userPasswordEscaped'; + EOSQL_USER if [ -n "$MARIADB_DATABASE" ]; then mysql_note "Giving user ${MARIADB_USER} access to schema ${MARIADB_DATABASE}" @@ -303,18 +332,6 @@ docker_setup_db() { fi } -_mysql_passfile() { - # echo the password to the "file" the client uses - # the client command will use process substitution to create a file on the fly - # ie: --defaults-extra-file=<( _mysql_passfile ) - if [ '--dont-use-mysql-root-password' != "$1" ] && [ -n "$MARIADB_ROOT_PASSWORD" ]; then - cat <<-EOF - [client] - password="${MARIADB_ROOT_PASSWORD}" - EOF - fi -} - # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { diff --git a/10.4/docker-entrypoint.sh b/10.4/docker-entrypoint.sh index 2375375d..d420d79f 100755 --- a/10.4/docker-entrypoint.sh +++ b/10.4/docker-entrypoint.sh @@ -139,9 +139,11 @@ docker_temp_server_start() { # Stop the server. When using a local socket file mysqladmin will block until # the shutdown is complete. docker_temp_server_stop() { - if ! mysqladmin --defaults-extra-file=<( _mysql_passfile ) shutdown -uroot --socket="${SOCKET}"; then + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD + if ! mysqladmin shutdown -uroot --socket="${SOCKET}"; then mysql_error "Unable to shut down server." fi + unset MYSQL_PWD } # Verify that the minimally required password settings are set for new databases. @@ -216,15 +218,32 @@ docker_setup_env() { docker_process_sql() { passfileArgs=() if [ '--dont-use-mysql-root-password' = "$1" ]; then - passfileArgs+=( "$1" ) shift + unset MYSQL_PWD + else + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD fi + local count=5 + while [ $count -gt 0 ] && [ ! -S "${SOCKET}" ] + do + count=$(( $count - 1 )) + mysql_note "Waiting for MariaDB to start, $count more seconds" + sleep 1 + done # args sent in can override this db, since they will be later in the command if [ -n "$MYSQL_DATABASE" ]; then set -- --database="$MYSQL_DATABASE" "$@" fi - mysql --defaults-extra-file=<( _mysql_passfile "${passfileArgs[@]}") --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + unset MYSQL_PWD +} + +# SQL escape the string $1 to be placed in a string literal. +# escape, \ followed by ' +docker_sql_escape_string_literal() { + local escaped=${1//\\/\\\\} + echo "${escaped//\'/\\\'}" } # Initializes database with timezone info and root password, plus optional extra db/user @@ -258,24 +277,29 @@ docker_setup_db() { fi # Sets root password and creates root users for non-localhost hosts local rootCreate= + local rootPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_ROOT_PASSWORD}" ) + # default root to listen for connections from anywhere if [ -n "$MARIADB_ROOT_HOST" ] && [ "$MARIADB_ROOT_HOST" != 'localhost' ]; then # no, we don't care if read finds a terminating character in this heredoc # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 read -r -d '' rootCreate <<-EOSQL || true - CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${MARIADB_ROOT_PASSWORD}' ; + CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${rootPasswordEscaped}' ; GRANT ALL ON *.* TO 'root'@'${MARIADB_ROOT_HOST}' WITH GRANT OPTION ; EOSQL fi # tell docker_process_sql to not use MARIADB_ROOT_PASSWORD since it is just now being set - docker_process_sql --dont-use-mysql-root-password --database=mysql <<-EOSQL + # --binary-mode to save us from the semi-mad users go out of their way to confuse the encoding. + docker_process_sql --dont-use-mysql-root-password --database=mysql --binary-mode <<-EOSQL -- What's done in this file shouldn't be replicated -- or products like mysql-fabric won't work SET @@SESSION.SQL_LOG_BIN=0; + -- we need the SQL_MODE NO_BACKSLASH_ESCAPES mode to be clear for the password to be set + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mariadb.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; - SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MARIADB_ROOT_PASSWORD}') ; + SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${rootPasswordEscaped}') ; -- 10.1: https://github.com/MariaDB/server/blob/d925aec1c10cebf6c34825a7de50afe4e630aff4/scripts/mysql_secure_installation.sh#L347-L365 -- 10.5: https://github.com/MariaDB/server/blob/00c3a28820c67c37ebbca72691f4897b57f2eed5/scripts/mysql_secure_installation.sh#L351-L369 DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%' ; @@ -294,7 +318,12 @@ docker_setup_db() { if [ -n "$MARIADB_USER" ] && [ -n "$MARIADB_PASSWORD" ]; then mysql_note "Creating user ${MARIADB_USER}" - docker_process_sql --database=mysql <<<"CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$MARIADB_PASSWORD' ;" + # SQL escape the user password, \ followed by ' + local userPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_PASSWORD}" ) + docker_process_sql --database=mysql --binary-mode <<-EOSQL_USER + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); + CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$userPasswordEscaped'; + EOSQL_USER if [ -n "$MARIADB_DATABASE" ]; then mysql_note "Giving user ${MARIADB_USER} access to schema ${MARIADB_DATABASE}" @@ -303,18 +332,6 @@ docker_setup_db() { fi } -_mysql_passfile() { - # echo the password to the "file" the client uses - # the client command will use process substitution to create a file on the fly - # ie: --defaults-extra-file=<( _mysql_passfile ) - if [ '--dont-use-mysql-root-password' != "$1" ] && [ -n "$MARIADB_ROOT_PASSWORD" ]; then - cat <<-EOF - [client] - password="${MARIADB_ROOT_PASSWORD}" - EOF - fi -} - # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { diff --git a/10.5/docker-entrypoint.sh b/10.5/docker-entrypoint.sh index 2375375d..d420d79f 100755 --- a/10.5/docker-entrypoint.sh +++ b/10.5/docker-entrypoint.sh @@ -139,9 +139,11 @@ docker_temp_server_start() { # Stop the server. When using a local socket file mysqladmin will block until # the shutdown is complete. docker_temp_server_stop() { - if ! mysqladmin --defaults-extra-file=<( _mysql_passfile ) shutdown -uroot --socket="${SOCKET}"; then + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD + if ! mysqladmin shutdown -uroot --socket="${SOCKET}"; then mysql_error "Unable to shut down server." fi + unset MYSQL_PWD } # Verify that the minimally required password settings are set for new databases. @@ -216,15 +218,32 @@ docker_setup_env() { docker_process_sql() { passfileArgs=() if [ '--dont-use-mysql-root-password' = "$1" ]; then - passfileArgs+=( "$1" ) shift + unset MYSQL_PWD + else + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD fi + local count=5 + while [ $count -gt 0 ] && [ ! -S "${SOCKET}" ] + do + count=$(( $count - 1 )) + mysql_note "Waiting for MariaDB to start, $count more seconds" + sleep 1 + done # args sent in can override this db, since they will be later in the command if [ -n "$MYSQL_DATABASE" ]; then set -- --database="$MYSQL_DATABASE" "$@" fi - mysql --defaults-extra-file=<( _mysql_passfile "${passfileArgs[@]}") --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + unset MYSQL_PWD +} + +# SQL escape the string $1 to be placed in a string literal. +# escape, \ followed by ' +docker_sql_escape_string_literal() { + local escaped=${1//\\/\\\\} + echo "${escaped//\'/\\\'}" } # Initializes database with timezone info and root password, plus optional extra db/user @@ -258,24 +277,29 @@ docker_setup_db() { fi # Sets root password and creates root users for non-localhost hosts local rootCreate= + local rootPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_ROOT_PASSWORD}" ) + # default root to listen for connections from anywhere if [ -n "$MARIADB_ROOT_HOST" ] && [ "$MARIADB_ROOT_HOST" != 'localhost' ]; then # no, we don't care if read finds a terminating character in this heredoc # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 read -r -d '' rootCreate <<-EOSQL || true - CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${MARIADB_ROOT_PASSWORD}' ; + CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${rootPasswordEscaped}' ; GRANT ALL ON *.* TO 'root'@'${MARIADB_ROOT_HOST}' WITH GRANT OPTION ; EOSQL fi # tell docker_process_sql to not use MARIADB_ROOT_PASSWORD since it is just now being set - docker_process_sql --dont-use-mysql-root-password --database=mysql <<-EOSQL + # --binary-mode to save us from the semi-mad users go out of their way to confuse the encoding. + docker_process_sql --dont-use-mysql-root-password --database=mysql --binary-mode <<-EOSQL -- What's done in this file shouldn't be replicated -- or products like mysql-fabric won't work SET @@SESSION.SQL_LOG_BIN=0; + -- we need the SQL_MODE NO_BACKSLASH_ESCAPES mode to be clear for the password to be set + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mariadb.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; - SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MARIADB_ROOT_PASSWORD}') ; + SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${rootPasswordEscaped}') ; -- 10.1: https://github.com/MariaDB/server/blob/d925aec1c10cebf6c34825a7de50afe4e630aff4/scripts/mysql_secure_installation.sh#L347-L365 -- 10.5: https://github.com/MariaDB/server/blob/00c3a28820c67c37ebbca72691f4897b57f2eed5/scripts/mysql_secure_installation.sh#L351-L369 DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%' ; @@ -294,7 +318,12 @@ docker_setup_db() { if [ -n "$MARIADB_USER" ] && [ -n "$MARIADB_PASSWORD" ]; then mysql_note "Creating user ${MARIADB_USER}" - docker_process_sql --database=mysql <<<"CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$MARIADB_PASSWORD' ;" + # SQL escape the user password, \ followed by ' + local userPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_PASSWORD}" ) + docker_process_sql --database=mysql --binary-mode <<-EOSQL_USER + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); + CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$userPasswordEscaped'; + EOSQL_USER if [ -n "$MARIADB_DATABASE" ]; then mysql_note "Giving user ${MARIADB_USER} access to schema ${MARIADB_DATABASE}" @@ -303,18 +332,6 @@ docker_setup_db() { fi } -_mysql_passfile() { - # echo the password to the "file" the client uses - # the client command will use process substitution to create a file on the fly - # ie: --defaults-extra-file=<( _mysql_passfile ) - if [ '--dont-use-mysql-root-password' != "$1" ] && [ -n "$MARIADB_ROOT_PASSWORD" ]; then - cat <<-EOF - [client] - password="${MARIADB_ROOT_PASSWORD}" - EOF - fi -} - # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() { diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 2375375d..d420d79f 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -139,9 +139,11 @@ docker_temp_server_start() { # Stop the server. When using a local socket file mysqladmin will block until # the shutdown is complete. docker_temp_server_stop() { - if ! mysqladmin --defaults-extra-file=<( _mysql_passfile ) shutdown -uroot --socket="${SOCKET}"; then + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD + if ! mysqladmin shutdown -uroot --socket="${SOCKET}"; then mysql_error "Unable to shut down server." fi + unset MYSQL_PWD } # Verify that the minimally required password settings are set for new databases. @@ -216,15 +218,32 @@ docker_setup_env() { docker_process_sql() { passfileArgs=() if [ '--dont-use-mysql-root-password' = "$1" ]; then - passfileArgs+=( "$1" ) shift + unset MYSQL_PWD + else + export MYSQL_PWD=$MARIADB_ROOT_PASSWORD fi + local count=5 + while [ $count -gt 0 ] && [ ! -S "${SOCKET}" ] + do + count=$(( $count - 1 )) + mysql_note "Waiting for MariaDB to start, $count more seconds" + sleep 1 + done # args sent in can override this db, since they will be later in the command if [ -n "$MYSQL_DATABASE" ]; then set -- --database="$MYSQL_DATABASE" "$@" fi - mysql --defaults-extra-file=<( _mysql_passfile "${passfileArgs[@]}") --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" "$@" + unset MYSQL_PWD +} + +# SQL escape the string $1 to be placed in a string literal. +# escape, \ followed by ' +docker_sql_escape_string_literal() { + local escaped=${1//\\/\\\\} + echo "${escaped//\'/\\\'}" } # Initializes database with timezone info and root password, plus optional extra db/user @@ -258,24 +277,29 @@ docker_setup_db() { fi # Sets root password and creates root users for non-localhost hosts local rootCreate= + local rootPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_ROOT_PASSWORD}" ) + # default root to listen for connections from anywhere if [ -n "$MARIADB_ROOT_HOST" ] && [ "$MARIADB_ROOT_HOST" != 'localhost' ]; then # no, we don't care if read finds a terminating character in this heredoc # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 read -r -d '' rootCreate <<-EOSQL || true - CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${MARIADB_ROOT_PASSWORD}' ; + CREATE USER 'root'@'${MARIADB_ROOT_HOST}' IDENTIFIED BY '${rootPasswordEscaped}' ; GRANT ALL ON *.* TO 'root'@'${MARIADB_ROOT_HOST}' WITH GRANT OPTION ; EOSQL fi # tell docker_process_sql to not use MARIADB_ROOT_PASSWORD since it is just now being set - docker_process_sql --dont-use-mysql-root-password --database=mysql <<-EOSQL + # --binary-mode to save us from the semi-mad users go out of their way to confuse the encoding. + docker_process_sql --dont-use-mysql-root-password --database=mysql --binary-mode <<-EOSQL -- What's done in this file shouldn't be replicated -- or products like mysql-fabric won't work SET @@SESSION.SQL_LOG_BIN=0; + -- we need the SQL_MODE NO_BACKSLASH_ESCAPES mode to be clear for the password to be set + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mariadb.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; - SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MARIADB_ROOT_PASSWORD}') ; + SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${rootPasswordEscaped}') ; -- 10.1: https://github.com/MariaDB/server/blob/d925aec1c10cebf6c34825a7de50afe4e630aff4/scripts/mysql_secure_installation.sh#L347-L365 -- 10.5: https://github.com/MariaDB/server/blob/00c3a28820c67c37ebbca72691f4897b57f2eed5/scripts/mysql_secure_installation.sh#L351-L369 DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%' ; @@ -294,7 +318,12 @@ docker_setup_db() { if [ -n "$MARIADB_USER" ] && [ -n "$MARIADB_PASSWORD" ]; then mysql_note "Creating user ${MARIADB_USER}" - docker_process_sql --database=mysql <<<"CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$MARIADB_PASSWORD' ;" + # SQL escape the user password, \ followed by ' + local userPasswordEscaped=$( docker_sql_escape_string_literal "${MARIADB_PASSWORD}" ) + docker_process_sql --database=mysql --binary-mode <<-EOSQL_USER + SET @@SESSION.SQL_MODE=REPLACE(@@SESSION.SQL_MODE, 'NO_BACKSLASH_ESCAPES', ''); + CREATE USER '$MARIADB_USER'@'%' IDENTIFIED BY '$userPasswordEscaped'; + EOSQL_USER if [ -n "$MARIADB_DATABASE" ]; then mysql_note "Giving user ${MARIADB_USER} access to schema ${MARIADB_DATABASE}" @@ -303,18 +332,6 @@ docker_setup_db() { fi } -_mysql_passfile() { - # echo the password to the "file" the client uses - # the client command will use process substitution to create a file on the fly - # ie: --defaults-extra-file=<( _mysql_passfile ) - if [ '--dont-use-mysql-root-password' != "$1" ] && [ -n "$MARIADB_ROOT_PASSWORD" ]; then - cat <<-EOF - [client] - password="${MARIADB_ROOT_PASSWORD}" - EOF - fi -} - # check arguments for an option that would cause mysqld to stop # return true if there is one _mysql_want_help() {