mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(proxy): keep DATABASE_URL out of prisma migrate diff argv
The schema diff on boot (check_prisma_schema_diff) and the v1 resolver's drift and baseline diffs passed the database URL, password included, as --from-url/--to-url. On Linux /proc/<pid>/cmdline is world-readable, so any local user could read it with ps while the diff ran. The bundled schema.prisma already reads its datasource url from DATABASE_URL, so use --from-schema-datasource / --to-schema-datasource and hand the URL to the child through its env. The drift diff still prefers DIRECT_URL by setting it as the child's DATABASE_URL. Fixes #47273
This commit is contained in:
parent
9c958aff34
commit
31cb43bf09
4 changed files with 106 additions and 10 deletions
|
|
@ -306,8 +306,10 @@ class ProxyExtrasDBManager:
|
|||
"migrate",
|
||||
"diff",
|
||||
"--from-empty",
|
||||
"--to-url",
|
||||
database_url,
|
||||
# Read the URL from DATABASE_URL (already in prisma_env) through
|
||||
# the schema's datasource, so the password stays out of argv.
|
||||
"--to-schema-datasource",
|
||||
schema_path,
|
||||
"--script",
|
||||
],
|
||||
stdout=open(migration_file, "w"),
|
||||
|
|
@ -519,6 +521,11 @@ class ProxyExtrasDBManager:
|
|||
diff_dir = Path(tempfile.mkdtemp(prefix="litellm_migration_diff_"))
|
||||
diff_sql_path = diff_dir / "migration.sql"
|
||||
|
||||
# Hand the URL to the child as DATABASE_URL, which the schema's datasource
|
||||
# reads. On the command line any local user could read the password.
|
||||
diff_env = _get_prisma_env()
|
||||
diff_env["DATABASE_URL"] = diff_url
|
||||
|
||||
# 1. Generate migration SQL for the diff between DB and schema
|
||||
try:
|
||||
logger.info("Generating migration diff between DB and schema.prisma...")
|
||||
|
|
@ -528,15 +535,15 @@ class ProxyExtrasDBManager:
|
|||
_get_prisma_command(),
|
||||
"migrate",
|
||||
"diff",
|
||||
"--from-url",
|
||||
diff_url,
|
||||
"--from-schema-datasource",
|
||||
schema_path,
|
||||
"--to-schema-datamodel",
|
||||
schema_path,
|
||||
"--script",
|
||||
],
|
||||
timeout=prisma_command_timeout(),
|
||||
stdout=f,
|
||||
env=_get_prisma_env(),
|
||||
env=diff_env,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(f"Failed to generate migration diff: {e.stderr}")
|
||||
|
|
|
|||
|
|
@ -70,20 +70,24 @@ def check_prisma_schema_diff_helper(db_url: str) -> tuple[bool, list[str]]:
|
|||
|
||||
verbose_logger.debug("Checking for Prisma schema diff...")
|
||||
timeout: Final = prisma_command_timeout()
|
||||
# schema.prisma reads its datasource url from DATABASE_URL, so hand the URL to
|
||||
# the child through its environment. On the command line the password would be
|
||||
# readable by any local user through ps or /proc/<pid>/cmdline.
|
||||
env: Final = {**os.environ, "DATABASE_URL": db_url}
|
||||
try:
|
||||
result: Final = run_prisma(
|
||||
[
|
||||
"prisma",
|
||||
"migrate",
|
||||
"diff",
|
||||
"--from-url",
|
||||
db_url,
|
||||
"--from-schema-datasource",
|
||||
"./schema.prisma",
|
||||
"--to-schema-datamodel",
|
||||
"./schema.prisma",
|
||||
"--script",
|
||||
],
|
||||
timeout=timeout,
|
||||
env=os.environ.copy(),
|
||||
env=env,
|
||||
)
|
||||
|
||||
sql_commands: Final = extract_sql_commands(result.stdout)
|
||||
|
|
|
|||
|
|
@ -724,6 +724,64 @@ class TestResolveAllMigrationsLedger:
|
|||
assert self._executed_sql(calls) == _PARTITIONED_DRIFT_SQL
|
||||
|
||||
|
||||
class TestMigrateDiffKeepsDatabaseUrlOutOfArgv:
|
||||
"""argv is world-readable through ps and /proc/<pid>/cmdline, so every
|
||||
`prisma migrate diff` has to take its URL from DATABASE_URL, read by the
|
||||
schema's datasource, instead of --from-url/--to-url."""
|
||||
|
||||
_SECRET = "s3cret-pw"
|
||||
|
||||
def _capture(self, monkeypatch):
|
||||
import litellm_proxy_extras.utils as utils_module
|
||||
|
||||
calls = []
|
||||
|
||||
def fake_run(cmd, **kwargs):
|
||||
calls.append((list(cmd), kwargs.get("env")))
|
||||
return _FakeCompleted()
|
||||
|
||||
monkeypatch.setattr(utils_module.prisma_toolchain, "run_prisma", fake_run)
|
||||
return calls
|
||||
|
||||
def _diff_call(self, calls):
|
||||
return next((cmd, env) for cmd, env in calls if "diff" in cmd)
|
||||
|
||||
def test_drift_diff_reads_direct_url_from_env(self, monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("DATABASE_URL", f"postgresql://u:{self._SECRET}@pooler:6543/db")
|
||||
monkeypatch.setenv("DIRECT_URL", f"postgresql://u:{self._SECRET}@direct:5432/db")
|
||||
calls = self._capture(monkeypatch)
|
||||
|
||||
ProxyExtrasDBManager._resolve_all_migrations(str(tmp_path), "schema.prisma")
|
||||
|
||||
cmd, env = self._diff_call(calls)
|
||||
assert not any(self._SECRET in arg for arg in cmd)
|
||||
assert cmd[cmd.index("--from-schema-datasource") + 1] == "schema.prisma"
|
||||
assert env["DATABASE_URL"] == f"postgresql://u:{self._SECRET}@direct:5432/db"
|
||||
|
||||
def test_drift_diff_falls_back_to_database_url(self, monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("DATABASE_URL", f"postgresql://u:{self._SECRET}@db:5432/db")
|
||||
monkeypatch.delenv("DIRECT_URL", raising=False)
|
||||
calls = self._capture(monkeypatch)
|
||||
|
||||
ProxyExtrasDBManager._resolve_all_migrations(str(tmp_path), "schema.prisma")
|
||||
|
||||
cmd, env = self._diff_call(calls)
|
||||
assert not any(self._SECRET in arg for arg in cmd)
|
||||
assert env["DATABASE_URL"] == f"postgresql://u:{self._SECRET}@db:5432/db"
|
||||
|
||||
def test_baseline_diff_reads_database_url_from_env(self, monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("DATABASE_URL", f"postgresql://u:{self._SECRET}@db:5432/db")
|
||||
monkeypatch.setattr(ProxyExtrasDBManager, "_get_prisma_dir", staticmethod(lambda: str(tmp_path)))
|
||||
calls = self._capture(monkeypatch)
|
||||
|
||||
assert ProxyExtrasDBManager._create_baseline_migration("schema.prisma") is True
|
||||
|
||||
cmd, env = self._diff_call(calls)
|
||||
assert not any(self._SECRET in arg for arg in cmd)
|
||||
assert cmd[cmd.index("--to-schema-datasource") + 1] == "schema.prisma"
|
||||
assert env["DATABASE_URL"] == f"postgresql://u:{self._SECRET}@db:5432/db"
|
||||
|
||||
|
||||
class TestPartitionedSpendLogsPushGuard:
|
||||
def _forbid_subprocess(self, monkeypatch):
|
||||
import litellm_proxy_extras.utils as utils_module
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
def test_check_migration_out_of_sync(mocker):
|
||||
"""
|
||||
Test that the check_prisma_schema_diff function
|
||||
|
|
@ -51,7 +52,7 @@ def test_migrate_diff_stops_at_its_budget_and_takes_its_process_tree_with_it(fak
|
|||
|
||||
assert check_prisma_schema_diff_helper("postgresql://u:p@localhost:9/x") == (False, [])
|
||||
assert fake_prisma_cli.calls == [
|
||||
["migrate", "diff", "--from-url", "postgresql://u:p@localhost:9/x",
|
||||
["migrate", "diff", "--from-schema-datasource", "./schema.prisma",
|
||||
"--to-schema-datamodel", "./schema.prisma", "--script"]
|
||||
]
|
||||
assert fake_prisma_cli.grandchild_is_gone(within_seconds=5)
|
||||
|
|
@ -69,3 +70,29 @@ def test_migrate_diff_without_the_prisma_runner_skips_instead_of_crashing_boot(m
|
|||
monkeypatch.setitem(sys.modules, "litellm_proxy_extras.prisma_toolchain", None)
|
||||
|
||||
assert check_prisma_schema_diff_helper("postgresql://u:p@localhost:9/x") == (False, [])
|
||||
|
||||
|
||||
def test_migrate_diff_passes_the_database_url_through_env_not_argv(monkeypatch):
|
||||
"""
|
||||
argv is world-readable through ps and /proc/<pid>/cmdline, so the URL (password
|
||||
included) has to reach `prisma migrate diff` through DATABASE_URL instead.
|
||||
"""
|
||||
from litellm_proxy_extras import prisma_toolchain
|
||||
|
||||
from litellm.proxy.db.check_migration import check_prisma_schema_diff_helper
|
||||
|
||||
db_url = "postgresql://user:s3cret@db:5432/litellm"
|
||||
monkeypatch.setenv("DATABASE_URL", "postgresql://other@elsewhere/db")
|
||||
captured = {}
|
||||
|
||||
def fake_run_prisma(argv, **kwargs):
|
||||
captured["argv"] = list(argv)
|
||||
captured["env"] = kwargs["env"]
|
||||
return subprocess.CompletedProcess(argv, 0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(prisma_toolchain, "run_prisma", fake_run_prisma)
|
||||
|
||||
assert check_prisma_schema_diff_helper(db_url) == (False, [])
|
||||
assert not any("s3cret" in arg for arg in captured["argv"])
|
||||
assert "--from-schema-datasource" in captured["argv"]
|
||||
assert captured["env"]["DATABASE_URL"] == db_url
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue