mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-10-20 07:19:03 +00:00
feat: 账号密钥用vault储存 (#10830)
* feat: 账号密钥用vault储存 * perf: 优化 Vault * perf: 重构 Vault Backend 设计架构 (未完成) * perf: 重构 Vault Backend 设计架构 (未完成2) * perf: 重构 Vault Backend 设计架构 (未完成3) * perf: 重构 Vault Backend 设计架构 (未完成4) * perf: 重构 Vault Backend 设计架构 (未完成5) * perf: 重构 Vault Backend 设计架构 (已完成) * perf: 重构 Vault Backend 设计架构 (已完成) * perf: 重构 Vault Backend 设计架构 (已完成) * perf: 小优化 * perf: 优化 --------- Co-authored-by: feng <1304903146@qq.com> Co-authored-by: Bai <baijiangjie@gmail.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>
This commit is contained in:
@@ -7,9 +7,10 @@ from simple_history.models import HistoricalRecords
|
||||
from assets.models.base import AbsConnectivity
|
||||
from common.utils import lazyproperty
|
||||
from .base import BaseAccount
|
||||
from .mixins import VaultModelMixin
|
||||
from ..const import AliasAccount, Source
|
||||
|
||||
__all__ = ['Account', 'AccountTemplate']
|
||||
__all__ = ['Account', 'AccountTemplate', 'AccountHistoricalRecords']
|
||||
|
||||
|
||||
class AccountHistoricalRecords(HistoricalRecords):
|
||||
@@ -32,7 +33,7 @@ class AccountHistoricalRecords(HistoricalRecords):
|
||||
diff = attrs - history_attrs
|
||||
if not diff:
|
||||
return
|
||||
super().post_save(instance, created, using=using, **kwargs)
|
||||
return super().post_save(instance, created, using=using, **kwargs)
|
||||
|
||||
def create_history_model(self, model, inherited):
|
||||
if self.included_fields and not self.excluded_fields:
|
||||
@@ -53,7 +54,7 @@ class Account(AbsConnectivity, BaseAccount):
|
||||
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
||||
)
|
||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
||||
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
|
||||
history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version'])
|
||||
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
|
||||
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
|
||||
|
||||
@@ -198,3 +199,21 @@ class AccountTemplate(BaseAccount):
|
||||
return
|
||||
self.bulk_update_accounts(accounts, {'secret': self.secret})
|
||||
self.bulk_create_history_accounts(accounts, user_id)
|
||||
|
||||
|
||||
def replace_history_model_with_mixin():
|
||||
"""
|
||||
替换历史模型中的父类为指定的Mixin类。
|
||||
|
||||
Parameters:
|
||||
model (class): 历史模型类,例如 Account.history.model
|
||||
mixin_class (class): 要替换为的Mixin类
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
model = Account.history.model
|
||||
model.__bases__ = (VaultModelMixin,) + model.__bases__
|
||||
|
||||
|
||||
replace_history_model_with_mixin()
|
||||
|
@@ -9,33 +9,32 @@ from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from accounts.const import SecretType
|
||||
from common.db import fields
|
||||
from common.utils import (
|
||||
ssh_key_string_to_obj, ssh_key_gen, get_logger,
|
||||
random_string, lazyproperty, parse_ssh_public_key_str, is_openssh_format_key
|
||||
)
|
||||
from accounts.models.mixins import VaultModelMixin, VaultManagerMixin, VaultQuerySetMixin
|
||||
from orgs.mixins.models import JMSOrgBaseModel, OrgManager
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class BaseAccountQuerySet(models.QuerySet):
|
||||
class BaseAccountQuerySet(VaultQuerySetMixin, models.QuerySet):
|
||||
def active(self):
|
||||
return self.filter(is_active=True)
|
||||
|
||||
|
||||
class BaseAccountManager(OrgManager):
|
||||
class BaseAccountManager(VaultManagerMixin, OrgManager):
|
||||
def active(self):
|
||||
return self.get_queryset().active()
|
||||
|
||||
|
||||
class BaseAccount(JMSOrgBaseModel):
|
||||
class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
|
||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
|
||||
secret_type = models.CharField(
|
||||
max_length=16, choices=SecretType.choices, default=SecretType.PASSWORD, verbose_name=_('Secret type')
|
||||
)
|
||||
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
|
||||
|
1
apps/accounts/models/mixins/__init__.py
Normal file
1
apps/accounts/models/mixins/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .vault import *
|
88
apps/accounts/models/mixins/vault.py
Normal file
88
apps/accounts/models/mixins/vault.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.db import fields
|
||||
|
||||
__all__ = ['VaultQuerySetMixin', 'VaultManagerMixin', 'VaultModelMixin']
|
||||
|
||||
|
||||
class VaultQuerySetMixin(models.QuerySet):
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""
|
||||
1. 替换 secret 为 _secret
|
||||
2. 触发 post_save 信号
|
||||
"""
|
||||
if 'secret' in kwargs:
|
||||
kwargs.update({
|
||||
'_secret': kwargs.pop('secret')
|
||||
})
|
||||
rows = super().update(**kwargs)
|
||||
|
||||
# 为了获取更新后的对象所以单独查询一次
|
||||
ids = self.values_list('id', flat=True)
|
||||
objs = self.model.objects.filter(id__in=ids)
|
||||
for obj in objs:
|
||||
post_save.send(obj.__class__, instance=obj, created=False)
|
||||
return rows
|
||||
|
||||
|
||||
class VaultManagerMixin(models.Manager):
|
||||
""" 触发 bulk_create 和 bulk_update 操作下的 post_save 信号 """
|
||||
|
||||
def bulk_create(self, objs, batch_size=None, ignore_conflicts=False):
|
||||
objs = super().bulk_create(objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts)
|
||||
for obj in objs:
|
||||
post_save.send(obj.__class__, instance=obj, created=True)
|
||||
return objs
|
||||
|
||||
def bulk_update(self, objs, batch_size=None, ignore_conflicts=False):
|
||||
objs = super().bulk_update(objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts)
|
||||
for obj in objs:
|
||||
post_save.send(obj.__class__, instance=obj, created=False)
|
||||
return objs
|
||||
|
||||
|
||||
class VaultModelMixin(models.Model):
|
||||
_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||
is_sync_metadata = True
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
# 缓存 secret 值, lazy-property 不能用
|
||||
__secret = False
|
||||
|
||||
@property
|
||||
def secret(self):
|
||||
if self.__secret is False:
|
||||
from accounts.backends import vault_client
|
||||
secret = vault_client.get(self)
|
||||
if not secret and not self.secret_has_save_to_vault:
|
||||
# vault_client 获取不到, 并且 secret 没有保存到 vault, 就从 self._secret 获取
|
||||
secret = self._secret
|
||||
self.__secret = secret
|
||||
return self.__secret
|
||||
|
||||
@secret.setter
|
||||
def secret(self, value):
|
||||
"""
|
||||
保存的时候通过 post_save 信号监听进行处理,
|
||||
先保存到 db, 再保存到 vault 同时删除本地 db _secret 值
|
||||
"""
|
||||
self._secret = value
|
||||
|
||||
_secret_save_to_vault_mark = '# Secret-has-been-saved-to-vault #'
|
||||
|
||||
def mark_secret_save_to_vault(self):
|
||||
self._secret = self._secret_save_to_vault_mark
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def secret_has_save_to_vault(self):
|
||||
return self._secret == self._secret_save_to_vault_mark
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" 通过 post_save signal 处理 _secret 数据 """
|
||||
return super().save(*args, **kwargs)
|
Reference in New Issue
Block a user