mirror of https://github.com/jumpserver/jumpserver
perf: update pam
parent
7afd25ca9c
commit
0fd5a2c4d9
|
@ -2,6 +2,10 @@
|
|||
#
|
||||
from django.db.models import Q, Count
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import MethodNotAllowed
|
||||
from operator import itemgetter
|
||||
|
||||
from rest_framework.response import Response
|
||||
|
||||
from accounts import serializers
|
||||
from accounts.const import AutomationTypes
|
||||
|
@ -15,6 +19,8 @@ __all__ = [
|
|||
'AccountRiskViewSet', 'CheckAccountEngineViewSet',
|
||||
]
|
||||
|
||||
from ...risk_handlers import RiskHandler
|
||||
|
||||
|
||||
class CheckAccountAutomationViewSet(OrgBulkModelViewSet):
|
||||
model = CheckAccountAutomation
|
||||
|
@ -46,6 +52,7 @@ class AccountRiskViewSet(OrgBulkModelViewSet):
|
|||
serializer_classes = {
|
||||
'default': serializers.AccountRiskSerializer,
|
||||
'assets': serializers.AssetRiskSerializer,
|
||||
'handle': serializers.HandleRiskSerializer
|
||||
}
|
||||
ordering_fields = (
|
||||
'asset', 'risk', 'status', 'username', 'date_created'
|
||||
|
@ -53,9 +60,15 @@ class AccountRiskViewSet(OrgBulkModelViewSet):
|
|||
ordering = ('-asset', 'date_created')
|
||||
rbac_perms = {
|
||||
'sync_accounts': 'assets.add_accountrisk',
|
||||
'assets': 'accounts.view_accountrisk'
|
||||
'assets': 'accounts.view_accountrisk',
|
||||
'handle': 'accounts.change_accountrisk'
|
||||
}
|
||||
http_method_names = ['get', 'head', 'options']
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
raise MethodNotAllowed('PUT')
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
raise MethodNotAllowed('POST')
|
||||
|
||||
@action(methods=['get'], detail=False, url_path='assets')
|
||||
def assets(self, request, *args, **kwargs):
|
||||
|
@ -72,6 +85,32 @@ class AccountRiskViewSet(OrgBulkModelViewSet):
|
|||
)
|
||||
return self.get_paginated_response_from_queryset(queryset)
|
||||
|
||||
@action(methods=['post'], detail=False, url_path='handle')
|
||||
def handle(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
asset, username, act, risk = itemgetter('asset', 'username', 'action', 'risk')(serializer.validated_data)
|
||||
handler = RiskHandler(asset=asset, username=username)
|
||||
data = handler.handle(act, risk)
|
||||
if not data:
|
||||
data = {'message': 'Success'}
|
||||
return Response(data)
|
||||
|
||||
# 处理风险
|
||||
|
||||
def handle_add_account(self):
|
||||
pass
|
||||
|
||||
def handle_disable_remote(self):
|
||||
pass
|
||||
|
||||
def handle_delete_remote(self):
|
||||
pass
|
||||
|
||||
def handle_delete_both(self):
|
||||
pass
|
||||
|
||||
|
||||
class CheckAccountEngineViewSet(JMSModelViewSet):
|
||||
search_fields = ('name',)
|
||||
|
|
|
@ -13,18 +13,22 @@ from accounts.filters import GatheredAccountFilterSet
|
|||
from accounts.models import GatherAccountsAutomation, AutomationExecution
|
||||
from accounts.models import GatheredAccount
|
||||
from assets.models import Asset
|
||||
from accounts.tasks.common import quickstart_automation_by_snapshot
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from .base import AutomationExecutionViewSet
|
||||
|
||||
__all__ = [
|
||||
'GatherAccountsAutomationViewSet', 'GatherAccountsExecutionViewSet',
|
||||
'GatheredAccountViewSet'
|
||||
"GatherAccountsAutomationViewSet",
|
||||
"GatherAccountsExecutionViewSet",
|
||||
"GatheredAccountViewSet",
|
||||
]
|
||||
|
||||
from ...risk_handlers import RiskHandler
|
||||
|
||||
|
||||
class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
|
||||
model = GatherAccountsAutomation
|
||||
filterset_fields = ('name',)
|
||||
filterset_fields = ("name",)
|
||||
search_fields = filterset_fields
|
||||
serializer_class = serializers.GatherAccountAutomationSerializer
|
||||
|
||||
|
@ -47,52 +51,56 @@ class GatherAccountsExecutionViewSet(AutomationExecutionViewSet):
|
|||
|
||||
class GatheredAccountViewSet(OrgBulkModelViewSet):
|
||||
model = GatheredAccount
|
||||
search_fields = ('username',)
|
||||
search_fields = ("username",)
|
||||
filterset_class = GatheredAccountFilterSet
|
||||
ordering = ("status",)
|
||||
serializer_classes = {
|
||||
'default': serializers.GatheredAccountSerializer,
|
||||
'status': serializers.GatheredAccountActionSerializer,
|
||||
"default": serializers.GatheredAccountSerializer,
|
||||
"status": serializers.GatheredAccountActionSerializer,
|
||||
}
|
||||
rbac_perms = {
|
||||
'sync_accounts': 'assets.add_gatheredaccount',
|
||||
'discover': 'assets.add_gatheredaccount',
|
||||
'status': 'assets.change_gatheredaccount',
|
||||
"sync_accounts": "assets.add_gatheredaccount",
|
||||
"discover": "assets.add_gatheredaccount",
|
||||
"status": "assets.change_gatheredaccount",
|
||||
}
|
||||
|
||||
@action(methods=['put'], detail=True, url_path='status')
|
||||
@action(methods=["put"], detail=True, url_path="status")
|
||||
def status(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
instance.status = request.data.get('status')
|
||||
instance.save(update_fields=['status'])
|
||||
instance.status = request.data.get("status")
|
||||
instance.save(update_fields=["status"])
|
||||
|
||||
if instance.status == 'confirmed':
|
||||
if instance.status == "confirmed":
|
||||
GatheredAccount.sync_accounts([instance])
|
||||
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
@action(methods=['get'], detail=False, url_path='discover')
|
||||
def discover(self, request, *args, **kwargs):
|
||||
asset_id = request.query_params.get('asset_id')
|
||||
if not asset_id:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={'asset_id': 'This field is required.'})
|
||||
@action(methods=["post"], detail=False, url_path="delete-remote")
|
||||
def delete_remote(self, request, *args, **kwargs):
|
||||
asset_id = request.data.get("asset_id")
|
||||
username = request.data.get("username")
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
handler = RiskHandler(asset, username)
|
||||
handler.handle_delete_remote()
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
@action(methods=["get"], detail=False, url_path="discover")
|
||||
def discover(self, request, *args, **kwargs):
|
||||
asset_id = request.query_params.get("asset_id")
|
||||
if not asset_id:
|
||||
return Response(status=400, data={"asset_id": "This field is required."})
|
||||
|
||||
get_object_or_404(Asset, pk=asset_id)
|
||||
execution = AutomationExecution()
|
||||
execution.snapshot = {
|
||||
'assets': [asset_id],
|
||||
'nodes': [],
|
||||
'type': 'gather_accounts',
|
||||
'is_sync_account': False,
|
||||
'check_risk': True,
|
||||
'name': 'Adhoc gather accounts: {}'.format(asset_id),
|
||||
"assets": [asset_id],
|
||||
"nodes": [],
|
||||
"type": "gather_accounts",
|
||||
"is_sync_account": False,
|
||||
"check_risk": True,
|
||||
"name": "Adhoc gather accounts: {}".format(asset_id),
|
||||
}
|
||||
execution.save()
|
||||
execution.start()
|
||||
report = execution.manager.gen_report()
|
||||
return HttpResponse(report)
|
||||
|
||||
@action(methods=['post'], detail=False, url_path='sync-accounts')
|
||||
def sync_accounts(self, request, *args, **kwargs):
|
||||
gathered_account_ids = request.data.get('gathered_account_ids')
|
||||
gathered_accounts = self.model.objects.filter(id__in=gathered_account_ids).filter(status='')
|
||||
self.model.sync_accounts(gathered_accounts)
|
||||
return Response(status=status.HTTP_201_CREATED)
|
||||
|
|
|
@ -56,7 +56,7 @@ def get_items_diff(ori_account, d):
|
|||
class AnalyseAccountRisk:
|
||||
long_time = timezone.timedelta(days=90)
|
||||
datetime_check_items = [
|
||||
{"field": "date_last_login", "risk": "zombie", "delta": long_time},
|
||||
{"field": "date_last_login", "risk": "long_time_no_login", "delta": long_time},
|
||||
{
|
||||
"field": "date_password_change",
|
||||
"risk": "long_time_password",
|
||||
|
@ -154,7 +154,7 @@ class AnalyseAccountRisk:
|
|||
else:
|
||||
self._create_risk(
|
||||
dict(
|
||||
**basic, risk="ghost", details=[{"datetime": self.now.isoformat()}]
|
||||
**basic, risk="new_found", details=[{"datetime": self.now.isoformat()}]
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -227,8 +227,9 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||
for asset_id, username in accounts:
|
||||
self.ori_asset_usernames[str(asset_id)].add(username)
|
||||
|
||||
ga_accounts = GatheredAccount.objects.filter(asset__in=assets).prefetch_related(
|
||||
"asset"
|
||||
ga_accounts = (
|
||||
GatheredAccount.objects.filter(asset__in=assets)
|
||||
.prefetch_related("asset")
|
||||
)
|
||||
for account in ga_accounts:
|
||||
self.ori_gathered_usernames[str(account.asset_id)].add(account.username)
|
||||
|
@ -315,8 +316,10 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||
.filter(present=True)
|
||||
.update(present=False)
|
||||
)
|
||||
queryset.filter(username__in=ori_users).filter(present=False).update(
|
||||
present=True
|
||||
(
|
||||
queryset.filter(username__in=ori_users)
|
||||
.filter(present=False)
|
||||
.update(present=True)
|
||||
)
|
||||
|
||||
@bulk_create_decorator(GatheredAccount)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
|
||||
from django.db.models import QuerySet
|
||||
|
@ -12,10 +13,16 @@ logger = get_logger(__name__)
|
|||
|
||||
|
||||
class RemoveAccountManager(AccountBasePlaybookManager):
|
||||
super_accounts = ['root', 'administrator']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.host_account_mapper = {}
|
||||
self.host_account_mapper = dict()
|
||||
self.host_accounts = defaultdict(list)
|
||||
snapshot_account = self.execution.snapshot.get('accounts', [])
|
||||
self.snapshot_asset_account_map = defaultdict(list)
|
||||
for account in snapshot_account:
|
||||
self.snapshot_asset_account_map[str(account['asset'])].append(account)
|
||||
|
||||
def prepare_runtime_dir(self):
|
||||
path = super().prepare_runtime_dir()
|
||||
|
@ -30,28 +37,22 @@ class RemoveAccountManager(AccountBasePlaybookManager):
|
|||
def method_type(cls):
|
||||
return AutomationTypes.remove_account
|
||||
|
||||
def get_gather_accounts(self, privilege_account, gather_accounts: QuerySet):
|
||||
gather_account_ids = self.execution.snapshot['gather_accounts']
|
||||
gather_accounts = gather_accounts.filter(id__in=gather_account_ids)
|
||||
gather_accounts = gather_accounts.exclude(
|
||||
username__in=[privilege_account.username, 'root', 'Administrator']
|
||||
)
|
||||
return gather_accounts
|
||||
|
||||
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
||||
if host.get('error'):
|
||||
return host
|
||||
|
||||
gather_accounts = asset.gatheredaccount_set.all()
|
||||
gather_accounts = self.get_gather_accounts(account, gather_accounts)
|
||||
|
||||
inventory_hosts = []
|
||||
accounts_to_remove = self.snapshot_asset_account_map.get(str(asset.id), [])
|
||||
|
||||
for gather_account in gather_accounts:
|
||||
for account in accounts_to_remove:
|
||||
username = account.get('username')
|
||||
if not username or username.lower() in self.super_accounts:
|
||||
print("Super account can not be remove: ", username)
|
||||
continue
|
||||
h = deepcopy(host)
|
||||
h['name'] += '(' + gather_account.username + ')'
|
||||
self.host_account_mapper[h['name']] = (asset, gather_account)
|
||||
h['account'] = {'username': gather_account.username}
|
||||
h['name'] += '(' + username + ')'
|
||||
self.host_account_mapper[h['name']] = account
|
||||
h['account'] = {'username': username}
|
||||
inventory_hosts.append(h)
|
||||
return inventory_hosts
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ class Migration(migrations.Migration):
|
|||
name="status",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[("confirmed", "Confirmed"), ("ignored", "Ignored")],
|
||||
choices=[("confirmed", "Confirmed"), ("ignored", "Ignored"), ("pending", "Pending")],
|
||||
default="",
|
||||
max_length=32,
|
||||
verbose_name="Status",
|
||||
|
|
|
@ -19,7 +19,7 @@ class Migration(migrations.Migration):
|
|||
name="status",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[("confirmed", "Confirmed"), ("ignored", "Ignored")],
|
||||
choices=[("confirmed", "Confirmed"), ("ignored", "Ignored"), ("pending", "Pending")],
|
||||
default="",
|
||||
max_length=32,
|
||||
verbose_name="Status",
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 4.1.13 on 2024-11-26 08:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("accounts", "0014_gatheraccountsautomation_check_risk"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="accountrisk",
|
||||
name="risk",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("long_time_no_login", "Long time no login"),
|
||||
("new_found", "New found"),
|
||||
("groups_changed", "Groups change"),
|
||||
("sudoers_changed", "Sudo changed"),
|
||||
("authorized_keys_changed", "Authorized keys changed"),
|
||||
("account_deleted", "Account delete"),
|
||||
("password_expired", "Password expired"),
|
||||
("long_time_password", "Long time no change"),
|
||||
("weak_password", "Weak password"),
|
||||
("password_error", "Password error"),
|
||||
("no_admin_account", "No admin account"),
|
||||
("others", "Others"),
|
||||
],
|
||||
max_length=128,
|
||||
verbose_name="Risk",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,43 @@
|
|||
# Generated by Django 4.1.13 on 2024-11-27 11:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("accounts", "0015_alter_accountrisk_risk"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="accountrisk",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("", "Pending"),
|
||||
("confirmed", "Confirmed"),
|
||||
("ignored", "Ignored"),
|
||||
],
|
||||
default="",
|
||||
max_length=32,
|
||||
verbose_name="Status",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="gatheredaccount",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("", "Pending"),
|
||||
("confirmed", "Confirmed"),
|
||||
("ignored", "Ignored"),
|
||||
],
|
||||
default="",
|
||||
max_length=32,
|
||||
verbose_name="Status",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -39,8 +39,8 @@ class CheckAccountAutomation(AccountBaseAutomation):
|
|||
|
||||
class RiskChoice(TextChoices):
|
||||
# 依赖自动发现的
|
||||
zombie = 'zombie', _('Long time no login') # 好久没登录的账号, 禁用、删除
|
||||
ghost = 'ghost', _('Not managed') # 未被纳管的账号, 纳管, 删除, 禁用
|
||||
long_time_no_login = 'long_time_no_login', _('Long time no login') # 好久没登录的账号, 禁用、删除
|
||||
new_found = 'new_found', _('New found') # 未被纳管的账号, 纳管, 删除, 禁用
|
||||
group_changed = 'groups_changed', _('Groups change') # 组变更, 确认
|
||||
sudo_changed = 'sudoers_changed', _('Sudo changed') # sudo 变更, 确认
|
||||
authorized_keys_changed = 'authorized_keys_changed', _('Authorized keys changed') # authorized_keys 变更, 确认
|
||||
|
@ -58,7 +58,7 @@ class AccountRisk(JMSOrgBaseModel):
|
|||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, related_name='risks', verbose_name=_('Asset'))
|
||||
username = models.CharField(max_length=32, verbose_name=_('Username'))
|
||||
risk = models.CharField(max_length=128, verbose_name=_('Risk'), choices=RiskChoice.choices)
|
||||
status = models.CharField(max_length=32, choices=ConfirmOrIgnore.choices, default='', blank=True, verbose_name=_('Status'))
|
||||
status = models.CharField(max_length=32, choices=ConfirmOrIgnore.choices, default=ConfirmOrIgnore.pending, blank=True, verbose_name=_('Status'))
|
||||
details = models.JSONField(default=list, verbose_name=_('Details'))
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -24,7 +24,7 @@ class GatheredAccount(JMSOrgBaseModel):
|
|||
present = models.BooleanField(default=False, verbose_name=_("Present")) # 系统资产上是否还存在
|
||||
date_password_change = models.DateTimeField(null=True, verbose_name=_("Date change password"))
|
||||
date_password_expired = models.DateTimeField(null=True, verbose_name=_("Date password expired"))
|
||||
status = models.CharField(max_length=32, default='', blank=True, choices=ConfirmOrIgnore.choices, verbose_name=_("Status"))
|
||||
status = models.CharField(max_length=32, default=ConfirmOrIgnore.pending, blank=True, choices=ConfirmOrIgnore.choices, verbose_name=_("Status"))
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from accounts.models import GatheredAccount, AccountRisk, SecretType, AutomationExecution
|
||||
|
||||
TYPE_CHOICES = [
|
||||
("ignore", _("Ignore")),
|
||||
("disable_remote", _("Disable remote")),
|
||||
("delete_remote", _("Delete remote")),
|
||||
("delete_both", _("Delete remote")),
|
||||
("add_account", _("Add account")),
|
||||
("change_password_add", _("Change password and Add")),
|
||||
("change_password", _("Change password")),
|
||||
]
|
||||
|
||||
|
||||
class RiskHandler:
|
||||
def __init__(self, asset, username):
|
||||
self.asset = asset
|
||||
self.username = username
|
||||
|
||||
def handle(self, tp, risk=""):
|
||||
attr = f"handle_{tp}"
|
||||
if hasattr(self, attr):
|
||||
return getattr(self, attr)(risk=risk)
|
||||
else:
|
||||
raise ValueError(f"Invalid risk type: {tp}")
|
||||
|
||||
def handle_ignore(self, risk=""):
|
||||
pass
|
||||
|
||||
def handle_add_account(self, risk=""):
|
||||
data = {
|
||||
"username": self.username,
|
||||
"name": self.username,
|
||||
"secret_type": SecretType.PASSWORD,
|
||||
"source": "collected",
|
||||
}
|
||||
self.asset.accounts.get_or_create(defaults=data, username=self.username)
|
||||
GatheredAccount.objects.filter(asset=self.asset, username=self.username).update(
|
||||
present=True, status="confirmed"
|
||||
)
|
||||
(
|
||||
AccountRisk.objects.filter(asset=self.asset, username=self.username)
|
||||
.filter(risk__in=["new_found"])
|
||||
.update(status="confirmed")
|
||||
)
|
||||
|
||||
def handle_disable_remote(self, risk=""):
|
||||
pass
|
||||
|
||||
def handle_delete_remote(self, risk=""):
|
||||
asset = self.asset
|
||||
execution = AutomationExecution()
|
||||
execution.snapshot = {
|
||||
"assets": [str(asset.id)],
|
||||
"accounts": [{"asset": str(asset.id), "username": self.username}],
|
||||
"type": "remove_account",
|
||||
"name": "Remove remote account: {}@{}".format(self.username, asset.name),
|
||||
}
|
||||
execution.save()
|
||||
execution.start()
|
||||
return execution
|
||||
|
||||
def handle_delete_both(self, risk=""):
|
||||
pass
|
||||
|
||||
def handle_change_password_add(self, risk=""):
|
||||
pass
|
||||
|
||||
def handle_change_password(self, risk=""):
|
||||
pass
|
|
@ -4,36 +4,47 @@ from django.utils.translation import gettext_lazy as _
|
|||
from rest_framework import serializers
|
||||
|
||||
from accounts.const import AutomationTypes
|
||||
from accounts.models import CheckAccountAutomation, AccountRisk, RiskChoice, CheckAccountEngine
|
||||
from accounts.models import (
|
||||
CheckAccountAutomation,
|
||||
AccountRisk,
|
||||
RiskChoice,
|
||||
CheckAccountEngine,
|
||||
)
|
||||
from assets.models import Asset
|
||||
from common.serializers.fields import ObjectRelatedField, LabeledChoiceField
|
||||
from common.utils import get_logger
|
||||
from .base import BaseAutomationSerializer
|
||||
from accounts.risk_handlers import TYPE_CHOICES
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
__all__ = [
|
||||
'CheckAccountAutomationSerializer',
|
||||
'AccountRiskSerializer',
|
||||
'CheckAccountEngineSerializer',
|
||||
'AssetRiskSerializer',
|
||||
"CheckAccountAutomationSerializer",
|
||||
"AccountRiskSerializer",
|
||||
"CheckAccountEngineSerializer",
|
||||
"AssetRiskSerializer",
|
||||
"HandleRiskSerializer",
|
||||
]
|
||||
|
||||
|
||||
class AccountRiskSerializer(serializers.ModelSerializer):
|
||||
asset = ObjectRelatedField(queryset=Asset.objects.all(), required=False,label=_("Asset"))
|
||||
risk = LabeledChoiceField(choices=RiskChoice.choices, required=False, read_only=True, label=_("Risk"))
|
||||
asset = ObjectRelatedField(
|
||||
queryset=Asset.objects.all(), required=False, label=_("Asset")
|
||||
)
|
||||
risk = LabeledChoiceField(
|
||||
choices=RiskChoice.choices, required=False, read_only=True, label=_("Risk")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AccountRisk
|
||||
fields = [
|
||||
'id', 'asset', 'username', 'risk', 'status',
|
||||
'date_created', 'details',
|
||||
"id", "asset", "username", "risk", "status",
|
||||
"date_created", "details",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
return queryset.select_related('asset')
|
||||
return queryset.select_related("asset")
|
||||
|
||||
|
||||
class RiskSummarySerializer(serializers.Serializer):
|
||||
|
@ -42,10 +53,14 @@ class RiskSummarySerializer(serializers.Serializer):
|
|||
|
||||
|
||||
class AssetRiskSerializer(serializers.Serializer):
|
||||
id = serializers.CharField(max_length=128, required=False, source='asset__id')
|
||||
name = serializers.CharField(max_length=128, required=False, source='asset__name')
|
||||
address = serializers.CharField(max_length=128, required=False, source='asset__address')
|
||||
platform = serializers.CharField(max_length=128, required=False, source='asset__platform__name')
|
||||
id = serializers.CharField(max_length=128, required=False, source="asset__id")
|
||||
name = serializers.CharField(max_length=128, required=False, source="asset__name")
|
||||
address = serializers.CharField(
|
||||
max_length=128, required=False, source="asset__address"
|
||||
)
|
||||
platform = serializers.CharField(
|
||||
max_length=128, required=False, source="asset__platform__name"
|
||||
)
|
||||
risk_total = serializers.IntegerField()
|
||||
risk_summary = serializers.SerializerMethodField()
|
||||
|
||||
|
@ -53,16 +68,26 @@ class AssetRiskSerializer(serializers.Serializer):
|
|||
def get_risk_summary(obj):
|
||||
summary = {}
|
||||
for risk in RiskChoice.choices:
|
||||
summary[f'{risk[0]}_count'] = obj.get(f'{risk[0]}_count', 0)
|
||||
summary[f"{risk[0]}_count"] = obj.get(f"{risk[0]}_count", 0)
|
||||
return summary
|
||||
|
||||
|
||||
class HandleRiskSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(max_length=128)
|
||||
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects)
|
||||
action = serializers.ChoiceField(choices=TYPE_CHOICES)
|
||||
risk = serializers.ChoiceField(choices=RiskChoice.choices, allow_null=True, allow_blank=True)
|
||||
|
||||
|
||||
class CheckAccountAutomationSerializer(BaseAutomationSerializer):
|
||||
class Meta:
|
||||
model = CheckAccountAutomation
|
||||
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
|
||||
fields = BaseAutomationSerializer.Meta.fields \
|
||||
+ ['engines', 'recipients'] + read_only_fields
|
||||
fields = (
|
||||
BaseAutomationSerializer.Meta.fields
|
||||
+ ["engines", "recipients"]
|
||||
+ read_only_fields
|
||||
)
|
||||
extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs
|
||||
|
||||
@property
|
||||
|
@ -73,8 +98,8 @@ class CheckAccountAutomationSerializer(BaseAutomationSerializer):
|
|||
class CheckAccountEngineSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = CheckAccountEngine
|
||||
fields = ['id', 'name', 'slug', 'is_active', 'comment']
|
||||
read_only_fields = ['slug']
|
||||
fields = ["id", "name", "slug", "is_active", "comment"]
|
||||
read_only_fields = ["slug"]
|
||||
extra_kwargs = {
|
||||
'is_active': {'required': False},
|
||||
"is_active": {"required": False},
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ def remove_accounts_task(gather_account_ids):
|
|||
|
||||
task_snapshot = {
|
||||
'assets': [str(i.asset_id) for i in gather_accounts],
|
||||
'gather_accounts': [str(i.id) for i in gather_accounts],
|
||||
'accounts': [{'asset': str(i.asset_id), 'username': i.username} for i in gather_accounts],
|
||||
}
|
||||
|
||||
tp = AutomationTypes.remove_account
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from rest_framework import serializers
|
||||
|
||||
from accounts.const import SecretType, DEFAULT_PASSWORD_RULES
|
||||
|
||||
from common.utils import ssh_key_gen, random_string
|
||||
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
|
||||
|
||||
|
@ -26,12 +27,12 @@ class SecretGenerator:
|
|||
rules = copy.deepcopy(DEFAULT_PASSWORD_RULES)
|
||||
rules.update(password_rules)
|
||||
rules = {
|
||||
'length': rules['length'],
|
||||
'lower': rules['lowercase'],
|
||||
'upper': rules['uppercase'],
|
||||
'digit': rules['digit'],
|
||||
'special_char': rules['symbol'],
|
||||
'exclude_chars': rules.get('exclude_symbols', ''),
|
||||
"length": rules["length"],
|
||||
"lower": rules["lowercase"],
|
||||
"upper": rules["uppercase"],
|
||||
"digit": rules["digit"],
|
||||
"special_char": rules["symbol"],
|
||||
"exclude_chars": rules.get("exclude_symbols", ""),
|
||||
}
|
||||
return random_string(**rules)
|
||||
|
||||
|
@ -46,10 +47,12 @@ class SecretGenerator:
|
|||
|
||||
|
||||
def validate_password_for_ansible(password):
|
||||
""" 校验 Ansible 不支持的特殊字符 """
|
||||
if password.startswith('{{') and password.endswith('}}'):
|
||||
"""校验 Ansible 不支持的特殊字符"""
|
||||
if password.startswith("{{") and password.endswith("}}"):
|
||||
raise serializers.ValidationError(
|
||||
_('If the password starts with {{` and ends with }} `, then the password is not allowed.')
|
||||
_(
|
||||
"If the password starts with {{` and ends with }} `, then the password is not allowed."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -45,3 +45,4 @@ def quickstart_automation(task_name, tp, task_snapshot=None):
|
|||
trigger=Trigger.manual, **data
|
||||
)
|
||||
execution.start()
|
||||
return execution
|
||||
|
|
|
@ -76,6 +76,7 @@ class Language(models.TextChoices):
|
|||
|
||||
|
||||
class ConfirmOrIgnore(models.TextChoices):
|
||||
pending = '', _('Pending')
|
||||
confirmed = 'confirmed', _('Confirmed')
|
||||
ignored = 'ignored', _('Ignored')
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -18,10 +18,10 @@
|
|||
"AccountDeleteConfirmMsg": "Delete account, continue?",
|
||||
"AccountExportTips": "The exported information contains sensitive information such as encrypted account numbers. the exported format is an encrypted zip file (if you have not set the encryption password, please go to personal info to set the file encryption password).",
|
||||
"AccountDiscoverDetail": "Gather account details",
|
||||
"AccountDiscoverList": "Gather accounts",
|
||||
"AccountDiscoverTaskCreate": "Create gather accounts task",
|
||||
"AccountDiscoverTaskList": "Gather accounts tasks",
|
||||
"AccountDiscoverTaskUpdate": "Update the gather accounts task",
|
||||
"AccountDiscoverList": "Discover accounts",
|
||||
"AccountDiscoverTaskCreate": "Create discover accounts task",
|
||||
"AccountDiscoverTaskList": "Discover accounts tasks",
|
||||
"AccountDiscoverTaskUpdate": "Update the discover accounts task",
|
||||
"AccountList": "Accounts",
|
||||
"AccountPolicy": "Account policy",
|
||||
"AccountPolicyHelpText": "For accounts that do not meet the requirements when creating, such as: non-compliant key types and unique key constraints, you can choose the above strategy.",
|
||||
|
@ -546,9 +546,9 @@
|
|||
"GatewayList": "Gateways",
|
||||
"GatewayPlatformHelpText": "Only platforms with names starting with ‘Gateway’ can be used as gateways.",
|
||||
"GatewayUpdate": "Update the gateway",
|
||||
"DiscoverAccounts": "Gather accounts",
|
||||
"DiscoverAccounts": "Discover accounts",
|
||||
"DiscoverAccountsHelpText": "Collect account information on assets. the collected account information can be imported into the system for centralized management.",
|
||||
"GatheredAccountList": "Gathered accounts",
|
||||
"DiscoveredAccountList": "Discovered accounts",
|
||||
"General": "General",
|
||||
"GeneralAccounts": "General accounts",
|
||||
"GeneralSetting": "General",
|
||||
|
|
|
@ -563,7 +563,7 @@
|
|||
"GatewayUpdate": "ゲートウェイの更新",
|
||||
"DiscoverAccounts": "アカウント収集",
|
||||
"DiscoverAccountsHelpText": "資産上のアカウント情報を収集します。収集したアカウント情報は、システムにインポートして一元管理が可能です",
|
||||
"GatheredAccountList": "収集したアカウント",
|
||||
"DiscoveredAccountList": "収集したアカウント",
|
||||
"General": "基本",
|
||||
"GeneralAccounts": "一般アカウント",
|
||||
"GeneralSetting": "汎用設定",
|
||||
|
|
|
@ -548,7 +548,7 @@
|
|||
"GatewayUpdate": "更新网关",
|
||||
"DiscoverAccounts": "账号发现",
|
||||
"DiscoverAccountsHelpText": "收集资产上的账号信息。收集后的账号信息可以导入到系统中,方便统一管理",
|
||||
"GatheredAccountList": "发现的账号",
|
||||
"DiscoveredAccountList": "发现的账号",
|
||||
"General": "基本",
|
||||
"GeneralAccounts": "普通账号",
|
||||
"GeneralSetting": "通用配置",
|
||||
|
|
|
@ -719,7 +719,7 @@
|
|||
"GatewayUpdate": "更新網關",
|
||||
"DiscoverAccounts": "帳號收集",
|
||||
"DiscoverAccountsHelpText": "收集資產上的賬號資訊。收集後的賬號資訊可以導入到系統中,方便統一",
|
||||
"GatheredAccountList": "Collected accounts",
|
||||
"DiscoveredAccountList": "Collected accounts",
|
||||
"General": "基本",
|
||||
"GeneralAccounts": "普通帳號",
|
||||
"GeneralSetting": "General Settings",
|
||||
|
|
Loading…
Reference in New Issue