From 83dc15d441d28be51f86a23b3da0126b99547780 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Fri, 3 Jan 2025 17:47:18 +0800 Subject: [PATCH] perf: Pam dashboard --- apps/accounts/api/account/__init__.py | 3 +- apps/accounts/api/account/pam_dashboard.py | 42 +++++++++++++++++++ .../automations/check_account/manager.py | 10 ++--- .../automations/gather_account/manager.py | 6 +-- apps/accounts/filters.py | 15 ++----- apps/accounts/models/account.py | 17 ++++++++ apps/accounts/risk_handlers.py | 8 ++-- apps/accounts/urls.py | 1 + 8 files changed, 76 insertions(+), 26 deletions(-) create mode 100644 apps/accounts/api/account/pam_dashboard.py diff --git a/apps/accounts/api/account/__init__.py b/apps/accounts/api/account/__init__.py index 2b1f578e7..c2221a379 100644 --- a/apps/accounts/api/account/__init__.py +++ b/apps/accounts/api/account/__init__.py @@ -1,5 +1,6 @@ from .account import * +from .application import * +from .pam_dashboard import * from .task import * from .template import * from .virtual import * -from .application import * diff --git a/apps/accounts/api/account/pam_dashboard.py b/apps/accounts/api/account/pam_dashboard.py new file mode 100644 index 000000000..9fb00dda6 --- /dev/null +++ b/apps/accounts/api/account/pam_dashboard.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +from django.http.response import JsonResponse +from rest_framework.views import APIView + +from accounts.models import Account, RiskChoice + +__all__ = ['PamDashboardApi'] + + +class PamDashboardApi(APIView): + http_method_names = ['get'] + rbac_perms = { + 'GET': 'accounts.view_account', + } + + def get(self, request, *args, **kwargs): + query_params = self.request.query_params + data = {} + + account_count = Account.objects.count() + privileged_account_count = Account.objects.filter(privileged=True).count() + + if query_params.get('total_privileged_accounts'): + data['total_privileged_accounts'] = privileged_account_count + + if query_params.get('total_ordinary_accounts'): + data['total_ordinary_accounts'] = account_count - privileged_account_count + + if query_params.get('total_unmanaged_accounts'): + data['total_unmanaged_accounts'] = Account.get_risks(RiskChoice.new_found).count() + + if query_params.get('total_unavailable_accounts'): + data['total_unavailable_accounts'] = Account.objects.filter(is_active=False).count() + + if query_params.get('total_weak_password_accounts'): + data['total_weak_password_accounts'] = Account.get_risks(RiskChoice.weak_password) + + if query_params.get('total_long_time_change_password_accounts'): + data['total_long_time_change_password_accounts'] = Account.get_risks(RiskChoice.long_time_password) + + return JsonResponse(data, status=200) diff --git a/apps/accounts/automations/check_account/manager.py b/apps/accounts/automations/check_account/manager.py index 9d802bd3e..162f40b2c 100644 --- a/apps/accounts/automations/check_account/manager.py +++ b/apps/accounts/automations/check_account/manager.py @@ -3,7 +3,7 @@ from collections import defaultdict from django.utils import timezone -from accounts.models import Account, AccountRisk +from accounts.models import Account, AccountRisk, RiskChoice from assets.automations.base.manager import BaseManager from common.decorators import bulk_create_decorator, bulk_update_decorator from common.utils.strings import color_fmt @@ -69,12 +69,12 @@ def check_account_secrets(accounts, assets): if is_weak_password(account.secret): print(tmpl % (account, color_fmt("weak", "red"))) - summary["weak_password"] += 1 - result["weak_password"].append(result_item) + summary[RiskChoice.weak_password] += 1 + result[RiskChoice.weak_password].append(result_item) risks.append( { "account": account, - "risk": "weak_password", + "risk": RiskChoice.weak_password, } ) else: @@ -143,7 +143,7 @@ class CheckAccountManager(BaseManager): "\n---\nSummary: \nok: %s, weak password: %s, no secret: %s, using time: %ss" % ( self.summary["ok"], - self.summary["weak_password"], + self.summary[RiskChoice.weak_password], self.summary["no_secret"], int(self.duration), ) diff --git a/apps/accounts/automations/gather_account/manager.py b/apps/accounts/automations/gather_account/manager.py index c52a72a35..af085926b 100644 --- a/apps/accounts/automations/gather_account/manager.py +++ b/apps/accounts/automations/gather_account/manager.py @@ -4,7 +4,7 @@ from collections import defaultdict from django.utils import timezone from accounts.const import AutomationTypes -from accounts.models import GatheredAccount, Account, AccountRisk +from accounts.models import GatheredAccount, Account, AccountRisk, RiskChoice from common.const import ConfirmOrIgnore from common.decorators import bulk_create_decorator, bulk_update_decorator from common.utils import get_logger @@ -68,7 +68,7 @@ class AnalyseAccountRisk: {"field": "date_last_login", "risk": "long_time_no_login", "delta": long_time}, { "field": "date_password_change", - "risk": "long_time_password", + "risk": RiskChoice.long_time_password, "delta": long_time, }, { @@ -164,7 +164,7 @@ class AnalyseAccountRisk: self._create_risk( dict( **basic, - risk="new_found", + risk=RiskChoice.new_found, details=[{"datetime": self.now.isoformat()}], ) ) diff --git a/apps/accounts/filters.py b/apps/accounts/filters.py index 514274700..df224b707 100644 --- a/apps/accounts/filters.py +++ b/apps/accounts/filters.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- # -from django.db.models import Q, Exists, OuterRef +from django.db.models import Q from django.utils import timezone from django_filters import rest_framework as drf_filters from assets.models import Node from common.drf.filters import BaseFilterSet from common.utils.timezone import local_zero_hour, local_now -from .models import Account, GatheredAccount, ChangeSecretRecord, AccountRisk +from .models import Account, GatheredAccount, ChangeSecretRecord class AccountFilterSet(BaseFilterSet): @@ -62,16 +62,7 @@ class AccountFilterSet(BaseFilterSet): if not value: return queryset - queryset = queryset.filter( - Exists( - AccountRisk.objects.filter( - risk=value, - asset_id=OuterRef('asset_id'), - username=OuterRef('username') - ) - ) - ) - return queryset + return Account.get_risks(queryset, value) @staticmethod def filter_latest(queryset, name, value): diff --git a/apps/accounts/models/account.py b/apps/accounts/models/account.py index b5cd382fa..0fe19a20a 100644 --- a/apps/accounts/models/account.py +++ b/apps/accounts/models/account.py @@ -1,4 +1,5 @@ from django.db import models +from django.db.models import Exists, OuterRef from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords @@ -165,6 +166,22 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount): return escape(value) + @classmethod + def get_risks(cls, queryset=None, risk_type=None): + from accounts.models import AccountRisk + subquery = AccountRisk.objects.filter( + asset_id=OuterRef('asset_id'), + username=OuterRef('username') + ) + + if risk_type: + subquery = subquery.filter(risk=risk_type) + + if queryset is None: + queryset = cls.objects.all() + + return queryset.filter(Exists(subquery)) + def replace_history_model_with_mixin(): """ diff --git a/apps/accounts/risk_handlers.py b/apps/accounts/risk_handlers.py index e2911da7e..2dd8c8d53 100644 --- a/apps/accounts/risk_handlers.py +++ b/apps/accounts/risk_handlers.py @@ -1,15 +1,13 @@ +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from accounts.const import AutomationTypes -from common.const import ConfirmOrIgnore from accounts.models import ( GatheredAccount, AccountRisk, SecretType, - AutomationExecution, + AutomationExecution, RiskChoice, ) -from django.utils import timezone - from common.const import ConfirmOrIgnore TYPE_CHOICES = [ @@ -83,7 +81,7 @@ class RiskHandler: GatheredAccount.objects.filter(asset=self.asset, username=self.username).update( present=True, status=ConfirmOrIgnore.confirmed ) - self.risk = "new_found" + self.risk = RiskChoice.new_found def handle_disable_remote(self): pass diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py index b9cfea710..f196e1146 100644 --- a/apps/accounts/urls.py +++ b/apps/accounts/urls.py @@ -49,6 +49,7 @@ urlpatterns = [ path('push-account//nodes/', api.PushAccountNodeAddRemoveApi.as_view(), name='push-account-add-or-remove-node'), path('push-account//assets/', api.PushAccountAssetsListApi.as_view(), name='push-account-assets'), + path('pam-dashboard/', api.PamDashboardApi.as_view(), name='pam-dashboard'), path('change-secret-dashboard/', api.ChangeSecretDashboardApi.as_view(), name='change-secret-dashboard'), ]