mirror of https://github.com/jumpserver/jumpserver
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
parent
ab9d8afe80
commit
c658252c01
|
@ -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
|
|
@ -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 アカウントの収集
|
|
@ -4,16 +4,25 @@ from datetime import datetime
|
||||||
__all__ = ['GatherAccountsFilter']
|
__all__ = ['GatherAccountsFilter']
|
||||||
|
|
||||||
|
|
||||||
def parse_date(date_str, default=''):
|
def parse_date(date_str, default=None):
|
||||||
if not date_str:
|
if not date_str:
|
||||||
return default
|
return default
|
||||||
if date_str == 'Never':
|
if date_str in ['Never', 'null']:
|
||||||
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
|
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 中
|
# TODO 后期会挪到 playbook 中
|
||||||
|
@ -24,17 +33,83 @@ class GatherAccountsFilter:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mysql_filter(info):
|
def mysql_filter(info):
|
||||||
result = {}
|
result = {}
|
||||||
for _, user_dict in info.items():
|
for username, user_info in info.items():
|
||||||
for username, _ in user_dict.items():
|
password_last_changed = parse_date(user_info.get('password_last_changed'))
|
||||||
if len(username.split('.')) == 1:
|
password_lifetime = user_info.get('password_lifetime')
|
||||||
result[username] = {}
|
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
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def postgresql_filter(info):
|
def postgresql_filter(info):
|
||||||
result = {}
|
result = {}
|
||||||
for username in info:
|
for username, user_info in info.items():
|
||||||
result[username] = {}
|
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
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -105,10 +180,12 @@ class GatherAccountsFilter:
|
||||||
user['date_password_change'] = start_date + timezone.timedelta(days=int(_password_date[0]))
|
user['date_password_change'] = start_date + timezone.timedelta(days=int(_password_date[0]))
|
||||||
if _password_date[1] and _password_date[1] != '0':
|
if _password_date[1] and _password_date[1] != '0':
|
||||||
user['date_password_expired'] = start_date + timezone.timedelta(days=int(_password_date[1]))
|
user['date_password_expired'] = start_date + timezone.timedelta(days=int(_password_date[1]))
|
||||||
|
detail = {
|
||||||
user['groups'] = username_groups.get(username) or ''
|
'groups': username_groups.get(username) or '',
|
||||||
user['sudoers'] = username_sudo.get(username) or ''
|
'sudoers': username_sudo.get(username) or '',
|
||||||
user['authorized_keys'] = username_authorized.get(username) or ''
|
'authorized_keys': username_authorized.get(username) or ''
|
||||||
|
}
|
||||||
|
user['detail'] = detail
|
||||||
result[username] = user
|
result[username] = user
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -125,13 +202,13 @@ class GatherAccountsFilter:
|
||||||
if len(parts) == 2:
|
if len(parts) == 2:
|
||||||
key, value = parts
|
key, value = parts
|
||||||
user_info[key.strip()] = value.strip()
|
user_info[key.strip()] = value.strip()
|
||||||
|
detail = {'groups': user_info.get('Global Group memberships', ''), }
|
||||||
user = {
|
user = {
|
||||||
'username': user_info.get('User name', ''),
|
'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_change': parse_date(user_info.get('Password last set', '')),
|
||||||
'date_password_expired': parse_date(user_info.get('Password expires', '')),
|
'date_password_expired': parse_date(user_info.get('Password expires', '')),
|
||||||
'date_last_login': parse_date(user_info.get('Last logon', '')),
|
'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
|
result[user['username']] = user
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -16,9 +16,9 @@ from ..base.manager import AccountBasePlaybookManager
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
risk_items = [
|
risk_items = [
|
||||||
"authorized_keys",
|
# "authorized_keys",
|
||||||
"sudoers",
|
# "sudoers",
|
||||||
"groups",
|
# "groups",
|
||||||
]
|
]
|
||||||
|
|
||||||
diff_items = risk_items + [
|
diff_items = risk_items + [
|
||||||
|
@ -81,8 +81,8 @@ class AnalyseAccountRisk:
|
||||||
|
|
||||||
risks = []
|
risks = []
|
||||||
for k, v in diff.items():
|
for k, v in diff.items():
|
||||||
if k not in risk_items:
|
# if k not in risk_items:
|
||||||
continue
|
# continue
|
||||||
risks.append(
|
risks.append(
|
||||||
dict(
|
dict(
|
||||||
asset=ori_account.asset,
|
asset=ori_account.asset,
|
||||||
|
|
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -9,7 +9,7 @@ from common.utils.timezone import is_date_more_than
|
||||||
from orgs.mixins.models import JMSOrgBaseModel
|
from orgs.mixins.models import JMSOrgBaseModel
|
||||||
from .base import AccountBaseAutomation
|
from .base import AccountBaseAutomation
|
||||||
|
|
||||||
__all__ = ['GatherAccountsAutomation', 'GatheredAccount',]
|
__all__ = ['GatherAccountsAutomation', 'GatheredAccount']
|
||||||
|
|
||||||
|
|
||||||
class GatheredAccount(JMSOrgBaseModel):
|
class GatheredAccount(JMSOrgBaseModel):
|
||||||
|
@ -17,14 +17,13 @@ class GatheredAccount(JMSOrgBaseModel):
|
||||||
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
|
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"))
|
address_last_login = models.CharField(max_length=39, default='', verbose_name=_("Address login"))
|
||||||
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date 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")) # 远端资产上是否还存在
|
remote_present = models.BooleanField(default=True, verbose_name=_("Remote present")) # 远端资产上是否还存在
|
||||||
present = models.BooleanField(default=False, verbose_name=_("Present")) # 系统资产上是否还存在
|
present = models.BooleanField(default=False, verbose_name=_("Present")) # 系统资产上是否还存在
|
||||||
date_password_change = models.DateTimeField(null=True, verbose_name=_("Date change password"))
|
date_password_change = models.DateTimeField(null=True, verbose_name=_("Date change password"))
|
||||||
date_password_expired = models.DateTimeField(null=True, verbose_name=_("Date password expired"))
|
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
|
@property
|
||||||
def address(self):
|
def address(self):
|
||||||
|
|
|
@ -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)
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
DOCUMENTATION = r'''
|
DOCUMENTATION = r'''
|
||||||
|
@ -161,6 +162,7 @@ class OracleInfo(object):
|
||||||
|
|
||||||
def __get_settings(self):
|
def __get_settings(self):
|
||||||
"""Get global variables (instance settings)."""
|
"""Get global variables (instance settings)."""
|
||||||
|
|
||||||
def _set_settings_value(item_dict):
|
def _set_settings_value(item_dict):
|
||||||
try:
|
try:
|
||||||
self.info['settings'][item_dict['name']] = item_dict['value']
|
self.info['settings'][item_dict['name']] = item_dict['value']
|
||||||
|
@ -178,11 +180,30 @@ class OracleInfo(object):
|
||||||
|
|
||||||
def __get_users(self):
|
def __get_users(self):
|
||||||
"""Get user info."""
|
"""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):
|
def _set_users_value(item_dict):
|
||||||
try:
|
try:
|
||||||
tablespace = item_dict.pop('default_tablespace')
|
tablespace = item_dict.pop('default_tablespace')
|
||||||
username = item_dict.pop('username')
|
username = item_dict.pop('username')
|
||||||
partial_users = self.info['users'].get(tablespace, {})
|
partial_users = self.info['users'].get(tablespace, {})
|
||||||
|
_set_users_roles(username, item_dict)
|
||||||
|
_set_users_privileges(username, item_dict)
|
||||||
partial_users[username] = item_dict
|
partial_users[username] = item_dict
|
||||||
self.info['users'][tablespace] = partial_users
|
self.info['users'][tablespace] = partial_users
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -198,6 +219,7 @@ class OracleInfo(object):
|
||||||
|
|
||||||
def __get_databases(self, exclude_fields):
|
def __get_databases(self, exclude_fields):
|
||||||
"""Get info about databases."""
|
"""Get info about databases."""
|
||||||
|
|
||||||
def _set_databases_value(item_dict):
|
def _set_databases_value(item_dict):
|
||||||
try:
|
try:
|
||||||
tablespace_name = item_dict.pop('tablespace_name')
|
tablespace_name = item_dict.pop('tablespace_name')
|
||||||
|
|
|
@ -125,7 +125,8 @@
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
rightClickSelectsWord: true,
|
rightClickSelectsWord: true,
|
||||||
disableStdin: true
|
disableStdin: true,
|
||||||
|
scrollback: 9999999,
|
||||||
});
|
});
|
||||||
term.open(document.getElementById('term'));
|
term.open(document.getElementById('term'));
|
||||||
window.fit.fit(term);
|
window.fit.fit(term);
|
||||||
|
|
Loading…
Reference in New Issue