如何安全的使用 bash 操作 MySQL

最近在 percona blog 中看到这篇文章 use-mysql-shell-securely-from-bash , 讲述如何在 Bash 中安全的使用 MySQL 进行查询. 字面意思的安全要比文章所说的更宽泛, 不过这篇文章有几点可取之处值得学习, 因为 Bash 的方便性, 我们经常用到 Bash 来完成各种脚本任务, 如果操作数据库的话(比如备份, 监控等), 任何可以执行 ps aux 命令的用户都可以看到正在执行的命令, 一些敏感信息如配置文件, 用户密码等都可以看到, 另外在 MySQL 5.6 之后命令行下用密码连接数据库也会得到警告信息 Warning: Using a password on the command line interface can be insecure.

如何隐藏进程中的密码信息是我们都需要关注的, 文章中的作者用了很巧妙的方式来达到隐藏的目的, 甚至 ip, port 等也可以隐藏, 并且 5.6 版本中的 warning 信息也不会再出现, 我在下面会基于此技巧进行阐述, 另外会稍作修改以满足其它场景的需要.

percona 文章中介绍的技巧主要通过管道和 MySQL 的 --defaults-file 参数来实现隐藏的目的, 如下 bash 函数 mysql_exec 所示:

mysql_exec() {
  local query="$1"
  local opts="$2"
  mysql_exec_result=$(
    printf "%s\n" \
      "[client]" \
      "user=${mysql_user}" \
      "password=${mysql_password}" \
      "host=${mysql_host}" \
      "port=${mysql_port}" \
      "database=${mysql_database}" \
      | mysql --defaults-file=/dev/stdin "${opts}" -e "${query}"
  )

printf 输出 mysql client 相关的配置, 类似默认配置 my.cnf 里的 [client] 部分, 然后通过管道将相关的配置信息给到 --defaults-file 指定的 /dev/stdin 标准输入中. 很巧妙的使用了 stdin 来实现隐藏的目的, 再用 ps 查看进程的话就是类似下面的输出:

root      5051  4899  0 15:31 pts/0    00:00:00 mysql --defaults-file=/dev/stdin -e select sleep(10)

下面部分是 blog 中介绍的第二点 , 通过将配置保存到临时文件中, 这样就可以使用 mysql <file.sql 的方式导入 sql 文件, 这种方式和第一点是类似的:

mysql_exec_from_file() {
  local query_file="$1"
  local opts="$2"
  local tmpcnf="$(mktemp)"
  chmod 600 "${tmpcnf}"
  printf "%s\n" 
    "[client]" 
    "user=${mysql_user}" 
    "password=${mysql_password}" 
    "host=${mysql_host}" 
    "port=${mysql_port}" 
    "database=${mysql_database}" 
    > "${tmpcnf}" 
  mysql_exec_from_file_result=$(
      mysql --defaults-file="${tmpcnf}" "$opts" < "${query_file}"
  )
  rm "${tmpcnf}" # unlink "${tmpcnf}"
}

到这里, 很多人会有一个疑问, 这个问题在 blog 评论部分也有人提到, 也就是为什么下面的方式是错误的:

printf ..... | mysql --defaults-file=/dev/stdin ${opts} < dump.sql

我们手工执行后报错为:

error: Found option without preceding group in config file: /dev/stdin at line: 1
Fatal error in defaults handling. Program aborted

报错信息是和 config file 相关的, 由此可知 < dump.sql 的内容重定向到了 /dev/stdin 中, 成为了配置信息的一部分. 我们可以做下相应的更改就可以实现同样的功能:

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

其它的编程语言提供了更好的方式来访问 MySQL 数据库, 如果脚本执行的时候还是显示的存在密码等信息, 大家就可以借鉴上述的方法来隐藏敏感的信息.

另外, mysql 5.6.6 版本开始提供了 mysql_config_editor 工具对 [client] 相关的配置进行加密, 如何查看解密的信息参见 get-passwords-plain-text-mylogin-cnf.