mirror of
https://github.com/9001/copyparty.git
synced 2025-08-16 00:22:13 -06:00
restrict account to ip/subnet; closes #397
This commit is contained in:
parent
a4649d1e71
commit
62e072a2ed
15
README.md
15
README.md
|
@ -90,6 +90,7 @@ made in Norway 🇳🇴
|
|||
* [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
|
||||
* [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
|
||||
* [ip auth](#ip-auth) - autologin based on IP range (CIDR)
|
||||
* [restrict to ip](#restrict-to-ip) - limit a user to certain IP ranges (CIDR)
|
||||
* [identity providers](#identity-providers) - replace copyparty passwords with oauth and such
|
||||
* [generic header auth](#generic-header-auth) - other ways to auth by header
|
||||
* [user-changeable passwords](#user-changeable-passwords) - if permitted, users can change their own passwords
|
||||
|
@ -1897,6 +1898,20 @@ repeat the option to map additional subnets
|
|||
**be careful with this one!** if you have a reverseproxy, then you definitely want to make sure you have [real-ip](#real-ip) configured correctly, and it's probably a good idea to nullmap the reverseproxy's IP just in case; so if your reverseproxy is sending requests from `172.24.27.9` then that would be `--ipu=172.24.27.9/32=`
|
||||
|
||||
|
||||
### restrict to ip
|
||||
|
||||
limit a user to certain IP ranges (CIDR) , using the global-option `--ipr`
|
||||
|
||||
for example, if the user `spartacus` should get rejected if they're not connecting from an IP that starts with `192.168.123` or `172.16`, then you can either specify `--ipr=192.168.123.0/24,172.16.0.0/16=spartacus` as a commandline option, or put this in a config file:
|
||||
|
||||
```yaml
|
||||
[global]
|
||||
ipr: 192.168.123.0/24,172.16.0.0/16=spartacus
|
||||
```
|
||||
|
||||
repeat the option to map additional users
|
||||
|
||||
|
||||
## identity providers
|
||||
|
||||
replace copyparty passwords with oauth and such
|
||||
|
|
|
@ -1229,7 +1229,9 @@ def add_auth(ap):
|
|||
ap2.add_argument("--no-ses", action="store_true", help="disable sessions; use plaintext passwords in cookies")
|
||||
ap2.add_argument("--grp-all", metavar="NAME", type=u, default="acct", help="the name of the auto-generated group which contains every username which is known")
|
||||
ap2.add_argument("--ipu", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m users with IP matching \033[33mCIDR\033[0m are auto-authenticated as username \033[33mUSR\033[0m; example: [\033[32m172.16.24.0/24=dave]")
|
||||
ap2.add_argument("--ipr", metavar="CIDR=USR", type=u, action="append", help="\033[34mREPEATABLE:\033[0m username \033[33mUSR\033[0m can only connect from an IP matching one or more \033[33mCIDR\033[0m (comma-sep.); example: [\033[32m192.168.123.0/24,172.16.0.0/16=dave]")
|
||||
ap2.add_argument("--have-idp-hdrs", type=u, default="", help=argparse.SUPPRESS)
|
||||
ap2.add_argument("--have-ipu-or-ipr", type=u, default="", help=argparse.SUPPRESS)
|
||||
|
||||
|
||||
def add_chpw(ap):
|
||||
|
|
|
@ -1690,6 +1690,7 @@ class AuthSrv(object):
|
|||
raise
|
||||
|
||||
self.args.have_idp_hdrs = bool(self.args.idp_h_usr or self.args.idp_hm_usr)
|
||||
self.args.have_ipu_or_ipr = bool(self.args.ipu or self.args.ipr)
|
||||
|
||||
self.setup_pwhash(acct)
|
||||
defpw = acct.copy()
|
||||
|
|
|
@ -96,6 +96,10 @@ class FtpAuth(DummyAuthorizer):
|
|||
|
||||
if args.ipu and uname == "*":
|
||||
uname = args.ipu_iu[args.ipu_nm.map(ip)]
|
||||
if args.ipr and uname in args.ipr_u:
|
||||
if not args.ipr_u[uname].map(ip):
|
||||
logging.warning("username [%s] rejected by --ipr", uname)
|
||||
uname = "*"
|
||||
|
||||
if not uname or not (asrv.vfs.aread.get(uname) or asrv.vfs.awrite.get(uname)):
|
||||
g = self.hub.gpwd
|
||||
|
|
|
@ -695,8 +695,14 @@ class HttpCli(object):
|
|||
else:
|
||||
self.log("unknown username: %r" % (idp_usr,), 1)
|
||||
|
||||
if self.args.ipu and self.uname == "*":
|
||||
self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
|
||||
if self.args.have_ipu_or_ipr:
|
||||
if self.args.ipu and self.uname == "*":
|
||||
self.uname = self.conn.ipu_iu[self.conn.ipu_nm.map(self.ip)]
|
||||
ipr = self.conn.hsrv.ipr
|
||||
if ipr and self.uname in ipr:
|
||||
if not ipr[self.uname].map(self.ip):
|
||||
self.log("username [%s] rejected by --ipr" % (self.uname,), 3)
|
||||
self.uname = "*"
|
||||
|
||||
self.rvol = self.asrv.vfs.aread[self.uname]
|
||||
self.wvol = self.asrv.vfs.awrite[self.uname]
|
||||
|
|
|
@ -70,6 +70,7 @@ from .util import (
|
|||
build_netmap,
|
||||
has_resource,
|
||||
ipnorm,
|
||||
load_ipr,
|
||||
load_ipu,
|
||||
load_resource,
|
||||
min_ex,
|
||||
|
@ -193,6 +194,11 @@ class HttpSrv(object):
|
|||
else:
|
||||
self.ipu_iu = self.ipu_nm = None
|
||||
|
||||
if self.args.ipr:
|
||||
self.ipr = load_ipr(self.log, self.args.ipr)
|
||||
else:
|
||||
self.ipr = None
|
||||
|
||||
self.ipa_nm = build_netmap(self.args.ipa)
|
||||
self.xff_nm = build_netmap(self.args.xff_src)
|
||||
self.xff_lan = build_netmap("lan")
|
||||
|
|
|
@ -66,6 +66,7 @@ from .util import (
|
|||
build_netmap,
|
||||
expat_ver,
|
||||
gzip,
|
||||
load_ipr,
|
||||
load_ipu,
|
||||
lock_file,
|
||||
min_ex,
|
||||
|
@ -259,6 +260,10 @@ class SvcHub(object):
|
|||
setattr(args, "ipu_iu", iu)
|
||||
setattr(args, "ipu_nm", nm)
|
||||
|
||||
if args.ipr:
|
||||
ipr = load_ipr(self.log, args.ipr, True)
|
||||
setattr(args, "ipr_u", ipr)
|
||||
|
||||
for zs in "ah_salt fk_salt dk_salt".split():
|
||||
if getattr(args, "show_%s" % (zs,)):
|
||||
self.log("root", "effective %s is %s" % (zs, getattr(args, zs)))
|
||||
|
@ -432,6 +437,9 @@ class SvcHub(object):
|
|||
getattr(args, zs).mutex = threading.Lock()
|
||||
except:
|
||||
pass
|
||||
if args.ipr:
|
||||
for nm in args.ipr_u.values():
|
||||
nm.mutex = threading.Lock()
|
||||
|
||||
def _db_onfail_ses(self) -> None:
|
||||
self.args.no_ses = True
|
||||
|
|
|
@ -2954,6 +2954,27 @@ def load_ipu(
|
|||
return ip_u, nm
|
||||
|
||||
|
||||
def load_ipr(
|
||||
log: "RootLogger", iprs: list[str], defer_mutex: bool = False
|
||||
) -> dict[str, NetMap]:
|
||||
ret = {}
|
||||
for ipr in iprs:
|
||||
try:
|
||||
zs, uname = ipr.split("=")
|
||||
cidrs = zs.split(",")
|
||||
except:
|
||||
t = "\n invalid value %r for argument --ipr; must be CIDR[,CIDR[,...]]=UNAME (192.168.0.0/16=amelia)"
|
||||
raise Exception(t % (ipr,))
|
||||
try:
|
||||
nm = NetMap(["::"], cidrs, True, True, defer_mutex)
|
||||
except Exception as ex:
|
||||
t = "failed to translate --ipr into netmap, probably due to invalid config: %r"
|
||||
log("root", t % (ex,), 1)
|
||||
raise
|
||||
ret[uname] = nm
|
||||
return ret
|
||||
|
||||
|
||||
def yieldfile(fn: str, bufsz: int) -> Generator[bytes, None, None]:
|
||||
readsz = min(bufsz, 128 * 1024)
|
||||
with open(fsenc(fn), "rb", bufsz) as f:
|
||||
|
|
|
@ -170,7 +170,7 @@ class Cfg(Namespace):
|
|||
ex = "ban_403 ban_404 ban_422 ban_pw ban_pwc ban_url spinner"
|
||||
ka.update(**{k: "no" for k in ex.split()})
|
||||
|
||||
ex = "ext_th grp idp_h_usr idp_hm_usr on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm"
|
||||
ex = "ext_th grp idp_h_usr idp_hm_usr ipr on403 on404 xac xad xar xau xban xbc xbd xbr xbu xiu xm"
|
||||
ka.update(**{k: [] for k in ex.split()})
|
||||
|
||||
ex = "exp_lg exp_md"
|
||||
|
|
Loading…
Reference in a new issue