feat: 远程目录 remote_dir 改用相对路径(从远程用户 home 目录开始)
- 新增 _resolve_remote_dir(相对→~/xxx)+ _validate_remote_dir(防绝对路径/..穿越) - 移除 _FORBIDDEN_DIRS 系统目录黑名单(相对路径天然限制在 home 内) - rsync 迁移 + bwrap --bind 统一走 _resolve_remote_dir
This commit is contained in:
parent
07383a9dd5
commit
dc4379b235
@ -132,12 +132,28 @@ def get_org_pubkey(org_id: str) -> dict:
|
|||||||
_HOST_RE = re.compile(r"^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$")
|
_HOST_RE = re.compile(r"^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$")
|
||||||
_USER_RE = re.compile(r"^[a-zA-Z0-9._-]+$")
|
_USER_RE = re.compile(r"^[a-zA-Z0-9._-]+$")
|
||||||
|
|
||||||
# 禁止作为 remote_dir 的系统目录(防 rsync --delete 误删系统文件)
|
# remote_dir 现在是相对路径(从远程登录用户的 home 目录开始)。
|
||||||
_FORBIDDEN_DIRS = {
|
# 相对路径天然限制在 home 内,无需系统目录黑名单,只需防路径穿越(..)。
|
||||||
"/", "/bin", "/sbin", "/usr", "/etc", "/lib", "/lib64", "/var",
|
|
||||||
"/root", "/boot", "/dev", "/proc", "/sys", "/tmp", "/opt", "/run",
|
def _resolve_remote_dir(remote_dir: str) -> str:
|
||||||
"/srv", "/mnt", "/media", "/home",
|
"""把相对路径规范化为远程 home 下的路径(~/xxx)。"""
|
||||||
}
|
rel = (remote_dir or "").strip().strip("/")
|
||||||
|
return "~/" + rel if rel else "~"
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_remote_dir(remote_dir: str) -> str:
|
||||||
|
"""校验 remote_dir(相对路径),返回错误消息(None 表示通过)。"""
|
||||||
|
d = (remote_dir or "").strip()
|
||||||
|
if not d:
|
||||||
|
return "缺少 remote_dir"
|
||||||
|
if d.startswith("/"):
|
||||||
|
return "remote_dir 必须是相对路径(从远程用户 home 目录开始)"
|
||||||
|
parts = [p for p in d.split("/") if p]
|
||||||
|
if ".." in parts:
|
||||||
|
return "remote_dir 不能包含 .."
|
||||||
|
if not parts:
|
||||||
|
return "remote_dir 不能为空"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _validate_remote(remote_config: dict) -> str:
|
def _validate_remote(remote_config: dict) -> str:
|
||||||
@ -167,19 +183,9 @@ def _validate_remote(remote_config: dict) -> str:
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return "remote_port 必须是数字"
|
return "remote_port 必须是数字"
|
||||||
|
|
||||||
remote_dir = (remote_config.get("remote_dir") or "").strip()
|
err = _validate_remote_dir(remote_config.get("remote_dir", ""))
|
||||||
if not remote_dir:
|
if err:
|
||||||
return "缺少 remote_dir"
|
return err
|
||||||
if not remote_dir.startswith("/"):
|
|
||||||
return "remote_dir 必须是绝对路径"
|
|
||||||
_dir = remote_dir.rstrip("/")
|
|
||||||
if not _dir:
|
|
||||||
return "remote_dir 不能是根目录 /"
|
|
||||||
if _dir in _FORBIDDEN_DIRS:
|
|
||||||
return "remote_dir 不能是系统目录 " + _dir
|
|
||||||
for d in _FORBIDDEN_DIRS:
|
|
||||||
if d != "/" and _dir.startswith(d + "/"):
|
|
||||||
return "remote_dir 不能位于系统目录 " + d + " 下"
|
|
||||||
|
|
||||||
key = (remote_config.get("remote_key_path") or "").strip()
|
key = (remote_config.get("remote_key_path") or "").strip()
|
||||||
if key:
|
if key:
|
||||||
@ -214,17 +220,9 @@ async def migrate_work_dir(account_name: str, from_mode: str, to_mode: str, env:
|
|||||||
|
|
||||||
# remote_dir 安全校验(纵深防御:DB 脏数据/历史记录也拦一道)
|
# remote_dir 安全校验(纵深防御:DB 脏数据/历史记录也拦一道)
|
||||||
if from_mode == "remote" or to_mode == "remote":
|
if from_mode == "remote" or to_mode == "remote":
|
||||||
remote_dir = (env.get("remote_dir") or "").strip()
|
err = _validate_remote_dir(env.get("remote_dir", ""))
|
||||||
if not remote_dir:
|
if err:
|
||||||
return {"ok": False, "error": "缺少远程目录 remote_dir"}
|
return {"ok": False, "error": err}
|
||||||
_d = remote_dir.rstrip("/")
|
|
||||||
if not _d:
|
|
||||||
return {"ok": False, "error": "远程目录是根目录,拒绝迁移"}
|
|
||||||
if _d in _FORBIDDEN_DIRS:
|
|
||||||
return {"ok": False, "error": "远程目录是系统目录,拒绝迁移: " + _d}
|
|
||||||
for d in _FORBIDDEN_DIRS:
|
|
||||||
if d != "/" and _d.startswith(d + "/"):
|
|
||||||
return {"ok": False, "error": "远程目录位于系统目录下,拒绝迁移: " + d}
|
|
||||||
|
|
||||||
# 构造 rsync 命令
|
# 构造 rsync 命令
|
||||||
try:
|
try:
|
||||||
@ -233,7 +231,7 @@ async def migrate_work_dir(account_name: str, from_mode: str, to_mode: str, env:
|
|||||||
remote_dir = env.get("remote_dir", "")
|
remote_dir = env.get("remote_dir", "")
|
||||||
if not remote_dir:
|
if not remote_dir:
|
||||||
return {"ok": False, "error": "缺少远程目录 remote_dir"}
|
return {"ok": False, "error": "缺少远程目录 remote_dir"}
|
||||||
target = _ssh_target(env) + ":" + remote_dir
|
target = _ssh_target(env) + ":" + _resolve_remote_dir(remote_dir)
|
||||||
cmd = ["rsync", "-az", "--delete", "-e",
|
cmd = ["rsync", "-az", "--delete", "-e",
|
||||||
"ssh " + " ".join(_ssh_common_args(env)),
|
"ssh " + " ".join(_ssh_common_args(env)),
|
||||||
local_dir.rstrip("/") + "/", target.rstrip("/") + "/"]
|
local_dir.rstrip("/") + "/", target.rstrip("/") + "/"]
|
||||||
@ -242,7 +240,7 @@ async def migrate_work_dir(account_name: str, from_mode: str, to_mode: str, env:
|
|||||||
remote_dir = env.get("remote_dir", "")
|
remote_dir = env.get("remote_dir", "")
|
||||||
if not remote_dir:
|
if not remote_dir:
|
||||||
return {"ok": False, "error": "缺少远程目录 remote_dir"}
|
return {"ok": False, "error": "缺少远程目录 remote_dir"}
|
||||||
src = _ssh_target(env) + ":" + remote_dir
|
src = _ssh_target(env) + ":" + _resolve_remote_dir(remote_dir)
|
||||||
cmd = ["rsync", "-az", "--delete", "-e",
|
cmd = ["rsync", "-az", "--delete", "-e",
|
||||||
"ssh " + " ".join(_ssh_common_args(env)),
|
"ssh " + " ".join(_ssh_common_args(env)),
|
||||||
src.rstrip("/") + "/", local_dir.rstrip("/") + "/"]
|
src.rstrip("/") + "/", local_dir.rstrip("/") + "/"]
|
||||||
@ -366,7 +364,7 @@ def _remote_bwrap_cmd(env: dict, deploy_dir: str, command: str, workdir: str = "
|
|||||||
"--proc", "/proc",
|
"--proc", "/proc",
|
||||||
"--dev", "/dev",
|
"--dev", "/dev",
|
||||||
"--tmpfs", "/tmp",
|
"--tmpfs", "/tmp",
|
||||||
"--bind", deploy_dir, "/home",
|
"--bind", _resolve_remote_dir(deploy_dir), "/home",
|
||||||
"--chdir", "/home",
|
"--chdir", "/home",
|
||||||
"--setenv", "HOME", "/home",
|
"--setenv", "HOME", "/home",
|
||||||
"--", "bash", "-c", command,
|
"--", "bash", "-c", command,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user