mirror of
https://github.com/9001/copyparty.git
synced 2025-09-28 12:42:26 -06:00
restrict runtime-state in $TMP; closes #747
the preferred locations (XDG_CONFIG_HOME and ~/.config) are trusted and will behave as before, because they are only writable by the current unix-user but when an emergency fallback location ($TMPDIR or /tmp) is used because none of the preferred locations are writable, then this will now force-disable sessions-db, idp-db, chpw, and shares this security safeguard can be overridden with --unsafe-state will now also create the config folder with chmod 700 (rwx------)
This commit is contained in:
parent
230a146209
commit
e6755aa8a1
|
@ -2318,6 +2318,7 @@ buggy feature? rip it out by setting any of the following environment variables
|
|||
| `PRTY_NO_SQLITE` | disable all database-related functionality (file indexing, metadata indexing, most file deduplication logic) |
|
||||
| `PRTY_NO_TLS` | disable native HTTPS support; if you still want to accept HTTPS connections then TLS must now be terminated by a reverse-proxy |
|
||||
| `PRTY_NO_TPOKE` | disable systemd-tmpfilesd avoider |
|
||||
| `PRTY_UNSAFE_STATE` | allow storing secrets into emergency-fallback locations |
|
||||
|
||||
example: `PRTY_NO_IFADDR=1 python3 copyparty-sfx.py`
|
||||
|
||||
|
|
|
@ -112,6 +112,7 @@ class EnvParams(object):
|
|||
self.t0 = time.time()
|
||||
self.mod = ""
|
||||
self.cfg = ""
|
||||
self.scfg = True
|
||||
|
||||
|
||||
E = EnvParams()
|
||||
|
|
|
@ -36,6 +36,7 @@ from .__init__ import (
|
|||
)
|
||||
from .__version__ import CODENAME, S_BUILD_DT, S_VERSION
|
||||
from .authsrv import expand_config_file, split_cfg_ln, upgrade_cfg_fmt
|
||||
from .bos import bos
|
||||
from .cfg import flagcats, onedash
|
||||
from .svchub import SvcHub
|
||||
from .util import (
|
||||
|
@ -186,7 +187,7 @@ def init_E(EE: EnvParams) -> None:
|
|||
|
||||
E = EE # pylint: disable=redefined-outer-name
|
||||
|
||||
def get_unixdir() -> str:
|
||||
def get_unixdir() -> tuple[str, bool]:
|
||||
paths: list[tuple[Callable[..., Any], str]] = [
|
||||
(os.environ.get, "XDG_CONFIG_HOME"),
|
||||
(os.path.expanduser, "~/.config"),
|
||||
|
@ -197,6 +198,8 @@ def init_E(EE: EnvParams) -> None:
|
|||
]
|
||||
errs = []
|
||||
for npath, (pf, pa) in enumerate(paths):
|
||||
priv = npath < 2 # private/trusted location
|
||||
ram = npath > 1 # "nonvolatile"; not semantically same as `not priv`
|
||||
p = ""
|
||||
try:
|
||||
p = pf(pa)
|
||||
|
@ -206,15 +209,21 @@ def init_E(EE: EnvParams) -> None:
|
|||
p = os.path.normpath(p)
|
||||
mkdir = not os.path.isdir(p)
|
||||
if mkdir:
|
||||
os.mkdir(p)
|
||||
os.mkdir(p, 0o700)
|
||||
|
||||
p = os.path.join(p, "copyparty")
|
||||
if not priv and os.path.isdir(p):
|
||||
uid = os.geteuid()
|
||||
if os.stat(p).st_uid != uid:
|
||||
p += ".%s" % (uid,)
|
||||
if os.path.isdir(p) and os.stat(p).st_uid != uid:
|
||||
raise Exception("filesystem has broken unix permissions")
|
||||
try:
|
||||
os.listdir(p)
|
||||
except:
|
||||
os.mkdir(p)
|
||||
os.mkdir(p, 0o700)
|
||||
|
||||
if npath > 1:
|
||||
if ram:
|
||||
t = "Using %s/copyparty [%s] for config; filekeys/dirkeys will change on every restart. Consider setting XDG_CONFIG_HOME or giving the unix-user a ~/.config/"
|
||||
errs.append(t % (pa, p))
|
||||
elif mkdir:
|
||||
|
@ -226,13 +235,14 @@ def init_E(EE: EnvParams) -> None:
|
|||
if errs:
|
||||
warn(". ".join(errs))
|
||||
|
||||
return p # type: ignore
|
||||
return p, priv
|
||||
except Exception as ex:
|
||||
if p and npath < 2:
|
||||
if p:
|
||||
t = "Unable to store config in %s [%s] due to %r"
|
||||
errs.append(t % (pa, p, ex))
|
||||
|
||||
raise Exception("could not find a writable path for config")
|
||||
t = "could not find a writable path for runtime state:\n> %s"
|
||||
raise Exception(t % ("\n> ".join(errs)))
|
||||
|
||||
E.mod = os.path.dirname(os.path.realpath(__file__))
|
||||
if E.mod.endswith("__init__"):
|
||||
|
@ -247,7 +257,7 @@ def init_E(EE: EnvParams) -> None:
|
|||
p = os.path.abspath(os.path.realpath(p))
|
||||
p = os.path.join(p, "copyparty")
|
||||
if not os.path.isdir(p):
|
||||
os.mkdir(p)
|
||||
os.mkdir(p, 0o700)
|
||||
os.listdir(p)
|
||||
except:
|
||||
p = ""
|
||||
|
@ -260,11 +270,11 @@ def init_E(EE: EnvParams) -> None:
|
|||
elif sys.platform == "darwin":
|
||||
E.cfg = os.path.expanduser("~/Library/Preferences/copyparty")
|
||||
else:
|
||||
E.cfg = get_unixdir()
|
||||
E.cfg, E.scfg = get_unixdir()
|
||||
|
||||
E.cfg = E.cfg.replace("\\", "/")
|
||||
try:
|
||||
os.makedirs(E.cfg)
|
||||
bos.makedirs(E.cfg, bos.MKD_700)
|
||||
except:
|
||||
if not os.path.isdir(E.cfg):
|
||||
raise
|
||||
|
@ -1453,6 +1463,7 @@ def add_yolo(ap):
|
|||
ap2.add_argument("--no-fnugg", action="store_true", help="disable the smoketest for caching-related issues in the web-UI")
|
||||
ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
|
||||
ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)")
|
||||
ap2.add_argument("--unsafe-state", action="store_true", help="when one of the emergency fallback locations are used for runtime state ($TMPDIR, /tmp), certain features will be force-disabled for security reasons by default. This option overrides that safeguard and allows unsafe storage of secrets")
|
||||
|
||||
|
||||
def add_optouts(ap):
|
||||
|
|
|
@ -976,6 +976,24 @@ class SvcHub(object):
|
|||
t = "WARNING:\nDisabling WebDAV support because dxml selftest failed. Please report this bug;\n%s\n...and include the following information in the bug-report:\n%s | expat %s\n"
|
||||
self.log("root", t % (URL_BUG, VERSIONS, expat_ver()), 1)
|
||||
|
||||
if not E.scfg and not al.unsafe_state and not os.getenv("PRTY_UNSAFE_STATE"):
|
||||
t = "because runtime config is currently being stored in an untrusted emergency-fallback location. Please fix your environment so either XDG_CONFIG_HOME or ~/.config can be used instead, or disable this safeguard with --unsafe-state or env-var PRTY_UNSAFE_STATE=1."
|
||||
if not al.no_ses:
|
||||
al.no_ses = True
|
||||
t2 = "A consequence of this misconfiguration is that passwords will now be sent in the HTTP-header of every request!"
|
||||
self.log("root", "WARNING:\nWill disable sessions %s %s" % (t, t2), 1)
|
||||
if al.idp_store == 1:
|
||||
al.idp_store = 0
|
||||
self.log("root", "WARNING:\nDisabling --idp-store %s" % (t,), 3)
|
||||
if al.idp_store:
|
||||
t2 = "ERROR: Cannot enable --idp-store %s" % (t,)
|
||||
self.log("root", t2, 1)
|
||||
raise Exception(t2)
|
||||
if al.shr:
|
||||
t2 = "ERROR: Cannot enable shares %s" % (t,)
|
||||
self.log("root", t2, 1)
|
||||
raise Exception(t2)
|
||||
|
||||
def _process_config(self) -> bool:
|
||||
al = self.args
|
||||
|
||||
|
|
Loading…
Reference in a new issue