跳轉至

安全模型

Provisa在所有查詢語言(GraphQL、SQL、Cypher)及所有傳輸方式(REST、gRPC、Arrow Flight、JDBC、WebSocket)上,均實施多層次的安全模型。(REQ-001REQ-266)治理措施會統一應用——不存在任何可繞過治理的查詢路徑。(REQ-002REQ-266

各層按順序應用。每個請求必須先通過每一層,才會評估下一層。

分層模型

第0層——內省過濾

呈現給某個角色的結構描述 (Schema) 及目錄,只包含其domain_access清單中的資料表,以及通過逐欄visible_to規則的欄位。(REQ-039)角色權限以外的物件,在發現階段即屬不可見——無法查詢、無法自動完成,亦無法推斷其存在。(REQ-039)此規則適用於GraphQL結構描述、SQL目錄,以及查詢編輯器的結構描述瀏覽器。(REQ-039REQ-363

參閱結構描述可見性

第1層——公開存取

沒有domain_access限制的網域中的資料表,無需額外設定即可供所有已通過驗證的身份查看。對於真正公開的數據,完全沒有障礙。

第2層——網域存取

每個角色都有一份domain_access網域ID清單。凡涉及該等網域以外資料表的查詢,均會在執行前遭拒絕。(REQ-038REQ-039)這是粗粒度的擁有權邊界——無論SQL如何撰寫,人力資源角色都無法存取財務資料表。(REQ-002

參閱權限模型

第3層——行級安全

網域存取獲確認後,系統會在執行時,將按資料表、按角色設定的WHERE謂詞注入每個SELECT陳述式中。(REQ-041REQ-263)該等謂詞是針對原始數據進行評估的。即使使用SELECT *,查詢共用訂單資料表的區域經理也只會看到其所屬區域的資料列。(REQ-264

參閱行級安全 (RLS)

第4層——欄位可見性及遮罩

visible_to清單中不包括請求角色的欄位,會從查詢結果中移除。(REQ-040REQ-263)設有遮罩規則的欄位,其值會在結果離開伺服器前遭取代——方式包括正規表達式編修、常數取代或截斷。(REQ-263)遮罩適用於所有查詢語言及輸出格式。(REQ-263

參閱欄位權限模型欄位層級遮罩

第5層——謂詞防護

遭遮罩的欄位會在WHEREHAVING子句中被拒絕使用。(REQ-263)若無此防護,即使輸出結果已遮罩,呼叫方仍可透過在篩選條件中進行二分搜尋,推斷出未遮罩的值。此項拒絕會在查詢剖析階段(執行前)強制執行。(REQ-531

關係治理(V002)

SQL中的JOIN條件,必須符合資料表之間已登記並獲批核的關係。(REQ-001)未經批核的join會遭拒絕。每個關係均附有人類可讀的原因及描述——為使用者及自主代理提供指引,說明某遍歷路徑存在的原因。此屬治理政策,而非硬性的安全邊界:無論join結構如何,第2至5層依然有效,因此刻意的規避行為,並不會使角色接觸到其原本無法透過兩個獨立查詢取得的數據。規避的嘗試會被記錄並可供審計。

繞過機制——只有在以下兩項獨立條件同時成立時,才可繞過V002:

  1. 角色標記——角色定義中的relationship_guard: false(預設值:true)。[tool-verified: provisa/core/models.py:349]
  2. 按查詢退出——SQL中包含--relationship-guard=false註解。[tool-verified: provisa/compiler/params.py:80]

兩者必須同時具備。單靠角色標記並不能繞過V002;單靠註解亦不能繞過V002。

GraphQL路徑——對於GraphQL查詢,V002一律會被略過。SDL中定義的關係,按設計已預先獲批核;此項檢查屬多餘,故不會執行。[tool-verified: provisa/api/data/endpoint.py:468]

SQL及Cypher路徑——V002預設為啟用狀態。endpoint_dev.pycypher_router.py均會在呼叫validate_sql前,執行兩項條件的檢查。[tool-verified: provisa/api/data/endpoint_dev.py:127provisa/api/rest/cypher_router.py:260]

pgwire路徑——與SQL相同的兩項條件檢查。--relationship-guard=false註解會在執行前從查詢中移除;不會傳送至資料庫。[tool-verified: provisa/pgwire/_pipeline.py:60]


這些層次會相互組合。同時具備網域存取、RLS及遮罩欄位的角色,其五項限制會同時生效。新增數據來源、欄位或關係,無需逐一更新所有規則——每一層均獨立設定,並會自動套用於任何涉及受治理物件的查詢。


權限模型

各項能力獨立指派,並可透過parent_role_id實現可選的角色階層。admin授予全部能力。(REQ-042

能力 說明
source_registration 登記數據來源
table_registration 登記資料表、欄位
create_relationship 定義外部索引鍵關係
access_config 設定RLS、遮罩
query_development 執行查詢
write 呼叫已登記的變更操作(粗粒度控制;參閱「變更操作授權」)
full_results 繞過取樣限制
ignore_relationships 繞過關係治理(V002)
admin 超級使用者——授予全部能力

角色繼承

角色可透過parent_role_id,從父角色繼承能力及網域存取權。(REQ-215)階層會在啟動時扁平化——子角色會將父角色的能力及網域存取權,與自身的合併。(REQ-215

roles:
  - id: basic_user
    capabilities: [query_development]
    domain_access: [public]
  - id: analyst
    capabilities: [full_results]
    domain_access: [sales, analytics]
    parent_role_id: basic_user   # inherits query_development + public domain

欄位權限模型

每個欄位均設有四個欄位所組成的權限模型,用以控制各角色的讀取、寫入及遮罩存取權。(REQ-042REQ-249

三級可見性

級別 條件 結果
隱藏 角色不在visible_to 欄位不會出現在GraphQL SDL中
已遮罩 角色在visible_to中、設有遮罩規則、角色不在unmasked_to 欄位可見,但SQL中數據已遮罩
未遮罩 角色同時在visible_tounmasked_to中(或沒有遮罩規則) 完整讀取存取權

寫入權限

欄位 空白代表 用途
visible_to 所有角色均可讀取 控制誰可看到該欄位(已遮罩或未遮罩)
unmasked_to 沒有角色可看到未遮罩的值 控制誰可繞過遮罩
writable_by 沒有角色可寫入 控制誰可作出變更 (INSERT/UPDATE)

寫入權限會在變更操作管線中強制執行。不在writable_by中的角色,嘗試寫入受限欄位時會收到403錯誤。(REQ-033REQ-034

範例

columns:
  - name: email
    visible_to: [admin, analyst, viewer]
    writable_by: [admin]
    unmasked_to: [admin]
    mask_type: regex
    mask_pattern: "(.).*@"
    mask_replace: "$1***@"
  - name: salary
    visible_to: [admin, hr]
    writable_by: [hr]
    unmasked_to: [admin, hr]
    mask_type: constant
    mask_value: "0"
  - name: created_at
    visible_to: []           # all can read
    writable_by: []          # nobody can write (auto-set)

在此範例中:

  • email:admin可看到alice@example.com並可編輯;analyst/viewer則會看到a***@example.com
  • salary:admin及hr可看到真實值;hr可編輯;其餘所有角色完全看不到此欄位
  • created_at:所有人均可讀取,任何人都不可寫入

變更操作授權

已登記的變更操作(遠端GraphQL、OpenAPI、gRPC、Hasura)須經過兩項獨立檢查。(REQ-867REQ-868)角色只有在同時具備全域write能力,並列於該項變更操作的writable_by清單中,方可呼叫該操作。(REQ-868)空白的writable_by即代表預設拒絕——任何角色均不可呼叫。(REQ-867

變更操作按合約分類為寫入操作,而非按呼叫方的聲明而定。(REQ-869)若SELECT陳述式引用了屬變更操作類型的函式,會被提升為寫入操作,並須經過相同的兩重把關檢查,因此呼叫方無法透過將變更操作偽裝為讀取操作而繞過限制。(REQ-869)將某項變更操作重新分類為讀取安全,須具備access_config能力,並會被記錄為治理決定;並無按請求逐次退出的選項。(REQ-870

結構描述可見性

按角色劃分的GraphQL結構描述,會隱藏未經授權的內容:(REQ-039

  • 網域存取:角色只會看到其domain_access網域內的資料表("*" = 全部)(REQ-039
  • 欄位可見性:對某角色而言不在visible_to中的欄位,會從SDL中省略(REQ-039
  • 未經授權的資料表/欄位,不會出現在結構描述中(REQ-039

行級安全 (RLS)

按資料表、按角色注入SQL WHERE子句。此項操作於編譯後、執行前進行。(REQ-041REQ-263

rls_rules:
  - table_id: orders
    role_id: analyst
    filter: "region = current_setting('provisa.user_region')"

篩選條件會以AND方式併入查詢的WHERE子句中。此機制同時適用於查詢及變更操作 (UPDATE/DELETE)。(REQ-035REQ-041

欄位層級遮罩

遮罩設定只需為每個欄位定義一次——這是欄位本身的屬性,而非角色的屬性。unmasked_to欄位控制哪些角色可繞過遮罩。(REQ-249

遮罩類型 支援的類型 SQL運算式
regex 字串 (varchar, char, text) REGEXP_REPLACE(col, pattern, replace)
constant 任何類型 常值 (NULL、0、自訂)
truncate 日期/時間戳記 DATE_TRUNC(precision, col)

遮罩會被下推至SQL SELECT投影中——由資料庫直接傳回已遮罩的數據。(REQ-263)對於遭遮罩的角色而言,未遮罩的數據絕不會經網絡傳輸。(REQ-263)遭遮罩的欄位亦會在WHEREHAVING子句中遭封鎖(第5層謂詞防護),以防止透過篩選推斷出未遮罩的值。(REQ-263REQ-531

取樣

除非具備full_results能力,否則所有角色看到的均為經取樣的結果(預設:100列)。(REQ-554)可透過PROVISA_SAMPLE_SIZE環境變數控制。(REQ-554

審計記錄

任何涉及網域資產的查詢,均會記錄於只可新增的query_audit_log中。(REQ-596REQ-613)每列會擷取tenant_iduser_idrole_id、查詢文字的SHA-256雜湊值、table_idssourcestatus_codeduration_mslogged_at。(REQ-596)查詢文字絕不會以原文儲存——只會儲存其雜湊值。(REQ-596

該記錄在資料庫層面屬只可新增:PostgreSQL規則會封鎖DELETEUPDATE。(REQ-596REQ-613)兩個索引——(tenant_id, logged_at)(user_id, logged_at)——支援按租用戶範圍及按使用者的時間範圍合規查詢。(REQ-596REQ-613

啟用加密後,查詢文字雜湊值一欄會以加密方式儲存,並只會在獲授權的管理員讀取時解密。(REQ-689

速率限制

按角色設定的速率限制,會於provisa.yaml中設定:包括每秒最大請求數、最大同時進行的SSE訂閱數,以及最大同時進行的Arrow Flight串流數。(REQ-369)該等限制會在編譯或執行之前,於API層強制執行;超出限制的請求會遭拒絕,並回傳HTTP 429及Retry-After標頭。(REQ-369

自然語言查詢服務(POST /query/nl)另設有獨立限制,透過nl.rate_limit(每分鐘、每角色的請求數)控制。超出限制的請求會在呼叫任何LLM之前遭拒絕。(REQ-370

速率限制的狀態儲存於Redis(cache.redis_url)中,以滑動視窗計數器方式運作——並無按執行個體儲存的狀態——因此限制會在所有水平擴展的Provisa執行個體之間保持一致。(REQ-371

身份驗證

可插拔的身份驗證提供者:(REQ-120

提供者 權杖類型 使用案例
none X-Provisa-Role標頭 開發
firebase Firebase ID權杖 生產環境
keycloak Keycloak JWT 企業版
oauth OIDC JWT PingFed、Okta、Azure AD、Auth0
simple bcrypt + JWT 測試

角色對應:透過可設定的規則,將身份聲明對應至Provisa角色。(REQ-120assignments_source欄位控制角色指派的來源:claims會從JWT權杖的聲明中讀取(預設值);provisa則會從Provisa內部的指派儲存區中讀取。(REQ-551

provisa.yaml中設定的超級使用者(使用者名稱連同來自環境密鑰的密碼),無論設定何種提供者,均一律獲授予admin角色及全部能力——此為初始設定所用的啟動路徑。(REQ-125

ABAC批核掛勾 (Hook)

可選的外部政策掛勾,會在查詢執行前觸發。(REQ-203)當設定此項功能後,Provisa會呼叫您的政策引擎,並傳送使用者身份、角色、資料表、欄位及操作類型。回應結果會決定該查詢是否繼續執行。(REQ-203

適用範圍

只有當查詢涉及設定了範圍的資料表或來源時,該掛勾才會觸發——其餘情況則完全沒有額外負擔。(REQ-204

設定 效果
auth.approval_hook.scope: all 每個查詢均會觸發此掛勾
sources[].approval_hook: true 該來源上的所有資料表均會觸發此掛勾
tables[].approval_hook: true 該資料表會觸發此掛勾

協定

支援三種傳輸方式:(REQ-246

類型 使用案例 設定欄位
webhook 任何支援HTTP的政策服務(OPA、自訂) url
unix_socket 位於同一部機器上的OPA或政策旁路容器 (sidecar) socket_path + url
grpc 同址部署、高吞吐量的政策服務 url (host:port)

gRPC傳輸方式採用provisa/auth/approval.proto中定義的provisa.auth.ApprovalService合約。請在您的政策引擎中實作此服務:(REQ-246

service ApprovalService {
  rpc Evaluate (ApprovalRequest) returns (ApprovalResponse);
}

message ApprovalRequest {
  string user = 1;
  repeated string roles = 2;
  repeated string tables = 3;
  repeated string columns = 4;
  string operation = 5;
}

message ApprovalResponse {
  bool approved = 1;
  string reason = 2;
}

gRPC通道屬持續性——每個Provisa執行個體使用一條通道,並會在所有對該掛勾端點的呼叫中重複使用。(REQ-555

請求/回應

三種傳輸方式均承載相同的酬載:(REQ-246

欄位 類型 說明
user string 已通過驗證的使用者身份
roles string[] 使用者的Provisa角色
tables string[] 查詢中引用的資料表ID
columns string[] 查詢中選取的欄位
operation string "query""mutation"

webhook及Unix socket傳輸方式均以JSON交換資料。回應必須包含approved(布林值),並可選擇性包含reason(字串)。(REQ-246

逾時及後備處理

auth:
  approval_hook:
    type: grpc          # webhook | grpc | unix_socket
    url: "localhost:50051"
    timeout_ms: 500     # default 5000
    fallback: deny      # allow | deny — applied on timeout or error
    scope: ""           # "" = use per-table/per-source flags; "all" = every query

發生逾時或傳輸錯誤時,會套用fallback政策。(REQ-247)斷路器 (circuit breaker)(預設:連續失敗5次後開啟,30秒後轉為半開狀態)可防止因掛勾端點反應緩慢而引致的連鎖故障。(REQ-556

設定範例

auth:
  approval_hook:
    type: webhook
    url: "http://opa.internal:8181/v1/data/provisa/allow"
    timeout_ms: 300
    fallback: deny

sources:
  - id: analytics_pg
    approval_hook: true   # all tables on this source require hook approval

tables:
  - id: salary_data
    approval_hook: true   # this table always requires hook approval

密鑰

憑證使用${env:VAR_NAME}語法,並於執行階段解析。(REQ-557)密碼絕不會儲存於設定資料庫中。(REQ-557