Merge pull request #167 from sartography/simple_crypt

Encrypt secrets in the db
This commit is contained in:
jasquat 2023-03-07 10:48:24 -05:00 committed by GitHub
commit 8c1af67383
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 101 additions and 34 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"])

View File

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