jumpserver/apps/ops/ansible/inventory.py

232 lines
8.8 KiB
Python
Raw Normal View History

2017-03-05 12:53:24 +00:00
# ~*~ coding: utf-8 ~*~
2022-09-29 12:44:45 +00:00
import json
2022-10-08 11:12:04 +00:00
import os
2022-10-09 12:54:11 +00:00
from collections import defaultdict
2022-09-29 12:44:45 +00:00
2022-10-08 11:51:29 +00:00
from django.utils.translation import gettext as _
2022-10-08 08:55:14 +00:00
__all__ = ['JMSInventory']
2017-12-10 16:29:25 +00:00
class JMSInventory:
2022-11-01 03:52:51 +00:00
def __init__(self, assets, account_policy='privileged_first',
2023-02-13 11:22:52 +00:00
account_prefer='root,Administrator', host_callback=None, exclude_localhost=False):
"""
:param assets:
2022-10-14 08:33:24 +00:00
:param account_prefer: account username name if not set use account_policy
2022-11-01 03:52:51 +00:00
:param account_policy: privileged_only, privileged_first, skip
"""
2022-09-29 12:44:45 +00:00
self.assets = self.clean_assets(assets)
self.account_prefer = self.get_account_prefer(account_prefer)
2022-09-29 12:44:45 +00:00
self.account_policy = account_policy
2022-10-28 08:25:16 +00:00
self.host_callback = host_callback
self.exclude_hosts = {}
2023-02-13 11:22:52 +00:00
self.exclude_localhost = exclude_localhost
2022-09-29 12:44:45 +00:00
@staticmethod
def clean_assets(assets):
from assets.models import Asset
asset_ids = [asset.id for asset in assets]
2022-10-20 12:34:15 +00:00
assets = Asset.objects.filter(id__in=asset_ids, is_active=True) \
2022-09-29 12:44:45 +00:00
.prefetch_related('platform', 'domain', 'accounts')
return assets
@staticmethod
def group_by_platform(assets):
groups = defaultdict(list)
for asset in assets:
groups[asset.platform].append(asset)
return groups
@staticmethod
def make_proxy_command(gateway):
proxy_command_list = [
"ssh", "-o", "Port={}".format(gateway.port),
"-o", "StrictHostKeyChecking=no",
"{}@{}".format(gateway.username, gateway.address),
"-W", "%h:%p", "-q",
]
if gateway.password:
proxy_command_list.insert(
2023-03-17 08:57:40 +00:00
0, "sshpass -p {}".format(gateway.password)
2022-09-29 12:44:45 +00:00
)
if gateway.private_key:
proxy_command_list.append("-i {}".format(gateway.private_key_path))
2022-09-29 12:44:45 +00:00
2023-03-17 08:57:40 +00:00
proxy_command = "-o ProxyCommand='{}'".format(
2022-09-29 12:44:45 +00:00
" ".join(proxy_command_list)
)
return {"ansible_ssh_common_args": proxy_command}
2022-10-14 08:33:24 +00:00
@staticmethod
def make_account_ansible_vars(account):
var = {
'ansible_user': account.username,
}
if not account.secret:
return var
if account.secret_type == 'password':
var['ansible_password'] = account.secret
elif account.secret_type == 'ssh_key':
var['ansible_ssh_private_key_file'] = account.private_key_path
2022-10-14 08:33:24 +00:00
return var
def make_ssh_account_vars(self, host, asset, account, automation, protocols, platform, gateway):
if not account:
host['error'] = _("No account available")
return host
ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols))
ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None
host['ansible_host'] = asset.address
2022-10-28 10:19:44 +00:00
host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22
2022-10-14 08:33:24 +00:00
su_from = account.su_from
if platform.su_enabled and su_from:
host.update(self.make_account_ansible_vars(su_from))
become_method = 'sudo' if platform.su_method != 'su' else 'su'
host['ansible_become'] = True
host['ansible_become_method'] = 'sudo'
host['ansible_become_user'] = account.username
if become_method == 'sudo':
host['ansible_become_password'] = su_from.secret
else:
host['ansible_become_password'] = account.secret
else:
host.update(self.make_account_ansible_vars(account))
if gateway:
host.update(self.make_proxy_command(gateway))
2022-10-13 09:47:29 +00:00
def asset_to_host(self, asset, account, automation, protocols, platform):
2022-10-09 12:54:11 +00:00
host = {
2023-02-21 05:00:04 +00:00
'name': '{}'.format(asset.name.replace(' ', '_')),
2022-10-10 12:56:13 +00:00
'jms_asset': {
2022-10-10 05:56:42 +00:00
'id': str(asset.id), 'name': asset.name, 'address': asset.address,
2022-10-09 12:54:11 +00:00
'type': asset.type, 'category': asset.category,
2022-10-28 08:25:16 +00:00
'protocol': asset.protocol, 'port': asset.port,
'spec_info': asset.spec_info, 'secret_info': asset.secret_info,
2022-10-09 12:54:11 +00:00
'protocols': [{'name': p.name, 'port': p.port} for p in protocols],
},
2022-10-20 12:34:15 +00:00
'jms_account': {
2022-10-10 12:56:13 +00:00
'id': str(account.id), 'username': account.username,
'secret': account.secret, 'secret_type': account.secret_type
} if account else None
2022-10-09 12:54:11 +00:00
}
if host['jms_account'] and asset.platform.type == 'oracle':
host['jms_account']['mode'] = 'sysdba' if account.privileged else None
2023-02-13 07:04:45 +00:00
try:
ansible_config = dict(automation.ansible_config)
except Exception as e:
ansible_config = {}
2022-10-14 08:33:24 +00:00
ansible_connection = ansible_config.get('ansible_connection', 'ssh')
2022-10-13 09:47:29 +00:00
host.update(ansible_config)
2022-10-27 10:53:10 +00:00
2022-09-29 12:44:45 +00:00
gateway = None
if not asset.is_gateway and asset.domain:
2022-09-29 12:44:45 +00:00
gateway = asset.domain.select_gateway()
if ansible_connection == 'local':
if gateway:
2023-02-21 12:27:44 +00:00
host['gateway'] = {
'address': gateway.address, 'port': gateway.port,
'username': gateway.username, 'secret': gateway.password
}
2022-09-29 12:44:45 +00:00
else:
2022-10-14 08:33:24 +00:00
self.make_ssh_account_vars(host, asset, account, automation, protocols, platform, gateway)
2022-09-29 12:44:45 +00:00
return host
2023-03-16 11:06:50 +00:00
def get_asset_sorted_accounts(self, asset):
accounts = list(asset.accounts.filter(is_active=True))
connectivity_score = {'ok': 2, '-': 1, 'err': 0}
sort_key = lambda x: (x.privileged, connectivity_score.get(x.connectivity, 0), x.date_updated)
accounts_sorted = sorted(accounts, key=sort_key, reverse=True)
return accounts_sorted
@staticmethod
def get_account_prefer(account_prefer):
account_usernames = []
if isinstance(account_prefer, str) and account_prefer:
account_usernames = list(map(lambda x: x.lower(), account_prefer.split(',')))
return account_usernames
def get_refer_account(self, accounts):
account = None
if accounts:
account = list(filter(
lambda a: a.username.lower() in self.account_prefer, accounts
))
account = account[0] if account else None
return account
2022-09-29 12:44:45 +00:00
def select_account(self, asset):
2023-03-16 11:06:50 +00:00
accounts = self.get_asset_sorted_accounts(asset)
if not accounts:
return None
2022-10-08 11:51:29 +00:00
2023-03-16 11:06:50 +00:00
refer_account = self.get_refer_account(accounts)
if refer_account:
return refer_account
2022-09-29 12:44:45 +00:00
2023-03-16 11:06:50 +00:00
account_selected = accounts[0]
if self.account_policy == 'skip':
return None
elif self.account_policy == 'privileged_first':
2022-11-01 03:52:51 +00:00
return account_selected
2023-03-16 11:06:50 +00:00
elif self.account_policy == 'privileged_only' and account_selected.privileged:
return account_selected
else:
return None
2022-09-29 12:44:45 +00:00
2022-10-20 12:34:15 +00:00
def generate(self, path_dir):
2022-09-29 12:44:45 +00:00
hosts = []
platform_assets = self.group_by_platform(self.assets)
for platform, assets in platform_assets.items():
automation = platform.automation
2022-10-13 09:47:29 +00:00
for asset in assets:
2022-10-28 10:28:41 +00:00
protocols = asset.protocols.all()
2022-09-29 12:44:45 +00:00
account = self.select_account(asset)
2022-10-13 09:47:29 +00:00
host = self.asset_to_host(asset, account, automation, protocols, platform)
2022-10-12 10:08:57 +00:00
2022-10-08 11:51:29 +00:00
if not automation.ansible_enabled:
2022-10-12 10:08:57 +00:00
host['error'] = _('Ansible disabled')
2022-10-28 08:25:16 +00:00
if self.host_callback is not None:
host = self.host_callback(
2022-10-12 10:08:57 +00:00
host, asset=asset, account=account,
2022-10-20 12:34:15 +00:00
platform=platform, automation=automation,
path_dir=path_dir
2022-10-12 10:08:57 +00:00
)
if isinstance(host, list):
hosts.extend(host)
2022-10-09 12:54:11 +00:00
else:
hosts.append(host)
2022-09-29 12:44:45 +00:00
2022-10-17 03:22:21 +00:00
exclude_hosts = list(filter(lambda x: x.get('error'), hosts))
2022-10-08 11:51:29 +00:00
if exclude_hosts:
print(_("Skip hosts below:"))
2022-10-10 05:56:42 +00:00
for i, host in enumerate(exclude_hosts, start=1):
2022-10-12 10:08:57 +00:00
print("{}: [{}] \t{}".format(i, host['name'], host['error']))
self.exclude_hosts[host['name']] = host['error']
2022-10-17 03:22:21 +00:00
hosts = list(filter(lambda x: not x.get('error'), hosts))
2022-09-29 12:44:45 +00:00
data = {'all': {'hosts': {}}}
for host in hosts:
name = host.pop('name')
data['all']['hosts'][name] = host
2023-02-13 11:22:52 +00:00
if self.exclude_localhost and data['all']['hosts'].__contains__('localhost'):
data['all']['hosts'].update({'localhost': {'ansible_host': '255.255.255.255'}})
2022-10-08 11:12:04 +00:00
return data
def write_to_file(self, path):
path_dir = os.path.dirname(path)
if not os.path.exists(path_dir):
os.makedirs(path_dir, 0o700, True)
2022-10-24 12:24:56 +00:00
data = self.generate(path_dir)
2022-09-29 12:44:45 +00:00
with open(path, 'w') as f:
f.write(json.dumps(data, indent=4))