Repository navigation
feat(identity): refuse requests of suspended tenants on every request - #797
Merged
Merged
Conversation
The ingress cookie check now reads the tenant's state with the membership role, in the same tenant transaction. A suspended or deleted tenant gets 403 tenant_suspended without a token for every service but identity; identity's own token carries the new optional tenant_suspended claim, which service-kit's Requirement::Tenant refuses with 403 tenant_suspended, separate from permission_denied. The token exchange refuses that claim, and run delegations refuse to start or exchange while their tenant is suspended; OnBehalf and RunDelegations pass the code through to the user. The current session lists each tenant's state, so the app can explain the suspension. Reading the session, switching tenants, logging out, preferences and identity's platform endpoints (resuming the tenant) keep working. A membership whose tenant has no row reads as active. Refs #777 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnmrdxG91NgAkjWi1afk7W
The allow row had lost its third cell, and the tenant_suspended row described forwarding instead of returning the denial (security review). Refs #777 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnmrdxG91NgAkjWi1afk7W
4 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
摘要
清單 B1「每次請求檢查停權」:租戶被停權或已刪除之後,同一個 session 的下一個請求就被擋下。入口的 cookie 檢查(身分服務的 ext_authz)在讀成員角色的同一個查詢、同一個租戶交易裡讀租戶的狀態:路由到身分服務以外的服務時回 403
tenant_suspended、不簽 token;路由到身分服務時照常簽,token 帶新的選填 claimtenant_suspended,由 service-kit 的Requirement::Tenant以 403tenant_suspended(與permission_denied分開)拒絕。token 交換與 AI 的 run 委派(建立與每次換 token)也回tenant_suspended。讀 session、切換租戶、登出、偏好設定與平台管理者在身分服務的平台端點(含恢復)照常可用。變更
crates/contract:InternalClaims加選填的tenant_suspended(serde(default),false時不序列化)。crates/service-kit:Requirement::Tenant在比對角色前拒絕帶 claim 的 token,回ErrorCode::TenantSuspended;UserIdentity::is_tenant_suspended();OnBehalfError與RunDelegationError照傳tenant_suspended。services/identity:session.rs:MEMBERSHIP查詢以LEFT JOIN identity.tenants一起讀角色與狀態;沒有租戶列時視為active,不認得的狀態回 503(fail closed)。ext_authz.rs:停權租戶只給身分服務簽 token,其他服務 403tenant_suspended,日誌reason是tenant_suspended。issuer.rs:只有帶租戶時才寫 claim。exchange.rs:拒絕帶 claim 的 token(第二道防線)。delegation.rs:建立時看 claim 與當下的狀態,每次換 token 重查狀態;恢復之後同一個委派又換得到。current_session.rs:TenantMembership加state(active/suspended/deleted),不含停權原因。api/openapi.json與frontend/src/api/schema.gen.ts重新產生;frontend/src/api/identity.ts的 zod schema 讀state(畫面在 SPA 停權說明頁接上真實後端,並以瀏覽器實測(B1) #780)。services/identity/tests/tenant_suspension.rs(真的 binary 與 migration Job 套用的產品 schema),以及 ext_authz、exchange、delegation、issuer、session、current_session、service-kit permission / on_behalf / run_delegation 的單元測試。tests/ext_authz.rs與tests/adversarial.rs的資料庫多套用0002_tenants.sql與0008_tenant_state.sql。services/identity/README.md(新的「停權時的每次請求檢查」一節,含代價)、crates/service-kit/README.md、crates/contract/README.md、docs/architecture/permissions.md、docs/requirements/rebuild-checklist.mdB1。驗證
cargo fmt --all --check、cargo run -p openapi -- --check、cargo clippy --locked -p service-kit --no-default-features --all-targets -- -D warnings、cargo test --locked -p service-kit --no-default-features:通過(exit 0 at 19:11:59)。CI=1 cargo test --locked --workspace --exclude agent:2034 passed、0 failed、1 ignored,含tenant_suspension.rs的兩個測試與上列單元測試(同一次執行)。cargo clippy --locked --workspace --all-targets -- -D warnings:通過(exit 0 at 18:26:13)。bun run --cwd frontend api:check、typecheck、lint、test(252 pass、0 fail)、build、check:dist都通過。驗收條件逐條對照
停權之後同一個 session 的下一個請求回 403
tenant_suspended:tenant_suspension.rs的a_suspension_and_a_resumption_apply_to_the_next_request。涵蓋:a_deleted_tenant_is_cut_off_like_a_suspended_one驗證deleted也一樣被擋下。單元測試ext_authz::tests::a_suspended_tenant_reaches_the_identity_service_only驗證回應不帶身分標頭。停權期間照常可用的操作,同一個測試都驗證過:
suspended)、改偏好、切換到另一個租戶、切回、登出;active,版本 3)。恢復之後的下一個請求:其他服務、事件紀錄與同一個 run 委派都通過,session 顯示
active。指定的檢查:見上面「驗證」,全部通過。
已知代價與 Low
tenant_suspended,要先切換租戶;只屬於這個租戶的平台管理者在恢復之前用不了(README 已記載)。identity.tenants列的成員資格視為active:目前狀態 API 需要這一列,identity_app也不能刪租戶。合約終止:資料保留、匯出交付、期滿刪除與閘道器回收(B1) #109 刪除資料時,租戶列要和成員資格、session 一起刪,或比它們晚刪。state是嚴格的列舉:伺服器以後加了新的狀態,SPA 讀 session 會失敗。由 SPA 停權說明頁接上真實後端,並以瀏覽器實測(B1) #780 決定怎麼容錯。審查分類建議
建議
review:human。這個 PR 改了存取控制(入口 cookie 檢查、Requirement::Tenant、token 交換、run 委派),也改了對外 API 契約(CurrentSession的TenantMembership多了必填的state,前端 schema 也跟著改)與內部 token 的 claims。Closes #777
🤖 Generated with Claude Code
https://claude.ai/code/session_01EnmrdxG91NgAkjWi1afk7W