Merge branch 'pam' of github.com:jumpserver/jumpserver into pam

pull/14578/head
ibuler 2024-12-03 17:49:27 +08:00
commit 707a83ec19
9 changed files with 390 additions and 200 deletions

View File

@ -125,6 +125,7 @@ class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
("list", "accounts.view_changesecretexecution"),
("retrieve", "accounts.view_changesecretexecution"),
("create", "accounts.add_changesecretexecution"),
("report", "accounts.view_changesecretexecution"),
)
tp = AutomationTypes.change_secret

View File

@ -11,7 +11,7 @@ from accounts.const import (
AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy, ChangeSecretRecordStatusChoice
)
from accounts.models import ChangeSecretRecord, BaseAccountQuerySet
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretFailedMsg
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretReportMsg
from accounts.serializers import ChangeSecretRecordBackUpSerializer
from assets.const import HostTypes
from common.db.utils import safe_db_connection
@ -183,6 +183,14 @@ class ChangeSecretManager(AccountBasePlaybookManager):
with safe_db_connection():
recorder.save(update_fields=['status', 'date_finished'])
account.save(update_fields=['secret', 'version', 'date_updated'])
self.summary['ok_accounts'] += 1
self.result['ok_accounts'].append(
{
"asset": str(account.asset),
"username": account.username,
}
)
super().on_host_success(host, result)
def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host)
@ -195,9 +203,14 @@ class ChangeSecretManager(AccountBasePlaybookManager):
recorder.save()
except Exception as e:
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
def on_runner_failed(self, runner, e, **kwargs):
logger.error("Account error: ", e)
self.summary['fail_accounts'] += 1
self.result['fail_accounts'].append(
{
"asset": str(recorder.asset),
"username": recorder.account.username,
}
)
super().on_host_success(host, result)
def check_secret(self):
if self.secret_strategy == SecretStrategy.custom \
@ -236,24 +249,13 @@ class ChangeSecretManager(AccountBasePlaybookManager):
if self.record_map:
return
failed_recorders = [
r for r in recorders
if r.status == ChangeSecretRecordStatusChoice.failed.value
]
recipients = self.execution.recipients
if not recipients:
return
if failed_recorders:
name = self.execution.snapshot.get('name')
execution_id = str(self.execution.id)
_ids = [r.id for r in failed_recorders]
asset_account_errors = ChangeSecretRecord.objects.filter(
id__in=_ids).values_list('asset__name', 'account__username', 'error')
for user in recipients:
ChangeSecretFailedMsg(name, execution_id, user, asset_account_errors).publish()
context = self.get_report_context()
for user in recipients:
ChangeSecretReportMsg(user, context).publish()
if not recorders:
return
@ -295,3 +297,6 @@ class ChangeSecretManager(AccountBasePlaybookManager):
ws.write_string(row_index, col_index, col_data)
wb.close()
return True
def get_report_template(self):
return "accounts/change_secret_report.html"

View File

@ -1,8 +1,21 @@
from django.utils import timezone
from datetime import datetime
__all__ = ['GatherAccountsFilter']
def parse_date(date_str, default=''):
if not date_str:
return default
if date_str == 'Never':
return None
try:
dt = datetime.strptime(date_str, '%Y/%m/%d %H:%M:%S')
return timezone.make_aware(dt, timezone.get_current_timezone())
except ValueError:
return default
# TODO 后期会挪到 playbook 中
class GatherAccountsFilter:
def __init__(self, tp):
@ -101,11 +114,26 @@ class GatherAccountsFilter:
@staticmethod
def windows_filter(info):
info = info[4:-2]
result = {}
for i in info:
for username in i.split():
result[username] = {}
for user_details in info['user_details']:
user_info = {}
lines = user_details['stdout_lines']
for line in lines:
if not line.strip():
continue
parts = line.split(' ', 1)
if len(parts) == 2:
key, value = parts
user_info[key.strip()] = value.strip()
user = {
'username': user_info.get('User name', ''),
'groups': user_info.get('Global Group memberships', ''),
'date_password_change': parse_date(user_info.get('Password last set', '')),
'date_password_expired': parse_date(user_info.get('Password expires', '')),
'date_last_login': parse_date(user_info.get('Last logon', '')),
'can_change_password': user_info.get('User may change password', 'Yes')
}
result[user['username']] = user
return result
def run(self, method_id_meta_mapper, info):

View File

@ -1,14 +1,32 @@
- hosts: demo
gather_facts: no
tasks:
- name: Gather windows account
ansible.builtin.win_shell: net user
register: result
ignore_errors: true
- name: Run net user command to get all users
win_shell: net user
register: user_list_output
- name: Define info by set_fact
ansible.builtin.set_fact:
info: "{{ result.stdout_lines }}"
- name: Parse all users from net user command
set_fact:
all_users: >-
{%- set users = [] -%}
{%- for line in user_list_output.stdout_lines -%}
{%- if loop.index > 4 and line.strip() != "" and not line.startswith("The command completed") -%}
{%- for user in line.split() -%}
{%- set _ = users.append(user) -%}
{%- endfor -%}
{%- endif -%}
{%- endfor -%}
{{ users }}
- name: Run net user command for each user to get details
win_shell: net user {{ item }}
loop: "{{ all_users }}"
register: user_details
ignore_errors: yes
- set_fact:
info:
user_details: "{{ user_details.results }}"
- debug:
var: info

View File

@ -1,5 +1,6 @@
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
from premailer import transform
from accounts.models import ChangeSecretRecord
from common.tasks import send_mail_attachment_async, upload_backup_to_obj_storage
@ -101,24 +102,19 @@ class GatherAccountChangeMsg(UserMessage):
return cls(user, {})
class ChangeSecretFailedMsg(UserMessage):
class ChangeSecretReportMsg(UserMessage):
subject = _('Change secret or push account failed information')
def __init__(self, name, execution_id, user, asset_account_errors: list):
self.name = name
self.execution_id = execution_id
self.asset_account_errors = asset_account_errors
def __init__(self, user, context: dict):
self.context = context
super().__init__(user)
def get_html_msg(self) -> dict:
context = {
'name': self.name,
'recipient': self.user,
'execution_id': self.execution_id,
'asset_account_errors': self.asset_account_errors
}
message = render_to_string('accounts/change_secret_failed_info.html', context)
report = render_to_string(
'accounts/change_secret_report.html',
self.context
)
message = transform(report)
return {
'subject': str(self.subject),
'message': message
@ -130,4 +126,4 @@ class ChangeSecretFailedMsg(UserMessage):
user = User.objects.first()
record = ChangeSecretRecord.objects.first()
execution_id = str(record.execution_id)
return cls(name, execution_id, user, [])
return cls(user, {})

View File

@ -0,0 +1,134 @@
{% load i18n %}
<div class='summary'>
<p>{% trans 'The following is a summary of account change secret tasks, please review and handle them' %}</p>
<table>
<caption></caption>
<thead>
<tr>
<th colspan='2'>任务汇总:</th>
</tr>
</thead>
<tbody>
<tr>
<td>{% trans 'Task name' %}:</td>
<td>{{ execution.automation.name }} </td>
</tr>
<tr>
<td>{% trans 'Date start' %}:</td>
<td>{{ execution.date_start | date:"Y/m/d H:i:s" }}</td>
</tr>
<tr>
<td>{% trans 'Date end' %}:</td>
<td>{{ execution.date_finished | date:"Y/m/d H:i:s" }}</td>
</tr>
<tr>
<td>{% trans 'Time using' %}:</td>
<td>{{ execution.duration }}s</td>
</tr>
<tr>
<td>{% trans 'Assets count' %}:</td>
<td>{{ summary.total_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset success count' %}:</td>
<td>{{ summary.ok_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset failed count' %}:</td>
<td>{{ summary.fail_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset not support count' %}:</td>
<td>{{ summary.error_assets }}</td>
</tr>
</tbody>
</table>
</div>
<div class='result'>
{% if summary.ok_accounts %}
<p>{% trans 'Success accounts' %}: {{ summary.ok_accounts }}</p>
<table>
<caption></caption>
<thead>
<tr>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
</tr>
</thead>
<tbody>
{% for account in result.ok_accounts %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ account.asset }}</td>
<td>{{ account.username }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<div class='result'>
{% if summary.lost_accounts %}
<p>{% trans 'Failed accounts' %}: {{ summary.fail_accounts }}</p>
<table>
<caption></caption>
<thead>
<tr>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
</tr>
</thead>
<tbody>
{% for account in result.fail_accounts %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ account.asset }}</td>
<td>{{ account.username }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<style>
table {
width: 100%;
border-collapse: collapse;
max-width: 100%;
text-align: left;
margin-top: 10px;
padding: 20px;
}
th {
background: #f2f2f2;
font-size: 14px;
padding: 5px;
border: 1px solid #ddd;
}
tr :first-child {
width: 30%;
}
td {
border: 1px solid #ddd;
padding: 5px;
font-size: 12px;
}
.result {
margin-top: 20px;
}
.result tr :first-child {
width: 10%;
}
</style>

View File

@ -3,74 +3,88 @@
<div class='summary'>
<p>{% trans 'The following is a summary of the account check tasks. Please review and handle them' %}</p>
<table>
<caption></caption>
<thead>
<tr>
<th colspan='2'>任务汇总: </th>
</tr>
<tr>
<th colspan='2'>任务汇总:</th>
</tr>
</thead>
<tbody>
<tr>
<td>{% trans 'Task name' %}: </td>
<td>{{ execution.automation.name }} </td>
</tr>
<tr>
<td>{% trans 'Date start' %}: </td>
<td>{{ execution.date_start }}</td>
</tr>
<tr>
<td>{% trans 'Date end' %}: </td>
<td>{{ execution.date_finished }}</td>
</tr>
<tr>
<td>{% trans 'Time using' %}: </td>
<td>{{ execution.duration }}s</td>
</tr>
<tr>
<td>{% trans 'Assets count' %}: </td>
<td>{{ summary.assets }}</td>
</tr>
<tr>
<td>{% trans 'Account count' %}: </td>
<td>{{ summary.accounts }}</td>
</tr>
<tr>
<td>{% trans 'Week password count' %}:</td>
<td> <span> {{ summary.weak_password }}</span></td>
</tr>
<tr>
<td>{% trans 'Ok count' %}: </td>
<td>{{ summary.ok }}</td>
</tr>
<tr>
<td>{% trans 'No password count' %}: </td>
<td>{{ summary.no_secret }}</td>
</tr>
<tr>
<td>{% trans 'Task name' %}:</td>
<td>{{ execution.automation.name }} </td>
</tr>
<tr>
<td>{% trans 'Date start' %}:</td>
<td>{{ execution.date_start | date:"Y/m/d H:i:s" }}</td>
</tr>
<tr>
<td>{% trans 'Date end' %}:</td>
<td>{{ execution.date_finished | date:"Y/m/d H:i:s" }}</td>
</tr>
<tr>
<td>{% trans 'Time using' %}:</td>
<td>{{ execution.duration }}s</td>
</tr>
<tr>
<td>{% trans 'Assets count' %}:</td>
<td>{{ summary.assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset success count' %}:</td>
<td>{{ summary.ok_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset failed count' %}:</td>
<td>{{ summary.fail_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset not support count' %}:</td>
<td>{{ summary.error_assets }}</td>
</tr>
<tr>
<td>{% trans 'Account count' %}:</td>
<td>{{ summary.accounts }}</td>
</tr>
<tr>
<td>{% trans 'Ok count' %}:</td>
<td>{{ summary.ok }}</td>
</tr>
<tr>
<td>{% trans 'No password count' %}:</td>
<td>{{ summary.no_secret }}</td>
</tr>
</tbody>
</table>
</div>
<div class='result'>
<p>{% trans 'Account check details' %}:</p>
<table style="">
<thead>
<tr>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
<th>{% trans 'Result' %}</th>
</tr>
</thead>
<tbody>
{% for account in result.weak_password %}
{% if summary.weak_password %}
<p>{% trans 'Week password' %}: {{ summary.weak_password }}</p>
<p>{% trans 'Account check details' %}:</p>
<table>
<caption></caption>
<thead>
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ account.asset }}</td>
<td>{{ account.username }}</td>
<td style="color: red">{% trans 'Week password' %}</td>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
<th>{% trans 'Result' %}</th>
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for account in result.weak_password %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ account.asset }}</td>
<td>{{ account.username }}</td>
<td style="color: red">{% trans 'Week password' %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<style>

View File

@ -3,129 +3,123 @@
<div class='summary'>
<p>{% trans 'The following is a summary of the account check tasks. Please review and handle them' %}</p>
<table>
<caption></caption>
<thead>
<tr>
<th colspan='2'>任务汇总: </th>
</tr>
<tr>
<th colspan='2'>任务汇总:</th>
</tr>
</thead>
<tbody>
<tr>
<td>{% trans 'Task name' %}: </td>
<td>{{ execution.automation.name }} </td>
</tr>
<tr>
<td>{% trans 'Date start' %}: </td>
<td>{{ execution.date_start }}</td>
</tr>
<tr>
<td>{% trans 'Date end' %}: </td>
<td>{{ execution.date_finished }}</td>
</tr>
<tr>
<td>{% trans 'Time using' %}: </td>
<td>{{ execution.duration }}s</td>
</tr>
<tr>
<td>{% trans 'Assets count' %}: </td>
<td>{{ summary.total_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset success count' %}: </td>
<td>{{ summary.ok_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset failed count' %}: </td>
<td>{{ summary.fail_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset not support count' %}: </td>
<td>{{ summary.error_assets }}</td>
</tr>
<tr>
<td>{% trans 'Account new found count' %}: </td>
<td>{{ summary.new_accounts }}</td>
</tr>
<tr>
<td>{% trans 'Account lost count' %}: </td>
<td>{{ summary.lost_accounts }}</td>
</tr>
<tr>
<td>{% trans 'Task name' %}:</td>
<td>{{ execution.automation.name }} </td>
</tr>
<tr>
<td>{% trans 'Date start' %}:</td>
<td>{{ execution.date_start | date:"Y/m/d H:i:s" }}</td>
</tr>
<tr>
<td>{% trans 'Date end' %}:</td>
<td>{{ execution.date_finished | date:"Y/m/d H:i:s" }}</td>
</tr>
<tr>
<td>{% trans 'Time using' %}:</td>
<td>{{ execution.duration }}s</td>
</tr>
<tr>
<td>{% trans 'Assets count' %}:</td>
<td>{{ summary.total_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset success count' %}:</td>
<td>{{ summary.ok_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset failed count' %}:</td>
<td>{{ summary.fail_assets }}</td>
</tr>
<tr>
<td>{% trans 'Asset not support count' %}:</td>
<td>{{ summary.error_assets }}</td>
</tr>
</tbody>
</table>
</div>
<div class='result'>
<p>{% trans 'New found accounts' %}: {{ summary.new_accounts }}</p>
{% if summary.new_accounts %}
<table style="">
<thead>
<tr>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
</tr>
</thead>
<tbody>
{% for account in result.new_accounts %}
<p>{% trans 'New found accounts' %}: {{ summary.new_accounts }}</p>
<table>
<caption></caption>
<thead>
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ account.asset }}</td>
<td>{{ account.username }}</td>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for account in result.new_accounts %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ account.asset }}</td>
<td>{{ account.username }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<div class='result'>
<p>{% trans 'Lost accounts' %}: {{ summary.lost_accounts }}</p>
{% if summary.lost_accounts %}
<table style="">
<thead>
<tr>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
</tr>
</thead>
<tbody>
{% for account in result.lost_accounts %}
<p>{% trans 'Lost accounts' %}: {{ summary.lost_accounts }}</p>
<table>
<caption></caption>
<thead>
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ account.asset }}</td>
<td>{{ account.username }}</td>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for account in result.lost_accounts %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ account.asset }}</td>
<td>{{ account.username }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<div class='result'>
<p>{% trans 'New found risks' %}: {{ summary.new_risks }}</p>
<p>{% trans 'New found risks' %}: {{ summary.new_risks }}</p>
{% if summary.new_risks %}
<table style="">
<thead>
<tr>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
<th>{% trans 'Result' %}</th>
</tr>
</thead>
<tbody>
{% for risk in result.risks %}
<table>
<caption></caption>
<thead>
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ risk.asset }}</td>
<td>{{ risk.username }}</td>
<td>{{ risk.risk }}</td>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Asset' %}</th>
<th>{% trans 'Username' %}</th>
<th>{% trans 'Result' %}</th>
</tr>
{% endfor %}
</tbody>
</table>
</thead>
<tbody>
{% for risk in result.risks %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ risk.asset }}</td>
<td>{{ risk.username }}</td>
<td>{{ risk.risk }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>

View File

@ -143,7 +143,7 @@ class BaseManager:
recipients = self.execution.recipients
if not recipients:
return
print("Send report to: ", ",".join(recipients))
print("Send report to: ", ",".join([str(u) for u in recipients]))
report = self.gen_report()
report = transform(report)