fix(embedding): require non-default JWT secret when embedded dashboards are enabled (#39999)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Shaitan
2026-05-28 22:26:40 +01:00
committed by GitHub
parent 8a0026b173
commit f663f47628
4 changed files with 46 additions and 16 deletions

View File

@@ -51,7 +51,7 @@ from sqlalchemy.orm.query import Query
from superset.advanced_data_type.plugins.internet_address import internet_address
from superset.advanced_data_type.plugins.internet_port import internet_port
from superset.advanced_data_type.types import AdvancedDataType
from superset.constants import CHANGE_ME_SECRET_KEY
from superset.constants import CHANGE_ME_GUEST_TOKEN_JWT_SECRET, CHANGE_ME_SECRET_KEY
from superset.jinja_context import BaseTemplateProcessor
from superset.key_value.types import JsonKeyValueCodec
from superset.stats_logger import DummyStatsLogger
@@ -2354,7 +2354,7 @@ GLOBAL_ASYNC_QUERIES_CACHE_BACKEND = {
# Embedded config options
GUEST_ROLE_NAME = "Public"
GUEST_TOKEN_JWT_SECRET = "test-guest-secret-change-me" # noqa: S105
GUEST_TOKEN_JWT_SECRET = CHANGE_ME_GUEST_TOKEN_JWT_SECRET
GUEST_TOKEN_JWT_ALGO = "HS256" # noqa: S105
GUEST_TOKEN_HEADER_NAME = "X-GuestToken" # noqa: S105
GUEST_TOKEN_JWT_EXP_SECONDS = 300 # 5 minutes

View File

@@ -28,6 +28,7 @@ NULL_STRING = "<NULL>"
EMPTY_STRING = "<empty string>"
CHANGE_ME_SECRET_KEY = "CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET" # noqa: S105
CHANGE_ME_GUEST_TOKEN_JWT_SECRET = "test-guest-secret-change-me" # noqa: S105
# UUID for the examples database
EXAMPLES_DB_UUID = "a2dc77af-e654-49bb-b321-40f6b559a1ee"

View File

@@ -37,7 +37,7 @@ 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
from superset.constants import CHANGE_ME_GUEST_TOKEN_JWT_SECRET, CHANGE_ME_SECRET_KEY
from superset.databases.utils import make_url_safe
from superset.extensions import (
_event_logger,
@@ -634,12 +634,17 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
self.init_all_dependencies_and_extensions()
@staticmethod
def _log_config_warning(message: str) -> None:
top_banner = 80 * "-" + "\n" + 36 * " " + "WARNING\n" + 80 * "-"
bottom_banner = 80 * "-" + "\n" + 80 * "-"
logger.warning(top_banner)
logger.warning(message)
logger.warning(bottom_banner)
def check_secret_key(self) -> None:
def log_default_secret_key_warning() -> None:
top_banner = 80 * "-" + "\n" + 36 * " " + "WARNING\n" + 80 * "-"
bottom_banner = 80 * "-" + "\n" + 80 * "-"
logger.warning(top_banner)
logger.warning(
if self.config["SECRET_KEY"] == CHANGE_ME_SECRET_KEY:
warning = (
"A Default SECRET_KEY was detected, please use superset_config.py "
"to override it.\n"
"Use a strong complex alphanumeric string and use a tool to help"
@@ -648,21 +653,44 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
"For more info, see: https://superset.apache.org/docs/"
"configuration/configuring-superset#specifying-a-secret_key"
)
logger.warning(bottom_banner)
if self.config["SECRET_KEY"] == CHANGE_ME_SECRET_KEY:
if (
self.superset_app.debug
or self.superset_app.config["TESTING"]
or is_test()
):
logger.warning("Debug mode identified with default secret key")
log_default_secret_key_warning()
self._log_config_warning(warning)
return
log_default_secret_key_warning()
self._log_config_warning(warning)
logger.error("Refusing to start due to insecure SECRET_KEY")
sys.exit(1)
def check_guest_token_secret(self) -> None:
"""Refuse to start with default guest JWT secret when embedding is enabled."""
if not feature_flag_manager.is_feature_enabled("EMBEDDED_SUPERSET"):
return
if (
self.config.get("GUEST_TOKEN_JWT_SECRET")
!= CHANGE_ME_GUEST_TOKEN_JWT_SECRET
):
return
self._log_config_warning(
"EMBEDDED_SUPERSET is enabled but GUEST_TOKEN_JWT_SECRET has not "
"been changed from its default value.\n"
"The default value is publicly known and must be replaced before "
"running in production.\n"
"Set a strong random value in superset_config.py:\n"
" GUEST_TOKEN_JWT_SECRET = "
"'<output of: openssl rand -base64 42>'"
)
if self.superset_app.debug or self.superset_app.config["TESTING"] or is_test():
return
logger.error(
"Refusing to start: insecure GUEST_TOKEN_JWT_SECRET "
"with EMBEDDED_SUPERSET enabled"
)
sys.exit(1)
def configure_session(self) -> None:
if self.config["SESSION_SERVER_SIDE"]:
Session(self.superset_app)
@@ -747,6 +775,7 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
# Configuration of feature_flags must be done first to allow init features
# conditionally
self.configure_feature_flags()
self.check_guest_token_secret()
self.configure_db_encrypt()
self.setup_db()

View File

@@ -3529,14 +3529,14 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
r for r in user.resources if r["type"] == GuestTokenResourceType.DASHBOARD
]
if not dashboard.embedded:
return False
# TODO (embedded): remove this check once uuids are rolled out
for resource in dashboards:
if str(resource["id"]) == str(dashboard.id):
return True
if not dashboard.embedded:
return False
for resource in dashboards:
if str(resource["id"]) == str(dashboard.embedded[0].uuid):
return True