mirror of https://github.com/jumpserver/jumpserver
perf: update filter
parent
372196ca37
commit
80f04192eb
|
@ -5,7 +5,7 @@ from django.utils import timezone
|
|||
__all__ = ['GatherAccountsFilter']
|
||||
|
||||
|
||||
# TODO 后期会挪到playbook中
|
||||
# TODO 后期会挪到 playbook 中
|
||||
class GatherAccountsFilter:
|
||||
|
||||
def __init__(self, tp):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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="",
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue