Postgres - rotate password with zero downtime
為了資安安全性的問題,定期的 rotate password 是該做的事情,但問題是:rotate password 要如何避免 application 有 downtime?
以下紀錄 PostgreSQL 如何快速 rotate password with zero downtime。
如何快速在 Postgres Rotate Password
假設 application 在用的 role name 是 app
,則整體 rotate password 策略如下:
- 建立 temp role 叫做
app_temp
並且繼承app
role 的所有權限,密碼也可以設定跟app
role 一樣 - 設定
app_temp
role 連線後自動 SET ROLE 為app
,這樣就等同用app
role 來做任何操作 - 將 application rolling update 改用
app_temp
role 對 postgres 進行連線 - 確認
app
role 已經沒有任何 application 在使用,則將app
role 更新密碼 - 將 application rolling update 改回用原本的
app
role 對 postgres 進行連線 - 將
app_temp
role DROP 掉
說是完全 zero downtime 可能不是那麼精確,但如果你的 application 有做好 graceful shutdown 並且有 rolling update 通常 client 端是不會注意到 downtime 的。至少比直接改現有 role 的 password 並強制踢掉 client 連線並重連會是更好的體驗。
以下給個 psql example 示範:
-
建立
app
role1
2postgres=# CREATE ROLE app WITH LOGIN PASSWORD 'oldpassword';
CREATE ROLE -
用
app
role 來建立測試 table 並 insert data1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17psql -U app -d postgres -W -h 127.0.0.1
Password:
postgres=> select current_user;
current_user
--------------
app
(1 row)
postgres=> CREATE TABLE tests(id INTEGER PRIMARY KEY);
CREATE TABLE
postgres=> INSERT INTO tests(id) VALUES(1);
INSERT 0 1
postgres=> SELECT * FROM tests;
id
----
1
(1 row)這時候可以 SELECT tests table owner 確認一下:
1
2
3
4postgres=> select * from pg_tables where tablename = 'tests';
schemaname | tablename | tableowner | tablespace | hasindexes | hasrules | hastriggers | rowsecurity
------------+-----------+------------+------------+------------+----------+-------------+-------------
public | tests | app | | t | f | f | f -
建立
app_temp
role 並繼承app
role 的權限1
2
3
4postgres=> CREATE ROLE app_temp WITH LOGIN PASSWORD 'oldpassword' IN ROLE app;
CREATE ROLE
postgres=> ALTER ROLE app_temp SET ROLE app;
ALTER ROLE這時候 app_temp 再重新登入就會發現 current_user 為
app
1
2
3
4
5
6
7
8
9
10psql -U app_temp -d postgres -W -h 127.0.0.1
Password:
psql (16rc1, server 11.21)
Type "help" for help.
postgres=> select current_user;
current_user
--------------
app
(1 row)當然這時候去 SELECT tests table 是可以看得到的:
1
2
3
4
5postgres=> SELECT * FROM tests;
id
----
1
(1 row)這時候你可能會好奇,為什麼還要 SET ROLE 因為
app_temp
本身就有app
的權限了呀。原因在於,你用app_temp
role 做的任何操作都是用app_temp
的身份,也就是說如果你 CREATE 新 TABLE,這個 table owner 是掛在app_temp
上的。而通常換 role name 反而是很不方便的,你這樣可能其他有關聯到的服務都要跟著改設定檔,這就是為啥之後還需要將
app
role 還原回去給 application 做使用,因此才需要做 SET ROLE。之後你就可以對 application 做 rolling update,而且因為密碼與之前相同,DB connection 只需要改 username 即可。
-
對
app
role 進行更換密碼1
2
3
4postgres=# \password app
Enter new password for user "app":
Enter it again:
postgres=#我建議不要用
ALTER ROLE app SET PASSWORD 'xxxx'
這樣的做法,原因是這樣會留下 psql history 或是任何 log 機制都可能會看到 plain password。當然你是可以 SET PASSWORD 給 hash 的結果,PG 會根據你的 hash 去判斷你給的 password 是用 MD5 還是 SHA256,但這樣也是滿麻煩的。因此建議用 \password 的方式來進行密碼更動。
更換完之後,就可以一樣對 application 做 rolling update,DB connection 要改 username 跟 password。
總結
雖然這樣 rotate 是比較麻煩的,但是用 K8S 這類的工具幫你做 rolling update 且 application 有做好 graceful shutdown 的話,這樣已經盡可能降低 downtime 了,基本上使用者應該是根本不會發現才對。
題外話,有翻到這篇 Rotate database credentials without restarting containers,我大概看了一下做法就是在 client 端會定期去跟 secret manager 之類的服務拿憑證去連線資料庫,當過期的時候 client 端的 code 會斷掉連線拿最新的憑證進行重連。這樣的確就不需要 restart,不過還是需要 disconnect 在重連,只是速度更快也是一種方式,但就是要在 client 端的 code 做手腳。