feature/secrets-encryption-cleaning (#673)

* removed simple-crypt and cleaned up usage of keys for encryption w/ burnettk

* renamed var to SPIFFWORKFLOW_BACKEND_ENCRYPTION_KEY w/ burnettk

* pyl w/ burnettk

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
jasquat 2023-11-16 10:12:47 -05:00 committed by GitHub
parent 1eed6e2444
commit fc503ec76b
8 changed files with 52 additions and 226 deletions

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,6 @@ flask-mail = "*"
flask-marshmallow = "*" flask-marshmallow = "*"
flask-migrate = "*" flask-migrate = "*"
flask-restful = "*" flask-restful = "*"
flask-simple-crypt = "^0.3.3"
SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "main"} SpiffWorkflow = {git = "https://github.com/sartography/SpiffWorkflow", rev = "main"}
# SpiffWorkflow = {develop = true, path = "../../spiffworkflow/" } # SpiffWorkflow = {develop = true, path = "../../spiffworkflow/" }
# SpiffWorkflow = {develop = true, path = "../../SpiffWorkflow/" } # SpiffWorkflow = {develop = true, path = "../../SpiffWorkflow/" }

View File

@ -1,4 +1,3 @@
import base64
import faulthandler import faulthandler
import json import json
import os import os
@ -14,7 +13,6 @@ from apscheduler.schedulers.base import BaseScheduler # type: ignore
from flask.json.provider import DefaultJSONProvider from flask.json.provider import DefaultJSONProvider
from flask_cors import CORS # type: ignore from flask_cors import CORS # type: ignore
from flask_mail import Mail # type: ignore from flask_mail import Mail # type: ignore
from flask_simple_crypt import SimpleCrypt # type: ignore
from prometheus_flask_exporter import ConnexionPrometheusMetrics # type: ignore from prometheus_flask_exporter import ConnexionPrometheusMetrics # type: ignore
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
@ -111,14 +109,6 @@ def should_start_scheduler(app: flask.app.Flask) -> bool:
return True return True
class NoOpCipher:
def encrypt(self, value: str) -> bytes:
return str.encode(value)
def decrypt(self, value: str) -> str:
return value
def create_app() -> flask.app.Flask: def create_app() -> flask.app.Flask:
faulthandler.enable() faulthandler.enable()
@ -161,25 +151,6 @@ def create_app() -> flask.app.Flask:
configure_sentry(app) configure_sentry(app)
encryption_lib = app.config.get("SPIFFWORKFLOW_BACKEND_ENCRYPTION_LIB")
if encryption_lib == "cryptography":
from cryptography.fernet import Fernet
app_secret_key = app.config.get("SECRET_KEY")
app_secret_key_bytes = app_secret_key.encode()
base64_key = base64.b64encode(app_secret_key_bytes)
fernet_cipher = Fernet(base64_key)
app.config["CIPHER"] = fernet_cipher
# for comparison against possibly-slow encryption libraries
elif encryption_lib == "no_op_cipher":
no_op_cipher = NoOpCipher()
app.config["CIPHER"] = no_op_cipher
else:
simple_crypt_cipher = SimpleCrypt()
app.config["FSC_EXPANSION_COUNT"] = 2048
simple_crypt_cipher.init_app(app)
app.config["CIPHER"] = simple_crypt_cipher
app.before_request(verify_token) app.before_request(verify_token)
app.before_request(AuthorizationService.check_for_permission) app.before_request(AuthorizationService.check_for_permission)
app.after_request(_set_new_access_token_in_cookie) app.after_request(_set_new_access_token_in_cookie)

View File

@ -1,4 +1,5 @@
"""__init__.py.""" """__init__.py."""
import base64
import os import os
import threading import threading
import uuid import uuid
@ -11,12 +12,21 @@ from spiffworkflow_backend.services.logging_service import setup_logger
HTTP_REQUEST_TIMEOUT_SECONDS = 15 HTTP_REQUEST_TIMEOUT_SECONDS = 15
CONNECTOR_PROXY_COMMAND_TIMEOUT = 45 CONNECTOR_PROXY_COMMAND_TIMEOUT = 45
SUPPORTED_ENCRYPTION_LIBS = ["cryptography", "no_op_cipher"]
class ConfigurationError(Exception): class ConfigurationError(Exception):
pass pass
class NoOpCipher:
def encrypt(self, value: str) -> bytes:
return str.encode(value)
def decrypt(self, value: str) -> str:
return value
def setup_database_configs(app: Flask) -> None: def setup_database_configs(app: Flask) -> None:
worker_id = os.environ.get("PYTEST_XDIST_WORKER") worker_id = os.environ.get("PYTEST_XDIST_WORKER")
parallel_test_suffix = "" parallel_test_suffix = ""
@ -134,6 +144,33 @@ def _check_for_incompatible_frontend_and_backend_urls(app: Flask) -> None:
) )
def _setup_cipher(app: Flask) -> None:
encryption_lib = app.config.get("SPIFFWORKFLOW_BACKEND_ENCRYPTION_LIB")
if encryption_lib not in SUPPORTED_ENCRYPTION_LIBS:
raise ConfigurationError(
f"Received invalid encryption lib: {encryption_lib}. Supported libs are {SUPPORTED_ENCRYPTION_LIBS}"
)
if encryption_lib == "cryptography":
from cryptography.fernet import Fernet
app_secret_key = app.config.get("SPIFFWORKFLOW_BACKEND_ENCRYPTION_KEY")
if app_secret_key is None:
raise ConfigurationError(
"SPIFFWORKFLOW_BACKEND_ENCRYPTION_KEY must be specified if using cryptography encryption lib"
)
app_secret_key_bytes = app_secret_key.encode()
base64_key = base64.b64encode(app_secret_key_bytes)
fernet_cipher = Fernet(base64_key)
app.config["CIPHER"] = fernet_cipher
# no_op_cipher for comparison against possibly-slow encryption libraries
elif encryption_lib == "no_op_cipher":
no_op_cipher = NoOpCipher()
app.config["CIPHER"] = no_op_cipher
def setup_config(app: Flask) -> None: def setup_config(app: Flask) -> None:
# ensure the instance folder exists # ensure the instance folder exists
try: try:
@ -211,3 +248,4 @@ def setup_config(app: Flask) -> None:
_set_up_tenant_specific_fields_as_list_of_strings(app) _set_up_tenant_specific_fields_as_list_of_strings(app)
_check_for_incompatible_frontend_and_backend_urls(app) _check_for_incompatible_frontend_and_backend_urls(app)
_check_extension_api_configs(app) _check_extension_api_configs(app)
_setup_cipher(app)

View File

@ -162,6 +162,7 @@ config_from_env(
"SPIFFWORKFLOW_BACKEND_ENCRYPTION_LIB", "SPIFFWORKFLOW_BACKEND_ENCRYPTION_LIB",
default="no_op_cipher", default="no_op_cipher",
) )
config_from_env("SPIFFWORKFLOW_BACKEND_ENCRYPTION_KEY")
### locking ### locking
# timeouts for process instances locks as they are run to avoid stale locks # timeouts for process instances locks as they are run to avoid stale locks

View File

@ -3,7 +3,6 @@ import os
from os import environ from os import environ
TESTING = True TESTING = True
SECRET_KEY = "the_secret_key" # noqa: S105, do not care about security when running unit tests
SPIFFWORKFLOW_BACKEND_LOG_TO_FILE = environ.get("SPIFFWORKFLOW_BACKEND_LOG_TO_FILE", default="true") == "true" SPIFFWORKFLOW_BACKEND_LOG_TO_FILE = environ.get("SPIFFWORKFLOW_BACKEND_LOG_TO_FILE", default="true") == "true"
SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME = environ.get( SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME = environ.get(

View File

@ -52,7 +52,8 @@ class UserModel(SpiffworkflowBaseDBModel):
:return: string :return: string
""" """
secret_key = current_app.config.get("SECRET_KEY") # current_app.secret_key is the same as current_app.config['SECRET_KEY']
secret_key = str(current_app.secret_key)
if secret_key is None: if secret_key is None:
raise KeyError("we need current_app.config to have a SECRET_KEY") raise KeyError("we need current_app.config to have a SECRET_KEY")

View File

@ -153,7 +153,7 @@ class AuthenticationService:
self, code: str, authentication_identifier: str, redirect_url: str = "/v1.0/login_return" self, code: str, authentication_identifier: str, redirect_url: str = "/v1.0/login_return"
) -> dict: ) -> dict:
backend_basic_auth_string = ( backend_basic_auth_string = (
f"{self.client_id(authentication_identifier)}:{self.secret_key(authentication_identifier)}" f"{self.client_id(authentication_identifier)}:{self.__class__.secret_key(authentication_identifier)}"
) )
backend_basic_auth_bytes = bytes(backend_basic_auth_string, encoding="ascii") backend_basic_auth_bytes = bytes(backend_basic_auth_string, encoding="ascii")
backend_basic_auth = base64.b64encode(backend_basic_auth_bytes) backend_basic_auth = base64.b64encode(backend_basic_auth_bytes)
@ -300,10 +300,7 @@ class AuthenticationService:
@staticmethod @staticmethod
def decode_auth_token(auth_token: str) -> dict[str, str | None]: def decode_auth_token(auth_token: str) -> dict[str, str | None]:
secret_key = current_app.config.get("SECRET_KEY") """This is only used for debugging."""
if secret_key is None:
raise KeyError("we need current_app.config to have a SECRET_KEY")
try: try:
payload: dict[str, str | None] = jwt.decode(auth_token, options={"verify_signature": False}) payload: dict[str, str | None] = jwt.decode(auth_token, options={"verify_signature": False})
return payload return payload