mirror of
https://github.com/apache/superset.git
synced 2026-05-09 09:55:19 +00:00
feat: support server-side sessions (#25795)
This commit is contained in:
committed by
Michael S. Molina
parent
cc5235829e
commit
bd4c1a7445
@@ -6,7 +6,7 @@ sidebar_position: 1
|
||||
|
||||
### Roles
|
||||
|
||||
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.
|
||||
Please read its [Security documentation](https://flask-appbuilder.readthedocs.io/en/latest/security.html).
|
||||
|
||||
@@ -157,6 +157,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.
|
||||
|
||||
#### 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)
|
||||
|
||||
Superset uses the [Talisman](https://pypi.org/project/flask-talisman/) extension to enable implementation of a
|
||||
@@ -177,8 +205,8 @@ It's extremely important to correctly configure a Content Security Policy when d
|
||||
prevent many types of attacks. Superset provides two variables in `config.py` for deploying a CSP:
|
||||
|
||||
- `TALISMAN_ENABLED` defaults to `True`; set this to `False` in order to disable CSP
|
||||
- `TALISMAN_CONFIG` holds the actual the policy definition (*see example below*) as well as any
|
||||
other arguments to be passed to Talisman.
|
||||
- `TALISMAN_CONFIG` holds the actual the policy definition (_see example below_) as well as any
|
||||
other arguments to be passed to Talisman.
|
||||
|
||||
When running in production mode, Superset will check at startup for the presence
|
||||
of a CSP. If one is not found, it will issue a warning with the security risks. For environments
|
||||
@@ -187,15 +215,15 @@ this warning using the `CONTENT_SECURITY_POLICY_WARNING` key in `config.py`.
|
||||
|
||||
#### CSP Requirements
|
||||
|
||||
* Superset needs the `style-src unsafe-inline` CSP directive in order to operate.
|
||||
- Superset needs the `style-src unsafe-inline` CSP directive in order to operate.
|
||||
|
||||
```
|
||||
style-src 'self' 'unsafe-inline'
|
||||
```
|
||||
|
||||
* Only scripts marked with a [nonce](https://content-security-policy.com/nonce/) can be loaded and executed.
|
||||
Nonce is a random string automatically generated by Talisman on each page load.
|
||||
You can get current nonce value by calling jinja macro `csp_nonce()`.
|
||||
- Only scripts marked with a [nonce](https://content-security-policy.com/nonce/) can be loaded and executed.
|
||||
Nonce is a random string automatically generated by Talisman on each page load.
|
||||
You can get current nonce value by calling jinja macro `csp_nonce()`.
|
||||
|
||||
```
|
||||
<script nonce="{{ csp_nonce() }}">
|
||||
@@ -203,25 +231,24 @@ You can get current nonce value by calling jinja macro `csp_nonce()`.
|
||||
</script>
|
||||
```
|
||||
|
||||
- Some dashboards load images using data URIs and require `data:` in their `img-src`
|
||||
* Some dashboards load images using data URIs and require `data:` in their `img-src`
|
||||
|
||||
```
|
||||
img-src 'self' data:
|
||||
```
|
||||
|
||||
- MapBox charts use workers and need to connect to MapBox servers in addition to the Superset origin
|
||||
* MapBox charts use workers and need to connect to MapBox servers in addition to the Superset origin
|
||||
|
||||
```
|
||||
worker-src 'self' blob:
|
||||
connect-src 'self' https://api.mapbox.com https://events.mapbox.com
|
||||
```
|
||||
|
||||
* Other CSP directives default to `'self'` to limit content to the same origin as the Superset server.
|
||||
- Other CSP directives default to `'self'` to limit content to the same origin as the Superset server.
|
||||
|
||||
In order to adjust provided CSP configuration to your needs, follow the instructions and examples provided in
|
||||
[Content Security Policy Reference](https://content-security-policy.com/)
|
||||
|
||||
|
||||
#### Other Talisman security considerations
|
||||
|
||||
Setting `TALISMAN_ENABLED = True` will invoke Talisman's protection with its default arguments,
|
||||
@@ -231,7 +258,7 @@ These generally improve security, but administrators should be aware of their ex
|
||||
|
||||
In particular, the option of `force_https = True` (`False` by default) may break Superset's Alerts & Reports
|
||||
if workers are configured to access charts via a `WEBDRIVER_BASEURL` beginning
|
||||
with `http://`. As long as a Superset deployment enforces https upstream, e.g.,
|
||||
with `http://`. As long as a Superset deployment enforces https upstream, e.g.,
|
||||
through a loader balancer or application gateway, it should be acceptable to keep this
|
||||
option disabled. Otherwise, you may want to enable `force_https` like this:
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ flask==2.2.5
|
||||
# flask-limiter
|
||||
# flask-login
|
||||
# flask-migrate
|
||||
# flask-session
|
||||
# flask-sqlalchemy
|
||||
# flask-wtf
|
||||
flask-appbuilder==4.3.10
|
||||
@@ -107,6 +108,8 @@ flask-login==0.6.0
|
||||
# flask-appbuilder
|
||||
flask-migrate==3.1.0
|
||||
# via apache-superset
|
||||
flask-session==0.5.0
|
||||
# via apache-superset
|
||||
flask-sqlalchemy==2.5.1
|
||||
# via
|
||||
# flask-appbuilder
|
||||
|
||||
@@ -117,6 +117,8 @@ protobuf==4.23.0
|
||||
# proto-plus
|
||||
pydata-google-auth==1.7.0
|
||||
# via pandas-gbq
|
||||
pyee==9.0.4
|
||||
# via playwright
|
||||
pyfakefs==5.2.2
|
||||
# via -r requirements/testing.in
|
||||
pyhive[presto]==0.6.5
|
||||
|
||||
1
setup.py
1
setup.py
@@ -86,6 +86,7 @@ setup(
|
||||
"flask-talisman>=1.0.0, <2.0",
|
||||
"flask-login>=0.6.0, < 1.0",
|
||||
"flask-migrate>=3.1.0, <4.0",
|
||||
"flask-session>=0.4.0, <1.0",
|
||||
"flask-wtf>=1.1.0, <2.0",
|
||||
"func_timeout",
|
||||
"geopy",
|
||||
|
||||
@@ -1462,6 +1462,18 @@ TALISMAN_DEV_CONFIG = {
|
||||
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_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.
|
||||
SEND_FILE_MAX_AGE_DEFAULT = int(timedelta(days=365).total_seconds())
|
||||
|
||||
@@ -27,6 +27,7 @@ from flask import Flask, redirect
|
||||
from flask_appbuilder import expose, IndexView
|
||||
from flask_babel import gettext as __, lazy_gettext as _
|
||||
from flask_compress import Compress
|
||||
from flask_session import Session
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
from superset.constants import CHANGE_ME_SECRET_KEY
|
||||
@@ -474,6 +475,10 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
|
||||
logger.error("Refusing to start due to insecure SECRET_KEY")
|
||||
sys.exit(1)
|
||||
|
||||
def configure_session(self) -> None:
|
||||
if self.config["SESSION_SERVER_SIDE"]:
|
||||
Session(self.superset_app)
|
||||
|
||||
def init_app(self) -> None:
|
||||
"""
|
||||
Main entry point which will delegate to other methods in
|
||||
@@ -481,6 +486,7 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
self.pre_init()
|
||||
self.check_secret_key()
|
||||
self.configure_session()
|
||||
# Configuration of logging must be done first to apply the formatter properly
|
||||
self.configure_logging()
|
||||
# Configuration of feature_flags must be done first to allow init features
|
||||
|
||||
Reference in New Issue
Block a user