mirror of
https://github.com/9001/copyparty.git
synced 2026-04-12 15:22:32 -06:00
add optional update-checker (#1315)
can check if the current version has a known vulnerability, with the option to panic and exit if so, and otherwise show a warning in the controlpanel for admins --------- Co-authored-by: ed <s@ocv.me>
This commit is contained in:
parent
31b23843f2
commit
c6965f0614
|
|
@ -18,6 +18,15 @@
|
||||||
# (note: enable compression by adding .xz at the end)
|
# (note: enable compression by adding .xz at the end)
|
||||||
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
|
q, lo: $LOGS_DIRECTORY/%Y-%m%d.log
|
||||||
|
|
||||||
|
# enable version-checker by uncommenting one of the 'vc-url' lines below; this will
|
||||||
|
# periodically check if your copyparty version has a known security vulnerability,
|
||||||
|
# showing a warning on /?h (control-panel) for all users with permission 'a' or 'A'
|
||||||
|
#vc-url: https://api.github.com/repos/9001/copyparty/security-advisories?per_page=9
|
||||||
|
#vc-url: https://api.copyparty.eu/advisories
|
||||||
|
|
||||||
|
vc-age: 3 # how many hours to wait between each version-check
|
||||||
|
vc-exit # emergency-exit if a version-check indicates that the current version is vulnerable
|
||||||
|
|
||||||
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
|
# p: 80,443,3923 # listen on 80/443 as well (requires CAP_NET_BIND_SERVICE)
|
||||||
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
|
# i: 127.0.0.1 # only allow connections from localhost (reverse-proxies)
|
||||||
# ftp: 3921 # enable ftp server on port 3921
|
# ftp: 3921 # enable ftp server on port 3921
|
||||||
|
|
|
||||||
|
|
@ -1201,6 +1201,9 @@ def add_general(ap, nc, srvname):
|
||||||
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
|
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
|
||||||
ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
|
ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
|
||||||
ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="num cpu-cores for uploads/downloads (0=all); keeping the default is almost always best")
|
ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="num cpu-cores for uploads/downloads (0=all); keeping the default is almost always best")
|
||||||
|
ap2.add_argument("--vc-url", metavar="URL", type=u, default="", help="URL to check for vulnerable versions (default-disabled)")
|
||||||
|
ap2.add_argument("--vc-age", metavar="HOURS", type=int, default=3, help="how many hours to wait between vulnerability checks")
|
||||||
|
ap2.add_argument("--vc-exit", action="store_true", help="panic and exit if current version is vulnerable")
|
||||||
ap2.add_argument("--license", action="store_true", help="show licenses and exit")
|
ap2.add_argument("--license", action="store_true", help="show licenses and exit")
|
||||||
ap2.add_argument("--version", action="store_true", help="show versions and exit")
|
ap2.add_argument("--version", action="store_true", help="show versions and exit")
|
||||||
ap2.add_argument("--versionb", action="store_true", help="show version and exit")
|
ap2.add_argument("--versionb", action="store_true", help="show version and exit")
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,10 @@ class BrokerMp(object):
|
||||||
elif dest == "cb_httpsrv_up":
|
elif dest == "cb_httpsrv_up":
|
||||||
self.hub.cb_httpsrv_up()
|
self.hub.cb_httpsrv_up()
|
||||||
|
|
||||||
|
elif dest == "httpsrv.set_bad_ver":
|
||||||
|
for p in self.procs:
|
||||||
|
p.q_pend.put((0, dest, list(args)))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("what is " + str(dest))
|
raise Exception("what is " + str(dest))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,13 +53,18 @@ class BrokerThr(BrokerCli):
|
||||||
return NotExQueue(obj(*args)) # type: ignore
|
return NotExQueue(obj(*args)) # type: ignore
|
||||||
|
|
||||||
def say(self, dest: str, *args: Any) -> None:
|
def say(self, dest: str, *args: Any) -> None:
|
||||||
if dest == "httpsrv.listen":
|
if dest.startswith("httpsrv."):
|
||||||
self.httpsrv.listen(args[0], 1)
|
if dest == "httpsrv.listen":
|
||||||
return
|
self.httpsrv.listen(args[0], 1)
|
||||||
|
return
|
||||||
|
|
||||||
if dest == "httpsrv.set_netdevs":
|
if dest == "httpsrv.set_netdevs":
|
||||||
self.httpsrv.set_netdevs(args[0])
|
self.httpsrv.set_netdevs(args[0])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if dest == "httpsrv.set_bad_ver":
|
||||||
|
self.httpsrv.set_bad_ver()
|
||||||
|
return
|
||||||
|
|
||||||
# new ipc invoking managed service in hub
|
# new ipc invoking managed service in hub
|
||||||
obj = self.hub
|
obj = self.hub
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@ BADXFF = " due to dangerous misconfiguration (the http-header specified by --xff
|
||||||
BADXFF2 = ". Some copyparty features are now disabled as a safety measure.\n\n\n"
|
BADXFF2 = ". Some copyparty features are now disabled as a safety measure.\n\n\n"
|
||||||
BADXFP = ', or change the copyparty global-option "xf-proto" to another header-name to read this value from. Alternatively, if your reverseproxy is not able to provide a header similar to "X-Forwarded-Proto", then you must tell copyparty which protocol to assume; either "--xf-proto-fb=http" or "--xf-proto-fb=https"'
|
BADXFP = ', or change the copyparty global-option "xf-proto" to another header-name to read this value from. Alternatively, if your reverseproxy is not able to provide a header similar to "X-Forwarded-Proto", then you must tell copyparty which protocol to assume; either "--xf-proto-fb=http" or "--xf-proto-fb=https"'
|
||||||
BADXFFB = "<b>NOTE: serverlog has a message regarding your reverse-proxy config</b>"
|
BADXFFB = "<b>NOTE: serverlog has a message regarding your reverse-proxy config</b>"
|
||||||
|
BADVER = '<a class="r" href="https://github.com/9001/copyparty/security/advisories">Please upgrade copyparty; Your version has a vulnerability</a><p>(only users with permission "a" or "A" can see this message)</p>'
|
||||||
|
|
||||||
H_CONN_KEEPALIVE = "Connection: Keep-Alive"
|
H_CONN_KEEPALIVE = "Connection: Keep-Alive"
|
||||||
H_CONN_CLOSE = "Connection: Close"
|
H_CONN_CLOSE = "Connection: Close"
|
||||||
|
|
@ -5625,7 +5626,13 @@ class HttpCli(object):
|
||||||
no304=self.no304(),
|
no304=self.no304(),
|
||||||
k304vis=self.args.k304 > 0,
|
k304vis=self.args.k304 > 0,
|
||||||
no304vis=self.args.no304 > 0,
|
no304vis=self.args.no304 > 0,
|
||||||
msg=BADXFFB if hasattr(self, "bad_xff") else "",
|
msg=(
|
||||||
|
BADVER
|
||||||
|
if self.conn.hsrv.bad_ver and self.can_admin
|
||||||
|
else BADXFFB
|
||||||
|
if hasattr(self, "bad_xff")
|
||||||
|
else ""
|
||||||
|
),
|
||||||
ver=S_VERSION if show_ver else "",
|
ver=S_VERSION if show_ver else "",
|
||||||
chpw=self.args.chpw and self.uname != "*",
|
chpw=self.args.chpw and self.uname != "*",
|
||||||
ahttps="" if self.is_https else "https://" + self.host + self.req,
|
ahttps="" if self.is_https else "https://" + self.host + self.req,
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ class HttpSrv(object):
|
||||||
self.name = "hsrv" + nsuf
|
self.name = "hsrv" + nsuf
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.u2mutex = threading.Lock()
|
self.u2mutex = threading.Lock()
|
||||||
|
self.bad_ver = False
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
|
||||||
self.tp_nthr = 0 # actual
|
self.tp_nthr = 0 # actual
|
||||||
|
|
@ -239,6 +240,9 @@ class HttpSrv(object):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def set_bad_ver(self) -> None:
|
||||||
|
self.bad_ver = True
|
||||||
|
|
||||||
def set_netdevs(self, netdevs: dict[str, Netdev]) -> None:
|
def set_netdevs(self, netdevs: dict[str, Netdev]) -> None:
|
||||||
ips = set()
|
ips = set()
|
||||||
for ip, _ in self.bound:
|
for ip, _ in self.bound:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
|
||||||
import argparse
|
import argparse
|
||||||
import atexit
|
import atexit
|
||||||
import errno
|
import errno
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
@ -27,6 +28,7 @@ if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
|
from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, E, EnvParams, unicode
|
||||||
|
from .__version__ import S_VERSION, VERSION
|
||||||
from .authsrv import BAD_CFG, AuthSrv, derive_args, n_du_who, n_ver_who
|
from .authsrv import BAD_CFG, AuthSrv, derive_args, n_du_who, n_ver_who
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .cert import ensure_cert
|
from .cert import ensure_cert
|
||||||
|
|
@ -75,6 +77,7 @@ from .util import (
|
||||||
mp,
|
mp,
|
||||||
odfusion,
|
odfusion,
|
||||||
pybin,
|
pybin,
|
||||||
|
read_utf8,
|
||||||
start_log_thrs,
|
start_log_thrs,
|
||||||
start_stackmon,
|
start_stackmon,
|
||||||
termsize,
|
termsize,
|
||||||
|
|
@ -95,6 +98,11 @@ if TYPE_CHECKING:
|
||||||
if PY2:
|
if PY2:
|
||||||
range = xrange # type: ignore
|
range = xrange # type: ignore
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
from urllib2 import Request, urlopen
|
||||||
|
else:
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
|
|
||||||
VER_IDP_DB = 1
|
VER_IDP_DB = 1
|
||||||
VER_SESSION_DB = 1
|
VER_SESSION_DB = 1
|
||||||
|
|
@ -1382,6 +1390,8 @@ class SvcHub(object):
|
||||||
Daemon(self.tcpsrv.netmon, "netmon")
|
Daemon(self.tcpsrv.netmon, "netmon")
|
||||||
|
|
||||||
Daemon(self.thr_httpsrv_up, "sig-hsrv-up2")
|
Daemon(self.thr_httpsrv_up, "sig-hsrv-up2")
|
||||||
|
if self.args.vc_url:
|
||||||
|
Daemon(self.check_ver, "ver-chk")
|
||||||
|
|
||||||
sigs = [signal.SIGINT, signal.SIGTERM]
|
sigs = [signal.SIGINT, signal.SIGTERM]
|
||||||
if not ANYWIN:
|
if not ANYWIN:
|
||||||
|
|
@ -1778,3 +1788,79 @@ class SvcHub(object):
|
||||||
zb = gzip.compress(zb)
|
zb = gzip.compress(zb)
|
||||||
zs = ub64enc(zb).decode("ascii")
|
zs = ub64enc(zb).decode("ascii")
|
||||||
self.log("stacks", zs)
|
self.log("stacks", zs)
|
||||||
|
|
||||||
|
def check_ver(self) -> None:
|
||||||
|
next_chk = 0
|
||||||
|
# self.args.vc_age = 2 / 60
|
||||||
|
fpath = os.path.join(self.E.cfg, "vuln_advisory.json")
|
||||||
|
while not self.stopping:
|
||||||
|
now = time.time()
|
||||||
|
if now < next_chk:
|
||||||
|
time.sleep(min(999, next_chk - now))
|
||||||
|
continue
|
||||||
|
|
||||||
|
age = 0
|
||||||
|
jtxt = ""
|
||||||
|
src = "[cache] "
|
||||||
|
try:
|
||||||
|
mtime = os.path.getmtime(fpath)
|
||||||
|
age = time.time() - mtime
|
||||||
|
if age < self.args.vc_age * 3600 - 69:
|
||||||
|
zs, jtxt = read_utf8(None, fpath, True).split("\n", 1)
|
||||||
|
if zs != self.args.vc_url:
|
||||||
|
jtxt = ""
|
||||||
|
except Exception as e:
|
||||||
|
t = "will download advisory because cache-file %r could not be read: %s"
|
||||||
|
self.log("ver-chk", t % (fpath, e), 6)
|
||||||
|
|
||||||
|
if not jtxt:
|
||||||
|
src = ""
|
||||||
|
age = 0
|
||||||
|
try:
|
||||||
|
req = Request(self.args.vc_url)
|
||||||
|
with urlopen(req, timeout=30) as f:
|
||||||
|
jtxt = f.read().decode("utf-8")
|
||||||
|
try:
|
||||||
|
with open(fpath, "wb") as f:
|
||||||
|
zs = self.args.vc_url + "\n" + jtxt
|
||||||
|
f.write(zs.encode("utf-8"))
|
||||||
|
except Exception as e:
|
||||||
|
t = "failed to write advisory to cache; %s"
|
||||||
|
self.log("ver-chk", t % (e,), 3)
|
||||||
|
except Exception as e:
|
||||||
|
t = "failed to fetch vulnerability advisory; %s"
|
||||||
|
self.log("ver-chk", t % (e,), 1)
|
||||||
|
|
||||||
|
next_chk = time.time() + 699
|
||||||
|
if not jtxt:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
advisories = json.loads(jtxt)
|
||||||
|
for adv in advisories:
|
||||||
|
if adv.get("state") == "closed":
|
||||||
|
continue
|
||||||
|
vuln = {}
|
||||||
|
for x in adv["vulnerabilities"]:
|
||||||
|
if x["package"]["name"].lower() == "copyparty":
|
||||||
|
vuln = x
|
||||||
|
break
|
||||||
|
if not vuln:
|
||||||
|
continue
|
||||||
|
sver = vuln["patched_versions"].strip(".v")
|
||||||
|
tver = tuple([int(x) for x in sver.split(".")])
|
||||||
|
if VERSION < tver:
|
||||||
|
zs = json.dumps(adv, indent=2)
|
||||||
|
t = "your version (%s) has a vulnerability! please upgrade:\n%s"
|
||||||
|
self.log("ver-chk", t % (S_VERSION, zs), 1)
|
||||||
|
self.broker.say("httpsrv.set_bad_ver")
|
||||||
|
if self.args.vc_exit:
|
||||||
|
self.shutdown()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
t = "%sok; v%s and newer is safe"
|
||||||
|
self.log("ver-chk", t % (src, sver), 2)
|
||||||
|
next_chk = time.time() + self.args.vc_age * 3600 - age
|
||||||
|
except Exception as e:
|
||||||
|
t = "failed to process vulnerability advisory; %s"
|
||||||
|
self.log("ver-chk", t % (min_ex()), 1)
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,15 @@
|
||||||
|
|
||||||
# show versions and exit
|
# show versions and exit
|
||||||
version
|
version
|
||||||
|
|
||||||
|
# url to check against for vulnerable versions (default disabled); setting this will enable automatic
|
||||||
|
# vulnerability checks. the notification, in case you are running a vulnerable version, is shown on the
|
||||||
|
# admin panel (/?h) and only for users with admin permissions. you can choose between the value given here,
|
||||||
|
# or alternatively use https://api.copyparty.eu/security-advisories, or your own custom endpoint
|
||||||
|
vc-url: https://api.github.com/repos/9001/copyparty/security-advisories
|
||||||
|
|
||||||
|
# how many seconds to wait between vulnerability checks; default is 86400 (= 1 day).
|
||||||
|
vc-interval: 86400
|
||||||
|
|
||||||
###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
|
###000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\
|
||||||
###// qr options \\000000000000000000000000000000000000000000000000000000000000000000000000000\
|
###// qr options \\000000000000000000000000000000000000000000000000000000000000000000000000000\
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue