feat: support server-side sessions (#25795)

This commit is contained in:
Daniel Vaz Gaspar
2023-10-31 16:05:18 +00:00
committed by Michael S. Molina
parent cc5235829e
commit bd4c1a7445
6 changed files with 63 additions and 12 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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())

View File

@@ -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