安全與權限管理:從認證到 Row Level Security 全方位防護 | PostgreSQL
安全是資料庫運維的基石。PostgreSQL 採用「縱深防禦(Defense in Depth)」策略,從網路層、認證層、授權層到資料加密層,每一層都有對應的防護機制。本文將從 SCRAM-SHA-256 認證、pg_hba.conf 設定,到 Row Level Security、pgAudit 稽核與 pgcrypto 加密,帶你建立完整的 PostgreSQL 安全防線。
PostgreSQL 安全模型總覽
PostgreSQL 採用分層安全架構,每層負責不同範疇的存取控制:
┌──────────────────────────────────────────┐
│ 網路層(Network Layer) │
│ 防火牆、SSL/TLS、IP 白名單 │
├──────────────────────────────────────────┤
│ 認證層(Authentication Layer) │
│ pg_hba.conf、SCRAM、Certificate、LDAP │
├──────────────────────────────────────────┤
│ 授權層(Authorization Layer) │
│ GRANT/REVOKE、角色繼承、物件權限 │
├──────────────────────────────────────────┤
│ 行級安全(Row Level Security) │
│ CREATE POLICY、PERMISSIVE/RESTRICTIVE │
├──────────────────────────────────────────┤
│ 欄位級安全(Column-Level Security) │
│ 欄位 GRANT、視圖遮罩 │
├──────────────────────────────────────────┤
│ 加密層(Encryption Layer) │
│ pgcrypto、TDE、傳輸加密 │
└──────────────────────────────────────────┘
核心原則是「即使某一層被突破,其他層仍能限制損失範圍」:
| 原則 | 說明 |
|---|---|
| 最小權限(Least Privilege) | 每個帳號只授予完成工作所需的最低權限 |
| 預設拒絕(Default Deny) | 未明確授權的操作一律拒絕 |
| 稽核可追溯(Auditability) | 所有敏感操作均有日誌紀錄 |
| 定期複查(Periodic Review) | 定期審查權限、憑證與設定 |
認證機制(Authentication)
認證是安全的第一道防線——確認「你是誰」。
SCRAM-SHA-256(推薦)
SCRAM-SHA-256 是 PostgreSQL 10+ 預設且最安全的密碼認證方式,取代舊有的 MD5 認證。它的優點包括:密碼不以明文傳輸、防止重放攻擊(Replay Attack)、伺服器端無法反推明文密碼。
-- postgresql.conf
password_encryption = 'scram-sha-256'
-- 建立使用者,密碼以 SCRAM 格式儲存
CREATE ROLE app_user WITH LOGIN PASSWORD 'StrongP@ssw0rd!';
-- 驗證密碼加密格式
SELECT rolname, LEFT(rolpassword, 14) AS format_prefix
FROM pg_authid
WHERE rolname = 'app_user';
-- 正確輸出:SCRAM-SHA-256$
Certificate 憑證認證
憑證認證透過 TLS 客戶端憑證識別使用者身份,無需密碼,適合**機器對機器(M2M)**場景:
# 1. 產生 CA 憑證
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/CN=PostgreSQL CA"
# 2. 產生伺服器憑證
openssl genrsa -out server.key 4096
openssl req -new -key server.key -out server.csr \
-subj "/CN=db.example.com"
openssl x509 -req -days 365 -in server.csr -CA ca.crt \
-CAkey ca.key -CAcreateserial -out server.crt
# 3. 產生客戶端憑證(CN 需與 PostgreSQL 使用者名稱一致)
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr \
-subj "/CN=app_user"
openssl x509 -req -days 365 -in client.csr -CA ca.crt \
-CAkey ca.key -CAcreateserial -out client.crt
# pg_hba.conf — 使用 cert 方法
hostssl all app_user 10.0.0.0/8 cert clientcert=verify-full
LDAP / GSSAPI 認證
| 方式 | 適用場景 | 特色 |
|---|---|---|
| LDAP | 企業統一身份管理(Active Directory) | 密碼驗證委派給 LDAP Server |
| GSSAPI | Kerberos 環境 | 單一登入(SSO),無需額外密碼 |
# pg_hba.conf — LDAP Search+Bind
host all all 10.0.0.0/8 ldap \
ldapserver=ldap.example.com \
ldapbasedn="ou=users,dc=example,dc=com" \
ldapbinddn="cn=pgbind,dc=example,dc=com" \
ldapbindpasswd="bindpassword" \
ldapsearchattribute=uid \
ldapscheme=ldaps ldapport=636
注意:LDAP 認證只驗證密碼,使用者仍需在 PostgreSQL 中以
CREATE ROLE建立。
pg_hba.conf 最佳實踐
pg_hba.conf(Host-Based Authentication)是認證的核心設定檔,規則由上至下逐行匹配,第一條匹配的規則生效:
# TYPE DATABASE USER ADDRESS METHOD
# 1. 本機超級使用者使用 peer(OS 使用者即 PG 使用者)
local all postgres peer
# 2. 本機一般連線
local all all scram-sha-256
# 3. 複製連線限制來源 IP
host replication replicator 10.0.1.0/24 scram-sha-256
# 4. 應用程式連線:限定使用者 + 資料庫 + IP + 加密
hostssl app_db app_user 10.0.2.0/24 scram-sha-256
# 5. DBA 管理工具:限定 IP 段
hostssl all dba_role 10.0.3.0/24 scram-sha-256
# 6. 預設拒絕(最後一行)
host all all 0.0.0.0/0 reject
常見錯誤(務必避免):
- 將
trust用於非本機連線 → 無需密碼即可登入 - 未限定來源 IP(
0.0.0.0/0+ 非 reject) - 使用 MD5 而非 SCRAM-SHA-256
- 寬鬆規則放在嚴格規則之前(後者永遠不生效)
角色與權限管理
角色架構設計
PostgreSQL 的角色(Role)統一了使用者與群組的概念。最佳實踐是建立「群組角色(不可登入)」+ 「個人角色(可登入)」的層級結構:
-- 建立群組角色(不可登入)
CREATE ROLE readonly_group NOLOGIN;
CREATE ROLE readwrite_group NOLOGIN;
-- 建立可登入使用者
CREATE ROLE app_user WITH
LOGIN
PASSWORD 'StrongP@ssw0rd!'
CONNECTION LIMIT 50 -- 限制連線數
VALID UNTIL '2027-01-01' -- 帳號有效期
NOSUPERUSER
NOCREATEDB
NOCREATEROLE;
-- 建立唯讀分析帳號
CREATE ROLE analyst WITH LOGIN PASSWORD 'AnalystP@ss!' NOSUPERUSER;
GRANT / REVOKE
-- 授予資料庫連線權限
GRANT CONNECT ON DATABASE app_db TO readonly_group;
GRANT CONNECT ON DATABASE app_db TO readwrite_group;
-- 授予 Schema 使用權
GRANT USAGE ON SCHEMA public TO readonly_group;
GRANT USAGE ON SCHEMA public TO readwrite_group;
-- 授予資料表權限
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_group;
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA public TO readwrite_group;
-- 授予 Sequence(INSERT 需要)
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO readwrite_group;
-- 將群組角色指派給使用者
GRANT readonly_group TO analyst;
GRANT readwrite_group TO app_user;
-- 撤銷 public schema 的預設公開建立權限(PG15 前的安全隱患)
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
DEFAULT PRIVILEGES
DEFAULT PRIVILEGES 確保未來新建立的物件自動套用指定權限,解決「忘記 GRANT 新資料表」的常見問題:
-- 未來由 app_user 建立的資料表,自動授予 readonly_group SELECT
ALTER DEFAULT PRIVILEGES FOR ROLE app_user IN SCHEMA public
GRANT SELECT ON TABLES TO readonly_group;
-- 未來的 Sequence
ALTER DEFAULT PRIVILEGES FOR ROLE app_user IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO readwrite_group;
-- 未來的函式
ALTER DEFAULT PRIVILEGES FOR ROLE app_user IN SCHEMA public
GRANT EXECUTE ON FUNCTIONS TO readwrite_group;
角色繼承與 SET ROLE
-- INHERIT(預設):自動繼承權限
CREATE ROLE senior_analyst INHERIT;
GRANT readonly_group TO senior_analyst;
GRANT senior_analyst TO john_doe;
-- john_doe 自動擁有 readonly_group 的所有權限
-- NOINHERIT:需明確 SET ROLE 才能取得權限
CREATE ROLE privileged_ops NOINHERIT;
GRANT privileged_ops TO ops_user;
-- ops_user 需執行:
SET ROLE privileged_ops;
-- 執行特權操作...
RESET ROLE;
PG14+ 預定義角色
PostgreSQL 14 引入預定義角色,簡化全庫授權:
-- 可讀取所有資料表、視圖、序列
GRANT pg_read_all_data TO analyst;
-- 可寫入所有資料表、視圖、序列
GRANT pg_write_all_data TO etl_user;
-- 其他實用預定義角色
GRANT pg_monitor TO monitoring_user; -- 監控相關視圖
GRANT pg_read_all_settings TO config_reader; -- 讀取 GUC 設定
GRANT pg_read_all_stats TO stats_reader; -- 讀取統計資訊
GRANT pg_signal_backend TO ops_user; -- 可發送 pg_terminate_backend
Row Level Security(RLS)
RLS 讓資料表的每一列都有存取政策,不同使用者看到不同的資料子集。這是實現**多租戶(Multi-Tenant)**資料隔離最優雅的方式。
基本用法
-- 步驟 1:啟用 RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- 步驟 2:建立政策
-- 使用者只能看到自己的訂單
CREATE POLICY user_orders_policy ON orders
FOR ALL
TO app_user
USING (user_id = current_user::TEXT) -- 查詢過濾(SELECT/UPDATE/DELETE)
WITH CHECK (user_id = current_user::TEXT); -- 寫入驗證(INSERT/UPDATE)
-- 唯讀政策
CREATE POLICY select_own_data ON orders
FOR SELECT
USING (user_id = current_user::TEXT);
PERMISSIVE vs RESTRICTIVE
兩種政策模式決定多個 Policy 如何組合:
| 模式 | 組合方式 | 典型用途 |
|---|---|---|
| PERMISSIVE(預設) | 多個 Policy 以 OR 合併 | admin 看全部、使用者看自己的 |
| RESTRICTIVE | 多個 Policy 以 AND 合併 | 強制所有人不能看已刪除資料 |
-- PERMISSIVE:admin 看到全部
CREATE POLICY admin_sees_all ON orders
AS PERMISSIVE
FOR SELECT
TO admin_role
USING (true);
-- RESTRICTIVE:無論其他政策,已刪除的資料都不可見
CREATE POLICY active_records_only ON orders
AS RESTRICTIVE
FOR ALL
USING (is_deleted = false);
多租戶場景
-- 建立多租戶資料表
CREATE TABLE tenant_data (
id BIGSERIAL PRIMARY KEY,
tenant_id INTEGER NOT NULL,
payload JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);
ALTER TABLE tenant_data ENABLE ROW LEVEL SECURITY;
-- 透過 Session Variable 隔離租戶
CREATE POLICY tenant_isolation ON tenant_data
USING (tenant_id = current_setting('app.tenant_id')::INTEGER)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::INTEGER);
-- 應用程式連線後設定:
-- SET app.tenant_id = '42';
-- 連表擁有者也受 RLS 控制
ALTER TABLE tenant_data FORCE ROW LEVEL SECURITY;
-- 為管理員建立繞過政策
CREATE POLICY admin_bypass ON tenant_data
TO admin_role
USING (true);
Column-Level Privileges
欄位級權限(Column-Level Privileges)允許對特定欄位授予或拒絕存取,適合保護薪資、**個人識別資訊(PII)**等敏感欄位:
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
department VARCHAR(50),
salary NUMERIC(10,2), -- 敏感
ssn VARCHAR(20), -- 敏感
email VARCHAR(100)
);
-- 一般查詢角色:只看非敏感欄位
GRANT SELECT (id, name, department, email) ON employees TO hr_viewer;
-- 薪資管理角色:只看薪資相關
GRANT SELECT (id, name, salary) ON employees TO payroll_role;
-- 欄位級 UPDATE
GRANT UPDATE (email) ON employees TO self_service_user;
使用視圖(View)作為欄位遮罩的替代方案:
-- 建立遮罩視圖,SSN 只顯示後四碼
CREATE VIEW employees_public AS
SELECT
id, name, department, email,
'***-**-' || RIGHT(ssn, 4) AS ssn_masked
FROM employees;
GRANT SELECT ON employees_public TO hr_viewer;
REVOKE SELECT ON employees FROM hr_viewer;
SSL/TLS 傳輸加密
設定 SSL
# 產生自簽憑證(生產環境建議使用受信任 CA)
openssl req -new -x509 -days 365 -nodes \
-out server.crt -keyout server.key \
-subj "/CN=db.example.com"
# 設定正確的檔案權限
chmod 600 server.key
chown postgres:postgres server.key server.crt
# postgresql.conf
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_min_protocol_version = 'TLSv1.2' -- PG12+
ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL'
ssl_prefer_server_ciphers = on
強制 SSL 連線
# pg_hba.conf
hostssl app_db app_user 10.0.0.0/8 scram-sha-256
hostssl all dba_role 10.0.3.0/24 scram-sha-256
# 拒絕所有非 SSL 連線
hostnossl all all 0.0.0.0/0 reject
-- 驗證當前連線是否使用 SSL
SELECT ssl, version, cipher, bits
FROM pg_stat_ssl
WHERE pid = pg_backend_pid();
pgAudit 稽核日誌
pgAudit 提供詳細的工作階段(Session)和物件(Object)層級稽核日誌,符合 SOX、PCI-DSS 等合規需求。
安裝與設定
# 安裝(Ubuntu/Debian)
apt-get install postgresql-17-pgaudit
# postgresql.conf
shared_preload_libraries = 'pgaudit'
# 記錄所有 DDL、DML、角色操作
pgaudit.log = 'ddl, write, role'
pgaudit.log_catalog = off -- 避免系統目錄日誌爆炸
pgaudit.log_parameter = on -- 記錄查詢參數
pgaudit.log_relation = on -- 為每個關聯記錄單獨條目
-- 啟用擴充
CREATE EXTENSION pgaudit;
-- 物件級稽核:針對特定資料表
CREATE ROLE auditor;
GRANT SELECT ON sensitive_table TO auditor;
ALTER ROLE app_user SET pgaudit.role = 'auditor';
-- 現在 app_user 對 sensitive_table 的所有 SELECT 都會被記錄
日誌格式
AUDIT: SESSION,<audit_count>,<sub_count>,<class>,<command>,
<object_type>,<object_name>,<statement>,<parameter>
範例:
AUDIT: SESSION,1,1,WRITE,INSERT,TABLE,public.orders,
"INSERT INTO orders (user_id, amount) VALUES ($1, $2)",<none>
AUDIT: SESSION,2,1,DDL,ALTER TABLE,TABLE,public.employees,
"ALTER TABLE employees ADD COLUMN phone VARCHAR(20)",<none>
pgcrypto 資料加密
pgcrypto 提供應用層級的資料加密函式,可對欄位值進行加密/解密:
CREATE EXTENSION pgcrypto;
-- 對稱加密(AES)
INSERT INTO sensitive_data (encrypted_payload)
VALUES (
pgp_sym_encrypt(
'{"ssn": "123-45-6789"}',
'key_from_vault', -- 金鑰應來自密鑰管理服務
'cipher-algo=aes256'
)
);
-- 解密
SELECT pgp_sym_decrypt(encrypted_payload::BYTEA, 'key_from_vault')
FROM sensitive_data WHERE id = 1;
-- 密碼雜湊(bcrypt,不可逆)
SELECT crypt('user_password', gen_salt('bf', 12));
-- 驗證密碼
SELECT crypt('input_password', stored_hash) = stored_hash AS is_valid
FROM users WHERE username = 'john_doe';
-- 產生安全隨機 Token
SELECT encode(gen_random_bytes(32), 'hex') AS api_token;
TDE 現況
**TDE(Transparent Data Encryption)**是在儲存層對整個資料庫檔案加密,無需修改應用程式:
| 方案 | 狀態 |
|---|---|
| PostgreSQL 原生 TDE | 社群開發中,預計未來版本加入 |
| EDB Postgres Advanced Server | 商業支援 |
| AWS RDS/Aurora | 透過 AWS KMS 管理金鑰 |
| Azure Database for PostgreSQL | 透過 Azure Key Vault 整合 |
| 檔案系統加密(LUKS) | 作業系統層,PG 無感知 |
安全稽核檢查清單
定期執行以下查詢進行安全健檢:
-- 1. 找出所有 SUPERUSER
SELECT rolname, rolsuper, rolcreatedb, rolcreaterole
FROM pg_roles WHERE rolsuper = true;
-- 2. 找出無密碼的可登入帳號
SELECT rolname FROM pg_roles
WHERE rolcanlogin = true AND rolpassword IS NULL;
-- 3. 找出已過期但仍可登入的帳號
SELECT rolname, rolvaliduntil FROM pg_roles
WHERE rolcanlogin = true AND rolvaliduntil < now();
-- 4. 找出 PUBLIC 擁有權限的敏感資料表
SELECT schemaname, tablename, privilege_type
FROM information_schema.role_table_grants
WHERE grantee = 'PUBLIC';
-- 5. 找出未啟用 RLS 的多租戶資料表
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE tablename IN (
SELECT table_name FROM information_schema.columns
WHERE column_name = 'tenant_id'
) AND rowsecurity = false;
-- 6. 查看 pg_hba.conf 目前設定
SELECT type, database, user_name, address, auth_method
FROM pg_hba_file_rules ORDER BY line_number;
-- 7. 找出長時間閒置的連線
SELECT pid, usename, client_addr, state,
now() - state_change AS idle_duration
FROM pg_stat_activity
WHERE state = 'idle'
AND now() - state_change > INTERVAL '30 minutes';
常見陷阱
-- 陷阱 1:public schema 對所有使用者開放(PG15 前)
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
-- 陷阱 2:應用程式使用 postgres 超級使用者帳號
-- 正確:建立最小權限帳號
CREATE ROLE app_service WITH LOGIN PASSWORD '...'
NOSUPERUSER NOCREATEDB NOCREATEROLE;
-- 陷阱 3:未設定連線限制
ALTER ROLE app_user CONNECTION LIMIT 100;
-- 陷阱 4:GRANT ... TO PUBLIC(影響所有現有和未來帳號)
REVOKE ALL ON TABLE sensitive_data FROM PUBLIC;
-- 陷阱 5:RLS 未啟用 FORCE,表擁有者繞過 RLS
ALTER TABLE tenant_data FORCE ROW LEVEL SECURITY;
-- 陷阱 6:未設定 search_path,可能導致 Schema 注入
ALTER ROLE app_user SET search_path = '$user', public;
-- 陷阱 7:密碼記錄在命令列歷史
-- 改用 \password 命令(提示輸入,不記錄)
-- \password app_user
版本演進
| 版本 | 安全相關重要變更 |
|---|---|
| PG 9.5 | Row Level Security(RLS) 正式引入 |
| PG 10 | SCRAM-SHA-256 認證取代 MD5 |
| PG 12 | ssl_min_protocol_version 設定 |
| PG 14 | pg_read_all_data、pg_write_all_data 預定義角色 |
| PG 15 | public schema 預設不再允許 PUBLIC CREATE |
| PG 16 | pg_hba.conf 支援連線參數過濾(options 欄位) |
| PG 17 | 改進的 SSL 憑證驗證、oauth 認證方法(實驗性) |
總結
PostgreSQL 的安全模型建立在縱深防禦的理念上——從網路層的 SSL/TLS 和防火牆,到認證層的 SCRAM-SHA-256 和憑證認證,再到授權層的 GRANT/REVOKE 與角色繼承,最後深入到 Row Level Security 的行級隔離和 pgcrypto 的資料加密。每一層都是獨立的防線,層層堆疊才能構成完整的安全體系。
關鍵要點:永遠遵循最小權限原則、使用 DEFAULT PRIVILEGES 自動化權限管理、為多租戶場景啟用 RLS、用 pgAudit 實現合規稽核,以及定期執行安全檢查清單。
下一篇,我們將深入探討 備份與還原——掌握 pg_dump、pg_basebackup、PITR 時間點還原與自動化備份策略,確保你的資料永不丟失。