Use MySQL Shell Securely from Bash

PREVIOUS POST
NEXT POST

mysql shellThis blog post discusses how to use MySQL shell securely from Bash.

The Bourne shell is everywhere. It is part of the most basic Linux install. You will find it on the biggest SPARC machines down to a Raspberry Pi. It is nice to know it will always be there. Unlike other, more complex scripting environments such as Perl and Python, it doesn’t require any additional dependencies to be installed.

Anyone that has automated a MySQL task using a Bourne shell such as Bash will be familiar with the following message:

This semi-ominous warning describes a security flaw in passing credentials on a process command line. Any unprivileged user on the system can use a command like ps aux to find these credentials. While the MySQL shell has added some additional protections to hide these credentials, other information such as database user names, host names, ports and sockets can still be determined by process scanning.

The recommended approach to get around this warning is to use a configuration file to store these credentials. However, in the case of a self-contained script, we may not want to require the user to create a credential file. It would need to be maintained, and might interfere with other tools that use the MySQL shell. For creating automated tools in the Percona Development Services department, I came up with a couple of methods for integrating the MySQL shell into Bash (or any other Bourne Shell) securely.

This first script demonstrates the shell function mysql_exec(). This is for use with small queries that are normally passed to the MySQL shell via the -e parameter.

The above script allows the specification of credentials and connection information via variables in the script. As with any other shell script, these can be moved into a configuration file and secured with chown/chmod, then included with the source or . command. The mysql_exec() function creates a default my.cnf [client] on the fly and passes it to the MySQL shell via –defaults-file=/dev/stdin. The configuration is never written to disk, which makes this method a bit more secure.

Sometimes, you need to process too many queries to pass on the command line. In this case, there is another technique for passing the credentials.

This technique uses a temporary file, which allows the queries to be passed from a file or input device. Restrictive permissions are set on the file before the configuration is written. The temporary configuration is removed immediately after the shell exits.

While other languages may offer cleaner ways to access your MySQL database, you’ll always know that you’ll be able to execute your shell-based MySQL job scripts across all of the Unix machines in your enterprise.

You can download these scripts directly from my github account.

Happy scripting!

PREVIOUS POST
NEXT POST

Share this post

Comments (21)

  • ferny Reply

    It isn’t KISS

    October 6, 2016 at 2:30 pm
  • Dave Rix Reply

    I use the mysql_config_editor utility to create a file in the users home folder, containing the connection details and an encrypted password, then use mysql --login-path=${connection} within my scripts, negating the need for the password to be in a shell script anywhere, or in plain text.
    I know it’s possible to extract the password from this file, but it makes things a lot simpler to use 🙂

    October 6, 2016 at 5:13 pm
  • Andrei ZeeGiant Reply

    Why create a script, which is stored on disk, then generates a my.cnf configuration on the fly, instead of simply creating a ~/.my.cnf with the same credentials and chmod restriction? This way you never have the password in the command line, and there are no worries about a wrapper not properly passing queries.

    October 7, 2016 at 6:23 am
    • David Bennett Reply

      “However, in the case of a self-contained script, we may not want to require the user to create a credential file. It would need to be maintained, and might interfere with other tools that use the MySQL shell.”

      October 7, 2016 at 9:55 am
      • Andrei ZeeGiant Reply

        This honestly makes no sense and just introduces another possible point of failure.

        October 13, 2016 at 1:28 am
        • Roel Van de Paar Reply

          Take the example where 1000 users need to run a given script, for example a report.

          We can now do two things;

          1) Create a script using David’s method and give the users access to that script using a Linux group. The credentials in the script can be fixed, and it’s privileges restricted in mysqld. It would be quite secure from attacks like ‘checking the process list for credentials’ etc.

          2) Create 1000 individual ~/.my.cnf files – either with a fixed credentials as in (1) – but then we have a problem when any individual needs to connect with other credentials – or with individual per-user credentials (to avoid said issue), but that would mean having to create 1000 mysqld user accounts. Thus, it would mean having introduced 2000 possible points of failure, and each one needs to be maintained – as David pointed out.

          October 13, 2016 at 2:10 am
  • bunam Reply

    is there any trics to use

    mysql_exec

    with a dump ? Like :

    …”database=${mysql_database}”\
    | mysql–defaults-file=/dev/stdin” ${opts} < dump.sql

    doesn't work as in this simplier test

    echo "un" | cat < <(echo "deux")

    show "deux"

    mysql doesn't seems to have an option to get the dump

    October 11, 2016 at 5:21 pm
    • David Bennett Reply

      Use the mysql_exec_from_file() function from the article.

      October 12, 2016 at 8:51 pm
  • chenzhe07 Reply

    the content of <dump.sql will send to /dev/stdin(the –defaults-file value), and then config file error will be occured

    October 12, 2016 at 3:19 am
    • bunam Reply

      you right

      so i have succeed with a simple query “source ;” (i have forget we can do this way)
      so ‘mysql_exec()’ is a really good function

      October 12, 2016 at 5:14 am
      • bunam Reply

        query “source the dump.sql ;”

        (wp comments didn’t like some chars i see)

        October 12, 2016 at 5:16 am
        • chenzhe07 Reply

          yes, the following is also ok:

          printf ….. | mysql –defaults-file=/dev/stdin ${opts} -e “$(cat dump.sql)”

          October 12, 2016 at 6:24 am
          • David Bennett

            Or use the mysql_exec_from_file() function

            October 12, 2016 at 8:52 pm
  • Roel Van de Paar Reply

    David, I love this inventive solution.

    It looks like a great way to securely connect without the details being visible (for example in the process list) anywhere, and without the credentials being stored anywhere except in the one location where they have to be – the script from which a function is executed.

    Thank you!

    October 12, 2016 at 6:16 pm
  • Daniël van Eeden Reply

    I was confused for a second as this is MySQL Monitor (mysql) and not MySQL Shell (mysqlsh)… But a cool solution anyways. probably more secure that using environment variables or any other option.

    October 26, 2016 at 11:40 am
  • Vadim Tkachenko Reply

    David,

    We also can provide password as

    MYSQL_PWD=secret mysql -h192.168.109.201

    November 4, 2016 at 3:34 pm
    • David Bennett Reply

      It may still be possible to access the password from the process environment:

      find /proc/ \
      -name ‘environ’ \
      -type f \
      -exec grep -q MYSQL_PWD {} \; \
      -exec sh -c “cat environ | tr ‘\0’ ‘\n’ | grep MYSQL_PWD” \; \
      2> /dev/null \
      | uniq

      December 27, 2016 at 11:41 am
  • Peter Zaitsev Reply

    This approach seems to have substantial downside. If .mylogin.cnf file is present it will override the options in defaults-file causing wrong credentials used.

    http://dev.mysql.com/doc/refman/5.7/en/mysql-config-editor.html

    “When you invoke a client program to connect to the server, the client uses .mylogin.cnf in conjunction with other option files. Its precedence is higher than other option files, but less than options specified explicitly on the client command line”

    As currently .mylogin.cnf is rarely used this is especially dangerous as it can have scripts using this method to magically break when such file is added to the system.

    December 22, 2016 at 2:09 pm
    • David Bennett Reply

      Thank you for the report Peter. Apparently the .mylogin.cnf file overrides the –defaults-file option. This issue can be resolved by changing the HOME directory for the mysql process to a location that does not contain .a mylogin.cnf file.

      By adding HOME=”/sys” before the call to mysql in the script you can set the process home directory. /sys is always read-only and will never contain mysql configuration files. This will ensure that any user configurations do not conflict with the scripts operation.

      For example:

      HOME=”/sys” mysql –defaults-file=/dev/stdin “${opts}” -e “${query}”

      I have made changes to the repository: https://github.com/dbpercona/mysql_shell_secure

      December 27, 2016 at 11:24 am

Leave a Reply