diff --git a/litellm/proxy/common_utils/admin_ui_utils.py b/litellm/proxy/common_utils/admin_ui_utils.py
new file mode 100644
index 0000000000..bb35ecd691
--- /dev/null
+++ b/litellm/proxy/common_utils/admin_ui_utils.py
@@ -0,0 +1,167 @@
+import os
+
+
+def show_missing_vars_in_env():
+ from fastapi.responses import HTMLResponse
+
+ from litellm.proxy.proxy_server import master_key, prisma_client
+
+ if prisma_client is None and master_key is None:
+ return HTMLResponse(
+ content=missing_keys_form(
+ missing_key_names="DATABASE_URL, LITELLM_MASTER_KEY"
+ ),
+ status_code=200,
+ )
+ if prisma_client is None:
+ return HTMLResponse(
+ content=missing_keys_form(missing_key_names="DATABASE_URL"), status_code=200
+ )
+
+ if master_key is None:
+ return HTMLResponse(
+ content=missing_keys_form(missing_key_names="LITELLM_MASTER_KEY"),
+ status_code=200,
+ )
+ return None
+
+
+# LiteLLM Admin UI - Non SSO Login
+url_to_redirect_to = os.getenv("PROXY_BASE_URL", "")
+url_to_redirect_to += "/login"
+html_form = f"""
+
+
+
+ LiteLLM Login
+
+
+
+
+"""
+
+
+def missing_keys_form(missing_key_names: str):
+ missing_keys_html_form = """
+
+
+
+
+
+
+ Environment Setup Instructions
+
+
+
+
Environment Setup Instructions
+
Please add the following variables to your environment variables:
+
+ LITELLM_MASTER_KEY="sk-1234"
+ LITELLM_SALT_KEY="sk-XXXXXXXX"
+ DATABASE_URL="postgres://..."
+
+ PORT=4000
+ STORE_MODEL_IN_DB="True"
+
+
Missing Environment Variables
+
{missing_keys}
+
+
+
+
+
+ """
+ return missing_keys_html_form.format(missing_keys=missing_key_names)
diff --git a/litellm/proxy/common_utils/encrypt_decrypt_utils.py b/litellm/proxy/common_utils/encrypt_decrypt_utils.py
new file mode 100644
index 0000000000..f0090046b5
--- /dev/null
+++ b/litellm/proxy/common_utils/encrypt_decrypt_utils.py
@@ -0,0 +1,89 @@
+import base64
+import os
+
+from litellm._logging import verbose_proxy_logger
+
+LITELLM_SALT_KEY = os.getenv("LITELLM_SALT_KEY", None)
+verbose_proxy_logger.debug(
+ "LITELLM_SALT_KEY is None using master_key to encrypt/decrypt secrets stored in DB"
+)
+
+
+def encrypt_value_helper(value: str):
+ from litellm.proxy.proxy_server import master_key
+
+ signing_key = LITELLM_SALT_KEY
+ if LITELLM_SALT_KEY is None:
+ signing_key = master_key
+
+ try:
+ if isinstance(value, str):
+ encrypted_value = encrypt_value(value=value, signing_key=signing_key) # type: ignore
+ encrypted_value = base64.b64encode(encrypted_value).decode("utf-8")
+
+ return encrypted_value
+
+ raise ValueError(
+ f"Invalid value type passed to encrypt_value: {type(value)} for Value: {value}\n Value must be a string"
+ )
+ except Exception as e:
+ raise e
+
+
+def decrypt_value_helper(value: str):
+ from litellm.proxy.proxy_server import master_key
+
+ signing_key = LITELLM_SALT_KEY
+ if LITELLM_SALT_KEY is None:
+ signing_key = master_key
+
+ try:
+ if isinstance(value, str):
+ decoded_b64 = base64.b64decode(value)
+ value = decrypt_value(value=decoded_b64, signing_key=signing_key) # type: ignore
+ return value
+ except Exception as e:
+ verbose_proxy_logger.error(f"Error decrypting value: {value}\nError: {str(e)}")
+ # [Non-Blocking Exception. - this should not block decrypting other values]
+ pass
+
+
+def encrypt_value(value: str, signing_key: str):
+ import hashlib
+
+ import nacl.secret
+ import nacl.utils
+
+ # get 32 byte master key #
+ hash_object = hashlib.sha256(signing_key.encode())
+ hash_bytes = hash_object.digest()
+
+ # initialize secret box #
+ box = nacl.secret.SecretBox(hash_bytes)
+
+ # encode message #
+ value_bytes = value.encode("utf-8")
+
+ encrypted = box.encrypt(value_bytes)
+
+ return encrypted
+
+
+def decrypt_value(value: bytes, signing_key: str) -> str:
+ import hashlib
+
+ import nacl.secret
+ import nacl.utils
+
+ # get 32 byte master key #
+ hash_object = hashlib.sha256(signing_key.encode())
+ hash_bytes = hash_object.digest()
+
+ # initialize secret box #
+ box = nacl.secret.SecretBox(hash_bytes)
+
+ # Convert the bytes object to a string
+ plaintext = box.decrypt(value)
+
+ plaintext = plaintext.decode("utf-8") # type: ignore
+ return plaintext # type: ignore
diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py
index f954b9dd54..b673b26ab9 100644
--- a/litellm/proxy/proxy_server.py
+++ b/litellm/proxy/proxy_server.py
@@ -140,7 +140,15 @@ from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
## Import All Misc routes here ##
from litellm.proxy.caching_routes import router as caching_router
+from litellm.proxy.common_utils.admin_ui_utils import (
+ html_form,
+ show_missing_vars_in_env,
+)
from litellm.proxy.common_utils.debug_utils import router as debugging_endpoints_router
+from litellm.proxy.common_utils.encrypt_decrypt_utils import (
+ decrypt_value_helper,
+ encrypt_value_helper,
+)
from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
from litellm.proxy.common_utils.init_callbacks import initialize_callbacks_on_proxy
from litellm.proxy.common_utils.openai_endpoint_utils import (
@@ -186,13 +194,9 @@ from litellm.proxy.utils import (
_get_projected_spend_over_limit,
_is_projected_spend_over_limit,
_is_valid_team_configs,
- decrypt_value,
- encrypt_value,
get_error_message_str,
get_instance_fn,
hash_token,
- html_form,
- missing_keys_html_form,
reset_budget,
send_email,
update_spend,
@@ -1243,6 +1247,7 @@ class ProxyConfig:
## DB
if prisma_client is not None and (
general_settings.get("store_model_in_db", False) == True
+ or store_model_in_db is True
):
_tasks = []
keys = [
@@ -1885,16 +1890,8 @@ class ProxyConfig:
# decrypt values
for k, v in _litellm_params.items():
if isinstance(v, str):
- # decode base64
- try:
- decoded_b64 = base64.b64decode(v)
- except Exception as e:
- verbose_proxy_logger.error(
- "Error decoding value - {}".format(v)
- )
- continue
# decrypt value
- _value = decrypt_value(value=decoded_b64, master_key=master_key)
+ _value = decrypt_value_helper(value=v)
# sanity check if string > size 0
if len(_value) > 0:
_litellm_params[k] = _value
@@ -1938,13 +1935,8 @@ class ProxyConfig:
if isinstance(_litellm_params, dict):
# decrypt values
for k, v in _litellm_params.items():
- if isinstance(v, str):
- # decode base64
- decoded_b64 = base64.b64decode(v)
- # decrypt value
- _litellm_params[k] = decrypt_value(
- value=decoded_b64, master_key=master_key # type: ignore
- )
+ decrypted_value = decrypt_value_helper(value=v)
+ _litellm_params[k] = decrypted_value
_litellm_params = LiteLLM_Params(**_litellm_params)
else:
verbose_proxy_logger.error(
@@ -2005,10 +1997,8 @@ class ProxyConfig:
environment_variables = config_data.get("environment_variables", {})
for k, v in environment_variables.items():
try:
- if v is not None:
- decoded_b64 = base64.b64decode(v)
- value = decrypt_value(value=decoded_b64, master_key=master_key) # type: ignore
- os.environ[k] = value
+ decrypted_value = decrypt_value_helper(value=v)
+ os.environ[k] = decrypted_value
except Exception as e:
verbose_proxy_logger.error(
"Error setting env variable: %s - %s", k, str(e)
@@ -5941,11 +5931,8 @@ async def add_new_model(
_litellm_params_dict = model_params.litellm_params.dict(exclude_none=True)
_orignal_litellm_model_name = model_params.litellm_params.model
for k, v in _litellm_params_dict.items():
- if isinstance(v, str):
- encrypted_value = encrypt_value(value=v, master_key=master_key) # type: ignore
- model_params.litellm_params[k] = base64.b64encode(
- encrypted_value
- ).decode("utf-8")
+ encrypted_value = encrypt_value_helper(value=v)
+ model_params.litellm_params[k] = encrypted_value
_data: dict = {
"model_id": model_params.model_info.id,
"model_name": model_params.model_name,
@@ -6076,11 +6063,8 @@ async def update_model(
### ENCRYPT PARAMS ###
for k, v in _new_litellm_params_dict.items():
- if isinstance(v, str):
- encrypted_value = encrypt_value(value=v, master_key=master_key) # type: ignore
- model_params.litellm_params[k] = base64.b64encode(
- encrypted_value
- ).decode("utf-8")
+ encrypted_value = encrypt_value_helper(value=v)
+ model_params.litellm_params[k] = encrypted_value
### MERGE WITH EXISTING DATA ###
merged_dictionary = {}
@@ -7198,10 +7182,9 @@ async def google_login(request: Request):
)
####### Detect DB + MASTER KEY in .env #######
- if prisma_client is None or master_key is None:
- from fastapi.responses import HTMLResponse
-
- return HTMLResponse(content=missing_keys_html_form, status_code=200)
+ missing_env_vars = show_missing_vars_in_env()
+ if missing_env_vars is not None:
+ return missing_env_vars
# get url from request
redirect_url = os.getenv("PROXY_BASE_URL", str(request.base_url))
@@ -8404,11 +8387,8 @@ async def update_config(config_info: ConfigYAML):
# encrypt updated_environment_variables #
for k, v in _updated_environment_variables.items():
- if isinstance(v, str):
- encrypted_value = encrypt_value(value=v, master_key=master_key) # type: ignore
- _updated_environment_variables[k] = base64.b64encode(
- encrypted_value
- ).decode("utf-8")
+ encrypted_value = encrypt_value_helper(value=v)
+ _updated_environment_variables[k] = encrypted_value
_existing_env_variables = config["environment_variables"]
@@ -8825,11 +8805,8 @@ async def get_config():
env_vars_dict[_var] = None
else:
# decode + decrypt the value
- decoded_b64 = base64.b64decode(env_variable)
- _decrypted_value = decrypt_value(
- value=decoded_b64, master_key=master_key
- )
- env_vars_dict[_var] = _decrypted_value
+ decrypted_value = decrypt_value_helper(value=env_variable)
+ env_vars_dict[_var] = decrypted_value
_data_to_return.append({"name": _callback, "variables": env_vars_dict})
elif _callback == "langfuse":
@@ -8845,11 +8822,8 @@ async def get_config():
_langfuse_env_vars[_var] = None
else:
# decode + decrypt the value
- decoded_b64 = base64.b64decode(env_variable)
- _decrypted_value = decrypt_value(
- value=decoded_b64, master_key=master_key
- )
- _langfuse_env_vars[_var] = _decrypted_value
+ decrypted_value = decrypt_value_helper(value=env_variable)
+ _langfuse_env_vars[_var] = decrypted_value
_data_to_return.append(
{"name": _callback, "variables": _langfuse_env_vars}
@@ -8870,10 +8844,7 @@ async def get_config():
_slack_env_vars[_var] = _value
else:
# decode + decrypt the value
- decoded_b64 = base64.b64decode(env_variable)
- _decrypted_value = decrypt_value(
- value=decoded_b64, master_key=master_key
- )
+ _decrypted_value = decrypt_value_helper(value=env_variable)
_slack_env_vars[_var] = _decrypted_value
_alerting_types = proxy_logging_obj.slack_alerting_instance.alert_types
@@ -8909,10 +8880,7 @@ async def get_config():
_email_env_vars[_var] = None
else:
# decode + decrypt the value
- decoded_b64 = base64.b64decode(env_variable)
- _decrypted_value = decrypt_value(
- value=decoded_b64, master_key=master_key
- )
+ _decrypted_value = decrypt_value_helper(value=env_variable)
_email_env_vars[_var] = _decrypted_value
alerting_data.append(
diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py
index ba1a610809..8d4eff99a6 100644
--- a/litellm/proxy/utils.py
+++ b/litellm/proxy/utils.py
@@ -2705,178 +2705,6 @@ def _is_valid_team_configs(team_id=None, team_config=None, request_data=None):
return
-def encrypt_value(value: str, master_key: str):
- import hashlib
-
- import nacl.secret
- import nacl.utils
-
- # get 32 byte master key #
- hash_object = hashlib.sha256(master_key.encode())
- hash_bytes = hash_object.digest()
-
- # initialize secret box #
- box = nacl.secret.SecretBox(hash_bytes)
-
- # encode message #
- value_bytes = value.encode("utf-8")
-
- encrypted = box.encrypt(value_bytes)
-
- return encrypted
-
-
-def decrypt_value(value: bytes, master_key: str) -> str:
- import hashlib
-
- import nacl.secret
- import nacl.utils
-
- # get 32 byte master key #
- hash_object = hashlib.sha256(master_key.encode())
- hash_bytes = hash_object.digest()
-
- # initialize secret box #
- box = nacl.secret.SecretBox(hash_bytes)
-
- # Convert the bytes object to a string
- plaintext = box.decrypt(value)
-
- plaintext = plaintext.decode("utf-8") # type: ignore
- return plaintext # type: ignore
-
-
-# LiteLLM Admin UI - Non SSO Login
-url_to_redirect_to = os.getenv("PROXY_BASE_URL", "")
-url_to_redirect_to += "/login"
-html_form = f"""
-
-
-
- LiteLLM Login
-
-
-
-
-"""
-
-
-missing_keys_html_form = """
-
-
-
-
-
-
- Environment Setup Instructions
-
-
-
-
Environment Setup Instructions
-
Please add the following configurations to your environment variables:
-
-LITELLM_MASTER_KEY="sk-1234"
-DATABASE_URL="postgres://..."
-
-
-PORT=4000
-STORE_MODEL_IN_DB="True"
-
-
-
-
- """
-
-
def _to_ns(dt):
return int(dt.timestamp() * 1e9)
diff --git a/litellm/tests/test_config.py b/litellm/tests/test_config.py
index cd61101a3c..28d144e4dc 100644
--- a/litellm/tests/test_config.py
+++ b/litellm/tests/test_config.py
@@ -2,23 +2,30 @@
## Unit tests for ProxyConfig class
-import sys, os
+import os
+import sys
import traceback
+
from dotenv import load_dotenv
load_dotenv()
-import os, io
+import io
+import os
sys.path.insert(
0, os.path.abspath("../..")
) # Adds the parent directory to the system path
-import pytest, litellm
-from pydantic import BaseModel, ConfigDict
-from litellm.proxy.proxy_server import ProxyConfig
-from litellm.proxy.utils import encrypt_value, ProxyLogging, DualCache
-from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo
from typing import Literal
+import pytest
+from pydantic import BaseModel, ConfigDict
+
+import litellm
+from litellm.proxy.common_utils.encrypt_decrypt_utils import encrypt_value
+from litellm.proxy.proxy_server import ProxyConfig
+from litellm.proxy.utils import DualCache, ProxyLogging
+from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo
+
class DBModel(BaseModel):
model_id: str
@@ -28,6 +35,7 @@ class DBModel(BaseModel):
model_config = ConfigDict(protected_namespaces=())
+
@pytest.mark.asyncio
async def test_delete_deployment():
"""