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 依赖于该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命令加入环境变量。
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_password和connection_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账户以外,其它所有账户不应访问系统数据库mysql的user表。 - 了解MySQL访问权限系统的工作原理。使用
GRANT和REVOKE语句控制对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_password和connection_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_cipher和Ssl_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