私密保险库:利用 AlloyDB 行级安全性构建“零信任智能”

1. 概览

在争先构建生成式 AI 应用时,我们往往会忘记最重要的组件:安全性

假设您要构建一个人力资源聊天机器人。您希望它回答“我的工资是多少?”“我的团队表现如何?”等问题。

  • 如果 Alice (普通员工)提问,她应该只能看到自己的数据。
  • 如果 Bob (经理)提问,他应该看到其团队的数据。

问题

大多数 RAG(检索增强生成)架构都尝试在应用层处理此问题。 它们会在检索到数据块后对其进行过滤,或者依赖于 LLM 的“行为”。 这种做法很脆弱。如果应用逻辑失败,数据就会泄露。

解决方案

将安全性下推到数据库层 。通过在 AlloyDB 中使用 PostgreSQL 行级安全性 (RLS) ,我们确保数据库会 物理拒绝返回用户无权查看的数据,无论 AI 请求什么数据。

在本指南中,我们将构建“私人保险库”:一个安全的人力资源助理,它会根据登录的用户动态更改答案。

1e095ac5fe069bb6.png

架构

我们不会在 Python 中构建复杂的权限逻辑,而是使用数据库引擎本身。

  1. 界面 :一个简单的 Streamlit 应用,用于模拟登录。
  2. 大脑 :AlloyDB AI(与 PostgreSQL 兼容)。
  3. 机制 :我们在每项事务开始时设置会话变量 (app.active_user)。数据库政策会自动检查 user_roles 表(充当我们的身份提供方)以过滤行。

构建内容

一个安全的人力资源助理应用。您将直接在 AlloyDB 数据库引擎中实现行级安全性 (RLS),而不是依赖于应用逻辑来过滤敏感数据。这样可确保,即使 AI 模型“产生幻觉”或尝试访问未经授权的数据,数据库也会物理拒绝返回这些数据。

学习内容

您将学习以下内容:

  • 如何为 RLS 设计架构(将数据与身份分开)。
  • 如何编写 PostgreSQL 政策 (CREATE POLICY)。
  • 如何使用 FORCE ROW LEVEL SECURITY 绕过“表所有者”豁免。
  • 如何构建一个为用户执行“上下文切换”的 Python 应用。

要求

  • 浏览器,例如 ChromeFirefox
  • 启用了结算功能的 Google Cloud 项目。
  • 可以访问 Cloud Shell 或安装了 gcloud 和 psql 的终端。

2. 准备工作

创建项目

  1. Google Cloud 控制台的项目选择器页面上,选择或创建一个 Google Cloud 项目
  2. 确保您的 Cloud 项目已启用结算功能。了解如何 检查项目是否已启用结算功能
  1. 您将使用 Cloud Shell,它是在 Google Cloud 中运行的命令行环境。点击 Google Cloud 控制台顶部的激活 Cloud Shell 。

“激活 Cloud Shell”按钮图片

  1. 连接到 Cloud Shell 后,您可以使用以下命令检查自己是否已通过身份验证,以及项目是否已设置为您的项目 ID:
gcloud auth list
  1. 在 Cloud Shell 中运行以下命令,以确认 gcloud 命令了解您的项目。
gcloud config list project
  1. 如果项目未设置,请使用以下命令进行设置:
gcloud config set project <YOUR_PROJECT_ID>
  1. 启用必需的 API:点击 链接 并启用 API。

或者,您也可以使用 gcloud 命令执行此操作。如需了解 gcloud 命令和用法,请参阅 文档

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

注意事项和问题排查

“幽灵项目” 综合征

您运行了 gcloud config set project,但实际上在控制台界面中查看的是另一个项目。检查左上角下拉列表中的项目 ID!

结算 障碍

您启用了项目,但忘记了结算账号。AlloyDB 是一种高性能引擎;如果“油箱”(结算)为空,它将无法启动。

API 传播 延迟

您点击了“启用 API”,但命令行仍然显示 Service Not Enabled。请等待 60 秒。云需要一点时间来唤醒其神经元。

配额 陷阱

如果您使用的是全新的试用账号,则可能会达到 AlloyDB 实例的区域配额。如果 us-central1 失败,请尝试 us-east1

3. 数据库设置

在此实验中,我们将使用 AlloyDB 作为测试数据的数据库。它使用 集群来保存所有资源,例如数据库和日志。每个集群都有一个 主实例,用于提供对数据的接入点。表将保存实际数据。

让我们创建一个 AlloyDB 集群、实例和表,用于加载测试数据集。

  1. 点击该按钮,或将以下链接复制到您已登录 Google Cloud 控制台用户的浏览器中。

  1. 完成此步骤后,代码库将克隆到本地 Cloud Shell 编辑器,您将能够从项目文件夹中运行以下命令(请务必确保您位于项目目录中):
sh run.sh
  1. 现在,使用界面(点击终端中的链接或点击终端中的“在网页上预览”链接)。
  2. 输入项目 ID、集群和实例名称的详细信息,即可开始使用。
  3. 在日志滚动时,您可以去喝杯咖啡,并在此处了解其在后台执行此操作的方式。这可能需要大约 10-15 分钟。

注意事项和问题排查

“耐心”问题

数据库集群是重型基础架构。如果您刷新页面或终止 Cloud Shell 会话,因为它“看起来卡住了”,您最终可能会得到一个“幽灵”实例,该实例已部分预配,并且无法在不进行手动干预的情况下删除。

区域不匹配

如果您在 us-central1 中启用了 API,但尝试在 asia-south1 中预配集群,则可能会遇到配额问题或服务账号权限延迟。在整个实验过程中,请坚持使用一个区域!

僵尸集群

如果您之前对集群使用了相同的名称但未删除该集群,脚本可能会显示集群名称已存在。集群名称在项目中必须是唯一的。

Cloud Shell 超时

如果您的咖啡休息时间为 30 分钟,Cloud Shell 可能会进入休眠状态并断开 sh run.sh 进程。请保持标签页处于活动状态!

4. 架构预配

在此步骤中,我们将介绍以下内容:

d05d7d2706c689dc.png

以下是详细的分步操作:

AlloyDB 集群和实例运行后,前往 AlloyDB Studio SQL 编辑器以启用 AI 扩展程序并预配架构。

1e3ac974b18a8113.png

您可能需要等待实例创建完成。 完成后,使用创建集群时创建的凭据登录 AlloyDB。 使用以下数据向 PostgreSQL 进行身份验证:

  • 用户名:“postgres
  • 数据库:“postgres
  • 密码:“alloydb”(或您在创建时设置的任何密码)

成功向 AlloyDB Studio 进行身份验证后,在编辑器中输入 SQL 命令。 您可以使用最后一个窗口右侧的加号添加多个编辑器窗口。

28cb9a8b6aa0789f.png

您将在编辑器窗口中输入 AlloyDB 的命令,并根据需要使用“运行”“格式”和“清除”选项。

创建表

我们需要两个表:一个用于存储敏感数据(员工),另一个用于存储身份规则 (user_roles)。将它们分开对于避免政策中的“无限递归”错误至关重要。

您可以在 AlloyDB Studio 中使用以下 DDL 语句创建表:

-- 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.');

注意事项和问题排查

在员工表中定义角色时检测到无限递归

失败原因:如果您的政策显示“检查员工表以查看我是否是经理”,数据库必须查询该表以检查该政策,这会再次触发该政策。结果:检测到无限递归。修复方法:始终保留单独的对照表 (user_roles),或使用实际的数据库用户作为角色。

验证数据

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

5. 启用和强制执行安全性

现在,我们打开防护盾。我们还将创建一个通用“应用用户”,我们的 Python 代码将使用该用户进行连接。

从 AlloyDB 查询编辑器运行以下 SQL 语句:

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

注意事项和问题排查

以 postgres(超级用户)身份进行测试,并查看所有数据。

失败原因: 默认情况下,RLS 不适用于表所有者或超级用户。他们会绕过所有政策。问题排查: 如果您的政策看起来“已损坏”(允许所有内容),请检查您是否以 postgres 身份登录。修复方法FORCE ROW LEVEL SECURITY 命令可确保 即使是所有者 也要遵守规则。这对于测试至关重要。

6. 创建访问权限政策

我们将使用会话变量 (app.active_user) 定义两个规则,稍后我们将从应用代码中设置该变量。

从 AlloyDB 查询编辑器运行以下 SQL 语句:

-- 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'
    )
);

注意事项和问题排查

使用 current_user 而不是 app.active_user。

问题: Current_user 是一个保留的 SQL 关键字,用于返回数据库角色(例如 app_user)。我们需要应用用户(例如 Alice)。修复方法:始终使用自定义命名空间,例如 app.variable_name

忘记 current_setting 中的 true 参数。

问题: 如果未设置变量,查询会崩溃并显示错误。修复方法: current_setting('...', true) 会返回 NULL 而不是崩溃,这会安全地导致返回 0 行。

7. 构建“变色龙”应用

我们将使用 Python 和 Streamlit 来模拟应用逻辑。

296a980887b5c700.png

在编辑器模式下打开 Cloud Shell 终端,导航到根文件夹或要创建此应用的目录。创建新文件夹。

1. 安装依赖项

在 Cloud Shell 终端中,在新的项目目录中运行以下命令:

pip install streamlit psycopg2-binary

**2. 创建 app.py

创建一个名为 app.py 的新文件,然后从 仓库文件 中复制内容。

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. 运行应用

在 Cloud Shell 终端中,在新的项目目录中运行以下命令:

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

注意事项和问题排查

连接池。

风险:如果您使用连接池,会话变量 SET app.active_user 可能会在连接中保留,并“泄露”给获取该连接的下一个用户。修复方法:在生产环境中,将连接返回到池时,请始终使用 RESET app.active_user 或 DISCARD ALL。

Cloud Shell 中的空白屏幕。

修复方法: 使用端口 8080 上的“网页预览”按钮。请勿点击终端中的 localhost 链接。

8. 验证零信任

试用该应用以确保零信任架构实现:

选择 “Alice”:她应该看到 1 行(她自己)。

b3b7e374fa66ac87.png

选择 “Bob”:他应该看到 3 行(所有人)。

fdc65cb1acdee8a4.png

为什么这对 AI 代理很重要

假设您将模型连接到此数据库。如果用户向您的模型询问“总结所有绩效考核”,它将生成 SELECT performance_review FROM employees。

  1. 没有 RLS:您的模型会检索所有人的私人考核,并将其泄露给 Alice。
  2. 使用 RLS:您的模型运行完全相同的查询,但数据库仅返回 Alice 的考核。

这就是零信任 AI。您不信任模型来过滤数据;您强制数据库隐藏数据。

将此架构投入生产环境

此处演示的架构是生产级架构,但具体实现经过简化,以便于学习。如需在实际企业环境中安全部署此架构,您应实现以下增强功能:

  1. 真实身份验证: 将“身份切换器”下拉列表替换为强大的身份提供方 (IDP),例如 Google Identity Platform、Okta 或 Auth0。您的应用应验证用户的令牌并安全地提取其身份,然后再设置数据库会话变量,确保用户无法伪造自己的身份。
  2. 连接池安全性: 使用连接池时,如果处理不当,会话变量有时可能会在不同的用户请求之间保留。确保您的应用在将连接返回到池时重置会话变量(例如 RESET app.active_user)或清除连接状态,以防止用户之间的数据泄露。
  3. Secret 管理: 对数据库凭据进行硬编码存在安全风险。使用专用 Secret Manager 服务(例如 Google Secret Manager)在运行时安全地存储和检索数据库密码和连接字符串。

9. 清理

完成此实验后,请勿忘记删除 alloyDB 集群和实例。

它应清理集群及其实例。

10. 恭喜

恭喜!您已成功将安全性下推到数据层。即使您的 Python 代码存在尝试 print(all_salaries) 的错误,数据库也不会向 Alice 返回任何内容。

后续步骤