diff --git a/docs/my-website/docs/proxy/enterprise.md b/docs/my-website/docs/proxy/enterprise.md index cfd6ab31015..3c6d77cc7a2 100644 --- a/docs/my-website/docs/proxy/enterprise.md +++ b/docs/my-website/docs/proxy/enterprise.md @@ -15,8 +15,7 @@ Features: - ✅ [SSO for Admin UI](./ui.md#✨-enterprise-features) - ✅ [Audit Logs with retention policy](#audit-logs) - ✅ [JWT-Auth](./token_auth.md) - - ✅ [Control available public, private routes (Restrict certain endpoints on proxy)](#control-available-public-private-routes) - - ✅ [Control available public, private routes](#control-available-public-private-routes) + - ✅ [Control available public, private routes](./public_routes.md) - ✅ [Secret Managers - AWS Key Manager, Google Secret Manager, Azure Key, Hashicorp Vault](../secret) - ✅ [[BETA] AWS Key Manager v2 - Key Decryption](#beta-aws-key-manager---key-decryption) - ✅ IP address‑based access control lists @@ -181,148 +180,7 @@ Expected Response ### Control available public, private routes -**Restrict certain endpoints of proxy** - -:::info - -❓ Use this when you want to: -- make an existing private route -> public -- set certain routes as admin_only routes - -::: - -#### Usage - Define public, admin only routes - -**Step 1** - Set on config.yaml - - -| Route Type | Optional | Requires Virtual Key Auth | Admin Can Access | All Roles Can Access | Description | -|------------|----------|---------------------------|-------------------|----------------------|-------------| -| `public_routes` | ✅ | ❌ | ✅ | ✅ | Routes that can be accessed without any authentication | -| `admin_only_routes` | ✅ | ✅ | ✅ | ❌ | Routes that can only be accessed by [Proxy Admin](./self_serve#available-roles) | -| `allowed_routes` | ✅ | ✅ | ✅ | ✅ | Routes are exposed on the proxy. If not set then all routes exposed. | - -`LiteLLMRoutes.public_routes` is an ENUM corresponding to the default public routes on LiteLLM. [You can see this here](https://github.com/BerriAI/litellm/blob/main/litellm/proxy/_types.py) - -```yaml -general_settings: - master_key: sk-1234 - public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate"] # routes that can be accessed without any auth - admin_only_routes: ["/key/generate"] # Optional - routes that can only be accessed by Proxy Admin - allowed_routes: ["/chat/completions", "/spend/calculate", "LiteLLMRoutes.public_routes"] # Optional - routes that can be accessed by anyone after Authentication -``` - -**Step 2** - start proxy - -```shell -litellm --config config.yaml -``` - -**Step 3** - Test it - - - - - -```shell -curl --request POST \ - --url 'http://localhost:4000/spend/calculate' \ - --header 'Content-Type: application/json' \ - --data '{ - "model": "gpt-4", - "messages": [{"role": "user", "content": "Hey, how'\''s it going?"}] - }' -``` - -🎉 Expect this endpoint to work without an `Authorization / Bearer Token` - - - - - - -**Successful Request** - -```shell -curl --location 'http://0.0.0.0:4000/key/generate' \ ---header 'Authorization: Bearer ' \ ---header 'Content-Type: application/json' \ ---data '{}' -``` - - -**Un-successfull Request** - -```shell - curl --location 'http://0.0.0.0:4000/key/generate' \ ---header 'Authorization: Bearer ' \ ---header 'Content-Type: application/json' \ ---data '{"user_role": "internal_user"}' -``` - -**Expected Response** - -```json -{ - "error": { - "message": "user not allowed to access this route. Route=/key/generate is an admin only route", - "type": "auth_error", - "param": "None", - "code": "403" - } -} -``` - - - - - - - -**Successful Request** - -```shell -curl http://localhost:4000/chat/completions \ --H "Content-Type: application/json" \ --H "Authorization: Bearer sk-1234" \ --d '{ -"model": "fake-openai-endpoint", -"messages": [ - {"role": "user", "content": "Hello, Claude"} -] -}' -``` - - -**Un-successfull Request** - -```shell -curl --location 'http://0.0.0.0:4000/embeddings' \ ---header 'Content-Type: application/json' \ --H "Authorization: Bearer sk-1234" \ ---data ' { -"model": "text-embedding-ada-002", -"input": ["write a litellm poem"] -}' -``` - -**Expected Response** - -```json -{ - "error": { - "message": "Route /embeddings not allowed", - "type": "auth_error", - "param": "None", - "code": "403" - } -} -``` - - - - - +See [Control Public & Private Routes](./public_routes.md) for detailed documentation on configuring public routes, admin-only routes, allowed routes, and wildcard patterns. ## Spend Tracking diff --git a/docs/my-website/docs/proxy/public_routes.md b/docs/my-website/docs/proxy/public_routes.md new file mode 100644 index 00000000000..21a92a00be5 --- /dev/null +++ b/docs/my-website/docs/proxy/public_routes.md @@ -0,0 +1,223 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Control Public & Private Routes + +:::info + +Requires a LiteLLM Enterprise License. [Get a free trial](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat). + +::: + +Control which routes require authentication and which routes are publicly accessible. + +## Route Types + +| Route Type | Requires Auth | Description | +|------------|---------------|-------------| +| `public_routes` | No | Routes accessible without any authentication | +| `admin_only_routes` | Yes (Admin only) | Routes only accessible by [Proxy Admin](./self_serve#available-roles) | +| `allowed_routes` | Yes | Routes exposed on the proxy. If not set, all routes are exposed | + +## Quick Start + +### Make Routes Public + +Allow specific routes to be accessed without authentication: + +```yaml +general_settings: + master_key: sk-1234 + public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate"] +``` + +### Restrict Routes to Admin Only + +Restrict certain routes to only be accessible by Proxy Admin: + +```yaml +general_settings: + master_key: sk-1234 + admin_only_routes: ["/key/generate", "/key/delete"] +``` + +### Limit Available Routes + +Only expose specific routes on the proxy: + +```yaml +general_settings: + master_key: sk-1234 + allowed_routes: ["/chat/completions", "/embeddings", "LiteLLMRoutes.public_routes"] +``` + +## Usage Examples + +### Define Public, Admin Only, and Allowed Routes + +```yaml +general_settings: + master_key: sk-1234 + public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate"] + admin_only_routes: ["/key/generate"] + allowed_routes: ["/chat/completions", "/spend/calculate", "LiteLLMRoutes.public_routes"] +``` + +`LiteLLMRoutes.public_routes` is an ENUM corresponding to the default public routes on LiteLLM. [View the source](https://github.com/BerriAI/litellm/blob/main/litellm/proxy/_types.py). + +### Testing + + + + + +```shell +curl --request POST \ + --url 'http://localhost:4000/spend/calculate' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "gpt-4", + "messages": [{"role": "user", "content": "Hey, how'\''s it going?"}] + }' +``` + +This endpoint works without an `Authorization` header. + + + + + +**Successful Request (Admin)** + +```shell +curl --location 'http://0.0.0.0:4000/key/generate' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data '{}' +``` + +**Unsuccessful Request (Non-Admin)** + +```shell +curl --location 'http://0.0.0.0:4000/key/generate' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data '{"user_role": "internal_user"}' +``` + +**Expected Response** + +```json +{ + "error": { + "message": "user not allowed to access this route. Route=/key/generate is an admin only route", + "type": "auth_error", + "param": "None", + "code": "403" + } +} +``` + + + + + +**Successful Request** + +```shell +curl http://localhost:4000/chat/completions \ +-H "Content-Type: application/json" \ +-H "Authorization: Bearer sk-1234" \ +-d '{ +"model": "fake-openai-endpoint", +"messages": [ + {"role": "user", "content": "Hello, Claude"} +] +}' +``` + +**Unsuccessful Request (Route Not Allowed)** + +```shell +curl --location 'http://0.0.0.0:4000/embeddings' \ +--header 'Content-Type: application/json' \ +-H "Authorization: Bearer sk-1234" \ +--data '{ +"model": "text-embedding-ada-002", +"input": ["write a litellm poem"] +}' +``` + +**Expected Response** + +```json +{ + "error": { + "message": "Route /embeddings not allowed", + "type": "auth_error", + "param": "None", + "code": "403" + } +} +``` + + + + + +## Advanced: Wildcard Patterns + +Use wildcard patterns to match multiple routes at once. + +### Syntax + +| Pattern | Description | Example | +|---------|-------------|---------| +| `/path/*` | Matches any route starting with `/path/` | `/api/*` matches `/api/users`, `/api/users/123` | + + +### Examples + +#### Make All Routes Under a Path Public + +```yaml +general_settings: + master_key: sk-1234 + public_routes: + - "LiteLLMRoutes.public_routes" + - "/api/v1/*" # All routes under /api/v1/ + - "/health/*" # All health check routes +``` + +#### Restrict Admin Routes with Wildcards + +```yaml +general_settings: + master_key: sk-1234 + admin_only_routes: + - "/admin/*" # All admin routes + - "/internal/*" # All internal routes +``` + +### Testing Wildcard Routes + +**Config:** +```yaml +general_settings: + master_key: sk-1234 + public_routes: + - "/public/*" +``` + +**Test:** +```shell +# This works without auth (matches /public/*) +curl http://localhost:4000/public/status + +# This also works without auth (matches /public/*) +curl http://localhost:4000/public/health/detailed + +# This requires auth (doesn't match /public/*) +curl http://localhost:4000/private/data +``` + diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 1a0aa352390..21fda61c3f6 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -298,6 +298,7 @@ const sidebars = { "proxy/custom_auth", "proxy/ip_address", "proxy/multiple_admins", + "proxy/public_routes", ], }, { diff --git a/litellm/proxy/auth/auth_utils.py b/litellm/proxy/auth/auth_utils.py index e6de65da2bd..c4d0d2f8f1c 100644 --- a/litellm/proxy/auth/auth_utils.py +++ b/litellm/proxy/auth/auth_utils.py @@ -251,30 +251,38 @@ def route_in_additonal_public_routes(current_route: str): - bool - True if the route is defined in public_routes - bool - False if the route is not defined in public_routes + Supports wildcard patterns (e.g., "/api/*" matches "/api/users", "/api/users/123") In order to use this the litellm config.yaml should have the following in general_settings: ```yaml general_settings: master_key: sk-1234 - public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate"] + public_routes: ["LiteLLMRoutes.public_routes", "/spend/calculate", "/api/*"] ``` """ - - # check if user is premium_user - if not do nothing + from litellm.proxy.auth.route_checks import RouteChecks from litellm.proxy.proxy_server import general_settings, premium_user try: if premium_user is not True: return False - # check if this is defined on the config if general_settings is None: return False routes_defined = general_settings.get("public_routes", []) + + # Check exact match first if current_route in routes_defined: return True + # Check wildcard patterns + for route_pattern in routes_defined: + if RouteChecks._route_matches_wildcard_pattern( + route=current_route, pattern=route_pattern + ): + return True + return False except Exception as e: verbose_proxy_logger.error(f"route_in_additonal_public_routes: {str(e)}") diff --git a/tests/test_litellm/proxy/auth/test_route_checks.py b/tests/test_litellm/proxy/auth/test_route_checks.py index f9276645a58..4402c09e278 100644 --- a/tests/test_litellm/proxy/auth/test_route_checks.py +++ b/tests/test_litellm/proxy/auth/test_route_checks.py @@ -804,4 +804,34 @@ def test_proxy_admin_viewer_can_access_global_spend_tags(): pytest.fail( f"proxy_admin_viewer should be able to access /global/spend/tags route. Got error: {str(e)}" ) + + +def test_route_in_additional_public_routes_wildcard_match(): + """ + Test that route_in_additonal_public_routes supports wildcard patterns. + """ + from litellm.proxy.auth.auth_utils import route_in_additonal_public_routes + + with patch("litellm.proxy.proxy_server.general_settings", {"public_routes": ["/api/*"]}), \ + patch("litellm.proxy.proxy_server.premium_user", True): + # Wildcard should match subpaths + assert route_in_additonal_public_routes("/api/users") is True + assert route_in_additonal_public_routes("/api/users/123") is True + # Should not match different prefix + assert route_in_additonal_public_routes("/other/path") is False + + +def test_route_in_additional_public_routes_exact_match(): + """ + Test that route_in_additonal_public_routes supports exact matches. + """ + from litellm.proxy.auth.auth_utils import route_in_additonal_public_routes + + with patch("litellm.proxy.proxy_server.general_settings", {"public_routes": ["/health", "/status"]}), \ + patch("litellm.proxy.proxy_server.premium_user", True): + # Exact matches should work + assert route_in_additonal_public_routes("/health") is True + assert route_in_additonal_public_routes("/status") is True + # Non-matching routes should fail + assert route_in_additonal_public_routes("/other") is False