feat: 密码存储 增加内置 AES 加密;增加 Vault Transit 加密

This commit is contained in:
MimoAtHome 2023-10-21 02:17:20 +08:00
parent 6503d32e6e
commit 1b2a97890a
5 changed files with 51 additions and 4 deletions

View File

@ -26,6 +26,7 @@ Flask-Bcrypt = "==1.0.1"
Flask-Cors = ">=3.0.8" Flask-Cors = ">=3.0.8"
ldap3 = "==2.9.1" ldap3 = "==2.9.1"
pycryptodome = "==3.12.0" pycryptodome = "==3.12.0"
hvac = "==1.2.1"
# Caching # Caching
Flask-Caching = ">=1.0.0" Flask-Caching = ">=1.0.0"
# Environment variable parsing # Environment variable parsing

View File

@ -6,6 +6,7 @@ import time
from typing import Set from typing import Set
import elasticsearch import elasticsearch
import hvac
import redis import redis
import six import six
from Crypto.Cipher import AES from Crypto.Cipher import AES
@ -286,3 +287,35 @@ class AESCrypto(object):
text_decrypted = cipher.decrypt(encode_bytes) text_decrypted = cipher.decrypt(encode_bytes)
return cls.unpad(text_decrypted).decode('utf8') return cls.unpad(text_decrypted).decode('utf8')
class VaultTransitCrypto:
TRANSIT_KEY_NAME = 'cmdb-hvac-key'
client = None
@classmethod
def init_client(cls):
if not cls.client or not cls.client.is_authenticated():
cls.client = hvac.Client(
url=current_app.config.get('VAULT_URL'),
token=current_app.config.get('VAULT_TOKEN'),
)
@classmethod
def encrypt(cls, text):
cls.init_client()
cls.client.secrets.transit.create_key(name=cls.TRANSIT_KEY_NAME)
encrypt_data_response = cls.client.secrets.transit.encrypt_data(
name=cls.TRANSIT_KEY_NAME,
plaintext=base64.b64encode(text.encode()).decode(),
)
return encrypt_data_response['data']['ciphertext']
@classmethod
def decrypt(cls, ciphertext):
cls.init_client()
decrypt_data_response = cls.client.secrets.transit.decrypt_data(
name=cls.TRANSIT_KEY_NAME,
ciphertext=ciphertext,
)
return base64.b64decode(decrypt_data_response['data']['plaintext']).decode()

View File

@ -16,6 +16,7 @@ from api.lib.database import Model
from api.lib.database import SoftDeleteMixin from api.lib.database import SoftDeleteMixin
from api.lib.perm.acl.const import ACL_QUEUE from api.lib.perm.acl.const import ACL_QUEUE
from api.lib.perm.acl.const import OperateType from api.lib.perm.acl.const import OperateType
from api.lib.utils import AESCrypto, VaultTransitCrypto
class App(Model): class App(Model):
@ -126,7 +127,7 @@ class User(CRUDModel, SoftDeleteMixin):
catalog = db.Column(db.String(64)) catalog = db.Column(db.String(64))
email = db.Column(db.String(100), unique=True, nullable=False) email = db.Column(db.String(100), unique=True, nullable=False)
mobile = db.Column(db.String(14), unique=True) mobile = db.Column(db.String(14), unique=True)
_password = db.Column("password", db.String(80)) _password = db.Column("password", db.String(128))
key = db.Column(db.String(32), nullable=False) key = db.Column(db.String(32), nullable=False)
secret = db.Column(db.String(32), nullable=False) secret = db.Column(db.String(32), nullable=False)
date_joined = db.Column(db.DateTime, default=datetime.utcnow) date_joined = db.Column(db.DateTime, default=datetime.utcnow)
@ -155,14 +156,21 @@ class User(CRUDModel, SoftDeleteMixin):
return self._password return self._password
def _set_password(self, password): def _set_password(self, password):
self._password = hashlib.md5(password.encode('utf-8')).hexdigest() md5_password = hashlib.md5(password.encode('utf-8')).hexdigest()
if current_app.config.get("ENCRYPT_PASSWORD_TYPE") == 'VAULT':
self._password = VaultTransitCrypto.encrypt(md5_password)
else:
self._password = AESCrypto.encrypt(md5_password)
password = db.synonym("_password", descriptor=property(_get_password, _set_password)) password = db.synonym("_password", descriptor=property(_get_password, _set_password))
def check_password(self, password): def check_password(self, md5_password):
if self.password is None: if self.password is None:
return False return False
return self.password == password or self.password == hashlib.md5(password.encode('utf-8')).hexdigest() if current_app.config.get("ENCRYPT_PASSWORD_TYPE") == 'VAULT':
return VaultTransitCrypto.decrypt(self.password) == md5_password
else:
return AESCrypto.decrypt(self.password) == md5_password
class RoleQuery(BaseQuery): class RoleQuery(BaseQuery):

View File

@ -18,6 +18,7 @@ Flask-RESTful==0.3.10
Flask-SQLAlchemy==2.5.0 Flask-SQLAlchemy==2.5.0
future==0.18.3 future==0.18.3
gunicorn==21.0.1 gunicorn==21.0.1
hvac==1.2.1
itsdangerous==2.1.2 itsdangerous==2.1.2
Jinja2==3.1.2 Jinja2==3.1.2
jinja2schema==0.1.4 jinja2schema==0.1.4

View File

@ -97,3 +97,7 @@ BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y'
# # messenger # # messenger
USE_MESSENGER = True USE_MESSENGER = True
ENCRYPT_PASSWORD_TYPE = "VAULT" # "VAULT" or "AES"
VAULT_URL = 'http://' # 当 ENCRYPT_PASSWORD_TYPE 为 VAULT 时,需要配置
VAULT_TOKEN = '' # 当 ENCRYPT_PASSWORD_TYPE 为 VAULT 时,需要配置