dpgaspar / Flask-AppBuilder

Simple and rapid application development framework, built on top of Flask. includes detailed security, auto CRUD generation for your models, google charts and much more. Demo (login with guest/welcome) - http://flaskappbuilder.pythonanywhere.com/
BSD 3-Clause "New" or "Revised" License
4.7k stars 1.36k forks source link

AUTH_REMOTE_USER reads REMOTE_USER variable instead of HTTP_REMOTE_USER #1764

Open sc-anssi opened 2 years ago

sc-anssi commented 2 years ago

Environment

Flask-Appbuilder version: 3.4.1

pip freeze output:

apispec==3.3.2
attrs==21.2.0
Babel==2.9.1
click==7.1.2
colorama==0.4.4
defusedxml==0.7.1
dnspython==2.1.0
email-validator==1.1.3
Flask==1.1.4
Flask-AppBuilder==3.4.1
Flask-Babel==2.0.0
Flask-JWT-Extended==3.25.1
Flask-Login==0.4.1
Flask-OpenID==1.3.0
Flask-SQLAlchemy==2.5.1
Flask-WTF==0.14.3
idna==3.3
itsdangerous==1.1.0
Jinja2==2.11.3
jsonschema==3.2.0
MarkupSafe==2.0.1
marshmallow==3.14.1
marshmallow-enum==1.5.1
marshmallow-sqlalchemy==0.26.1
prison==0.2.1
PyJWT==1.7.1
pyrsistent==0.18.0
python-dateutil==2.8.2
python3-openid==3.2.0
pytz==2021.3
PyYAML==6.0
six==1.16.0
SQLAlchemy==1.3.24
SQLAlchemy-Utils==0.38.1
Werkzeug==1.0.1
WTForms==2.3.3

Describe the expected results

When using FAB with AUTH_TYPE = AUTH_REMOTE_USER behind a reverse proxy which sets the request header REMOTE_USER, FAB should authenticate that user when trying to login

Describe the actual results

Authentication fails with message Invalid login. Please try again. when clicking "login" link.

Steps to reproduce

1) Setup the base skeleton app 2) Modify config.py to set AUTH_TYPE = AUTH_REMOTE_USER 3) Setup a reverse proxy in front of the app setting the request header REMOTE_USER to "Admin" (the following example is for Apache HTTPD):

        ProxyRequests Off
        ProxyPass / "http://localhost:5000/"
        ProxyPassReverse / "http://localhost:5000/"
        RequestHeader set REMOTE_USER Admin

4) Restart reverse proxy and start the app 5) Try to login and fail with message Invalid login. Please try again.

Potential lead

I believe CGI uses HTTP request headers as environment variable by prefixing them with HTTP_ (https://www.ietf.org/rfc/rfc3875, section 4.1.18). However FAB reads REMOTE_USER in flask_appbuilder/security/views.py.

Patching the code as follow seems to fix the problem:

--- a/flask_appbuilder/security/views.py
+++ b/flask_appbuilder/security/views.py
@@ -715,7 +715,7 @@ class AuthRemoteUserView(AuthView):

     @expose("/login/")
     def login(self) -> WerkzeugResponse:
-        username = request.environ.get("REMOTE_USER")
+        username = request.environ.get("HTTP_REMOTE_USER")
         if g.user is not None and g.user.is_authenticated:
             return redirect(self.appbuilder.get_url_for_index)
         if username:
dpgaspar commented 2 years ago

This is intended to be used with IIS on an internal network by setting windows authentication enabled and anonymous disabled.

sc-anssi commented 2 years ago

Hi,

The documentation mentions Apache and Nginx but I've not seen any reference to IIS in /docs/security.rst. But I don't think the webserver acting as reverse proxy is responsible for the issue at hand. If you tcpdump the traffic between the reverse proxy and the Flask webserver you can see the header is indeed REMOTE_USER, so the conversion of HTTP headers to environment variables by prefixing HTTP_ is done by Flask/Werkzeug I believe.

That's why I believe FAB should either read HTTP_REMOTE_USER from request.environ or read Remote-User from request.headers (the first option seems to be the easiest one as we don't have to worry about the case or the mix of dashes and underscores in headers)

What do you think ? Regards

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Feel free to reopen it if it's still relevant to you. Thank you

sc-anssi commented 2 years ago

Hi, This issue is still relevant. @dpgaspar what do you think of the suggested fix ? Regards

slmg commented 1 year ago

I am attempting to run an authenticating reverse proxy in front of a FAB app, and I am faced with this exact issue. Passing REMOTE_USER in the request headers from the proxy results to HTTP_REMOTE_USER in the request.environ object of the FAB web-server.

As suggested by @sc-anssi, a very simple change would enable authentication to be offloaded. We could check HTTP_REMOTE_USER exists in request.environ in addition to REMOTE_USER (to preserve compatibility for anyone relying on REMOTE_USER)?

Could this issue please be re-open @dpgaspar?