透過 Deep Data Security 強制執行一般使用者記憶體隔離

本指南說明如何使用 OCI IAM 和 Oracle Deep Data Security 強制執行 Oracle AI Agent Memory 應用程式的一般使用者隔離。

警告:僅學習環境。請勿將此範例部署至生產環境。

此應用程式使用 Flask 的開發伺服器、記憶體內階段作業存放區、環境檔案加密密碼,以及簡化的權杖生命週期處理。資料庫設定命令檔會修改整個資料庫的識別提供者設定值和權限。只能使用可棄用的使用者和獨立的非生產環境 Oracle Autonomous AI Database。

本指南建置精心設計的小型 Web 應用程式:

路線不會比較愛麗絲與鮑伯本身。它會將一般使用者權杖傳送至 Oracle AI Database,其中 Oracle Deep Data Security 會強制執行代理程式記憶體自有資料列原則。

本指南示範一個部署。如需完整的安全功能 (包括每個支援的原則、主要項目、管理功能、程式實際執行相關資訊環境 API、稽核及撤銷時間),請參閱深資料安全 API 與安全參考。

在本指南中,您將:

瞭解信任鏈

瀏覽器和應用程式使用兩個不同的 OAuth 存取權杖:

  1. 一般使用者記號可識別 Alice 或 Bob,並包含使用者的 IAM 群組。
  2. 由機密應用程式透過「從屬端證明資料」取得的資料庫存取記號,可證明應用程式可以存取資料庫資源。

Oracle AI Database 會先驗證兩個記號,再附加一般使用者安全相關資訊環境。

Oracle 會在瞭解認證流程和先決條件中記錄此雙權杖模型,並在一般使用者安全相關資訊環境中記錄整個生命週期。

先決條件

您需要:

Oracle Deep Data Security 是資料庫強制的微點授權。請參閱什麼是 Oracle Deep Data Security 。

設定 OCI IAM 識別網域

所有 OCI IAM 都在一個識別網域中運作。記錄其網域 URL ,例如 https://idcs-<id>.identity.oraclecloud.com:443。

權威 Oracle 逐步解說是設定 OCI IAM 以進行應用程式導向存取。下列範例中的值與本指南隨附的範例環境檔案相符。

註冊資料庫資源

在識別網域 > 整合式應用程式中:

  1. 新增名為 OracleDB 的機密應用程式。
  2. 將它設定為資源伺服器:
    • 主要受眾:OracleDB
    • 範圍:DB_ACCESS_SCOPE
  3. 將它設定為從屬端並允許從屬端證明資料。
  4. 啟用它。
  5. 記錄其應用程式 ID 、從屬端 ID 和從屬端加密密碼。

完整的資料庫範圍是與範圍名稱串連的對象:

OracleDBDB_ACCESS_SCOPE

請依照在 OCI IAM 中註冊資料庫,瞭解目前的主控台標籤和欄位描述。

註冊 Web 應用程式

新增第二個名為 OracleAgentMemoryWeb 的機密應用程式:

  1. 啟用強制授予作為授權。
  2. 將它設定為資源伺服器:
    • 主要受眾:OracleAgentMemoryWeb
    • 範圍:APP_ACCESS_SCOPE
    • 存取權杖存留時間:此練習的 3600 秒
  3. 將它設定為具有下列授權的從屬端:
    • 授權碼
    • 從屬端證明資料
  4. 新增這個精確重導網址 :
    http://127.0.0.1:8000/auth/callback
  5. 在從屬端資源底下,授與下列兩項存取權:
    • OracleDBDB_ACCESS_SCOPE
    • OracleAgentMemoryWebAPP_ACCESS_SCOPE
  6. 啟用應用程式並記錄其從屬端 ID 和從屬端加密密碼。

應用程式使用授權碼搭配 state 和 PKCE S256。OCI IAM 會在在 OCI IAM 中註冊應用程式中記錄必要的從屬端組態,並提供 PKCE 授權碼範例。

新增群組請款

Oracle AI Database 會從一般使用者的 group 宣告中啟用外部對應的資料角色。OCI IAM 自訂宣告是透過識別網域 REST API (而非主控台) 來設定。

作為識別網域管理員,下載可呼叫識別網域 API 的短期個人存取權杖。然後執行:

export OCI_IAM_DOMAIN_URL='https://<identity-domain-host>:443'
export OCI_IAM_ADMIN_TOKEN='<short-lived-personal-access-token>'

curl --fail-with-body \
  -X POST "$OCI_IAM_DOMAIN_URL/admin/v1/CustomClaims" \
  -H "Authorization: Bearer $OCI_IAM_ADMIN_TOKEN" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": [
      "urn:ietf:params:scim:schemas:oracle:idcs:CustomClaim"
    ],
    "name": "group",
    "value": "$user.groups.*.display",
    "expression": true,
    "mode": "always",
    "tokenType": "AT",
    "allScopes": true
  }'

成功的要求會傳回 HTTP 201。如果網域已有此宣告,請勿建立重複項目。請參閱在 OCI IAM 中設定群組資訊的自訂宣告。

建立 Alice、Bob 與應用程式群組

  1. 建立名為 ORACLEAGENTMEMORY_USERS 的群組。
  2. 建立測試使用者 alice 和 bob。
  3. 將這兩個使用者指派給 ORACLEAGENTMEMORY_USERS。
  4. 將群組指派給 OracleAgentMemoryWeb。

資料庫稍後會將此群組名稱對應至資料角色。群組名稱不區分大小寫,但在整個設定中使用相同的拼字。請參閱在 OCI IAM 中建立使用者和指派群組。

籌備環境

下載完整的 Web 應用程式 deepsec_oci_iam_webapp.zip 。此歸檔還包含此範例擁有的獨立資料庫設定和檢查程序檔。它們不會從 SDK 測試套件匯入或封裝命令檔。

擷取封存、輸入其專案目錄,並建立本機設定與執行時期環境檔案:

unzip deepsec_oci_iam_webapp.zip
cd deepsec_oci_iam_webapp
cp deepsec.env.example .deepsec.env
cp deepsec.runtime.env.example .deepsec.runtime.env
python -c "import secrets; print(secrets.token_hex(32))"

將產生的值放在 OAM_WEB_SECRET_KEY 中,並在兩個檔案中植入每個必要的預留位置。Git 會忽略這兩個檔案名稱。

database_scripts 目錄包含:

檔案 職責
_common.sh 載入 .deepsec.env 的專用協助程式、驗證必要值、檢查 SQL*Plus,以及在設定時匯出 TNS_ADMIN。不要直接執行。
db_ociiam_setup.sh 對目標自治式 AI 資料庫啟用 OCI IAM 外部認證,並取代 OCI_IAM_DOMAIN_DB_CRED$。
db_deepsec_user_setup.sh 建立綱要擁有者和 App-pool 使用者並授予其記錄的權限允許清單。
db_list_all_data_roles.sh 執行 Deep Data Security 角色、IAM 對應、資料授予、述詞、受保護物件、一般使用者和應用程式識別的唯讀檢查。

.deepsec.env 僅供資料庫設定使用。包括安全管理員和綱要擁有者的密碼。.deepsec.runtime.env 是由 Flask 處理作業載入,故意排除授權的密碼。請勿將設定檔案匯出至啟動 Web 應用程式的 Shell。若要使用不同的路徑,請將 OAM_DEEPSEC_ENV_FILE 設為設定,或將 OAM_WEB_ENV_FILE 設為 Web 處理。

重要的 IAM 值對應如下:

環境變量 OCI IAM 值
OAM_DEEPSEC_OCI_DB_APP_ID 資料庫應用程式的應用程式 ID
OAM_DEEPSEC_OCI_DB_CLIENT_ID 資料庫應用程式的從屬端 ID
OAM_DEEPSEC_OCI_DB_CLIENT_SECRET 資料庫應用程式的從屬端加密密碼
OAM_WEB_OCI_CLIENT_ID Web 應用程式的從屬端 ID
OAM_WEB_OCI_CLIENT_SECRET Web 應用程式的從屬端加密密碼
OAM_WEB_OCI_END_USER_SCOPE OracleAgentMemoryWebAPP_ACCESS_SCOPE
OAM_WEB_OCI_DATABASE_ACCESS_SCOPE OracleDBDB_ACCESS_SCOPE

只有當 DSN 是 tnsnames.ora 不能尋找的 TNS 別名時,才需要 OAM_DEEPSEC_CONFIG_DIR。當 sqlnet.ora 和 DSN 已經提供所有 python-oracledb 需求時,公事包位置和密碼是選擇性的。如需「自治式 AI 資料庫」連線選項,請參閱使用公事包連線 Python 應用程式。

也設定內嵌提供者。應用程式只會列出並新增記憶體,但「代理程式記憶體」仍會在儲存記憶體內容時建立內嵌項目。範本使用與 OpenAI 相容的模型作為範例;將模型、API 基礎、索引鍵及維度取代為您的提供者值。

設定三個資料庫帳戶

警告:此段落中的命令會變更整個資料庫的外部認證設定值和帳戶權限。先檢查命令檔。請勿對共用或實際執行資料庫執行它們。

使用三種不同的資料庫使用者:

職責 範例帳戶 允許的工作
安全管理員 ADMIN 啟用 OCI IAM 整合,並建立、授予、撤銷、列出及移除資料角色和資料授予。它永遠不會用於 Web 要求。
受管理綱要擁有者 OAM_SCHEMA_OWNER 建立「代理程式記憶體」表格、索引、程序以及排程程式工作。它只用於綱要週期作業。
應用程式資料庫使用者 OAM_APP_DB_USER 開啟階段作業並附加一般使用者安全相關資訊環境。它不會收到一般的 SELECT、INSERT、UPDATE 或 DELETE 權限。

這樣做會導致沒有有效一般使用者相關資訊環境的要求關閉失敗。Oracle 的資料庫組態指南建議使用 CREATE SESSION 和 CREATE END USER SECURITY CONTEXT 作為連線集區帳戶;請參閱設定 IAM 整合的資料庫。

此範例使用 Autonomous AI Database ADMIN 帳戶進行設定。自訂安全管理員帳戶必須能夠建立及更改兩個資料庫使用者,並授予他們所列的系統權限。因為「代理程式記憶體」表格屬於個別的綱要,所以原則管理需要 CREATE ANY DATA GRANT、DROP ANY DATA GRANT 和 ADMINISTER ANY DATA GRANT,而非只有管理員自己的綱要中的 CREATE DATA GRANT。它也必須經過授權,才能建立和刪除原則所使用的資料角色。

啟用 OCI IAM 權杖驗證

複查與執行:

bash database_scripts/db_ociiam_setup.sh

命令檔會以安全管理員身分連線,並且:

只能有一個作用中的外部身分識別提供者。此範例命令檔使用 force => TRUE 更新 OCI IAM 參數,這可能會中斷其他外部認證組態。Oracle 會記載設定 IAM 整合的資料庫中的確切 Autonomous Database 呼叫。

建立擁有者和集區使用者

複查與執行:

bash database_scripts/db_deepsec_user_setup.sh

程序檔不會變更 ADMIN。它會建立擁有者和應用程式集區使用者並授予:

-- Managed-schema owner
CREATE SESSION, CREATE TABLE, CREATE SEQUENCE,
CREATE VIEW, CREATE PROCEDURE, CREATE JOB

-- Runtime application pool
CREATE SESSION, CREATE END USER SECURITY CONTEXT

擁有者會收到自治式 AI 資料庫 DATA 表格空間的配額。集區使用者不會收到擁有者表格的直接權限。若要讓範例專注於必要的敘述句,命令檔不會檢查現有的使用者或檢查其目前的權限。使用新的帳戶名稱;如果使用者已存在,或其他 SQL 陳述式失敗,指令碼就會停止。

建立商店和自有資料列原則

安裝獨立範例環境:

uv sync

然後執行設定程式:

uv run python scripts/setup_memory.py

此命令僅針對 OAM_WEB_MEMORY_STORE_ID 故意破壞。它:

  1. 以綱要擁有者身分連線以重新建立「代理程式記憶體」綱要;
  2. 以安全管理員身分連線,以新增自己的資料列原則並將其授予 ORACLEAGENTMEMORY_USERS。

新增和授權管理呼叫都是等冪的。重新執行原則會取代相同的受管理原則定義和指派項目,並重試以中斷的嘗試保留部分受管理 DDL。

此原則會為 OCI IAM 群組建立外部對應資料角色,並為其資料列述詞將儲存的 user_id 值與 ORA_END_USER_CONTEXT.username 進行比較的資料授權。Oracle 會記錄 CREATE DATA ROLE 中的對應語法,以及建立資料授權中的資料列授權。

檢查結果:

bash database_scripts/db_list_all_data_roles.sh

您應該會看到 IAM 對應的角色、「代理程式記憶體」資料授權,以及受保護的擁有者綱要表格。此檢驗程序檔只會查詢目錄檢視;不會建立、更改或移除「深層資料安全性」物件。

使用含 Deep Sec 的資料庫內內嵌模型

當應用程式使用含有預設 provider="database" 的 OracleDBEmbedder,以及儲存在 Oracle AI Database 中的內嵌模型 (通常是匯入的 ONNX 模型,例如 DMUSER.DOC_MODEL) 時,會套用此區段。在此組態中,直接內嵌使用 Oracle 的 VECTOR_EMBEDDING SQL 運算子,因此模型需要下列描述的 SELECT ON MINING MODEL 權限。

OracleDBEmbedder 也支援透過 DBMS_VECTOR_CHAIN.UTL_TO_EMBEDDING 的遠端提供者,例如搭配 provider="openai"。這些組態仍然會從 Oracle AI Database 呼叫內嵌要求,但不會存取資料庫常駐探勘模型;本節中的 SELECT ON MINING MODEL 指示則不適用。「代理程式記憶體」作業仍必須在 OracleMemoryEndUserSecurityContext 內部執行。

「代理程式記憶體」原則會授予「代理程式記憶體」表格的存取權。它不會授予 Oracle AI Database 內嵌模型的存取權。模型需要自己的資料庫權限:SELECT ON MINING MODEL。

請勿將該權限授與應用程式集區使用者。在要求期間,Deep Sec 會透過從 OCI IAM 群組對應的資料角色授權使用者,而不是透過集區使用者的一般權限。因此,一般使用者安全相關資訊環境為作用中狀態時,只能授予集區使用者的權限無法使用。

為每個需要使用該模型的 IAM 群組建立一個對應的資料角色。賦予該資料角色具有模型存取權的一般資料庫角色。以資料庫安全性管理員或其他授權建立角色並授予模型存取權的帳戶身分,執行下列 SQL。以應用程式的名稱取代範例名稱:

-- This must exactly match the OCI IAM group passed to OciGroupPrincipal.
CREATE DATA ROLE APP_MEMORY_USERS_DATA_ROLE
  MAPPED TO 'IAM_OAUTH_GROUP=YOUR_IAM_GROUP';

-- This ordinary database role carries access to one embedding model.
CREATE ROLE APP_DOC_MODEL_ROLE;

GRANT SELECT ON MINING MODEL YOUR_USER.DOC_MODEL
  TO APP_DOC_MODEL_ROLE;

-- Give model access to the IAM group's Deep Sec data role.
GRANT APP_DOC_MODEL_ROLE TO APP_MEMORY_USERS_DATA_ROLE;

應用程式接著會在授予「代理程式記憶體」原則時使用相同的群組名稱:

grant_agent_memory_policies(
    admin_connection,
    owner_schema="OAM_SCHEMA_OWNER",
    memory_store_id="MEMORY",
    principals=[OciGroupPrincipal("YOUR_IAM_GROUP")],
    policies=[UserOwnRowsDeepDataSecurityPolicy()],
)

grant_agent_memory_policies() 會尋找對應至 YOUR_IAM_GROUP 的現有角色,並將「代理程式記憶體」表格資料授權新增至相同角色。它不會建立第二個角色。在程式實際執行時,其 OCI IAM 記號包含 YOUR_IAM_GROUP 的使用者可以存取允許的「代理程式記憶體」資料列和 DMUSER.DOC_MODEL。

如果應用程式在此模型設定之前已呼叫 grant_agent_memory_policies(),則「代理程式記憶體」已建立對應的資料角色。請勿為相同群組建立另一個對應角色。尋找現有角色並改為授予一般模型角色:

SELECT data_role
FROM sys.dba_data_roles
WHERE UPPER(mapped_to) = UPPER('IAM_OAUTH_GROUP=YOUR_IAM_GROUP');

GRANT APP_DOC_MODEL_ROLE TO <DATA_ROLE_RETURNED_BY_THE_QUERY>;

將每個程式實際執行 OracleDBEmbedder 作業保留在 OracleMemoryEndUserSecurityContext 中。請勿使用綱要擁有者、個別的管理員連線或沒有一般使用者安全相關資訊環境的連線來執行使用者要求的內嵌。這會略過 Deep Sec 授權界限。

瞭解應用程式

可下載的專案 deepsec_oci_iam_webapp.zip 包含完整的 Web 應用程式和資料庫命令檔。關鍵的應用程式片段非常小。

啟動並完成 OAuth

登入路由會隨機建立 state 和 PKCE 值。只有不透明階段作業 ID 會放置在 HttpOnly、SameSite=Lax Cookie 中;記號會保留在處理區域示範階段作業存放區中。

def start_authorization(config: RuntimeConfig) -> AuthorizationRequest:
    """Build a state-bound Authorization Code request with PKCE."""
    state = secrets.token_urlsafe(32)
    code_verifier = secrets.token_urlsafe(64)
    challenge = (
        base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest())
        .rstrip(b"=")
        .decode("ascii")
    )
    query = urllib.parse.urlencode(
        {
            "client_id": config.oauth_client_id,
            "response_type": "code",
            "redirect_uri": config.redirect_uri,
            "scope": config.end_user_scope,
            "state": state,
            "code_challenge": challenge,
            "code_challenge_method": "S256",
        }
    )
    return AuthorizationRequest(
        url=f"{config.domain_url}/oauth2/v1/authorize?{query}",
        state=state,
        code_verifier=code_verifier,
    )


def exchange_authorization_code(
    config: RuntimeConfig,
    code: str,
    code_verifier: str,
) -> EndUserToken:
    """Exchange one browser authorization code for an end-user token."""
    payload = _token_request(
        config,
        {
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": config.redirect_uri,
            "code_verifier": code_verifier,
        },
    )
    access_token, expires_at = _required_access_token(payload)
    return EndUserToken(
        access_token=access_token,
        username=_display_username(access_token),
        expires_at=expires_at,
    )

回呼會持續比較 state,並使用相同的重新導向 URI 和 PKCE 驗證程式交換程式碼。應用程式會另外取得資料庫存取記號:

    def get(self, config: RuntimeConfig) -> str:
        """Return a current database-access token."""
        with self._lock:
            if self._token is not None and time.time() < self._expires_at - 60:
                return self._token
            payload = _token_request(
                config,
                {
                    "grant_type": "client_credentials",
                    "scope": config.database_access_scope,
                },
            )
            self._token, self._expires_at = _required_access_token(payload)
            return self._token

OCI IAM 會在驗證 OCI IAM 組態中記錄兩個權杖要求。

設定每個代理程式記憶體作業的範圍

此應用程式將這兩個記號結合成一個 python-oracledb 一般使用者安全相關資訊環境。然後,它會使用 OracleMemoryEndUserSecurityContext 來調整每個存放區初始化和作業的範圍:

def list_visible_memories(
    config: RuntimeConfig,
    pool: Any,
    user_context: Any,
) -> list[Any]:
    """List rows visible to the effective OCI IAM end user."""
    with OracleMemoryEndUserSecurityContext(user_context):
        store = create_runtime_store(config, pool)
        return store.list("memory", limit=100)


def add_memory(
    config: RuntimeConfig,
    pool: Any,
    user_context: Any,
    content: str,
    target_username: str,
) -> None:
    """Attempt to insert a row for the username supplied by the browser."""
    with OracleMemoryEndUserSecurityContext(user_context):
        store = create_runtime_store(config, pool)
        store.add(
            contents=[content],
            record_type="memory",
            user_ids=[target_username],
        )

程式實際執行存放區使用 SchemaPolicy.NO_CHECK,因為綱要建立、驗證、升級及重新建立屬於綱要擁有者的設定路徑。當一般使用者相關資訊環境為作用中狀態時,SDK 會拒絕另一個綱要原則,並在沒有作用中相關資訊環境時,拒絕此受保護程式實際執行存放區的後續作業。安全管理功能也同樣會拒絕帶有一般使用者相關資訊環境的連線。

針對每個「代理程式記憶體」資料庫作業,相關資訊環境管理者:

集區的資料庫帳戶沒有備用表格權限。python-oracledb 有效負載 API 記載於建立一般使用者安全相關資訊環境有效負載 (Payload) 中。深資料安全性 API 和安全性參考說明內容管理者的完整合約。

讓資料庫做出決定

轉寄送出的使用者名稱未變更。它會擷取並清理資料庫錯誤,但未包含 username == signed_in_user 授權分支:

@web.route("/", methods=["GET", "POST"])
def index() -> Response | tuple[str, int]:
    """Show visible memories and let the user attempt one insert."""
    config, pool, _ = _extensions()
    _, session = _session()
    if session is None or session.end_user_token is None:
        return render_template("index.html", session=None, memories=[])

    message = None
    status = 200
    try:
        user_context = _security_context(config, session)
        if request.method == "POST":
            if not hmac.compare_digest(
                request.form.get("csrf_token", ""),
                session.csrf_token,
            ):
                return render_template("error.html", message="The form expired."), 400
            content = request.form.get("content", "").strip()
            target_username = request.form.get("username", "").strip()
            if not content or not target_username:
                message = "Content and username are required."
                status = 400
            elif len(content) > 4000 or len(target_username) > 255:
                message = "The submitted memory is too large."
                status = 400
            else:
                try:
                    add_memory(
                        config,
                        pool,
                        user_context,
                        content,
                        target_username,
                    )
                    message = "Memory added."
                except oracledb.DatabaseError:
                    #Keep listing permitted rows after the deliberately denied write.
                    message = "Oracle AI Database denied this operation for the effective end user."
                    status = 403

        memories = list_visible_memories(config, pool, user_context)
    except oracledb.DatabaseError:
        #Never expose raw database errors, token contents, or submitted values.
        message = "Oracle AI Database denied this operation for the effective end user."
        memories = []
        status = 403
    except OAuthError:
        message = "The login session expired. Sign in again."
        memories = []
        status = 401

    return (
        render_template(
            "index.html",
            session=session,
            memories=memories,
            message=message,
        ),
        status,
    )

Jinja 跳脫顯示的記憶體內容。不會呈現原始資料庫異常狀況、OAuth 回應、記號以及送出的值。

執行並驗證應用程式

啟動 Flask 開發伺服器:

uv run python run.py

開啟 http://127.0.0.1:8000。

驗證授權界限:

  1. 以 Alice 登入。
  2. 使用預先填入的使用者名稱 alice 新增 Alice's first memory。
  3. 確認記憶體顯示在可見的記憶體底下。
  4. 新增 Alice tries to write for Bob,但將使用者名稱變更為 bob。
  5. 確認 Oracle AI Database 拒絕作業。
  6. 登出、以 Bob 身分登入,並確認 Alice 的記憶體未顯示。
  7. 將記憶體新增為 Bob,重新登入為 Alice,並確認 Bob 的記憶體看不到。

這示範兩個獨立的控制項:

對於實際執行作業,請設定 Oracle Unified Auditing for Deep Sec 管理與貴組織必須保留的一般使用者安全相關資訊環境動作。此外,記載的撤銷時間合約:傳回管理呼叫之前會先確認撤銷 SDK 原則,從 OCI IAM 群組移除使用者並不會重新寫入已經發行的存取權杖。請參閱深資料安全性,瞭解稽核指南、權杖到期行為,以及相對應 Oracle Deep Sec 和 OCI IAM 文件的連結。

如果登入成功,但資料庫拒絕每項作業,請檢查一般使用者記號,並確認 group 包含 ORACLEAGENTMEMORY_USERS。Oracle 的深資料安全性存取與權限疑難排解也描述了管理員如何檢查有效資料角色。

仍然需要什麼生產

警告:完成本指南並不會讓範例生產就緒。

在調整設計之前,請至少取代或新增:

保持核心不變:程式實際執行集區帳戶沒有直接表格 DML 權限,而且每個「代理程式記憶體」要求都會在經過驗證的 OracleMemoryEndUserSecurityContext 內執行。