IdP: add safeguard --idp-h-key and also require --xff-src

This commit is contained in:
ed 2024-03-12 22:57:47 +00:00
parent e8b7f65f82
commit d71f844b43
7 changed files with 55 additions and 7 deletions

View file

@ -1266,9 +1266,9 @@ replace copyparty passwords with oauth and such
work is [ongoing](https://github.com/9001/copyparty/issues/62) to support authenticating / authorizing users based on a separate authentication proxy, which makes it possible to support oauth, single-sign-on, etc.
it is currently possible to specify `--idp-h-usr x-username`; copyparty will then skip password validation and blindly trust the username specified in the `X-Username` request header
there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik) which is hopefully a good starting point (alternatively see [./docs/idp.md](./docs/idp.md) if you're the DIY type)
the remaining stuff (accepting user groups through another header, creating volumes on the fly) are still to-do; configuration will probably [look like this](./docs/examples/docker/idp/copyparty.conf)
a more complete example of the copyparty configuration options [look like this](./docs/examples/docker/idp/copyparty.conf)
## hiding from google

View file

@ -956,6 +956,7 @@ def add_auth(ap):
ap2 = ap.add_argument_group('IdP / identity provider / user authentication options')
ap2.add_argument("--idp-h-usr", metavar="HN", type=u, default="", help="bypass the copyparty authentication checks and assume the request-header \033[33mHN\033[0m contains the username of the requesting user (for use with authentik/oauth/...)\n\033[1;31mWARNING:\033[0m if you enable this, make sure clients are unable to specify this header themselves; must be washed away and replaced by a reverse-proxy")
ap2.add_argument("--idp-h-grp", metavar="HN", type=u, default="", help="assume the request-header \033[33mHN\033[0m contains the groupname of the requesting user; can be referenced in config files for group-based access control")
ap2.add_argument("--idp-h-key", metavar="HN", type=u, default="", help="optional but recommended safeguard; your reverse-proxy will insert a secret header named \033[33mHN\033[0m into all requests, and the other IdP headers will be ignored if this header is not present")
ap2.add_argument("--idp-gsep", metavar="RE", type=u, default="|:;+,", help="if there are multiple groups in \033[33m--idp-h-grp\033[0m, they are separated by one of the characters in \033[33mRE\033[0m")

View file

@ -968,7 +968,7 @@ class AuthSrv(object):
* any non-zero value from IdP group header
* otherwise take --grps / [groups]
"""
ret = {un:gns[:] for un, gns in self.idp_accs.items()}
ret = {un: gns[:] for un, gns in self.idp_accs.items()}
ret.update({zs: [""] for zs in acct if zs not in ret})
for gn, uns in grps.items():
for un in uns:

View file

@ -300,6 +300,7 @@ class HttpCli(object):
zs = "%s:%s" % self.s.getsockname()[:2]
self.host = zs[7:] if zs.startswith("::ffff:") else zs
trusted_xff = False
n = self.args.rproxy
if n:
zso = self.headers.get(self.args.xff_hdr)
@ -333,6 +334,7 @@ class HttpCli(object):
self.is_vproxied = bool(self.args.R)
self.log_src = self.conn.set_rproxy(self.ip)
self.host = self.headers.get("x-forwarded-host") or self.host
trusted_xff = True
if self.is_banned():
return False
@ -467,7 +469,41 @@ class HttpCli(object):
if self.args.idp_h_grp
else ""
)
self.asrv.idp_checkin(self.conn.hsrv.broker, idp_usr, idp_grp)
if not trusted_xff:
pip = self.conn.addr[0]
trusted_xff = self.args.xff_re and self.args.xff_re.match(pip)
# always require --xff-src with idp, but check against original (xff_src) rather than computed value (xff_re) to allow 'any'
trusted_xff_strict = trusted_xff and self.args.xff_src
trusted_key = (
not self.args.idp_h_key
) or self.args.idp_h_key in self.headers
if trusted_key and trusted_xff_strict:
self.asrv.idp_checkin(self.conn.hsrv.broker, idp_usr, idp_grp)
else:
if not trusted_key:
t = 'the idp-h-key header ("%s") is not present in the request; will NOT trust the other headers saying that the client\'s username is "%s" and group is "%s"'
self.log(t % (self.args.idp_h_key, idp_usr, idp_grp), 3)
if not trusted_xff_strict:
t = 'got IdP headers from untrusted source "%s" claiming the client\'s username is "%s" and group is "%s"; if you trust this, you must allowlist this proxy with "--xff-src=%s"'
if not self.args.idp_h_key:
t += " Note: you probably also want to specify --idp-h-key <SECRET-HEADER-NAME> for additional security"
pip = self.conn.addr[0]
zs = (
".".join(pip.split(".")[:2]) + "."
if "." in pip
else ":".join(pip.split(":")[:4]) + ":"
)
self.log(t % (pip, idp_usr, idp_grp, zs), 3)
idp_usr = "*"
idp_grp = ""
if idp_usr in self.asrv.vfs.aread:
self.uname = idp_usr
else:

View file

@ -478,7 +478,8 @@ class SvcHub(object):
al.xff_hdr = al.xff_hdr.lower()
al.idp_h_usr = al.idp_h_usr.lower()
# al.idp_h_grp = al.idp_h_grp.lower()
al.idp_h_grp = al.idp_h_grp.lower()
al.idp_h_key = al.idp_h_key.lower()
al.xff_re = self._ipa2re(al.xff_src)
al.ipa_re = self._ipa2re(al.ipa)

View file

@ -21,8 +21,11 @@
ansi # enable colors in log messages
#q # disable logging for more performance
# since copyparty is only accessible through traefik, disable safetycheck on x-forwarded-for
xff-src: any
# if we are confident that we got the docker-network config correct
# (meaning copyparty is only accessible through traefik, and
# traefik makes sure that all requests go through authelia),
# then disable the reverse-proxy source-ip safety check like this:
#xff-src: any
# enable IdP support by expecting username/groupname in
# http-headers provided by the reverse-proxy; header "X-IdP-User"

7
docs/idp.md Normal file
View file

@ -0,0 +1,7 @@
there is a [docker-compose example](./examples/docker/idp-authelia-traefik) which is hopefully a good starting point (meaning you can skip the steps below) -- but if you want to set this up from scratch yourself (or learn about how it works), keep reading:
to configure IdP from scratch, you must place copyparty behind a reverse-proxy which sends all requests through a middleware (the IdP / identity-provider service) which will inject a set of headers into the requests, telling copyparty who the user is
in the copyparty `[global]` config, specify which headers to read client info from; username is required (`idp-h-usr: X-Authooley-User`), group(s) are optional (`idp-h-grp: X-Authooley-Groups`)
* it is also required to specify the subnet that legit requests will be coming from, for example `--xff-src=10.88.` to allow 10.88.x.x, and it is recommended to configure the reverseproxy to include a secret header as proof that the other headers are also legit (and not smuggled in by a malicious client), telling copyparty the headername to expect with `idp-h-key: X-Totes-Legit`