Skip to content

Commit

Permalink
SQL escape passwords
Browse files Browse the repository at this point in the history
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
  • Loading branch information
grooverdan committed Apr 27, 2021
1 parent 45d873d commit 58f4020
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 95 deletions.
55 changes: 36 additions & 19 deletions 10.2/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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\_%' ;
Expand All @@ -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}"
Expand All @@ -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() {
Expand Down
55 changes: 36 additions & 19 deletions 10.3/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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\_%' ;
Expand All @@ -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}"
Expand All @@ -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() {
Expand Down
55 changes: 36 additions & 19 deletions 10.4/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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\_%' ;
Expand All @@ -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}"
Expand All @@ -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() {
Expand Down
Loading

0 comments on commit 58f4020

Please sign in to comment.