feat: support server-side sessions (#25795)
This commit is contained in:
parent
8737a8a546
commit
d2f511abba
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
1
setup.py
1
setup.py
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue