mirror of https://github.com/jumpserver/jumpserver
feat: ssh_ping及custom_command支持sudo及su切换用户 (#11180)
parent
8cfec07faa
commit
ff2aace569
|
@ -2,9 +2,10 @@
|
|||
gather_facts: no
|
||||
vars:
|
||||
ansible_connection: local
|
||||
ansible_become: false
|
||||
|
||||
tasks:
|
||||
- name: Test privileged account
|
||||
- name: Test privileged account (paramiko)
|
||||
ssh_ping:
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
|
@ -12,9 +13,14 @@
|
|||
login_password: "{{ jms_account.secret }}"
|
||||
login_secret_type: "{{ jms_account.secret_type }}"
|
||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
||||
become: "{{ custom_become | default(False) }}"
|
||||
become_method: "{{ custom_become_method | default('su') }}"
|
||||
become_user: "{{ custom_become_user | default('') }}"
|
||||
become_password: "{{ custom_become_password | default('') }}"
|
||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
||||
register: ping_info
|
||||
|
||||
- name: Change asset password
|
||||
- name: Change asset password (paramiko)
|
||||
custom_command:
|
||||
login_user: "{{ jms_account.username }}"
|
||||
login_password: "{{ jms_account.secret }}"
|
||||
|
@ -22,6 +28,11 @@
|
|||
login_port: "{{ jms_asset.port }}"
|
||||
login_secret_type: "{{ jms_account.secret_type }}"
|
||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
||||
become: "{{ custom_become | default(False) }}"
|
||||
become_method: "{{ custom_become_method | default('su') }}"
|
||||
become_user: "{{ custom_become_user | default('') }}"
|
||||
become_password: "{{ custom_become_password | default('') }}"
|
||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
commands: "{{ params.commands }}"
|
||||
|
@ -30,9 +41,10 @@
|
|||
when: ping_info is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
- name: Verify password (paramiko)
|
||||
ssh_ping:
|
||||
login_user: "{{ account.username }}"
|
||||
login_password: "{{ account.secret }}"
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
become: false
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
gather_facts: no
|
||||
vars:
|
||||
ansible_connection: local
|
||||
ansible_become: false
|
||||
|
||||
tasks:
|
||||
- name: Verify account (paramiko)
|
||||
|
@ -12,3 +13,8 @@
|
|||
login_password: "{{ account.secret }}"
|
||||
login_secret_type: "{{ account.secret_type }}"
|
||||
login_private_key_path: "{{ account.private_key_path }}"
|
||||
become: "{{ custom_become | default(False) }}"
|
||||
become_method: "{{ custom_become_method | default('su') }}"
|
||||
become_user: "{{ custom_become_user | default('') }}"
|
||||
become_password: "{{ custom_become_password | default('') }}"
|
||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
gather_facts: no
|
||||
vars:
|
||||
ansible_connection: local
|
||||
ansible_become: false
|
||||
|
||||
tasks:
|
||||
- name: Test asset connection (paramiko)
|
||||
|
@ -12,3 +13,8 @@
|
|||
login_port: "{{ jms_asset.port }}"
|
||||
login_secret_type: "{{ jms_account.secret_type }}"
|
||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
||||
become: "{{ custom_become | default(False) }}"
|
||||
become_method: "{{ custom_become_method | default('su') }}"
|
||||
become_user: "{{ custom_become_user | default('') }}"
|
||||
become_password: "{{ custom_become_password | default('') }}"
|
||||
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
||||
|
|
|
@ -76,6 +76,16 @@ class JMSInventory:
|
|||
var['ansible_ssh_private_key_file'] = account.private_key_path
|
||||
return var
|
||||
|
||||
@staticmethod
|
||||
def make_custom_become_ansible_vars(account, platform):
|
||||
var = {
|
||||
'custom_become': True, 'custom_become_method': platform.su_method,
|
||||
'custom_become_user': account.su_from.username,
|
||||
'custom_become_password': account.su_from.secret,
|
||||
'custom_become_private_key_path': account.su_from.private_key_path
|
||||
}
|
||||
return var
|
||||
|
||||
def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway):
|
||||
from accounts.const import AutomationTypes
|
||||
if not account:
|
||||
|
@ -89,6 +99,7 @@ class JMSInventory:
|
|||
su_from = account.su_from
|
||||
if platform.su_enabled and su_from:
|
||||
host.update(self.make_account_ansible_vars(su_from))
|
||||
host.update(self.make_custom_become_ansible_vars(account, platform))
|
||||
become_method = 'sudo' if platform.su_method != 'su' else 'su'
|
||||
host['ansible_become'] = True
|
||||
host['ansible_become_method'] = 'sudo'
|
||||
|
|
|
@ -90,9 +90,6 @@ def main():
|
|||
name=dict(required=True, aliases=['user']),
|
||||
password=dict(aliases=['pass'], no_log=True),
|
||||
commands=dict(type='list', required=False),
|
||||
first_conn_delay_time=dict(
|
||||
type='float', required=False, default=0.5
|
||||
),
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec)
|
||||
|
||||
|
@ -102,10 +99,10 @@ def main():
|
|||
module.fail_json(
|
||||
msg='No command found, please go to the platform details to add'
|
||||
)
|
||||
err = ssh_client.execute(commands)
|
||||
if err:
|
||||
output, err_msg = ssh_client.execute(commands)
|
||||
if err_msg:
|
||||
module.fail_json(
|
||||
msg='There was a problem executing the command: %s' % err
|
||||
msg='There was a problem executing the command: %s' % err_msg
|
||||
)
|
||||
|
||||
user = module.params['name']
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import time
|
||||
|
||||
import paramiko
|
||||
from paramiko.ssh_exception import SSHException, NoValidConnectionsError
|
||||
|
||||
|
||||
def common_argument_spec():
|
||||
|
@ -12,6 +11,13 @@ def common_argument_spec():
|
|||
login_password=dict(type='str', required=False, no_log=True),
|
||||
login_secret_type=dict(type='str', required=False, default='password'),
|
||||
login_private_key_path=dict(type='str', required=False, no_log=True),
|
||||
first_conn_delay_time=dict(type='float', required=False, default=0.5),
|
||||
|
||||
become=dict(type='bool', default=False, required=False),
|
||||
become_method=dict(type='str', required=False),
|
||||
become_user=dict(type='str', required=False),
|
||||
become_password=dict(type='str', required=False, no_log=True),
|
||||
become_private_key_path=dict(type='str', required=False, no_log=True),
|
||||
)
|
||||
return options
|
||||
|
||||
|
@ -19,6 +25,7 @@ def common_argument_spec():
|
|||
class SSHClient:
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.channel = None
|
||||
self.is_connect = False
|
||||
self.client = paramiko.SSHClient()
|
||||
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
@ -28,40 +35,90 @@ class SSHClient:
|
|||
'allow_agent': False, 'look_for_keys': False,
|
||||
'hostname': self.module.params['login_host'],
|
||||
'port': self.module.params['login_port'],
|
||||
'username': self.module.params['login_user'],
|
||||
'key_filename': self.module.params['login_private_key_path'] or None
|
||||
}
|
||||
secret_type = self.module.params['login_secret_type']
|
||||
if secret_type == 'ssh_key':
|
||||
params['key_filename'] = self.module.params['login_private_key_path']
|
||||
if self.module.params['become']:
|
||||
params['username'] = self.module.params['become_user']
|
||||
params['password'] = self.module.params['become_password']
|
||||
params['key_filename'] = self.module.params['become_private_key_path'] or None
|
||||
else:
|
||||
params['username'] = self.module.params['login_user']
|
||||
params['password'] = self.module.params['login_password']
|
||||
params['key_filename'] = self.module.params['login_private_key_path'] or None
|
||||
return params
|
||||
|
||||
def _get_channel(self):
|
||||
self.channel = self.client.invoke_shell()
|
||||
# 读取首次登陆终端返回的消息
|
||||
self.channel.recv(2048)
|
||||
# 网络设备一般登录有延迟,等终端有返回后再执行命令
|
||||
delay_time = self.module.params['first_conn_delay_time']
|
||||
time.sleep(delay_time)
|
||||
|
||||
@staticmethod
|
||||
def _is_match_user(user, content):
|
||||
# 正常命令切割后是[命令,用户名,交互前缀]
|
||||
remote_user = content.split()[1] if len(content.split()) >= 3 else None
|
||||
return remote_user and remote_user == user
|
||||
|
||||
def switch_user(self):
|
||||
self._get_channel()
|
||||
if not self.module.params['become']:
|
||||
return None
|
||||
method = self.module.params['become_method']
|
||||
username = self.module.params['login_user']
|
||||
if method == 'sudo':
|
||||
switch_method = 'sudo su -'
|
||||
password = self.module.params['become_password']
|
||||
elif method == 'su':
|
||||
switch_method = 'su -'
|
||||
password = self.module.params['login_password']
|
||||
else:
|
||||
self.module.fail_json(msg='Become method %s not support' % method)
|
||||
return
|
||||
commands = [f'{switch_method} {username}', password]
|
||||
su_output, err_msg = self.execute(commands)
|
||||
if err_msg:
|
||||
return err_msg
|
||||
i_output, err_msg = self.execute(['whoami'])
|
||||
if err_msg:
|
||||
return err_msg
|
||||
|
||||
if self._is_match_user(username, i_output):
|
||||
err_msg = ''
|
||||
else:
|
||||
err_msg = su_output
|
||||
return err_msg
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
self.client.connect(**self.get_connect_params())
|
||||
except (SSHException, NoValidConnectionsError) as err:
|
||||
err_msg = str(err)
|
||||
else:
|
||||
self.is_connect = True
|
||||
err_msg = ''
|
||||
err_msg = self.switch_user()
|
||||
except Exception as err:
|
||||
err_msg = str(err)
|
||||
return err_msg
|
||||
|
||||
def _get_recv(self, size=1024, encoding='utf-8'):
|
||||
output = self.channel.recv(size).decode(encoding)
|
||||
return output
|
||||
|
||||
def execute(self, commands):
|
||||
if not self.is_connect:
|
||||
self.connect()
|
||||
|
||||
channel = self.client.invoke_shell()
|
||||
# 读取首次登陆终端返回的消息
|
||||
channel.recv(2048)
|
||||
# 网络设备一般登录有延迟,等终端有返回后再执行命令
|
||||
delay_time = self.module.params['first_conn_delay_time']
|
||||
time.sleep(delay_time)
|
||||
err_msg = ''
|
||||
output, error_msg = '', ''
|
||||
try:
|
||||
for command in commands:
|
||||
channel.send(command + '\n')
|
||||
self.channel.send(command + '\n')
|
||||
time.sleep(0.3)
|
||||
except SSHException as e:
|
||||
err_msg = str(e)
|
||||
return err_msg
|
||||
output = self._get_recv()
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
return output, error_msg
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.channel.close()
|
||||
self.client.close()
|
||||
except:
|
||||
pass
|
||||
|
|
Loading…
Reference in New Issue