Merge pull request #167 from sartography/simple_crypt
Encrypt secrets in the db
This commit is contained in:
commit
8c1af67383
|
@ -1032,6 +1032,22 @@ six = ">=1.3.0"
|
|||
[package.extras]
|
||||
docs = ["sphinx"]
|
||||
|
||||
[[package]]
|
||||
name = "flask-simple-crypt"
|
||||
version = "0.3.3"
|
||||
description = "Flask extension based on simple-crypt that allows simple, secure encryption and decryption for Python."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Flask-Simple-Crypt-0.3.3.tar.gz", hash = "sha256:0d4033b6c9a03ac85d10f0fd213914390217dc53b2d41d153fa050fee9723594"},
|
||||
{file = "Flask_Simple_Crypt-0.3.3-py3-none-any.whl", hash = "sha256:08c3fcad955ac148bb885b1de4798c1cfce8512452072beee414bacf1552e8ef"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Flask = "*"
|
||||
pycryptodome = "*"
|
||||
|
||||
[[package]]
|
||||
name = "flask-sqlalchemy"
|
||||
version = "3.0.2"
|
||||
|
@ -1980,6 +1996,49 @@ files = [
|
|||
{file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodome"
|
||||
version = "3.17"
|
||||
description = "Cryptographic library for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
{file = "pycryptodome-3.17-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:2c5631204ebcc7ae33d11c43037b2dafe25e2ab9c1de6448eb6502ac69c19a56"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:04779cc588ad8f13c80a060b0b1c9d1c203d051d8a43879117fe6b8aaf1cd3fa"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f812d58c5af06d939b2baccdda614a3ffd80531a26e5faca2c9f8b1770b2b7af"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:9453b4e21e752df8737fdffac619e93c9f0ec55ead9a45df782055eb95ef37d9"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:121d61663267f73692e8bde5ec0d23c9146465a0d75cad75c34f75c752527b01"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27m-win32.whl", hash = "sha256:ba2d4fcb844c6ba5df4bbfee9352ad5352c5ae939ac450e06cdceff653280450"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27m-win_amd64.whl", hash = "sha256:87e2ca3aa557781447428c4b6c8c937f10ff215202ab40ece5c13a82555c10d6"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f44c0d28716d950135ff21505f2c764498eda9d8806b7c78764165848aa419bc"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5a790bc045003d89d42e3b9cb3cc938c8561a57a88aaa5691512e8540d1ae79c"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:d086d46774e27b280e4cece8ab3d87299cf0d39063f00f1e9290d096adc5662a"},
|
||||
{file = "pycryptodome-3.17-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:5587803d5b66dfd99e7caa31ed91fba0fdee3661c5d93684028ad6653fce725f"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:e7debd9c439e7b84f53be3cf4ba8b75b3d0b6e6015212355d6daf44ac672e210"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ca1ceb6303be1282148f04ac21cebeebdb4152590842159877778f9cf1634f09"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:dc22cc00f804485a3c2a7e2010d9f14a705555f67020eb083e833cabd5bd82e4"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80ea8333b6a5f2d9e856ff2293dba2e3e661197f90bf0f4d5a82a0a6bc83a626"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c133f6721fba313722a018392a91e3c69d3706ae723484841752559e71d69dc6"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:333306eaea01fde50a73c4619e25631e56c4c61bd0fb0a2346479e67e3d3a820"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1a30f51b990994491cec2d7d237924e5b6bd0d445da9337d77de384ad7f254f9"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:909e36a43fe4a8a3163e9c7fc103867825d14a2ecb852a63d3905250b308a4e5"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-win32.whl", hash = "sha256:a3228728a3808bc9f18c1797ec1179a0efb5068c817b2ffcf6bcd012494dffb2"},
|
||||
{file = "pycryptodome-3.17-cp35-abi3-win_amd64.whl", hash = "sha256:9ec565e89a6b400eca814f28d78a9ef3f15aea1df74d95b28b7720739b28f37f"},
|
||||
{file = "pycryptodome-3.17-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:e1819b67bcf6ca48341e9b03c2e45b1c891fa8eb1a8458482d14c2805c9616f2"},
|
||||
{file = "pycryptodome-3.17-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:f8e550caf52472ae9126953415e4fc554ab53049a5691c45b8816895c632e4d7"},
|
||||
{file = "pycryptodome-3.17-pp27-pypy_73-win32.whl", hash = "sha256:afbcdb0eda20a0e1d44e3a1ad6d4ec3c959210f4b48cabc0e387a282f4c7deb8"},
|
||||
{file = "pycryptodome-3.17-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a74f45aee8c5cc4d533e585e0e596e9f78521e1543a302870a27b0ae2106381e"},
|
||||
{file = "pycryptodome-3.17-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38bbd6717eac084408b4094174c0805bdbaba1f57fc250fd0309ae5ec9ed7e09"},
|
||||
{file = "pycryptodome-3.17-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f68d6c8ea2974a571cacb7014dbaada21063a0375318d88ac1f9300bc81e93c3"},
|
||||
{file = "pycryptodome-3.17-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8198f2b04c39d817b206ebe0db25a6653bb5f463c2319d6f6d9a80d012ac1e37"},
|
||||
{file = "pycryptodome-3.17-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3a232474cd89d3f51e4295abe248a8b95d0332d153bf46444e415409070aae1e"},
|
||||
{file = "pycryptodome-3.17-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4992ec965606054e8326e83db1c8654f0549cdb26fce1898dc1a20bc7684ec1c"},
|
||||
{file = "pycryptodome-3.17-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53068e33c74f3b93a8158dacaa5d0f82d254a81b1002e0cd342be89fcb3433eb"},
|
||||
{file = "pycryptodome-3.17-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:74794a2e2896cd0cf56fdc9db61ef755fa812b4a4900fa46c49045663a92b8d0"},
|
||||
{file = "pycryptodome-3.17.tar.gz", hash = "sha256:bce2e2d8e82fcf972005652371a3e8731956a0c1fbb719cc897943b3695ad91b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydocstyle"
|
||||
version = "6.1.1"
|
||||
|
@ -3567,4 +3626,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.12"
|
||||
content-hash = "966e10cff1c2553a407e0119d04fe01e7cdf40c1155086e551deb2c9df1914d6"
|
||||
content-hash = "2fd5138221eabec441b601bb3769be478bed42099e72e20f7b8aaa1c1a888909"
|
||||
|
|
|
@ -72,6 +72,7 @@ dateparser = "^1.1.2"
|
|||
types-dateparser = "^1.1.4.1"
|
||||
flask-jwt-extended = "^4.4.4"
|
||||
pylint = "^2.15.10"
|
||||
flask-simple-crypt = "^0.3.3"
|
||||
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
|
|
@ -13,6 +13,7 @@ from apscheduler.schedulers.base import BaseScheduler # type: ignore
|
|||
from flask.json.provider import DefaultJSONProvider
|
||||
from flask_cors import CORS # type: ignore
|
||||
from flask_mail import Mail # type: ignore
|
||||
from flask_simple_crypt import SimpleCrypt # type: ignore
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
import spiffworkflow_backend.load_database_models # noqa: F401
|
||||
|
@ -133,6 +134,11 @@ def create_app() -> flask.app.Flask:
|
|||
|
||||
configure_sentry(app)
|
||||
|
||||
cipher = SimpleCrypt()
|
||||
app.config["FSC_EXPANSION_COUNT"] = 2048
|
||||
cipher.init_app(app)
|
||||
app.config["CIPHER"] = cipher
|
||||
|
||||
app.before_request(verify_token)
|
||||
app.before_request(AuthorizationService.check_for_permission)
|
||||
app.after_request(set_new_access_token_in_cookie)
|
||||
|
|
|
@ -43,7 +43,7 @@ def authentication_callback(
|
|||
"""Authentication_callback."""
|
||||
verify_token(request.args.get("token"), force_run=True)
|
||||
response = request.args["response"]
|
||||
SecretService().update_secret(
|
||||
SecretService.update_secret(
|
||||
f"{service}/{auth_method}", response, g.user.id, create_if_not_exists=True
|
||||
)
|
||||
return redirect(
|
||||
|
|
|
@ -1,41 +1,38 @@
|
|||
"""Secret_service."""
|
||||
from typing import Optional
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from spiffworkflow_backend.exceptions.api_error import ApiError
|
||||
from spiffworkflow_backend.models.db import db
|
||||
from spiffworkflow_backend.models.secret_model import SecretModel
|
||||
|
||||
# from cryptography.fernet import Fernet
|
||||
#
|
||||
#
|
||||
# class EncryptionService:
|
||||
# key = Fernet.generate_key() # this is your "password"
|
||||
# cipher_suite = Fernet(key)
|
||||
# encoded_text = cipher_suite.encrypt(b"Hello stackoverflow!")
|
||||
# decoded_text = cipher_suite.decrypt(encoded_text)
|
||||
|
||||
|
||||
class SecretService:
|
||||
"""SecretService."""
|
||||
|
||||
# def encrypt_key(self, plain_key: str) -> str:
|
||||
# """Encrypt_key."""
|
||||
# # flask_secret = current_app.secret_key
|
||||
# # print("encrypt_key")
|
||||
# ...
|
||||
CIPHER_ENCODING = "ascii"
|
||||
|
||||
# def decrypt_key(self, encrypted_key: str) -> str:
|
||||
# """Decrypt key."""
|
||||
# ...
|
||||
@classmethod
|
||||
def _encrypt(cls, value: str) -> str:
|
||||
encrypted_bytes: bytes = current_app.config["CIPHER"].encrypt(value)
|
||||
return encrypted_bytes.decode(cls.CIPHER_ENCODING)
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def _decrypt(cls, value: str) -> str:
|
||||
bytes_to_decrypt = bytes(value, cls.CIPHER_ENCODING)
|
||||
decrypted_bytes: bytes = current_app.config["CIPHER"].decrypt(bytes_to_decrypt)
|
||||
return decrypted_bytes.decode(cls.CIPHER_ENCODING)
|
||||
|
||||
@classmethod
|
||||
def add_secret(
|
||||
cls,
|
||||
key: str,
|
||||
value: str,
|
||||
user_id: int,
|
||||
) -> SecretModel:
|
||||
"""Add_secret."""
|
||||
# encrypted_key = self.encrypt_key(key)
|
||||
value = cls._encrypt(value)
|
||||
secret_model = SecretModel(key=key, value=value, user_id=user_id)
|
||||
db.session.add(secret_model)
|
||||
try:
|
||||
|
@ -62,8 +59,9 @@ class SecretService:
|
|||
message=f"Unable to locate a secret with the name: {key}. ",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def update_secret(
|
||||
cls,
|
||||
key: str,
|
||||
value: str,
|
||||
user_id: Optional[int] = None,
|
||||
|
@ -72,6 +70,7 @@ class SecretService:
|
|||
"""Does this pass pre commit?"""
|
||||
secret_model = SecretModel.query.filter(SecretModel.key == key).first()
|
||||
if secret_model:
|
||||
value = cls._encrypt(value)
|
||||
secret_model.value = value
|
||||
db.session.add(secret_model)
|
||||
try:
|
||||
|
|
|
@ -31,8 +31,8 @@ class ServiceTaskDelegate:
|
|||
secret_prefix = "secret:" # noqa: S105
|
||||
if value.startswith(secret_prefix):
|
||||
key = value.removeprefix(secret_prefix)
|
||||
secret = SecretService().get_secret(key)
|
||||
return secret.value
|
||||
secret = SecretService.get_secret(key)
|
||||
return SecretService._decrypt(secret.value)
|
||||
|
||||
file_prefix = "file:"
|
||||
if value.startswith(file_prefix):
|
||||
|
@ -136,7 +136,7 @@ class ServiceTaskDelegate:
|
|||
secret_key = parsed_response["auth"]
|
||||
refreshed_token_set = json.dumps(parsed_response["refreshed_token_set"])
|
||||
user_id = g.user.id if UserService.has_user() else None
|
||||
SecretService().update_secret(secret_key, refreshed_token_set, user_id)
|
||||
SecretService.update_secret(secret_key, refreshed_token_set, user_id)
|
||||
return json.dumps(parsed_response["api_response"])
|
||||
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ class TestSecretService(SecretServiceTestHelpers):
|
|||
|
||||
assert test_secret is not None
|
||||
assert test_secret.key == self.test_key
|
||||
assert test_secret.value == self.test_value
|
||||
assert SecretService._decrypt(test_secret.value) == self.test_value
|
||||
assert test_secret.user_id == with_super_admin_user.id
|
||||
|
||||
def test_add_secret_duplicate_key_fails(
|
||||
|
@ -98,7 +98,7 @@ class TestSecretService(SecretServiceTestHelpers):
|
|||
|
||||
secret = SecretService().get_secret(self.test_key)
|
||||
assert secret is not None
|
||||
assert secret.value == self.test_value
|
||||
assert SecretService._decrypt(secret.value) == self.test_value
|
||||
|
||||
def test_get_secret_bad_key_fails(
|
||||
self,
|
||||
|
@ -123,13 +123,15 @@ class TestSecretService(SecretServiceTestHelpers):
|
|||
self.add_test_secret(with_super_admin_user)
|
||||
secret = SecretService.get_secret(self.test_key)
|
||||
assert secret
|
||||
assert secret.value == self.test_value
|
||||
assert SecretService._decrypt(secret.value) == self.test_value
|
||||
SecretService.update_secret(
|
||||
self.test_key, "new_secret_value", with_super_admin_user.id
|
||||
)
|
||||
new_secret = SecretService.get_secret(self.test_key)
|
||||
assert new_secret
|
||||
assert new_secret.value == "new_secret_value" # noqa: S105
|
||||
assert (
|
||||
SecretService._decrypt(new_secret.value) == "new_secret_value"
|
||||
) # noqa: S105
|
||||
|
||||
def test_update_secret_bad_secret_fails(
|
||||
self,
|
||||
|
@ -205,7 +207,7 @@ class TestSecretServiceApi(SecretServiceTestHelpers):
|
|||
for key in ["key", "value", "user_id"]:
|
||||
assert key in secret.keys()
|
||||
assert secret["key"] == self.test_key
|
||||
assert secret["value"] == self.test_value
|
||||
assert SecretService._decrypt(secret["value"]) == self.test_value
|
||||
assert secret["user_id"] == with_super_admin_user.id
|
||||
|
||||
def test_get_secret(
|
||||
|
@ -224,7 +226,7 @@ class TestSecretServiceApi(SecretServiceTestHelpers):
|
|||
assert secret_response
|
||||
assert secret_response.status_code == 200
|
||||
assert secret_response.json
|
||||
assert secret_response.json["value"] == self.test_value
|
||||
assert SecretService._decrypt(secret_response.json["value"]) == self.test_value
|
||||
|
||||
def test_update_secret(
|
||||
self,
|
||||
|
@ -237,7 +239,7 @@ class TestSecretServiceApi(SecretServiceTestHelpers):
|
|||
self.add_test_secret(with_super_admin_user)
|
||||
secret: Optional[SecretModel] = SecretService.get_secret(self.test_key)
|
||||
assert secret
|
||||
assert secret.value == self.test_value
|
||||
assert SecretService._decrypt(secret.value) == self.test_value
|
||||
secret_model = SecretModel(
|
||||
key=self.test_key,
|
||||
value="new_secret_value",
|
||||
|
@ -254,7 +256,7 @@ class TestSecretServiceApi(SecretServiceTestHelpers):
|
|||
secret_model = SecretModel.query.filter(
|
||||
SecretModel.key == self.test_key
|
||||
).first()
|
||||
assert secret_model.value == "new_secret_value"
|
||||
assert SecretService._decrypt(secret_model.value) == "new_secret_value"
|
||||
|
||||
def test_delete_secret(
|
||||
self,
|
||||
|
@ -267,7 +269,7 @@ class TestSecretServiceApi(SecretServiceTestHelpers):
|
|||
self.add_test_secret(with_super_admin_user)
|
||||
secret = SecretService.get_secret(self.test_key)
|
||||
assert secret
|
||||
assert secret.value == self.test_value
|
||||
assert SecretService._decrypt(secret.value) == self.test_value
|
||||
secret_response = client.delete(
|
||||
f"/v1.0/secrets/{self.test_key}",
|
||||
headers=self.logged_in_headers(with_super_admin_user),
|
||||
|
|
Loading…
Reference in New Issue