perf: update pam

pull/14430/head
ibuler 2024-11-04 18:34:35 +08:00
parent 3164b13e6d
commit 7f06190c5f
6 changed files with 182 additions and 13 deletions

View File

@ -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': '基于账号密码的安全性进行检查分析, 检查密码强度、泄露等信息'
}
]

View File

@ -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",
),
),
]

View File

@ -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",
),
),
]

View File

@ -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)

View File

@ -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

View File

@ -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'),