feat: support server-side sessions (#25795)

This commit is contained in:
Daniel Vaz Gaspar 2023-10-31 16:05:18 +00:00 committed by GitHub
parent 8737a8a546
commit d2f511abba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 59 additions and 4 deletions

View File

@ -4,7 +4,7 @@ hide_title: true
sidebar_position: 1 sidebar_position: 1
--- ---
Security in Superset is handled by Flask AppBuilder (FAB), an application development framework Authentication and authorization in Superset is handled by Flask AppBuilder (FAB), an application development framework
built on top of Flask. FAB provides authentication, user management, permissions and roles. built on top of Flask. FAB provides authentication, user management, permissions and roles.
Please read its [Security documentation](https://flask-appbuilder.readthedocs.io/en/latest/security.html). Please read its [Security documentation](https://flask-appbuilder.readthedocs.io/en/latest/security.html).
@ -67,7 +67,9 @@ objects (dashboards and slices) associated with the tables you just extended the
### REST API for user & role management ### REST API for user & role management
Flask-AppBuilder supports a REST API for user CRUD, but this feature is in beta and is not enabled by default in Superset. To enable this feature, set the following in your Superset configuration: Flask-AppBuilder supports a REST API for user CRUD,
but this feature is in beta and is not enabled by default in Superset.
To enable this feature, set the following in your Superset configuration:
```python ```python
FAB_ADD_SECURITY_API = True FAB_ADD_SECURITY_API = True
@ -165,6 +167,34 @@ HTTPS if the cookie is marked “secure”. The application must be served over
`PERMANENT_SESSION_LIFETIME`: (default: "31 days") The lifetime of a permanent session as a `datetime.timedelta` object. `PERMANENT_SESSION_LIFETIME`: (default: "31 days") The lifetime of a permanent session as a `datetime.timedelta` object.
#### Switching to server side sessions
Server side sessions offer benefits over client side sessions on security and performance.
By enabling server side sessions, the session data is stored server side and only a session ID
is sent to the client. When a user logs in, a session is created server side and the session ID
is sent to the client in a cookie. The client will send the session ID with each request and the
server will use it to retrieve the session data.
On logout, the session is destroyed server side and the session cookie is deleted on the client side.
This reduces the risk for replay attacks and session hijacking.
Superset uses [Flask-Session](https://flask-session.readthedocs.io/en/latest/) to manage server side sessions.
To enable this extension you have to set:
``` python
SESSION_SERVER_SIDE = True
```
Flask-Session offers multiple backend session interfaces for Flask, here's an example for Redis:
``` python
from redis import Redis
SESSION_TYPE = "redis"
SESSION_REDIS = Redis(host="redis", port=6379, db=0)
# sign the session cookie sid
SESSION_USE_SIGNER = True
```
### Content Security Policy (CSP) ### Content Security Policy (CSP)
Superset uses the [Talisman](https://pypi.org/project/flask-talisman/) extension to enable implementation of a Superset uses the [Talisman](https://pypi.org/project/flask-talisman/) extension to enable implementation of a

View File

@ -32,7 +32,9 @@ bottleneck==1.3.7
brotli==1.0.9 brotli==1.0.9
# via flask-compress # via flask-compress
cachelib==0.6.0 cachelib==0.6.0
# via flask-caching # via
# flask-caching
# flask-session
celery==5.2.2 celery==5.2.2
# via apache-superset # via apache-superset
certifi==2023.7.22 certifi==2023.7.22
@ -94,6 +96,7 @@ flask==2.2.5
# flask-limiter # flask-limiter
# flask-login # flask-login
# flask-migrate # flask-migrate
# flask-session
# flask-sqlalchemy # flask-sqlalchemy
# flask-wtf # flask-wtf
flask-appbuilder==4.3.9 flask-appbuilder==4.3.9
@ -114,6 +117,8 @@ flask-login==0.6.0
# flask-appbuilder # flask-appbuilder
flask-migrate==3.1.0 flask-migrate==3.1.0
# via apache-superset # via apache-superset
flask-session==0.5.0
# via apache-superset
flask-sqlalchemy==2.5.1 flask-sqlalchemy==2.5.1
# via # via
# flask-appbuilder # flask-appbuilder

View File

@ -48,7 +48,6 @@ google-auth==2.17.3
# google-cloud-core # google-cloud-core
# pandas-gbq # pandas-gbq
# pydata-google-auth # pydata-google-auth
# shillelagh
# sqlalchemy-bigquery # sqlalchemy-bigquery
google-auth-oauthlib==1.0.0 google-auth-oauthlib==1.0.0
# via # via
@ -120,6 +119,8 @@ protobuf==4.23.0
# proto-plus # proto-plus
pydata-google-auth==1.7.0 pydata-google-auth==1.7.0
# via pandas-gbq # via pandas-gbq
pyee==9.0.4
# via playwright
pyfakefs==5.2.2 pyfakefs==5.2.2
# via -r requirements/testing.in # via -r requirements/testing.in
pyhive[presto]==0.7.0 pyhive[presto]==0.7.0

View File

@ -90,6 +90,7 @@ setup(
"flask-talisman>=1.0.0, <2.0", "flask-talisman>=1.0.0, <2.0",
"flask-login>=0.6.0, < 1.0", "flask-login>=0.6.0, < 1.0",
"flask-migrate>=3.1.0, <4.0", "flask-migrate>=3.1.0, <4.0",
"flask-session>=0.4.0, <1.0",
"flask-wtf>=1.1.0, <2.0", "flask-wtf>=1.1.0, <2.0",
"func_timeout", "func_timeout",
"geopy", "geopy",

View File

@ -1482,6 +1482,18 @@ TALISMAN_DEV_CONFIG = {
SESSION_COOKIE_HTTPONLY = True # Prevent cookie from being read by frontend JS? SESSION_COOKIE_HTTPONLY = True # Prevent cookie from being read by frontend JS?
SESSION_COOKIE_SECURE = False # Prevent cookie from being transmitted over non-tls? SESSION_COOKIE_SECURE = False # Prevent cookie from being transmitted over non-tls?
SESSION_COOKIE_SAMESITE: Literal["None", "Lax", "Strict"] | None = "Lax" SESSION_COOKIE_SAMESITE: Literal["None", "Lax", "Strict"] | None = "Lax"
# Whether to use server side sessions from flask-session or Flask secure cookies
SESSION_SERVER_SIDE = False
# Example config using Redis as the backend for server side sessions
# from flask_session import RedisSessionInterface
#
# SESSION_SERVER_SIDE = True
# SESSION_USE_SIGNER = True
# SESSION_TYPE = "redis"
# SESSION_REDIS = Redis(host="localhost", port=6379, db=0)
#
# Other possible config options and backends:
# # https://flask-session.readthedocs.io/en/latest/config.html
# Cache static resources. # Cache static resources.
SEND_FILE_MAX_AGE_DEFAULT = int(timedelta(days=365).total_seconds()) SEND_FILE_MAX_AGE_DEFAULT = int(timedelta(days=365).total_seconds())

View File

@ -28,6 +28,7 @@ from flask import Flask, redirect
from flask_appbuilder import expose, IndexView from flask_appbuilder import expose, IndexView
from flask_babel import gettext as __ from flask_babel import gettext as __
from flask_compress import Compress from flask_compress import Compress
from flask_session import Session
from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.proxy_fix import ProxyFix
from superset.constants import CHANGE_ME_SECRET_KEY from superset.constants import CHANGE_ME_SECRET_KEY
@ -479,6 +480,10 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
logger.error("Refusing to start due to insecure SECRET_KEY") logger.error("Refusing to start due to insecure SECRET_KEY")
sys.exit(1) sys.exit(1)
def configure_session(self) -> None:
if self.config["SESSION_SERVER_SIDE"]:
Session(self.superset_app)
def init_app(self) -> None: def init_app(self) -> None:
""" """
Main entry point which will delegate to other methods in Main entry point which will delegate to other methods in
@ -486,6 +491,7 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
""" """
self.pre_init() self.pre_init()
self.check_secret_key() self.check_secret_key()
self.configure_session()
# Configuration of logging must be done first to apply the formatter properly # Configuration of logging must be done first to apply the formatter properly
self.configure_logging() self.configure_logging()
# Configuration of feature_flags must be done first to allow init features # Configuration of feature_flags must be done first to allow init features