Merge pull request #4083 from BerriAI/litellm_act_on_behalf

[Feat] Enterprise - Attribute Management changes to Users in Audit Logs
This commit is contained in:
Ishaan Jaff 2024-06-08 17:39:16 -07:00 committed by GitHub
commit 76488c57fc
6 changed files with 153 additions and 23 deletions

View file

@ -0,0 +1,89 @@
# ✨ Attribute Management changes to Users
Call management endpoints on behalf of a user. (Useful when connecting proxy to your development platform).
:::info
Requires Enterprise License for usage.
:::
## Set `LiteLLM-Changed-By` in request headers
Set the 'user_id' in request headers, when calling a management endpoint. [View Full List](https://litellm-api.up.railway.app/#/team%20management).
- Update Team budget with master key.
- Attribute change to 'krrish@berri.ai'.
**👉 Key change:** Passing `-H 'LiteLLM-Changed-By: krrish@berri.ai'`
```shell
curl -X POST 'http://0.0.0.0:4000/team/update' \
-H 'Authorization: Bearer sk-1234' \
-H 'LiteLLM-Changed-By: krrish@berri.ai' \
-H 'Content-Type: application/json' \
-d '{
"team_id" : "8bf18b11-7f52-4717-8e1f-7c65f9d01e52",
"max_budget": 2000
}'
```
## Emitted Audit Log
```bash
{
"id": "bd136c28-edd0-4cb6-b963-f35464cf6f5a",
"updated_at": "2024-06-08 23:41:14.793",
"changed_by": "krrish@berri.ai", # 👈 CHANGED BY
"changed_by_api_key": "88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",
"action": "updated",
"table_name": "LiteLLM_TeamTable",
"object_id": "8bf18b11-7f52-4717-8e1f-7c65f9d01e52",
"before_value": {
"spend": 0,
"max_budget": 0,
},
"updated_values": {
"team_id": "8bf18b11-7f52-4717-8e1f-7c65f9d01e52",
"max_budget": 2000 # 👈 CHANGED TO
},
}
```
## API SPEC of Audit Log
### `id`
- **Type:** `String`
- **Description:** This is the unique identifier for each audit log entry. It is automatically generated as a UUID (Universally Unique Identifier) by default.
### `updated_at`
- **Type:** `DateTime`
- **Description:** This field stores the timestamp of when the audit log entry was created or updated. It is automatically set to the current date and time by default.
### `changed_by`
- **Type:** `String`
- **Description:** The `user_id` that performed the audited action. If `LiteLLM-Changed-By` Header is passed then `changed_by=<value passed for LiteLLM-Changed-By header>`
### `changed_by_api_key`
- **Type:** `String`
- **Description:** This field stores the hashed API key that was used to perform the audited action. If left blank, it defaults to an empty string.
### `action`
- **Type:** `String`
- **Description:** The type of action that was performed. One of "create", "update", or "delete".
### `table_name`
- **Type:** `String`
- **Description:** This field stores the name of the table that was affected by the audited action. It can be one of the following values: `LiteLLM_TeamTable`, `LiteLLM_UserTable`, `LiteLLM_VerificationToken`
### `object_id`
- **Type:** `String`
- **Description:** This field stores the ID of the object that was affected by the audited action. It can be the key ID, team ID, user ID
### `before_value`
- **Type:** `Json?`
- **Description:** This field stores the value of the row before the audited action was performed. It is optional and can be null.
### `updated_values`
- **Type:** `Json?`
- **Description:** This field stores the values of the row that were updated after the audited action was performed

View file

@ -55,6 +55,7 @@ const sidebars = {
}, },
"proxy/ui", "proxy/ui",
"proxy/email", "proxy/email",
"proxy/multiple_admins",
"proxy/team_based_routing", "proxy/team_based_routing",
"proxy/customer_routing", "proxy/customer_routing",
"proxy/token_auth", "proxy/token_auth",

View file

@ -1305,6 +1305,7 @@ class LiteLLM_AuditLogs(LiteLLMBase):
id: str id: str
updated_at: datetime updated_at: datetime
changed_by: str changed_by: str
changed_by_api_key: Optional[str] = None
action: Literal["created", "updated", "deleted"] action: Literal["created", "updated", "deleted"]
table_name: Literal[ table_name: Literal[
LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.TEAM_TABLE_NAME,

View file

@ -6426,7 +6426,10 @@ async def supported_openai_params(model: str):
async def generate_key_fn( async def generate_key_fn(
data: GenerateKeyRequest, data: GenerateKeyRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
Authorization: Optional[str] = Header(None), litellm_changed_by: Optional[str] = Header(
None,
description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability",
),
): ):
""" """
Generate an API key based on the provided data. Generate an API key based on the provided data.
@ -6611,8 +6614,10 @@ async def generate_key_fn(
request_data=LiteLLM_AuditLogs( request_data=LiteLLM_AuditLogs(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
updated_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc),
changed_by=user_api_key_dict.user_id changed_by=litellm_changed_by
or user_api_key_dict.user_id
or litellm_proxy_admin_name, or litellm_proxy_admin_name,
changed_by_api_key=user_api_key_dict.api_key,
table_name=LitellmTableNames.KEY_TABLE_NAME, table_name=LitellmTableNames.KEY_TABLE_NAME,
object_id=response.get("token_id", ""), object_id=response.get("token_id", ""),
action="created", action="created",
@ -6654,6 +6659,10 @@ async def update_key_fn(
request: Request, request: Request,
data: UpdateKeyRequest, data: UpdateKeyRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
litellm_changed_by: Optional[str] = Header(
None,
description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability",
),
): ):
""" """
Update an existing key Update an existing key
@ -6714,8 +6723,10 @@ async def update_key_fn(
request_data=LiteLLM_AuditLogs( request_data=LiteLLM_AuditLogs(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
updated_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc),
changed_by=user_api_key_dict.user_id changed_by=litellm_changed_by
or user_api_key_dict.user_id
or litellm_proxy_admin_name, or litellm_proxy_admin_name,
changed_by_api_key=user_api_key_dict.api_key,
table_name=LitellmTableNames.KEY_TABLE_NAME, table_name=LitellmTableNames.KEY_TABLE_NAME,
object_id=data.key, object_id=data.key,
action="updated", action="updated",
@ -6751,6 +6762,10 @@ async def update_key_fn(
async def delete_key_fn( async def delete_key_fn(
data: KeyRequest, data: KeyRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
litellm_changed_by: Optional[str] = Header(
None,
description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability",
),
): ):
""" """
Delete a key from the key management system. Delete a key from the key management system.
@ -6804,8 +6819,10 @@ async def delete_key_fn(
request_data=LiteLLM_AuditLogs( request_data=LiteLLM_AuditLogs(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
updated_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc),
changed_by=user_api_key_dict.user_id changed_by=litellm_changed_by
or user_api_key_dict.user_id
or litellm_proxy_admin_name, or litellm_proxy_admin_name,
changed_by_api_key=user_api_key_dict.api_key,
table_name=LitellmTableNames.KEY_TABLE_NAME, table_name=LitellmTableNames.KEY_TABLE_NAME,
object_id=key, object_id=key,
action="deleted", action="deleted",
@ -9809,6 +9826,10 @@ async def delete_end_user(
async def new_team( async def new_team(
data: NewTeamRequest, data: NewTeamRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
litellm_changed_by: Optional[str] = Header(
None,
description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability",
),
): ):
""" """
Allow users to create a new team. Apply user permissions to their team. Allow users to create a new team. Apply user permissions to their team.
@ -9983,7 +10004,10 @@ async def new_team(
request_data=LiteLLM_AuditLogs( request_data=LiteLLM_AuditLogs(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
updated_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc),
changed_by=user_api_key_dict.user_id or litellm_proxy_admin_name, changed_by=litellm_changed_by
or user_api_key_dict.user_id
or litellm_proxy_admin_name,
changed_by_api_key=user_api_key_dict.api_key,
table_name=LitellmTableNames.TEAM_TABLE_NAME, table_name=LitellmTableNames.TEAM_TABLE_NAME,
object_id=data.team_id, object_id=data.team_id,
action="created", action="created",
@ -10037,6 +10061,10 @@ async def create_audit_log_for_update(request_data: LiteLLM_AuditLogs):
async def update_team( async def update_team(
data: UpdateTeamRequest, data: UpdateTeamRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
litellm_changed_by: Optional[str] = Header(
None,
description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability",
),
): ):
""" """
Use `/team/member_add` AND `/team/member/delete` to add/remove new team members Use `/team/member_add` AND `/team/member/delete` to add/remove new team members
@ -10114,7 +10142,10 @@ async def update_team(
request_data=LiteLLM_AuditLogs( request_data=LiteLLM_AuditLogs(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
updated_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc),
changed_by=user_api_key_dict.user_id or litellm_proxy_admin_name, changed_by=litellm_changed_by
or user_api_key_dict.user_id
or litellm_proxy_admin_name,
changed_by_api_key=user_api_key_dict.api_key,
table_name=LitellmTableNames.TEAM_TABLE_NAME, table_name=LitellmTableNames.TEAM_TABLE_NAME,
object_id=data.team_id, object_id=data.team_id,
action="updated", action="updated",
@ -10356,6 +10387,10 @@ async def team_member_delete(
async def delete_team( async def delete_team(
data: DeleteTeamRequest, data: DeleteTeamRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
litellm_changed_by: Optional[str] = Header(
None,
description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability",
),
): ):
""" """
delete team and associated team keys delete team and associated team keys
@ -10407,8 +10442,10 @@ async def delete_team(
request_data=LiteLLM_AuditLogs( request_data=LiteLLM_AuditLogs(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
updated_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc),
changed_by=user_api_key_dict.user_id changed_by=litellm_changed_by
or user_api_key_dict.user_id
or litellm_proxy_admin_name, or litellm_proxy_admin_name,
changed_by_api_key=user_api_key_dict.api_key,
table_name=LitellmTableNames.TEAM_TABLE_NAME, table_name=LitellmTableNames.TEAM_TABLE_NAME,
object_id=team_id, object_id=team_id,
action="deleted", action="deleted",

View file

@ -247,12 +247,13 @@ model LiteLLM_InvitationLink {
model LiteLLM_AuditLog { model LiteLLM_AuditLog {
id String @id @default(uuid()) id String @id @default(uuid())
updated_at DateTime @default(now()) updated_at DateTime @default(now())
changed_by String // user or system that performed the action changed_by String @default("") // user or system that performed the action
action String // create, update, delete changed_by_api_key String @default("") // api key hash that performed the action
table_name String // on of LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.USER_TABLE_NAME, LitellmTableNames.PROXY_MODEL_TABLE_NAME, action String // create, update, delete
object_id String // id of the object being audited. This can be the key id, team id, user id, model id table_name String // on of LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.USER_TABLE_NAME, LitellmTableNames.PROXY_MODEL_TABLE_NAME,
before_value Json? // value of the row object_id String // id of the object being audited. This can be the key id, team id, user id, model id
updated_values Json? // value of the row after change before_value Json? // value of the row
updated_values Json? // value of the row after change
} }

View file

@ -247,12 +247,13 @@ model LiteLLM_InvitationLink {
model LiteLLM_AuditLog { model LiteLLM_AuditLog {
id String @id @default(uuid()) id String @id @default(uuid())
updated_at DateTime @default(now()) updated_at DateTime @default(now())
changed_by String // user or system that performed the action changed_by String @default("") // user or system that performed the action
action String // create, update, delete changed_by_api_key String @default("") // api key hash that performed the action
table_name String // on of LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.USER_TABLE_NAME, LitellmTableNames.PROXY_MODEL_TABLE_NAME, action String // create, update, delete
object_id String // id of the object being audited. This can be the key id, team id, user id, model id table_name String // on of LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.USER_TABLE_NAME, LitellmTableNames.PROXY_MODEL_TABLE_NAME,
before_value Json? // value of the row object_id String // id of the object being audited. This can be the key id, team id, user id, model id
updated_values Json? // value of the row after change before_value Json? // value of the row
updated_values Json? // value of the row after change
} }