

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 搭配 Aurora PostgreSQL 使用動態遮罩
<a name="AuroraPostgreSQL.Security.DynamicMasking"></a>

動態資料遮罩是一項安全功能，可透過控制資料在查詢時對使用者顯示的方式，保護 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 版和更新版本。可免費使用。

# 開始使用動態遮罩
<a name="AuroraPostgreSQL.Security.DynamicMasking.GetStarted"></a>

若要動態遮罩資料，您可以在資料庫中安裝 `pg_columnmask` 擴充功能，並為資料表建立遮罩政策。設定程序包含先決條件驗證、延伸安裝、角色組態、政策建立和驗證測試。

## 延伸模組安裝和組態
<a name="AuroraPostgreSQL.Security.DynamicMasking.GetStarted.Installation"></a>

使用 RDS 主控台查詢編輯器或 PostgreSQL 用戶端連線至 Aurora PostgreSQL 叢集，例如具有 rds\$1superuser （主要使用者） 憑證的 psql。

執行延伸項目建立命令以啟用 `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)
```

# 管理資料遮罩政策的程序
<a name="AuroraPostgreSQL.Security.DynamicMasking.Procedures"></a>

您可以使用 `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
<a name="AuroraPostgreSQL.Security.DynamicMasking.Procedures.CreateMaskingPolicy"></a>

下列程序會為使用者資料表建立新的遮罩政策：

**語法**

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

**Arguments (引數)**


| 參數 | Datatype | Description | 
| --- | --- | --- | 
| policy\$1name | NAME |  遮罩政策的名稱。每個資料表必須是唯一的。  | 
| table\$1name | REGCLASS |  要套用遮罩政策之資料表的合格/不合格名稱或 oid。  | 
| masking\$1expressions | JSONB |  包含資料欄名稱和遮罩函數對的 JSON 物件。每個索引鍵都是資料欄名稱，其值是要套用至該資料欄的遮罩表達式。  | 
| roles | NAME【】 |  此遮罩政策套用的角色。預設為 PUBLIC。  | 
| weight | INT |  遮罩政策的權重。當多個政策適用於指定使用者的查詢時，具有最高權重 （較高的整數） 的政策將套用至每個遮罩的資料欄。 預設值為 0。資料表上沒有任何兩個遮罩政策可以具有相同的 Wight。  | 

**傳回類型**

無

**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
<a name="AuroraPostgreSQL.Security.DynamicMasking.Procedures.AlterMaskingPolicy"></a>

此程序會修改現有的遮罩政策。 `ALTER_MASKING_POLICY`可以修改政策遮罩表達式、政策套用的一組角色，以及遮罩政策的權重。省略其中一個參數時，政策的對應部分保持不變。

**語法**

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

**Arguments (引數)**


| 參數 | Datatype | Description | 
| --- | --- | --- | 
| policy\$1name | NAME |  遮罩政策的現有名稱。  | 
| table\$1name | REGCLASS |  包含遮罩政策之資料表的合格/不合格名稱 oid。  | 
| masking\$1expressions | JSONB |  包含資料欄名稱和遮罩函數對或 NULL 的新 JSON 物件。  | 
| 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
<a name="AuroraPostgreSQL.Security.DynamicMasking.Procedures.DropMaskingPolicy"></a>

此程序會移除現有的遮罩政策。

**語法**

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

**Arguments (引數)**


| 參數 | Datatype | Description | 
| --- | --- | --- | 
| policy\$1name | NAME |  遮罩政策的現有名稱。  | 
| table\$1name | REGCLASS |  包含遮罩政策之資料表的合格/不合格名稱 oid。  | 

**傳回類型**

無

**Example 捨棄遮罩政策 customer\$1mask**  

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

# 遮罩政策 DDL 程序中的逸出識別符
<a name="AuroraPostgreSQL.Security.DynamicMasking.EscapeIdentifiers"></a>

使用引號識別符建立資料遮罩政策時，需要適當的逸出，以確保正確的物件參考和政策應用程式。若要在`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
```

## 管理檢視
<a name="AuroraPostgreSQL.Security.DynamicMasking.AdminViews"></a>

您可以使用可公開存取的`pgcolumnmask.pg_columnmask_policies`管理檢視來檢閱所有`pg_columnmask`政策。下列資訊可透過此檢視取得。檢視只會傳回目前使用者擁有的遮罩政策。


| 欄名稱 | 資料類型 | Description | 
| --- | --- | --- | 
|  結構描述名稱  | NAME |  政策所連接關係的結構描述  | 
|  資料表名稱  | NAME |  政策所連接關係的名稱  | 
|  policyname  | NAME |  遮罩政策的名稱，所有遮罩政策都有唯一的名稱  | 
|  roles  | TEXT【】 |  政策適用的角色。  | 
|  masked\$1columns  | TEXT【】 |  遮罩的資料欄  | 
|  masking\$1functions  | TEXT【】 |  遮罩函數  | 
| Weight | INT |  附加政策的權重  | 

# 預先定義的資料遮罩函數
<a name="AuroraPostgreSQL.Security.DynamicMasking.PredefinedMaskingFunctions"></a>

`pg_columnmask` extension 提供以 C 語言寫入的內建公用程式函數 （以加快執行速度），可用於`pg_columnmask`政策的遮罩表達式。

**mask\$1text**

使用可設定的可見性選項遮罩文字資料的 函數。

**Arguments (引數)**


| 參數 | Datatype | Description | 
| --- | --- | --- | 
| 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 | Description | 
| --- | --- | --- | 
| ts\$1to\$1mask | TIMESTAMP |  要遮罩的原始時間戳記  | 
| mask\$1part | TEXT |  指定要遮罩的時間戳記部分 （預設值：'all') 有效值：'year'、'month'、'day'、'hour'、'minute'、'second'、'all'  | 
| 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 | Description | 
| --- | --- | --- | 
| 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
```

# 在end-to-end工作流程中實作 pg\$1columnmask
<a name="AuroraPostgreSQL.Security.DynamicMasking.WorkflowExample"></a>

本節示範`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 使用者，該使用者同時是 intern 和支援使用者：

   ```
   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，它是 intern 和分析師：

   ```
   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 操作中的遮罩行為
<a name="AuroraPostgreSQL.Security.DynamicMasking.DMLMasking"></a>

`pg_columnmask` 會一致地套用至所有 DML 操作，包括 INSERT、UPDATE、DELETE 和 MERGE 陳述式。當您執行這些操作時，Aurora PostgreSQL 會根據核心原則遮罩資料 – 從儲存體讀取的任何資料都會根據目前使用者的適用政策遮罩。

遮罩會影響下列一些查詢元件，例如：
+ WHERE 子句
+ JOIN 條件
+ 子查詢
+ RETURNING 子句

所有這些元件都以遮罩值運作，而不是原始資料。當資料寫入儲存體時未遮罩，使用者只會在回讀時看到其遮罩檢視。

Aurora PostgreSQL 會對實際儲存的值強制執行所有資料庫限制 （非 NULL、UNIQUE、 CHECK、FOREIGN KEY)，而非遮罩的值。如果未仔細設計遮罩函數，這有時可能會產生明顯的不一致。

遮罩可與資料欄層級許可搭配使用：
+ 沒有 SELECT 權限的使用者無法讀取資料欄
+ 具有 SELECT 權限的使用者會根據其適用的政策查看遮罩的值

# 了解觸發函數中的遮罩行為
<a name="AuroraPostgreSQL.Security.DynamicMasking.TriggerFunctionMasking"></a>

將`pg_columnmask`政策套用至資料表時，請務必了解遮罩如何與觸發函數互動。觸發條件是自動執行的資料庫函數，以回應資料表上的某些事件，例如 INSERT、UPDATE 或 DELETE 操作。

根據預設，DDM 會根據觸發類型套用不同的遮罩規則：

資料表觸發條件  
**未遮罩轉換資料表** – 資料表上的觸發函數可以存取其轉換資料表中舊資料列和新資料列版本的未遮罩資料  
資料表擁有者會建立觸發條件並擁有資料，因此他們具有有效管理資料表的完整存取權

檢視觸發 (INSTEAD OF Triggers)  
**轉換資料表已遮罩** – 檢視上的觸發函數會根據目前使用者的許可查看遮罩的資料  
檢視擁有者可能與基礎資料表擁有者不同，因此應遵守基礎資料表上的遮罩政策

兩個伺服器層級組態參數會使用遮罩資料表控制觸發行為。這些只能由 設定`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;
```

# 設定遮罩政策管理角色
<a name="AuroraPostgreSQL.Security.DynamicMasking.PolicyManagementRole"></a>

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 實作的最佳實務
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices"></a>

下一節提供`pg_columnmask`在 Aurora PostgreSQL 環境中實作 的安全最佳實務。請遵循這些建議：
+ 建立安全的角色型存取控制架構
+ 開發防止安全漏洞的遮罩函數
+ 使用遮罩資料了解和控制觸發行為

## 角色型安全架構
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices.architecture"></a>

定義角色階層以在資料庫中實作存取控制。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`的權限。遵循最低權限原則，選擇性地授予這些權限。定期審查結構描述存取許可，以確保只有獲得授權的人員才能維護政策管理功能。  
政策管理員角色參數組態僅限於資料庫管理員。此參數無法在資料庫或工作階段層級修改，防止無權限使用者覆寫政策管理員指派。此限制可確保遮罩政策控制保持集中且安全。  
將政策管理員角色指派給特定個人，而非群組。此目標方法可確保選擇性存取遮罩政策管理，因為政策管理員能夠遮罩資料庫中的所有資料表。

## 安全遮罩函數開發
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices.MaskingDevelopment"></a>

使用早期繫結語意開發遮罩函數，以確保適當的相依性追蹤，並防止延遲繫結漏洞，例如在執行期修改搜尋路徑。建議使用 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;
```

## DML 使用 pg\$1columnmask 觸發行為
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices.DMLTriggerBehavior"></a>

對於資料表觸發，轉換資料表將完全取消遮罩。對於檢視觸發 (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 的檢視觸發 （而非觸發）  
觸發條件只能在 PostgreSQL 的檢視上建立。它們用於在無法更新的檢視上執行 DML 陳述式。傳輸資料表一律會在內部遮罩，而非觸發 (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;
```

控制觸發行為的資料庫/使用者層級 GuCs   
兩個組態參數控制具有適用遮罩政策之使用者的觸發執行行為。當需要額外的安全限制時，請使用這些參數來防止觸發程序在遮罩的資料表或檢視上執行。預設會停用這兩個參數，允許觸發程序正常執行。  
**第一個 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 資料移動案例
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement"></a>

`pg_columnmask` 根據操作是發生在儲存體、邏輯或應用程式層，行為會因不同的資料移動操作而有所不同。儲存層級操作 （例如複製） 的行為與邏輯操作 （例如 `pg_dump`) 和應用程式層級操作 （例如 FDW 查詢） 不同。本節說明複寫、備份、匯出和遷移等常見案例的遮罩行為，並說明每個案例的安全影響。

**Topics**
+ [Aurora 全域資料庫和僅供讀取複本](#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)
+ [AWS 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 全域資料庫和僅供讀取複本
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.RR"></a>

Aurora `pg_columnmask`政策存放在叢集磁碟區的資料庫系統資料表中。所有複本都會存取相同的政策，並傳回一致的遮罩結果。對於 Aurora Global Database 部署，`pg_columnmask`政策 AWS 區域 會與其他資料庫系統資料表一起複寫到次要 ，以確保跨區域的一致資料保護。在容錯移轉案例期間，所有`pg_columnmask`政策都保持不變且正常運作。

## 資料庫複製和快照還原
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Clones"></a>

Aurora Fast Clone 和快照還原操作會保留所有`pg_columnmask`政策、角色和組態，做為資料庫系統資料表的一部分。複製或還原的資料庫會從來源叢集繼承所有現有的政策。複製或還原之後，每個資料庫叢集都會維護獨立的`pg_columnmask`政策。

## 邏輯複寫
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.LogRep"></a>

在初始同步期間，邏輯複寫會使用標準 SQL COPY 操作，並根據複寫使用者的許可強制執行`pg_columnmask`政策。在進行中的 CDC （變更資料擷取） 期間，不會套用遮罩政策，並透過 WAL 記錄複寫未遮罩的資料。具有 `pg_create_subscription`權限的使用者可以透過將複寫設定到他們控制的系統，潛在地洩漏未遮罩的資料。

## 藍/綠部署
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.BlueGreen"></a>

在快照還原期間，會自動包含`pg_columnmask`政策。綠色環境從藍色環境中所有政策的相同副本開始。從藍色複寫到綠色期間，不會遮罩資料。藍色叢集上的後續遮罩政策變更 (DDL 命令） 不會複寫到綠色叢集，並使 RDS 藍/綠部署失效。

## 零 ETL 和 CDC 串流
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.ZETL"></a>

資料複寫不受`pg_columnmask`政策影響。零 ETL 支援 DDL 複寫，但不複寫 `pg_columnmask`或 RLS 政策。不會將遮罩政策套用至 Zero-ETL 中的複寫資料。

## AWS Database Migration Service
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DMS"></a>

初始資料同步會根據為 DMS 任務選取的使用者進行遮罩或取消遮罩。CDC 資料一律會取消遮罩。雖然`pg_columnmask`相關的內部 RLS 政策可能會遷移，但它們無法在啟用非 pg\$1columnmask 的目標上運作。

## 資料匯出
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DataExport"></a>

`pg_columnmask` 將匯出視為任何其他查詢操作，會根據執行使用者的許可套用遮罩。這適用於 SQL 命令，例如 COPY、SELECT INTO、CREATE TABLE AS 和 Aurora PostgreSQL 的 S3 匯出功能。

**注意**  
當遮罩使用者匯出資料時，產生的檔案包含遮罩值，這些值可能會在還原時違反資料庫限制。

## 檢視和具體化檢視
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Views"></a>

使用檢視時，請記住下列考量事項：
+ **一般檢視** – `INVOKER` 一律使用語意。無論誰建立檢視，目前使用者的遮罩政策都會在查詢檢視時套用。
+ **具體化視**觀表 – 重新整理時，套用具體化視觀表擁有者的遮罩政策，而非執行重新整理的使用者政策。如果擁有者有遮罩政策，具體化視觀表一律包含遮罩資料。

## 資料傾印和還原
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DDR"></a>

`pg_dump` 以一般資料庫使用者身分運作，並根據連線使用者的許可套用遮罩政策。如果遮罩的使用者執行傾印，備份檔案會包含遮罩的資料。 `pg_columnmask` 政策會包含在傾印中，做為資料庫結構描述的一部分。成功還原需要目標資料庫中存在所有參考的角色，且目標已安裝`pg_columnmask`延伸模組。

**注意**  
從 PostgreSQL 18 開始， `pg_dump`支援從資料庫傾印中排除資料列層級安全性 (RLS) 和`pg_columnmask`遮罩政策`—no-policies`的選項。如需詳細資訊，請參閱 [pg\$1dump](https://www.postgresql.org/docs/current/app-pgdump.html)。

## 外部資料包裝函式
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.FDQ"></a>

使用外部資料包裝函式時，遠端資料表上的遮罩政策會根據映射的使用者在來源伺服器上的許可套用，而不是本機查詢使用者的許可，而且雖然您可以透過 FDW 存取遮罩的遠端資料，但您無法直接在本機資料庫中的外部資料表上建立 DDM 或 RLS 政策。