mirror of https://github.com/jumpserver/jumpserver
perf: update pam
parent
3164b13e6d
commit
7f06190c5f
|
@ -1,10 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.db.models import Q, Count
|
||||
from rest_framework.decorators import action
|
||||
|
||||
from accounts import serializers
|
||||
from accounts.const import AutomationTypes
|
||||
from accounts.models import AccountCheckAutomation
|
||||
from accounts.models import AccountRisk
|
||||
from accounts.models import AccountCheckAutomation, AccountRisk, RiskChoice
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from .base import AutomationExecutionViewSet
|
||||
|
||||
|
@ -41,10 +42,28 @@ class AccountRiskViewSet(OrgBulkModelViewSet):
|
|||
search_fields = ('username',)
|
||||
serializer_classes = {
|
||||
'default': serializers.AccountRiskSerializer,
|
||||
'assets': serializers.AssetRiskSerializer,
|
||||
}
|
||||
rbac_perms = {
|
||||
'sync_accounts': 'assets.add_AccountRisk',
|
||||
'sync_accounts': 'assets.add_accountrisk',
|
||||
'assets': 'accounts.view_accountrisk'
|
||||
}
|
||||
http_method_names = ['get', 'head', 'options']
|
||||
|
||||
@action(methods=['get'], detail=False, url_path='assets')
|
||||
def assets(self, request, *args, **kwargs):
|
||||
annotations = {
|
||||
f'{risk[0]}_count': Count('id', filter=Q(risk=risk[0]))
|
||||
for risk in RiskChoice.choices
|
||||
}
|
||||
queryset = (
|
||||
AccountRisk.objects
|
||||
.select_related('asset', 'asset__platform') # 使用 select_related 来优化 asset 和 asset__platform 的查询
|
||||
.values('asset__id', 'asset__name', 'asset__address', 'asset__platform__name') # 添加需要的字段
|
||||
.annotate(risk_total=Count('id')) # 计算风险总数
|
||||
.annotate(**annotations) # 使用上面定义的 annotations 进行计数
|
||||
)
|
||||
return self.get_paginated_response_from_queryset(queryset)
|
||||
|
||||
|
||||
class AccountCheckEngineViewSet(OrgBulkModelViewSet):
|
||||
|
@ -60,12 +79,12 @@ class AccountCheckEngineViewSet(OrgBulkModelViewSet):
|
|||
'id': 1,
|
||||
'name': 'check_gathered_account',
|
||||
'display_name': '检查发现的账号',
|
||||
'description': '基于自动发现的账号结果进行检查分析 '
|
||||
'description': '基于自动发现的账号结果进行检查分析,检查 用户组、公钥、sudoers 等信息'
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'name': 'check_account_secret',
|
||||
'display_name': '检查账号密码强弱',
|
||||
'description': '基于账号密码的安全性进行检查分析'
|
||||
'description': '基于账号密码的安全性进行检查分析, 检查密码强度、泄露等信息'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# Generated by Django 4.1.13 on 2024-11-04 06:37
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("assets", "0006_baseautomation_start_time"),
|
||||
("accounts", "0005_account_secret_reset"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="accountrisk",
|
||||
name="account",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="accountrisk",
|
||||
name="asset",
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="risks",
|
||||
to="assets.asset",
|
||||
verbose_name="Asset",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="accountrisk",
|
||||
name="username",
|
||||
field=models.CharField(default="", max_length=32, verbose_name="Username"),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="accountrisk",
|
||||
name="risk",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("zombie", "Long time no login"),
|
||||
("ghost", "Not managed"),
|
||||
("long_time_no_change", "Long time no change"),
|
||||
("weak_password", "Weak password"),
|
||||
("login_bypass", "Login bypass"),
|
||||
("group_change", "Group change"),
|
||||
("account_delete", "Account delete"),
|
||||
("password_expired", "Password expired"),
|
||||
("no_admin_account", "No admin account"),
|
||||
("password_error", "Password error"),
|
||||
("other", "Other"),
|
||||
],
|
||||
max_length=128,
|
||||
verbose_name="Risk",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,34 @@
|
|||
# Generated by Django 4.1.13 on 2024-11-04 06:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("accounts", "0006_remove_accountrisk_account_accountrisk_asset_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="accountrisk",
|
||||
name="risk",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("zombie", "Long time no login"),
|
||||
("ghost", "Not managed"),
|
||||
("long_time_password", "Long time no change"),
|
||||
("weak_password", "Weak password"),
|
||||
("group_changed", "Group change"),
|
||||
("sudo_changed", "Sudo changed"),
|
||||
("account_deleted", "Account delete"),
|
||||
("password_expired", "Password expired"),
|
||||
("no_admin_account", "No admin account"),
|
||||
("password_error", "Password error"),
|
||||
("others", "Others"),
|
||||
],
|
||||
max_length=128,
|
||||
verbose_name="Risk",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,3 +1,5 @@
|
|||
from itertools import islice
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import TextChoices
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
@ -7,7 +9,7 @@ from orgs.mixins.models import JMSOrgBaseModel
|
|||
from .base import AccountBaseAutomation
|
||||
from ...const import AutomationTypes
|
||||
|
||||
__all__ = ['AccountCheckAutomation', 'AccountRisk']
|
||||
__all__ = ['AccountCheckAutomation', 'AccountRisk', 'RiskChoice']
|
||||
|
||||
|
||||
class AccountCheckAutomation(AccountBaseAutomation):
|
||||
|
@ -35,14 +37,22 @@ class AccountCheckAutomation(AccountBaseAutomation):
|
|||
|
||||
|
||||
class RiskChoice(TextChoices):
|
||||
zombie = 'zombie', _('Zombie') # 好久没登录的账号
|
||||
ghost = 'ghost', _('Ghost') # 未被纳管的账号
|
||||
zombie = 'zombie', _('Long time no login') # 好久没登录的账号
|
||||
ghost = 'ghost', _('Not managed') # 未被纳管的账号
|
||||
long_time_password = 'long_time_password', _('Long time no change')
|
||||
weak_password = 'weak_password', _('Weak password')
|
||||
longtime_no_change = 'long_time_no_change', _('Long time no change')
|
||||
password_error = 'password_error', _('Password error')
|
||||
password_expired = 'password_expired', _('Password expired')
|
||||
group_changed = 'group_changed', _('Group change')
|
||||
sudo_changed = 'sudo_changed', _('Sudo changed')
|
||||
account_deleted = 'account_deleted', _('Account delete')
|
||||
no_admin_account = 'no_admin_account', _('No admin account') # 为什么不叫 No privileged 呢,是因为有 privileged,但是不可用
|
||||
other = 'others', _('Others')
|
||||
|
||||
|
||||
class AccountRisk(JMSOrgBaseModel):
|
||||
account = models.ForeignKey('Account', on_delete=models.CASCADE, related_name='risks', verbose_name=_('Account'))
|
||||
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)
|
||||
confirmed = models.BooleanField(default=False, verbose_name=_('Confirmed'))
|
||||
|
||||
|
@ -50,4 +60,29 @@ class AccountRisk(JMSOrgBaseModel):
|
|||
verbose_name = _('Account risk')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.account} - {self.risk}"
|
||||
return f"{self.username}@{self.asset} - {self.risk}"
|
||||
|
||||
@classmethod
|
||||
def gen_fake_data(cls, count=1000, batch_size=50):
|
||||
from assets.models import Asset
|
||||
from accounts.models import Account
|
||||
|
||||
assets = Asset.objects.all()
|
||||
accounts = Account.objects.all()
|
||||
|
||||
counter = iter(range(count))
|
||||
while True:
|
||||
batch = list(islice(counter, batch_size))
|
||||
if not batch:
|
||||
break
|
||||
|
||||
to_create = []
|
||||
for i in batch:
|
||||
asset = assets[i % len(assets)]
|
||||
account = accounts[i % len(accounts)]
|
||||
risk = RiskChoice.choices[i % len(RiskChoice.choices)][0]
|
||||
to_create.append(cls(asset=asset, username=account.username, risk=risk))
|
||||
|
||||
cls.objects.bulk_create(to_create)
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from accounts.const import AutomationTypes
|
||||
from accounts.models import AccountCheckAutomation, AccountRisk
|
||||
from accounts.models import AccountCheckAutomation, AccountRisk, RiskChoice
|
||||
from common.utils import get_logger
|
||||
from .base import BaseAutomationSerializer
|
||||
|
||||
|
@ -12,7 +12,8 @@ logger = get_logger(__file__)
|
|||
__all__ = [
|
||||
'CheckAccountsAutomationSerializer',
|
||||
'AccountRiskSerializer',
|
||||
'AccountCheckEngineSerializer'
|
||||
'AccountCheckEngineSerializer',
|
||||
'AssetRiskSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -22,6 +23,27 @@ class AccountRiskSerializer(serializers.ModelSerializer):
|
|||
fields = '__all__'
|
||||
|
||||
|
||||
class RiskSummarySerializer(serializers.Serializer):
|
||||
risk = serializers.CharField(max_length=128)
|
||||
count = serializers.IntegerField()
|
||||
|
||||
|
||||
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')
|
||||
risk_total = serializers.IntegerField()
|
||||
risk_summary = serializers.SerializerMethodField()
|
||||
|
||||
@staticmethod
|
||||
def get_risk_summary(obj):
|
||||
summary = {}
|
||||
for risk in RiskChoice.choices:
|
||||
summary[f'{risk[0]}_count'] = obj.get(f'{risk[0]}_count', 0)
|
||||
return summary
|
||||
|
||||
|
||||
class CheckAccountsAutomationSerializer(BaseAutomationSerializer):
|
||||
class Meta:
|
||||
model = AccountCheckAutomation
|
||||
|
|
|
@ -25,6 +25,7 @@ router.register(r'push-account-automations', api.PushAccountAutomationViewSet, '
|
|||
router.register(r'push-account-executions', api.PushAccountExecutionViewSet, 'push-account-execution')
|
||||
router.register(r'push-account-records', api.PushAccountRecordViewSet, 'push-account-record')
|
||||
router.register(r'account-check-engines', api.AccountCheckEngineViewSet, 'account-check-engine')
|
||||
router.register(r'account-risks', api.AccountRiskViewSet, 'account-risks')
|
||||
|
||||
urlpatterns = [
|
||||
path('accounts/bulk/', api.AssetAccountBulkCreateApi.as_view(), name='account-bulk-create'),
|
||||
|
|
Loading…
Reference in New Issue