MySQL8.0裡引入了不少關于權限的改動,從這些改動可以看出來,權限管理更加的規範和周遊了,這和我們之前為rds mysql增加了大量權限管理很類似,想來Oracle也是通過這些改動為其雲業務服務的吧。
本文主要簡述下部分相關的權限改動,不會涉及代碼實作部分。目前版本為8.0.16
Atomic ACL Statement
由于實作了新的資料詞典表,所有的權限相關的資訊都存儲在innodb mysql tablespace裡。而innodb是事務性引擎,具有ACID特性,是以對應的ACL操作也具有原子特性。
例如之前如果一個語句對多個user操作的時候,有些成功,有些會失敗。而現在則是要麼全部成功,要麼全部失敗。binlog也會在事務送出時記錄到redo log裡。
這裡有個問題是當我們通過搭建備庫的方式從5.7更新到8.0時,那些在5.7部分成功的acl操作,到了以8.0作為備庫的執行個體上會全部失敗.
關于atomic ddl 見
官方文檔Role
Role是一個期待已久的功能,可以認為是一組權限的集合, 你可以為多個賬戶賦予相同的role權限,這也使得權限的管理更加規範,大大友善了運維和管理。你可以通過 create role 'role_name' 建立一個role名,然後再通過grant語句為role賦予權限。之後就可以grant 'role_name' to 一個指定的賬戶了。
關于role,之前寫了一篇文章介紹了,這裡不再贅述,感興趣的點
連結參考:
connection control plugin
引入了一個新的插件,代碼在plugin/connection_control/下,該插件使用的是audit plugin接口,其功能是在數次登陸失敗後,會延遲下次登陸的時間,這也有點類似于多次密碼輸入錯誤,會被當機一會的意思。
在lib/plugin目錄下,我們已經編譯好了插件connection_control.so,安裝也比較簡單:
mysql> INSTALL PLUGIN CONNECTION_CONTROL SONAME 'connection_control.so';
Query OK, 0 rows affected (0.01 sec)
mysql> INSTALL PLUGIN CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS SONAME 'connection_control.so';
Query OK, 0 rows affected (0.03 sec)
mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE 'connection%'\G
*************************** 1. row ***************************
PLUGIN_NAME: CONNECTION_CONTROL
PLUGIN_STATUS: ACTIVE
*************************** 2. row ***************************
PLUGIN_NAME: CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS
PLUGIN_STATUS: ACTIVE
2 rows in set (0.00 sec)
mysql> SHOW VARIABLES LIKE '%connection%control%';
+-------------------------------------------------+------------+
| Variable_name | Value |
+-------------------------------------------------+------------+
| connection_control_failed_connections_threshold | 3 |
| connection_control_max_connection_delay | 2147483647 |
| connection_control_min_connection_delay | 1000 |
+-------------------------------------------------+------------+
3 rows in set (0.00 sec)
如何使用:
connection_control_failed_connections_threshold: 允許失敗的次數,在這麼多次失敗後,會去增加delay的時間(設定為0則表示關閉該特性,不會去增加延遲)
當超出失敗上限後,就根據之後失敗的測試乘以connection_control_min_connection_delay作為delay時間,但最大不超過connection_control_max_connection_delay, 以預設配置為例子,當第四次失敗時是1000毫秒,當第五次失敗時就加倍到2000毫秒
支援雙重密碼
這也是個有趣的特性,意思是支援一個賬戶兩個密碼,這通常發生在你修改了密碼,但又不想導緻正在運作的業務中斷時。如worklog所述,當你有大規模的複制叢集時,又想修改複制密碼,當然不希望正在進行的複制中斷拉。那怎麼辦,可以在保持兩個密碼在一段時間内都是有效的。用法也比較簡單,我們舉個簡單的例子:
root@test 10:07:00>CREATE USER arthurdent@localhost IDENTIFIED WITH 'mysql_native_password' BY 'abcd';
Query OK, 0 rows affected (0.00 sec)
# 再建立一個密碼,同時保持目前密碼
root@test 10:07:02>ALTER USER arthurdent@localhost IDENTIFIED BY 'efgh' RETAIN CURRENT PASSWORD;
Query OK, 0 rows affected (0.01 sec)
#再建立一個密碼,同時保持目前密碼,但是第一個建立的密碼abcd就失效了
root@test 10:07:18>ALTER USER arthurdent@localhost IDENTIFIED BY 'efghh' RETAIN CURRENT PASSWORD;
Query OK, 0 rows affected (0.01 sec)
如果要抛棄舊密碼,可以執行如下語句
root@test 10:11:36>ALTER USER arthurdent@localhost DISCARD OLD PASSWORD;
Query OK, 0 rows affected (0.00 sec)
此時你再通過舊密碼efgh就無法成功登入了。
mysql.user表被擴充了來存儲兩個密碼,主密碼存儲在mysql.user.authentication_string中,次要密碼存儲在mysql.user.user_attributes中
root@test 10:31:36>select user, authentication_string, user_attributes from mysql.user where user = 'arthurdent'\G
*************************** 1. row ***************************
user: arthurdent
authentication_string: *7538919BBFC125D3F772537519E66F8242CD2E6B
user_attributes: {"additional_password": "*1ACFAF7821CBE8E2D6B7C3FA1A539F53CB41BB9D"}
1 row in set (0.00 sec)
除了ALTER USER外,SET PASSWORD也支援類似的文法:
SET PASSWORD [FOR user] = 'auth_string'
[REPLACE 'current_auth_string']
[RETAIN CURRENT PASSWORD]
參考文檔:
WL#11540: Support 2 active passwords per user accountPartial Revoker
在之前如果你有create user權限,相應的也有了drop/create/modify任何賬戶的權限,包括root賬戶。 如果使用者有delete/update權限的話,甚至還可以修改grant系統表, 因為有的時候我們需要把部分權限revoke掉
worklog舉了個例子,這裡直接列出來啦:
mysql@root> CREATE USER foo;
mysql@root> GRANT CREATE USER,UPDATE,DELETE ON *.* TO foo WITH GRANT OPTION;
mysql@root> GRANT SELECT ON mysql.* TO foo with grant option;
Now, foo has the ability to do the following:
mysql@foo>CREATE USER bar;
mysql@foo>ALTER USER root@localhost IDENTIFIED BY 'gibberish';
mysql@foo>DROP USER root@localhost;
mysql@foo>DELETE FROM mysql.user WHERE user = 'root';
mysql@foo>UPDATE mysql.user SET authentication_string = 'gibberish'
WHERE user='root';
如上例,當foo使用者有了由root賬戶賦予的grant權限,他甚至可以去操作root賬戶。這個worklog的目的,就確定foo使用者無法對root賬戶進行操作。
這個worklog把權限定義為三類:
- Global Privileges: DDL/DML privileges that allow object manipulation on all
databases. This includes administrative privileges,
dynamic privileges.
- Database Privileges: Restricted to a one (or more) databases.
They provide ability to manipulate objects and
data within database.
- Restrictions_list: List of tuples - (user, database, privileges).
Each entry in the list represents operations prohibited on
a given database for given user. Restrictions list implies
that even if user is granted GLOBAL privileges, if
revocation list prevents the operation, user can not perform
it for given database.
其中restrictions_list存儲在mysql.user表中,主要是引入Partial revoke, 可以revoke部分庫上的權限,例如mysql庫,這實際上對于雲業務而言是非常重要的功能:使用者通常希望擁有超級權限,但雲平台本身也有保留的賬号做維護用,這些我們是不希望被修改的,舉個簡單的例子:
root@(none) 09:26:43>CREATE USER foo;
Query OK, 0 rows affected (0.00 sec)
root@(none) 09:26:49>GRANT ALL ON *.* TO foo;
Query OK, 0 rows affected (0.00 sec)
root@(none) 09:27:00>SET GLOBAL partial_revokes = 0;
Query OK, 0 rows affected (0.00 sec)
root@(none) 09:27:05>REVOKE INSERT ON mysql.* FROM foo;
ERROR 1141 (42000): There is no such grant defined for user 'foo' on host '%'
root@(none) 09:27:12>SET GLOBAL partial_revokes = 1;
Query OK, 0 rows affected (0.00 sec)
root@(none) 09:27:14>REVOKE INSERT ON mysql.* FROM foo;
Query OK, 0 rows affected (0.00 sec)
root@(none) 09:27:24>REVOKE DELETE ON mysql.* FROM foo;
Query OK, 0 rows affected (0.00 sec)
這裡引入了一個全局參數partial_revokes, 隻有打開了,你才能對賬戶做partial revoke操作,這裡會産生一個對該賬戶的限制清單,存儲在mysql庫中:
root@(none) 09:29:08>select user, authentication_string, user_attributes from mysql.user where user = 'foo'\G
*************************** 1. row ***************************
user: foo
authentication_string:
user_attributes: {"Restrictions": [{"Database": "mysql", "Privileges": ["INSERT", "DELETE"]}]}
1 row in set (0.00 sec)
可以看到針對該賬戶産生了一個限制清單Restrictions, 以json的形式存儲。Partial Revoke的限制(摘自文檔):
- Partial revokes must name the schema literally. Schema names that contain the % or _ SQL wildcard characters (for example, myschema%) are not permitted.
- It is possible to use partial revokes to place restrictions on nonexistent schemas, but only if the revoked privilege is granted globally. If a privilege is not granted globally, revoking it for a nonexistent schema produces an error.
- Partial revokes apply at the schema level only. You cannot use partial revokes for privileges that apply only globally (such as FILE or BINLOG_ADMIN), or for table, column, or routine privileges.
當一個有restrictions list的賬戶再去建立别的賬戶時,他受限的清單也會傳遞出去
在wl#12098中還引入了system user這樣的權限類型,隻有相同權限的賬戶才能修改這種類型的賬戶,普通賬戶無權對其進行修改。在之後又在wl#12364中,避免擁有CONNECTION_ADMIN權限的普通使用者能夠去kill超級使用者的session或者query:
root@(none) 08:20:40>GRANT SYSTEM_USER ON *.* TO foo;
Query OK, 0 rows affected (0.00 sec)
root@(none) 08:20:54>GRANT SYSTEM_USER ON *.* TO bar;
Query OK, 0 rows affected (0.01 sec)
baz@(none) 08:27:38>GRANT CONNECTION_ADMIN ON *.* to baz;
Query OK, 0 rows affected (0.00 sec)
#login foo
foo@(none) 08:27:10>show grants;
+---------------------------------------+
| Grants for foo@% |
+---------------------------------------+
| GRANT USAGE ON *.* TO `foo`@`%` |
| GRANT SYSTEM_USER ON *.* TO `foo`@`%` |
+---------------------------------------+
2 rows in set (0.00 sec)
foo@(none) 08:28:04>show processlist;
+-----+------+-----------+------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+------+-----------+------+---------+------+----------+------------------+
| 348 | foo | localhost | NULL | Query | 0 | starting | show processlist |
+-----+------+-----------+------+---------+------+----------+------------------+
1 row in set (0.00 sec)
#login baz
baz@(none) 08:29:03>show grants;
+--------------------------------------------+
| Grants for baz@% |
+--------------------------------------------+
| GRANT USAGE ON *.* TO `baz`@`%` |
| GRANT CONNECTION_ADMIN ON *.* TO `baz`@`%` |
+--------------------------------------------+
2 rows in set (0.00 sec)
baz@(none) 08:29:05>show processlist;
+-----+------+-----------+------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+------+-----------+------+---------+------+----------+------------------+
| 349 | baz | localhost | NULL | Query | 0 | starting | show processlist |
+-----+------+-----------+------+---------+------+----------+------------------+
1 row in set (0.00 sec)
#baz賬戶隻能看到自己的線程,如果強制去kill foo呢 ?
baz@(none) 08:30:30>kill 348;
ERROR 1095 (HY000): You are not owner of thread 348
可以看到有connection_admin權限的賬戶被限制了,不僅無法看到system_user的連結,也無法去kill session.
簡單來說,有system_user權限的賬戶可以修改system user和regular user的賬戶;而regular user則無法修改system user的賬戶
關于這塊官方文檔有非常詳細的内容,筆者對這塊也不太熟悉,就不多說了,感興趣的直接翻閱如下文檔吧:
WL#12098: MySQL system users WL#12364: Kill administration for system users WL#12820: Extend GRANT syntax to cover partial revokes information Privilege Restriction Using Partial Revokes Account Categories 官方部落格:The SYSTEM_USER Dynamic Privilege 官方部落格:Partial Revokes from Database Objects 官方部落格:How to create multiple accounts for an app?Password Expiration
可以設定密碼過期時間,提供了三種操作:
- 通過參數 default_password_lifetime 來控制 , 機關為天
root@(none) 09:21:31>SET PERSIST default_password_lifetime = 180;
Query OK, 0 rows affected (0.00 sec)
該選項的值會被alter user覆寫
- 通過ALTER USER來控制
指定過期時間
CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
過期時間存儲在mysql.user表中
root@(none) 09:35:46>select user,password_lifetime from mysql.user where user = 'jeffrey'\G
*************************** 1. row ***************************
user: jeffrey
password_lifetime: 90
1 row in set (0.00 sec)
禁止密碼過期
CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE NEVER;
ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE NEVER;
預設過期時間為default_password_lifetime:
CREATE USER 'jeffrey'@'localhost' PASSWORD EXPIRE DEFAULT;
ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE DEFAULT;
- 直接手動過期
ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE;
WL#6587 : Protocol support for password expiration 密碼複用
現在很多系統在忘記密碼重設時,都會要求最近幾次使用付的密碼不允許再次使用,這也是為了安全考慮,MySQL也增加了這樣的功能,和密碼過期類似,也可以通過全局變量,ALTER USER來控制:
例如如下配置:
password_history=6
password_reuse_interval=365
表示不要服用最近6次用到的密碼或者365天内用過的密碼。
也可以通過create/alter user來設定:
CREATE USER 'jeffrey'@'localhost' PASSWORD HISTORY 5;
ALTER USER 'jeffrey'@'localhost' PASSWORD HISTORY 5;
CREATE USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL 365 DAY;
ALTER USER 'jeffrey'@'localhost' PASSWORD REUSE INTERVAL 365 DAY;
同樣的也可以把上例中的history 5 和 interval 365 day指定為default
WL#6595: Password rotation policy修改賬戶要求驗證
同樣是安全相關的,當修改一個賬戶時,需要去驗證密碼,可以使用參數
password_require_current來控制。預設關閉,當打開該選項時,如果要修改賬戶密碼,必須要提供目前的密碼才允許修改,如下摘錄的官方示例:
要求在修改時輸入目前密碼:
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT;
ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT;
可選的輸入目前密碼(感覺有點多餘...)
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT OPTIONAL;
ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT OPTIONAL;
根據參數配置來決定:
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT;
ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT;
那麼修改密碼時就需要顯示目前密碼:
CREATE USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT;
ALTER USER 'jeffrey'@'localhost' PASSWORD REQUIRE CURRENT DEFAULT;
SET PASSWORD也一樣.
SET PASSWORD [FOR user] = password_option
password_option : {
'auth_string' [REPLACE 'auth_string']
}
WL#11544 Current password required for SET PASSWORD 限制SET PERSIST
MySQL提供了線上持久化參數修改的功能,通過接口SET PERSIST 和SET PERSIST ONLY來實作,但有些涉及敏感資訊的變量則不應該被persist, 是以不應該通過遠端終端來管理,而是要管理者登入機器,手動的修改my.cnf
新增參數persist_only_admin_x509_subject , 當打開這個參數時,隻有通過SSL認證的使用者才能Persist一些受限的系統參數。
列舉了些可持久化的參數和不可持久化的參數
參數:persist_only_admin_x509_subject Nonpersistible and Persist-Restricted System Variablesskip-grant-tables
用過的人的都知道,當以skip-grant-tables啟動時候,系統将不檢查任何權限,這是是很危險的,但有時候如果application和資料庫執行個體部署在同一台機器時,我們又可以通過該選項來獲得更好的性能,但帶來的風險是其他人隻要知道host和端口号,也可以遠端連接配接過來,這就有資料安全問題
是以MySQL加入了新選項
skip_networking,不再監聽tcp/ip連接配接請求。
另外最近也修複了一個有趣的
bug#94394,當mysql.user表損壞時,執行個體啟動時僅僅列印了一條錯誤資訊,并以skip-grant-tables的方式啟動了。這實際上市不安全的,人們可能在install初始化階段不小心忽略這個錯誤,而後資料庫的正常運作,也會造成執行個體正确安裝的錯覺。
是以在8.0.16版本中,官方修複了這個問題,除非使用者指定skip-grant-tables,執行個體将列印資訊之後直接啟動失敗。
fk error不顯示父表資訊
這個修複很簡單,就是說對父表沒權限的使用者,如果在子表上因為foreign key限制,導緻錯誤的話,不應該将父表的資訊暴露出來,這可能導緻安全問題,而是傳回統一的錯誤:
ERROR 23000: Cannot add or update a child row: a foreign key constraint fails
WL#8910: Ensure foreign key error does not reveal information about parent table for which user has no access privileges. SESSION_VARIABLES_ADMIN
通常任何賬戶都允許設定session級别的變量,但某些session級别的變量隻能特定權限的使用者設定,例如binlog_format, sql_log_bin,火鶴sql_log_off等,需要需要SYSTEM_VARIABLES_ADMIN或者SUPER權限來設定。
從MySQL8.0.14開始了增加了一個新的權限位session_variables_admin, wl#12217列出了一些需要該權限位的變量:
The following vairables need to enforce SESSION_VARIABLES_ADMIN:
auto_increment_increment
auto_increment_offset
binlog_direct_non_transactional_updates
bulk_insert_buffer_size
character_set_database
character-set-filesystem
collation_database
pseudo_slave_mode
pseudo_thread_id
transaction_write_set_extraction
rbr_exec_mode
The following variables will not be protected:
default_storage_engine
default_tmp_storage_engine
max_allowed_packet
rand_seed1
rand_seed2
These variables should transition from checking SYSTEM_VARIABLES_ADMIN to
SESSION_VARIABLES_ADMIN:
histogram_generation_max_mem_size
sql_log_off
debug_sync
original_commit_timestamp
The not documented gtid_next
The disabled and not documented gtid_next_list
default_collation_for_utf8mb4
explicit_defaults_for_timestamp
sql_log_bin
explicit_defaults_for_timestamp The variable is mis-documented as not requiring SYSTEM_VARIABLES_ADMIN for SET SESSION. But in reality it does require it. Since the variable is deprecated we'll keep the current behavior.
binlog_format
binlog_row_image
binlog_row_value_options
binlog_rows_query_log_events
官方文檔:SESSION_VARIABLES_ADMIN WL#12217: SESSION_VARIABLE_ADMIN