perf(secrets): review

This commit is contained in:
pycook 2023-10-28 16:17:35 +08:00
parent 7bc62ba66b
commit 68aec77d75
5 changed files with 41 additions and 21 deletions

View File

@ -36,7 +36,7 @@ marshmallow = "==2.20.2"
celery = ">=5.3.1"
celery_once = "==3.0.1"
more-itertools = "==5.0.0"
kombu = "==5.3.1"
kombu = ">=5.3.1"
# common setting
timeout-decorator = "==0.5.0"
WTForms = "==3.0.0"

View File

@ -20,8 +20,8 @@ import api.views.entry
from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager, migrate, rd)
from api.extensions import inner_secrets
from api.flask_cas import CAS
from api.models.acl import User
from api.lib.secrets.secrets import InnerKVManger
from api.models.acl import User
HERE = os.path.abspath(os.path.dirname(__file__))
PROJECT_ROOT = os.path.join(HERE, os.pardir)
@ -127,7 +127,10 @@ def register_extensions(app):
app.config.update(app.config.get("CELERY"))
celery.conf.update(app.config)
inner_secrets.init_app(app, InnerKVManger())
if app.config.get('SECRETS_ENGINE') == 'inner':
with app.app_context():
inner_secrets.init_app(app, InnerKVManger())
def register_blueprints(app):

View File

@ -1,24 +1,23 @@
import os
import secrets
import sys
from base64 import b64encode, b64decode
from base64 import b64decode, b64encode
from Cryptodome.Protocol.SecretSharing import Shamir
from colorama import Back
from colorama import Fore
from colorama import Style
from colorama import init as colorama_init
from colorama import Style
from Cryptodome.Protocol.SecretSharing import Shamir
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers import modes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from flask import current_app
# global_root_key just for test here
global_iv_length = 16
global_key_shares = 5 # Number of generated key shares
global_key_threshold = 3 # Minimum number of shares required to rebuild the key
@ -38,6 +37,7 @@ def string_to_bytes(value):
byte_string = value
else:
byte_string = value.encode("utf-8")
return byte_string
@ -65,8 +65,9 @@ class KeyManage:
if not self.trigger:
return
self.backend = backend
# resp = self.auto_unseal()
# self.print_response(resp)
resp = self.auto_unseal()
self.print_response(resp)
def hash_root_key(self, value):
algorithm = hashes.SHA256()
@ -76,6 +77,7 @@ class KeyManage:
msg, ok = self.backend.add(backend_root_key_salt_name, salt)
if not ok:
return msg, ok
kdf = PBKDF2HMAC(
algorithm=algorithm,
length=32,
@ -83,6 +85,7 @@ class KeyManage:
iterations=100000,
)
key = kdf.derive(string_to_bytes(value))
return b64encode(key).decode('utf-8'), True
def generate_encrypt_key(self, key):
@ -90,6 +93,7 @@ class KeyManage:
salt = self.backend.get(backend_encrypt_key_salt_name)
if not salt:
salt = secrets.token_hex(32)
kdf = PBKDF2HMAC(
algorithm=algorithm,
length=32,
@ -106,21 +110,22 @@ class KeyManage:
@classmethod
def generate_keys(cls, secret):
shares = Shamir.split(global_key_threshold, global_key_shares, secret)
shares = Shamir.split(global_key_threshold, global_key_shares, secret, False)
new_shares = []
for share in shares:
t = [i for i in share[1]] + [ord(i) for i in "{:0>2}".format(share[0])]
new_shares.append(b64encode(bytes(t)))
return new_shares
def auth_root_secret(self, root_key):
# root_key_hash, ok = self.hash_root_key(b64encode(root_key))
root_key_hash, ok = self.hash_root_key(root_key)
if not ok:
return {
"message": root_key_hash,
"status": "failed"
}
backend_root_key_hash = self.backend.get(backend_root_key_name)
if not backend_root_key_hash:
return {
@ -132,12 +137,14 @@ class KeyManage:
"message": "invalid root key",
"status": "failed"
}
encrypt_key_aes = self.backend.get(backend_encrypt_key_name)
if not encrypt_key_aes:
return {
"message": "encrypt key is empty",
"status": "failed"
}
secrets_encrypt_key, ok = InnerCrypt.aes_decrypt(string_to_bytes(root_key), encrypt_key_aes)
if ok:
current_app.config["secrets_encrypt_key"] = secrets_encrypt_key
@ -156,19 +163,17 @@ class KeyManage:
"message": "current status is unseal, skip",
"status": "skip"
}
try:
t = [i for i in b64decode(key)]
v = (int("".join([chr(i) for i in t[-2:]])), bytes(t[:-2]))
print("............")
# shares = getattr(current_app.config, "secrets_shares", [])
shares = current_app.config.get("secrets_shares", [])
print("222222222222")
if v not in shares:
shares.append(v)
current_app.config["secrets_shares"] = shares
print("shares:", shares)
if len(shares) >= global_key_threshold:
recovered_secret = Shamir.combine(shares[:global_key_threshold])
recovered_secret = Shamir.combine(shares[:global_key_threshold], False)
return self.auth_root_secret(b64encode(recovered_secret))
else:
return {
@ -186,6 +191,7 @@ class KeyManage:
info = self.backend.get(backend_root_key_name)
if info:
return "already exist", [], False
secret = AESGCM.generate_key(128)
shares = self.generate_keys(secret)
@ -203,27 +209,31 @@ class KeyManage:
root_key, shares, status = self.generate_unseal_keys()
if not status:
return {"message": root_key}, False
# hash root key and store in backend
root_key_hash, ok = self.hash_root_key(root_key)
if not ok:
return {"message": root_key_hash}, False
msg, ok = self.backend.add(backend_root_key_name, root_key_hash)
if not ok:
return {"message": msg}, False
# generate encrypt key from root_key and store in backend
encrypt_key, ok = self.generate_encrypt_key(root_key)
if not ok:
return {"message": encrypt_key}
encrypt_key_aes, status = InnerCrypt.aes_encrypt(root_key, encrypt_key)
if not status:
return {"message": encrypt_key_aes}
msg, ok = self.backend.add(backend_encrypt_key_name, encrypt_key_aes)
if not ok:
return {"message": msg}, False
current_app.config["secrets_root_key"] = root_key
current_app.config["secrets_encrypt_key"] = encrypt_key
print(".....", current_app.config["secrets_root_key"], current_app.config["secrets_encrypt_key"])
self.print_token(shares, root_token=root_key)
return {"message": "OK",
@ -238,6 +248,7 @@ class KeyManage:
"message": "trigger config is empty, skip",
"status": "skip"
}
if self.trigger.startswith("http"):
return {
"message": "todo in next step, skip",
@ -270,6 +281,7 @@ class KeyManage:
"message": root_key_hash,
"status": "failed"
}
backend_root_key_hash = self.backend.get(backend_root_key_name)
if not backend_root_key_hash:
return {
@ -315,10 +327,12 @@ class KeyManage:
print(Style.BRIGHT, "Please be sure to store the Unseal Key in a secure location and avoid losing it."
" The Unseal Key is required to unseal the system every time when it restarts."
" Successful unsealing is necessary to enable the password feature." + Style.RESET_ALL)
for i, v in enumerate(shares):
print(
"unseal token " + str(i + 1) + ": " + Fore.RED + Back.CYAN + v.decode("utf-8") + Style.RESET_ALL)
print()
print(Fore.GREEN + "root token: " + root_token.decode("utf-8") + Style.RESET_ALL)
@classmethod
@ -338,7 +352,6 @@ class KeyManage:
class InnerCrypt:
def __init__(self):
secrets_encrypt_key = current_app.config.get("secrets_encrypt_key", "")
print("secrets_encrypt_key:", secrets_encrypt_key)
self.encrypt_key = b64decode(secrets_encrypt_key.encode("utf-8"))
def encrypt(self, plaintext):
@ -364,6 +377,7 @@ class InnerCrypt:
v_padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_plaintext = v_padder.update(plaintext) + v_padder.finalize()
ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
return b64encode(iv + ciphertext).decode("utf-8"), True
except Exception as e:
return str(e), False
@ -379,6 +393,7 @@ class InnerCrypt:
decrypted_padded_plaintext = decrypter.update(ciphertext) + decrypter.finalize()
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
plaintext = unpadder.update(decrypted_padded_plaintext) + unpadder.finalize()
return plaintext.decode('utf-8'), True
except Exception as e:
return str(e), False

View File

@ -11,11 +11,13 @@ class InnerKVManger(object):
res = InnerKV.create(**data)
if res.key == key:
return "success", True
return "add failed", False
@classmethod
def get(cls, key):
res = InnerKV().get_by(first=True, to_dict=False, **{"key": key})
res = InnerKV.get_by(first=True, to_dict=False, **{"key": key})
if not res:
return None
return res.value

View File

@ -23,7 +23,7 @@ itsdangerous==2.1.2
Jinja2==3.1.2
jinja2schema==0.1.4
jsonschema==4.18.0
kombu==5.3.1
kombu>=5.3.1
Mako==1.2.4
MarkupSafe==2.1.3
marshmallow==2.20.2