perf: update filter

pull/14430/head
ibuler 2024-10-30 16:10:46 +08:00
parent 372196ca37
commit 80f04192eb
7 changed files with 180 additions and 46 deletions

View File

@ -5,7 +5,7 @@ from django.utils import timezone
__all__ = ['GatherAccountsFilter']
# TODO 后期会挪到playbook中
# TODO 后期会挪到 playbook
class GatherAccountsFilter:
def __init__(self, tp):

View File

@ -1,21 +1,64 @@
- hosts: demo
gather_facts: no
tasks:
- name: Gather posix account
- name: Get users
ansible.builtin.shell:
cmd: >
users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users;
do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $0 }')
if [ -n "$k" ]; then
echo $k
else
echo $i
fi;done
register: result
getent passwd | awk -F: '$7 !~ /(false|nologin)$/' | grep -v '^$' | awk -F":" '{ print $1 }'
register: users
- name: Gather posix account
ansible.builtin.shell: |
for user in {{ users.stdout_lines | join(" ") }}; do
k=$(last --time-format iso $user -1 | head -1 | grep -v ^$ | awk '{ print $0 }')
if [ -n "$k" ]; then
echo $k
fi
done
register: last_login
- name: Get user groups
ansible.builtin.shell: |
for user in {{ users.stdout_lines | join(" ") }}; do
echo "$(groups $user)"
done
register: user_groups
- name: Get sudo permissions
ansible.builtin.shell: |
for user in {{ users.stdout_lines | join(" ") }}; do
echo "$user: $(grep "^$user " /etc/sudoers | tr '\n' ';' || echo '')"
done
register: user_sudo
- name: Get authorized keys
ansible.builtin.shell: |
for user in {{ users.stdout_lines | join(" ") }}; do
home=$(getent passwd $user | cut -d: -f6)
echo -n "$user:"
if [[ -f ${home}/.ssh/authorized_keys ]]; then
cat ${home}/.ssh/authorized_keys | tr '\n' ';'
echo
fi
done
register: user_authorized
- name: Display user groups
ansible.builtin.debug:
var: user_groups.stdout_lines
- name: Display sudo permissions
ansible.builtin.debug:
var: user_sudo.stdout_lines
- name: Display authorized keys
ansible.builtin.debug:
var: user_authorized.stdout_lines
- name: Display last login
ansible.builtin.debug:
var: last_login.stdout_lines
- name: Define info by set_fact
ansible.builtin.set_fact:
info: "{{ result.stdout_lines }}"
- debug:
var: info
var: last_login.stdout_lines

View File

@ -1,3 +1,4 @@
import json
from collections import defaultdict
from accounts.const import AutomationTypes
@ -61,6 +62,9 @@ class GatherAccountsManager(AccountBasePlaybookManager):
return data
def on_host_success(self, host, result):
print("Result: ")
print(json.dumps(result, indent=4))
print(">>>>>>>>>>>>>>>>.")
info = self.get_nested_info(result, 'debug', 'res', 'info')
asset = self.host_asset_mapper.get(host)
if asset and info:
@ -69,9 +73,28 @@ class GatherAccountsManager(AccountBasePlaybookManager):
else:
print(f'\033[31m Not found {host} info \033[0m\n')
@staticmethod
def update_gather_accounts_status(asset):
"""
对于资产上不存在的账号标识为待处理
对于账号中不存在的标识为待处理
"""
asset_accounts_usernames = asset.accounts.values_list('username', flat=True)
# 账号中不存在的标识为待处理的, 有可能是账号那边删除了
GatheredAccount.objects \
.filter(asset=asset, present=True) \
.exclude(username__in=asset_accounts_usernames) \
.exclude(status=ConfirmOrIgnore.ignored) \
.update(status='')
# 远端资产上不存在的,标识为待处理,需要管理员介入
GatheredAccount.objects \
.filter(asset=asset, present=False) \
.exclude(status=ConfirmOrIgnore.ignored) \
.update(status='')
def update_or_create_accounts(self):
for asset, data in self.asset_account_info.items():
asset_accounts_usernames = set(asset.accounts.values_list('username', flat=True))
with (tmp_to_org(asset.org_id)):
gathered_accounts = []
# 把所有的设置为 present = False, 创建的时候如果有就会更新
@ -83,21 +106,8 @@ class GatherAccountsManager(AccountBasePlaybookManager):
)
gathered_accounts.append(gathered_account)
# 账号中不存在的标识为待处理的, 有可能是账号那边删除了
GatheredAccount.objects \
.filter(asset=asset, present=True) \
.exclude(username__in=asset_accounts_usernames) \
.exclude(status=ConfirmOrIgnore.ignored) \
.update(status='')
# 远端资产上不存在的,标识为待处理,需要管理员介入
GatheredAccount.objects \
.filter(asset=asset, present=False) \
.exclude(status=ConfirmOrIgnore.ignored) \
.update(status='')
if not self.is_sync_account:
continue
GatheredAccount.sync_accounts(gathered_accounts)
self.update_gather_accounts_status(asset)
GatheredAccount.sync_accounts(gathered_accounts, self.is_sync_account)
def run(self, *args, **kwargs):
super().run(*args, **kwargs)
@ -115,7 +125,6 @@ class GatherAccountsManager(AccountBasePlaybookManager):
return users, None
asset_ids = self.asset_username_mapper.keys()
assets = Asset.objects.filter(id__in=asset_ids).prefetch_related('accounts')
gather_accounts = GatheredAccount.objects.filter(asset_id__in=asset_ids, present=True)

View File

@ -26,7 +26,7 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name="gatheredaccount",
name="action",
name="status",
field=models.CharField(
choices=[("confirmed", "Confirmed"), ("ignored", "Ignored")],
default="",

View File

@ -0,0 +1,64 @@
# Generated by Django 4.1.13 on 2024-10-30 02:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("accounts", "0010_alter_accountrisk_options_and_more"),
]
operations = [
migrations.AddField(
model_name="gatheredaccount",
name="authorized_keys",
field=models.TextField(
blank=True, default="", verbose_name="Authorized keys"
),
),
migrations.AddField(
model_name="gatheredaccount",
name="groups",
field=models.TextField(blank=True, default="", verbose_name="Groups"),
),
migrations.AddField(
model_name="gatheredaccount",
name="sudo",
field=models.TextField(default=False, verbose_name="Sudo"),
),
migrations.CreateModel(
name="GatheredAccountDiff",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("diff", models.TextField(default="", verbose_name="Diff")),
(
"item",
models.CharField(default="", max_length=32, verbose_name="Item"),
),
(
"date_created",
models.DateTimeField(
auto_now_add=True, verbose_name="Date created"
),
),
(
"account",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="accounts.gatheredaccount",
verbose_name="Gathered account",
),
),
],
),
]

View File

@ -1,17 +1,24 @@
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes, Source
from accounts.models import Account
from common.const import ConfirmOrIgnore
from common.utils.timezone import is_date_more_than
from orgs.mixins.models import JMSOrgBaseModel
from .base import AccountBaseAutomation
__all__ = ['GatherAccountsAutomation', 'GatheredAccount']
class GatheredAccountDiff(models.Model):
account = models.ForeignKey('GatheredAccount', on_delete=models.CASCADE, verbose_name=_("Gathered account"))
diff = models.TextField(default='', verbose_name=_("Diff"))
item = models.CharField(max_length=32, default='', verbose_name=_("Item"))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
class GatheredAccount(JMSOrgBaseModel):
present = models.BooleanField(default=True, verbose_name=_("Remote present")) # 资产上是否还存在
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date login"))
@ -19,25 +26,32 @@ class GatheredAccount(JMSOrgBaseModel):
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
address_last_login = models.CharField(max_length=39, default='', verbose_name=_("Address login"))
status = models.CharField(max_length=32, default='', blank=True, choices=ConfirmOrIgnore.choices, verbose_name=_("Status"))
authorized_keys = models.TextField(default='', blank=True, verbose_name=_("Authorized keys"))
sudo = models.TextField(default=False, verbose_name=_("Sudo"))
groups = models.TextField(default='', blank=True, verbose_name=_("Groups"))
@property
def address(self):
return self.asset.address
@classmethod
def find_account_risk(cls, gathered_account, accounts):
pass
@classmethod
def update_exists_accounts(cls, gathered_account, accounts):
if not gathered_account.date_last_login:
return
for account in accounts:
if (not account.date_last_login or
account.date_last_login - gathered_account.date_last_login > timezone.timedelta(minutes=5)):
# 这里是否可以考虑,标记成未从堡垒机登录风险
if is_date_more_than(gathered_account.date_last_login, account.date_last_login, '5m'):
account.date_last_login = gathered_account.date_last_login
account.login_by = '{}({})'.format('unknown', gathered_account.address_last_login)
account.save(update_fields=['date_last_login', 'login_by'])
@classmethod
def create_accounts(cls, gathered_account, accounts):
def create_accounts(cls, gathered_account):
account_objs = []
asset_id = gathered_account.asset_id
username = gathered_account.username
@ -50,9 +64,14 @@ class GatheredAccount(JMSOrgBaseModel):
)
account_objs.append(account)
Account.objects.bulk_create(account_objs)
gathered_account.status = ConfirmOrIgnore.confirmed
gathered_account.save(update_fields=['status'])
@classmethod
def sync_accounts(cls, gathered_accounts):
def sync_accounts(cls, gathered_accounts, auto_create=True):
"""
更新为已存在的账号或者创建新的账号, 原来的 sync 重构了如果存在则自动更新一些信息
"""
for gathered_account in gathered_accounts:
asset_id = gathered_account.asset_id
username = gathered_account.username
@ -60,13 +79,11 @@ class GatheredAccount(JMSOrgBaseModel):
Q(asset_id=asset_id, username=username) |
Q(asset_id=asset_id, name=username)
)
if accounts.exists():
cls.update_exists_accounts(gathered_account, accounts)
else:
cls.create_accounts(gathered_account, accounts)
gathered_account.status = ConfirmOrIgnore.confirmed
gathered_account.save(update_fields=['status'])
elif auto_create:
cls.create_accounts(gathered_account)
class Meta:
verbose_name = _("Gather asset accounts")

View File

@ -37,9 +37,11 @@ def local_monday():
return zero_hour_time - timedelta(zero_hour_time.weekday())
def is_date_difference_than(d1, d2, threshold='1d'):
if d1 is None or d2 is None:
def is_date_more_than(d1, d2, threshold='1d'):
if d1 is None:
return False
if d2 is None:
return True
kwargs = {}
if 'd' in threshold:
@ -52,8 +54,7 @@ def is_date_difference_than(d1, d2, threshold='1d'):
raise ValueError('Invalid threshold format')
delta = dj_timezone.timedelta(**kwargs)
return abs((time1 - time2).days) > threshold_in_days
return d1 - d2 > delta
_rest_dt_field = DateTimeField()