pull/8873/head
ibuler 2022-08-04 10:44:11 +08:00
parent d43acd8612
commit 6c57db0897
20 changed files with 137 additions and 412 deletions

View File

@ -12,7 +12,6 @@ from assets.models import Asset, Node, Gateway
from assets import serializers
from assets.tasks import (
update_assets_hardware_info_manual, test_assets_connectivity_manual,
test_system_users_connectivity_a_asset, push_system_users_a_asset
)
from assets.filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend

View File

@ -1,20 +0,0 @@
# Generated by Django 3.1.14 on 2022-03-30 10:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0089_auto_20220310_0616'),
]
operations = [
migrations.CreateModel(
name='Host',
fields=[
('asset_ptr', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='assets.asset')),
],
),
]

View File

@ -1,40 +0,0 @@
# Generated by Django 3.1.14 on 2022-04-01 07:58
from django.db import migrations, models
import django.db.models.deletion
def migrate_to_host(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
host_model = apps.get_model("assets", 'Host')
db_alias = schema_editor.connection.alias
created = 0
batch_size = 1000
while True:
start = created
end = created + batch_size
assets = asset_model.objects.using(db_alias).all()[start:end]
if not assets:
break
hosts = [host_model(asset_ptr=asset) for asset in assets]
host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True)
created += len(hosts)
class Migration(migrations.Migration):
dependencies = [
('assets', '0090_add_host'),
]
operations = [
migrations.AlterField(
model_name='host',
name='asset_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset'),
),
migrations.RunPython(migrate_to_host)
]

View File

@ -1,17 +1,23 @@
# Generated by Django 3.1.14 on 2022-04-02 09:09
# Generated by Django 3.1.14 on 2022-03-30 10:35
import uuid
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0091_auto_20220401_1558'),
('assets', '0091_auto_20220629_1826'),
]
operations = [
migrations.CreateModel(
name='Host',
fields=[
('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')),
],
),
migrations.CreateModel(
name='DeviceInfo',
fields=[

View File

@ -41,12 +41,33 @@ def migrate_hardware(apps, *args):
created += len(hardware_infos)
def migrate_to_host(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
host_model = apps.get_model("assets", 'Host')
db_alias = schema_editor.connection.alias
created = 0
batch_size = 1000
while True:
start = created
end = created + batch_size
assets = asset_model.objects.using(db_alias).all()[start:end]
if not assets:
break
hosts = [host_model(asset_ptr=asset) for asset in assets]
host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True)
created += len(hosts)
class Migration(migrations.Migration):
dependencies = [
('assets', '0092_hardware'),
('assets', '0092_add_host'),
]
operations = [
migrations.RunPython(migrate_hardware)
migrations.RunPython(migrate_to_host),
migrations.RunPython(migrate_hardware),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 3.2.12 on 2022-07-13 09:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0093_auto_20220711_1413'),
]
operations = [
migrations.RemoveField(
model_name='systemuser',
name='assets',
),
migrations.AddField(
model_name='systemuser',
name='assets',
field=models.ManyToManyField(blank=True, related_name='system_users', to='assets.Asset', verbose_name='Assets'),
),
]

View File

@ -1,16 +0,0 @@
# Generated by Django 3.2.14 on 2022-08-03 06:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0093_auto_20220711_1413'),
]
operations = [
migrations.DeleteModel(
name='Cluster',
),
]

View File

@ -1,45 +0,0 @@
# Generated by Django 3.2.12 on 2022-07-13 09:46
import time
from django.db import migrations
def migrate_asset_system_user_relations(apps, schema_editor):
system_user_model = apps.get_model('assets', 'SystemUser')
old_model = apps.get_model('assets', 'AuthBook')
new_model = system_user_model.assets.through
count = 0
bulk_size = 1000
print("\nStart migrate asset system user relations")
while True:
start = time.time()
auth_books = old_model.objects.only('asset_id', 'systemuser_id')[count:count+bulk_size]
auth_books = list(auth_books)
count += len(auth_books)
if not auth_books:
break
asset_system_users = []
for auth_book in auth_books:
if not auth_book.asset_id or not auth_book.systemuser_id:
continue
asset_system_user = new_model(
asset_id=auth_book.asset_id,
systemuser_id=auth_book.systemuser_id
)
asset_system_users.append(asset_system_user)
new_model.objects.bulk_create(asset_system_users, ignore_conflicts=True)
print("Create asset system user relations: {}-{} using: {:.2f}s".format(
count - bulk_size, count, time.time()-start
))
class Migration(migrations.Migration):
dependencies = [
('assets', '0094_alter_systemuser_assets'),
]
operations = [
migrations.RunPython(migrate_asset_system_user_relations)
]

View File

@ -1,23 +0,0 @@
# Generated by Django 3.2.12 on 2022-07-14 08:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0095_auto_20220713_1746'),
]
operations = [
migrations.RenameField(
model_name='systemuser',
old_name='auto_push',
new_name='auto_push_account',
),
migrations.AddField(
model_name='systemuser',
name='account_template_enabled',
field=models.BooleanField(default=False, verbose_name='Auto account if not exist'),
),
]

View File

@ -13,7 +13,7 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('assets', '0091_auto_20220629_1826'),
('assets', '0100_auto_20220430_2126'),
]
operations = [

View File

@ -57,7 +57,7 @@ def migrate_accounts(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
('assets', '0092_auto_20220711_1409'),
('assets', '0101_auto_20220711_1409'),
]
operations = [

View File

@ -0,0 +1,51 @@
# Generated by Django 3.2.14 on 2022-08-03 10:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0102_auto_20220711_1413'),
]
operations = [
migrations.CreateModel(
name='Protocol',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32, verbose_name='Name')),
('port', models.IntegerField(verbose_name='Port')),
],
),
migrations.RemoveField(
model_name='asset',
name='port',
),
migrations.RemoveField(
model_name='asset',
name='protocol',
),
migrations.AddField(
model_name='asset',
name='_protocols',
field=models.CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'),
),
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.AddField(
model_name='asset',
name='protocols',
field=models.ManyToManyField(blank=True, to='assets.Protocol', verbose_name='Protocols'),
),
migrations.DeleteModel(
name='Cluster',
),
]

View File

@ -0,0 +1,42 @@
# Generated by Django 3.2.14 on 2022-08-03 10:59
import time
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')
count = 0
bulk_size = 1000
print("\nStart migrate asset protocols")
while True:
start = time.time()
assets = asset_model.objects.all()[count:count+bulk_size]
count += len(assets)
if not assets:
break
protocols = []
for asset in assets:
for protocol in asset.protocols.all():
protocols.append(protocol_model(
asset_id=asset.id,
protocol=protocol.protocol,
port=protocol.port,
))
protocol_model.objects.bulk_create(protocols, ignore_conflicts=True)
print("Create asset protocols: {}-{} using: {:.2f}s".format(
count - bulk_size, count, time.time()-start
))
class Migration(migrations.Migration):
dependencies = [
('assets', '0103_auto_20220803_1448'),
]
operations = [
migrations.RunPython(migrate_asset_protocols)
]

View File

@ -41,7 +41,7 @@ class SystemUser(ProtocolMixin, BaseUser):
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type'))
priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)])
protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol'))
protocol = models.CharField(max_length=16, default='ssh', verbose_name=_('Protocol'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))

View File

@ -5,19 +5,17 @@
import uuid
import logging
from functools import reduce
from collections import OrderedDict
from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.db.fields import JsonDictTextField
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager
from assets.const import Category, AllTypes
from ..platform import Platform
from ..base import AbsConnectivity
__all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_cluster']
__all__ = ['Asset', 'AssetQuerySet', 'default_node']
logger = logging.getLogger(__name__)
@ -45,49 +43,6 @@ class AssetQuerySet(models.QuerySet):
return self.filter(protocols__contains=name)
class ProtocolsMixin:
protocols = ''
class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH'
rdp = 'rdp', 'RDP'
telnet = 'telnet', 'Telnet'
vnc = 'vnc', 'VNC'
@property
def protocols_as_list(self):
if not self.protocols:
return []
return self.protocols.split(' ')
@property
def protocols_as_dict(self):
d = OrderedDict()
protocols = self.protocols_as_list
for i in protocols:
if '/' not in i:
continue
name, port = i.split('/')[:2]
if not all([name, port]):
continue
d[name] = int(port)
return d
@property
def protocols_as_json(self):
return [
{"name": name, "port": port}
for name, port in self.protocols_as_dict.items()
]
def has_protocol(self, name):
return name in self.protocols_as_dict
@property
def ssh_port(self):
return self.protocols_as_dict.get("ssh", 22)
class NodesRelationMixin:
NODES_CACHE_KEY = 'ASSET_NODES_{}'
ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES'
@ -112,17 +67,15 @@ class NodesRelationMixin:
return nodes
class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin):
Category = Category
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_("Category"))
type = models.CharField(max_length=128, choices=AllTypes.choices, verbose_name=_("Type"))
protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh,
choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
_protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocols"), blank=True)
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT,
verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets',
@ -134,7 +87,6 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
# Auth
admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True,
verbose_name=_("Admin user"), related_name='admin_assets')
# Some information
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
number = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Asset number'))
@ -178,55 +130,6 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
"""
return self.admin_user.username
def is_windows(self):
return self.platform.is_windows()
def is_unixlike(self):
return self.platform.is_unixlike()
def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform_base not in ("Other",)
def get_auth_info(self, with_become=False):
if not self.admin_user:
return {}
if self.is_unixlike() and self.admin_user.su_enabled and self.admin_user.su_from:
auth_user = self.admin_user.su_from
become_user = self.admin_user
else:
auth_user = self.admin_user
become_user = None
auth_user.load_asset_special_auth(self)
info = {
'username': auth_user.username,
'password': auth_user.password,
'private_key': auth_user.private_key_file
}
if not with_become or self.is_windows():
return info
if become_user:
become_user.load_asset_special_auth(self)
become_method = 'su'
become_username = become_user.username
become_pass = become_user.password
else:
become_method = 'sudo'
become_username = 'root'
become_pass = auth_user.password
become_info = {
'become': {
'method': become_method,
'username': become_username,
'pass': become_pass
}
}
info.update(become_info)
return info
def nodes_display(self):
names = []
for n in self.nodes.all():
@ -270,7 +173,7 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'protocols': self.protocols_as_list,
'protocols': self.protocols,
'platform': self.platform_base,
}
}

View File

@ -1,10 +1,8 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from common.db.models import JMSBaseModel
class Protocol(JMSBaseModel):
class Protocol(models.Model):
name = models.CharField(max_length=32, verbose_name=_("Name"))
port = models.IntegerField(verbose_name=_("Port"))

View File

@ -3,7 +3,6 @@
from .asset import *
from .label import *
from .system_user import *
from .node import *
from .domain import *
from .gathered_user import *

View File

@ -1,128 +0,0 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
__all__ = [
'SystemUserSerializer', 'MiniSystemUserSerializer',
'SystemUserSimpleSerializer',
]
class SystemUserSerializer(BulkOrgResourceModelSerializer):
"""
系统用户
"""
class Meta:
model = SystemUser
fields_mini = ['id', 'name', 'username', 'protocol']
fields_small = fields_mini + [
'login_mode', 'su_enabled', 'su_from',
'date_created', 'date_updated', 'comment',
'created_by',
]
fields = fields_small
extra_kwargs = {
'cmd_filters': {"required": False, 'label': _('Command filter')},
'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True},
'ad_domain': {'required': False, 'allow_blank': True, 'label': _('Ad domain')},
'su_from': {'help_text': _('Only ssh and automatic login system users are supported')}
}
def validate_username_same_with_user(self, username_same_with_user):
if not username_same_with_user:
return username_same_with_user
protocol = self.get_initial_value("protocol", "ssh")
queryset = SystemUser.objects.filter(
protocol=protocol,
username_same_with_user=True
)
if self.instance:
queryset = queryset.exclude(id=self.instance.id)
exists = queryset.exists()
if not exists:
return username_same_with_user
error = _("Username same with user with protocol {} only allow 1").format(protocol)
raise serializers.ValidationError(error)
def validate_username(self, username):
protocol = self.get_initial_value("protocol")
if username:
if protocol == Protocol.telnet:
regx = alphanumeric_cn_re
elif protocol == Protocol.rdp:
regx = alphanumeric_win_re
else:
regx = alphanumeric_re
if not regx.match(username):
raise serializers.ValidationError(_('Special char not allowed'))
return username
username_same_with_user = self.get_initial_value("username_same_with_user")
if username_same_with_user:
return ''
login_mode = self.get_initial_value("login_mode")
if login_mode == SystemUser.LOGIN_AUTO and protocol != Protocol.vnc \
and protocol != Protocol.redis:
msg = _('* Automatic login mode must fill in the username.')
raise serializers.ValidationError(msg)
return username
@staticmethod
def validate_sftp_root(value):
if value in ['home', 'tmp']:
return value
if not value.startswith('/'):
error = _("Path should starts with /")
raise serializers.ValidationError(error)
return value
def validate_su_from(self, su_from: SystemUser):
# self: su enabled
su_enabled = self.get_initial_value('su_enabled', default=False)
if not su_enabled:
return
if not su_from:
error = _('This field is required.')
raise serializers.ValidationError(error)
# self: protocol ssh
protocol = self.get_initial_value('protocol', default=Protocol.ssh.value)
if protocol not in [Protocol.ssh.value]:
error = _('Only ssh protocol system users are allowed')
raise serializers.ValidationError(error)
# su_from: protocol same
if su_from.protocol != protocol:
error = _('The protocol must be consistent with the current user: {}').format(protocol)
raise serializers.ValidationError(error)
# su_from: login model auto
if su_from.login_mode != su_from.LOGIN_AUTO:
error = _('Only system users with automatic login are allowed')
raise serializers.ValidationError(error)
return su_from
class MiniSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
fields = SystemUserSerializer.Meta.fields_mini
class SystemUserSimpleSerializer(serializers.ModelSerializer):
"""
系统用户最基本信息的数据结构
"""
class Meta:
model = SystemUser
fields = ('id', 'name', 'username')
class SystemUserTempAuthSerializer(SystemUserSerializer):
instance_id = serializers.CharField()
class Meta(SystemUserSerializer.Meta):
fields = ['instance_id', 'username', 'password']

View File

@ -4,11 +4,11 @@ from rest_framework.exceptions import PermissionDenied
from rest_framework.decorators import action
from common.drf.api import JMSModelViewSet
from common.mixins.api import PaginatedResponseMixin
from ..filters import RoleFilter
from ..serializers import RoleSerializer, RoleUserSerializer
from ..models import Role, SystemRole, OrgRole
from .permission import PermissionViewSet
from common.mixins.api import PaginatedResponseMixin
__all__ = [
'RoleViewSet', 'SystemRoleViewSet', 'OrgRoleViewSet',
@ -16,7 +16,7 @@ __all__ = [
]
class RoleViewSet(PaginatedResponseMixin, JMSModelViewSet):
class RoleViewSet(JMSModelViewSet):
queryset = Role.objects.all()
serializer_classes = {
'default': RoleSerializer,

View File

@ -6,7 +6,7 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('terminal', '0047_auto_20220302_1951'),
('terminal', '0051_sessionsharing_users'),
]
operations = [