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 compilar aplicaciones de IA generativa, a menudo olvidamos el componente más importante: la seguridad.

Imagina que compilas 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 habitual) 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 dependen de que el LLM "se comporte". Esto es frágil. Si falla la lógica de la app, se filtran los datos.

La solución

Envía la seguridad a la capa de base de datos. Si usas la seguridad a nivel de la fila (RLS) de PostgreSQL en AlloyDB, te aseguras de que la base de datos se niegue físicamente a mostrar datos que el usuario no tiene permiso para ver, sin importar lo que solicite la IA.

En esta guía, compilaremos "The Private Vault": 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 compilaremos una lógica de permisos compleja en Python. Usaremos el motor de base de datos.

  1. La interfaz: Una app simple de Streamlit 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 datos sensibles, implementarás la seguridad a nivel de la fila (RLS) directamente en el motor de 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 mostrarlos.

Qué aprenderás

Aprenderás a hacer lo siguiente:

  • Cómo diseñar un esquema para 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.
  3. 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 conectes a Cloud Shell, verifica que ya te autenticaste y que el proyecto esté configurado con tu ID del 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 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 y solución de problemas

Síndrome del "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.

Barrera de facturación

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

Demora en 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.

Cuotas

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. Usa 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 vínculo que aparece a continuación en el navegador en el que accediste como usuario de la consola de Google Cloud.

  1. Una vez que se complete este paso, el repositorio se clonará en tu editor de Cloud Shell local y podrás ejecutar el siguiente comando desde la carpeta del proyecto (es importante asegurarte de que estás 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 "vista previa en la Web" de la terminal).
  2. Ingresa los detalles del ID del proyecto, el clúster y los nombres de las instancias para comenzar.
  3. Toma un café mientras se desplazan los registros y puedes leer cómo lo hace tras bambalinas aquí. Puede tardar entre 10 y 15 minutos.

Problemas y solución de problemas

Problema de "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 atascada", es posible que termines con una instancia "fantasma" que se aprovisionó parcialmente y que no se puede borrar sin intervención manual.

Incompatibilidad 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. Usa una región para todo el lab.

Clústeres 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 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 ejecución el clúster y la 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 se termine de crear 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 lo que hayas configurado en el momento de la creación)

Una vez que te autentiques correctamente en AlloyDB Studio, se ingresarán los comandos de 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, con 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). Separarlas es fundamental 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 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 administrador", 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 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 sentencia 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 y solución de problemas

Prueba como postgres (superusuario) y ve todos los datos.

Por qué falla: De forma predeterminada, RLS no se aplica al propietario de la tabla ni a los superusuarios. Omiten todas las políticas.Solución de problemas: Si tus políticas parecen "rotas" (permiten todo), verifica si accediste como postgres.Solución: El FORCE ROW LEVEL SECURITY comando 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 nuestro código de la aplicación.

Ejecuta la siguiente sentencia 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 y solución de problemas

Usa current_user en lugar de app.active_user.

Problema: Current_user es una palabra clave de SQL reservada que muestra 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.

Olvidaste el parámetro true en current_setting.

Problema: Si no se establece la variable, la consulta falla con un error.Solución: current_setting('...', true) muestra NULL en lugar de fallar, lo que genera de forma segura 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 proyecto nuevo:

pip install streamlit psycopg2-binary

2. Crea app.py:

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

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 proyecto nuevo:

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

Problemas 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 puede 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 garantizar la implementación de 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 evaluaciones de rendimiento", generará SELECT performance_review FROM employees.

  1. Sin RLS: Tu modelo recupera las evaluaciones privadas de todos y se las filtra a Alice.
  2. Con RLS: Tu modelo ejecuta la misma consulta, pero la base de datos solo muestra la evaluación de Alice.

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

Llevar esto a producción

La arquitectura que se muestra aquí es de nivel de producción, pero la implementación específica se simplifica para 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 "Identity Switcher" 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 establecer 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., RESET app.active_user) o borre el estado de la conexión cuando devuelva una conexión al grupo para evitar la filtración de datos entre los 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 base de datos y cadenas de conexión durante el tiempo de ejecución.

9. Limpia

Una vez que 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 Lograste enviar 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 mostraría nada a Alice.

Próximos pasos

  • Prueba esto con tu propio conjunto de datos.
  • Explora la documentación de AlloyDB AI.
  • Consulta el sitio web de Code Vipassana para obtener más talleres.