Private Vault: Cómo crear "inteligencia de confianza cero" con la seguridad a nivel de la fila de AlloyDB

1. Descripción general

En la prisa por crear aplicaciones basadas en IA generativa, a menudo olvidamos el componente más importante: la seguridad.

Imagina que creas un chatbot de RR.HH. Quieres que responda preguntas como "¿Cuál es mi salario?" o "¿Cómo se desempeña mi equipo?"

  • Si Alice (una empleada normal) pregunta, solo debería ver sus datos.
  • Si Bob (un administrador) pregunta, debería ver los datos de su equipo.

El problema

La mayoría de las arquitecturas de RAG (generación mejorada por recuperación) intentan controlar esto en la capa de aplicación. Filtran fragmentos después de recuperarlos o confían en que el LLM se "comportará". Es frágil. Si falla la lógica de la app, se filtran datos.

La solución

Envía la seguridad a la capa de la base de datos. Con la seguridad a nivel de las filas (RLS) de PostgreSQL en AlloyDB, garantizamos que la base de datos se niegue físicamente a devolver datos que el usuario no tiene permiso para ver, sin importar lo que solicite la IA.

En esta guía, crearemos "La bóveda privada": un asistente de RR.HH. seguro que cambia sus respuestas de forma dinámica según quién haya accedido.

1e095ac5fe069bb6.png

La arquitectura

No estamos creando una lógica de permisos compleja en Python. Estamos usando el motor de base de datos en sí.

  1. La interfaz: Una app de Streamlit simple que simula un acceso.
  2. El cerebro: AlloyDB AI (compatible con PostgreSQL)
  3. El mecanismo: Establecemos una variable de sesión (app.active_user) al comienzo de cada transacción. Las políticas de la base de datos verifican automáticamente una tabla user_roles (que actúa como nuestro proveedor de identidad) para filtrar las filas.

Qué compilarás

Una aplicación segura de asistente de RR.HH. En lugar de depender de la lógica de la aplicación para filtrar los datos sensibles, implementarás la seguridad a nivel de la fila (RLS) directamente en el motor de la base de datos de AlloyDB. Esto garantiza que, incluso si tu modelo de IA "alucina" o intenta acceder a datos no autorizados, la base de datos se negará físicamente a devolverlos.

Qué aprenderás

Aprenderás a hacer lo siguiente:

  • Cómo diseñar un esquema para la RLS (separación de datos y de identidad)
  • Cómo escribir políticas de PostgreSQL (CREATE POLICY)
  • Cómo omitir la exención de "Propietario de la tabla" con FORCE ROW LEVEL SECURITY
  • Cómo compilar una app de Python que realice un "cambio de contexto" para los usuarios

Requisitos

  • Un navegador, como Chrome o Firefox
  • Un proyecto de Google Cloud con facturación habilitada.
  • Acceso a Cloud Shell o a una terminal con gcloud y psql instalados

2. Antes de comenzar

Crea un proyecto

  1. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.
  2. Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Obtén información para verificar si la facturación está habilitada en un proyecto.
  1. Usarás Cloud Shell, un entorno de línea de comandos que se ejecuta en Google Cloud. Haz clic en Activar Cloud Shell en la parte superior de la consola de Google Cloud.

Imagen del botón Activar Cloud Shell

  1. Una vez que te hayas conectado a Cloud Shell, verifica que ya te autenticaste y que el proyecto se configuró con tu ID de proyecto con el siguiente comando:
gcloud auth list
  1. En Cloud Shell, ejecuta el siguiente comando para confirmar que el comando gcloud conoce tu proyecto.
gcloud config list project
  1. Si tu proyecto no está configurado, usa el siguiente comando para hacerlo:
gcloud config set project <YOUR_PROJECT_ID>
  1. Habilita las APIs necesarias: Sigue el vínculo y habilita las APIs.

Como alternativa, puedes usar el comando de gcloud para esto. Consulta la documentación para ver los comandos y el uso de gcloud.

gcloud services enable \
  alloydb.googleapis.com \
  compute.googleapis.com \
  cloudresourcemanager.googleapis.com \
  servicenetworking.googleapis.com \
  aiplatform.googleapis.com

Problemas potenciales y solución de problemas

El "Proyecto Fantasma"

Ejecutaste gcloud config set project, pero, en realidad, estás viendo un proyecto diferente en la IU de la consola. Verifica el ID del proyecto en el menú desplegable de la esquina superior izquierda.

La barricada de facturación

Habilitaste el proyecto, pero olvidaste la cuenta de facturación. AlloyDB es un motor de alto rendimiento que no se iniciará si el "tanque de combustible" (facturación) está vacío.

Retraso de la propagación de la API

Hiciste clic en "Habilitar APIs", pero la línea de comandos aún dice Service Not Enabled. Espera 60 segundos. La nube necesita un momento para activar sus neuronas.

Cuota de Quags

Si usas una cuenta de prueba nueva, es posible que alcances una cuota regional para las instancias de AlloyDB. Si falla us-central1, prueba con us-east1.

3. Configuración de la base de datos

En este lab, usaremos AlloyDB como la base de datos para los datos de prueba. Utiliza clústeres para contener todos los recursos, como bases de datos y registros. Cada clúster tiene una instancia principal que proporciona un punto de acceso a los datos. Las tablas contendrán los datos reales.

Creemos un clúster, una instancia y una tabla de AlloyDB en los que se cargará el conjunto de datos de prueba.

  1. Haz clic en el botón o copia el siguiente vínculo en el navegador en el que accediste como usuario a la consola de Google Cloud.

  1. Una vez que se complete este paso, el repo se clonará en tu editor local de Cloud Shell y podrás ejecutar el siguiente comando desde la carpeta del proyecto (es importante que te asegures de estar en el directorio del proyecto):
sh run.sh
  1. Ahora usa la IU (haz clic en el vínculo de la terminal o en el vínculo "preview on web" de la terminal).
  2. Ingresa los detalles del ID del proyecto, el clúster y los nombres de las instancias para comenzar.
  3. Ve a tomar un café mientras se desplazan los registros. Aquí puedes leer cómo se hace esto en segundo plano. Esto puede tardar entre 10 y 15 minutos.

Problemas potenciales y solución de problemas

El problema de la "paciencia"

Los clústeres de bases de datos son una infraestructura pesada. Si actualizas la página o finalizas la sesión de Cloud Shell porque "parece que se detuvo", es posible que termines con una instancia "fantasma" que se aprovisionó parcialmente y que es imposible borrar sin intervención manual.

Incongruencia de región

Si habilitaste tus APIs en us-central1, pero intentas aprovisionar el clúster en asia-south1, es posible que tengas problemas de cuota o demoras en los permisos de la cuenta de servicio. Mantén una sola región para todo el lab.

Clústeres de zombis

Si antes usaste el mismo nombre para un clúster y no lo borraste, es posible que la secuencia de comandos indique que el nombre del clúster ya existe. Los nombres de los clústeres deben ser únicos dentro de un proyecto.

Tiempo de espera de Cloud Shell

Si tu descanso para tomar café dura 30 minutos, es posible que Cloud Shell entre en modo de suspensión y desconecte el proceso sh run.sh. Mantén la pestaña activa.

4. Aprovisionamiento de esquemas

En este paso, abordaremos lo siguiente:

d05d7d2706c689dc.png

Estas son las acciones detalladas paso a paso:

Una vez que tengas en funcionamiento tu clúster y tu instancia de AlloyDB, dirígete al editor de SQL de AlloyDB Studio para habilitar las extensiones de IA y aprovisionar el esquema.

1e3ac974b18a8113.png

Es posible que debas esperar a que termine de crearse la instancia. Una vez que lo hagas, accede a AlloyDB con las credenciales que creaste cuando creaste el clúster. Usa los siguientes datos para autenticarte en PostgreSQL:

  • Nombre de usuario : "postgres"
  • Base de datos : "postgres"
  • Contraseña: “alloydb” (o la que hayas configurado en el momento de la creación)

Una vez que te autentiques correctamente en AlloyDB Studio, ingresa los comandos SQL en el editor. Puedes agregar varias ventanas del editor con el signo más que se encuentra a la derecha de la última ventana.

28cb9a8b6aa0789f.png

Ingresarás comandos para AlloyDB en ventanas del editor, y usarás las opciones Ejecutar, Formatear y Borrar según sea necesario.

Crea una tabla

Necesitamos dos tablas: una para los datos sensibles (empleados) y otra para las reglas de identidad (user_roles). Es fundamental separarlos para evitar errores de "recursión infinita" en las políticas.

Puedes crear una tabla con la siguiente instrucción DDL en AlloyDB Studio:

-- 1. Create User Roles (The Identity Provider)
CREATE TABLE user_roles (
    username TEXT PRIMARY KEY,
    role TEXT -- 'employee', 'manager', 'admin'
);

INSERT INTO user_roles (username, role) VALUES
('Alice', 'employee'),
('Bob', 'manager'),
('Charlie', 'employee');

-- 2. Create the Data Table
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name TEXT,
    salary INTEGER,
    performance_review TEXT
);

INSERT INTO employees (name, salary, performance_review) VALUES
('Alice', 80000, 'Alice meets expectations but needs to improve punctuality.'),
('Bob', 120000, 'Bob is a strong leader. Team morale is high.'),
('Charlie', 85000, 'Charlie exceeds expectations. Ready for promotion.');

Problemas potenciales y solución de problemas

Se detectó una recursión infinita mientras se definían roles dentro de la tabla de empleados

Por qué falla: Si tu política dice "Consulta la tabla de empleados para ver si soy gerente", la base de datos debe consultar la tabla para verificar la política, lo que vuelve a activar la política.Resultado: Se detectó una recursión infinita.Solución: Siempre mantén una tabla de búsqueda separada (user_roles) o usa usuarios de la base de datos reales para los roles.

Verifica los datos:

SELECT count(*) FROM employees;
-- Output: 3

5. Habilita y aplica la seguridad

Ahora activaremos los escudos. También crearemos un "Usuario de la app" genérico que nuestro código de Python usará para conectarse.

Ejecuta la siguiente instrucción de SQL desde el editor de consultas de AlloyDB:

-- 1. Activate RLS
ALTER TABLE employees ENABLE ROW LEVEL SECURITY;


-- 2. CRITICAL: Force RLS for Table Owners
ALTER TABLE employees FORCE ROW LEVEL SECURITY;


-- 3. Create the Application User
DO
$do$
BEGIN
   IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'app_user') THEN
      CREATE ROLE app_user LOGIN PASSWORD 'password';
   END IF;
END
$do$;


-- 4. Grant Access
GRANT SELECT ON employees TO app_user;
GRANT SELECT ON user_roles TO app_user;

Problemas potenciales y solución de problemas

Pruebas como postgres (superusuario) y visualización de todos los datos.

Por qué falla: De forma predeterminada, la RLS no se aplica al propietario de la tabla ni a los superusuarios. Eluden todas las políticas.Solución de problemas: Si tus políticas parecen "rotas" (permiten todo), verifica si accediste como postgres.Solución: El comando FORCE ROW LEVEL SECURITY garantiza que incluso el propietario esté sujeto a las reglas. Esto es fundamental para las pruebas.

6. Crea las políticas de acceso

Definiremos dos reglas con una variable de sesión (app.active_user) que estableceremos más adelante desde el código de nuestra aplicación.

Ejecuta la siguiente instrucción de SQL desde el editor de consultas de AlloyDB:

-- Policy 1: Self-View
-- Users can see rows where their name matches the session variable
CREATE POLICY "view_own_data" ON employees
FOR SELECT
USING (name = current_setting('app.active_user', true));

-- Policy 2: Manager-View
-- Managers can see ALL rows.
CREATE POLICY "manager_view_all" ON employees
FOR SELECT
USING (
    EXISTS (
        SELECT 1 FROM user_roles
        WHERE username = current_setting('app.active_user', true)
        AND role = 'manager'
    )
);

Problemas potenciales y solución de problemas

Usar current_user en lugar de app.active_user

Problema: Current_user es una palabra clave reservada de SQL que devuelve el rol de la base de datos (p.ej., app_user). Necesitamos el usuario de la aplicación (p.ej., Alice).Solución: Siempre usa un espacio de nombres personalizado, como app.variable_name.

Olvidar el parámetro true en current_setting

Problema: Si no se configura la variable, la consulta falla con un error.Solución: current_setting('...', true) devuelve NULL en lugar de fallar, lo que genera de forma segura que se devuelvan 0 filas.

7. Compila la app "Chameleon"

Usaremos Python y Streamlit para simular la lógica de la aplicación.

296a980887b5c700.png

Abre la terminal de Cloud Shell en modo Editor, navega a tu carpeta raíz o al directorio en el que deseas crear esta aplicación. Crear una carpeta nueva.

1. Instala las dependencias:

Ejecuta el siguiente comando en la terminal de Cloud Shell desde el directorio de tu nuevo proyecto:

pip install streamlit psycopg2-binary

2. Crea app.py:

Crea un archivo nuevo llamado app.py y copia el contenido del archivo del repo.

import streamlit as st
import psycopg2

# CONFIGURATION (Replace with your IP)
DB_HOST = "10.x.x.x"
DB_NAME = "postgres"
DB_USER = "postgres"
DB_PASS = "alloydb"

def get_db_connection():
    return psycopg2.connect(
        host=DB_HOST, database=DB_NAME, user=DB_USER, password=DB_PASS
    )

def query_database(user_name):
    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            # THE SECURITY HANDSHAKE
            # We tell the database: "For this transaction, I am acting as..."
            cur.execute(f"SET app.active_user = '{user_name}';")
            
            # THE BLIND QUERY
            # We ask for EVERYTHING. The database silently filters it.
            cur.execute("SELECT name, role, salary, performance_review FROM employees;")
            return cur.fetchall()
    finally:
        conn.close()

# UI
st.title("🛡️ The Private Vault")
user = st.sidebar.radio("Act as User:", ["Alice", "Bob", "Charlie", "Eve"])

if st.button("Access Data"):
    results = query_database(user)
    if not results:
        st.error("🚫 Access Denied.")
    else:
        st.success(f"Viewing data as {user}")
        for row in results:
            st.write(row)

3. Ejecuta la app:

Ejecuta el siguiente comando en la terminal de Cloud Shell desde el directorio de tu nuevo proyecto:

streamlit run app.py --server.port 8080 --server.enableCORS false

Problemas potenciales y solución de problemas

Agrupación de conexiones

Riesgo: Si usas un grupo de conexiones, la variable de sesión SET app.active_user podría persistir en la conexión y “filtrarse” al siguiente usuario que tome esa conexión.Solución: En producción, siempre usa RESET app.active_user o DISCARD ALL cuando devuelvas una conexión al grupo.

Pantalla en blanco en Cloud Shell.

Solución: Usa el botón "Vista previa en la Web" en el puerto 8080. No hagas clic en el vínculo localhost de la terminal.

8. Verifica la confianza cero

Prueba la app para asegurarte de que se implementó la confianza cero:

Selecciona "Alice": Debería ver 1 fila (ella misma).

b3b7e374fa66ac87.png

Selecciona "Bob": Debería ver 3 filas (Todos).

fdc65cb1acdee8a4.png

Por qué esto es importante para los agentes de IA

Imagina que conectas tu modelo a esta base de datos. Si un usuario le pregunta a tu modelo: "Resume todas las revisiones de rendimiento", se generará SELECT performance_review FROM employees.

  1. Sin RLS: Tu modelo recupera las opiniones privadas de todos y se las filtra a Alicia.
  2. Con RLS: Tu modelo ejecuta exactamente la misma consulta, pero la base de datos solo devuelve la revisión de Alice.

Esta es la IA de confianza cero. No confías en el modelo para filtrar los datos, sino que obligas a la base de datos a ocultarlos.

Lanzamiento en producción

La arquitectura que se muestra aquí es apta para la producción, pero la implementación específica se simplificó para facilitar el aprendizaje. Para implementar esto de forma segura en un entorno empresarial real, debes implementar las siguientes mejoras:

  1. Autenticación real: Reemplaza el menú desplegable "Selector de identidad" por un proveedor de identidad (IDP) sólido, como Google Identity Platform, Okta o Auth0. Tu aplicación debe verificar el token del usuario y extraer su identidad de forma segura antes de configurar la variable de sesión de la base de datos, lo que garantiza que los usuarios no puedan suplantar su identidad.
  2. Seguridad de la agrupación de conexiones: Cuando se usan grupos de conexiones, las variables de sesión a veces pueden persistir en diferentes solicitudes de usuarios si no se controlan correctamente. Asegúrate de que tu aplicación restablezca la variable de sesión (p.ej., RESTABLECE app.active_user) o borra el estado de conexión cuando devuelve una conexión al grupo para evitar la filtración de datos entre usuarios.
  3. Administración de secretos: Codificar de forma rígida las credenciales de la base de datos es un riesgo de seguridad. Usa un servicio de administración de secretos dedicado, como Google Secret Manager, para almacenar y recuperar de forma segura tus contraseñas de bases de datos y cadenas de conexión durante el tiempo de ejecución.

9. Limpia

Cuando termines este lab, no olvides borrar el clúster y la instancia de AlloyDB.

Debería limpiar el clúster junto con sus instancias.

10. Felicitaciones

¡Felicitaciones! Enviaste correctamente la seguridad a la capa de datos. Incluso si tu código de Python tuviera un error que intentara print(all_salaries), la base de datos no devolvería nada a Alice.

Próximos pasos