خزانه خصوصی: ساخت «هوشمندی بدون اعتماد» با امنیت سطح ردیف AlloyDB

۱. مرور کلی

در عجله برای ساخت برنامه‌های GenAI، ما اغلب مهم‌ترین جزء را فراموش می‌کنیم: ایمنی.

تصور کنید که یک چت‌بات منابع انسانی می‌سازید. می‌خواهید به سوالاتی مانند «حقوق من چقدر است؟» یا «عملکرد تیم من چگونه است؟» پاسخ دهد.

  • اگر آلیس (یک کارمند معمولی) درخواست کند، او فقط باید داده‌های خودش را ببیند.
  • اگر باب (یک مدیر) درخواست کند، باید داده‌های تیمش را ببیند.

مشکل

اکثر معماری‌های RAG (بازیابی نسل افزوده) سعی می‌کنند این مشکل را در لایه کاربرد مدیریت کنند. آن‌ها تکه‌ها را پس از بازیابی فیلتر می‌کنند، یا برای "رفتار" به LLM متکی هستند. این روش شکننده است. اگر منطق برنامه با مشکل مواجه شود، داده‌ها نشت می‌کنند.

راه حل

امنیت را به لایه پایگاه داده منتقل کنید. با استفاده از PostgreSQL Row-Level Security (RLS) در AlloyDB ، اطمینان حاصل می‌کنیم که پایگاه داده از نظر فیزیکی از بازگرداندن داده‌هایی که کاربر مجاز به دیدن آنها نیست، خودداری می‌کند - صرف نظر از اینکه هوش مصنوعی چه چیزی را درخواست می‌کند.

در این راهنما، ما «خزانه‌ی خصوصی» را خواهیم ساخت: یک دستیار منابع انسانی امن که به صورت پویا پاسخ‌های خود را بر اساس اینکه چه کسی وارد سیستم شده است تغییر می‌دهد.

1e095ac5fe069bb6.png

معماری

ما در پایتون منطق پیچیده‌ی مجوزها را نمی‌سازیم. ما از خود موتور پایگاه داده استفاده می‌کنیم.

  1. رابط کاربری: یک اپلیکیشن ساده‌ی Streamlit که ورود به سیستم را شبیه‌سازی می‌کند.
  2. مغز: هوش مصنوعی AlloyDB (سازگار با PostgreSQL).
  3. مکانیزم: ما یک متغیر جلسه ( app.active_user ) را در ابتدای هر تراکنش تنظیم می‌کنیم. سیاست‌های پایگاه داده به طور خودکار جدول user_roles (که به عنوان ارائه دهنده هویت ما عمل می‌کند) را برای فیلتر کردن ردیف‌ها بررسی می‌کنند.

آنچه خواهید ساخت

یک برنامه دستیار منابع انسانی امن. به جای تکیه بر منطق برنامه برای فیلتر کردن داده‌های حساس، شما امنیت سطح ردیف (RLS) را مستقیماً در موتور پایگاه داده AlloyDB پیاده‌سازی خواهید کرد. این تضمین می‌کند که حتی اگر مدل هوش مصنوعی شما "توهم" کند یا سعی در دسترسی به داده‌های غیرمجاز داشته باشد، پایگاه داده از بازگشت فیزیکی آن خودداری خواهد کرد.

آنچه یاد خواهید گرفت

شما یاد خواهید گرفت:

  • نحوه طراحی یک طرحواره برای RLS (جداسازی داده‌ها در مقابل هویت).
  • نحوه نوشتن سیاست‌های PostgreSQL ( CREATE POLICY )
  • نحوه دور زدن معافیت "مالک جدول" با استفاده از FORCE ROW LEVEL SECURITY .
  • چگونه یک برنامه پایتون بسازیم که "تغییر زمینه" را برای کاربران انجام دهد.

الزامات

  • یک مرورگر، مانند کروم یا فایرفاکس .
  • یک پروژه گوگل کلود با قابلیت پرداخت.
  • دسترسی به Cloud Shell یا ترمینالی که gcloud و psql روی آن نصب شده باشد.

۲. قبل از شروع

ایجاد یک پروژه

  1. در کنسول گوگل کلود ، در صفحه انتخاب پروژه، یک پروژه گوگل کلود را انتخاب یا ایجاد کنید.
  2. مطمئن شوید که صورتحساب برای پروژه ابری شما فعال است. یاد بگیرید که چگونه بررسی کنید که آیا صورتحساب در یک پروژه فعال است یا خیر .
  1. شما از Cloud Shell ، یک محیط خط فرمان که در Google Cloud اجرا می‌شود، استفاده خواهید کرد. روی Activate Cloud Shell در بالای کنسول Google Cloud کلیک کنید.

تصویر دکمه فعال کردن Cloud Shell

  1. پس از اتصال به Cloud Shell، با استفاده از دستور زیر بررسی می‌کنید که آیا از قبل احراز هویت شده‌اید و پروژه روی شناسه پروژه شما تنظیم شده است یا خیر:
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 اجرا کردید، اما در واقع در رابط کاربری کنسول به پروژه‌ی دیگری نگاه می‌کنید. شناسه‌ی پروژه را در منوی کشویی بالا سمت چپ بررسی کنید!

سنگر بیلینگ

شما پروژه را فعال کردید، اما حساب صورتحساب را فراموش کردید. AlloyDB یک موتور با کارایی بالا است؛ اگر "مخزن بنزین" (صورتحساب) خالی باشد، روشن نمی‌شود.

تأخیر انتشار API

شما روی «فعال کردن APIها» کلیک کرده‌اید، اما خط فرمان هنوز می‌گوید Service Not Enabled . ۶۰ ثانیه به آن فرصت دهید. ابر به یک لحظه نیاز دارد تا نورون‌های خود را بیدار کند.

کواگ‌های سهمیه‌ای

اگر از یک حساب آزمایشی کاملاً جدید استفاده می‌کنید، ممکن است به سهمیه منطقه‌ای برای نمونه‌های AlloyDB برسید. اگر us-central1 با شکست مواجه شد، us-east1 امتحان کنید.

۳. راه‌اندازی پایگاه داده

در این آزمایش، ما از AlloyDB به عنوان پایگاه داده برای داده‌های آزمایشی استفاده خواهیم کرد. این پایگاه داده از خوشه‌ها برای نگهداری تمام منابع، مانند پایگاه‌های داده و گزارش‌ها، استفاده می‌کند. هر خوشه یک نمونه اصلی دارد که یک نقطه دسترسی به داده‌ها را فراهم می‌کند. جداول، داده‌های واقعی را نگهداری می‌کنند.

بیایید یک کلاستر، نمونه و جدول AlloyDB ایجاد کنیم که مجموعه داده‌های آزمایشی در آن بارگذاری شوند.

  1. روی دکمه کلیک کنید یا لینک زیر را در مرورگر خود که کاربر Google Cloud Console در آن وارد شده است، کپی کنید.

  1. پس از اتمام این مرحله، مخزن در ویرایشگر پوسته ابری محلی شما کلون می‌شود و می‌توانید دستور زیر را از پوشه پروژه اجرا کنید (مهم است که مطمئن شوید در دایرکتوری پروژه هستید):
sh run.sh
  1. حالا از رابط کاربری استفاده کنید (با کلیک روی لینک در ترمینال یا کلیک روی لینک «پیش‌نمایش در وب» در ترمینال).
  2. برای شروع، اطلاعات مربوط به شناسه پروژه، نام کلاستر و نمونه را وارد کنید.
  3. در حالی که گزارش‌ها در حال پیمایش هستند، یک قهوه بنوشید و می‌توانید در اینجا در مورد نحوه انجام این کار در پشت صحنه بخوانید. ممکن است حدود ۱۰ تا ۱۵ دقیقه طول بکشد.

اشکالات و عیب‌یابی

مشکل «صبر»

خوشه‌های پایگاه داده زیرساخت‌های سنگینی هستند. اگر صفحه را رفرش کنید یا جلسه Cloud Shell را به دلیل «گیر کردن» از بین ببرید، ممکن است در نهایت با یک نمونه «شبح» مواجه شوید که تا حدی آماده شده و حذف آن بدون مداخله دستی غیرممکن است.

عدم تطابق منطقه

اگر APIهای خود را در us-central1 فعال کرده‌اید اما سعی می‌کنید کلاستر را در asia-south1 آماده‌سازی کنید، ممکن است با مشکلات سهمیه‌بندی یا تأخیر در مجوز حساب سرویس مواجه شوید. در کل آزمایش به یک منطقه پایبند باشید!

خوشه‌های زامبی

اگر قبلاً از نام یکسانی برای یک خوشه استفاده کرده باشید و آن را حذف نکرده باشید، ممکن است اسکریپت بگوید که نام خوشه از قبل وجود دارد. نام خوشه‌ها باید در یک پروژه منحصر به فرد باشند.

مهلت زمانی پوسته ابری

اگر زمان استراحت قهوه شما 30 دقیقه طول بکشد، ممکن است Cloud Shell به حالت خواب برود و فرآیند sh run.sh قطع کند. تب را فعال نگه دارید!

۴. تأمین طرحواره

در این مرحله، موارد زیر را پوشش خواهیم داد:

d05d7d2706c689dc.png

در اینجا اقدامات گام به گام با جزئیات آمده است:

پس از اجرای کلاستر و نمونه AlloyDB، به ویرایشگر SQL در AlloyDB Studio بروید تا افزونه‌های هوش مصنوعی را فعال کرده و طرحواره را آماده کنید.

1e3ac974b18a8113.png

ممکن است لازم باشد منتظر بمانید تا نمونه شما به طور کامل ایجاد شود. پس از اتمام این کار، با استفاده از اعتبارنامه‌هایی که هنگام ایجاد خوشه ایجاد کرده‌اید، وارد AlloyDB شوید. از داده‌های زیر برای تأیید اعتبار در PostgreSQL استفاده کنید:

  • نام کاربری: " postgres "
  • پایگاه داده: " postgres "
  • رمز عبور: " alloydb " (یا هر چیزی که در زمان ایجاد تعیین کرده‌اید)

پس از اینکه با موفقیت در AlloyDB Studio احراز هویت شدید، دستورات SQL در ویرایشگر وارد می‌شوند. می‌توانید با استفاده از علامت + در سمت راست آخرین پنجره، چندین پنجره ویرایشگر اضافه کنید.

۲۸cb9a8b6aa0789f.png

شما می‌توانید دستورات AlloyDB را در پنجره‌های ویرایشگر وارد کنید و در صورت لزوم از گزینه‌های Run، Format و Clear استفاده کنید.

ایجاد یک جدول

ما به دو جدول نیاز داریم: یکی برای داده‌های حساس (کارمندان) و یکی برای قوانین هویت (user_roles). جداسازی آنها برای جلوگیری از خطاهای "بازگشت نامحدود" در سیاست‌ها بسیار مهم است.

شما می‌توانید با استفاده از دستور DDL زیر در 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.');

اشکالات و عیب‌یابی

هنگام تعریف نقش‌ها در جدول کارمندان، بازگشت بی‌نهایت شناسایی شد

چرا شکست می‌خورد: اگر سیاست شما بگوید «جدول کارمندان را بررسی کنید تا ببینید آیا من مدیر هستم یا خیر»، پایگاه داده باید جدول را برای بررسی سیاست جستجو کند، که این امر دوباره سیاست را فعال می‌کند. نتیجه: بازگشت نامتناهی شناسایی شد. راه حل: همیشه یک جدول جستجوی جداگانه (user_roles) نگه دارید یا از کاربران واقعی پایگاه داده برای نقش‌ها استفاده کنید.

داده‌ها را تأیید کنید:

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

۵. فعال‌سازی و اجرای امنیت

حالا سپرها را روشن می‌کنیم. همچنین یک "کاربر برنامه" عمومی ایجاد خواهیم کرد که کد پایتون ما برای اتصال از آن استفاده خواهد کرد.

دستور SQL زیر را از ویرایشگر کوئری 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;

اشکالات و عیب‌یابی

تست به عنوان postgres (Superuser) و مشاهده تمام داده‌ها.

چرا از کار می‌افتد: به طور پیش‌فرض، RLS برای مالک جدول یا کاربران ارشد اعمال نمی‌شود. آنها همه خط‌مشی‌ها را دور می‌زنند. عیب‌یابی: اگر خط‌مشی‌های شما "خراب" به نظر می‌رسند (همه چیز را مجاز می‌دانند)، بررسی کنید که آیا با نام postgres وارد سیستم شده‌اید یا خیر. راه حل: دستور FORCE ROW LEVEL SECURITY تضمین می‌کند که حتی مالک جدول نیز تابع قوانین است. این برای آزمایش بسیار حیاتی است.

۶. ایجاد سیاست‌های دسترسی

ما دو قانون را با استفاده از یک متغیر Session (app.active_user) تعریف خواهیم کرد که بعداً از کد برنامه خود تنظیم خواهیم کرد.

دستور SQL زیر را از ویرایشگر کوئری 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'
    )
);

اشکالات و عیب‌یابی

استفاده از current_user به جای app.active_user.

مشکل: Current_user یک کلمه کلیدی SQL رزرو شده است که نقش پایگاه داده را برمی‌گرداند (مثلاً app_user). ما به کاربر برنامه نیاز داریم (مثلاً Alice). راه حل: همیشه از یک فضای نام سفارشی مانند app.variable_name استفاده کنید .

فراموش کردن پارامتر true در current_setting .

مشکل: اگر متغیر تنظیم نشده باشد، کوئری با خطا از کار می‌افتد. رفع مشکل: current_setting('...', true) به جای از کار افتادن، مقدار NULL را برمی‌گرداند که در حالت ایمن منجر به برگرداندن 0 ردیف می‌شود.

۷. اپلیکیشن «آفتاب‌پرست» را بسازید

ما از پایتون و Streamlit برای شبیه‌سازی منطق برنامه استفاده خواهیم کرد.

296a980887b5c700.png

ترمینال Cloud Shell را در حالت ویرایشگر باز کنید، به پوشه ریشه یا دایرکتوری که می‌خواهید این برنامه را در آن ایجاد کنید بروید. یک پوشه جدید ایجاد کنید.

۱. نصب وابستگی‌ها:

دستور زیر را در ترمینال Cloud Shell از داخل دایرکتوری پروژه جدید خود اجرا کنید:

pip install streamlit psycopg2-binary

۲. فایل app.py را ایجاد کنید:

یک فایل جدید با نام app.py ایجاد کنید و محتوای آن را از فایل 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)

۳. برنامه را اجرا کنید:

دستور زیر را در ترمینال Cloud Shell از داخل دایرکتوری پروژه جدید خود اجرا کنید:

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

اشکالات و عیب‌یابی

تجمیع اتصال.

خطر: اگر از یک مخزن اتصال استفاده می‌کنید، متغیر جلسه SET app.active_user ممکن است روی اتصال باقی بماند و به کاربر بعدی که آن اتصال را دریافت می‌کند، "نشت" کند. راه حل: در محیط عملیاتی، همیشه هنگام بازگرداندن یک اتصال به مخزن، از RESET app.active_user یا DISCARD ALL استفاده کنید.

صفحه خالی در Cloud Shell.

راه حل: از دکمه «پیش‌نمایش وب» روی پورت ۸۰۸۰ استفاده کنید. روی لینک localhost در ترمینال کلیک نکنید.

۸. تأیید صفر اعتماد

برای اطمینان از پیاده‌سازی Zero Trust، برنامه را امتحان کنید:

«آلیس» را انتخاب کنید: او باید ۱ ردیف (خودش) را ببیند.

b3b7e374fa66ac87.png

«باب» را انتخاب کنید: او باید ۳ ردیف (همه) را ببیند.

fdc65cb1acdee8a4.png

چرا این موضوع برای عوامل هوش مصنوعی اهمیت دارد؟

تصور کنید که مدل خود را به این پایگاه داده متصل می‌کنید. اگر کاربری از مدل شما بپرسد: "تمام بررسی‌های عملکرد را خلاصه کن"، مدل عبارت SELECT performance_review FROM employees را تولید خواهد کرد.

  1. بدون RLS: مدل شما نظرات خصوصی همه را بازیابی کرده و آنها را به آلیس ارسال می‌کند.
  2. با RLS: مدل شما دقیقاً همان پرس‌وجو را اجرا می‌کند، اما پایگاه داده فقط نظر آلیس را برمی‌گرداند.

این هوش مصنوعیِ بدون اعتماد (Zero-Trust) است. شما به مدل برای فیلتر کردن داده‌ها اعتماد ندارید؛ شما پایگاه داده را مجبور می‌کنید که آنها را پنهان کند.

بردن این به مرحله تولید

معماری نشان داده شده در اینجا در سطح تولید است، اما پیاده‌سازی خاص آن برای یادگیری ساده‌سازی شده است. برای استقرار ایمن این معماری در یک محیط سازمانی واقعی، باید پیشرفت‌های زیر را پیاده‌سازی کنید:

  1. احراز هویت واقعی: منوی کشویی «Identity Switcher» را با یک ارائه‌دهنده هویت (IDP) قوی مانند Google Identity Platform، Okta یا Auth0 جایگزین کنید. برنامه شما باید توکن کاربر را تأیید کند و هویت او را قبل از تنظیم متغیر جلسه پایگاه داده، به طور ایمن استخراج کند و اطمینان حاصل کند که کاربران نمی‌توانند هویت خود را جعل کنند.
  2. ایمنی در جمع‌آوری اتصالات: هنگام استفاده از مخازن اتصال، متغیرهای جلسه (session variables) در صورت عدم مدیریت صحیح، گاهی اوقات می‌توانند در درخواست‌های مختلف کاربران باقی بمانند. اطمینان حاصل کنید که برنامه شما هنگام بازگرداندن اتصال به مخزن، متغیر جلسه (session variable) را مجدداً تنظیم می‌کند (مثلاً RESET app.active_user) یا وضعیت اتصال را پاک می‌کند تا از نشت داده‌ها بین کاربران جلوگیری شود.
  3. مدیریت مخفی: کدگذاری سخت اعتبارنامه‌های پایگاه داده یک ریسک امنیتی است. از یک سرویس مدیریت مخفی اختصاصی مانند Google Secret Manager برای ذخیره و بازیابی ایمن رمزهای عبور و رشته‌های اتصال پایگاه داده خود در زمان اجرا استفاده کنید.

۹. تمیز کردن

پس از انجام این آزمایش، فراموش نکنید که کلاستر و نمونه alloyDB را حذف کنید.

باید کلاستر را به همراه نمونه(های) آن پاکسازی کند.

۱۰. تبریک

تبریک! شما با موفقیت امنیت را به لایه داده منتقل کرده‌اید. حتی اگر کد پایتون شما باگی داشته باشد که سعی در print(all_salaries) داشته باشد، پایگاه داده چیزی به آلیس برنمی‌گرداند.

مراحل بعدی