mirror of https://github.com/jumpserver/jumpserver
perf: 优化 account 结构
parent
9e84989bbe
commit
8c72bab82d
|
@ -23,10 +23,8 @@ class Migration(migrations.Migration):
|
|||
('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
|
||||
('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
|
||||
('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
|
||||
('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')),
|
||||
('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
||||
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')),
|
||||
|
@ -55,10 +53,8 @@ class Migration(migrations.Migration):
|
|||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
|
||||
('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
|
||||
('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
|
||||
('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')),
|
||||
('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')),
|
||||
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
|
@ -70,7 +66,7 @@ class Migration(migrations.Migration):
|
|||
options={
|
||||
'verbose_name': 'Account',
|
||||
'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')],
|
||||
'unique_together': {('username', 'asset'), ('name', 'asset')},
|
||||
'unique_together': {('name', 'asset'), ('username', 'asset', 'secret_type')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
|
|
|
@ -29,29 +29,51 @@ def migrate_accounts(apps, schema_editor):
|
|||
accounts = []
|
||||
# auth book 和 account 相同的属性
|
||||
same_attrs = [
|
||||
'id', 'comment', 'date_created', 'date_updated',
|
||||
'id', 'username', 'comment', 'date_created', 'date_updated',
|
||||
'created_by', 'asset_id', 'org_id',
|
||||
]
|
||||
# 认证的属性,可能是 authbook 的,可能是 systemuser 的
|
||||
auth_attrs = ['username', 'password', 'private_key', 'public_key']
|
||||
auth_attrs = ['password', 'private_key', 'token']
|
||||
all_attrs = same_attrs + auth_attrs
|
||||
|
||||
for auth_book in auth_books:
|
||||
values = {attr: getattr(auth_book, attr) for attr in same_attrs}
|
||||
values['version'] = 1
|
||||
values = {'version': 1}
|
||||
|
||||
system_user = auth_book.systemuser
|
||||
if system_user:
|
||||
values.update({attr: getattr(system_user, attr) for attr in auth_attrs})
|
||||
# 更新一次系统用户的认证属性
|
||||
values.update({attr: getattr(system_user, attr, '') for attr in all_attrs})
|
||||
values['created_by'] = str(system_user.id)
|
||||
values['privileged'] = system_user.type == 'admin'
|
||||
|
||||
auth_book_auth = {attr: getattr(auth_book, attr) for attr in auth_attrs}
|
||||
auth_book_auth = {attr: value for attr, value in auth_book_auth.items() if value}
|
||||
auth_book_auth = {attr: getattr(auth_book, attr, '') for attr in all_attrs if getattr(auth_book, attr, '')}
|
||||
# 最终使用 authbook 的认证属性
|
||||
values.update(auth_book_auth)
|
||||
values['name'] = values['username']
|
||||
|
||||
account = account_model(**values)
|
||||
accounts.append(account)
|
||||
auth_infos = []
|
||||
username = values['username']
|
||||
for attr in auth_attrs:
|
||||
secret = values.pop(attr, None)
|
||||
if not secret:
|
||||
continue
|
||||
|
||||
if attr == 'private_key':
|
||||
secret_type = 'ssh_key'
|
||||
name = f'{username}(ssh key)'
|
||||
elif attr == 'token':
|
||||
secret_type = 'token'
|
||||
name = f'{username}(token)'
|
||||
else:
|
||||
secret_type = attr
|
||||
name = username
|
||||
auth_infos.append((name, secret_type, secret))
|
||||
|
||||
if not auth_infos:
|
||||
auth_infos.append((username, 'password', ''))
|
||||
|
||||
for name, secret_type, secret in auth_infos:
|
||||
account = account_model(**values, name=name, secret=secret, secret_type=secret_type)
|
||||
accounts.append(account)
|
||||
|
||||
account_model.objects.bulk_create(accounts, ignore_conflicts=True)
|
||||
print("Create accounts: {}-{} using: {:.2f}s".format(
|
||||
|
|
|
@ -10,24 +10,11 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='port',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='asset',
|
||||
old_name='protocols',
|
||||
new_name='_protocols',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Protocol',
|
||||
fields=[
|
||||
|
|
|
@ -6,12 +6,10 @@ from django.db import migrations
|
|||
def migrate_asset_protocols(apps, schema_editor):
|
||||
asset_model = apps.get_model('assets', 'Asset')
|
||||
protocol_model = apps.get_model('assets', 'Protocol')
|
||||
asset_protocol_through = asset_model.protocols.through
|
||||
|
||||
count = 0
|
||||
bulk_size = 1000
|
||||
print("\nStart migrate asset protocols")
|
||||
protocol_map = {}
|
||||
while True:
|
||||
start = time.time()
|
||||
assets = asset_model.objects.all()[count:count+bulk_size]
|
||||
|
@ -19,23 +17,25 @@ def migrate_asset_protocols(apps, schema_editor):
|
|||
break
|
||||
count += len(assets)
|
||||
assets_protocols = []
|
||||
for asset in assets:
|
||||
old_protocols = asset._protocols
|
||||
|
||||
for name_port in old_protocols.split(','):
|
||||
for asset in assets:
|
||||
old_protocols = asset._protocols or '{}/{}'.format(asset.protocol, asset.port) or 'ssh/22'
|
||||
|
||||
if ',' in old_protocols:
|
||||
_protocols = old_protocols.split(',')
|
||||
else:
|
||||
_protocols = old_protocols.split()
|
||||
|
||||
for name_port in _protocols:
|
||||
name_port_list = name_port.split('/')
|
||||
if len(name_port_list) != 2:
|
||||
continue
|
||||
|
||||
name, port = name_port_list
|
||||
protocol = protocol_map.get(name_port)
|
||||
if not protocol:
|
||||
protocol = protocol_model.objects.get_or_create(
|
||||
defaults={'name': name, 'port': port, 'asset': asset},
|
||||
name=name, port=port
|
||||
)[0]
|
||||
assets_protocols.append(asset_protocol_through(asset_id=asset.id, protocol_id=protocol.id))
|
||||
asset_model.protocols.through.objects.bulk_create(assets_protocols, ignore_conflicts=True)
|
||||
protocol = protocol_model(**{'name': name, 'port': port, 'asset': asset})
|
||||
assets_protocols.append(protocol)
|
||||
|
||||
protocol_model.objects.bulk_create(assets_protocols, ignore_conflicts=True)
|
||||
print("Create asset protocols: {}-{} using: {:.2f}s".format(
|
||||
count - len(assets), count, time.time()-start
|
||||
))
|
||||
|
|
|
@ -18,11 +18,24 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='_protocols',
|
||||
name='admin_user',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
name='port',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='_protocols',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
|
|
|
@ -20,14 +20,12 @@ class Migration(migrations.Migration):
|
|||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
|
||||
('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
|
||||
('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
|
||||
('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'),),
|
||||
('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')),
|
||||
('privileged', models.BooleanField(default=False, verbose_name='Privileged')),
|
||||
],
|
||||
options={
|
||||
|
|
|
@ -11,11 +11,6 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='platform',
|
||||
name='brand',
|
||||
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Brand'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PlatformAutomation',
|
||||
fields=[
|
||||
|
|
|
@ -23,7 +23,7 @@ class Account(BaseAccount):
|
|||
class Meta:
|
||||
verbose_name = _('Account')
|
||||
unique_together = [
|
||||
('username', 'asset'),
|
||||
('username', 'asset', 'secret_type'),
|
||||
('name', 'asset'),
|
||||
]
|
||||
permissions = [
|
||||
|
|
|
@ -77,6 +77,9 @@ class Protocol(models.Model):
|
|||
port = models.IntegerField(verbose_name=_("Port"))
|
||||
asset = models.ForeignKey('Asset', on_delete=models.CASCADE, related_name='protocols', verbose_name=_("Asset"))
|
||||
|
||||
def __str__(self):
|
||||
return '{}/{}'.format(self.name, self.port)
|
||||
|
||||
|
||||
class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
|
|
|
@ -55,15 +55,17 @@ class AbsConnectivity(models.Model):
|
|||
|
||||
|
||||
class BaseAccount(OrgModelMixin):
|
||||
class SecretType(models.TextChoices):
|
||||
password = 'password', _('Password')
|
||||
ssh_key = 'ssh_key', _('SSH key')
|
||||
access_key = 'access_key', _('Access key')
|
||||
token = 'token', _('Token')
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
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, default='password', verbose_name=_('Secret type'))
|
||||
secret_type = models.CharField(max_length=16, choices=SecretType.choices, default='password', verbose_name=_('Secret type'))
|
||||
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
|
||||
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
|
||||
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
|
||||
token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token'))
|
||||
privileged = models.BooleanField(verbose_name=_("Privileged"), default=False)
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
|
@ -76,6 +78,28 @@ class BaseAccount(OrgModelMixin):
|
|||
APPS_AMOUNT_CACHE_KEY = "APP_USER_{}_APPS_AMOUNT"
|
||||
APP_USER_CACHE_TIME = 600
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
return ''
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
return ''
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, value):
|
||||
self.secret = value
|
||||
self.secret_type = 'private_key'
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return self.secret
|
||||
|
||||
@password.setter
|
||||
def password(self, value):
|
||||
self.secret = value
|
||||
self.secret_type = 'password'
|
||||
|
||||
def expire_assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cache.delete(cache_key)
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from common.db import fields
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from .base import BaseAccount
|
||||
|
||||
|
@ -64,7 +65,12 @@ class Gateway(BaseAccount):
|
|||
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
|
||||
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
token = None
|
||||
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
|
||||
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
|
||||
|
||||
secret = None
|
||||
secret_type = None
|
||||
privileged = None
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -52,7 +52,6 @@ class Platform(models.Model):
|
|||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||
# 资产有关的
|
||||
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
|
||||
brand = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Brand")) # 厂商主要是给网络设备
|
||||
domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled"))
|
||||
protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled"))
|
||||
protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols"))
|
||||
|
|
|
@ -9,16 +9,16 @@ class AccountFieldsSerializerMixin(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
fields_mini = [
|
||||
'id', 'name', 'username', 'privileged',
|
||||
'platform', 'version'
|
||||
'platform', 'version', 'secret_type',
|
||||
]
|
||||
fields_write_only = ['password', 'private_key', 'public_key', 'passphrase']
|
||||
fields_write_only = ['secret', 'passphrase']
|
||||
fields_other = ['date_created', 'date_updated', 'comment']
|
||||
fields_small = fields_mini + fields_write_only + fields_other
|
||||
fields_fk = ['asset']
|
||||
fields = fields_small + fields_fk
|
||||
extra_kwargs = {
|
||||
'private_key': {'write_only': True},
|
||||
'public_key': {'write_only': True},
|
||||
'secret': {'write_only': True},
|
||||
'passphrase': {'write_only': True},
|
||||
'token': {'write_only': True},
|
||||
'password': {'write_only': True},
|
||||
}
|
||||
|
|
|
@ -22,13 +22,6 @@ class AssetProtocolsSerializer(serializers.ModelSerializer):
|
|||
model = Protocol
|
||||
fields = ['id', 'name', 'port']
|
||||
|
||||
def create(self, validated_data):
|
||||
instance = Protocol.objects.filter(**validated_data).first()
|
||||
if instance:
|
||||
return instance
|
||||
instance = Protocol.objects.create(**validated_data)
|
||||
return instance
|
||||
|
||||
|
||||
class AssetLabelSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
|
@ -55,10 +48,10 @@ class AssetAccountSerializer(AccountSerializer):
|
|||
class Meta(AccountSerializer.Meta):
|
||||
fields_mini = [
|
||||
'id', 'name', 'username', 'privileged', 'version',
|
||||
'secret_type',
|
||||
]
|
||||
fields_write_only = [
|
||||
'password', 'private_key', 'public_key',
|
||||
'passphrase', 'token', 'push_now'
|
||||
'secret', 'passphrase', 'push_now'
|
||||
]
|
||||
fields = fields_mini + fields_write_only
|
||||
|
||||
|
|
|
@ -70,7 +70,6 @@ class PlatformSerializer(JMSWritableNestedModelSerializer):
|
|||
choices=[('sudo', 'sudo su -'), ('su', 'su - ')],
|
||||
label='切换方式', required=False, default='sudo'
|
||||
)
|
||||
brand = LabeledChoiceField(choices=[], label='厂商', required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = Platform
|
||||
|
@ -80,7 +79,7 @@ class PlatformSerializer(JMSWritableNestedModelSerializer):
|
|||
]
|
||||
fields = fields_small + [
|
||||
'protocols_enabled', 'protocols', 'domain_enabled',
|
||||
'su_enabled', 'su_method', 'brand', 'automation', 'comment',
|
||||
'su_enabled', 'su_method', 'automation', 'comment',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'su_enabled': {'label': '启用切换账号'},
|
||||
|
|
|
@ -13,6 +13,7 @@ def migrate_system_role_binding(apps, schema_editor):
|
|||
|
||||
count = 0
|
||||
bulk_size = 1000
|
||||
print('')
|
||||
while True:
|
||||
users = user_model.objects.using(db_alias) \
|
||||
.only('role', 'id') \
|
||||
|
|
|
@ -16,7 +16,7 @@ def migrate_system_to_account(apps, schema_editor):
|
|||
)
|
||||
|
||||
for model, old_field, new_field, m2m in model_system_user_account:
|
||||
print("Start migrate '{}' system user to account".format(model))
|
||||
print("Start migrate '{}' system user to account".format(model.__name__))
|
||||
count = 0
|
||||
bulk_size = 1000
|
||||
|
||||
|
|
Loading…
Reference in New Issue