apache / superset

Apache Superset is a Data Visualization and Data Exploration Platform
https://superset.apache.org/
Apache License 2.0
62.2k stars 13.65k forks source link

Proposal: make single sign on and other auth/authn easier by removing Flask AppBuilder #3312

Closed laranzu closed 7 years ago

laranzu commented 7 years ago

[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?

mistercrunch commented 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.

mistercrunch commented 7 years ago

For reference, here's the sanitized code: https://gist.github.com/mistercrunch/6d31af4a11c47edcedc1ba6ceb5f5fab

xrmx commented 7 years ago

@laranzu what about improving FAB instead?

laranzu commented 7 years ago

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.

mistercrunch commented 7 years ago

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.

laranzu commented 7 years ago

OK, "no not interested" so we'll just patch internally

deshpand commented 6 years ago

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.

ajgil commented 6 years ago

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

ajgil commented 6 years ago

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 -
mistercrunch commented 6 years ago

Please open new issues instead of re-using long-closed issues.