mirror of https://github.com/jumpserver/jumpserver
perf: gather accounts
parent
097ebc2362
commit
2355d1af83
|
@ -27,6 +27,7 @@
|
|||
password: "{{ account.secret }}"
|
||||
host: "%"
|
||||
when: db_info is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
community.mysql.mysql_info:
|
||||
|
@ -35,3 +36,6 @@
|
|||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
filter: version
|
||||
when:
|
||||
- db_info is succeeded
|
||||
- change_info is succeeded
|
|
@ -1,8 +1,8 @@
|
|||
- hosts: postgre
|
||||
gather_facts: no
|
||||
vars:
|
||||
# ansible_python_interpreter: /usr/local/bin/python
|
||||
ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python
|
||||
ansible_python_interpreter: /usr/local/bin/python
|
||||
|
||||
tasks:
|
||||
- name: Test PostgreSQL connection
|
||||
community.postgresql.postgresql_ping:
|
||||
|
@ -37,4 +37,6 @@
|
|||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
db: "{{ jms_asset.database }}"
|
||||
when: db_info is succeeded and change_info is changed
|
||||
when:
|
||||
- db_info is succeeded
|
||||
- change_info is succeeded
|
||||
|
|
|
@ -1,29 +1,58 @@
|
|||
- hosts: demo
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: ping
|
||||
- name: Test privileged account
|
||||
ansible.builtin.ping:
|
||||
|
||||
#- name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Password: {{ account.password }}"
|
||||
#
|
||||
# - name: print variables
|
||||
# debug:
|
||||
# msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ secret_type }}"
|
||||
|
||||
- name: Change password
|
||||
user:
|
||||
ansible.builtin.user:
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.password | password_hash('des') }}"
|
||||
password: "{{ account.secret | password_hash('sha512') }}"
|
||||
update_password: always
|
||||
when: account.password
|
||||
when: secret_type == "password"
|
||||
|
||||
- name: Change public key
|
||||
authorized_key:
|
||||
- name: create user If it already exists, no operation will be performed
|
||||
ansible.builtin.user:
|
||||
name: "{{ account.username }}"
|
||||
when: secret_type == "ssh_key"
|
||||
|
||||
- name: remove jumpserver ssh key
|
||||
ansible.builtin.lineinfile:
|
||||
dest: "{{ kwargs.dest }}"
|
||||
regexp: "{{ kwargs.regexp }}"
|
||||
state: absent
|
||||
when:
|
||||
- secret_type == "ssh_key"
|
||||
- kwargs.strategy == "set_jms"
|
||||
|
||||
- name: Change SSH key
|
||||
ansible.builtin.authorized_key:
|
||||
user: "{{ account.username }}"
|
||||
key: "{{ account.public_key }}"
|
||||
state: present
|
||||
when: account.public_key
|
||||
key: "{{ account.secret }}"
|
||||
exclusive: "{{ kwargs.exclusive }}"
|
||||
when: secret_type == "ssh_key"
|
||||
|
||||
- name: Refresh connection
|
||||
ansible.builtin.meta: reset_connection
|
||||
|
||||
- name: Verify password
|
||||
ansible.builtin.ping:
|
||||
become: no
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_pass: "{{ account.password }}"
|
||||
ansible_ssh_connection: paramiko
|
||||
ansible_password: "{{ account.secret }}"
|
||||
ansible_become: no
|
||||
when: secret_type == "password"
|
||||
|
||||
- name: Verify SSH key
|
||||
ansible.builtin.ping:
|
||||
become: no
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
||||
ansible_become: no
|
||||
when: secret_type == "ssh_key"
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
dest: "{{ kwargs.dest }}"
|
||||
regexp: "{{ kwargs.regexp }}"
|
||||
state: absent
|
||||
when: secret_type == "ssh_key" and kwargs.strategy == "set_jms"
|
||||
when:
|
||||
- secret_type == "ssh_key"
|
||||
- kwargs.strategy == "set_jms"
|
||||
|
||||
- name: Change SSH key
|
||||
ansible.builtin.authorized_key:
|
|
@ -1,5 +1,5 @@
|
|||
id: change_secret_linux
|
||||
name: Change password for Linux
|
||||
id: change_secret_posix
|
||||
name: Change secret for posix
|
||||
category: host
|
||||
type:
|
||||
- unix
|
|
@ -1,8 +1,6 @@
|
|||
# from .backup.manager import AccountBackupExecutionManager
|
||||
#
|
||||
#
|
||||
from .change_secret.manager import ChangeSecretManager
|
||||
from .gather_facts.manager import GatherFactsManager
|
||||
from .gather_accounts.manager import GatherAccountsManager
|
||||
from ..const import AutomationTypes
|
||||
|
||||
|
||||
|
@ -10,6 +8,7 @@ class ExecutionManager:
|
|||
manager_type_mapper = {
|
||||
AutomationTypes.change_secret: ChangeSecretManager,
|
||||
AutomationTypes.gather_facts: GatherFactsManager,
|
||||
AutomationTypes.gather_accounts: GatherAccountsManager,
|
||||
}
|
||||
|
||||
def __init__(self, execution):
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
- hosts: mysql
|
||||
gather_facts: no
|
||||
vars:
|
||||
# ansible_python_interpreter: /usr/local/bin/python
|
||||
ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python
|
||||
|
||||
tasks:
|
||||
- name: Get info
|
||||
community.mysql.mysql_info:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
filter: users
|
||||
register: db_info
|
||||
|
||||
- name: Define info by set_fact
|
||||
set_fact:
|
||||
info: "{{ db_info.users }}"
|
||||
|
||||
- debug:
|
||||
var: info
|
|
@ -0,0 +1,6 @@
|
|||
id: gather_accounts_mysql
|
||||
name: Gather account from MySQL
|
||||
category: database
|
||||
type:
|
||||
- mysql
|
||||
method: gather_accounts
|
|
@ -0,0 +1,23 @@
|
|||
- hosts: postgresql
|
||||
gather_facts: no
|
||||
vars:
|
||||
# ansible_python_interpreter: /usr/local/bin/python
|
||||
ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python
|
||||
|
||||
tasks:
|
||||
- name: Get info
|
||||
community.postgresql.postgresql_info:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
login_db: "{{ jms_asset.database }}"
|
||||
filter: "roles"
|
||||
register: db_info
|
||||
|
||||
- name: Define info by set_fact
|
||||
set_fact:
|
||||
info: "{{ db_info.roles }}"
|
||||
|
||||
- debug:
|
||||
var: info
|
|
@ -0,0 +1,6 @@
|
|||
id: gather_accounts_postgresql
|
||||
name: Gather account for PostgreSQL
|
||||
category: database
|
||||
type:
|
||||
- postgresql
|
||||
method: gather_accounts
|
|
@ -0,0 +1,56 @@
|
|||
from django.utils import timezone
|
||||
|
||||
__all__ = ['GatherAccountsFilter']
|
||||
|
||||
|
||||
# TODO 后期会挪到playbook中
|
||||
class GatherAccountsFilter:
|
||||
|
||||
def __init__(self, tp):
|
||||
self.tp = tp
|
||||
|
||||
@staticmethod
|
||||
def mysql_filter(info):
|
||||
result = {}
|
||||
for _, user_dict in info.items():
|
||||
for username, data in user_dict.items():
|
||||
if data.get('account_locked') == 'N':
|
||||
result[username] = {}
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def postgresql_filter(info):
|
||||
result = {}
|
||||
for username in info:
|
||||
result[username] = {}
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def posix_filter(info):
|
||||
result = {}
|
||||
for line in info:
|
||||
data = line.split('@')
|
||||
if len(data) != 3:
|
||||
continue
|
||||
username, address, dt = data
|
||||
date = timezone.datetime.strptime(f'{dt} +0800', '%b %d %H:%M:%S %Y %z')
|
||||
result[username] = {'address': address, 'date': date}
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def windows_filter(info):
|
||||
# TODO
|
||||
result = {}
|
||||
return result
|
||||
|
||||
def run(self, method_id_meta_mapper, info):
|
||||
run_method_name = None
|
||||
for k, v in method_id_meta_mapper.items():
|
||||
if self.tp not in v['type']:
|
||||
continue
|
||||
run_method_name = k.replace(f'{v["method"]}_', '')
|
||||
|
||||
if not run_method_name:
|
||||
return info
|
||||
|
||||
return getattr(self, f'{run_method_name}_filter')(info)
|
|
@ -0,0 +1,14 @@
|
|||
- hosts: demo
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Gather posix account
|
||||
ansible.builtin.win_shell:
|
||||
cmd: net user
|
||||
register: result
|
||||
|
||||
- name: Define info by set_fact
|
||||
ansible.builtin.set_fact:
|
||||
info: "{{ result.stdout_lines }}"
|
||||
|
||||
- debug:
|
||||
var: info
|
|
@ -0,0 +1,7 @@
|
|||
id: gather_accounts_posix
|
||||
name: Gather posix account
|
||||
category: host
|
||||
type:
|
||||
- linux
|
||||
- unix
|
||||
method: gather_accounts
|
|
@ -0,0 +1,18 @@
|
|||
- hosts: windows
|
||||
gather_facts: yes
|
||||
tasks:
|
||||
- name: Get info
|
||||
set_fact:
|
||||
info:
|
||||
arch: "{{ ansible_architecture2 }}"
|
||||
distribution: "{{ ansible_distribution }}"
|
||||
distribution_version: "{{ ansible_distribution_version }}"
|
||||
kernel: "{{ ansible_kernel }}"
|
||||
vendor: "{{ ansible_system_vendor }}"
|
||||
model: "{{ ansible_product_name }}"
|
||||
sn: "{{ ansible_product_serial }}"
|
||||
cpu_vcpus: "{{ ansible_processor_vcpus }}"
|
||||
memory: "{{ ansible_memtotal_mb }}"
|
||||
|
||||
- debug:
|
||||
var: info
|
|
@ -0,0 +1,7 @@
|
|||
id: gather_accounts_windows
|
||||
name: Gather account windows
|
||||
version: 1
|
||||
method: gather_accounts
|
||||
category: host
|
||||
type:
|
||||
- windows
|
|
@ -0,0 +1,45 @@
|
|||
from common.utils import get_logger
|
||||
from assets.const import AutomationTypes
|
||||
from orgs.utils import tmp_to_org
|
||||
from .filter import GatherAccountsFilter
|
||||
from ...models import Account, GatheredUser
|
||||
from ..base.manager import BasePlaybookManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class GatherAccountsManager(BasePlaybookManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.host_asset_mapper = {}
|
||||
|
||||
@classmethod
|
||||
def method_type(cls):
|
||||
return AutomationTypes.gather_accounts
|
||||
|
||||
def host_callback(self, host, asset=None, **kwargs):
|
||||
super().host_callback(host, asset=asset, **kwargs)
|
||||
self.host_asset_mapper[host['name']] = asset
|
||||
return host
|
||||
|
||||
def filter_success_result(self, host, result):
|
||||
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
|
||||
return result
|
||||
|
||||
def on_host_success(self, host, result):
|
||||
info = result.get('debug', {}).get('res', {}).get('info', {})
|
||||
asset = self.host_asset_mapper.get(host)
|
||||
org_id = asset.org_id
|
||||
if asset and info:
|
||||
result = self.filter_success_result(host, info)
|
||||
with tmp_to_org(org_id):
|
||||
GatheredUser.objects.filter(asset=asset, present=True).update(present=False)
|
||||
for username, data in result.items():
|
||||
defaults = {'asset': asset, 'present': True, 'username': username}
|
||||
if data.get('date'):
|
||||
defaults['date_last_login'] = data['date']
|
||||
if data.get('address'):
|
||||
defaults['ip_last_login'] = data['address'][:32]
|
||||
GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username)
|
||||
else:
|
||||
logger.error("Not found info, task name must be 'Get info': {}".format(host))
|
|
@ -13,7 +13,7 @@
|
|||
filter: version
|
||||
register: db_info
|
||||
|
||||
- name: Define Mysql info by set_fact
|
||||
- name: Define info by set_fact
|
||||
set_fact:
|
||||
info:
|
||||
version: "{{ db_info.version.full }}"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- hosts: postgre
|
||||
- hosts: postgresql
|
||||
gather_facts: no
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/local/bin/python
|
||||
|
@ -13,7 +13,7 @@
|
|||
login_db: "{{ jms_asset.database }}"
|
||||
register: db_info
|
||||
|
||||
- name: Define Postgresql info by set_fact
|
||||
- name: Define info by set_fact
|
||||
set_fact:
|
||||
info:
|
||||
version: "{{ db_info.server_version.raw }}"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- hosts: website
|
||||
- hosts: demo
|
||||
gather_facts: yes
|
||||
tasks:
|
||||
- name: Get info
|
||||
|
|
|
@ -1,75 +1,9 @@
|
|||
import os
|
||||
import shutil
|
||||
from copy import deepcopy
|
||||
from collections import defaultdict
|
||||
|
||||
import yaml
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from ops.ansible import PlaybookRunner
|
||||
from common.utils import get_logger
|
||||
from assets.const import AutomationTypes
|
||||
from ..base.manager import BasePlaybookManager
|
||||
from assets.automations.methods import platform_automation_methods
|
||||
|
||||
|
||||
class ChangePasswordManager(BasePlaybookManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.id_method_mapper = {
|
||||
method['id']: method
|
||||
for method in platform_automation_methods
|
||||
}
|
||||
self.method_hosts_mapper = defaultdict(list)
|
||||
self.playbooks = []
|
||||
|
||||
def inventory_kwargs(self):
|
||||
return {
|
||||
'host_callback': self.host_duplicator
|
||||
}
|
||||
|
||||
def generate_playbook(self):
|
||||
playbook = []
|
||||
for method_id, host_names in self.method_hosts_mapper.items():
|
||||
method = self.id_method_mapper[method_id]
|
||||
method_playbook_dir_path = method['dir']
|
||||
method_playbook_dir_name = os.path.basename(method_playbook_dir_path)
|
||||
sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name)
|
||||
shutil.copytree(method_playbook_dir_path, sub_playbook_dir)
|
||||
sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml')
|
||||
|
||||
with open(sub_playbook_path, 'r') as f:
|
||||
host_playbook_play = yaml.safe_load(f)
|
||||
|
||||
if isinstance(host_playbook_play, list):
|
||||
host_playbook_play = host_playbook_play[0]
|
||||
|
||||
step = 10
|
||||
hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)]
|
||||
for i, hosts in enumerate(hosts_grouped):
|
||||
plays = []
|
||||
play = deepcopy(host_playbook_play)
|
||||
play['hosts'] = ':'.join(hosts)
|
||||
plays.append(play)
|
||||
|
||||
playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i))
|
||||
with open(playbook_path, 'w') as f:
|
||||
yaml.safe_dump(plays, f)
|
||||
self.playbooks.append(playbook_path)
|
||||
|
||||
playbook.append({
|
||||
'name': method['name'] + ' for part {}'.format(i),
|
||||
'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i))
|
||||
})
|
||||
|
||||
with open(self.playbook_path, 'w') as f:
|
||||
yaml.safe_dump(playbook, f)
|
||||
|
||||
print("Generate playbook done: " + self.playbook_path)
|
||||
|
||||
def get_runner(self):
|
||||
return PlaybookRunner(
|
||||
self.inventory_path,
|
||||
self.playbook_path,
|
||||
self.runtime_dir
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PingManager(BasePlaybookManager):
|
||||
pass
|
||||
|
|
|
@ -15,7 +15,7 @@ class AutomationTypes(TextChoices):
|
|||
push_account = 'push_account', _('Create account')
|
||||
change_secret = 'change_secret', _('Change secret')
|
||||
verify_account = 'verify_account', _('Verify account')
|
||||
gather_account = 'gather_account', _('Gather account')
|
||||
gather_accounts = 'gather_accounts', _('Gather accounts')
|
||||
|
||||
|
||||
class SecretStrategy(TextChoices):
|
||||
|
|
|
@ -166,6 +166,7 @@ class AllTypes(ChoicesMixin):
|
|||
data['protocols'] = protocols
|
||||
|
||||
automation = constraints.get('automation', {})
|
||||
|
||||
enable_fields = {k: v for k, v in automation.items() if k.endswith('_enabled')}
|
||||
for k, v in enable_fields.items():
|
||||
auto_item = k.replace('_enabled', '')
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 3.2.14 on 2022-10-27 02:53
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0110_auto_20221021_1506'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='GatherAccountsAutomation',
|
||||
fields=[
|
||||
('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Gather asset accounts',
|
||||
},
|
||||
bases=('assets.baseautomation',),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='baseautomation',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('ping', 'Ping'), ('gather_facts', 'Gather facts'), ('push_account', 'Create account'), ('change_secret', 'Change secret'), ('verify_account', 'Verify account'), ('gather_accounts', 'Gather accounts')], max_length=16, verbose_name='Type'),
|
||||
),
|
||||
]
|
|
@ -3,3 +3,4 @@ from .discovery_account import *
|
|||
from .push_account import *
|
||||
from .verify_secret import *
|
||||
from .gather_facts import *
|
||||
from .gather_accounts import *
|
||||
|
|
|
@ -10,6 +10,7 @@ from orgs.mixins.models import OrgModelMixin
|
|||
from ops.mixin import PeriodTaskModelMixin
|
||||
from assets.models import Node, Asset
|
||||
from assets.tasks import execute_automation
|
||||
from assets.const import AutomationTypes
|
||||
|
||||
|
||||
class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
|
||||
|
@ -20,7 +21,7 @@ class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
|
|||
assets = models.ManyToManyField(
|
||||
'assets.Asset', blank=True, verbose_name=_("Assets")
|
||||
)
|
||||
type = models.CharField(max_length=16, verbose_name=_('Type'))
|
||||
type = models.CharField(max_length=16, choices=AutomationTypes.choices, verbose_name=_('Type'))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from assets.const import AutomationTypes
|
||||
from .base import BaseAutomation
|
||||
|
||||
__all__ = ['GatherAccountsAutomation']
|
||||
|
||||
|
||||
class GatherAccountsAutomation(BaseAutomation):
|
||||
def save(self, *args, **kwargs):
|
||||
self.type = AutomationTypes.gather_accounts
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Gather asset accounts")
|
|
@ -106,7 +106,7 @@ class JMSInventory:
|
|||
'jms_asset': {
|
||||
'id': str(asset.id), 'name': asset.name, 'address': asset.address,
|
||||
'type': asset.type, 'category': asset.category,
|
||||
'protocol': asset.protocol, 'port': asset.port,
|
||||
'protocol': asset.protocol, 'port': asset.port,'database': '',
|
||||
'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
|
||||
},
|
||||
'jms_account': {
|
||||
|
@ -117,6 +117,10 @@ class JMSInventory:
|
|||
ansible_config = dict(automation.ansible_config)
|
||||
ansible_connection = ansible_config.get('ansible_connection', 'ssh')
|
||||
host.update(ansible_config)
|
||||
|
||||
if platform.category == 'database':
|
||||
host['jms_asset']['database'] = asset.database.db_name
|
||||
|
||||
gateway = None
|
||||
if asset.domain:
|
||||
gateway = asset.domain.select_gateway()
|
||||
|
|
Loading…
Reference in New Issue