Compare commits

...

12 Commits

Author SHA1 Message Date
fit2bot
078ba2f4d9 feat: Update v3.2.2 2023-05-09 14:36:56 +08:00
feng
2e5dfd8bab fix: /prometheus/metrics/ api 500 2023-05-08 14:51:18 +08:00
Bai
53da79ae46 perf: 优化迁移后的 Redis 数据库平台从 Redis6+ 修改为 Redis6 2023-05-04 17:31:57 +08:00
Bai
e258772242 fix: 修复迁移应用时(组织下只有根节点,同步后的应用资产没有设置节点的问题) 2023-05-04 16:36:52 +08:00
Bai
0a5bf9cf03 fix: 修复迁移redis资产账号丢失的问题(系统用户用户名为空字符串) 2023-05-04 15:44:08 +08:00
Eric
ac7c865b67 fix: 修复旧 ssh 私钥,解析失败的问题 2023-04-27 17:49:29 +08:00
feng
ff6a8fa4d7 perf: 开源 acl去除 review 2023-04-21 18:41:26 +08:00
ibuler
802d6136d6 perf: 账号模版 protocols 过滤 2023-04-21 17:11:46 +08:00
ibuler
7e8bb9752c perf: 优化自定义类型的冲突 2023-04-21 15:20:50 +08:00
feng
3fb0197e99 perf: 创建资产 nodes 可为空 默认 default 2023-04-21 14:57:50 +08:00
ibuler
7127b2da93 perf: 去掉 debug msg 2023-04-21 11:32:21 +08:00
ibuler
48b3699591 perf: 优化支持 自定义 applet
perf: 优化平台
2023-04-21 11:31:49 +08:00
21 changed files with 88 additions and 65 deletions

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
2e5dfd8bab051dfcc4007d0c02707607f80acf8a

View File

@@ -1,13 +1,13 @@
from django_filters import rest_framework as drf_filters
from assets.const import Protocol
from accounts import serializers
from accounts.models import AccountTemplate
from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission
from assets.const import Protocol
from common.drf.filters import BaseFilterSet
from common.permissions import UserConfirmation, ConfirmType
from common.views.mixins import RecordViewLogMixin
from common.drf.filters import BaseFilterSet
from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission
class AccountTemplateFilterSet(BaseFilterSet):
@@ -27,6 +27,8 @@ class AccountTemplateFilterSet(BaseFilterSet):
continue
_st = protocol_secret_type_map[p].get('secret_types', [])
secret_types.update(_st)
if not secret_types:
secret_types = ['password']
queryset = queryset.filter(secret_type__in=secret_types)
return queryset

View File

@@ -28,7 +28,6 @@ class ChangeSecretMixin(models.Model):
default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy')
)
accounts: list[str] # account usernames
get_all_assets: callable # get all assets
class Meta:

View File

@@ -3,6 +3,7 @@ from rest_framework import serializers
from acls.models.base import ActionChoices
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
from jumpserver.utils import has_valid_xpack_license
from orgs.models import Organization
from users.models import User
@@ -51,7 +52,26 @@ class ACLAccountsSerializer(serializers.Serializer):
)
class BaseUserAssetAccountACLSerializerMixin(serializers.Serializer):
class ActionAclSerializer(serializers.Serializer):
action = LabeledChoiceField(
choices=ActionChoices.choices, default=ActionChoices.reject, label=_("Action")
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_action_choices()
def set_action_choices(self):
action = self.fields.get("action")
if not action:
return
choices = action.choices
if not has_valid_xpack_license():
choices.pop(ActionChoices.review, None)
action._choices = choices
class BaseUserAssetAccountACLSerializerMixin(ActionAclSerializer, serializers.Serializer):
users = ACLUsersSerializer(label=_('User'))
assets = ACLAssestsSerializer(label=_('Asset'))
accounts = ACLAccountsSerializer(label=_('Account'))
@@ -77,9 +97,6 @@ class BaseUserAssetAccountACLSerializerMixin(serializers.Serializer):
reviewers_amount = serializers.IntegerField(
read_only=True, source="reviewers.count", label=_('Reviewers amount')
)
action = LabeledChoiceField(
choices=ActionChoices.choices, default=ActionChoices.reject, label=_("Action")
)
class Meta:
fields_mini = ["id", "name"]

View File

@@ -2,12 +2,11 @@ from django.utils.translation import ugettext as _
from rest_framework import serializers
from common.serializers import BulkModelSerializer, MethodSerializer
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
from jumpserver.utils import has_valid_xpack_license
from common.serializers.fields import ObjectRelatedField
from users.models import User
from .base import ActionAclSerializer
from .rules import RuleSerializer
from ..models import LoginACL
from ..models.base import ActionChoices
__all__ = [
"LoginACLSerializer",
@@ -18,12 +17,11 @@ common_help_text = _(
)
class LoginACLSerializer(BulkModelSerializer):
class LoginACLSerializer(ActionAclSerializer, BulkModelSerializer):
user = ObjectRelatedField(queryset=User.objects, label=_("User"))
reviewers = ObjectRelatedField(
queryset=User.objects, label=_("Reviewers"), many=True, required=False
)
action = LabeledChoiceField(choices=ActionChoices.choices, label=_('Action'))
reviewers_amount = serializers.IntegerField(
read_only=True, source="reviewers.count", label=_("Reviewers amount")
)
@@ -45,18 +43,5 @@ class LoginACLSerializer(BulkModelSerializer):
"is_active": {"default": True},
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_action_choices()
def set_action_choices(self):
action = self.fields.get("action")
if not action:
return
choices = action.choices
if not has_valid_xpack_license():
choices.pop(LoginACL.ActionChoices.review, None)
action._choices = choices
def get_rules_serializer(self):
return RuleSerializer()

View File

@@ -3,6 +3,7 @@ from rest_framework.decorators import action
from rest_framework.response import Response
from common.api import JMSGenericViewSet
from common.permissions import IsValidUser
from assets.serializers import CategorySerializer, TypeSerializer
from assets.const import AllTypes
@@ -14,7 +15,7 @@ class CategoryViewSet(ListModelMixin, JMSGenericViewSet):
'default': CategorySerializer,
'types': TypeSerializer
}
permission_classes = ()
permission_classes = (IsValidUser,)
def get_queryset(self):
return AllTypes.categories()

View File

@@ -57,11 +57,23 @@ class SerializeToTreeNodeMixin:
]
return data
@lazyproperty
def support_types(self):
from assets.const import AllTypes
return AllTypes.get_types_values(exclude_custom=True)
def get_icon(self, asset):
if asset.type in self.support_types:
return asset.type
else:
return 'file'
@timeit
def serialize_assets(self, assets, node_key=None):
sftp_enabled_platform = PlatformProtocol.objects \
.filter(name='ssh', setting__sftp_enabled=True) \
.values_list('platform', flat=True).distinct()
.values_list('platform', flat=True) \
.distinct()
if node_key is None:
get_pid = lambda asset: getattr(asset, 'parent_key', '')
else:
@@ -75,7 +87,7 @@ class SerializeToTreeNodeMixin:
'pId': get_pid(asset),
'isParent': False,
'open': False,
'iconSkin': asset.type,
'iconSkin': self.get_icon(asset),
'chkDisabled': not asset.is_active,
'meta': {
'type': 'asset',

View File

@@ -40,7 +40,6 @@ def get_platform_automation_methods(path):
continue
with open(path, 'r') as f:
print("path: ", path)
manifest = yaml_load_with_i18n(f)
check_platform_method(manifest, path)
manifest['dir'] = os.path.dirname(path)

View File

@@ -8,7 +8,7 @@ class CustomTypes(BaseType):
platforms = list(cls.get_custom_platforms())
except Exception:
return []
types = [p.type for p in platforms]
types = set([p.type for p in platforms])
return [(t, t) for t in types]
@classmethod
@@ -48,11 +48,7 @@ class CustomTypes(BaseType):
@classmethod
def internal_platforms(cls):
return {
# cls.PUBLIC: [],
# cls.PRIVATE: [{'name': 'Vmware-vSphere'}],
# cls.K8S: [{'name': 'Kubernetes'}],
}
return {}
@classmethod
def get_custom_platforms(cls):

View File

@@ -151,15 +151,18 @@ class AllTypes(ChoicesMixin):
)
@classmethod
def get_types(cls):
def get_types(cls, exclude_custom=False):
choices = []
for i in dict(cls.category_types()).values():
choices.extend(i.get_types())
for name, tp in dict(cls.category_types()).items():
if name == Category.CUSTOM and exclude_custom:
continue
choices.extend(tp.get_types())
return choices
@classmethod
def get_types_values(cls):
choices = cls.get_types()
def get_types_values(cls, exclude_custom=False):
choices = cls.get_types(exclude_custom=exclude_custom)
return [c.value for c in choices]
@staticmethod

View File

@@ -20,12 +20,13 @@ def get_prop_name_id(apps, app, category):
def migrate_database_to_asset(apps, *args):
node_model = apps.get_model('assets', 'Node')
app_model = apps.get_model('applications', 'Application')
db_model = apps.get_model('assets', 'Database')
platform_model = apps.get_model('assets', 'Platform')
applications = app_model.objects.filter(category='db')
platforms = platform_model.objects.all().filter(internal=True)
platforms = platform_model.objects.all().filter(internal=True).exclude(name='Redis6+')
platforms_map = {p.type: p for p in platforms}
print()
@@ -84,11 +85,18 @@ def create_app_nodes(apps, org_id):
node_keys = node_model.objects.filter(org_id=org_id) \
.filter(key__regex=child_pattern) \
.values_list('key', flat=True)
if not node_keys:
return
node_key_split = [key.split(':') for key in node_keys]
next_value = max([int(k[1]) for k in node_key_split]) + 1
parent_key = node_key_split[0][0]
if node_keys:
node_key_split = [key.split(':') for key in node_keys]
next_value = max([int(k[1]) for k in node_key_split]) + 1
parent_key = node_key_split[0][0]
else:
root_node = node_model.objects.filter(org_id=org_id)\
.filter(parent_key='', key__regex=r'^[0-9]+$').exclude(key__startswith='-').first()
if not root_node:
return
parent_key = root_node.key
next_value = 0
next_key = '{}:{}'.format(parent_key, next_value)
name = 'Apps'
parent = node_model.objects.get(key=parent_key)

View File

@@ -161,11 +161,12 @@ def migrate_db_accounts(apps, schema_editor):
name = f'{username}(token)'
else:
secret_type = attr
name = username
name = username or f'{username}(password)'
auth_infos.append((name, secret_type, secret))
if not auth_infos:
auth_infos.append((username, 'password', ''))
name = username or f'{username}(password)'
auth_infos.append((name, 'password', ''))
for name, secret_type, secret in auth_infos:
values['name'] = name

View File

@@ -145,6 +145,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
'name': {'label': _("Name")},
'address': {'label': _('Address')},
'nodes_display': {'label': _('Node path')},
'nodes': {'allow_empty': True},
}
def __init__(self, *args, **kwargs):

View File

@@ -2,7 +2,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from assets.const.web import FillType
from common.serializers import WritableNestedModelSerializer
from common.serializers import WritableNestedModelSerializer, type_field_map
from common.serializers.fields import LabeledChoiceField
from common.utils import lazyproperty
from ..const import Category, AllTypes
@@ -88,14 +88,7 @@ class PlatformProtocolSerializer(serializers.ModelSerializer):
class PlatformCustomField(serializers.Serializer):
TYPE_CHOICES = [
("str", "str"),
("text", "text"),
("int", "int"),
("bool", "bool"),
("choice", "choice"),
("list", "list"),
]
TYPE_CHOICES = [(t, t) for t, c in type_field_map.items()]
name = serializers.CharField(label=_("Name"), max_length=128)
label = serializers.CharField(label=_("Label"), max_length=128)
type = serializers.ChoiceField(choices=TYPE_CHOICES, label=_("Type"), default='str')

View File

@@ -7,6 +7,7 @@ example_info = [
type_field_map = {
"str": serializers.CharField,
"password": serializers.CharField,
"int": serializers.IntegerField,
"bool": serializers.BooleanField,
"text": serializers.CharField,
@@ -27,6 +28,8 @@ def set_default_if_need(data, i):
def set_default_by_type(tp, data, field_info):
if tp == 'str':
data['max_length'] = 4096
elif tp == 'password':
data['write_only'] = True
elif tp == 'choice':
choices = field_info.pop('choices', [])
if isinstance(choices, str):

View File

@@ -175,6 +175,8 @@ def _parse_ssh_private_key(text, password=None):
dsa.DSAPrivateKey,
ed25519.Ed25519PrivateKey,
"""
if not bool(password):
password = None
if isinstance(text, str):
try:
text = text.encode("utf-8")

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2dd0610d610c2660f35d50dc2871ac08cc09080d2503e1080a57d97c47fea471
size 114418
oid sha256:591b458d6f8ea8d125bd584ca57768cd5aa5a7103b42e345eaadac744a73d475
size 114412

View File

@@ -1060,7 +1060,7 @@ msgstr "Web"
#: assets/const/category.py:15
msgid "Custom type"
msgstr "自定义类型"
msgstr "自定义"
#: assets/const/cloud.py:7
msgid "Public cloud"

View File

@@ -123,7 +123,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
for i in range(0, len(account_ids), slice_count):
push_accounts_to_assets_task.delay(account_ids[i:i + slice_count])
def validate_accounts(self, usernames: list[str]):
def validate_accounts(self, usernames):
template_ids = []
account_usernames = []
for username in usernames:

View File

@@ -98,7 +98,7 @@ class Applet(JMSBaseModel):
return
try:
with open(os.path.join(d, 'platform.yml')) as f:
data = yaml.safe_load(f)
data = yaml_load_with_i18n(f)
except Exception as e:
raise ValidationError({'error': _('Load platform.yml failed: {}').format(e)})

View File

@@ -140,7 +140,7 @@ class ComponentsPrometheusMetricsUtil(TypedComponentsStatusMetricsUtil):
for component in self.components:
if not component.is_alive:
continue
component_stat = component.latest_stat
component_stat = component.last_stat
if not component_stat:
continue
metric_text = state_metric_text % (