#9206 closed enhancement (worksforme)
patch: authorize using Remote-User: header
Reported by: | Owned by: | ||
---|---|---|---|
Priority: | normal | Milestone: | |
Component: | web frontend | Version: | 0.11-stable |
Severity: | normal | Keywords: | |
Cc: | Ryan J Ollos | Branch: | |
Release Notes: | |||
API Changes: | |||
Internal Changes: |
Description
for obscure reasons, my tracd-behind-proxypass wasn't able to authorize the usual way.
This patch will make trac trust the 'Remote-User:' header, even if the adaptor (CGI/etc) didn't otherwise set the remote_user field.
This probably should be a configuration option as it would otherwise have security implications but is perfect for our use - apache LDAP-based authentication. I'm aware of LdapPlugin but this seemed simpler and with broader implications (could work for other authentication types, kerberos etc).
Attachments (2)
Change History (19)
by , 15 years ago
Attachment: | auth-from-header.patch added |
---|
comment:1 by , 15 years ago
Milestone: | → next-minor-0.12.x |
---|
Thanks for the patch. Would you mind adding that configuration option? This would be also the place to briefly document why you would need to set up such option.
The test could also be written if not remote_user and req.get_header('Remote-User'):
, I suppose that would be slightly more efficient.
comment:2 by , 15 years ago
The better patch here automatically logs you in if present (instead of requiring you to click login), and has a configuration option.
comment:3 by , 15 years ago
Why not make this an implementation of the IAuthenticator extension point interface?
E.g.
class MyRemoteUserAuthenticator(IAuthenticator): obey_remote_user_header = BoolOption('trac', 'obey_remote_user_header', 'false', """Whether the 'Remote-User:' HTTP header is to be trusted for user logins (''since ??.??').""") def authenticate(self, req): if self.obey_remote_user_header and req.get_header('Remote-User'): return req.get_header('Remote-User') return None
That way we can keep trac free of the change that would likely raise security issues, and you are free to deploy your plugin to all the trac installations that you have.
comment:4 by , 15 years ago
Carsten,
That's a great idea. I'll do that and post a link here, thanks.
-Steven
comment:5 by , 14 years ago
Milestone: | next-minor-0.12.x |
---|---|
Resolution: | → worksforme |
Status: | new → closed |
I suppose the code in comment:3 is enough then (could be placed in a single file plugin).
comment:6 by , 13 years ago
i'm trying to do the same thing: basic auth on <Location>
mod_proxy forwarded to tracd. unfortunately tracd doesn't seem to take this auth info no matter which way i try. tried:
- with project/plugins/remote-user-auth.py
from trac.config import BoolOption from trac.web.api import IAuthenticator class MyRemoteUserAuthenticator(IAuthenticator): obey_remote_user_header = BoolOption('trac', 'obey_remote_user_header', 'false', """Whether the 'Remote-User:' HTTP header is to be trusted for user logins (''since ??.??').""") def authenticate(self, req): if self.obey_remote_user_header and req.get_header('Remote-User'): return req.get_header('Remote-User') return None
- without project/plugins/remote-user-auth.py
- tracd with —basic-auth="*,htpasswd,My Proxy Realm"
- tracd without —basic-auth
- with rewriterule
RewriteEngine On RewriteCond %{LA-U:REMOTE_USER} (.+) RewriteRule . - [E=RU:%1] RequestHeader add X-Forwarded-User %{RU}e
- without rewriterule
what am i missing?
comment:8 by , 13 years ago
Very good - it did solve my problem as well.
I have transformed the script into a setuptools Trac plugin that can be packaged and installed just like any other trac-hack. Along with polishing the documentation it is now ready - download at
comment:10 by , 9 years ago
It seems like the configuration option is unnecessary when the authenticator is packaged as a single-file plugins since the behavior can be controlled by enabling/disabling the plugin.
The security risk seems relatively low provided there are no caveats to the statement documented on the Django site:
This warning doesn’t apply to RemoteUserMiddleware in its default configuration with header = 'REMOTE_USER', since a key that doesn’t start with HTTP_ in request.META can only be set by your WSGI server, not directly from an HTTP request header.
Any objections to applying attachment:9206-remoteuser.patch, perhaps renaming the option to trust_remote_user_header
?
Alternatively, we could put the IAuthenticator
in /contrib
or /tracopt/web/auth
.
comment:11 by , 9 years ago
We discussed this a bit more in gmessage:trac-users:5p8DjrgFHvw/atuGxm90DQAJ. I'm unsure how the login button can function in the recipe TracStandalone#Authenticationfortracdbehindaproxy. Perhaps the site described in the recipe is completely unavailable to anonymous users, and authentication occurs in the web server before hitting the Trac application. For the login button to work, it looks like req.environ['REMOTE_USER']
needs to be set.
A different solution to the issue of running TracStandalone behind a proxy might be to plugin in a different implementation of AuthenticationMiddleware. Making the middleware "pluggable" is probably related to #11585 (or at least the related work that was done in Bloodhound).
comment:12 by , 9 years ago
Yes! The site is completely unavailable to anonymous. Sorry for not making that clear.
follow-up: 14 comment:13 by , 9 years ago
Cc: | added |
---|
Thanks for the additional information. It's good to be able to reconcile the behavior of your site with what we are seeing elsewhere.
There are very few places that req.remote_user
is used:
$grep -R -in "req.remote_user" . --exclude-dir=.git --exclude-dir=build --exclude=*.pyc ./trac/web/auth.py:93: if req.remote_user: ./trac/web/auth.py:94: authname = req.remote_user ./trac/web/auth.py:150: in req.remote_user will be converted to lower case before ./trac/web/auth.py:155: if not req.remote_user: ./trac/web/auth.py:164: remote_user = req.remote_user
It's only used in LoginModule.authenticate
and LoginModule._do_login
. Plugins such as AccountManagerPlugin access req.remote_user
as well.
Summarizing what I see in the code, req.authname
can be populated in an IAuthenticator
implementation from the value of the HTTP_REMOTE_USER
header, or in the case of LoginModule.authenticate
, from the value of req.remote_user. req.remote_user
is either set by AuthenticationMiddleware or by the WSGI application.
What I don't understand is why LoginModule needs to access req.remote_user
in addition to req.authname
: tags/trac-1.0.9/trac/web/auth.py@:148,157,161#L131. If trac.web.main.RequestDispatcher.authenticate
is called before processing the request to the /login
path (which I'm not entirely sure is the case), couldn't we just rely on the IAuthenticator
implementation to set req.authname
and use the value of req.authname
in LoginModule._do_login
rather than req.remote_user
?
comment:14 by , 9 years ago
Summarizing what I see in the code,
req.authname
can be populated in anIAuthenticator
implementation from the value of theHTTP_REMOTE_USER
header, or in the case ofLoginModule.authenticate
, from the value of req.remote_user.req.remote_user
is either set by AuthenticationMiddleware or by the WSGI application.
If the patch is put to Trac, I think we should be able to use any header for the remote-user's name, not only Remote-User
. The HTTP_REMOTE_USER
variable can be set by remote attacker, easily. The following commands can set the HTTP_REMOTE_USER
variable for Apache.
$ curl -o /dev/null --header 'REMOTE-USER: xx' http://hostname/trac # works with apache 2.2 and 2.4 $ curl -o /dev/null --header 'REMOTE!USER: xx' http://hostname/trac # works with apache 2.2 $ curl -o /dev/null --header 'REMOTE.USER: xx' http://hostname/trac # works with apache 2.2 $ curl -o /dev/null --header 'REMOTE=USER: xx' http://hostname/trac # works with apache 2.2
RequestHeader unset
can remove the header. However, it cannot remove in the cases except use of -
.
RequestHeader unset Remote-User early
IMO, I don't recommend to use HTTP header….
follow-up: 16 comment:15 by , 9 years ago
I misunderstood the statement in the Django documentation that I referenced in comment:10. I had hoped we could modify AuthenticationMiddleware to set the REMOTE_USER
from an HTTP header, as in the flask example. If that's not secure, any other ideas on how to set REMOTE_USER
in TracStandalone when the web server acts as a proxy and handles authentication? It seems like a generalized problem that we should try to support in Trac.
comment:16 by , 9 years ago
Replying to Ryan J Ollos:
I misunderstood the statement in the Django documentation that I referenced in comment:10. I had hoped we could modify AuthenticationMiddleware to set the
REMOTE_USER
from an HTTP header, as in the flask example.
That example is insecure, I think. If an HTTP header is set on the reverse proxy, the reverse proxy must remove the header from remote.
Apache 2.4:
RequestHeader unset Remote-User early
Nginx:
server { listen *:3000; server_name localhost; location / { proxy_pass http://127.0.0.1:3001; proxy_redirect http://127.0.0.1:3001 /; proxy_set_header Remote-User ""; } location /login { proxy_pass http://127.0.0.1:3001/login; proxy_redirect http://127.0.0.1:3001 /; proxy_set_header Remote-User $remote_user; auth_basic "auth"; auth_basic_user_file "./htpasswd.txt"; } }
However, non-alphanumeric characters are replaced with _
in Apache 2.2 (e.g. Remote!User: admin
⇒ HTTP_REMOTE_USER: admin
). It's hard to remove such headers only using configurations. Since Apache 2.4, such headers is not converted to HTTP_
variables.
This changes is introduced in http://svn.apache.org/viewvc?view=revision&revision=1053353.
If that's not secure, any other ideas on how to set
REMOTE_USER
in TracStandalone when the web server acts as a proxy and handles authentication? It seems like a generalized problem that we should try to support in Trac.
Another idea is adding configurable option to use secret header. If the header is unforeseeable, it would be unable to send the header from remote. Untested patch:
-
trac/web/auth.py
diff --git a/trac/web/auth.py b/trac/web/auth.py index 81be36fac..64a54812a 100644
a b class LoginModule(Component): 80 80 base path of several Trac instances if you want them to share 81 81 the cookie. (''since 0.12'')""") 82 82 83 remote_user_http_header = Option('trac', 'remote_user_http_header', '', 84 """HTTP header for the remote user. 85 86 The option can be used when REMOTE_USER variable is not available. 87 The header from remote should be removed to be secure. If it's hard 88 to remove it, keep the header secret using long enough and 89 unforeseeable value. (''since 1.0.10'')""") 90 83 91 # IAuthenticator methods 84 92 85 93 def authenticate(self, req): 86 authname = None 87 if req.remote_user: 88 authname = req.remote_user 89 elif 'trac_auth' in req.incookie: 94 authname = req.remote_user 95 if not authname: 96 header = self.remote_user_http_header 97 if header: 98 authname = req.get_header(header) 99 if not authname and 'trac_auth' in req.incookie: 90 100 authname = self._get_name_for_cookie(req, 91 101 req.incookie['trac_auth']) 92 102
comment:17 by , 9 years ago
Thanks, it sounds like a good idea to use a secret key for servers that don't allow secure web server configuration. We might run into the same issue of req.remote_user
not being set in LoginModule._do_login
. If that's the case, maybe it's possible to implement a similar idea of specifying the secret key as an option of TracStandalone and renaming the key in AuthenticationMiddleware
. I'll do some testing in the coming days.
auth-from-header.patch