Prywatny magazyn: tworzenie „inteligencji opartej na zasadzie zero zaufania” za pomocą zabezpieczeń na poziomie wiersza w AlloyDB

1. Przegląd

W pośpiechu związanym z tworzeniem aplikacji opartych na generatywnej AI często zapominamy o najważniejszym elemencie: bezpieczeństwie.

Wyobraź sobie, że tworzysz czatbota HR. Chcesz, aby odpowiadał na pytania takie jak „Ile zarabiam?” lub „Jak radzi sobie mój zespół?”.

  • Jeśli zapyta Alicja (zwykły pracownik), powinna zobaczyć tylko swoje dane.
  • Jeśli Bob (menedżer) zapyta, powinien zobaczyć dane swojego zespołu.

Problem

Większość architektur RAG (Retrieval Augmented Generation) próbuje sobie z tym poradzić w warstwie aplikacji. Filtrują one fragmenty po ich pobraniu lub polegają na „zachowaniu” modelu LLM. To jest delikatne. Jeśli logika aplikacji zawiedzie, dane wyciekną.

Rozwiązanie

Przenieś zabezpieczenia do warstwy bazy danych. Dzięki zastosowaniu w AlloyDB funkcji bezpieczeństwa na poziomie wiersza (RLS) w PostgreSQL zapewniamy, że baza danych fizycznie odmawia zwrócenia danych, do których użytkownik nie ma dostępu – niezależnie od tego, o co poprosi AI.

W tym przewodniku stworzymy „Prywatny skarbiec”, czyli bezpiecznego asystenta HR, który dynamicznie zmienia odpowiedzi w zależności od tego, kto jest zalogowany.

1e095ac5fe069bb6.png

Architektura

Nie tworzymy w Pythonie złożonej logiki uprawnień. Używamy samego silnika bazy danych.

  1. Interfejs: prosta aplikacja Streamlit symulująca logowanie.
  2. The Brain: AlloyDB AI (zgodny z PostgreSQL).
  3. Mechanizm: na początku każdej transakcji ustawiamy zmienną sesji (app.active_user). Zasady bazy danych automatycznie sprawdzają tabelę user_roles (pełniącą rolę dostawcy tożsamości), aby filtrować wiersze.

Co utworzysz

bezpieczną aplikację Asystent HR, Zamiast polegać na logice aplikacji w zakresie filtrowania danych wrażliwych, możesz wdrożyć zabezpieczenia na poziomie wiersza (RLS) bezpośrednio w silniku bazy danych AlloyDB. Dzięki temu nawet jeśli model AI „halucynuje” lub próbuje uzyskać dostęp do nieautoryzowanych danych, baza danych fizycznie odmówi ich zwrócenia.

Czego się nauczysz

Dowiesz się:

  • Jak zaprojektować schemat na potrzeby RLS (oddzielenie danych od tożsamości).
  • Jak pisać zasady PostgreSQL (CREATE POLICY).
  • Jak ominąć wyłączenie „Właściciel tabeli” za pomocą FORCE ROW LEVEL SECURITY.
  • Jak utworzyć aplikację w Pythonie, która wykonuje „przełączanie kontekstu” dla użytkowników.

Wymagania

  • przeglądarka, np. Chrome lub Firefox;
  • Projekt Google Cloud z włączonymi płatnościami.
  • Dostęp do Cloud Shell lub terminala z zainstalowanymi narzędziami gcloud i psql.

2. Zanim zaczniesz

Utwórz projekt

  1. W konsoli Google Cloud na stronie selektora projektów wybierz lub utwórz projekt Google Cloud.
  2. Sprawdź, czy w projekcie Cloud włączone są płatności. Dowiedz się, jak sprawdzić, czy w projekcie włączone są płatności.
  1. Będziesz używać Cloud Shell, czyli środowiska wiersza poleceń działającego w Google Cloud. U góry konsoli Google Cloud kliknij Aktywuj Cloud Shell.

Obraz przycisku aktywacji Cloud Shell

  1. Po połączeniu z Cloud Shell sprawdź, czy jesteś już uwierzytelniony i czy projekt jest ustawiony na Twój identyfikator projektu, używając tego polecenia:
gcloud auth list
  1. Aby potwierdzić, że polecenie gcloud zna Twój projekt, uruchom w Cloud Shell to polecenie:
gcloud config list project
  1. Jeśli projekt nie jest ustawiony, użyj tego polecenia, aby go ustawić:
gcloud config set project <YOUR_PROJECT_ID>
  1. Włącz wymagane interfejsy API: kliknij link i włącz interfejsy API.

Możesz też użyć polecenia gcloud. Informacje o poleceniach gcloud i ich użyciu znajdziesz w dokumentacji.

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

Pułapki i rozwiązywanie problemów

Syndrom „projektu widma”

Uruchomiono polecenie gcloud config set project, ale w interfejsie konsoli wyświetlany jest inny projekt. Sprawdź identyfikator projektu w menu w lewym górnym rogu.

Bariera rozliczeniowa

Projekt został włączony, ale zapomniano o koncie rozliczeniowym. AlloyDB to wydajny mechanizm, który nie uruchomi się, jeśli „zbiornik paliwa” (płatności) jest pusty.

Opóźnienie propagacji interfejsu API

Kliknięto „Włącz interfejsy API”, ale w wierszu poleceń nadal widnieje symbol Service Not Enabled. Odczekaj 60 sekund. Chmura potrzebuje chwili, aby aktywować swoje neurony.

Limit Quags

Jeśli korzystasz z nowego konta próbnego, możesz osiągnąć regionalny limit instancji AlloyDB. Jeśli us-central1 się nie powiedzie, spróbuj us-east1.

3. Konfiguracja bazy danych

W tym module użyjemy AlloyDB jako bazy danych do przechowywania danych testowych. Używa klastrów do przechowywania wszystkich zasobów, takich jak bazy danych i logi. Każdy klaster ma instancję podstawową, która zapewnia punkt dostępu do danych. Tabele będą zawierać rzeczywiste dane.

Utwórzmy klaster, instancję i tabelę AlloyDB, do których zostanie załadowany testowy zbiór danych.

  1. Kliknij przycisk lub skopiuj poniższy link do przeglądarki, w której zalogowany jest użytkownik konsoli Google Cloud.

  1. Po wykonaniu tego kroku repozytorium zostanie sklonowane do lokalnego edytora Cloud Shell i będziesz mieć możliwość uruchomienia poniższego polecenia z folderu projektu (ważne jest, aby upewnić się, że jesteś w katalogu projektu):
sh run.sh
  1. Teraz użyj interfejsu (kliknij link w terminalu lub link „Podgląd w internecie” w terminalu).
  2. Aby rozpocząć, wpisz szczegóły identyfikatora projektu, klastra i nazw instancji.
  3. Idź po kawę, podczas gdy dzienniki będą się przewijać. Tutaj możesz przeczytać, jak to działa w tle. Może to potrwać około 10–15 minut.

Pułapki i rozwiązywanie problemów

Problem z „cierpliwością”

Klastry baz danych to rozbudowana infrastruktura. Jeśli odświeżysz stronę lub zakończysz sesję Cloud Shell, ponieważ „utknęła”, możesz skończyć z „duchem” instancji, która jest częściowo udostępniona i niemożliwa do usunięcia bez ręcznej interwencji.

Niezgodny region

Jeśli interfejsy API zostały włączone w regionie us-central1, ale klaster jest wdrażany w regionie asia-south1, mogą wystąpić problemy z limitami lub opóźnienia w przypisywaniu uprawnień do konta usługi. W całym module trzymaj się jednego regionu.

Zombie Clusters

Jeśli nazwa klastra była już wcześniej używana i nie została usunięta, skrypt może wyświetlić komunikat, że nazwa klastra już istnieje. Nazwy klastrów muszą być unikalne w projekcie.

Limit czasu Cloud Shell

Jeśli przerwa na kawę trwa 30 minut, Cloud Shell może przejść w stan uśpienia i odłączyć proces sh run.sh. Pozostaw kartę aktywną.

4. Obsługa administracyjna schematu

W tym kroku omówimy te kwestie:

d05d7d2706c689dc.png

Aby to zrobić:

Gdy klaster i instancja AlloyDB będą działać, przejdź do edytora SQL w AlloyDB Studio, aby włączyć rozszerzenia AI i udostępnić schemat.

1e3ac974b18a8113.png

Może być konieczne poczekanie na zakończenie tworzenia instancji. Gdy to zrobisz, zaloguj się w AlloyDB przy użyciu danych logowania utworzonych podczas tworzenia klastra. Do uwierzytelniania w PostgreSQL użyj tych danych:

  • Nazwa użytkownika: „postgres
  • Baza danych: „postgres
  • Hasło: „alloydb” (lub inne hasło ustawione podczas tworzenia)

Po pomyślnym uwierzytelnieniu w AlloyDB Studio polecenia SQL są wpisywane w Edytorze. Możesz dodać wiele okien Edytora, klikając znak plusa po prawej stronie ostatniego okna.

28cb9a8b6aa0789f.png

Polecenia dla AlloyDB będziesz wpisywać w oknach edytora, używając w razie potrzeby opcji Uruchom, Formatuj i Wyczyść.

Tworzenie tabeli

Potrzebujemy 2 tabel: jednej z danymi wrażliwymi (pracownicy) i jednej z regułami tożsamości (user_roles). Rozdzielenie tych elementów jest kluczowe, aby uniknąć błędów „Infinite Recursion” w zasadach.

Tabelę możesz utworzyć za pomocą instrukcji DDL poniżej w 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.');

Pułapki i rozwiązywanie problemów

Wykryto nieskończoną rekurencję podczas definiowania ról w tabeli pracowników

Przyczyna błędu: jeśli zasada brzmi „Sprawdź w tabeli pracowników, czy jestem menedżerem”, baza danych musi wysłać zapytanie do tabeli, aby sprawdzić zasadę, co ponownie ją wywołuje.Wynik: wykryto nieskończoną rekurencję.Rozwiązanie: zawsze używaj oddzielnej tabeli wyszukiwania (user_roles) lub rzeczywistych użytkowników bazy danych do określania ról.

Sprawdź dane:

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

5. Włączanie i wymuszanie zabezpieczeń

Teraz włączamy osłony. Utworzymy też ogólnego „Użytkownika aplikacji”, którego nasz kod Pythona będzie używać do łączenia się.

Uruchom poniższą instrukcję SQL w edytorze zapytań 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;

Pułapki i rozwiązywanie problemów

Testowanie jako postgres (superużytkownik) i wyświetlanie wszystkich danych.

Przyczyna błędu: domyślnie RLS nie ma zastosowania do właściciela tabeli ani superużytkowników. Omijają one wszystkie zasady.Rozwiązywanie problemów: jeśli Twoje zasady wydają się „zepsute” (dopuszczają wszystko), sprawdź, czy jesteś zalogowany(-a) jako postgres.Rozwiązanie: polecenie FORCE ROW LEVEL SECURITY zapewnia, że nawet właściciel podlega zasadom. Jest to niezbędne do testowania.

6. Tworzenie zasad dostępu

Zdefiniujemy 2 reguły za pomocą zmiennej sesji (app.active_user), którą ustawimy później w kodzie aplikacji.

Uruchom poniższą instrukcję SQL w edytorze zapytań 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'
    )
);

Pułapki i rozwiązywanie problemów

Używanie zmiennej current_user zamiast app.active_user.

Problem: current_user to zarezerwowane słowo kluczowe SQL, które zwraca rolę bazy danych (np. app_user). Potrzebujemy użytkownika aplikacji (np. Alice).Rozwiązanie: zawsze używaj niestandardowej przestrzeni nazw, np. app.variable_name.

Zapomnienie parametru true w pliku current_setting.

Problem: jeśli zmienna nie jest ustawiona, zapytanie ulega awarii i zwraca błąd.Rozwiązanie: funkcja current_setting('...', true) zwraca wartość NULL zamiast powodować awarię, co bezpiecznie skutkuje zwróceniem 0 wierszy.

7. Tworzenie aplikacji „Kameleon”

Do symulowania logiki aplikacji użyjemy Pythona i Streamlit.

296a980887b5c700.png

Otwórz terminal Cloud Shell w trybie edytora, przejdź do folderu głównego lub do katalogu, w którym chcesz utworzyć tę aplikację. Utwórz nowy folder.

1. Zainstaluj zależności:

Uruchom to polecenie w terminalu Cloud Shell w katalogu nowego projektu:

pip install streamlit psycopg2-binary

2. Utwórz plik app.py:

Utwórz nowy plik o nazwie app.py i skopiuj do niego zawartość z pliku repozytorium.

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. Uruchom aplikację:

Uruchom to polecenie w terminalu Cloud Shell w katalogu nowego projektu:

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

Pułapki i rozwiązywanie problemów

Agregacja połączeń.

Ryzyko: jeśli używasz puli połączeń, zmienna sesji SET app.active_user może być zachowywana w połączeniu i „wyciekać” do następnego użytkownika, który skorzysta z tego połączenia.Rozwiązanie: w środowisku produkcyjnym zawsze używaj polecenia RESET app.active_user lub DISCARD ALL, gdy zwracasz połączenie do puli.

Pusty ekran w Cloud Shell.

Rozwiązanie: użyj przycisku „Podgląd w przeglądarce” na porcie 8080. Nie klikaj linku localhost w terminalu.

8. Weryfikacja modelu „zero zaufania”

Wypróbuj aplikację, aby sprawdzić, czy wdrożenie modelu „zero zaufania” przebiegło prawidłowo:

Wybierz „Alice”: powinna zobaczyć 1 wiersz (swoje dane).

b3b7e374fa66ac87.png

Wybierz „Bob”: powinien zobaczyć 3 wiersze (Wszyscy).

fdc65cb1acdee8a4.png

Dlaczego jest to ważne w przypadku agentów AI

Wyobraź sobie, że łączysz model z tą bazą danych. Jeśli użytkownik zapyta model: „Podsumuj wszystkie oceny wyników”, wygeneruje on zapytanie SELECT performance_review FROM employees.

  1. Bez RLS: model pobiera prywatne opinie wszystkich użytkowników i ujawnia je Alicji.
  2. W przypadku RLS: model uruchamia dokładnie to samo zapytanie, ale baza danych zwraca tylko opinię Alicji.

To jest AI oparta na modelu „zero zaufania”. Nie ufasz modelowi, że odfiltruje dane, więc wymuszasz na bazie danych ich ukrycie.

Wdrażanie w środowisku produkcyjnym

Przedstawiona tu architektura jest przeznaczona do użytku produkcyjnego, ale konkretne wdrożenie zostało uproszczone na potrzeby szkoleniowe. Aby bezpiecznie wdrożyć to rozwiązanie w prawdziwym środowisku firmowym, należy wprowadzić te ulepszenia:

  1. Prawdziwe uwierzytelnianie: zastąp menu „Przełącznik tożsamości” solidnym dostawcą tożsamości, takim jak Google Identity Platform, Okta lub Auth0. Aplikacja powinna zweryfikować token użytkownika i bezpiecznie wyodrębnić jego tożsamość przed ustawieniem zmiennej sesji bazy danych, aby uniemożliwić użytkownikom podszywanie się pod inne osoby.
  2. Bezpieczeństwo puli połączeń: podczas korzystania z puli połączeń zmienne sesji mogą czasami utrzymywać się w różnych żądaniach użytkowników, jeśli nie są prawidłowo obsługiwane. Upewnij się, że aplikacja resetuje zmienną sesji (np. RESET app.active_user) lub czyści stan połączenia podczas zwracania połączenia do puli, aby zapobiec wyciekowi danych między użytkownikami.
  3. Zarządzanie danymi tajnymi: zakodowanie na stałe danych logowania do bazy danych stwarza zagrożenie dla bezpieczeństwa. Używaj specjalnej usługi zarządzania danymi tajnymi, takiej jak Google Secret Manager, do bezpiecznego przechowywania i pobierania haseł do bazy danych oraz ciągów połączenia w czasie działania programu.

9. Czyszczenie danych

Po ukończeniu tego modułu nie zapomnij usunąć klastra i instancji AlloyDB.

Powinien on wyczyścić klaster wraz z instancjami.

10. Gratulacje

Gratulacje! Udało Ci się przenieść zabezpieczenia na warstwę danych. Nawet jeśli w kodzie w języku Python wystąpił błąd, który spowodował próbę print(all_salaries), baza danych nie zwróci Alicji żadnych informacji.

Następne kroki