lamess / MySQL安装、初始化及安全加固

Created Tue, 28 May 2024 17:34:44 +0800 Modified Tue, 04 Mar 2025 17:23:18 +0800

MySQL安装、初始化及安全加固

说明

本文基于MySQL9.0.0版本。内容来源于官方文档及网络资料。
MySQL Document

软件下载

查询当前Glibc版本

# ldd --version

ldd (GNU libc) 2.34

CentOS7.9的Glibc的版本是2.12,MySQL官方已经提供了相应版本的Glibc编译的MySQL。
而在openEuler2403上Glibc是2.38版本,MySQL官方只提供了最高2.28版本Glibc编译的MySQL,只能使用此版本了。
一般来说低版本的Glibc不可使用高版本Glibc编译的MySQL,反过来是可以的但是版本差距不可太大。
选择下载操作系统对应的版本的MySQL文件。

通过wget命令下载MySQL:

wget -c https://dev.mysql.com/get/Downloads/MySQL-9.0/mysql-9.0.0-linux-glibc2.28-x86_64.tar.xz

也可以到MySQL官方发布页面下载Glibc版本的软件包。

MySQL下载

环境依赖

MySQL 依赖于该libaio 库。如果未在本地安装此库,则数据目录初始化和后续服务器启动步骤将失败。如有必要,请使用适当的包管理器安装它。例如,在基于 Yum 的系统上:

yum search libaio
yum install libaio

基础配置

创建MySQL进程专用的用户,这里创建了mysql用户组和mysql用户。

groupadd mysql
useradd -g mysql -s /bin/false mysql

这里设计的数据目录是/opt/data/mysql/,程序目录软链接到/usr/local/mysql/

将下载的MySQL程序包解压到程序目录,并调整文件夹权限。新解压的文件要重新更改权限。注意命令中的斜杠/

tar -xvf mysql-9.0.0-linux-glibc2.28-x86_64.tar.xz -C /usr/local/
ln -s /usr/local/mysql-9.0.0-linux-glibc2.28-x86_64/ /usr/local/mysql
chown -R mysql:mysql /usr/local/mysql/
mkdir -p /opt/data/

检查文件夹权限。

MySQL文件权限

将MySQL命令加入环境变量。

echo 'export PATH=$PATH:/usr/local/mysql/bin/' >> /etc/profile
source /etc/profile

配置systemd

添加systemd服务单元配置文件。

cat > /usr/lib/systemd/system/mysqld.service << EOF
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target

[Install]
WantedBy=multi-user.target

[Service]
User=mysql
Group=mysql

# Have mysqld write its state to the systemd notify socket
Type=notify

# Disable service start and stop timeout logic of systemd for mysqld service.
TimeoutSec=0

# Start main service
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf \$MYSQLD_OPTS 

# Use this to switch malloc implementation
EnvironmentFile=-/etc/sysconfig/mysql

# Sets open_files_limit
LimitNOFILE = 65535
LimitNPROC  = 65535

Restart=on-failure

RestartPreventExitStatus=1

# Set environment variable MYSQLD_PARENT_PID. This is required for restart.
Environment=MYSQLD_PARENT_PID=1

PrivateTmp=false
EOF
systemctl daemon-reload

数据初始化

创建my.cnf配置文件。

cat > /etc/my.cnf << EOF
[client]
port    =   3306
socket  =   /tmp/mysql.sock
[mysqld]
port    =   3306
user    =   mysql
socket  =   /tmp/mysql.sock
basedir =   /usr/local/mysql
datadir =   /opt/data/mysql
log-error   =   error.log
pid-file    =   mysql.pid
default-time-zone   =   '+8:00'
skip-log-bin
EOF
chmod 400 /etc/my.cnf
chown mysql:mysql /etc/my.cnf

执行初始化。如果目录非空,需要删除原有数据。

/usr/local/mysql/bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql/ --datadir=/opt/data/mysql/

等待初始化结束,查询error.log,得到生成的随机密码。

cat /opt/data/mysql/error.log | grep "temporary password" | awk '{print $NF}'

启动MySQL,修改临时密码

systemctl start mysqld
mysql --user='root' --password

mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'Changeme_123';

在MySQL命令行执行查询,安装密码验证组件。安装完成后,退出MySQL命令行。

mysql> INSTALL COMPONENT 'file://component_validate_password';
mysql> SELECT * FROM mysql.component;
+--------------+--------------------+------------------------------------+
| component_id | component_group_id | component_urn                      |
+--------------+--------------------+------------------------------------+
|            1 |                  1 | file://component_validate_password |
+--------------+--------------------+------------------------------------+
mysql> exit

my.cnf中增加validate_passwordconnection_control配置。

mkdir -p /usr/local/mysql/mysql-files
chown -R mysql:mysql /usr/local/mysql/mysql-files
chmod 750 /usr/local/mysql/mysql-files
chmod 755 /etc/my.cnf
cat > /etc/my.cnf << EOF
[client]
port    =   3306
socket  =   /tmp/mysql.sock
[mysqld]
port    =   3306
user    =   mysql
socket  =   /tmp/mysql.sock
basedir =   /usr/local/mysql
datadir =   /opt/data/mysql
log-error   =   error.log
pid-file    =   mysql.pid
default-time-zone   =   '+8:00'
local_infile        =   OFF
max_connections     =   1000
admin_address       =   127.0.0.1
secure_file_priv    =   /usr/local/mysql/mysql-files
create_admin_listener_thread        =   1
block_encryption_mode               =   aes-256-cbc
default_password_lifetime           =   120
password_history                    =   12
password_reuse_interval             =   1095
generated_random_password_length    =   20
password_require_current            =   1
validate_password.policy                =   1
validate_password.length                =   8
validate_password.number_count          =   1
validate_password.mixed_case_count      =   1
validate_password.special_char_count    =   1
validate_password.check_user_name       =   1
plugin-load-add                                 =   connection_control.so
connection-control                              =   FORCE_PLUS_PERMANENT
connection-control-failed-login-attempts        =   FORCE_PLUS_PERMANENT
connection_control_failed_connections_threshold =   5
connection_control_min_connection_delay         =   300000
connection_control_max_connection_delay         =   2147483647
EOF
chmod 400 /etc/my.cnf
chown mysql:mysql /etc/my.cnf

也可以在my.cnf文件中按需指定监听IP地址

[mysqld]
bind_address    =   192.0.2.24

重启MySQL

systemctl restart mysqld

业务配置

登录MySQL,新建业务账户。创建业务数据库。根据实际需要可以使用使用CIDR表示法约束业务账户的IP地址。
例如'username'@'192.168.100.0/24'。不建议直接使用%以允许所有来源的用户访问,形如'username'@'%'的权限 为业务账户单独授予业务数据库的权限。

这里使用IDENTIFIED BY RANDOM PASSWORD直接生成随机密码。也可以按需使用IDENTIFIED BY 'password'指定密码

mysql --user="root" --password

mysql> CREATE DATABASE `project`;
mysql> CREATE USER 'username'@'192.168.100.0/24' IDENTIFIED BY RANDOM PASSWORD;
mysql> GRANT ALL PRIVILEGES ON `project`.* TO 'username'@'192.168.100.0/24';
mysql> FLUSH PRIVILEGES;

至此,完成MySQL的安装与初始化。

可以将MySQL设置开机自启动。

systemctl enable mysqld

安全指南

综述

  • 除了root账户以外,其它所有账户不应访问系统数据库mysqluser表。
  • 了解MySQL访问权限系统的工作原理。使用GRANTREVOKE语句控制对MySQL的访问,只授予必要的权限,并且永远不向全局访问%授予ALL权限。
    • 检查root账户的密码强度。
    • 使用SHOW GRANTS语句检查权限信息,并使用REVOKE语句删除不必要的权限。
  • 不要在数据库中存储明文密码。应使用SHA2()或其它哈希算法等方式进行密码加密存储。
  • 保持密码强度,保证密码不会以可预测的顺序出现。
  • 使用防火墙保护MySQL,禁止从不受信任的主机进行连接。建议将MySQL放入DMZ防火墙隔离区。
  • 访问MySQL的应用程序不应信任用户输入的任何数据,并且应使用适当的防御性编程技术编写。
  • 避免直接从互联网传输未加密数据,应使用SSL或SSH对数据传输进行加密。
  • 使用tcpdump命令和string命令检查MySQL数据流是否加密
tcpdump -l -i eth0 -w - src or dst port 3306 | strings

密码使用

  • 在命令行中连接MySQL时,避免明文输入密码。例如:
mysql dbname --user="lamess" --password 
Enter password: ********

如果在配置文件my.cnf中存储了密码信息,例如:

[client]
password=password

应将文件权限设置为400

chmod 400 my.cnf

密码强度

配置密码强度要求安装密码验证组件。

mysql> INSTALL COMPONENT 'file://component_validate_password';
mysql> SELECT * FROM mysql.component;
+--------------+--------------------+------------------------------------+
| component_id | component_group_id | component_urn                      |
+--------------+--------------------+------------------------------------+
|            1 |                  1 | file://component_validate_password |
+--------------+--------------------+------------------------------------+
mysql> exit

之后在my.cnf中增加validate_passwordconnection_control配置。

[mysqld]
validate_password.policy                =   1
validate_password.length                =   8
validate_password.number_count          =   1
validate_password.mixed_case_count      =   1
validate_password.special_char_count    =   1
validate_password.check_user_name       =   1
参数 含义
validate_password.policy 控制验证策略(0:LOW;1MEDIUM;2:STRONG)
validate_password.length 密码最小长度
validate_password.number_count 密码中包含数字的最小数量
validate_password.mixed_case_count 密码中包含大小写的最小数量
validate_password.special_char_count 密码中包含特殊字符的最小数量
validate_password.check_user_name 一个复杂的密码策略参数,功能之一是禁止密码内容中包含用户名

密码过期策略

查询mysql.user表,获取所有账户的过期信息。

mysql> SELECT user,host,password_expired,password_lifetime,password_last_changed,account_locked FROM mysql.user;
+------------------+-----------+------------------+-------------------+-----------------------+----------------+
| user             | host      | password_expired | password_lifetime | password_last_changed | account_locked |
+------------------+-----------+------------------+-------------------+-----------------------+----------------+
| user             | %         | N                |              NULL | 2024-10-08 11:14:33   | N              |
| mysql.infoschema | localhost | N                |              NULL | 2024-06-05 17:27:25   | Y              |
| mysql.session    | localhost | N                |              NULL | 2024-06-05 17:27:25   | Y              |
| mysql.sys        | localhost | N                |              NULL | 2024-06-05 17:27:25   | Y              |
| root             | localhost | N                |              NULL | 2024-10-08 11:12:07   | N              |
+------------------+-----------+------------------+-------------------+-----------------------+----------------+

my.cnf中修改参数,设置全局密码过期时间。

default_password_lifetime = 120

查询全局密码自动过期时间。

mysql> SHOW VARIABLES LIKE 'default_password_lifetime';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| default_password_lifetime | 120   |
+---------------------------+-------+

执行查询以修改用户的密码过期配置。

设置指定账户使用默认的密码过期策略:

mysql> ALTER USER 'user'@'%' PASSWORD EXPIRE DEFAULT;
Query OK, 0 rows affected (0.01 sec)

设置指定账户的密码永不过期:

mysql> ALTER USER 'user'@'%' PASSWORD EXPIRE NEVER;
Query OK, 0 rows affected (0.01 sec)

设置指定账户的密码的过期时间:

mysql> ALTER USER 'user'@'%' PASSWORD EXPIRE INTERVAL 90 DAY;
Query OK, 0 rows affected (0.01 sec)

设置指定账户的密码立即过期:

mysql> ALTER USER 'user'@'%' PASSWORD EXPIRE;
Query OK, 0 rows affected (0.01 sec)

密码重用策略

my.cnf中修改参数,设置全局密码重用次数、重用时间间隔策略。

password_history = 12
password_reuse_interval = 1095

查询全局密码重用策略参数。

mysql> SHOW VARIABLES LIKE 'password_history';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| password_history | 12    |
+------------------+-------+
mysql> SHOW VARIABLES LIKE 'password_reuse_interval';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| password_reuse_interval | 1095  |
+-------------------------+-------+

password_history参数限制了密码修改时,新密码不能与历史密码重复。此处限制历史密码个数为12个。
password_reuse_interval参数限制了密码修改时,新密码不能与过去时间段内使用过的密码重复。此处配置为1095天。

安全加固

执行一次安全增强配置命令。此过程将要求更新root用户的密码,禁用root用户远程登录,并且删除名为test的测试数据库。

/usr/local/mysql/bin/mysql_secure_installation

启用SSL

MySQL在初始化时会自动在数据目录下生成SSL证书和密钥文件以及RSA密钥对文件。

ca.pem               Self-signed CA certificate
ca-key.pem           CA private key
server-cert.pem      Server certificate
server-key.pem       Server private key
client-cert.pem      Client certificate
client-key.pem       Client private key

执行查询可以检查SSL证书过期信息。自动生成的SSL证书的有效时间是十年。

mysql --user="root" --password

mysql> SHOW STATUS LIKE 'Ssl_server_not%';
+-----------------------+--------------------------+
| Variable_name         | Value                    |
+-----------------------+--------------------------+
| Ssl_server_not_after  | Jan 01 00:00:00 2034 GMT |
| Ssl_server_not_before | Jan 01 00:00:00 2024 GMT |
+-----------------------+--------------------------+
mysql> exit

my.cnf中增加参数,启用SSL。

ssl_ca                      =   ca.pem
ssl_cert                    =   server-cert.pem
ssl_key                     =   server-key.pem
tls_version                 =   TLSv1.3
tls_ciphersuites            =   TLS_AES_128_GCM_SHA256
require_secure_transport    =   ON

重启MySQL,验证配置生效。

systemctl restart mysqld
cd /opt/data/mysql/
mysql --ssl-ca=ca.pem --ssl-cert=client-cert.pem --ssl-key=client-key.pem --ssl-mode=VERIFY_CA --user="root" --password

mysql> SHOW SESSION STATUS LIKE 'Ssl_cipher';
+---------------+------------------------+
| Variable_name | Value                  |
+---------------+------------------------+
| Ssl_cipher    | TLS_AES_128_GCM_SHA256 |
+---------------+------------------------+

mysql> SHOW SESSION STATUS LIKE 'Ssl_version';
+---------------+---------+
| Variable_name | Value   |
+---------------+---------+
| Ssl_version   | TLSv1.3 |
+---------------+---------+

如果使用了SSL连接,两个变量Ssl_cipherSsl_version都不为空值。

如果不使用MySQL自带的证书,或者因为其它原因需要重新生成,可以使用OpenSSL命令生成自签名的证书。详见《使用OpenSSL为MySQL生成SSL证书》

使用SSL连接时,可以用tcpdump确认报文传输已经加密。

tcpdump -l -i eth0 -w - src or dst port 3306 | strings

启用连接控制插件

直接在my.cnf中增加配置以启用连接控制插件。

plugin-load-add                                 =   connection_control.so
connection-control                              =   FORCE_PLUS_PERMANENT
connection-control-failed-login-attempts        =   FORCE_PLUS_PERMANENT
connection_control_failed_connections_threshold =   5
connection_control_min_connection_delay         =   300000
connection_control_max_connection_delay         =   2147483647

connection_control_min_connection_delay配置为一个非零值N时,用户连续连接失败小于等于N次,延迟为零; 此后服务端会增加服务响应的延迟,直到连接成功。延迟时间从connection_control_min_connection_delay开始每次失败递增1秒,直到connection_control_max_connection_delay
connection_control_min_connection_delay等于connection_control_max_connection_delay则每次延迟时间不变。
​注意,连续失败后第一次成功的链接依然会有响应延迟,但会重置该用户的失败连接次数。

查询连续登录错误次数。

SELECT * FROM INFORMATION_SCHEMA.CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS;

强制释放所有失败连接,重置计数。

SET GLOBAL connection_control_failed_connections_threshold=5;

启用通用查询日志

通用查询日志 (General Query Log) 记录了客户端的登录日志以及所有执行的SQL语句。在判断客户端可能存在错误,并且需要获知客户端发送到服务端的确切内容时,可以考虑使用通用查询日志。

查询启用状态

登录MySQL,查询general_log变量。

mysql --user="root" --password

mysql> SHOW VARIABLES LIKE 'general_log%';
+------------------+-------------------------------+
| Variable_name    | Value                         |
+------------------+-------------------------------+
| general_log      | OFF                           |
| general_log_file | /opt/data/mysql/localhost.log |
+------------------+-------------------------------+

默认情况下general_log为关闭状态。

启用通用查询日志

修改my.cnf文件,新增配置信息,启用通用查询日志。

[mysqld]
general_log=ON
general_log_file=/opt/data/mysql/general.log

重启MySQL生效。

重启后登入MySQL确认环境变量信息。

mysql --user="root" --password

mysql> SHOW VARIABLES LIKE 'general_log%';
+------------------+-----------------------------+
| Variable_name    | Value                       |
+------------------+-----------------------------+
| general_log      | ON                          |
| general_log_file | /opt/data/mysql/general.log |
+------------------+-----------------------------+

日志已经启用,可以跟踪查询日志内容。

tail -f /opt/data/mysql/general.log

注意通用查询日志占用磁盘空间较大,应设立专门的管理机制进行维护,避免写入量过大导致磁盘空间不足。


my.cnf配置示例

mkdir -p /usr/local/mysql/mysql-files
chown -R mysql:mysql /usr/local/mysql/mysql-files
chmod 750 /usr/local/mysql/mysql-files
chmod 755 /etc/my.cnf
cat > /etc/my.cnf << EOF
[client]
port    =   3306
socket  =   /tmp/mysql.sock
[mysqld]
port    =   3306
user    =   mysql
socket  =   /tmp/mysql.sock
basedir =   /usr/local/mysql
datadir =   /opt/data/mysql
log-error   =   error.log
pid-file    =   mysql.pid
max_connections     =   1000
default-time-zone   =   '+8:00'
local_infile        =   OFF
admin_address       =   127.0.0.1
secure_file_priv    =   /usr/local/mysql/mysql-files
create_admin_listener_thread        =   1
block_encryption_mode               =   aes-256-cbc
default_password_lifetime           =   120
password_history                    =   12
password_reuse_interval             =   1095
generated_random_password_length    =   20
password_require_current            =   1
validate_password.policy                =   1
validate_password.length                =   8
validate_password.number_count          =   1
validate_password.mixed_case_count      =   1
validate_password.special_char_count    =   1
validate_password.check_user_name       =   1
plugin-load-add                                 =   connection_control.so
connection-control                              =   FORCE_PLUS_PERMANENT
connection-control-failed-login-attempts        =   FORCE_PLUS_PERMANENT
connection_control_failed_connections_threshold =   5
connection_control_min_connection_delay         =   300000
connection_control_max_connection_delay         =   2147483647
ssl_ca                      =   ca.pem
ssl_cert                    =   server-cert.pem
ssl_key                     =   server-key.pem
tls_version                 =   TLSv1.3
tls_ciphersuites            =   TLS_AES_128_GCM_SHA256
require_secure_transport    =   ON
EOF
chmod 400 /etc/my.cnf
chown mysql:mysql /etc/my.cnf

也可以在my.cnf文件中按需指定监听IP地址。

[mysqld]
bind_address    =   192.0.2.24