feat: oracle accounts gather (#14571)

* feat: oracle accounts gather

* feat: sqlserver accounts gather

* feat: postgresql accounts gather

* feat: mysql accounts gather

---------

Co-authored-by: wangruidong <940853815@qq.com>
pull/14586/head
fit2bot 2024-12-05 14:27:05 +08:00 committed by GitHub
parent ab9d8afe80
commit c658252c01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 216 additions and 30 deletions

View File

@ -0,0 +1,24 @@
- hosts: sqlserver
gather_facts: no
vars:
ansible_python_interpreter: /opt/py3/bin/python
tasks:
- name: Test SQLServer connection
community.general.mssql_script:
login_user: "{{ jms_account.username }}"
login_password: "{{ jms_account.secret }}"
login_host: "{{ jms_asset.address }}"
login_port: "{{ jms_asset.port }}"
name: '{{ jms_asset.spec_info.db_name }}'
script: |
select * from sys.sql_logins
output: dict
register: db_info
- name: Define info by set_fact
set_fact:
info: "{{ db_info.query_results_dict }}"
- debug:
var: info

View File

@ -0,0 +1,10 @@
id: gather_accounts_sqlserver
name: "{{ 'SQLServer account gather' | trans }}"
category: database
type:
- sqlserver
method: gather_accounts
i18n:
SQLServer account gather:
zh: SQLServer 账号收集
ja: SQLServer アカウントの収集

View File

@ -4,16 +4,25 @@ from datetime import datetime
__all__ = ['GatherAccountsFilter']
def parse_date(date_str, default=''):
def parse_date(date_str, default=None):
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:
if date_str in ['Never', 'null']:
return default
formats = [
'%Y/%m/%d %H:%M:%S',
'%Y-%m-%dT%H:%M:%S',
'%d-%m-%Y %H:%M:%S',
'%Y/%m/%d',
'%d-%m-%Y',
]
for fmt in formats:
try:
dt = datetime.strptime(date_str, fmt)
return timezone.make_aware(dt, timezone.get_current_timezone())
except ValueError:
continue
return default
# TODO 后期会挪到 playbook 中
@ -24,17 +33,83 @@ class GatherAccountsFilter:
@staticmethod
def mysql_filter(info):
result = {}
for _, user_dict in info.items():
for username, _ in user_dict.items():
if len(username.split('.')) == 1:
result[username] = {}
for username, user_info in info.items():
password_last_changed = parse_date(user_info.get('password_last_changed'))
password_lifetime = user_info.get('password_lifetime')
user = {
'username': username,
'date_password_change': password_last_changed,
'date_password_expired': password_last_changed + timezone.timedelta(
days=password_lifetime) if password_last_changed and password_lifetime else None,
'date_last_login': None,
'groups': '',
}
result[username] = user
return result
@staticmethod
def postgresql_filter(info):
result = {}
for username in info:
result[username] = {}
for username, user_info in info.items():
user = {
'username': username,
'date_password_change': None,
'date_password_expired': parse_date(user_info.get('valid_until')),
'date_last_login': None,
'groups': '',
}
detail = {
'canlogin': user_info.get('canlogin'),
'superuser': user_info.get('superuser'),
}
user['detail'] = detail
result[username] = user
return result
@staticmethod
def sqlserver_filter(info):
if not info:
return {}
result = {}
for user_info in info[0][0]:
user = {
'username': user_info.get('name', ''),
'date_password_change': None,
'date_password_expired': None,
'date_last_login': None,
'groups': '',
}
detail = {
'create_date': user_info.get('create_date', ''),
'is_disabled': user_info.get('is_disabled', ''),
'default_database_name': user_info.get('default_database_name', ''),
}
user['detail'] = detail
result[user['username']] = user
return result
@staticmethod
def oracle_filter(info):
result = {}
for default_tablespace, users in info.items():
for username, user_info in users.items():
user = {
'username': username,
'date_password_change': parse_date(user_info.get('password_change_date')),
'date_password_expired': parse_date(user_info.get('expiry_date')),
'date_last_login': parse_date(user_info.get('last_login')),
'groups': '',
}
detail = {
'uid': user_info.get('user_id', ''),
'create_date': user_info.get('created', ''),
'account_status': user_info.get('account_status', ''),
'default_tablespace': default_tablespace,
'roles': user_info.get('roles', []),
'privileges': user_info.get('privileges', []),
}
user['detail'] = detail
result[user['username']] = user
return result
@staticmethod
@ -105,10 +180,12 @@ class GatherAccountsFilter:
user['date_password_change'] = start_date + timezone.timedelta(days=int(_password_date[0]))
if _password_date[1] and _password_date[1] != '0':
user['date_password_expired'] = start_date + timezone.timedelta(days=int(_password_date[1]))
user['groups'] = username_groups.get(username) or ''
user['sudoers'] = username_sudo.get(username) or ''
user['authorized_keys'] = username_authorized.get(username) or ''
detail = {
'groups': username_groups.get(username) or '',
'sudoers': username_sudo.get(username) or '',
'authorized_keys': username_authorized.get(username) or ''
}
user['detail'] = detail
result[username] = user
return result
@ -125,13 +202,13 @@ class GatherAccountsFilter:
if len(parts) == 2:
key, value = parts
user_info[key.strip()] = value.strip()
detail = {'groups': user_info.get('Global Group memberships', ''), }
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')
'groups': detail,
}
result[user['username']] = user
return result

View File

@ -16,9 +16,9 @@ from ..base.manager import AccountBasePlaybookManager
logger = get_logger(__name__)
risk_items = [
"authorized_keys",
"sudoers",
"groups",
# "authorized_keys",
# "sudoers",
# "groups",
]
diff_items = risk_items + [
@ -81,8 +81,8 @@ class AnalyseAccountRisk:
risks = []
for k, v in diff.items():
if k not in risk_items:
continue
# if k not in risk_items:
# continue
risks.append(
dict(
asset=ori_account.asset,

View File

@ -0,0 +1,30 @@
# Generated by Django 4.1.13 on 2024-12-03 07:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0018_changesecretrecord_ignore_fail_and_more'),
]
operations = [
migrations.RemoveField(
model_name='gatheredaccount',
name='authorized_keys',
),
migrations.RemoveField(
model_name='gatheredaccount',
name='groups',
),
migrations.RemoveField(
model_name='gatheredaccount',
name='sudoers',
),
migrations.AddField(
model_name='gatheredaccount',
name='detail',
field=models.JSONField(blank=True, default=dict, verbose_name='Detail'),
),
]

View File

@ -9,7 +9,7 @@ from common.utils.timezone import is_date_more_than
from orgs.mixins.models import JMSOrgBaseModel
from .base import AccountBaseAutomation
__all__ = ['GatherAccountsAutomation', 'GatheredAccount',]
__all__ = ['GatherAccountsAutomation', 'GatheredAccount']
class GatheredAccount(JMSOrgBaseModel):
@ -17,14 +17,13 @@ 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"))
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date login"))
authorized_keys = models.TextField(default='', blank=True, verbose_name=_("Authorized keys"))
sudoers = models.TextField(default='', verbose_name=_("Sudoers"), blank=True)
groups = models.TextField(default='', blank=True, verbose_name=_("Groups"))
remote_present = models.BooleanField(default=True, verbose_name=_("Remote present")) # 远端资产上是否还存在
present = models.BooleanField(default=False, verbose_name=_("Present")) # 系统资产上是否还存在
date_password_change = models.DateTimeField(null=True, verbose_name=_("Date change password"))
date_password_expired = models.DateTimeField(null=True, verbose_name=_("Date password expired"))
status = models.CharField(max_length=32, default=ConfirmOrIgnore.pending, blank=True, choices=ConfirmOrIgnore.choices, verbose_name=_("Status"))
status = models.CharField(max_length=32, default=ConfirmOrIgnore.pending, blank=True,
choices=ConfirmOrIgnore.choices, verbose_name=_("Status"))
detail = models.JSONField(default=dict, blank=True, verbose_name=_("Detail"))
@property
def address(self):

View File

@ -0,0 +1,23 @@
# Generated by Django 4.1.13 on 2024-12-04 07:16
from django.db import migrations
def migrate_platform_sqlserver_automation(apps, schema_editor):
platform_model = apps.get_model('assets', 'Platform')
platform = platform_model.objects.filter(name='SQLServer').first()
if platform:
automation = platform.automation
automation.gather_accounts_method = 'gather_accounts_sqlserver'
automation.save()
class Migration(migrations.Migration):
dependencies = [
('assets', '0010_alter_automationexecution_duration'),
]
operations = [
migrations.RunPython(migrate_platform_sqlserver_automation)
]

View File

@ -1,6 +1,7 @@
#!/usr/bin/python
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
@ -161,6 +162,7 @@ class OracleInfo(object):
def __get_settings(self):
"""Get global variables (instance settings)."""
def _set_settings_value(item_dict):
try:
self.info['settings'][item_dict['name']] = item_dict['value']
@ -178,11 +180,30 @@ class OracleInfo(object):
def __get_users(self):
"""Get user info."""
def _set_users_roles(username, item_dict):
users_sql = f"SELECT GRANTED_ROLE FROM DBA_ROLE_PRIVS WHERE GRANTEE = '{username}';"
try:
rtn, err = self.oracle_client.execute(users_sql, exception_to_fail=True)
item_dict['roles'] = [r['role'] for r in rtn]
except Exception:
pass
def _set_users_privileges(username, item_dict):
users_sql = f"SELECT PRIVILEGE FROM DBA_SYS_PRIVS WHERE GRANTEE = '{username}';"
try:
rtn, err = self.oracle_client.execute(users_sql, exception_to_fail=True)
item_dict['privileges'] = [r['privilege'] for r in rtn]
except Exception:
pass
def _set_users_value(item_dict):
try:
tablespace = item_dict.pop('default_tablespace')
username = item_dict.pop('username')
partial_users = self.info['users'].get(tablespace, {})
_set_users_roles(username, item_dict)
_set_users_privileges(username, item_dict)
partial_users[username] = item_dict
self.info['users'][tablespace] = partial_users
except KeyError:
@ -198,6 +219,7 @@ class OracleInfo(object):
def __get_databases(self, exclude_fields):
"""Get info about databases."""
def _set_databases_value(item_dict):
try:
tablespace_name = item_dict.pop('tablespace_name')

View File

@ -125,7 +125,8 @@
fontSize: 13,
lineHeight: 1.2,
rightClickSelectsWord: true,
disableStdin: true
disableStdin: true,
scrollback: 9999999,
});
term.open(document.getElementById('term'));
window.fit.fit(term);