diff --git a/superset/config.py b/superset/config.py index 2f83c65e4d6..cd64e54bcc8 100644 --- a/superset/config.py +++ b/superset/config.py @@ -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 diff --git a/superset/constants.py b/superset/constants.py index d285fb1f901..3c766906734 100644 --- a/superset/constants.py +++ b/superset/constants.py @@ -28,6 +28,7 @@ NULL_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" diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index 4f7268458e8..630df13ebe3 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -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 = " + "''" + ) + 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() diff --git a/superset/security/manager.py b/superset/security/manager.py index 9a5055d43fe..e4e93567c99 100644 --- a/superset/security/manager.py +++ b/superset/security/manager.py @@ -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