

# Uso del enmascaramiento dinámico con Aurora PostgreSQL
<a name="AuroraPostgreSQL.Security.DynamicMasking"></a>

El enmascaramiento de datos dinámico es una característica de seguridad que protege la información confidencial de las bases de datos de Aurora PostgreSQL al controlar la forma en que los usuarios ven los datos en el momento de la consulta. Aurora lo implementa a través de la extensión `pg_columnmask`. `pg_columnmask` proporciona una protección de datos por columna que complementa los mecanismos nativos de control de acceso granular y de seguridad por fila de PostgreSQL.

Con `pg_columnmask`, puede crear políticas de enmascaramiento que determinan la visibilidad de los datos en función de los roles de los usuarios. Cuando los usuarios consultan tablas con políticas de enmascaramiento, Aurora PostgreSQL aplica la función de enmascaramiento adecuada en el momento de la consulta en función del rol del usuario y del peso de la política. Los datos subyacentes permanecen sin cambios en el almacenamiento.

`pg_columnmask` admite las siguientes capacidades:
+ **Funciones de enmascaramiento integradas y personalizadas**: utilice funciones prediseñadas para patrones comunes, como el enmascaramiento del correo electrónico y el texto, o cree sus propias funciones personalizadas para proteger la información confidencial (PII) mediante políticas de enmascaramiento basadas en SQL.
+ **Múltiples estrategias de enmascaramiento**: oculte completamente la información, sustituya valores parciales por caracteres comodín o defina enfoques de enmascaramiento personalizados.
+ **Priorización de políticas**: defina varias políticas para una sola columna. Use ponderaciones para determinar qué política de enmascaramiento debe usarse cuando se apliquen varias políticas a una columna. Aurora PostgreSQL aplica políticas según el peso y la pertenencia a los roles de usuario. 

`pg_columnmask` está disponible en la versión 16.10 y superior de Aurora PostgreSQL y en la versión 17.6 y superior. Está disponible sin costo adicional.

# Introducción al enmascaramiento dinámico
<a name="AuroraPostgreSQL.Security.DynamicMasking.GetStarted"></a>

Para enmascarar los datos de forma dinámica, debe instalar la extensión `pg_columnmask` en la base de datos y crear políticas de enmascaramiento para las tablas. El proceso de configuración implica la verificación de los requisitos previos, la instalación de la extensión, la configuración de los roles, la creación de políticas y las pruebas de validación.

## Instalación y configuración de la extensión
<a name="AuroraPostgreSQL.Security.DynamicMasking.GetStarted.Installation"></a>

Conéctese al clúster de Aurora PostgreSQL mediante el editor de consultas de la consola de RDS o un cliente de PostgreSQL, como psql, con las credenciales de rds\$1superuser (usuario maestro).

Ejecute el comando de creación de extensiones para habilitar la funcionalidad `pg_columnmask`:

```
CREATE EXTENSION pg_columnmask;
```

Este comando instala la extensión `pg_columnmask`, crea las tablas de catálogo necesarias y registra las funciones de enmascaramiento integradas. La instalación de la extensión es específica de la base de datos, lo que significa que debe instalarla de forma independiente en cada base de datos en la que se requiera la funcionalidad.

**nota**  
Las conexiones realizadas antes de instalar esta extensión seguirán mostrando los datos desenmascarados. Ciérrela y vuelva a conectarla para solucionar este problema.

Verifique la instalación de la extensión comprobando las funciones de enmascaramiento disponibles:

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

# Procedimientos para administrar políticas de enmascaramiento de datos
<a name="AuroraPostgreSQL.Security.DynamicMasking.Procedures"></a>

Puede administrar las políticas de enmascaramiento mediante los procedimientos proporcionados por la extensión `pg_columnmask`. Para crear, modificar o eliminar políticas de enmascaramiento, debe contar con uno de los siguientes privilegios:
+ Propietario de la tabla en la que va a crear la política de `pg_columnmask`.
+ Miembro de `rds_superuser`.
+ Miembro del rol de mánager de la política de `pg_columnmask` establecido por el parámetro `pgcolumnmask.policy_admin_rolname`.

El siguiente comando crea una tabla que se utiliza en las siguientes secciones:

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

El siguiente procedimiento crea una nueva política de enmascaramiento para una tabla de usuarios:

**Sintaxis**

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

**Argumentos**


| Parámetro | Tipo de datos | Descripción | 
| --- | --- | --- | 
| policy\$1name | NAME |  Nombre de la política de enmascaramiento. Debe ser único por tabla.  | 
| table\$1name | REGCLASS |  El nombre cualificado o no cualificado o nulo de la tabla para aplicar la política de enmascaramiento.  | 
| masking\$1expressions | JSONB |  Objeto JSON que contiene los pares de nombre de columna y función de enmascaramiento. Cada clave es el nombre de una columna y su valor es la expresión de enmascaramiento que se va a aplicar a esa columna.  | 
| roles | NAME[] |  Los roles a los que se aplica esta política de enmascaramiento. Predeterminado es PUBLIC.  | 
| weight | INT |  Peso de la política de enmascaramiento. Cuando se apliquen varias políticas a la consulta de un usuario determinado, la política con el peso más alto (número entero más alto) se aplicará a cada columna enmascarada. El valor predeterminado es 0. No hay dos políticas de enmascaramiento de la tabla que puedan tener el mismo peso.  | 

**Tipo de devolución**

Ninguno

**Example de crear una política de enmascaramiento que oculte la columna de correo electrónico del rol `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>

Este procedimiento modifica una política de enmascaramiento existente. `ALTER_MASKING_POLICY` puede modificar las expresiones de enmascaramiento de la política, el conjunto de roles a los que se aplica la política y el peso de la política de enmascaramiento. Cuando se omite uno de esos parámetros, la parte correspondiente de la política permanece inalterada.

**Sintaxis**

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

**Argumentos**


| Parámetro | Tipo de datos | Descripción | 
| --- | --- | --- | 
| policy\$1name | NAME |  El nombre existente de la política de enmascaramiento.  | 
| table\$1name | REGCLASS |  El nombre cualificado o no cualificado o nulo de la tabla que contiene la política de enmascaramiento.  | 
| masking\$1expressions | JSONB |  Nuevo objeto JSON que contiene los pares de nombre de columna y función de enmascaramiento, de lo contrario, NULL.  | 
| roles | NAME[] |  La lista de roles nuevos a los que se aplica esta política de enmascaramiento o NULL en caso contrario.  | 
| weight | INT |  Nuevo peso para la política de enmascaramiento o NULL en caso contrario.  | 

**Tipo de devolución**

Ninguno

**Example de agregar el rol de analista a una política de enmascaramiento existente sin cambiar otros atributos de la 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>

Este procedimiento elimina una política de enmascaramiento existente.

**Sintaxis**

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

**Argumentos**


| Parámetro | Tipo de datos | Descripción | 
| --- | --- | --- | 
| policy\$1name | NAME |  El nombre existente de la política de enmascaramiento.  | 
| table\$1name | REGCLASS |  El nombre cualificado o no cualificado o nulo de la tabla que contiene la política de enmascaramiento.  | 

**Tipo de devolución**

Ninguno

**Example de eliminar la política de enmascaramiento customer\$1mask**  

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

# Identificadores de escape en el procedimiento de DDL de la política de enmascaramiento
<a name="AuroraPostgreSQL.Security.DynamicMasking.EscapeIdentifiers"></a>

Al crear políticas de enmascaramiento de datos con los identificadores entre comillas, es necesario utilizarlas adecuadamente para garantizar que las referencias a los objetos y la aplicación de la política sean correctas. Para utilizar los identificadores entre comillas en los procedimientos de administración de la política de enmascaramiento `pg_columnmask`:
+ **Nombre de la política**: debe estar entre comillas dobles.
+ **Nombre de la tabla**: el nombre del esquema y el nombre de la tabla deben escribirse entre comillas dobles de forma individual cuando sea necesario.
+ **Expresiones de enmascaramiento**: los nombres de las columnas y funciones de las expresiones de enmascaramiento deben ir entre comillas dobles y las propias comillas deben ir precedidas de una barra invertida.
+ **Roles**: la matriz de nombres de roles se cita automáticamente. El nombre del rol debe coincidir exactamente con el nombre que se muestra en `pg_roles` al distinguir mayúsculas y minúsculas.

**Example de la sintaxis de escape y comillas**  
En este ejemplo, se muestra la sintaxis de escape y comillas adecuada al crear políticas de enmascaramiento para tablas, columnas, funciones y roles que utilizan nombres en mayúsculas y minúsculas o que requieren identificadores entre comillas en 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
```

## Vistas administrativas
<a name="AuroraPostgreSQL.Security.DynamicMasking.AdminViews"></a>

Puede revisar toda la política de `pg_columnmask` mediante la vista administrativa de `pgcolumnmask.pg_columnmask_policies` de acceso público. La siguiente información está disponible mediante esta vista. La vista solo devuelve las políticas de enmascaramiento que son propiedad del usuario actual.


| Nombre de la columna | Tipo de datos: | Descripción | 
| --- | --- | --- | 
|  schemaname  | NAME |  Esquema de la relación a la que está asociada la política  | 
|  tablename  | NAME |  Nombre de la relación a la que está asociada la política  | 
|  policyname  | NAME |  Nombre de la política de enmascaramiento, todas las políticas de enmascaramiento tienen nombres únicos  | 
|  roles  | TEXT[] |  Rol al que se aplica la política.  | 
|  masked\$1columns  | TEXT[] |  Columnas de enmascaramiento  | 
|  masking\$1functions  | TEXT[] |  Funciones de enmascaramiento  | 
| weight | INT |  Peso de la política adjunta  | 

# Funciones de enmascaramiento de datos predefinidas
<a name="AuroraPostgreSQL.Security.DynamicMasking.PredefinedMaskingFunctions"></a>

La extensión `pg_columnmask` proporciona funciones de utilidad integradas escritas en lenguaje C (para una ejecución más rápida) que se pueden utilizar como expresión de enmascaramiento para las políticas de `pg_columnmask`.

**mask\$1text**

Una función para enmascarar datos de texto con opciones de visibilidad configurables.

**Argumentos**


| Parámetro | Tipo de datos | Descripción | 
| --- | --- | --- | 
| input | TEXT |  La cadena de texto original que se va a enmascarar  | 
| mask\$1char | CHAR (1) |  Carácter utilizado para enmascarar (predeterminado: “X”)  | 
| visible\$1prefix | INT |  Número de caracteres al principio del texto ingresado que permanecerán desenmascarados (predeterminado: 0)  | 
| visible\$1suffix | INT |  Número de caracteres al final del texto ingresado que permanecerán desenmascarados (predeterminado: 0)  | 
| use\$1hash\$1mask | BOOLEANO |  Si es TRUE, utiliza un enmascaramiento basado en hash en lugar de mask\$1char (predeterminado: FALSE)  | 

**Example de usar diferentes opciones de enmascaramiento**  
Enmascaramiento de toda la cadena de entrada con el carácter “X” predeterminado  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World');
  mask_text  
-------------
 XXXXXXXXXXX
```
Uso del argumento `mask_char` para enmascarar la entrada de texto con un carácter diferente  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World', '*');
  mask_text  
-------------
 ***********
```
Uso de los parámetros `visible_prefix` y `visible_suffix` para controlar el número de caracteres que permanecen desenmascarados al principio y al final del texto  

```
postgres=> SELECT pgcolumnmask.mask_text('Hello World', '*', 5, 1);
  mask_text  
-------------
 Hello*****d
```
Cuando `use_hash_mask` es verdadero, la cadena de entrada está enmascarada con caracteres aleatorios, el argumento `mask_char` se ignora pero se sigue respetando `visible_prefix` y `visible_suffix`  

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

**mask\$1timestamp**


| Parámetro | Tipo de datos | Descripción | 
| --- | --- | --- | 
| ts\$1to\$1mask | TIMESTAMP |  La marca temporal original que se va a enmascarar  | 
| mask\$1part | TEXT |  Especifica qué parte de la marca temporal se debe enmascarar (predeterminado: “todas”) Valores válidos: “año”, “mes”, “día”, “hora”, “minuto”, “segundo”, “todo”  | 
| mask\$1value | TIMESTAMP |  El valor de la marca de tiempo que se va a usar para enmascarar (predeterminado: “1900-01-01 00:00:00”)  | 

**Example de cómo usar `mask_timestamps`**  
Estos ejemplos muestran el enmascaramiento completo de la marca de tiempo con un valor predeterminado, el enmascaramiento parcial de componentes específicos de la marca de tiempo (solo en el año) y el enmascaramiento con un valor de reemplazo personalizado.  
Enmascaramiento completo del valor de entrada con la marca de tiempo predeterminada  

```
postgres=> SELECT pgcolumnmask.mask_timestamp('2023-06-15 14:30:00');
   mask_timestamp    
---------------------
 1900-01-01 00:00:00
```
Enmascaramiento solo de una parte de la marca de tiempo, por ejemplo, solo el año  

```
postgres=> SELECT pgcolumnmask.mask_timestamp('2023-06-15 14:30:00', 'year');
   mask_timestamp    
---------------------
 1900-06-15 14:30:00
```
Cambio del valor enmascarado de la marca de tiempo mediante el 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**

Una función para enmascarar las direcciones de correo electrónico y, al mismo tiempo, preservar la estructura del correo electrónico.


| Parámetro | Tipo de datos | Descripción | 
| --- | --- | --- | 
| input | TEXT |  La dirección de correo electrónico original que se va a enmascarar  | 
| mask\$1char | CHAR (1) |  Carácter utilizado para enmascarar (predeterminado: “X”)  | 
| mask\$1local | BOOLEANO |  Si es TRUE, enmascara la parte local del correo electrónico (antes de @) (predeterminado: TRUE)  | 
| mask\$1domain | BOOLEANO |  Si es TRUE, enmascara la parte de dominio del correo electrónico (después de @) (predeterminado: TRUE)  | 

**Example de cómo usar `mask_email`**  
Estos ejemplos muestran el enmascaramiento completo del correo electrónico, los caracteres de máscara personalizados y el enmascaramiento selectivo de la parte local o de la parte del dominio de la dirección de correo electrónico.  
Enmascaramiento completo  

```
postgres=> SELECT pgcolumnmask.mask_email('user@example.com');
    mask_email    
------------------
 XXXX@XXXXXXX.com
```
Uso de `mask_char` para cambiar el carácter utilizado para enmascarar  

```
postgres=> SELECT pgcolumnmask.mask_email('user@example.com', '*');
    mask_email    
------------------
 ****@*******.com
```
Uso de `mask_local` y `mask_domain` para controlar el enmascaramiento en el entorno local y en el dominio  

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

# Implementación de pg\$1columnmask en un flujo de trabajo integral
<a name="AuroraPostgreSQL.Security.DynamicMasking.WorkflowExample"></a>

En esta sección se muestra una implementación completa del `pg_columnmask` mediante el uso de un ejemplo de tabla de empleados con información confidencial. Obtendrá información sobre cómo crear funciones de enmascaramiento personalizadas, definir múltiples políticas de enmascaramiento con diferentes niveles de peso para roles diferentes (pasante, de soporte, analista) y a observar cómo los usuarios que pertenecen a uno o varios roles ven diferentes niveles de datos enmascarados. Los ejemplos también incluyen el comportamiento de enmascaramiento en las instrucciones DML con cláusulas RETURNING, los activadores que utilizan tablas frente a vistas y las operaciones de administración de políticas, como el cambio de nombre, la modificación de los pesos y la limpieza.

1. Cree una tabla de ejemplo con información confidencial:

   ```
   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. Cree funciones de enmascaramiento 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. Cree varias políticas con diferentes niveles de enmascaramiento en función de los roles de los usuarios:

   ```
   -- 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. Los siguientes ejemplos muestran cómo los diferentes usuarios ven los datos en función de la pertenencia a un rol y los pesos de las 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 pasante (enmascaramiento más estricto):

   ```
   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 usuario de apoyo (enmascaramiento 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 (el enmascaramiento más ligero):

   ```
   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 usuario ethan\$1support\$1intern, que es a la vez pasante y usuario de soporte:

   ```
   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 es a la vez pasante y 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
   ```

# Comprensión del comportamiento de enmascaramiento en las operaciones de DML
<a name="AuroraPostgreSQL.Security.DynamicMasking.DMLMasking"></a>

`pg_columnmask` se aplica de forma coherente en todas las operaciones de DML, como las instrucciones INSERT, UPDATE, DELETE y MERGE. Al ejecutar estas operaciones, Aurora PostgreSQL enmascara los datos de acuerdo con un principio fundamental: todos los datos leídos del almacenamiento se enmascaran de acuerdo con las políticas aplicables del usuario actual.

El enmascaramiento afecta a algunos de los siguientes componentes de la consulta, como:
+ cláusulas WHERE
+ condiciones de JOIN
+ subconsultas
+ cláusulas RETURNING

Todos estos componentes funcionan con valores enmascarados, no con los datos originales. Aunque los datos se escriben en el almacenamiento desenmascarado, los usuarios solo ven su vista enmascarada cuando los vuelven a leer.

Aurora PostgreSQL aplica todas las restricciones de la base de datos (NOT NULL, UNIQUE, CHECK, FOREIGN KEY) a los valores almacenados reales, no a los valores enmascarados. En ocasiones, esto puede crear aparentes incoherencias si las funciones de enmascaramiento no se diseñan cuidadosamente.

El enmascaramiento trabaja junto con los permisos por columnas:
+ Los usuarios sin privilegios SELECT no pueden leer las columnas
+ Los usuarios con privilegios SELECT ven los valores enmascarados de acuerdo con las políticas aplicables

# Comprensión del comportamiento de enmascaramiento en las funciones de activación
<a name="AuroraPostgreSQL.Security.DynamicMasking.TriggerFunctionMasking"></a>

Cuando se aplican políticas de `pg_columnmask` a las tablas, es importante entender cómo interactúa el enmascaramiento con las funciones de activación. Los activadores son funciones de la base de datos que se ejecutan automáticamente en respuesta a determinados eventos de una tabla, como las operaciones INSERT, UPDATE o DELETE.

De forma predeterminada, DDM aplica diferentes reglas de enmascaramiento según el tipo de activador:

Activadores de tablas  
**Las tablas de transición están desenmascaradas**: las funciones activadoras de las tablas tienen acceso a los datos desenmascarados de sus tablas de transición en las versiones de filas antiguas y en las nuevas  
Los propietarios de las tablas crean los activadores y son dueños de los datos, por lo que tienen acceso total para administrar sus tablas de manera eficaz

Consulta de activadores (EN LUGAR DE los activadores)  
**Las tablas de transición están enmascaradas**: las funciones de activación de las vistas ven los datos enmascarados según los permisos del usuario actual  
Los propietarios de las vistas pueden diferir de los propietarios de la tabla base y deben respetar las políticas de enmascaramiento de las tablas subyacentes

Dos parámetros de configuración por servidor controlan el comportamiento de los activadores con tablas enmascaradas. Estos solo se pueden configurar mediante `rds_superuser`:
+ **Restringir los activadores en las tablas enmascaradas**: impide la ejecución de los activadores cuando un usuario enmascarado realiza operaciones de DML en tablas con las políticas de enmascaramiento aplicables.
+ **Restringir los activadores en las vistas con tablas enmascaradas:** impide la ejecución de los activadores en las vistas cuando la definición de la vista incluye tablas con políticas de enmascaramiento aplicables al usuario actual.

**Example de las diferencias entre la aplicación de la función a la tabla y la vista**  
El siguiente ejemplo crea una función de activación que imprime los valores de fila antiguos y nuevos y, a continuación, demuestra cómo la misma función se comporta de forma diferente cuando se adjunta a una tabla y no a una vista.  

```
-- 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;
```
Se recomienda revisar el comportamiento de los activadores antes de implementarlos en las tablas enmascaradas. Los activadores de tablas tienen acceso a los datos desenmascarados de las tablas de transición, mientras que los activadores de visualización ven los datos enmascarados.

**Example de cambio de nombre de la política de enmascaramiento**  
En el siguiente ejemplo se muestra cómo cambiar el nombre de las políticas existentes mediante el procedimiento `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 modificar el peso de las políticas**  
En el siguiente ejemplo se muestra cómo modificar las ponderaciones de las políticas para cambiar su 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 limpieza**  
En el siguiente ejemplo se muestra cómo eliminar todas las políticas, tablas y usuarios.  

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

# Configuración del rol de administración de políticas de enmascaramiento
<a name="AuroraPostgreSQL.Security.DynamicMasking.PolicyManagementRole"></a>

La extensión de enmascaramiento de columnas de PostgreSQL, `pg_columnmask`, le permite delegar la administración de las políticas de enmascaramiento a un rol específico, en lugar de requerir privilegios de propietario de tablas o `rds_superuser`. Esto proporciona un control más detallado sobre quién puede crear, modificar y eliminar las políticas de enmascaramiento.

Para configurar el rol que tendrá privilegios de administración de políticas de enmascaramiento, siga estos pasos:

1. Cree el rol de administrador de políticas: como un `rds_superuser`, cree un nuevo rol responsable de administrar las políticas de enmascaramiento:

   ```
   CREATE ROLE mask_admin NOLOGIN;
   ```

1. Configure el parámetro PostgreSQL: en el grupo de parámetros de clúster de base de datos personalizado, defina el parámetro de configuración del motor de `pgcolumnmask.policy_admin_rolname` con el nombre del rol que creó:

   ```
   pgcolumnmask.policy_admin_rolname = mask_admin
   ```

   Estos parámetros de configuración del motor se pueden establecer en un grupo de parámetros de un clúster de base de datos y no requiere el reinicio de la instancia. Para obtener más información sobre la actualización del parámetro, consulte [Modificación de los parámetros en un grupo de parámetros de clúster de base de datos en Amazon Aurora](USER_WorkingWithParamGroups.ModifyingCluster.md).

1. Otorgue el rol a los usuarios Como `rds_superuser`, conceda el rol de `mask_admin` a los usuarios que deberían administrar las políticas de enmascaramiento:

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

   Además, asegúrese de que los usuarios tengan el privilegio de USO en los esquemas en los que administrarán las políticas de enmascaramiento:

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

Ahora, cuando los usuarios `alice` y `bob` se conecten a la base de datos, podrán utilizar las funciones de extensión estándar `pg_columnmask` para crear, modificar y eliminar políticas de enmascaramiento en todas las tablas de todos los esquemas en los que tengan privilegios `USAGE` en el esquema.

# Prácticas recomendadas para la implementación segura de pg\$1columnmask
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices"></a>

En la siguiente sección, se proporcionan las prácticas recomendadas de seguridad para la implementación de `pg_columnmask` en el entorno de Aurora PostgreSQL. Siga estas recomendaciones para:
+ Establecimiento de una arquitectura de control de acceso basada en roles segura
+ Desarrollo de funciones de enmascaramiento que eviten las vulnerabilidades de seguridad
+ Comprensión y control del comportamiento de los activadores con datos enmascarados

## Arquitectura de seguridad basada en roles
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices.architecture"></a>

Defina una jerarquía de roles para implementar controles de acceso en la base de datos. Aurora PostgreSQL `pg_columnmask` amplía estos controles al proporcionar una capa adicional para el enmascaramiento de datos detallado dentro de esos roles.

Cree roles dedicados que se alineen con funciones organizativas en lugar de conceder permisos a usuarios individuales. Este enfoque proporciona una mejor auditoría y simplifica la administración de permisos a medida que la estructura organizativa evoluciona.

**Example de creación de una jerarquía de roles organizativos**  
En el siguiente ejemplo, se crea una jerarquía de roles organizativos con roles específicos para distintas funciones y, a continuación, se asignan usuarios individuales a los roles correspondientes. En este ejemplo, primero se crean los roles organizativos (analyst\$1role, support\$1role) y, a continuación, se concede a los usuarios individuales la pertenencia a estos roles. Esta estructura le permite administrar los permisos por rol y no para cada usuario 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 el principio de privilegio mínimo mediante la concesión de solo los permisos mínimos necesarios para cada rol. Evite conceder permisos amplios que puedan aprovecharse si las credenciales se ven 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;
```
Los administradores de políticas requieren privilegios de `USAGE` en los esquemas en los que administran las políticas de enmascaramiento. Conceda estos privilegios de forma selectiva, siguiendo el principio de privilegio mínimo. Realice revisiones periódicas de los permisos de acceso al esquema para asegurarse de que solo el personal autorizado mantenga las capacidades de administración de políticas.  
La configuración de los parámetros del rol de administrador de políticas está restringida solo a los administradores de bases de datos. Este parámetro no se puede modificar en la base de datos o sesión, lo que impide que los usuarios sin privilegios invaliden las asignaciones de administradores de políticas. Esta restricción garantiza que el control de la política de enmascaramiento permanezca centralizado y seguro.  
Asigne el rol de administrador de políticas a individuos específicos en lugar de a grupos. Este enfoque específico garantiza el acceso selectivo a la administración de políticas de enmascaramiento, ya que los administradores de políticas tienen la capacidad de enmascarar todas las tablas de la base de datos. 

## Desarrollo seguro de la función de enmascaramiento
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices.MaskingDevelopment"></a>

Desarrolle funciones de enmascaramiento utilizando una semántica de enlace temprana para garantizar un seguimiento adecuado de las dependencias y evitar vulnerabilidades de enlace tardío, como la modificación de la ruta de búsqueda durante el tiempo de ejecución. Se recomienda utilizar la sintaxis `BEGIN ATOMIC` de las funciones de SQL para permitir la validación en tiempo de compilación (es decir, la vinculación temprana) y la administración de las dependencias.

```
-- 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, cree funciones que sean inmunes a los cambios en la ruta de búsqueda mediante un esquema explícito que califique todas las referencias a los objetos para garantizar un comportamiento coherente en las distintas sesiones de usuario.

```
-- 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 la validación de entradas en las funciones de enmascaramiento para gestionar los casos de periferia y evitar comportamientos inesperados. Incluya siempre el manejo NULL y valide los formatos de entrada para garantizar un comportamiento de enmascaramiento coherente. 

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

## Comportamiento de activadores de DML con pg\$1columnmask
<a name="AuroraPostgreSQL.Security.DynamicMasking.BestPractices.DMLTriggerBehavior"></a>

Para los activadores de tablas, las tablas de transición se desenmascararán por completo. Para los activadores de visualización (IOT), las tablas de transición se enmascararán de acuerdo con los permisos de visualización del usuario actual.

Activadores de tablas con pg\$1columnmask  
A los activadores se les pasa una tabla de transición que contiene la versión antigua y la nueva de las filas modificadas por la consulta de DML de activación. Según el momento en que se active el activador, Aurora PostgreSQL rellena las filas antiguas y nuevas. Por ejemplo, un activador `BEFORE INSERT` solo tiene versiones nuevas de las filas y versiones antiguas vacías porque no hay ninguna versión anterior a la que hacer referencia.  
`pg_columnmask` no enmascara las tablas de transición dentro de los activadores de las tablas. Los activadores pueden usar columnas enmascaradas dentro de su cuerpo y ve los datos desenmascarados. El creador del activador debe asegurarse de cómo se ejecuta el activador para un usuario. El siguiente ejemplo funciona correctamente en este 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;
```
El creador del activador filtra datos desenmascarados al usuario si no tiene cuidado con las instrucciones que utiliza en el cuerpo del activador. Por ejemplo, el uso de `RAISE NOTICE ‘%’, masked_column;` imprime la columna al usuario actual.  

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

Se activa en las vistas con pg\$1columnmask (en lugar de activadores)  
Los activadores solo se pueden crear en las vistas de PostgreSQL. Se utilizan para ejecutar instrucciones DML en vistas que no se pueden actualizar. Las tablas de tránsito siempre están enmascaradas en el interior en lugar de activarlas (IOT), ya que la vista y las tablas base utilizadas dentro de la consulta de vista pueden tener propietarios diferentes. En ese caso, las tablas base pueden tener algunas políticas de enmascaramiento aplicables al propietario de la vista y el propietario de la vista siempre debe ver los datos enmascarados de las tablas base dentro de sus activadores. Esto es diferente de los activadores de las tablas porque, en ese caso, el creador del activador y los datos contenidos en las tablas son propiedad del mismo usuario, lo que no es el caso aquí.  

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

Los GUC por base de datos o usuario para controlar el comportamiento de los activadores  
Dos parámetros de configuración controlan el comportamiento de ejecución de los activadores para los usuarios con políticas de enmascaramiento aplicables. Utilice estos parámetros para evitar que los activadores se ejecuten en tablas o vistas enmascaradas cuando se requieran restricciones de seguridad adicionales. Ambos parámetros están desactivados de forma predeterminada, lo que permite que los activadores se ejecuten con normalidad.  
**Primer GUC: desencadene la restricción de activación en tablas enmascaradas**  
Especificaciones:  
+ Nombre: `pgcolumnmask.restrict_dml_triggers_for_masked_users`
+ Tipo: `boolean`
+ Predeterminado: `false` (se permite la ejecución de los activadores)
Impide la ejecución del activador en tablas enmascaradas para los usuarios enmascarados cuando se establece en TRUE. `pg_columnmask` corrige el error.  
**Segundo GUC: desencadene la restricción de activación en vistas con tablas enmascaradas**  
Especificaciones:  
+ Nombre: `pgcolumnmask.restrict_iot_triggers_for_masked_users`
+ Tipo: `boolean`
+ Predeterminado: `false` (se permite la ejecución de los activadores)
Impide la ejecución del activador en vistas que incluyen tablas enmascaradas en su definición para los usuarios enmascarados cuando se establece en TRUE.

Estos parámetros funcionan de forma independiente y se pueden configurar como los parámetros de configuración de una base de datos estándar.

# Escenarios de movimiento de datos pg\$1columnmask de Aurora PostgreSQL
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement"></a>

El comportamiento `pg_columnmask` varía según las distintas operaciones de movimiento de datos en función de si la operación se produce en la capa de almacenamiento, lógica o aplicación. Las operaciones por almacenamiento (como la clonación) se comportan de manera diferente a las operaciones lógicas (como `pg_dump`) y las operaciones por aplicación (como las consultas de FDW). En esta sección se describe el comportamiento de enmascaramiento en escenarios comunes, como la replicación, las copias de seguridad, las exportaciones y las migraciones, y se explican las implicaciones de seguridad de cada uno de ellos.

**Topics**
+ [Base de datos global de Aurora y réplicas de lectura](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.RR)
+ [Restauración de copias e instantáneas de bases de datos](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Clones)
+ [Replicación lógica](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.LogRep)
+ [Implementaciones azul/verde](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.BlueGreen)
+ [Transmisiones zero-ETL y CDC](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.ZETL)
+ [AWS Database Migration Service](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DMS)
+ [Exportaciones de datos](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DataExport)
+ [Vistas y vistas materializadas](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Views)
+ [Volcado y restauración de datos](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DDR)
+ [Contenedor de datos externos](#AuroraPostgreSQL.Security.DynamicMasking.DataMovement.FDQ)

## Base de datos global de Aurora y réplicas de lectura
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.RR"></a>

Las políticas de `pg_columnmask` de Aurora se almacenan en tablas del sistema de bases de datos dentro del volumen del clúster. Todas las réplicas acceden a las mismas políticas y devuelven resultados enmascarados de forma coherente. Para las implementaciones de Aurora Global Database, las políticas de `pg_columnmask` se replican en Regiones de AWS secundarias junto con otras tablas del sistema de bases de datos, lo que garantiza una protección de datos coherente en todas las regiones. Durante los escenarios de conmutación por error, todas las políticas de `pg_columnmask` permanecen intactas y funcionales.

## Restauración de copias e instantáneas de bases de datos
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Clones"></a>

Las operaciones de restauración de instantáneas y clonación rápida de Aurora conservan todas las políticas de `pg_columnmask`, roles y configuraciones como parte de las tablas del sistema de bases de datos. La base de datos clonada o restaurada hereda todas las políticas existentes del clúster de origen. Tras la clonación o la restauración, cada clúster de base de datos mantiene políticas de `pg_columnmask` independientes.

## Replicación lógica
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.LogRep"></a>

Durante la sincronización inicial, la replicación lógica utiliza operaciones estándar de SQL COPY y las políticas de `pg_columnmask` se aplican en función de los permisos del usuario de la replicación. Durante el CDC continuo (captura de datos de cambio), no se aplican políticas de enmascaramiento y los datos desenmascarados se replican a través de los registros de WAL. Los usuarios con privilegios de `pg_create_subscription` pueden filtrar datos desenmascarados configurando la replicación en un sistema que controlen.

## Implementaciones azul/verde
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.BlueGreen"></a>

Durante la restauración de las instantáneas, las políticas de `pg_columnmask` se incluyen automáticamente. El entorno verde comienza con una copia idéntica de todas las políticas del entorno azul. Durante la replicación, de azul a verde, los datos no se enmascaran. Los cambios posteriores en la política de enmascaramiento (comandos DDL) en el clúster azul no se replican en el clúster verde e invalidan las implementaciones azul/verde de RDS.

## Transmisiones zero-ETL y CDC
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.ZETL"></a>

La replicación de datos no se ve afectada por las políticas de `pg_columnmask`. Zero-ETL admite la replicación de DDL, pero no replica `pg_columnmask` ni las políticas de RLS. No se aplican políticas de enmascaramiento a los datos replicados en Zero-ETL.

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

La sincronización de datos inicial se enmascara o desenmascara en función del usuario seleccionado para la tarea de DMS. Los datos de CDC siempre están desenmascarados. Aunque las políticas de RLS internas relacionadas con `pg_columnmask` se pueden migrar, no funcionarán en destinos non-pg\$1columnmask-enabled.

## Exportaciones de datos
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DataExport"></a>

`pg_columnmask` trata las exportaciones como cualquier otra operación de consulta: el enmascaramiento se aplica en función de los permisos del usuario ejecutor. Esto se aplica a los comandos de SQL como COPY, SELECT INTO, CREATE TABLE AS y a la funcionalidad de exportación de S3 de Aurora PostgreSQL. 

**nota**  
Cuando los usuarios enmascarados exportan datos, los archivos resultantes contienen valores enmascarados que, al restaurarse, pueden infringir las restricciones de la base de datos.

## Vistas y vistas materializadas
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.Views"></a>

Tenga en cuenta las siguientes consideraciones al utilizar las vistas:
+ **Vistas normales**: utilice siempre la semántica de `INVOKER`. Las políticas de enmascaramiento del usuario actual se aplican al consultar la vista, independientemente de quién la haya creado.
+ **Vistas materializadas**: cuando se actualizan, se aplican las políticas de enmascaramiento del propietario de la vista materializada, no las políticas del usuario que realiza la actualización. Si el propietario tiene políticas de enmascaramiento, la vista materializada siempre contiene datos enmascarados.

## Volcado y restauración de datos
<a name="AuroraPostgreSQL.Security.DynamicMasking.DataMovement.DDR"></a>

`pg_dump` funciona como un usuario normal de la base de datos y aplica políticas de enmascaramiento en función de los permisos del usuario que se conecta. Si un usuario enmascarado realiza un volcado, el archivo de copia de seguridad contiene datos enmascarados. Las políticas de `pg_columnmask` se incluyen en el volcado como parte del esquema de la base de datos. La restauración correcta requiere que todos los roles a los que se hace referencia estén en la base de datos de destino y que el destino tenga instalada la extensión `pg_columnmask`.

**nota**  
A partir de PostgreSQL 18, `pg_dump` admite la opción `—no-policies` que excluye la seguridad por fila (RLS) y políticas de enmascaramiento de `pg_columnmask` de los volcados de bases de datos. Para obtener más información, consulte [pg\$1dump](https://www.postgresql.org/docs/current/app-pgdump.html).

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

Cuando se utilizan contenedores de datos externos, las políticas de enmascaramiento en las tablas remotas se aplican en función de los permisos del usuario asignado en el servidor de origen, no de los permisos del usuario local que consulta, y aunque puede acceder a los datos remotos enmascarados mediante FDW, no puede crear políticas de DDM o RLS directamente en las tablas externas de la base de datos local.