mirror of https://github.com/jumpserver/jumpserver
perf: Custom secret change
parent
a9433bc48e
commit
c7eb170942
|
@ -97,7 +97,7 @@ def main():
|
|||
msg='No command found, please go to the platform details to add'
|
||||
)
|
||||
with SSHClient(module) as client:
|
||||
output, err_msg = client.execute(commands, answers)
|
||||
__, err_msg = client.execute(commands, answers)
|
||||
if err_msg:
|
||||
module.fail_json(
|
||||
msg='There was a problem executing the command: %s' % err_msg
|
||||
|
|
|
@ -116,7 +116,7 @@ def main():
|
|||
|
||||
try:
|
||||
client.close()
|
||||
except Exception:
|
||||
except Exception: # noqa
|
||||
pass
|
||||
|
||||
return module.exit_json(**result)
|
||||
|
|
|
@ -7,14 +7,44 @@ __metaclass__ = type
|
|||
DOCUMENTATION = '''
|
||||
---
|
||||
module: custom_rdp_ping
|
||||
short_description: Use rdp to probe whether an asset is connectable
|
||||
short_description: Use RDP to probe whether an asset is connectable.
|
||||
description:
|
||||
- Use rdp to probe whether an asset is connectable
|
||||
- Use RDP to probe whether an asset is connectable.
|
||||
options:
|
||||
login_host:
|
||||
description: Target host to connect.
|
||||
type: str
|
||||
required: False
|
||||
default: localhost
|
||||
login_port:
|
||||
description: Target port to connect.
|
||||
type: int
|
||||
required: False
|
||||
default: 22
|
||||
login_user:
|
||||
description: Login user for the connection.
|
||||
type: str
|
||||
required: False
|
||||
default: root
|
||||
login_password:
|
||||
description: Login password.
|
||||
type: str
|
||||
required: False
|
||||
no_log: True
|
||||
login_secret_type:
|
||||
description: Authentication method.
|
||||
type: str
|
||||
required: False
|
||||
default: password
|
||||
gateway_args:
|
||||
description: Arguments for setting up an SSH tunnel.
|
||||
type: dict
|
||||
required: False
|
||||
default: null
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: >
|
||||
Ping asset server.
|
||||
- name: Ping asset server using RDP.
|
||||
custom_rdp_ping:
|
||||
login_host: 127.0.0.1
|
||||
login_port: 3389
|
||||
|
@ -24,12 +54,12 @@ EXAMPLES = '''
|
|||
|
||||
RETURN = '''
|
||||
is_available:
|
||||
description: Windows server availability.
|
||||
description: Indicates if the Windows asset is available.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
conn_err_msg:
|
||||
description: Connection error message.
|
||||
description: Connection error message (if any).
|
||||
returned: always
|
||||
type: str
|
||||
sample: ''
|
||||
|
@ -41,11 +71,6 @@ from sshtunnel import SSHTunnelForwarder
|
|||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
# =========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
|
||||
def common_argument_spec():
|
||||
options = dict(
|
||||
login_host=dict(type='str', required=False, default='localhost'),
|
||||
|
@ -67,13 +92,12 @@ class RDPConnectionManager:
|
|||
self.result_queue = multiprocessing.Queue()
|
||||
|
||||
def build_connection_details(self):
|
||||
connection_details = {
|
||||
return {
|
||||
'hostname': self.params['login_host'],
|
||||
'port': self.params['login_port'],
|
||||
'username': self.params['username'],
|
||||
'password': self.params['password']
|
||||
'username': self.params['login_user'],
|
||||
'password': self.params['login_password']
|
||||
}
|
||||
return connection_details
|
||||
|
||||
def setup_ssh_tunnel(self):
|
||||
gateway_args = self.params['gateway_args'] or {}
|
||||
|
@ -90,8 +114,8 @@ class RDPConnectionManager:
|
|||
self.connection_details['port']
|
||||
)
|
||||
)
|
||||
|
||||
tunnel.start()
|
||||
|
||||
self.connection_details['hostname'] = '127.0.0.1'
|
||||
self.connection_details['port'] = tunnel.local_bind_port
|
||||
self.ssh_tunnel = tunnel
|
||||
|
@ -107,13 +131,23 @@ class RDPConnectionManager:
|
|||
self.close_ssh_tunnel()
|
||||
|
||||
def check_rdp_connectivity(self):
|
||||
connect_params = list(self.connection_details.values()) + ['', 0]
|
||||
is_reachable = pyfreerdp.check_connectivity(*connect_params)
|
||||
connect_params = [
|
||||
self.connection_details['hostname'],
|
||||
self.connection_details['port'],
|
||||
self.connection_details['username'],
|
||||
self.connection_details['password'],
|
||||
'', # extra parameter (if needed)
|
||||
0 # timeout (if needed)
|
||||
]
|
||||
try:
|
||||
is_reachable = pyfreerdp.check_connectivity(*connect_params)
|
||||
except Exception as ex:
|
||||
is_reachable = False
|
||||
self.result_queue.put(is_reachable)
|
||||
|
||||
def attempt_connection(self):
|
||||
if self.params['login_secret_type'] != 'password':
|
||||
error_message = f'unsupported authentication method: {self.params["login_secret_type"]}'
|
||||
error_message = f"Unsupported authentication method: {self.params['login_secret_type']}"
|
||||
return False, error_message
|
||||
|
||||
try:
|
||||
|
@ -138,17 +172,21 @@ class RDPConnectionManager:
|
|||
def main():
|
||||
argument_spec = common_argument_spec()
|
||||
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
|
||||
result = {'changed': False}
|
||||
module_params = module.params
|
||||
rdp_manager = RDPConnectionManager(module_params)
|
||||
rdp_manager = RDPConnectionManager(module.params)
|
||||
is_available, error_message = rdp_manager.attempt_connection()
|
||||
result['is_available'] = is_available
|
||||
|
||||
# Prepare the result structure.
|
||||
result = {
|
||||
'changed': False,
|
||||
'is_available': is_available,
|
||||
'conn_err_msg': error_message
|
||||
}
|
||||
|
||||
if not is_available:
|
||||
module.fail_json(msg=f'Unable to connect to asset: {error_message}')
|
||||
|
||||
return module.exit_json(**result)
|
||||
module.fail_json(msg=f"Unable to connect to asset: {error_message}", **result)
|
||||
else:
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
|
|
@ -4,18 +4,35 @@ from __future__ import absolute_import, division, print_function
|
|||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ssh_ping
|
||||
short_description: Use ssh to probe whether an asset is connectable
|
||||
description:
|
||||
- Use ssh to probe whether an asset is connectable
|
||||
- Use ssh to probe whether an asset is connectable.
|
||||
options:
|
||||
login_host:
|
||||
description: The target host to connect.
|
||||
type: str
|
||||
required: True
|
||||
login_port:
|
||||
description: The port on the target host.
|
||||
type: int
|
||||
required: False
|
||||
default: 22
|
||||
login_user:
|
||||
description: The username for the SSH connection.
|
||||
type: str
|
||||
required: True
|
||||
login_password:
|
||||
description: The password for the SSH connection.
|
||||
type: str
|
||||
required: True
|
||||
no_log: True
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: >
|
||||
Ping asset server.
|
||||
- name: Ping asset server using SSH.
|
||||
ssh_ping:
|
||||
login_host: 127.0.0.1
|
||||
login_port: 22
|
||||
|
@ -25,36 +42,27 @@ EXAMPLES = '''
|
|||
|
||||
RETURN = '''
|
||||
is_available:
|
||||
description: Ping server availability.
|
||||
description: Indicate whether the target server is reachable via SSH.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
'''
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from libs.ansible.modules_utils.remote_client import (
|
||||
SSHClient, common_argument_spec
|
||||
)
|
||||
|
||||
|
||||
# =========================================
|
||||
# Module execution.
|
||||
#
|
||||
from libs.ansible.modules_utils.remote_client import SSHClient, common_argument_spec
|
||||
|
||||
|
||||
def main():
|
||||
options = common_argument_spec()
|
||||
module = AnsibleModule(argument_spec=options, supports_check_mode=True,)
|
||||
module = AnsibleModule(argument_spec=options, supports_check_mode=True)
|
||||
|
||||
result = {'changed': False, 'is_available': False}
|
||||
|
||||
result = {
|
||||
'changed': False, 'is_available': False
|
||||
}
|
||||
with SSHClient(module) as client:
|
||||
client.connect()
|
||||
|
||||
result['is_available'] = True
|
||||
return module.exit_json(**result)
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -1,55 +1,25 @@
|
|||
import re
|
||||
import signal
|
||||
import time
|
||||
|
||||
import paramiko
|
||||
|
||||
from functools import wraps
|
||||
|
||||
import paramiko
|
||||
from sshtunnel import SSHTunnelForwarder
|
||||
|
||||
|
||||
DEFAULT_RE = '.*'
|
||||
SU_PROMPT_LOCALIZATIONS = [
|
||||
'Password',
|
||||
'암호',
|
||||
'パスワード',
|
||||
'Adgangskode',
|
||||
'Contraseña',
|
||||
'Contrasenya',
|
||||
'Hasło',
|
||||
'Heslo',
|
||||
'Jelszó',
|
||||
'Lösenord',
|
||||
'Mật khẩu',
|
||||
'Mot de passe',
|
||||
'Parola',
|
||||
'Parool',
|
||||
'Pasahitza',
|
||||
'Passord',
|
||||
'Passwort',
|
||||
'Salasana',
|
||||
'Sandi',
|
||||
'Senha',
|
||||
'Wachtwoord',
|
||||
'ססמה',
|
||||
'Лозинка',
|
||||
'Парола',
|
||||
'Пароль',
|
||||
'गुप्तशब्द',
|
||||
'शब्दकूट',
|
||||
'సంకేతపదము',
|
||||
'හස්පදය',
|
||||
'密码',
|
||||
'密碼',
|
||||
'口令',
|
||||
]
|
||||
'Password', '암호', 'パスワード', 'Adgangskode', 'Contraseña', 'Contrasenya',
|
||||
'Hasło', 'Heslo', 'Jelszó', 'Lösenord', 'Mật khẩu', 'Mot de passe',
|
||||
'Parola', 'Parool', 'Pasahitza', 'Passord', 'Passwort', 'Salasana',
|
||||
'Sandi', 'Senha', 'Wachtwoord', 'ססמה', 'Лозинка', 'Парола', 'Пароль',
|
||||
'गुप्तशब्द', 'शब्दकूट', 'సంకేతపదము', 'හස්පදය', '密码', '密碼', '口令',
|
||||
]
|
||||
|
||||
|
||||
def get_become_prompt_re():
|
||||
b_password_string = "|".join((r'(\w+\'s )?' + p) for p in SU_PROMPT_LOCALIZATIONS)
|
||||
b_password_string = b_password_string + ' ?(:|:) ?'
|
||||
return re.compile(b_password_string, flags=re.IGNORECASE)
|
||||
pattern_segments = (r'(\w+\'s )?' + p for p in SU_PROMPT_LOCALIZATIONS)
|
||||
prompt_pattern = "|".join(pattern_segments) + r' ?(:|:) ?'
|
||||
return re.compile(prompt_pattern, flags=re.IGNORECASE)
|
||||
|
||||
|
||||
become_prompt_re = get_become_prompt_re()
|
||||
|
@ -88,8 +58,8 @@ def raise_timeout(name=''):
|
|||
def handler(signum, frame):
|
||||
raise TimeoutError(f'{name} timed out, wait {timeout}s')
|
||||
|
||||
timeout = getattr(self, 'timeout', 0)
|
||||
try:
|
||||
timeout = getattr(self, 'timeout', 0)
|
||||
if timeout > 0:
|
||||
signal.signal(signal.SIGALRM, handler)
|
||||
signal.alarm(timeout)
|
||||
|
@ -97,7 +67,9 @@ def raise_timeout(name=''):
|
|||
except Exception as error:
|
||||
signal.alarm(0)
|
||||
raise error
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorate
|
||||
|
||||
|
||||
|
@ -122,8 +94,8 @@ class SSHClient:
|
|||
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
self.connect_params = self.get_connect_params()
|
||||
self._channel = None
|
||||
|
||||
self.buffer_size = 1024
|
||||
self.connect_params = self.get_connect_params()
|
||||
self.prompt = self.module.params['prompt']
|
||||
self.timeout = self.module.params['recv_timeout']
|
||||
|
||||
|
@ -134,45 +106,54 @@ class SSHClient:
|
|||
return self._channel
|
||||
|
||||
def get_connect_params(self):
|
||||
p = self.module.params
|
||||
params = {
|
||||
'allow_agent': False, 'look_for_keys': False,
|
||||
'hostname': self.module.params['login_host'],
|
||||
'port': self.module.params['login_port'],
|
||||
'key_filename': self.module.params['login_private_key_path'] or None
|
||||
'allow_agent': False,
|
||||
'look_for_keys': False,
|
||||
'hostname': p['login_host'],
|
||||
'port': p['login_port'],
|
||||
'key_filename': p['login_private_key_path'] or None
|
||||
}
|
||||
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
|
||||
|
||||
if p['become']:
|
||||
params['username'] = p['become_user']
|
||||
params['password'] = p['become_password']
|
||||
params['key_filename'] = p['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
|
||||
if self.module.params['old_ssh_version']:
|
||||
params['username'] = p['login_user']
|
||||
params['password'] = p['login_password']
|
||||
params['key_filename'] = p['login_private_key_path'] or None
|
||||
|
||||
if p['old_ssh_version']:
|
||||
params['transport_factory'] = OldSSHTransport
|
||||
|
||||
return params
|
||||
|
||||
def switch_user(self):
|
||||
if not self.module.params['become']:
|
||||
return
|
||||
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)
|
||||
p = self.module.params
|
||||
if not p['become']:
|
||||
return
|
||||
|
||||
__, e_msg = self.execute(
|
||||
[f'{switch_method} {username}', password, 'whoami'],
|
||||
method = p['become_method']
|
||||
username = p['login_user']
|
||||
|
||||
if method == 'sudo':
|
||||
switch_cmd = 'sudo su -'
|
||||
pword = p['become_password']
|
||||
elif method == 'su':
|
||||
switch_cmd = 'su -'
|
||||
pword = p['login_password']
|
||||
else:
|
||||
self.module.fail_json(msg=f'Become method {method} not supported.')
|
||||
return
|
||||
|
||||
# Expected to see a prompt, type the password, and check the username
|
||||
output, error = self.execute(
|
||||
[f'{switch_cmd} {username}', pword, 'whoami'],
|
||||
[become_prompt_re, DEFAULT_RE, username]
|
||||
)
|
||||
if e_msg:
|
||||
self.module.fail_json(msg='Become user %s failed.' % username)
|
||||
if error:
|
||||
self.module.fail_json(msg=f'Failed to become user {username}. Output: {output}')
|
||||
|
||||
def connect(self):
|
||||
self.before_runner_start()
|
||||
|
@ -193,28 +174,32 @@ class SSHClient:
|
|||
return answers
|
||||
|
||||
@staticmethod
|
||||
def __match(re_, content):
|
||||
re_pattern = re_
|
||||
if isinstance(re_, str):
|
||||
re_pattern = re.compile(re_, re.DOTALL | re.IGNORECASE)
|
||||
elif not isinstance(re_pattern, re.Pattern):
|
||||
raise ValueError(f'{re_} should be a regular expression')
|
||||
return bool(re_pattern.search(content))
|
||||
def __match(expression, content):
|
||||
if isinstance(expression, str):
|
||||
expression = re.compile(expression, re.DOTALL | re.IGNORECASE)
|
||||
elif not isinstance(expression, re.Pattern):
|
||||
raise ValueError(f'{expression} should be a regular expression')
|
||||
|
||||
return bool(expression.search(content))
|
||||
|
||||
@raise_timeout('Recv message')
|
||||
def _get_match_recv(self, answer_reg=DEFAULT_RE):
|
||||
last_output, output = '', ''
|
||||
buffer_str = ''
|
||||
prev_str = ''
|
||||
|
||||
check_reg = self.prompt if answer_reg == DEFAULT_RE else answer_reg
|
||||
while True:
|
||||
if self.channel.recv_ready():
|
||||
recv = self.channel.recv(self.buffer_size).decode()
|
||||
output += recv
|
||||
if output and last_output != output:
|
||||
fin_reg = self.prompt if answer_reg == DEFAULT_RE else answer_reg
|
||||
if self.__match(fin_reg, output):
|
||||
chunk = self.channel.recv(self.buffer_size).decode('utf-8', 'replace')
|
||||
buffer_str += chunk
|
||||
|
||||
if buffer_str and buffer_str != prev_str:
|
||||
if self.__match(check_reg, buffer_str):
|
||||
break
|
||||
last_output = output
|
||||
prev_str = buffer_str
|
||||
time.sleep(0.01)
|
||||
return output
|
||||
|
||||
return buffer_str
|
||||
|
||||
@raise_timeout('Wait send message')
|
||||
def _check_send(self):
|
||||
|
@ -223,38 +208,44 @@ class SSHClient:
|
|||
time.sleep(self.module.params['delay_time'])
|
||||
|
||||
def execute(self, commands, answers=None):
|
||||
all_output, error_msg = '', ''
|
||||
combined_output = ''
|
||||
error_msg = ''
|
||||
|
||||
try:
|
||||
answers = self._fit_answers(commands, answers)
|
||||
for index, command in enumerate(commands):
|
||||
for cmd, ans_regex in zip(commands, answers):
|
||||
self._check_send()
|
||||
self.channel.send(command + '\n')
|
||||
all_output += f'{self._get_match_recv(answers[index])}\n'
|
||||
self.channel.send(cmd + '\n')
|
||||
combined_output += self._get_match_recv(ans_regex) + '\n'
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
return all_output, error_msg
|
||||
|
||||
return combined_output, error_msg
|
||||
|
||||
def local_gateway_prepare(self):
|
||||
gateway_args = self.module.params['gateway_args'] or ''
|
||||
pattern = r"(?:sshpass -p ([^ ]+))?\s*ssh -o Port=(\d+)\s+-o StrictHostKeyChecking=no\s+([\w@]+)@([" \
|
||||
r"\d.]+)\s+-W %h:%p -q(?: -i (.+))?'"
|
||||
pattern = (
|
||||
r"(?:sshpass -p ([^ ]+))?\s*ssh -o Port=(\d+)\s+-o StrictHostKeyChecking=no\s+"
|
||||
r"([\w@]+)@([\d.]+)\s+-W %h:%p -q(?: -i (.+))?'"
|
||||
)
|
||||
match = re.search(pattern, gateway_args)
|
||||
|
||||
if not match:
|
||||
return
|
||||
|
||||
password, port, username, address, private_key_path = match.groups()
|
||||
password = password if password else None
|
||||
private_key_path = private_key_path if private_key_path else None
|
||||
remote_hostname = self.module.params['login_host']
|
||||
remote_port = self.module.params['login_port']
|
||||
password, port, username, remote_addr, key_path = match.groups()
|
||||
password = password or None
|
||||
key_path = key_path or None
|
||||
|
||||
server = SSHTunnelForwarder(
|
||||
(address, int(port)),
|
||||
(remote_addr, int(port)),
|
||||
ssh_username=username,
|
||||
ssh_password=password,
|
||||
ssh_pkey=private_key_path,
|
||||
remote_bind_address=(remote_hostname, remote_port)
|
||||
ssh_pkey=key_path,
|
||||
remote_bind_address=(
|
||||
self.module.params['login_host'],
|
||||
self.module.params['login_port']
|
||||
)
|
||||
)
|
||||
|
||||
server.start()
|
||||
|
@ -263,11 +254,8 @@ class SSHClient:
|
|||
self.gateway_server = server
|
||||
|
||||
def local_gateway_clean(self):
|
||||
gateway_server = self.gateway_server
|
||||
if not gateway_server:
|
||||
return
|
||||
|
||||
gateway_server.stop()
|
||||
if self.gateway_server:
|
||||
self.gateway_server.stop()
|
||||
|
||||
def before_runner_start(self):
|
||||
self.local_gateway_prepare()
|
||||
|
|
Loading…
Reference in New Issue