mirror of https://github.com/jumpserver/jumpserver
feat: 支持 ansible receptor private 方式认证, 支持运行完成工作空间清理
parent
fa5d9d3df4
commit
574639d5e1
|
@ -137,16 +137,13 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
|
|||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def private_key_path(self):
|
||||
def get_private_key_path(self, path):
|
||||
if self.secret_type != SecretType.SSH_KEY \
|
||||
or not self.secret \
|
||||
or not self.private_key:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
key_path = os.path.join(path, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
# https://github.com/ansible/ansible-runner/issues/544
|
||||
# ssh requires OpenSSH format keys to have a full ending newline.
|
||||
|
@ -158,6 +155,12 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
|
|||
os.chmod(key_path, 0o400)
|
||||
return key_path
|
||||
|
||||
@property
|
||||
def private_key_path(self):
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
return self.get_private_key_path(tmp_dir)
|
||||
|
||||
def get_private_key(self):
|
||||
if not self.private_key:
|
||||
return None
|
||||
|
|
|
@ -73,3 +73,7 @@ class Gateway(Host):
|
|||
def private_key_path(self):
|
||||
account = self.select_account
|
||||
return account.private_key_path if account else None
|
||||
|
||||
def get_private_key_path(self, path):
|
||||
account = self.select_account
|
||||
return account.get_private_key_path(path) if account else None
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import os
|
||||
import shutil
|
||||
from functools import wraps
|
||||
|
||||
from settings.api import settings
|
||||
|
||||
|
||||
def cleanup_post_run(func):
|
||||
def get_instance(*args):
|
||||
if not len(args) > 0:
|
||||
return
|
||||
return args[0]
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
instance = get_instance(*args)
|
||||
if not instance or not issubclass(type(instance), WorkPostRunCleaner):
|
||||
raise NotImplementedError("you should extend 'WorkPostRunCleaner'")
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
instance.clean_post_run()
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class WorkPostRunCleaner:
|
||||
@property
|
||||
def clean_dir(self):
|
||||
raise NotImplemented
|
||||
|
||||
def clean_post_run(self):
|
||||
if settings.DEBUG_DEV:
|
||||
return
|
||||
if self.clean_dir and os.path.exists(self.clean_dir):
|
||||
shutil.rmtree(self.clean_dir)
|
|
@ -45,7 +45,7 @@ class JMSInventory:
|
|||
return groups
|
||||
|
||||
@staticmethod
|
||||
def make_proxy_command(gateway):
|
||||
def make_proxy_command(gateway, path_dir):
|
||||
proxy_command_list = [
|
||||
"ssh", "-o", "Port={}".format(gateway.port),
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
|
@ -58,7 +58,7 @@ class JMSInventory:
|
|||
0, "sshpass -p {}".format(gateway.password)
|
||||
)
|
||||
if gateway.private_key:
|
||||
proxy_command_list.append("-i {}".format(gateway.private_key_path))
|
||||
proxy_command_list.append("-i {}".format(gateway.get_private_key_path(path_dir)))
|
||||
|
||||
proxy_command = "-o ProxyCommand='{}'".format(
|
||||
" ".join(proxy_command_list)
|
||||
|
@ -66,7 +66,7 @@ class JMSInventory:
|
|||
return {"ansible_ssh_common_args": proxy_command}
|
||||
|
||||
@staticmethod
|
||||
def make_account_ansible_vars(account):
|
||||
def make_account_ansible_vars(account, path_dir):
|
||||
var = {
|
||||
'ansible_user': account.username,
|
||||
}
|
||||
|
@ -76,18 +76,18 @@ class JMSInventory:
|
|||
if account.secret_type == 'password':
|
||||
var['ansible_password'] = account.escape_jinja2_syntax(account.secret)
|
||||
elif account.secret_type == 'ssh_key':
|
||||
var['ansible_ssh_private_key_file'] = account.private_key_path
|
||||
var['ansible_ssh_private_key_file'] = account.get_private_key_path(path_dir)
|
||||
return var
|
||||
|
||||
@staticmethod
|
||||
def make_custom_become_ansible_vars(account, su_from_auth):
|
||||
def make_custom_become_ansible_vars(account, su_from_auth, path_dir):
|
||||
su_method = su_from_auth['ansible_become_method']
|
||||
var = {
|
||||
'custom_become': True,
|
||||
'custom_become_method': su_method,
|
||||
'custom_become_user': account.su_from.username,
|
||||
'custom_become_password': account.escape_jinja2_syntax(account.su_from.secret),
|
||||
'custom_become_private_key_path': account.su_from.private_key_path
|
||||
'custom_become_private_key_path': account.su_from.get_private_key_path(path_dir)
|
||||
}
|
||||
return var
|
||||
|
||||
|
@ -100,7 +100,7 @@ class JMSInventory:
|
|||
setting = getattr(p, 'setting')
|
||||
host['old_ssh_version'] = setting.get('old_ssh_version', False)
|
||||
|
||||
def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway):
|
||||
def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway, path_dir):
|
||||
from accounts.const import AutomationTypes
|
||||
if not account:
|
||||
host['error'] = _("No account available")
|
||||
|
@ -114,15 +114,15 @@ class JMSInventory:
|
|||
if platform.su_enabled and su_from:
|
||||
su_from_auth = account.get_ansible_become_auth()
|
||||
host.update(su_from_auth)
|
||||
host.update(self.make_custom_become_ansible_vars(account, su_from_auth))
|
||||
host.update(self.make_custom_become_ansible_vars(account, su_from_auth, path_dir))
|
||||
elif platform.su_enabled and not su_from and \
|
||||
self.task_type in (AutomationTypes.change_secret, AutomationTypes.push_account):
|
||||
host.update(self.make_account_ansible_vars(account))
|
||||
host.update(self.make_account_ansible_vars(account, path_dir))
|
||||
host['ansible_become'] = True
|
||||
host['ansible_become_user'] = 'root'
|
||||
host['ansible_become_password'] = account.escape_jinja2_syntax(account.secret)
|
||||
else:
|
||||
host.update(self.make_account_ansible_vars(account))
|
||||
host.update(self.make_account_ansible_vars(account, path_dir))
|
||||
|
||||
if platform.name == 'Huawei':
|
||||
host['ansible_connection'] = 'network_cli'
|
||||
|
@ -134,11 +134,11 @@ class JMSInventory:
|
|||
host['gateway'] = {
|
||||
'address': gateway.address, 'port': gateway.port,
|
||||
'username': gateway.username, 'secret': gateway.password,
|
||||
'private_key_path': gateway.private_key_path
|
||||
'private_key_path': gateway.get_private_key_path(path_dir)
|
||||
}
|
||||
host['jms_asset']['port'] = port
|
||||
else:
|
||||
ansible_ssh_common_args = self.make_proxy_command(gateway)
|
||||
ansible_ssh_common_args = self.make_proxy_command(gateway, path_dir)
|
||||
host['jms_asset'].update(ansible_ssh_common_args)
|
||||
host.update(ansible_ssh_common_args)
|
||||
|
||||
|
@ -168,7 +168,7 @@ class JMSInventory:
|
|||
ansible_config['ansible_winrm_connection_timeout'] = 120
|
||||
return ansible_config
|
||||
|
||||
def asset_to_host(self, asset, account, automation, protocols, platform):
|
||||
def asset_to_host(self, asset, account, automation, protocols, platform, path_dir):
|
||||
try:
|
||||
ansible_config = dict(automation.ansible_config)
|
||||
except (AttributeError, TypeError):
|
||||
|
@ -191,7 +191,7 @@ class JMSInventory:
|
|||
'jms_account': {
|
||||
'id': str(account.id), 'username': account.username,
|
||||
'secret': account.escape_jinja2_syntax(account.secret),
|
||||
'secret_type': account.secret_type, 'private_key_path': account.private_key_path
|
||||
'secret_type': account.secret_type, 'private_key_path': account.get_private_key_path(path_dir)
|
||||
} if account else None
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ class JMSInventory:
|
|||
gateway = asset.domain.select_gateway()
|
||||
|
||||
self.make_account_vars(
|
||||
host, asset, account, automation, protocol, platform, gateway
|
||||
host, asset, account, automation, protocol, platform, gateway, path_dir
|
||||
)
|
||||
return host
|
||||
|
||||
|
@ -274,7 +274,7 @@ class JMSInventory:
|
|||
for asset in assets:
|
||||
protocols = self.set_platform_protocol_setting_to_asset(asset, platform_protocols)
|
||||
account = self.select_account(asset)
|
||||
host = self.asset_to_host(asset, account, automation, protocols, platform)
|
||||
host = self.asset_to_host(asset, account, automation, protocols, platform, path_dir)
|
||||
|
||||
if not automation.ansible_enabled:
|
||||
host['error'] = _('Ansible disabled')
|
||||
|
|
|
@ -2,11 +2,14 @@ import concurrent.futures
|
|||
import os
|
||||
import queue
|
||||
import socket
|
||||
|
||||
from django.conf import settings
|
||||
import ansible_runner
|
||||
from django.utils.functional import LazyObject
|
||||
from receptorctl import ReceptorControl
|
||||
|
||||
from ops.ansible.cleaner import WorkPostRunCleaner, cleanup_post_run
|
||||
|
||||
|
||||
class WarpedReceptorctl(LazyObject):
|
||||
def _setup(self):
|
||||
|
@ -33,7 +36,7 @@ def run(**kwargs):
|
|||
return receptor_runner.run()
|
||||
|
||||
|
||||
class AnsibleReceptorRunner:
|
||||
class AnsibleReceptorRunner(WorkPostRunCleaner):
|
||||
def __init__(self, **kwargs):
|
||||
self.runner_params = kwargs
|
||||
self.unit_id = None
|
||||
|
@ -46,6 +49,11 @@ class AnsibleReceptorRunner:
|
|||
f.write(self.unit_id)
|
||||
f.flush()
|
||||
|
||||
@property
|
||||
def clean_dir(self):
|
||||
return self.runner_params.get("private_data_dir", None)
|
||||
|
||||
@cleanup_post_run
|
||||
def run(self):
|
||||
input, output = socket.socketpair()
|
||||
|
||||
|
|
Loading…
Reference in New Issue