

# 将动态掩蔽与 Aurora PostgreSQL 结合使用
Aurora PostgreSQL 动态掩蔽

动态数据掩蔽是一项安全功能，可通过控制数据在查询时向用户显示的方式来保护 Aurora PostgreSQL 数据库中的敏感数据。Aurora 通过 `pg_columnmask` 扩展实现此项功能。`pg_columnmask` 提供列级别数据保护，以便为 PostgreSQL 的原生行级安全性和精细访问控制机制提供补充。

使用 `pg_columnmask`，您可以创建掩蔽策略，以便根据用户角色确定数据可见性。当用户使用掩蔽策略查询表时，Aurora PostgreSQL 会在查询时根据用户的角色和策略权重来应用相应的掩蔽函数。底层数据在存储中保持不变。

`pg_columnmask` 支持以下功能：
+ **内置和自定义掩蔽函数**：使用预先构建的函数来处理电子邮件和文本掩蔽等常见模式，或者创建自己的自定义函数，以通过基于 SQL 的掩蔽策略保护敏感数据（PII）。
+ **多种掩蔽策略**：完全隐藏信息、用通配符替换部分值或定义自定义掩蔽方法。
+ **策略优先级**：为单个列定义多个策略。使用权重来确定当多个策略应用于某个列时，应使用哪个掩蔽策略。Aurora PostgreSQL 根据权重和用户角色成员资格应用策略。

`pg_columnmask` 在 Aurora PostgreSQL 版本 16.10 及更高版本以及版本 17.6 及更高版本上可用。它不会产生额外的成本。

# 动态掩蔽入门
开始使用

要动态掩蔽数据，请在数据库中安装 `pg_columnmask` 扩展，然后为表创建掩蔽策略。设置过程包括先决条件验证、扩展安装、角色配置、策略创建和验证测试。

## 扩展安装和配置
扩展安装

使用 RDS 控制台查询编辑器或具有 rds\$1superuser（主用户）凭证的 PostgreSQL 客户端（如 psql）连接到 Aurora PostgreSQL 集群。

执行扩展创建命令以启用 `pg_columnmask` 功能：

```
CREATE EXTENSION pg_columnmask;
```

此命令安装 `pg_columnmask` 扩展，创建必要的目录表，并注册内置的掩蔽函数。扩展安装是特定于数据库的，这意味着您必须将其单独安装在需要该功能的每个数据库中。

**注意**  
在安装此扩展之前建立的连接仍将显示未掩蔽的数据。关闭并重新连接以修复此问题。

通过检查可用的掩蔽函数来验证扩展安装：

```
SELECT proname FROM pg_proc
    WHERE pronamespace = 'pgcolumnmask'::regnamespace AND proname LIKE 'mask_%';
    proname     
--------Output --------
 mask_email
 mask_text
 mask_timestamp
(3 rows)
```

# 用于管理数据掩蔽策略的过程
数据掩蔽策略

您可以使用 `pg_columnmask` 扩展提供的过程来管理掩蔽策略。要创建、修改或删除掩蔽策略，您必须具有以下权限之一：
+ 您要在其上创建 `pg_columnmask` 策略的表的所有者。
+ `rds_superuser` 的成员。
+ 由 `pgcolumnmask.policy_admin_rolname` 参数设置的 `pg_columnmask` 策略管理员角色的成员。

以下命令可创建一个表，该表将在后续章节中使用：

```
CREATE TABLE public.customers (
    id SERIAL PRIMARY KEY,
    name TEXT,
    phone TEXT,
    address TEXT,
    email TEXT
);
```

## CREATE\$1MASKING\$1POLICY
CREATE\$1MASKING\$1POLICY

以下过程为用户表创建新的掩蔽策略：

**语法**

```
create_masking_policy(
    policy_name,
    table_name,
    masking_expressions,
    roles,
    weight)
```

**Arguments**


| 参数 | DataType | 说明 | 
| --- | --- | --- | 
| policy\$1name | NAME |  屏蔽策略的名称。对于每个表都必须是唯一的。  | 
| table\$1name | REGCLASS |  要应用掩蔽策略的表的限定/非限定名称或 oid。  | 
| masking\$1expressions | JSONB |  包含列名称和掩蔽函数对的 JSON 对象。每个键都是一个列名称，其值是要应用于该列的掩蔽表达式。  | 
| roles | NAME[] |  此掩蔽策略应用到的角色。默认值为 PUBLIC。  | 
| weight | INT |  掩蔽策略的权重。当多个策略适用于给定用户的查询时，权重最高（更高的整数）的策略将应用于每个掩蔽的列。 默认值为 0。表上的任何两个掩蔽策略都不能具有相同的权重。  | 

**返回类型**：

无

**Example ：创建掩蔽策略来掩蔽 `test_user` 角色的电子邮件列：**  

```
CALL pgcolumnmask.create_masking_policy(
    'customer_mask',
    'public.customers',
    JSON_OBJECT('{
        "email", "pgcolumnmask.mask_email(email)"
    }')::JSONB,
    ARRAY['test_user'],
    100
);
```

## ALTER\$1MASKING\$1POLICY
ALTER\$1MASKING\$1POLICY

此过程可修改现有掩蔽策略。`ALTER_MASKING_POLICY` 可以修改策略掩蔽表达式、要对其应用策略的角色集以及掩蔽策略的权重。忽略其中一个参数时，策略的相应部分将保持不变。

**语法**

```
alter_masking_policy(
    policy_name,
    table_name,
    masking_expressions,
    roles,
    weight)
```

**Arguments**


| 参数 | DataType | 说明 | 
| --- | --- | --- | 
| policy\$1name | NAME |  掩蔽策略的现有名称。  | 
| table\$1name | REGCLASS |  包含掩蔽策略的表的限定/非限定名称 oid。  | 
| masking\$1expressions | JSONB |  包含列名称和掩蔽函数对的新 JSON 对象，否则为 NULL。  | 
| roles | NAME[] |  要应用此掩蔽策略的新角色的列表，否则为 NULL。  | 
| weight | INT |  掩蔽策略的新权重，否则为 NULL。  | 

**返回类型**：

无

**Example ：在不更改其它策略属性的情况下将分析师角色添加到现有掩蔽策略中。**  

```
CALL pgcolumnmask.alter_masking_policy(
    'customer_mask',
    'public.customers',
    NULL,
    ARRAY['test_user', 'analyst'],
    NULL 
);

-- Alter the weight of the policy without altering other details
CALL pgcolumnmask.alter_masking_policy(
    'customer_mask',
    'customers',
    NULL,
    NULL,
    4
);
```

## DROP\$1MASKING\$1POLICY
DROP\$1MASKING\$1POLICY

此过程可移除现有掩蔽策略。

**语法**

```
drop_masking_policy(
        policy_name,
        table_name)
```

**Arguments**


| 参数 | DataType | 说明 | 
| --- | --- | --- | 
| policy\$1name | NAME |  掩蔽策略的现有名称。  | 
| table\$1name | REGCLASS |  包含掩蔽策略的表的限定/非限定名称 oid。  | 

**返回类型**：

无

**Example ：删除掩蔽策略 customer\$1mask**  

```
-- Drop a masking policy
    CALL pgcolumnmask.drop_masking_policy(
        'customer_mask',
        'public.customers',
    );
```

# 在掩蔽策略 DDL 过程中对标识符进行转义
对标识符进行转义

使用带引号的标识符创建数据掩蔽策略时，需要进行适当的转义，以确保对象引用和策略应用正确无误。要在 `pg_columnmask` 掩蔽策略管理过程中使用带引号的标识符：
+ **策略名称**：必须包含在双引号内。
+ **表名称**：需要时，架构名称和表名称必须分别用双引号括起来。
+ **掩蔽表达式**：掩蔽表达式中的列名称和函数名称必须用双引号括起来，并且必须使用反斜杠对引号本身进行转义。
+ **角色**：自动为角色名称的数组加引号。角色名称应与 `pg_roles` 中显示的名称完全匹配，包括区分大小写。

**Example ：转义和加引号语法**  
此示例显示了在 Aurora PostgreSQL 中为使用大小写混合名称或需要带引号的标识符的表、列、函数和角色创建掩蔽策略时，正确的转义和加引号语法。  

```
-- Create a table and columns with mixed case name 
CREATE TABLE public."Employees" (
    "Name" TEXT,
    "Email" TEXT,
    ssn VARCHAR(20)
);

-- Create a role with mixed case name
CREATE ROLE "Masked_user";

-- Create a function with mixed case name
CREATE OR REPLACE FUNCTION public."MaskEmail"(text)
    RETURNS character varying
    LANGUAGE plpgsql
    IMMUTABLE PARALLEL SAFE
    AS $$ BEGIN
        RETURN 'XXXXXXXX'::text;
    END $$;

-- Now use these objects with mixed case names in
-- masking policy management procedures
CALL pgcolumnmask.create_masking_policy(
    '"Policy1"',  -- policy name should be surrounded with double quotes for quoting
    'public."Employees"', -- table and schema name should be individually 
                          -- surrounded with double quotes for quoting
    JSON_OBJECT('{
        "\"Email\"", "\"MaskEmail\"(\"Email\")"
    }')::JSONB, -- masking expression should have double quotes around function names
                -- and columns names etc when needed. Also the double quotes itself
                -- should be escaped using \ (backslash) since this is a JSON string
    ARRAY['Masked_user'], -- Rolename do not need quoting
                          -- (this behaviour may change in future release)
    100
);

SELECT * FROM pgcolumnmask.pg_columnmask_policies
    WHERE tablename = 'Employees';
-[ RECORD 1 ]-----+-------------------------------------
schemaname        | public
tablename         | Employees
policyname        | Policy1
roles             | {Masked_user}
masked_columns    | {Email}
masking_functions | {"(\"MaskEmail\"(\"Email\"))::text"}
weight            | 100
```

## 管理视图
管理视图

您可以使用可公开访问的 `pgcolumnmask.pg_columnmask_policies` 管理视图来查看所有 `pg_columnmask` 策略。使用此视图可获得以下信息。该视图仅返回当前用户拥有的掩蔽策略。


| 列名称 | 数据类型 | 描述 | 
| --- | --- | --- | 
|  schemaname  | NAME |  已附加策略的关系的架构  | 
|  tablename  | NAME |  已附加策略的关系的名称  | 
|  policyname  | NAME |  掩蔽策略的名称，所有掩蔽策略都有唯一的名称  | 
|  角色  | TEXT[] |  策略应用到的角色。  | 
|  masked\$1columns  | TEXT[] |  掩蔽的列  | 
|  masking\$1functions  | TEXT[] |  掩蔽的函数  | 
| weight | INT |  附加的策略的权重  | 

# 预定义的数据掩蔽函数
数据掩蔽函数

`pg_columnmask` 扩展提供了用 C 语言（执行速度更快）编写的内置实用程序函数，可用作 `pg_columnmask` 策略的掩蔽表达式。

**mask\$1text**

一个使用可配置的可见性选项掩蔽文本数据的函数。

**参数**


| 参数 | DataType | 说明 | 
| --- | --- | --- | 
| input | TEXT |  要掩蔽的原始文本字符串  | 
| mask\$1char | CHAR(1) |  用于掩蔽的字符（默认值：“X”）  | 
| visible\$1prefix | INT |  输入文本开头将保持未掩蔽状态的字符数（默认值：0）  | 
| visible\$1suffix | INT |  输入文本末尾将保持未掩蔽状态的字符数（默认值：0）  | 
| use\$1hash\$1mask | BOOLEAN |  如果为 TRUE，则使用基于哈希的掩蔽，而不是 mask\$1char（默认值：FALSE）  | 

**Example ：使用不同的掩蔽选项**  
使用默认“X”字符掩蔽整个输入字符串  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World');
  mask_text  
-------------
 XXXXXXXXXXX
```
使用 `mask_char` 参数通过不同的字符来掩蔽文本输入  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World', '*');
  mask_text  
-------------
 ***********
```
使用 `visible_prefix` 和 `visible_suffix` 参数来控制文本开头和结尾处有多少个字符保持未掩蔽状态  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World', '*', 5, 1);
  mask_text  
-------------
 Hello*****d
```
当 `use_hash_mask` 为 true 时，使用随机字符掩蔽输入字符串而忽略 `mask_char` 参数，但 `visible_prefix` 和 `visible_suffix` 仍然有效  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World', '*', 2, 2, true);
  mask_text  
-------------
 Hex36dOHild
```

**mask\$1timestamp**


| 参数 | DataType | 说明 | 
| --- | --- | --- | 
| ts\$1to\$1mask | TIMESTAMP |  要掩蔽的原始时间戳  | 
| mask\$1part | TEXT |  指定要掩蔽时间戳的哪个部分（默认值：“全部”）。有效值：“年”、“月”、“日”、“小时”、“分钟”、“秒”、“全部”  | 
| mask\$1value | TIMESTAMP |  要用于掩蔽的时间戳值（默认值：“1900-01-01 00:00:00”）  | 

**Example 使用 `mask_timestamps`**  
这些示例演示了将时间戳完全掩蔽为默认值、部分掩蔽特定时间戳组成部分（仅限年份）以及使用自定义替换值进行掩蔽。  
将输入值完全掩蔽为默认时间戳  

```
postgres=> SELECT pgcolumnmask.mask_timestamp('2023-06-15 14:30:00');
   mask_timestamp    
---------------------
 1900-01-01 00:00:00
```
仅掩蔽时间戳的一部分，例如仅掩蔽年份  

```
postgres=> SELECT pgcolumnmask.mask_timestamp('2023-06-15 14:30:00', 'year');
   mask_timestamp    
---------------------
 1900-06-15 14:30:00
```
要更改时间戳的掩蔽值，请使用 `mask_value` 参数  

```
postgres=> SELECT pgcolumnmask.mask_timestamp('2023-06-15 14:30:00', 'all', '2012-12-12 12:12:12');
   mask_timestamp    
---------------------
 2012-12-12 12:12:12
```

**mask\$1timestamp**

一个在保留电子邮件结构的同时掩蔽电子邮件地址的函数。


| 参数 | DataType | 说明 | 
| --- | --- | --- | 
| input | TEXT |  要掩蔽的原始电子邮件地址  | 
| mask\$1char | CHAR(1) |  用于掩蔽的字符（默认值：“X”）  | 
| mask\$1local | BOOLEAN |  如果为 TRUE，则掩蔽电子邮件的本地部分（位于 @ 之前）（默认值：TRUE）  | 
| mask\$1domain | BOOLEAN |  如果为 TRUE，则掩蔽电子邮件的域部分（位于 @ 之后）（默认值：TRUE）  | 

**Example 使用 `mask_email`**  
这些示例演示了完全电子邮件掩蔽、自定义掩蔽字符以及对电子邮件地址的本地部分或域部分进行选择性掩蔽。  
完全掩蔽  

```
postgres=> SELECT pgcolumnmask.mask_email('user@example.com');
    mask_email    
------------------
 XXXX@XXXXXXX.com
```
使用 `mask_char` 更改用于掩蔽的字符  

```
postgres=> SELECT pgcolumnmask.mask_email('user@example.com', '*');
    mask_email    
------------------
 ****@*******.com
```
使用 `mask_local` 和 `mask_domain` 控制本地和域的掩蔽  

```
postgres=> SELECT pgcolumnmask.mask_email('user@example.com', '*', true, false);
    mask_email    
------------------
 ****@example.com

postgres=> SELECT pgcolumnmask.mask_email('user@example.com', '*', false, true);
    mask_email    
------------------
 user@*******.com
```

# 在端到端工作流程中实施 pg\$1columnmask
端到端工作流程

本节使用一个包含敏感数据的示例员工表，完整演示了 `pg_columnmask` 的实施过程。您将了解如何创建自定义掩蔽函数，为各种角色（实习生、支持人员、分析师）定义具有不同权重级别的多个掩蔽策略，并观察具有单个或多个角色成员资格的用户如何查看不同级别的掩蔽数据。这些示例还涵盖了带有 RETURNING 子句的 DML 语句中的掩蔽行为、表与视图上的触发器，以及包括重命名、更改权重和清理在内的策略管理操作。

1. 创建包含一些敏感数据的示例表：

   ```
   CREATE SCHEMA hr;
   
   CREATE TABLE hr.employees (
       id INT PRIMARY KEY,
       name TEXT NOT NULL,
       email TEXT,
       ssn TEXT,
       salary NUMERIC(10,2)
    );
   
   INSERT INTO hr.employees VALUES
       (1, 'John Doe', 'john.doe@example.com', '123-45-6789', 50000.00),
       (2, 'Jane Smith', 'jane.smith@example.com', '987-65-4321', 60000.00);
   ```

1. 创建自定义掩蔽函数：

   ```
   CREATE OR REPLACE FUNCTION public.mask_ssn(ssn TEXT)
       RETURNS TEXT AS $$
       BEGIN
           RETURN 'XXX-XX-' || RIGHT(ssn, 4);
       END;
       $$ LANGUAGE plpgsql;
   
   CREATE OR REPLACE FUNCTION public.mask_salary(salary NUMERIC, multiplier NUMERIC DEFAULT 0.0)
       RETURNS NUMERIC AS $$
       BEGIN
           RETURN salary * multiplier;
       END;
       $$ LANGUAGE plpgsql;
   ```

1. 根据用户角色创建多个具有不同掩蔽级别的策略：

   ```
   -- Create different roles
   CREATE ROLE analyst_role;
   CREATE ROLE support_role;
   CREATE ROLE intern_role;
   
   GRANT USAGE ON SCHEMA hr TO analyst_role, support_role, intern_role;
   GRANT SELECT ON hr.employees TO analyst_role, support_role, intern_role;
   ----------------------------------------------------------------------
   
   -- Low-Weight Policy (Intern)
   CALL pgcolumnmask.create_masking_policy(
       'employee_mask_strict',
       'hr.employees',
       JSON_BUILD_OBJECT('name', 'pgcolumnmask.mask_text(name, ''*'')',
                         'email', 'pgcolumnmask.mask_email(email)',
                         'ssn', 'pgcolumnmask.mask_text(ssn, ''*'')',
                         'salary', 'public.mask_salary(salary)')::JSONB,
       ARRAY['intern_role'],
       10  -- Lowest weight
   );
   
   ----------------------------------------------------------------------
   -- Medium-Weight Policy (Support)
   CALL pgcolumnmask.create_masking_policy(
       'employee_mask_moderate',
       'hr.employees',
       JSON_BUILD_OBJECT('email', 'pgcolumnmask.mask_email(email, ''#'')',
                         'ssn', 'public.mask_ssn(ssn)',
                         'salary', 'public.mask_salary(salary)')::JSONB,
       ARRAY['support_role'],
       50   -- Medium weight
   );
   
   ----------------------------------------------------------------------
   -- High-Weight Policy (Analyst)
   CALL pgcolumnmask.create_masking_policy(
       'employee_mask_light',
       'hr.employees',
       JSON_BUILD_OBJECT('ssn', 'public.mask_ssn(ssn)',
                         'salary', 'public.mask_salary(salary, 0.9)')::JSONB,
       ARRAY['analyst_role'],
       100   -- Highest weight
   );
   ```

1. 以下示例演示了不同的用户如何根据其角色成员资格和策略权重来查看数据。

   ```
   -- Create users
   CREATE USER sarah_intern;
   GRANT intern_role TO sarah_intern;
   
   CREATE USER lisa_support;
   GRANT support_role TO lisa_support;
   
   CREATE USER mike_analyst;
   GRANT analyst_role TO mike_analyst;
   
   CREATE USER ethan_support_intern;
   GRANT support_role, intern_role TO ethan_support_intern;
   
   CREATE USER john_analyst_intern;
   GRANT analyst_role, intern_role TO john_analyst_intern;
   ```

   作为实习生（最严格的掩蔽）：

   ```
   SET ROLE sarah_intern;
   
   SELECT * FROM hr.employees;
    id |    name    |         email          |     ssn     | salary 
   ----+------------+------------------------+-------------+--------
     1 | ********   | XXXXXXXX@XXXXXXX.com   | *********** |   0.00
     2 | ********** | XXXXXXXXXX@XXXXXXX.com | *********** |   0.00
   ```

   作为支持用户（中等掩蔽）：

   ```
   SET ROLE lisa_support;
   
   SELECT * FROM hr.employees;
    id |    name    |         email          |     ssn     | salary 
   ----+------------+------------------------+-------------+--------
     1 | John Doe   | ########@#######.com   | XXX-XX-6789 |   0.00
     2 | Jane Smith | ##########@#######.com | XXX-XX-4321 |   0.00
   ```

   作为分析师（最轻的掩蔽）：

   ```
   SET ROLE mike_analyst;
   
   SELECT * FROM hr.employees;
    id |    name    |         email          |     ssn     |  salary  
   ----+------------+------------------------+-------------+----------
     1 | John Doe   | john.doe@example.com   | XXX-XX-6789 | 45000.00
     2 | Jane Smith | jane.smith@example.com | XXX-XX-4321 | 54000.00
   ```

   作为 ethan\$1support\$1intern 用户，既是实习生，又是支持用户：

   ```
   SET ROLE ethan_support_intern;
   
   -- masking policies appliable to this user: employee_mask_strict and employee_mask_moderate
   -- id : unmasked because no masking policy appliable on ethan_support_intern
   --            masks these columns
   -- name : masked because of employee_mask_strict policy
   -- email, ssn, salary : both employee_mask_strict and employee_mask_moderate mask these columns
   --                      but employee_mask_moderate will be use because of higher weight 
   
   SELECT * FROM hr.employees;
    id |    name    |         email          |     ssn     | salary 
   ----+------------+------------------------+-------------+--------
     1 | ********   | ########@#######.com   | XXX-XX-6789 |   0.00
     2 | ********** | ##########@#######.com | XXX-XX-4321 |   0.00
   ```

   作为 john\$1analyst\$1intern，既是实习生，又是分析师：

   ```
   SET ROLE john_analyst_intern;
   
   -- masking policies appliable to this user: employee_mask_strict and employee_mask_light
   -- id : unmasked because no masking policy appliable on john_analyst_intern
   --            masks these columns
   -- name, email : masked because of employee_mask_strict
   -- ssn, salary : both employee_mask_strict and employee_mask_light mask these columns
   --               but employee_mask_light will be use because of higher weight 
   
   SELECT * FROM hr.employees;
    id |    name    |         email          |     ssn     |  salary  
   ----+------------+------------------------+-------------+----------
     1 | ********   | XXXXXXXX@XXXXXXX.com   | XXX-XX-6789 | 45000.00
     2 | ********** | XXXXXXXXXX@XXXXXXX.com | XXX-XX-4321 | 54000.00
   ```

# 了解 DML 操作中的掩蔽行为
DML 中的掩蔽

`pg_columnmask` 一致地应用于所有 DML 操作，包括 INSERT、UPDATE、DELETE 和 MERGE 语句。当您执行这些操作时，Aurora PostgreSQL 会根据核心原则掩蔽数据，即根据当前用户的适用策略掩蔽从存储中读取的任何数据。

掩蔽会影响以下一些查询组件，例如：
+ WHERE 子句
+ JOIN 条件
+ 子查询
+ RETURNING 子句

所有这些组件都对掩蔽值而不是原始数据进行操作。当数据以未掩蔽的方式写入存储时，用户只有在读回数据时才能看到其掩蔽视图。

Aurora PostgreSQL 对实际存储的值而不是掩蔽值强制执行所有数据库约束（NOT NULL、UNIQUE、CHECK、FOREIGN KEY）。如果未精心设计掩蔽函数，这偶尔会造成明显的不一致。

掩蔽与列级别权限一起使用：
+ 没有 SELECT 权限的用户无法读取列
+ 拥有 SELECT 权限的用户会根据其适用的策略查看掩蔽值

# 了解触发器函数中的掩蔽行为
触发器函数中的掩蔽

将 `pg_columnmask` 策略应用于表时，了解掩蔽如何与触发器函数交互非常重要。触发器是指为响应表上的某些事件（例如 INSERT、UPDATE 或 DELETE 操作）而自动执行的数据库函数。

默认情况下，DDM 根据触发器的类型应用不同的掩蔽规则：

表触发器  
**转换表未掩蔽**：表上的触发器函数可以访问其转换表中未掩蔽的数据，无论是对于旧的行版本还是新的行版本  
表所有者创建触发器并拥有数据，因此他们拥有有效管理表的完全访问权限

查看触发器（INSTEAD OF 触发器）  
**转换表已掩蔽**：视图上的触发器函数根据当前用户的权限查看掩蔽的数据  
视图所有者可能与基表所有者不同，应遵从有关底层表的掩蔽策略

两个服务器级别的配置参数控制对掩蔽表的触发器行为。这些只能通过 `rds_superuser` 设置：
+ **限制掩蔽表上的触发器**：当掩蔽的用户对具有适用的掩蔽策略的表执行 DML 操作时，防止触发器执行。
+ **限制带有掩蔽表的视图上的触发器**：当视图定义包括具有适用于当前用户的掩蔽策略的表时，防止在视图上执行触发器。

**Example ：函数应用于表和视图之间的区别**  
以下示例创建一个触发器函数，该函数可打印旧的行值和新的行值，然后演示同一个函数在附加到表与附加到视图时的行为有何不同。  

```
-- Create trigger function
CREATE OR REPLACE FUNCTION print_changes()
    RETURNS TRIGGER AS
    $$
        BEGIN
        RAISE NOTICE 'Old row: name=%, email=%, ssn=%, salary=%',
            OLD.name, OLD.email, OLD.ssn, OLD.salary;
        
        RAISE NOTICE 'New row: name=%, email=%, ssn=%, salary=%',
            NEW.name, NEW.email, NEW.ssn, NEW.salary;
        
        RETURN NEW;
        END;
    $$ LANGUAGE plpgsql;

-- Create trigger
CREATE TRIGGER print_changes_trigger
    BEFORE UPDATE ON hr.employees
    FOR EACH ROW
    EXECUTE FUNCTION print_changes();

-- Grant update to analyst role
GRANT UPDATE ON hr.employees TO analyst_role;

-- Unmasked data must be seen inside trigger even for masked user for the OLD and NEW
-- row passed to trigger function
BEGIN;
SET ROLE mike_analyst;
UPDATE hr.employees SET id = id + 10 RETURNING *;
NOTICE:  Old row: name=John Doe, email=john.doe@example.com, ssn=123-45-6789, salary=50000.00
NOTICE:  New row: name=John Doe, email=john.doe@example.com, ssn=123-45-6789, salary=50000.00
NOTICE:  Old row: name=Jane Smith, email=jane.smith@example.com, ssn=987-65-4321, salary=60000.00
NOTICE:  New row: name=Jane Smith, email=jane.smith@example.com, ssn=987-65-4321, salary=60000.00
 id |    name    |         email          |     ssn     |  salary  
----+------------+------------------------+-------------+----------
 11 | John Doe   | john.doe@example.com   | XXX-XX-6789 | 45000.00
 12 | Jane Smith | jane.smith@example.com | XXX-XX-4321 | 54000.00
(2 rows)

ROLLBACK;


-- Triggers on views (which are supposed to see masked data for new/old row)
CREATE VIEW hr.view_over_employees AS SELECT * FROM hr.employees;
GRANT UPDATE, SELECT ON hr.view_over_employees TO analyst_role;

-- Create trigger for this view
CREATE TRIGGER print_changes_trigger
    INSTEAD OF UPDATE ON hr.view_over_employees
    FOR EACH ROW
    EXECUTE FUNCTION print_changes();

-- Masked new and old rows should be passed to trigger if trigger is on view
BEGIN;
SET ROLE mike_analyst;
UPDATE hr.view_over_employees SET id = id + 10 RETURNING *;
NOTICE:  Old row: name=John Doe, email=john.doe@example.com, ssn=XXX-XX-6789, salary=45000.00
NOTICE:  New row: name=John Doe, email=john.doe@example.com, ssn=XXX-XX-6789, salary=45000.00
NOTICE:  Old row: name=Jane Smith, email=jane.smith@example.com, ssn=XXX-XX-4321, salary=54000.00
NOTICE:  New row: name=Jane Smith, email=jane.smith@example.com, ssn=XXX-XX-4321, salary=54000.00
 id |    name    |         email          |     ssn     |  salary  
----+------------+------------------------+-------------+----------
 11 | John Doe   | john.doe@example.com   | XXX-XX-6789 | 45000.00
 12 | Jane Smith | jane.smith@example.com | XXX-XX-4321 | 54000.00
(2 rows)
ROLLBACK;
```
我们建议在掩蔽表上实现触发器之前，先检查触发器行为。表触发器可以访问转换表中的未掩蔽数据，而视图触发器可以看到掩蔽的数据。

**Example ：重命名掩蔽策略**  
以下示例演示如何使用 `rename_masking_policy` 过程重命名现有策略。  

```
-- Rename the strict policy
CALL pgcolumnmask.rename_masking_policy(
    'employee_mask_strict',
    'hr.employees',
    'intern_protection_policy'
);

-- Verify the rename
SELECT policyname, roles, weight
    FROM pgcolumnmask.pg_columnmask_policies
    WHERE tablename = 'employees'
    ORDER BY weight DESC;

        policyname        |     roles      | weight 
--------------------------+----------------+--------
 employee_mask_light      | {analyst_role} |    100
 employee_mask_moderate   | {support_role} |     50
 intern_protection_policy | {intern_role}  |     10
```

**Example ：更改策略权重**  
以下示例演示如何更改策略权重以更改其权重。  

```
-- Change weight of moderate policy
CALL pgcolumnmask.alter_masking_policy(
    'employee_mask_moderate'::NAME,
    'hr.employees'::REGCLASS,
    NULL,    -- Keep existing masking expressions
    NULL,    -- Keep existing roles
    75       -- New weight
);

-- Verify the changes
SELECT policyname, roles, weight
    FROM pgcolumnmask.pg_columnmask_policies
    WHERE tablename = 'employees'
    ORDER BY weight DESC;
        policyname        |     roles      | weight 
--------------------------+----------------+--------
 employee_mask_light      | {analyst_role} |    100
 employee_mask_moderate   | {support_role} |     75
 intern_protection_policy | {intern_role}  |     10
```

**Example ：清理**  
以下示例演示如何删除所有策略、表和用户。  

```
-- Drop policies
CALL pgcolumnmask.drop_masking_policy(
    'intern_protection_policy',
    'hr.employees'
);

CALL pgcolumnmask.drop_masking_policy(
    'employee_mask_moderate',
    'hr.employees'
);

CALL pgcolumnmask.drop_masking_policy(
    'employee_mask_light',
    'hr.employees'
);

-- Drop table and functions
DROP VIEW IF EXISTS hr.view_over_employees;
DROP TABLE IF EXISTS hr.employees;
DROP SCHEMA IF EXISTS hr;
DROP FUNCTION IF EXISTS public.mask_ssn(text);
DROP FUNCTION IF EXISTS public.mask_salary(numeric, numeric);

-- Drop users
DROP USER sarah_intern, lisa_support, mike_analyst,
    ethan_support_intern, john_analyst_intern;
DROP ROLE intern_role, support_role, analyst_role;
```

# 配置掩蔽策略管理角色
掩蔽策略管理角色

PostgreSQL 列掩蔽扩展 `pg_columnmask` 支持您将掩蔽策略的管理委托给特定角色，而不是要求 `rds_superuser` 或表所有者权限。这样就可以更精细地控制谁可以创建、更改和删除掩蔽策略。

要配置将具有掩蔽策略管理权限的角色，请执行以下步骤：

1. 创建策略管理员角色：作为 `rds_superuser`，创建一个负责管理掩蔽策略的新角色：

   ```
   CREATE ROLE mask_admin NOLOGIN;
   ```

1. 配置 PostgreSQL 参数：在自定义数据库集群参数组中，将 `pgcolumnmask.policy_admin_rolname` 引擎配置参数设置为您创建的角色的名称：

   ```
   pgcolumnmask.policy_admin_rolname = mask_admin
   ```

   可以在数据库集群参数组中设置此引擎配置参数，并且不需要重启实例。有关更新参数的详细信息，请参阅[在 Amazon Aurora 中修改数据库集群参数组中的参数](USER_WorkingWithParamGroups.ModifyingCluster.md)。

1. 将该角色授予用户。作为 `rds_superuser`，将 `mask_admin` 角色授予应能够管理掩蔽策略的用户：

   ```
   CREATE USER alice LOGIN;
   CREATE USER bob LOGIN;
   GRANT mask_admin TO alice, bob;
   ```

   此外，请确保用户对其将在其中管理掩蔽策略的架构拥有 USAGE 权限：

   ```
   GRANT USAGE ON SCHEMA hr TO alice, bob;
   ```

现在，当用户 `alice` 和 `bob` 连接到数据库时，他们可以使用标准 `pg_columnmask` 扩展函数，在他们对架构拥有 `USAGE` 权限的所有架构中的所有表上创建、更改和删除掩蔽策略。

# 安全 pg\$1columnmask 实施的最佳实践
最佳实践

下一节提供了在 Aurora PostgreSQL 环境中实施 `pg_columnmask` 的安全最佳实践。请遵循以下建议，以便：
+ 建立基于角色的安全访问控制架构
+ 开发用于防范安全漏洞的掩蔽函数
+ 使用掩蔽数据了解和控制触发器行为

## 基于角色的安全架构
安全架构

定义角色层次结构，以便在数据库中实施访问控制。Aurora PostgreSQL `pg_columnmask` 通过在这些角色中提供额外的一层精细数据掩蔽，从而增强了这些控制措施。

创建与组织职能相一致的专用角色，而不是向各个用户授予权限。随着组织结构发展，这种方法可提供更好的可审计性并简化权限管理。

**Example ：创建组织角色层次结构**  
以下示例创建了一个组织角色层次结构（其中包含用于不同职能的专用角色），然后将各个用户分配给相应的角色。在此示例中，首先创建组织角色（analyst\$1role、support\$1role），然后向各个用户授予这些角色中的成员资格。这种结构支持您管理角色级别的权限，而不是每个单独用户的权限。  

```
-- Create organizational role hierarchy
CREATE ROLE data_admin_role;
CREATE ROLE security_admin_role;
CREATE ROLE analyst_role;
CREATE ROLE support_role;
CREATE ROLE developer_role;

-- Specify security_admin_role as masking policy manager in the DB cluster parameter
-- group pgcolumnmask.policy_admin_rolname = 'security_admin_role'

-- Create specific users and assign to appropriate roles
CREATE USER security_manager;
CREATE USER data_analyst1, data_analyst2;
CREATE USER support_agent1, support_agent2;

GRANT security_admin_role TO security_manager;
GRANT analyst_role TO data_analyst1, data_analyst2;
GRANT support_role TO support_agent1, support_agent2;
```
通过仅授予每个角色所需的最少权限，实现最低权限原则。避免授予在凭证泄露时可能被利用的宽泛权限。  

```
-- Grant specific table permissions rather than schema-wide access
GRANT SELECT ON sensitive_data.customers TO analyst_role;
GRANT SELECT ON sensitive_data.transactions TO analyst_role;
-- Do not grant: GRANT ALL ON SCHEMA sensitive_data TO analyst_role;
```
策略管理员需要对他们在其中管理掩蔽策略的架构拥有 `USAGE` 权限。遵循最低权限原则，有选择地授予这些权限。定期审查架构访问权限，以确保只有获得授权的人员才能维护策略管理功能。  
策略管理员角色参数配置仅限于数据库管理员。无法在数据库或会话级别修改此参数，从而防止非特权用户覆盖策略管理员分配。此限制可确保掩蔽策略控制保持集中化和安全。  
将策略管理员角色分配给特定的个人而不是组。这种有针对性的方法可确保对掩蔽策略管理进行选择性访问，因为策略管理员能够掩蔽数据库中的所有表。

## 安全的掩蔽函数开发
掩蔽函数开发

使用早期绑定语义开发掩蔽函数，以确保适当的依赖项跟踪，并防止延迟绑定漏洞，例如在运行时修改搜索路径。建议对 SQL 函数使用 `BEGIN ATOMIC` 语法来启用编译时验证（即早期绑定）和依赖项管理。

```
-- Example - Secure masking function with early binding
CREATE OR REPLACE FUNCTION secure_mask_ssn(input_ssn TEXT)
    RETURNS TEXT
    LANGUAGE SQL
    IMMUTABLE PARALLEL SAFE STRICT
    BEGIN ATOMIC
        SELECT CASE
            WHEN input_ssn IS NULL THEN NULL
            WHEN length(input_ssn) < 4 THEN repeat('X', length(input_ssn))
            ELSE repeat('X', length(input_ssn) - 4) || right(input_ssn, 4)
        END;
    END;
```

或者，通过对所有对象引用进行显式架构限定，来创建不受搜索路径更改影响的函数，从而确保不同用户会话中的行为一致性。

```
-- Function immune to search path changes
CREATE OR REPLACE FUNCTION data_masking.secure_phone_mask(phone_number TEXT)
    RETURNS TEXT
    LANGUAGE SQL
    IMMUTABLE PARALLEL SAFE STRICT
    AS $$
    SELECT CASE
        WHEN phone_number IS NULL THEN NULL
        WHEN public.length(public.regexp_replace(phone_number, '[^0-9]', '', 'g')) < 10 THEN 'XXX-XXX-XXXX'
        ELSE public.regexp_replace(
            phone_number,
            '([0-9]{3})[0-9]{3}([0-9]{4})',
            public.concat('\1-XXX-\2')
        )
    END;
    $$;
```

在掩蔽函数中实施输入验证，以处理边缘情况并防止意外行为。始终包含 NULL 处理并验证输入格式，以确保一致的掩蔽行为。

```
-- Robust masking function with comprehensive input validation
CREATE OR REPLACE FUNCTION secure_mask_phone(phone_number TEXT)
    RETURNS TEXT
    LANGUAGE SQL
    IMMUTABLE PARALLEL SAFE STRICT
    BEGIN ATOMIC
        SELECT CASE
            WHEN phone_number IS NULL THEN NULL
            WHEN length(trim(phone_number)) = 0 THEN phone_number
            WHEN length(regexp_replace(phone_number, '[^0-9]', '', 'g')) < 10 THEN 'XXX-XXX-XXXX'
            ELSE regexp_replace(phone_number, '([0-9]{3})[0-9]{3}([0-9]{4})', '\1-XXX-\2')
        END;
    END;
```

## 带有 pg\$1columnmask 的 DML 触发器行为
DML 触发器行为

对于表触发器，将对转换表完全不掩蔽。对于视图触发器（IOT），将根据当前用户的视图权限掩蔽转换表。

带有 pg\$1columnmask 的表触发器  
将向触发器传递一个转换表，其中包含由负责触发的 DML 查询修改的行的旧版本和新版本。根据触发器的触发时间，Aurora PostgreSQL 填充旧行和新行。例如，`BEFORE INSERT` 触发器只有行的新版本和空的旧版本，因为没有旧版本可供引用。  
`pg_columnmask` 不会掩蔽表上触发器内的转换表。触发器可以在其主体中使用掩蔽的列，并看到未掩蔽的数据。触发器创建者应确保了解如何为用户执行触发器。在这种情况下，以下示例可以正常工作。  

```
-- Example for table trigger uses masked column in its definition
-- Create a table and insert some rows
CREATE TABLE public.credit_card_table (
    name TEXT,
    credit_card_no VARCHAR(16),
    is_fraud BOOL
);

INSERT INTO public.credit_card_table (name, credit_card_no, is_fraud)
    VALUES
    ('John Doe', '4532015112830366', false),
    ('Jane Smith', '5410000000000000', true),
    ('Brad Smith', '1234567891234567', true);

-- Create a role which will see masked data and grant it privileges
CREATE ROLE intern_user;
GRANT SELECT, DELETE ON public.credit_card_table TO intern_user;

-- Trigger which will silenty skip delete of non fraudelent credit cards
CREATE OR REPLACE FUNCTION prevent_non_fraud_delete()
    RETURNS TRIGGER AS
    $$
    BEGIN
        IF OLD.is_fraud = false THEN
            RETURN NULL;
        END IF;
        RETURN OLD;
    END;
    $$ LANGUAGE plpgsql;

CREATE TRIGGER prevent_non_fraud_delete
    BEFORE DELETE ON credit_card_table
    FOR EACH ROW
    EXECUTE FUNCTION prevent_non_fraud_delete();

CREATE OR REPLACE FUNCTION public.return_false()
    RETURNS BOOLEAN
    LANGUAGE SQL
    IMMUTABLE PARALLEL SAFE STRICT
    BEGIN ATOMIC
      SELECT false;
    END;

-- A masking policy that masks both credit card number and is_fraud column.
-- If we apply masking inside trigger then prevent_non_fraud_delete trigger will
-- allow deleting more rows to masked user (even non fraud ones).
CALL pgcolumnmask.create_masking_policy(
    'mask_credit_card_no_&_is_fraud'::NAME,
    'public.credit_card_table'::REGCLASS,
    JSON_BUILD_OBJECT('credit_card_no', 'pgcolumnmask.mask_text(credit_card_no)',
                      'is_fraud', 'public.return_false()')::JSONB,
    ARRAY['intern_user']::NAME[],
    10::INT
);

-- Test trigger behaviour using intern_user
BEGIN;
SET ROLE intern_user;
-- credit card number & is_fraud is completely masked from intern_user
SELECT * FROM public.credit_card_table;
    name    |  credit_card_no  | is_fraud 
------------+------------------+----------
 John Doe   | XXXXXXXXXXXXXXXX | f
 Jane Smith | XXXXXXXXXXXXXXXX | f
 Brad Smith | XXXXXXXXXXXXXXXX | f
(3 rows)

-- The delete trigger lets the intern user delete rows for Jane and Brad even though
-- intern_user sees their is_fraud = false, but the table trigger works with original
-- unmasked value
DELETE FROM public.credit_card_table RETURNING *;
    name    |  credit_card_no  | is_fraud 
------------+------------------+----------
 Jane Smith | XXXXXXXXXXXXXXXX | f
 Brad Smith | XXXXXXXXXXXXXXXX | f
(2 rows)

COMMIT;
```
如果触发器创建者对他们在触发器主体中使用的语句不够谨慎，则触发器创建者会向用户泄露未掩蔽的数据。例如，使用 `RAISE NOTICE ‘%’, masked_column;` 会将列打印给当前用户。  

```
-- Example showing table trigger leaking column value to current user
CREATE OR REPLACE FUNCTION leaky_trigger_func()
    RETURNS TRIGGER AS
    $$
    BEGIN
        RAISE NOTICE 'Old credit card number was: %', OLD.credit_card_no;
        RAISE NOTICE 'New credit card number is %', NEW.credit_card_no;
        RETURN NEW;
    END;
    $$ LANGUAGE plpgsql;

CREATE TRIGGER leaky_trigger
    AFTER UPDATE ON public.credit_card_table
    FOR EACH ROW
    EXECUTE FUNCTION leaky_trigger_func();

-- Grant update on column is_fraud to auditor role
-- auditor will NOT HAVE PERMISSION TO READ DATA
CREATE ROLE auditor;
GRANT UPDATE (is_fraud) ON public.credit_card_table TO auditor;

-- Also add auditor role to existing masking policy on credit card table
CALL pgcolumnmask.alter_masking_policy(
    'mask_credit_card_no_&_is_fraud'::NAME,
    'public.credit_card_table'::REGCLASS,
    NULL::JSONB,
    ARRAY['intern_user', 'auditor']::NAME[],
    NULL::INT
);

-- Log in as auditor
-- [auditor]
-- Update will fail if trying to read data from the table
UPDATE public.credit_card_table
    SET is_fraud = true
    WHERE credit_card_no = '4532015112830366';
ERROR:  permission denied for table cc_table

-- [auditor]
-- But leaky update trigger will still print the entire row even though
-- current user does not have permission to select from public.credit_card_table
UPDATE public.credit_card_table SET is_fraud = true;
NOTICE:  Old credit_card_no was: 4532015112830366
NOTICE:  New credit_card_no is 4532015112830366
```

带有 pg\$1columnmask 的视图上的触发器（Instead of 触发器）  
只能在 PostgreSQL 中的视图上创建触发器。它们用于对不可更新的视图运行 DML 语句。传输表始终在 Instead of 触发器（IOT）中被掩蔽，因为在视图查询中使用的视图和基表可能有不同的所有者。在这种情况下，基表可能有一些适用于视图所有者的掩蔽策略，并且视图所有者必须始终在其触发器中看到来自基表的掩蔽数据。这与表上的触发器不同，因为在这种情况下，触发器创建者和表中的数据归同一个用户所有，而这里的情况并非如此。  

```
-- Create a view over credit card table
CREATE OR REPLACE VIEW public.credit_card_view
    AS
    SELECT * FROM public.credit_card_table;

-- Truncate credit card table and insert fresh data
TRUNCATE TABLE public.credit_card_table;
INSERT INTO public.credit_card_table (name, credit_card_no, is_fraud)
    VALUES
    ('John Doe', '4532015112830366', false),
    ('Jane Smith', '5410000000000000', true),
    ('Brad Smith', '1234567891234567', true);

CREATE OR REPLACE FUNCTION public.print_changes()
    RETURNS TRIGGER AS
    $$
    BEGIN
        RAISE NOTICE 'Old row: name=%, credit card number=%, is fraud=%',
            OLD.name, OLD.credit_card_no, OLD.is_fraud;
    
        RAISE NOTICE 'New row: name=%, credit card number=%, is fraud=%',
            NEW.name, NEW.credit_card_no, NEW.is_fraud;
    
    RETURN NEW;
   END;
   $$ LANGUAGE plpgsql;

CREATE TRIGGER print_changes_trigger
    INSTEAD OF UPDATE ON public.credit_card_view
    FOR EACH ROW
    EXECUTE FUNCTION public.print_changes();

GRANT SELECT, UPDATE ON public.credit_card_view TO auditor;

-- [auditor]
-- Login as auditor role
BEGIN;

-- Any data coming out from the table will be masked in instead of triggers
-- according to masking policies applicable to current user
UPDATE public.credit_card_view
    SET name = CONCAT(name, '_new_name')
    RETURNING *;
NOTICE:  Old row: name=John Doe, credit card number=XXXXXXXXXXXXXXXX, is fraud=f
NOTICE:  New row: name=John Doe_new_name, credit card number=XXXXXXXXXXXXXXXX, is fraud=f
NOTICE:  Old row: name=Jane Smith, credit card number=XXXXXXXXXXXXXXXX, is fraud=f
NOTICE:  New row: name=Jane Smith_new_name, credit card number=XXXXXXXXXXXXXXXX, is fraud=f
NOTICE:  Old row: name=Brad Smith, credit card number=XXXXXXXXXXXXXXXX, is fraud=f
NOTICE:  New row: name=Brad Smith_new_name, credit card number=XXXXXXXXXXXXXXXX, is fraud=f
        name         |  credit_card_no  | is_fraud 
---------------------+------------------+----------
 John Doe_new_name   | XXXXXXXXXXXXXXXX | f
 Jane Smith_new_name | XXXXXXXXXXXXXXXX | f
 Brad Smith_new_name | XXXXXXXXXXXXXXXX | f
 
 -- Any new data going into the table using INSERT or UPDATE command will be unmasked
 UPDATE public.credit_card_view
    SET credit_card_no = '9876987698769876'
    RETURNING *;
NOTICE:  Old row: name=John Doe, credit card number=XXXXXXXXXXXXXXXX, is fraud=f
NOTICE:  New row: name=John Doe, credit card number=9876987698769876, is fraud=f
NOTICE:  Old row: name=Jane Smith, credit card number=XXXXXXXXXXXXXXXX, is fraud=f
NOTICE:  New row: name=Jane Smith, credit card number=9876987698769876, is fraud=f
NOTICE:  Old row: name=Brad Smith, credit card number=XXXXXXXXXXXXXXXX, is fraud=f
NOTICE:  New row: name=Brad Smith, credit card number=9876987698769876, is fraud=f
    name    |  credit_card_no  | is_fraud 
------------+------------------+----------
 John Doe   | 9876987698769876 | f
 Jane Smith | 9876987698769876 | f
 Brad Smith | 9876987698769876 | f
 
 COMMIT;
```

用于控制触发器行为的数据库/用户级别 GuC  
两个配置参数控制具有适用掩蔽策略的用户的触发器执行行为。当需要额外的安全限制时，使用这些参数可以防止对掩蔽表或视图执行触发器。默认情况下，这两个参数均处于禁用状态，可让触发器正常执行。  
**第一个 GUC：对掩蔽表触发限制的触发器**  
规范：  
+ 名称：`pgcolumnmask.restrict_dml_triggers_for_masked_users`
+ 类型：`boolean`
+ 默认值：`false`（支持执行触发器）
设置为 TRUE 时，防止掩蔽的用户对掩蔽表执行触发器。`pg_columnmask` 运行直至出现错误。  
**第二个 GUC：对带有掩蔽表的视图触发限制的触发器**  
规范：  
+ 名称：`pgcolumnmask.restrict_iot_triggers_for_masked_users`
+ 类型：`boolean`
+ 默认值：`false`（支持执行触发器）
当设置为 TRUE 时，防止掩蔽的用户对在定义中包含掩蔽表的视图执行触发器。

这些参数独立运行，可以像标准数据库配置参数一样进行配置。

# Aurora PostgreSQL pg\$1columnmask 数据移动场景
数据移动场景

`pg_columnmask` 的行为因数据移动操作不同而异，具体取决于操作是发生在存储层、逻辑层还是应用程序层。存储级别操作（例如克隆）的行为不同于逻辑操作（例如 `pg_dump`）和应用程序级别操作（例如 FDW 查询）。本节介绍常见场景（包括复制、备份、导出和迁移）的掩蔽行为，并解释了每种场景的安全影响。

**Topics**
+ [

## Aurora Global Database 和只读副本
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.RR)
+ [

## 数据库克隆和快照还原
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Clones)
+ [

## 逻辑复制
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.LogRep)
+ [

## 蓝/绿部署
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.BlueGreen)
+ [

## 零 ETL 和 CDC 流
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.ZETL)
+ [

## Amazon Database Migration Service
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DMS)
+ [

## 数据导出
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DataExport)
+ [

## 视图和实体化视图
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Views)
+ [

## 数据转储和还原
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DDR)
+ [

## 外部数据包装器
](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.FDQ)

## Aurora Global Database 和只读副本
副本

Aurora `pg_columnmask` 策略存储在集群卷内的数据库系统表中。所有副本都访问相同的策略并返回以一致的方式掩蔽的结果。对于 Aurora Global Database 部署，`pg_columnmask` 策略会与其它数据库系统表一起复制到辅助 Amazon Web Services 区域，从而确保跨区域的数据保护一致性。在失效转移场景中，所有 `pg_columnmask` 策略都保持不变且正常运行。

## 数据库克隆和快照还原
克隆和还原

Aurora 快速克隆和快照还原操作将所有 `pg_columnmask` 策略、角色和配置保留为数据库系统表的一部分。克隆或还原的数据库会从源集群继承所有现有策略。克隆或还原后，每个数据库集群都会维护独立的 `pg_columnmask` 策略。

## 逻辑复制
逻辑复制

在初始同步期间，逻辑复制使用标准 SQL COPY 操作，并根据复制用户的权限强制执行 `pg_columnmask` 策略。在持续的 CDC（变更数据捕获）期间，不应用掩蔽策略，而通过 WAL 记录复制未掩蔽的数据。具有 `pg_create_subscription` 权限的用户可能会通过设置复制到他们控制的系统，来潜在泄露未掩蔽的数据。

## 蓝/绿部署
蓝/绿部署

在快照还原期间，会自动将 `pg_columnmask` 策略包括在内。绿色环境从蓝色环境中所有策略的相同副本开始。在从蓝色复制到绿色期间，不对数据进行掩蔽。蓝色集群上的后续掩蔽策略更改（DDL 命令）不会复制到绿色集群，也不会使 RDS 蓝绿部署失效。

## 零 ETL 和 CDC 流
零 ETL 和 CDC

数据复制不受 `pg_columnmask` 策略影响。零 ETL 支持 DDL 复制，但不复制 `pg_columnmask` 或 RLS 策略。不对零 ETL 中的复制数据应用任何掩蔽策略。

## Amazon Database Migration Service
Amazon DMS

根据为 DMS 任务选择的用户，对初始数据同步进行掩蔽或不进行掩蔽。始终不对 CDC 数据进行掩蔽。虽然可以迁移 `pg_columnmask` 相关的内部 RLS 策略，但它们无法在 non-pg\$1columnmask-enabled 目标上起作用。

## 数据导出
数据导出

`pg_columnmask` 像对待任何其它查询操作一样对待导出，即根据执行用户的权限应用掩蔽。这适用于诸如 COPY、SELECT INTO、CREATE TABLE AS 等 SQL 命令和 Aurora PostgreSQL 的 S3 导出功能。

**注意**  
当掩蔽的用户导出数据时，生成的文件包含掩蔽的值，这些值在还原时可能会违反数据库约束。

## 视图和实体化视图
视图

使用视图时，请记住以下注意事项：
+ **常规视图**：始终使用 `INVOKER` 语义。无论视图的创建者是谁，当前用户的掩蔽策略在查询视图时都适用。
+ **实体化视图**：刷新后，将应用实体化视图所有者的掩蔽策略，而不是执行刷新的用户的策略。如果所有者有掩蔽策略，则实体化视图将始终包含掩蔽的数据。

## 数据转储和还原
数据转储和还原

`pg_dump` 以普通数据库用户身份运行，并根据连接用户的权限应用掩蔽策略。如果掩蔽的用户执行转储，则备份文件将包含掩蔽的数据。`pg_columnmask` 策略作为数据库架构的一部分包含在转储中。成功还原要求所有引用的角色都存在于目标数据库中，并且目标必须安装了 `pg_columnmask` 扩展。

**注意**  
从 PostgreSQL 18 开始，`pg_dump` 支持 `—no-policies` 选项，该选项从数据库转储中同时排除行级别安全性（RLS）和 `pg_columnmask` 掩蔽策略。有关更多信息，请参阅 [pg\$1dump](https://www.postgresql.org/docs/current/app-pgdump.html)。

## 外部数据包装器
外部数据包装器

使用外部数据包装器时，对远程表应用掩蔽策略的依据是映射用户在源服务器上的权限，而不是本地查询用户的权限，虽然您可以通过 FDW 访问掩蔽的远程数据，但您不能直接在本地数据库中的外部表上创建 DDM 或 RLS 策略。