mirror of https://github.com/veops/cmdb.git
feat: 密码存储 增加内置 AES 加密;增加 Vault Transit 加密
This commit is contained in:
parent
6503d32e6e
commit
1b2a97890a
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 时,需要配置
|
||||||
|
|
Loading…
Reference in New Issue