

# Usar o mascaramento dinâmico do Aurora PostgreSQL
<a name="AuroraPostgreSQL.Security.DynamicMasking"></a>

O mascaramento dinâmico de dados é um recurso de segurança que protege dados sensíveis em bancos de dados do Aurora PostgreSQL, controlando como os dados aparecem para os usuários no momento da consulta. O Aurora o implementa por meio da extensão `pg_columnmask`. `pg_columnmask` oferece proteção de dados em nível de coluna que complementa a segurança por linha nativa e os mecanismos granulares de controle de acesso do PostgreSQL.

Com `pg_columnmask`, você cria políticas de mascaramento que determinam a visibilidade dos dados com base em perfis de usuários. Quando os usuários consultam tabelas com políticas de mascaramento, o Aurora PostgreSQL aplica a função de mascaramento apropriada no momento da consulta, com base no perfil do usuário e no peso da política. Os dados subjacentes permanecem inalterados no armazenamento.

`pg_columnmask` comporta os seguintes recursos:
+ **Funções de mascaramento integradas e personalizadas**: use funções pré-integradas para padrões comuns, como mascaramento de e-mail e texto, ou crie suas próprias funções personalizadas para proteger dados sensíveis (PII) por meio de políticas de mascaramento baseadas em SQL.
+ **Várias estratégias de mascaramento**: oculte completamente as informações, substitua valores parciais por curingas ou defina abordagens de mascaramento personalizadas.
+ **Priorização de políticas**: defina várias políticas para uma única coluna. Use pesos para determinar qual política de mascaramento deve ser usada quando várias políticas se aplicam a uma coluna. O Aurora PostgreSQL aplica políticas com base no peso e na associação do perfil do usuário. 

`pg_columnmask` está disponível no Aurora PostgreSQL versão 16.10 e posterior e na versão 17.6 e posterior. As informações estão disponíveis sem custo adicional.

# Conceitos básicos de mascaramento dinâmico
<a name="AuroraPostgreSQL.Security.DynamicMasking.GetStarted"></a>

Para mascarar dados dinamicamente, você deve instalar a extensão `pg_columnmask` em seu banco de dados e cria políticas de mascaramento para suas tabelas. O processo de configuração envolve verificação de pré-requisitos, instalação de extensões, configuração de perfis, criação de políticas e testes de validação.

## Instalação e configuração da extensão
<a name="AuroraPostgreSQL.Security.DynamicMasking.GetStarted.Installation"></a>

Conecte-se ao seu cluster do Aurora PostgreSQL usando o Editor de consultas do console do RDS ou um cliente do PostgreSQL, como psql, com credenciais rds\$1superuser (usuário mestre).

Execute o comando de criação da extensão para habilitar a funcionalidade `pg_columnmask`:

```
CREATE EXTENSION pg_columnmask;
```

Esse comando instala a extensão `pg_columnmask`, cria as tabelas de catálogo necessárias e registra as funções de mascaramento integradas. A instalação da extensão é específica do banco de dados, o que significa que você deve instalá-la separadamente em cada banco de dados em que a funcionalidade é necessária.

**nota**  
As conexões estabelecidas antes da instalação dessa extensão ainda mostrarão dados não mascarados. Feche e restabeleça a conexão para corrigir isso.

Verifique a instalação da extensão conferindo as funções de mascaramento disponíveis:

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

# Procedimentos para gerenciar políticas de mascaramento de dados
<a name="AuroraPostgreSQL.Security.DynamicMasking.Procedures"></a>

Você pode gerenciar políticas de mascaramento usando os procedimentos fornecidos pela extensão `pg_columnmask`. Para criar, modificar ou eliminar políticas de mascaramento, é necessário ter um dos seguintes privilégios:
+ Proprietário da tabela na qual você está criando a política `pg_columnmask`.
+ Membro do `rds_superuser`.
+ Membro do perfil de gerente de políticas `pg_columnmask` definido pelo parâmetro `pgcolumnmask.policy_admin_rolname`.

O seguinte comando cria uma tabela que é usada nas seções seguintes:

```
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>

O seguinte procedimento cria uma política de mascaramento para uma tabela de usuários:

**Sintaxe**

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

**Argumentos**


| Parâmetro | DataType | Descrição | 
| --- | --- | --- | 
| policy\$1name | NAME |  O nome da política de mascaramento. Deve ser exclusivo por tabela.  | 
| table\$1name | REGCLASS |  O nome ou oid qualificado/não qualificado da tabela à qual aplicar a política de mascaramento.  | 
| masking\$1expressions | JSONB |  Objeto JSON que contém o nome da coluna e pares de funções de mascaramento. Cada chave é um nome de coluna e seu valor é a expressão de mascaramento a ser aplicada nessa coluna.  | 
| roles | NAME[] |  Os perfis aos quais essa política de mascaramento se aplica. O padrão é PUBLIC.  | 
| weight | INT |  Peso da política de mascaramento. Quando várias políticas são aplicáveis à consulta de determinado usuário, a política com o maior peso (maior número inteiro) será aplicada a cada coluna mascarada. O padrão é 0. Não há duas políticas de mascaramento na tabela com o mesmo peso.  | 

**Tipo de retorno**

Nenhum

**Example da criação de uma política de mascaramento que mascare a coluna de e-mail do perfil `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>

Esse procedimento modifica uma política de mascaramento existente. `ALTER_MASKING_POLICY` pode modificar as expressões de mascaramento da política, o conjunto de perfis aos quais a política se aplica e o peso da política de mascaramento. Quando um desses parâmetros é omitido, a parte correspondente da política permanece inalterada.

**Sintaxe**

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

**Argumentos**


| Parâmetro | DataType | Descrição | 
| --- | --- | --- | 
| policy\$1name | NAME |  Nome da política de mascaramento existente.  | 
| table\$1name | REGCLASS |  O oid do nome qualificado/não qualificado da tabela que contém a política de mascaramento.  | 
| masking\$1expressions | JSONB |  Novo objeto JSON que contém pares de função de mascaramento e nome da coluna ou, caso contrário, NULL.  | 
| roles | NAME[] |  A lista de novos perfis aos quais essa política de mascaramento se aplica ou, caso contrário, NULL.  | 
| weight | INT |  Novo peso para a política de mascaramento ou, caso contrário, NULL.  | 

**Tipo de retorno**

Nenhum

**Example de adicionar o perfil de analista a uma política de mascaramento existente sem alterar outros atributos da política.**  

```
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>

Esse procedimento remove uma política de mascaramento existente.

**Sintaxe**

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

**Argumentos**


| Parâmetro | DataType | Descrição | 
| --- | --- | --- | 
| policy\$1name | NAME |  Nome da política de mascaramento existente.  | 
| table\$1name | REGCLASS |  O oid do nome qualificado/não qualificado da tabela que contém a política de mascaramento.  | 

**Tipo de retorno**

Nenhum

**Example de descartar a política de mascaramento customer\$1mask**  

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

# Inserir caractere de escape em identificadores no procedimento DDL da política de mascaramento
<a name="AuroraPostgreSQL.Security.DynamicMasking.EscapeIdentifiers"></a>

Quando você cria políticas de mascaramento de dados com identificadores entre aspas, o escape adequado é necessário para garantir referências de objetos e aplicação de políticas corretas. Para usar identificadores entre aspas nos procedimentos de gerenciamento da política de mascaramento `pg_columnmask`:
+ **Nome da política**: deve ser entre aspas duplas.
+ **Nome da tabela**: tanto o nome do esquema quanto o nome da tabela devem ser colocados entre aspas duplas individualmente quando necessário.
+ **Expressões de mascaramento**: os nomes de colunas e funções nas expressões de mascaramento devem estar entre aspas duplas e as próprias aspas devem ser excluídas usando uma barra invertida.
+ **Funções**: a matriz de nomes de perfil é automaticamente citada. O nome do perfil deve corresponder exatamente ao nome, conforme visto em `pg_roles` incluindo a distinção entre maiúsculas e minúsculas.

**Example da sintaxe de escape e aspas**  
Este exemplo mostra a sintaxe adequada de escape e aspas na criação de políticas de mascaramento para tabelas, colunas, funções e perfis que usam nomes em maiúsculas e minúsculas ou exigem identificadores entre aspas no 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
```

## Visualizações administrativas
<a name="AuroraPostgreSQL.Security.DynamicMasking.AdminViews"></a>

Você pode revisar toda a política `pg_columnmask` usando a visualização administrativa `pgcolumnmask.pg_columnmask_policies` acessível ao público. As informações a seguir estão disponíveis usando essa visualização. A visualização exibe somente as políticas de mascaramento pertencentes ao usuário atual.


| Nome da coluna | Tipo de dados | Descrição | 
| --- | --- | --- | 
|  schemaname  | NAME |  Esquema da relação à qual a política está anexada  | 
|  tablename  | NAME |  Nome da relação à qual a política está anexada  | 
|  policyname  | NAME |  Nome da política de mascaramento, todas as políticas de mascaramento têm nomes exclusivos  | 
|  perfis  | TEXT[] |  Perfil ao qual a política se aplica.  | 
|  masked\$1columns  | TEXT[] |  Colunas mascaradas  | 
|  masking\$1functions  | TEXT[] |  Funções de mascaramento  | 
| weight | INT |  Peso da política anexada  | 

# Funções de mascaramento de dados predefinidas
<a name="AuroraPostgreSQL.Security.DynamicMasking.PredefinedMaskingFunctions"></a>

A extensão `pg_columnmask` fornece funções utilitárias integradas escritas em linguagem C (para execução mais rápida) que podem ser usadas como expressão de mascaramento para políticas `pg_columnmask`.

**mask\$1text**

Uma função para mascarar dados de texto com opções de visibilidade configuráveis.

**Argumentos**


| Parâmetro | DataType | Descrição | 
| --- | --- | --- | 
| input | TEXT |  A string de texto original a ser mascarada.  | 
| mask\$1char | CHAR(1) |  Caractere usado para mascarar (padrão: “X”).  | 
| visible\$1prefix | INT |  Número de caracteres no início do texto de entrada que permanecerão sem máscara (padrão: 0).  | 
| visible\$1suffix | INT |  Número de caracteres no fim do texto de entrada que permanecerão sem máscara (padrão: 0).  | 
| use\$1hash\$1mask | BOOLEAN |  Se TRUE, usa um mascaramento baseado em hash em vez de mask\$1char (padrão: FALSE).  | 

**Example de usar diferentes opções de mascaramento**  
Mascare toda a string de entrada com o caractere “X” padrão.  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World');
  mask_text  
-------------
 XXXXXXXXXXX
```
Use o argumento `mask_char` para mascarar a entrada de texto usando um caractere diferente.  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World', '*');
  mask_text  
-------------
 ***********
```
Use os parâmetros `visible_prefix` e `visible_suffix` para controlar quantos caracteres permanecem sem máscara no início e no final do texto.  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World', '*', 5, 1);
  mask_text  
-------------
 Hello*****d
```
Quando `use_hash_mask` é true, a string de entrada é mascarada usando caracteres aleatórios, o argumento `mask_char` é ignorado, mas `visible_prefix` ainda `visible_suffix` ainda são respeitados  

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

**mask\$1timestamp**


| Parâmetro | DataType | Descrição | 
| --- | --- | --- | 
| ts\$1to\$1mask | TIMESTAMP |  O carimbo de data/hora original a ser mascarado.  | 
| mask\$1part | TEXT |  Especifica qual parte do carimbo de data/hora deve ser mascarada (padrão: “tudo”). Valores válidos: “ano”, “mês”, “dia”, “hora”, “minuto”, “segundo”, “tudo”.  | 
| mask\$1value | TIMESTAMP |  O valor do carimbo de data/hora a ser usado para mascaramento (padrão: “1900-01-01 00:00:00”).  | 

**Example de usar `mask_timestamps`**  
Esses exemplos demonstram o mascaramento completo do carimbo de data/hora em um valor padrão, o mascaramento parcial de componentes específicos do carimbo de data/hora (somente ano) e o mascaramento com um valor de substituição personalizado.  
Mascarar completamente o valor de entrada para o carimbo de data/hora padrão  

```
postgres=> SELECT pgcolumnmask.mask_timestamp('2023-06-15 14:30:00');
   mask_timestamp    
---------------------
 1900-01-01 00:00:00
```
Como mascarar apenas uma parte do carimbo de data/hora, por exemplo, apenas o ano  

```
postgres=> SELECT pgcolumnmask.mask_timestamp('2023-06-15 14:30:00', 'year');
   mask_timestamp    
---------------------
 1900-06-15 14:30:00
```
Para alterar o valor mascarado do carimbo de data/hora, use o argumento `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**

Uma função para mascarar endereços de e-mail, mas preservar a estrutura.


| Parâmetro | DataType | Descrição | 
| --- | --- | --- | 
| input | TEXT |  O endereço de e-mail original a ser mascarado.  | 
| mask\$1char | CHAR(1) |  Caractere usado para mascarar (padrão: “X”).  | 
| mask\$1local | BOOLEAN |  Se TRUE, mascara a parte local do e-mail (antes de @) (padrão: TRUE).  | 
| mask\$1domain | BOOLEAN |  Se TRUE, mascara a parte do domínio do e-mail (após @) (padrão: TRUE).  | 

**Example de usar `mask_email`**  
Esses exemplos demonstram o mascaramento de e-mail completo, os caracteres de máscara personalizados e o mascaramento seletivo da parte local ou da parte do domínio do endereço de e-mail.  
Mascaramento completo  

```
postgres=> SELECT pgcolumnmask.mask_email('user@example.com');
    mask_email    
------------------
 XXXX@XXXXXXX.com
```
Usar `mask_char` para alterar o caractere usado para mascarar  

```
postgres=> SELECT pgcolumnmask.mask_email('user@example.com', '*');
    mask_email    
------------------
 ****@*******.com
```
Usar `mask_local` e `mask_domain` para controlar o mascaramento no local e no domínio  

```
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
```

# Implementar pg\$1columnmask em um fluxo de trabalho completo
<a name="AuroraPostgreSQL.Security.DynamicMasking.WorkflowExample"></a>

Esta seção demonstra uma implementação completa do `pg_columnmask` utilizando um exemplo de tabela de funcionários com dados sensíveis. Você vai aprender a criar funções de mascaramento personalizadas, definir várias políticas de mascaramento com diferentes níveis de peso para vários perfis (estagiário, suporte, analista) e observará como usuários com uma ou várias associações de perfil veem diferentes níveis de dados mascarados. Os exemplos também abrangem o comportamento de mascaramento em instruções do DML com cláusulas RETURNING, gatilhos em tabelas em comparação a visualizações e operações de gerenciamento de políticas, incluindo renomeação, alteração de pesos e limpeza.

1. Crie uma tabela de exemplo com alguns dados sensíveis:

   ```
   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. Crie funções de mascaramento personalizadas:

   ```
   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. Crie várias políticas com diferentes níveis de mascaramento com base em perfis de usuários:

   ```
   -- 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. Os exemplos a seguir demonstram como diferentes usuários veem os dados com base em sua associação de perfil e nos pesos das políticas.

   ```
   -- 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;
   ```

   Como estagiário (mascaramento mais rigoroso):

   ```
   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
   ```

   Como usuário de suporte (mascaramento moderado):

   ```
   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
   ```

   Como analista (mascaramento mais leve):

   ```
   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
   ```

   Como o usuário ethan\$1support\$1intern, que é estagiário e usuário de suporte:

   ```
   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
   ```

   Como john\$1analyst\$1intern, que é, ao mesmo tempo, estagiário e analista:

   ```
   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
   ```

# Noções básicas sobre o comportamento de mascaramento em operações de DML
<a name="AuroraPostgreSQL.Security.DynamicMasking.DMLMasking"></a>

`pg_columnmask` é aplicado de modo consistente a todas as operações do DML, incluindo INSERT, UPDATE, DELETE e MERGE. Quando você executa essas operações, o Aurora PostgreSQL mascara os dados de acordo com um princípio fundamental: qualquer dado lido do armazenamento é mascarado de acordo com as políticas aplicáveis do usuário atual.

O mascaramento afeta alguns dos seguintes componentes de consulta, como:
+ Cláusulas WHERE
+ Condições JOIN
+ Subconsultas
+ Cláusulas RETURNING

Todos esses componentes operam com valores mascarados, não com os dados originais. Enquanto os dados são gravados no armazenamento sem máscara, os usuários só acessam a visualização mascarada ao lê-la novamente.

O Aurora PostgreSQL impõe todas as restrições do banco de dados (NOT NULL, UNIQUE, CHECK, FOREIGN KEY) nos valores reais armazenados, não nos valores mascarados. Ocasionalmente, isso poderá criar inconsistências aparentes se as funções de mascaramento não forem cuidadosamente projetadas.

O mascaramento funciona com permissões em nível de coluna:
+ Usuários sem privilégios SELECT não podem ler colunas.
+ Usuários com privilégios SELECT veem valores mascarados de acordo com as políticas aplicáveis.

# Noções básicas sobre o comportamento de mascaramento nas funções de gatilho
<a name="AuroraPostgreSQL.Security.DynamicMasking.TriggerFunctionMasking"></a>

Quando políticas `pg_columnmask` são aplicadas às tabelas, é importante entender como o mascaramento interage com as funções de gatilho. Gatilhos são funções de banco de dados que são executadas automaticamente em resposta a determinados eventos em uma tabela, como as operações INSERT, UPDATE ou DELETE.

Por padrão, o DDM aplica regras de mascaramento diferentes, dependendo do tipo de gatilho:

Gatilhos de tabela  
**Tabelas de transição não são mascaradas**: as funções de gatilho nas tabelas têm acesso aos dados não mascarados em suas tabelas de transição para versões de linha antigas e novas.  
Os proprietários de tabelas criam gatilhos e são proprietários dos dados, para que tenham acesso total para gerenciar suas tabelas de forma eficaz

Visualizar gatilhos (gatilhos INSTEAD OF)  
**As tabelas de transição são mascaradas**: as funções de gatilho nas visualizações veem os dados mascarados de acordo com as permissões do usuário atual.  
Os proprietários das visualizações podem ser diferentes dos proprietários da tabela base e devem respeitar as políticas de mascaramento nas tabelas subjacentes.

Dois parâmetros de configuração em nível de servidor controlam o comportamento do gatilho com tabelas mascaradas. Eles só podem ser definidos por `rds_superuser`:
+ **Restringir gatilhos em tabelas mascaradas**: impede a execução de gatilhos quando um usuário mascarado executa operações do DML em tabelas com políticas de mascaramento aplicáveis.
+ **Restringir gatilhos em visualizações com tabelas mascaradas**: impede a execução de gatilhos em visualizações quando a definição da visualização inclui tabelas com políticas de mascaramento aplicáveis ao usuário atual.

**Example das diferenças entre a aplicação da função à tabela e à visualização**  
O exemplo a seguir cria uma função de gatilho que imprime valores de linha antigos e novos e, depois, demonstra como a mesma função se comporta de forma diferente quando anexada a uma tabela em comparação a uma visualização.  

```
-- 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;
```
Recomendamos revisar o comportamento dos gatilhos antes de implementá-los em tabelas mascaradas. Os gatilhos de tabela têm acesso a dados não mascarados em tabelas em transição, enquanto os gatilhos de visualização veem dados mascarados.

**Example de renomear uma política de mascaramento**  
O exemplo a seguir demonstra como renomear políticas existentes usando o procedimento `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 de alterar o peso da política**  
O exemplo a seguir demonstra como alterar o peso da política para alterar seu peso.  

```
-- 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 de como limpar**  
O exemplo a seguir demonstra como eliminar todas as políticas, tabelas e usuários.  

```
-- 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;
```

# Configurar o perfil de gerenciamento de políticas de mascaramento
<a name="AuroraPostgreSQL.Security.DynamicMasking.PolicyManagementRole"></a>

A extensão de mascaramento de colunas do PostgreSQL, `pg_columnmask`, permite delegar o gerenciamento de políticas de mascaramento a um perfil específico, em vez de exigir privilégios de proprietário da tabela ou `rds_superuser`. Isso fornece um controle mais granular sobre quem pode criar, alterar e eliminar políticas de mascaramento.

Para configurar o perfil que terá privilégios de gerenciamento de políticas de mascaramento, siga estas etapas:

1. Crie o perfil de administrador de políticas: como `rds_superuser`, crie um perfil responsável pelo gerenciamento de políticas de mascaramento:

   ```
   CREATE ROLE mask_admin NOLOGIN;
   ```

1. Configure o parâmetro PostgreSQL: em seu grupo de parâmetros de cluster de banco de dados personalizado, defina o parâmetro de configuração do mecanismo `pgcolumnmask.policy_admin_rolname` com o nome do perfil que você criou:

   ```
   pgcolumnmask.policy_admin_rolname = mask_admin
   ```

   Esses parâmetros de configuração do mecanismo podem ser definidos em um grupo de parâmetros do cluster de banco de dados e não exigem a reinicialização da instância. Para acessar detalhes sobre a atualização de parâmetros, consulte [Modificar parâmetros em um grupo de parâmetros de cluster de banco de dadosno Amazon Aurora](USER_WorkingWithParamGroups.ModifyingCluster.md).

1. Conceda o perfil aos usuários como `rds_superuser`, conceda o perfil `mask_admin` aos usuários que devem ser capazes de gerenciar políticas de mascaramento:

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

   Além disso, garanta que os usuários tenham o privilégio USAGE nos esquemas em que gerenciarão as políticas de mascaramento:

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

Agora, quando os usuários `alice` e `bob` se conectam ao banco de dados, eles podem usar as funções de extensão `pg_columnmask` padrão para criar, alterar e eliminar políticas de mascaramento em todas as tabelas, em todos os esquemas em que eles têm privilégios `USAGE` no esquema.

# Práticas recomendadas para a implementação segura de pg\$1columnmask
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices"></a>

A seção a seguir fornece as práticas recomendadas de segurança para implementação de `pg_columnmask` em seu ambiente Aurora PostgreSQL. Siga estas recomendações para:
+ Estabelecer uma arquitetura de controle de acesso com base em perfil seguro
+ Desenvolver funções de mascaramento que evitem vulnerabilidades de segurança
+ Entender e controlar o comportamento do gatilho com dados mascarados

## Arquitetura de segurança baseada em perfis
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices.architecture"></a>

Defina uma hierarquia de perfis para implementar controles de acesso em seu banco de dados. O Aurora PostgreSQL `pg_columnmask` aumenta esses controles fornecendo uma camada adicional para mascaramento de dados refinado dentro desses perfis.

Criar perfis dedicados que se alinhem às funções organizacionais em vez de conceder permissões a usuários individuais. Essa abordagem fornece melhor capacidade de auditoria e simplifica o gerenciamento de permissões à medida que sua estrutura organizacional evolui.

**Example de criar uma hierarquia de perfis organizacionais**  
O exemplo a seguir cria uma hierarquia de perfis organizacionais com perfis dedicados para diferentes funções e, depois, atribui usuários individuais aos perfis apropriados. Neste exemplo, os perfis organizacionais (analyst\$1role, support\$1role) são criados primeiro e, depois, os usuários individuais recebem a associação a esses perfis. Essa estrutura permite que você gerencie as permissões em nível de perfil, e não de cada usuário individual.  

```
-- 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;
```
Implemente o princípio de privilégio mínimo concedendo apenas o mínimo de permissões necessárias para cada perfil. Evite conceder permissões amplas que poderiam ser exploradas se as credenciais fossem comprometidas.  

```
-- 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;
```
Os administradores de políticas exigem privilégios `USAGE` nos esquemas em que gerenciam políticas de mascaramento. Conceda esses privilégios de modo seletivo, seguindo o princípio do privilégio mínimo. Realize análises regulares das permissões de acesso ao esquema para garantir que somente o pessoal autorizado mantenha os recursos de gerenciamento de políticas.  
A configuração dos parâmetros do perfil de administrador da política é restrita somente aos administradores do banco de dados. Esse parâmetro não pode ser modificado em nível de banco de dados ou de sessão, impedindo que usuários sem privilégios substituam as atribuições do administrador da política. Essa restrição garante que o controle da política de mascaramento permaneça centralizado e seguro.  
Atribua o perfil de administrador de políticas a indivíduos específicos em vez de grupos. Essa abordagem direcionada garante acesso seletivo ao gerenciamento de políticas de mascaramento, pois os administradores de políticas têm a capacidade de mascarar todas as tabelas no banco de dados. 

## Desenvolvimento seguro da função de mascaramento
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices.MaskingDevelopment"></a>

Desenvolva funções de mascaramento usando semântica de vinculação antecipada para garantir o monitoramento adequado de dependências e evitar vulnerabilidades de vinculação tardia, como modificação do caminho de pesquisa durante o runtime. É recomendável usar a sintaxe `BEGIN ATOMIC` para funções SQL a fim de permitir a validação em tempo de compilação (ou seja, vinculação antecipada) e o gerenciamento de dependências.

```
-- 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;
```

Como alternativa, crie funções imunes às alterações do caminho de pesquisa qualificando explicitamente o esquema de todas as referências de objetos, garantindo um comportamento consistente em diferentes sessões de usuário.

```
-- 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;
    $$;
```

Implemente a validação de entrada nas funções de mascaramento para lidar com casos de borda e evitar comportamentos inesperados. Sempre inclua o tratamento de NULL e valide os formatos de entrada para garantir um comportamento consistente de mascaramento. 

```
-- 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;
```

## Comportamento dos gatilhos do DML com pg\$1columnmask
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices.DMLTriggerBehavior"></a>

Para gatilhos de tabela, as tabelas de transição serão totalmente sem máscara. Para gatilhos de visualização (IOT), as tabelas de transição serão mascaradas de acordo com as permissões de visualização do usuário atual.

Gatilhos de tabela com pg\$1columnmask  
Os gatilhos recebem uma tabela de transição que contém a versão antiga e a nova das linhas modificadas pela consulta do DML de acionamento. Dependendo de quando o gatilho é acionado, o Aurora PostgreSQL preenche as linhas antiga e nova. Por exemplo, um gatilho `BEFORE INSERT` só tem novas versões das linhas e versões antigas vazias porque não há uma versão antiga para consultar.  
O `pg_columnmask` não mascara as tabelas de transição dentro dos gatilhos das tabelas. Os gatilhos podem usar colunas mascaradas dentro de seu corpo e ver dados não mascarados. O criador do gatilho deve se certificar de como ele é executado por um usuário. O exemplo a seguir funciona corretamente nesse caso.  

```
-- 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;
```
O criador do gatilho vazará dados não mascarados para o usuário se ele não tiver cuidado com as declarações que usa no corpo do gatilho. Por exemplo, usar `RAISE NOTICE ‘%’, masked_column;` imprime a coluna para o usuário atual.  

```
-- 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
```

Gatilhos em visualizações com pg\$1columnmask (em vez de gatilhos)  
Os gatilhos só podem ser criados em visualizações no PostgreSQL. Eles são usados para executar instruções do DML em visualizações que não são atualizáveis. As tabelas de trânsito são sempre mascaradas internamente em vez de no gatilho (IOT), porque a visualização e as tabelas base usadas na consulta de visualização podem ter proprietários diferentes. Nesse caso, as tabelas base podem ter algumas políticas de mascaramento aplicáveis ao proprietário da visualização e este deve sempre ver os dados mascarados das tabelas base dentro dos respectivos gatilhos. Isso é diferente dos gatilhos nas tabelas porque, nesse caso, o criador do gatilho e os dados dentro das tabelas pertencem ao mesmo usuário, o que não é o caso aqui.  

```
-- 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 em nível de banco de dados/usuário para controlar o comportamento dos gatilhos  
Dois parâmetros de configuração controlam o comportamento de execução do gatilho para usuários com políticas de mascaramento aplicáveis. Use esses parâmetros para evitar que os gatilhos sejam executados em tabelas ou visualizações mascaradas quando restrições de segurança adicionais forem necessárias. Ambos os parâmetros são desabilitados por padrão, permitindo que os gatilhos sejam executados normalmente.  
**Primeiro GUC: restrição de disparo de gatilho em tabelas mascaradas**.  
Especificações:  
+ Nome: : `pgcolumnmask.restrict_dml_triggers_for_masked_users`
+ Tipo:: `boolean`
+ Padrão: `false` (os gatilhos podem ser executados).
Impede a execução do gatilho em tabelas mascaradas para usuários mascarados quando definido como TRUE. `pg_columnmask` é executado ocasionando um erro.  
**Segundo GUC: restrição de acionamento do gatilho em visualizações com tabelas mascaradas**.  
Especificações:  
+ Nome: : `pgcolumnmask.restrict_iot_triggers_for_masked_users`
+ Tipo:: `boolean`
+ Padrão: `false` (os gatilhos podem ser executados).
Impede a execução de gatilhos em visualizações que incluem tabelas mascaradas em sua definição para usuários mascarados quando definidas como TRUE.

Esses parâmetros funcionam de forma independente e são configuráveis, como os parâmetros padrão de configuração do banco de dados.

# Cenários de movimentação de dados do pg\$1columnmask do Aurora PostgreSQL
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement"></a>

O comportamento de `pg_columnmask` varia nas diferentes operações de movimentação de dados, dependendo se a operação ocorre na camada de armazenamento, lógica ou de aplicação. As operações em nível de armazenamento (como clonagem) se comportam de maneira diferente das operações lógicas (como `pg_dump`) e das operações em nível de aplicação (como consultas FDW). Esta seção descreve o comportamento de mascaramento em cenários comuns, incluindo replicação, backups, exportações e migrações, e explica as implicações de segurança de cada um.

**Topics**
+ [Aurora Global Database e réplicas de leitura](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.RR)
+ [Clone de banco de dados e restauração de snapshot](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Clones)
+ [Replicação lógica](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.LogRep)
+ [Implantações azuis/verdes](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.BlueGreen)
+ [Fluxos ETL zero e CDC](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.ZETL)
+ [AWS Database Migration Service](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DMS)
+ [Exportações de dados](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DataExport)
+ [Visualizações e visões materializadas](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Views)
+ [Despejo e restauração de dados](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DDR)
+ [Wrapper de dados externos](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.FDQ)

## Aurora Global Database e réplicas de leitura
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.RR"></a>

As políticas `pg_columnmask` do Aurora são armazenadas em tabelas do sistema de banco de dados dentro do volume do cluster. Todas as réplicas acessam as mesmas políticas e exibem resultados consistentemente mascarados. Para implantações do Aurora Global Database, as políticas `pg_columnmask` são replicadas para as Regiões da AWS secundárias junto com outras tabelas do sistema de banco de dados, garantindo proteção de dados consistente em todas as regiões. Durante os cenários de failover, todas as políticas `pg_columnmask` permanecem intactas e funcionais.

## Clone de banco de dados e restauração de snapshot
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Clones"></a>

As operações de clone rápido e restauração de snapshot do Aurora preservam todas as políticas `pg_columnmask`, perfis e configurações como parte das tabelas do sistema de banco de dados. O banco de dados clonado ou restaurado herda todas as políticas existentes do cluster de origem. Após a clonagem ou a restauração, cada cluster de banco de dados mantém políticas `pg_columnmask` independentes.

## Replicação lógica
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.LogRep"></a>

Durante a sincronização inicial, a replicação lógica usa operações SQL COPY padrão e as políticas `pg_columnmask` são aplicadas com base nas permissões do usuário de replicação. Durante o CDC (captura de dados de alteração) em andamento, as políticas de mascaramento não são aplicadas e os dados não mascarados são replicados por meio de registros do WAL. Usuários com privilégios `pg_create_subscription` podem exfiltrar dados não mascarados configurando a replicação em um sistema que eles controlem.

## Implantações azuis/verdes
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.BlueGreen"></a>

Durante a restauração do snapshot, as políticas `pg_columnmask` são incluídas automaticamente. O ambiente verde começa com uma cópia idêntica de todas as políticas do ambiente azul. Durante a replicação do azul para o verde, os dados não são mascarados. Alterações subsequentes da política de mascaramento (comandos DDL) no cluster azul não são replicadas para o cluster verde e invalidam as implantações azul/verde do RDS.

## Fluxos ETL zero e CDC
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.ZETL"></a>

A replicação de dados não é afetada pelas políticas `pg_columnmask`. O ETL zero comporta a replicação de DDL, mas não replica as políticas de RLS nem `pg_columnmask`. Nenhuma política de mascaramento é aplicada aos dados replicados em ETL zero.

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

A sincronização inicial de dados é mascarada ou não mascarada com base no usuário selecionado para a tarefa do DMS. Os dados do CDC nunca são mascarados. Embora as políticas de RLS internas relacionadas a `pg_columnmask` possam ser migradas, elas não funcionarão em destinos não habilitados para pg\$1columnmask.

## Exportações de dados
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DataExport"></a>

A `pg_columnmask` trata as exportações como qualquer outra operação de consulta: o mascaramento é aplicado com base nas permissões do usuário em execução. Isso se aplica a comandos SQL, como COPY, SELECT INTO, CREATE TABLE AS e à funcionalidade de exportação do S3 do Aurora PostgreSQL. 

**nota**  
Quando usuários mascarados exportam dados, os arquivos resultantes contêm valores mascarados que podem violar as restrições do banco de dados quando restaurados.

## Visualizações e visões materializadas
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Views"></a>

Lembre-se das seguintes considerações ao utilizar visualizações:
+ **Visualizações regulares**: sempre usam a semântica `INVOKER`. As políticas de mascaramento do usuário atual se aplicam ao consultar a visualização, independentemente de quem a criou.
+ **Visões materializadas**: quando atualizadas, as políticas de mascaramento do proprietário da visão materializada se aplicam, não as políticas do usuário que realiza a atualização. Se o proprietário tiver políticas de mascaramento, a visão materializada sempre conterá dados mascarados.

## Despejo e restauração de dados
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DDR"></a>

`pg_dump` funciona como um usuário regular do banco de dados e aplica políticas de mascaramento com base nas permissões do usuário conectado. Se um usuário mascarado realizar um despejo, o arquivo de backup conterá dados mascarados. As politicas `pg_columnmask` são incluídas no despejo como parte do esquema do banco de dados. A restauração bem-sucedida exige que todos os perfis referidos existam no banco de dados de destino e que o destino tenha a extensão `pg_columnmask` instalada.

**nota**  
A partir do PostgreSQL 18, `pg_dump` comporta a opção `—no-policies` que exclui tanto a segurança por linha (RLS) e as políticas de mascaramento `pg_columnmask` dos despejos do banco de dados. Para acessar mais informações, consulte [pg\$1dump](https://www.postgresql.org/docs/current/app-pgdump.html).

## Wrapper de dados externos
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.FDQ"></a>

Ao usar wrappers de dados externos, as políticas de mascaramento em tabelas remotas são aplicadas com base nas permissões do usuário associado no servidor de origem, não nas permissões do usuário de consulta local. Embora você possa acessar dados remotos mascarados por meio do FDW, não pode criar políticas de DDM ou RLS diretamente em tabelas externas em seu banco de dados local.