Skip to content

PostgreSQL 逻辑复制安全性深度指南

概述

逻辑复制是 PostgreSQL 的一项强大功能,它允许在不同的数据库实例之间实时同步数据。然而,这一功能涉及复杂的安全机制,需要正确配置权限和访问控制,以确保数据的安全性和一致性。

INFO

逻辑复制的安全性主要涉及两个方面:**发布者(Publisher)的权限控制和订阅者(Subscriber)**的权限管理。理解这些机制对于构建安全的数据复制系统至关重要。

复制连接角色权限要求

基础权限要求

用于复制连接的角色必须满足以下条件之一:

  • 具有 REPLICATION 属性
  • 或者是超级用户
sql
-- 创建具有复制权限的角色
CREATE ROLE replication_user WITH REPLICATION LOGIN PASSWORD 'secure_password';

-- 或者为现有用户授予复制权限
ALTER ROLE existing_user REPLICATION;

行级安全策略处理

当复制角色缺少 SUPERUSERBYPASSRLS 权限时,发布者的行级安全策略将被执行。

WARNING

如果不信任所有表所有者,应在连接字符串中包含 options=-crow_security=off 设置。这样,当表所有者添加行级安全策略时,复制将停止而不是执行该策略。

实际业务场景示例

假设您有一个多租户应用,其中每个租户只能看到自己的数据:

sql
-- 在发布者上设置行级安全策略
CREATE TABLE tenant_data (
    id SERIAL PRIMARY KEY,
    tenant_id INTEGER,
    sensitive_data TEXT
);

ALTER TABLE tenant_data ENABLE ROW LEVEL SECURITY;

-- 创建行级安全策略
CREATE POLICY tenant_isolation ON tenant_data
    FOR ALL TO public
    USING (tenant_id = current_setting('app.current_tenant_id')::INTEGER);

-- 复制连接字符串示例
host=publisher_host port=5432 dbname=source_db user=repl_user options=-crow_security=off

发布(Publication)权限管理

创建发布的权限要求

操作类型所需权限说明
创建发布数据库 CREATE 权限基础发布创建权限
添加特定表到发布表的拥有权必须是表的所有者
添加模式中所有表SUPERUSER需要超级用户权限
创建全表发布SUPERUSER自动发布所有表或模式中所有表

完整示例:发布创建与权限配置

sql
-- 1. 创建普通用户并授予CREATE权限
CREATE USER pub_admin PASSWORD 'secure_pass';
GRANT CREATE ON DATABASE company_db TO pub_admin;

-- 2. 切换到pub_admin用户
SET ROLE pub_admin;

-- 3. 创建发布(成功)
CREATE PUBLICATION employee_pub;

-- 4. 尝试添加表到发布
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    department VARCHAR(50)
);

-- 添加自己拥有的表(成功)
ALTER PUBLICATION employee_pub ADD TABLE employees;

-- 尝试添加其他用户的表(失败)
-- ALTER PUBLICATION employee_pub ADD TABLE other_user.products; -- 权限不足错误

发布访问控制的局限性

DANGER

当前 PostgreSQL 的发布没有任何权限控制机制。任何能够连接的订阅都可以访问任何发布。这意味着:

  1. 相同数据库中的其他发布可能暴露敏感信息
  2. 行过滤器和列列表并不能真正隐藏数据
  3. 需要在数据库级别进行访问控制

安全风险示例

sql
-- 敏感数据发布
CREATE PUBLICATION sensitive_pub FOR TABLE employee_salaries;

-- 公开数据发布(在同一数据库中)
CREATE PUBLICATION public_pub FOR TABLE employee_info;

-- 风险:恶意订阅者可以同时访问两个发布
-- 即使 sensitive_pub 有行过滤器,public_pub 可能暴露相同的敏感信息

订阅(Subscription)权限管理

创建订阅的权限

要创建订阅,用户需要:

  1. 拥有 pg_create_subscription 角色的权限
  2. 具有数据库上的 CREATE 权限
sql
-- 授予用户创建订阅的权限
GRANT pg_create_subscription TO subscription_admin;
GRANT CREATE ON DATABASE target_db TO subscription_admin;

-- 创建订阅
CREATE SUBSCRIPTION emp_sub
CONNECTION 'host=source_host port=5432 dbname=source_db user=repl_user'
PUBLICATION employee_pub;

订阅执行时的权限切换

订阅应用进程的权限模型比较复杂:

默认权限模式示例

sql
-- 订阅所有者需要能够SET ROLE为每个表所有者
CREATE USER sub_owner;
CREATE USER table_owner_1;
CREATE USER table_owner_2;

-- 订阅所有者需要能够切换到表所有者角色
GRANT table_owner_1 TO sub_owner;
GRANT table_owner_2 TO sub_owner;

-- 创建订阅
CREATE SUBSCRIPTION multi_owner_sub
CONNECTION 'host=source dbname=src user=repl_user'
PUBLICATION multi_table_pub
WITH (slot_name = 'multi_owner_slot');

run_as_owner 模式的安全考虑

WARNING

使用 run_as_owner = true 时存在重大安全风险:

sql
-- 启用run_as_owner模式
ALTER SUBSCRIPTION risky_sub SET (run_as_owner = true);

安全风险场景

sql
-- 恶意表所有者可以创建触发器
CREATE TABLE user_owned_table (
    id INTEGER,
    data TEXT
);

-- 恶意触发器,在复制时执行任意代码
CREATE OR REPLACE FUNCTION malicious_function()
RETURNS TRIGGER AS $$
BEGIN
    -- 恶意代码:访问敏感数据或执行危险操作
    PERFORM pg_read_file('/etc/passwd');
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER malicious_trigger
    BEFORE INSERT ON user_owned_table
    FOR EACH ROW
    EXECUTE FUNCTION malicious_function();

DANGER

run_as_owner = true 模式下,恶意触发器将以订阅所有者的权限执行,可能导致严重的安全漏洞。

权限检查时机

发布者权限检查

在发布者上,权限检查的时机:

订阅者权限检查

在订阅者上,权限检查更加频繁:

并发所有权变更场景

sql
-- 会话1:正在应用复制事务
BEGIN;
-- 复制操作正在进行...

-- 会话2:同时更改订阅所有权
ALTER SUBSCRIPTION test_sub OWNER TO new_owner;

-- 结果:当前事务继续以旧所有者权限执行
-- 下一个事务将以新所有者权限执行

安全配置最佳实践

1. 复制用户安全配置

sql
-- 创建专用复制用户
CREATE USER replication_service WITH 
    REPLICATION 
    LOGIN 
    PASSWORD 'complex_secure_password_2024!'
    VALID UNTIL '2025-12-31';

-- 限制连接来源
-- 在 pg_hba.conf 中配置
-- host replication replication_service 192.168.1.0/24 scram-sha-256

2. 发布权限最小化

sql
-- 创建专门的发布管理用户
CREATE USER pub_manager;
GRANT CREATE ON DATABASE production_db TO pub_manager;

-- 只授予必要表的所有权或访问权限
GRANT SELECT ON critical_table TO pub_manager;

-- 避免使用FOR ALL TABLES
-- CREATE PUBLICATION bad_pub FOR ALL TABLES; -- 避免这样做
CREATE PUBLICATION good_pub FOR TABLE specific_table1, specific_table2;

3. 订阅安全配置

sql
-- 创建专门的订阅用户
CREATE USER sub_manager;
GRANT pg_create_subscription TO sub_manager;
GRANT CREATE ON DATABASE replica_db TO sub_manager;

-- 配置表权限(避免使用run_as_owner = true)
GRANT SELECT, INSERT, UPDATE, DELETE ON target_table TO table_owner;
GRANT table_owner TO sub_manager; -- 允许角色切换

4. 监控和审计

sql
-- 监控复制状态
SELECT 
    slot_name,
    plugin,
    slot_type,
    datoid,
    active,
    active_pid,
    catalog_xmin,
    restart_lsn
FROM pg_replication_slots;

-- 监控订阅状态
SELECT 
    subname,
    pid,
    received_lsn,
    last_msg_send_time,
    last_msg_receipt_time,
    latest_end_lsn,
    latest_end_time
FROM pg_stat_subscription;

-- 审计发布访问
SELECT 
    pubname,
    puballtables,
    pubinsert,
    pubupdate,
    pubdelete,
    pubtruncate
FROM pg_publication;

故障排除常见安全问题

权限不足错误

sql
-- 错误:权限被拒绝
ERROR: permission denied for table sensitive_data

-- 解决方案:检查并授予必要权限
GRANT SELECT ON sensitive_data TO replication_user;

复制槽访问错误

sql
-- 错误:复制槽不存在或无权限访问
ERROR: replication slot "my_slot" does not exist

-- 解决方案:检查槽的所有权和权限
SELECT slot_name, active, active_pid FROM pg_replication_slots;

行级安全策略冲突

sql
-- 错误:行级安全策略阻止复制
ERROR: new row violates row-level security policy

-- 解决方案:调整连接参数或禁用RLS
-- 连接字符串添加:options=-crow_security=off

总结

PostgreSQL 逻辑复制的安全性是一个多层次的体系,涉及:

  1. 连接级安全:复制用户权限和网络访问控制
  2. 发布级安全:表所有权和发布权限管理
  3. 订阅级安全:角色切换和权限检查机制
  4. 运行时安全:权限检查时机和并发控制

正确理解和配置这些安全机制,对于构建安全可靠的数据复制系统至关重要。在生产环境中,应该采用最小权限原则,避免使用超级用户进行复制操作,并定期审计复制权限配置。