Closed laranzu closed 7 years ago
Authentication is a web framework-related feature and I'd prefer for the work to take place within FAB. I'm a maintainer of FAB (I obtained write access on the repository after many contributions) so I can help on that side.
At Airbnb we login against LDAP using AUTH_REMOTE_USER. We have a nginx reverse proxy that takes care of authenticating against LDAP and add headers that Superset can pick up. We use this service for countless internal apps. It's nice since it works with apps written in all languages.
On the login event we simply create new users on the fly. It's not too hard to setup a script that will create/maintain roles dynamically/periodically in Superset either.
Better LDAP support would be nice though.
For reference, here's the sanitized code: https://gist.github.com/mistercrunch/6d31af4a11c47edcedc1ba6ceb5f5fab
@laranzu what about improving FAB instead?
Flask is designed to be modular, a construction kit that you assemble how you want and add new pieces to.
Flask AppBuilder simplifies things by making design decisions for you. You fill in a few settings and it Just Works.
For single sign on and other cases that Flask AppBuilder doesn't address, we'd have to turn Flask AppBuilder back into something like Flask anyway. I don't see the point in taking a piece of software that's perfectly good at what it does (doing simple cases well) and trying to turn it into something it was designed not to be.
Authentication is totally in scope for FAB: http://flask-appbuilder.readthedocs.io/en/latest/security.html#authentication-methods
And the good thing with FAB is that not only your pick your auth for your home grown app, but you can easily (like for Superset) let the person who install it setup their own auth. Much like you'd go about a reusable Django app.
I don't think Superset (and all other pick-your-own-auth-method-in-your-environment Flask apps for that matter) should have their own multi-authentication-method logic. That belongs somewhere in web framework land. The Flask ecosystem kind of falls short in that area outside of FAB, you'd have to duct tape flask-login and flask-principal and write a fair amount of code.
I vote for making FAB better.
OK, "no not interested" so we'll just patch internally
I have been researching both superset and airflow for adoption at my enterprise (thanks mistercrunch for your open source contributions!) and I know airflow is also planning to use FAB for security. I am just beginning to look at the security aspects and I have some similar thoughts/needs as shared by laranzu.
The gist code was very helpful to get me started but I may not be able to have a database at the backend, like FAB seems to presume.
I currently have a flask based application where I get a SamlToken from SSO provider which gives me user id, AD groups the user belongs to. From the AD groups, I construct roles (that I persist in session/cookie) and use Flask Principal and provide role based access to routes. I am hoping for a similar mechanism with a superset_config where I override the bare minimum classes.
I hope I am at least making sense! Please do let me know of additional resources, possibly including any work by laranzu that he may want to share.
Hi @mistercrunch I am implementing your post last 17 Aug 2017 extended and overrided your functions but I have no idea why do you use
@expose('/login/')
Superset starts ok but got this error:
superset runserver -d
Traceback (most recent call last):
File "/usr/bin/superset", line 12, in <module>
from superset.cli import manager
File "/usr/lib/python3.6/site-packages/superset/__init__.py", line 22, in <module>
from superset import config, utils
File "/usr/lib/python3.6/site-packages/superset/config.py", line 469, in <module>
class AppAuthRemoteUserView(AuthRemoteUserView):
File "/usr/lib/python3.6/site-packages/superset/config.py", line 479, in AppAuthRemoteUserView
@expose('/login/')
NameError: name 'expose' is not defined
Please, be kind and paste some trick. Thanks in advance
I tried get app url and got this trace
File "/usr/lib/python3.6/site-packages/superset/views/core.py", line 2656, in welcome
return redirect(appbuilder.get_url_for_login)
File "/usr/lib64/python3.6/site-packages/flask_appbuilder/base.py", line 449, in get_url_for_login
return url_for('%s.%s' % (self.sm.auth_view.endpoint, 'login'))
File "/usr/lib64/python3.6/site-packages/flask/helpers.py", line 333, in url_for
return appctx.app.handle_url_build_error(error, endpoint, values)
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1805, in handle_url_build_error
reraise(exc_type, exc_value, tb)
File "/usr/lib64/python3.6/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/usr/lib64/python3.6/site-packages/flask/helpers.py", line 323, in url_for
force_external=external)
File "/usr/lib/python3.6/site-packages/werkzeug/routing.py", line 1776, in build
raise BuildError(endpoint, values, method, self)
werkzeug.routing.BuildError: Could not build url for endpoint 'AppAuthRemoteUserView.login'. Did you mean 'AppAuthRemoteUserView.logout' instead?
2018-07-30 16:56:26,149:INFO:werkzeug:10.168.24.69 - - [30/Jul/2018 16:56:26] "GET / HTTP/1.1" 302 -
2018-07-30 16:56:26,256:INFO:werkzeug:10.168.24.69 - - [30/Jul/2018 16:56:26] "GET /superset/welcome HTTP/1.1" 500 -
Traceback (most recent call last):
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1997, in __call__
return self.wsgi_app(environ, start_response)
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1985, in wsgi_app
response = self.handle_exception(e)
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1540, in handle_exception
reraise(exc_type, exc_value, tb)
File "/usr/lib64/python3.6/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/lib64/python3.6/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/usr/lib/python3.6/site-packages/superset/views/core.py", line 2656, in welcome
return redirect(appbuilder.get_url_for_login)
File "/usr/lib64/python3.6/site-packages/flask_appbuilder/base.py", line 449, in get_url_for_login
return url_for('%s.%s' % (self.sm.auth_view.endpoint, 'login'))
File "/usr/lib64/python3.6/site-packages/flask/helpers.py", line 333, in url_for
return appctx.app.handle_url_build_error(error, endpoint, values)
File "/usr/lib64/python3.6/site-packages/flask/app.py", line 1805, in handle_url_build_error
reraise(exc_type, exc_value, tb)
File "/usr/lib64/python3.6/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/usr/lib64/python3.6/site-packages/flask/helpers.py", line 323, in url_for
force_external=external)
File "/usr/lib/python3.6/site-packages/werkzeug/routing.py", line 1776, in build
raise BuildError(endpoint, values, method, self)
werkzeug.routing.BuildError: Could not build url for endpoint 'AppAuthRemoteUserView.login'. Did you mean 'AppAuthRemoteUserView.logout' instead?
2018-07-30 16:56:26,324:INFO:werkzeug:10.168.24.69 - - [30/Jul/2018 16:56:26] "GET /superset/welcome?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
2018-07-30 16:56:26,325:INFO:werkzeug:10.168.24.69 - - [30/Jul/2018 16:56:26] "GET /superset/welcome?__debugger__=yes&cmd=resource&f=jquery.js HTTP/1.1" 200 -
2018-07-30 16:56:26,331:INFO:werkzeug:10.168.24.69 - - [30/Jul/2018 16:56:26] "GET /superset/welcome?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
2018-07-30 16:56:26,394:INFO:werkzeug:10.168.24.69 - - [30/Jul/2018 16:56:26] "GET /superset/welcome?__debugger__=yes&cmd=resource&f=ubuntu.ttf HTTP/1.1" 200 -
2018-07-30 16:56:26,471:INFO:werkzeug:10.168.24.69 - - [30/Jul/2018 16:56:26] "GET /superset/welcome?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
2018-07-30 16:56:26,509:INFO:werkzeug:10.168.24.69 - - [30/Jul/2018 16:56:26] "GET /superset/welcome?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -
Please open new issues instead of re-using long-closed issues.
[proposed label: airbnb_roadmap] Making single sign on work for Superset, and other useful styles of authentication/authorization, will be much easier if the underlying code is rewritten to rely on just Flask, not Flask AppBuilder.
I'm evaluating Superset for a business, and single sign on is a requirement. People using a browser on MS Windows in our domain should not need to re-enter their username and password. (Using SPNEGO in the client browser.) We would also like to get roles (groups) from Active Directory via LDAP.
Flask, no problem. I've never tried doing this before, but it was quick and easy to set up a test Flask web app using flask_kerberos that had single sign on for MS Windows users. And just as easy to set up a different Flask web app using Apache mod-auth_kerb for authentication and pass via REMOTE_USER to Flask_login.
I can't do the same with either Superset or a test Flask AppBuilder app. There's no way to plug flask_kerberos into AppBuilder. AUTH_REMOTE_USER ought to work but doesn't. After a couple of weeks I've managed to hack authentication into sort of working by manually entering users with matching name@domain entries into superset.db as Admins. It's ugly.
That won't scale to large organisations. And it still can't get roles from Active Directory.
The problem as I see it is that Flask AppBuilder is fine for small cases, but just too simple. Single sign on with groups needs both remote user and LDAP lookups.
I have informal agreement that I can spend the time to make this work. If I do, are y'all interested?