mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
feat(proxy): auth_v2 governs vector_store, budget, customer resources
Extends the control-plane route map to the remaining clean-CRUD management resources, following the established read/write/delete/manage pattern with per-resource ids. These routes were loud-open before, so this only tightens coverage. Non-uniform surfaces (guardrails, mcp servers, credentials) stay deferred because their verbs don't map cleanly onto the four actions. Also corrects two stale comments that still described the data plane as casbin ABAC after it became a plain predicate.
This commit is contained in:
parent
119732227d
commit
04bb7f2361
2 changed files with 70 additions and 5 deletions
|
|
@ -12,15 +12,18 @@ class GovernedRoute:
|
|||
id_fields: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
# Governs the model and team management planes. Every other route is
|
||||
# intentionally left ungoverned (loud-open) until later slices wire it in. Team
|
||||
# membership/permission routes (member_add, etc.) are deferred with the recursive
|
||||
# `manage` action.
|
||||
# Control-plane management resources governed by RBAC. Every route not listed
|
||||
# here (and not an inference route) is loud-open until a later slice wires it in;
|
||||
# non-uniform surfaces (guardrails, mcp servers, credentials) are deferred
|
||||
# because their verbs don't map cleanly onto read/write/delete/manage.
|
||||
_MODEL_ID_FIELDS = ["model_id", "id"]
|
||||
_TEAM_ID_FIELDS = ["team_id", "id"]
|
||||
_KEY_ID_FIELDS = ["key", "token", "key_name"]
|
||||
_USER_ID_FIELDS = ["user_id"]
|
||||
_ORG_ID_FIELDS = ["organization_id"]
|
||||
_VECTOR_STORE_ID_FIELDS = ["vector_store_id", "id"]
|
||||
_BUDGET_ID_FIELDS = ["budget_id", "id"]
|
||||
_CUSTOMER_ID_FIELDS = ["user_id"]
|
||||
|
||||
_GOVERNED: Dict[str, GovernedRoute] = {
|
||||
"/model/new": GovernedRoute("model", "write"),
|
||||
|
|
@ -60,11 +63,35 @@ _GOVERNED: Dict[str, GovernedRoute] = {
|
|||
"/auth/v2/policy/assignment/add": GovernedRoute("policy", "write"),
|
||||
"/auth/v2/policy/assignment/remove": GovernedRoute("policy", "delete"),
|
||||
"/auth/v2/policy/list": GovernedRoute("policy", "read"),
|
||||
"/vector_store/new": GovernedRoute("vector_store", "write"),
|
||||
"/vector_store/update": GovernedRoute(
|
||||
"vector_store", "write", _VECTOR_STORE_ID_FIELDS
|
||||
),
|
||||
"/vector_store/delete": GovernedRoute(
|
||||
"vector_store", "delete", _VECTOR_STORE_ID_FIELDS
|
||||
),
|
||||
"/vector_store/info": GovernedRoute(
|
||||
"vector_store", "read", _VECTOR_STORE_ID_FIELDS
|
||||
),
|
||||
"/vector_store/list": GovernedRoute("vector_store", "read"),
|
||||
"/budget/new": GovernedRoute("budget", "write"),
|
||||
"/budget/update": GovernedRoute("budget", "write", _BUDGET_ID_FIELDS),
|
||||
"/budget/delete": GovernedRoute("budget", "delete", _BUDGET_ID_FIELDS),
|
||||
"/budget/info": GovernedRoute("budget", "read", _BUDGET_ID_FIELDS),
|
||||
"/budget/list": GovernedRoute("budget", "read"),
|
||||
"/budget/settings": GovernedRoute("budget", "read"),
|
||||
"/customer/new": GovernedRoute("customer", "write"),
|
||||
"/customer/update": GovernedRoute("customer", "write", _CUSTOMER_ID_FIELDS),
|
||||
"/customer/delete": GovernedRoute("customer", "delete", _CUSTOMER_ID_FIELDS),
|
||||
"/customer/info": GovernedRoute("customer", "read", _CUSTOMER_ID_FIELDS),
|
||||
"/customer/list": GovernedRoute("customer", "read"),
|
||||
"/customer/block": GovernedRoute("customer", "write", _CUSTOMER_ID_FIELDS),
|
||||
"/customer/unblock": GovernedRoute("customer", "write", _CUSTOMER_ID_FIELDS),
|
||||
}
|
||||
|
||||
|
||||
# Inference routes carry a `model` in the body and are authorized on the data
|
||||
# plane (casbin ABAC over the principal's allowed-model attribute), not the
|
||||
# plane (a plain allowed-model predicate over the principal's key), not the
|
||||
# control-plane RBAC map.
|
||||
_INFERENCE_ROUTES = {
|
||||
"/chat/completions",
|
||||
|
|
|
|||
|
|
@ -70,6 +70,44 @@ def test_membership_changes_are_the_manage_action():
|
|||
assert match_route("/organization/member_add").action == "manage"
|
||||
|
||||
|
||||
def test_vector_store_resource_is_governed():
|
||||
assert match_route("/vector_store/new").resource == "vector_store"
|
||||
assert match_route("/vector_store/new").action == "write"
|
||||
assert match_route("/vector_store/delete").action == "delete"
|
||||
assert match_route("/vector_store/info").action == "read"
|
||||
assert match_route("/vector_store/list").action == "read"
|
||||
assert match_route("/vector_store/delete").id_fields == ["vector_store_id", "id"]
|
||||
|
||||
|
||||
def test_budget_resource_is_governed():
|
||||
assert match_route("/budget/new").resource == "budget"
|
||||
assert match_route("/budget/update").action == "write"
|
||||
assert match_route("/budget/delete").action == "delete"
|
||||
assert match_route("/budget/info").action == "read"
|
||||
assert match_route("/budget/settings").action == "read"
|
||||
assert match_route("/budget/delete").id_fields == ["budget_id", "id"]
|
||||
|
||||
|
||||
def test_customer_resource_is_governed():
|
||||
assert match_route("/customer/new").resource == "customer"
|
||||
assert match_route("/customer/delete").action == "delete"
|
||||
assert match_route("/customer/info").action == "read"
|
||||
# block/unblock are state writes, not their own action.
|
||||
assert match_route("/customer/block").action == "write"
|
||||
assert match_route("/customer/unblock").action == "write"
|
||||
assert match_route("/customer/info").id_fields == ["user_id"]
|
||||
|
||||
|
||||
def test_deferred_nonuniform_surfaces_are_still_loud_open():
|
||||
# Guardrails/MCP/credentials have non-CRUD verbs; deliberately not governed yet.
|
||||
for route in (
|
||||
"/guardrails/apply_guardrail",
|
||||
"/guardrails/register",
|
||||
"/v1/mcp/server/register",
|
||||
):
|
||||
assert match_route(route) is None
|
||||
|
||||
|
||||
def test_ungoverned_routes_return_none():
|
||||
# Genuinely not yet owned by v2: loud-open.
|
||||
for route in ("/v1/models", "/health", "/"):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue