zoukankan      html  css  js  c++  java
  • 解决 Jumpserver coco 使用登录用户(ldap)进行SSH连接目标主机,忽略系统用户

    前言

      Jumpserver 作为国内流行的开源堡垒机,很多公司都在尝试使用,同时 Jumpserver 为了契合众多公司的用户认证,也提供了 LDAP 的用户认证方式,作为 Jumpserver 的用户,大家可能知道了 Jumpserver 的 LDAP 认证方式,仅是作为 登录Jumpserver Web UI、登录 Jumpserver 终端(COCO) 的用户认证,进入 Jumpserver 终端(COCO)后,再而跳到目标主机,却需要使用Jumpserver 创建的系统用户,也就是 登录Jumpserver 和 Jumpserver登录目标主机 是需要两个完全没有关系的用户,对于很多基于LDAP用户登录主机的场景,Jumpserver 这种双用户认证概念显得有点鸡肋,既然接入了 LDAP, 我们希望做到 登录Jumpserver 和 Jumpserver跳转主机都使用 LDAP 完成认证登录,带着这一想法,便开始了对 Jumpserver 终端核心 COCO 进行了部分修改。

    COCO前后对比

    注:LDAP 用户登录Jumpserver coco、选择登录主机后,直接使用登录coco 的用户进行登录主机,取消了选择系统用户的步骤。

    详细流程

    注1:用户名密码登录 Jumpserver 时,COCO 处理线程存储用户名密码,用于SSH 连接目标主机;

    注2:公钥登录 Jumpserver 时,COCO 处理线程存储用户名和空密码,SSH 连接目标主机时,根据用户名从COCO本地查找密码,有则使用,无则提示输出密码;

    注3:SSH 连接认证失败可尝试输入密码尝试三次,认证成功则向本地存储最近一次连接成功的加密密码。

    代码实现

    修改 coco/models.py,添加 password 参数

     1 class Request:
     2     def __init__(self, addr):
     3         self.type = []
     4         self.meta = {"width": 80, "height": 24}
     5         self.user = None
     6         self.password = ''     # @ 周旺
     7         self.addr = addr
     8         self.remote_ip = self.addr[0]
     9         self.change_size_event = threading.Event()
    10         self.date_start = datetime.datetime.now()
    11     
    12 
    13 class Client:
    14     def __init__(self, chan, request):
    15         self.chan = chan
    16         self.request = request
    17         self.user = request.user
    18         self.password = request.password    # @ 周旺
    19         self.addr = request.addr

    修改 coco/interface.py, 赋值 request.password 

     1 class SSHInterface(paramiko.ServerInterface):
     2     def validate_auth(self, username, password="", public_key=""):
     3         info = app_service.authenticate(
     4             username, password=password, public_key=public_key,
     5             remote_addr=self.request.remote_ip
     6         )
     7         user = info.get('user', None)
     8         if user:
     9             self.request.user = user
    10             self.request.password = password   # request password 赋值 @ 周旺
    11             self.info = info
    12 
    13         seed = info.get('seed', None)
    14         token = info.get('token', None)
    15         if seed and not token:
    16             self.otp_auth = True
    17 
    18         return user

    修改 coco/interactive.py 

     1 class InteractiveServer:
     2     def display_search_result(self):
     3         sort_by = current_app.config["ASSET_LIST_SORT_BY"]
     4         self.search_result = sort_assets(self.search_result, sort_by)
     5         fake_data = [_("ID"), _("Hostname"), _("IP"), _("LoginAs")]
     6         id_length = max(len(str(len(self.search_result))), 4)
     7         hostname_length = item_max_length(self.search_result, 15,
     8                                           key=lambda x: x.hostname)
     9         sysuser_length = item_max_length(self.search_result,
    10                                          key=lambda x: x.system_users_name_list)
    11         size_list = [id_length, hostname_length, 16, sysuser_length]
    12         header_without_comment = format_with_zh(size_list, *fake_data)
    13         comment_length = max(
    14             self.request.meta["width"] -
    15             size_of_str_with_zh(header_without_comment) - 1,
    16             2
    17         )
    18         size_list.append(comment_length)
    19         fake_data.append(_("Comment"))
    20         self.client.send(wr(title(format_with_zh(size_list, *fake_data))))
    21         for index, asset in enumerate(self.search_result, 1):
    22             # data = [                                      # 注释主机显示列表  @ 周旺
    23             #     index, asset.hostname, asset.ip,
    24             #     asset.system_users_name_list, asset.comment
    25             # ]
    26 
    27             data = [                                        # 主机显示列表 @ 周旺
    28                 index, asset.hostname, asset.ip,
    29                 self.client.user.username, asset.comment
    30             ]
    31 
    32             self.client.send(wr(format_with_zh(size_list, *data)))
    33         self.client.send(wr(_("总共: {} 匹配: {}").format(
    34             len(self.assets), len(self.search_result)), before=1)
    35         )
    36 
    37     def proxy(self, asset):
    38         # system_user = self.choose_system_user(asset.system_users_granted)   # 注释 @ 周旺
    39         # if system_user is None:
    40         #     self.client.send(_("没有系统用户"))
    41         #     return
    42         system_user = self.client.user      # 修改系统用户为登录用户 @ 周旺   注: 仍保持system_user 变量名,后面所有 system_user 皆是登录用户
    43         password = self.client.password     # 密码 @ 周旺   --> by client -> by request
    44         forwarder = ProxyServer(self.client)
    45         forwarder.proxy(asset, system_user, password)  # password @ 周旺

    修改 coco/proxy.py

     1 class ProxyServer:
     2     def proxy(self, asset, system_user, password=''):  # 添加 password 参数 @ 周旺
     3         #self.get_system_user_auth(system_user)        # 注释 @ 周旺
     4 
     5         if not password:                                # 添加46-74行 @ 周旺
     6             with open('/opt/pwd/%s.pwd' % system_user.username, 'ab+') as pwd:    # 查找本地缓存密码
     7                 pwd.seek(0)
     8                 try:
     9                     password = base64.b64decode(pwd.read().strip()).decode().strip()
    10                     # password = pwd.read().strip()
    11                 except:
    12                     password = ''
    13 
    14             if not password:
    15                 prompt = "{}@{} password: ".format(system_user.username, asset.ip)
    16                 password = net_input(self.client, prompt=prompt, sensitive=True)
    17 
    18         for n in range(4):
    19             self.connecting = True
    20             self.send_connecting_message(asset, system_user)
    21             self.server = self.get_server_conn(asset, system_user, password)
    22             if self.server:
    23                 with open('/opt/pwd/%s.pwd' % system_user.username, 'wb') as pwd:    # 保存最后一次的正确密码
    24                     pwd.write(base64.b64encode(password.encode(encoding='utf-8')))
    25                     #pwd.write(password)
    26                 break
    27 
    28             if n < 3:
    29                 prompt = "{}@{} password({}/3): ".format(system_user.username, asset.ip, n+1)
    30                 password = net_input(self.client, prompt=prompt, sensitive=True)
    31         else:
    32             return False
    33 
    34 
    35         # self.send_connecting_message(asset, system_user)                  # 注释 @ 周旺
    36         # self.server = self.get_server_conn(asset, system_user, password)
    37 
    38         command_recorder = current_app.new_command_recorder()
    39         replay_recorder = current_app.new_replay_recorder()
    40         session = Session(
    41             self.client, self.server,
    42             command_recorder=command_recorder,
    43             replay_recorder=replay_recorder,
    44         )
    45         current_app.add_session(session)
    46         self.watch_win_size_change_async()
    47         session.bridge()
    48         self.stop_event.set()
    49         self.end_watch_win_size_change()
    50         current_app.remove_session(session)
    51 
    52     def get_server_conn(self, asset, system_user, password=''):  # 添加 password 参数 @ 周旺
    53         logger.info("Connect to {}".format(asset.hostname))
    54         # if not self.validate_permission(asset, system_user):    # 注释 @ 周旺
    55         #     self.client.send(warning('No permission'))
    56         #     return None
    57         # if True:
    58         #     server = self.get_ssh_server_conn(asset, system_user)
    59         # else:
    60         #     server = self.get_ssh_server_conn(asset, system_user)
    61 
    62         server = self.get_ssh_server_conn(asset, system_user, password) # password @ 周旺
    63         return server
    64 
    65     def get_ssh_server_conn(self, asset, system_user, password=''): # 添加 password 参数 @ 周旺
    66         request = self.client.request
    67         term = request.meta.get('term', 'xterm')
    68         width = request.meta.get('width', 80)
    69         height = request.meta.get('height', 24)
    70         ssh = SSHConnection()
    71         chan, sock, msg = ssh.get_channel(
    72             asset, system_user, term=term, width=width, height=height, password=password)   # password @ 周旺
    73         if not chan:
    74             self.client.send(warning(wr(msg, before=1, after=0)))
    75             server = None
    76         else:
    77             server = Server(chan, sock, asset, system_user)
    78         self.connecting = False
    79         self.client.send(b'
    ')
    80         return server
    81 
    82     def send_connecting_message(self, asset, system_user):
    83         def func():
    84             delay = 0.0
    85             self.client.send('Connecting to {}@{} {:.1f}'.format(
    86                 system_user.username, asset.ip, delay)          # 修改为 用户名,ip地址 @ 周旺
    87             )
    88             while self.connecting and delay < TIMEOUT:
    89                 self.client.send('x08x08x08{:.1f}'.format(delay).encode())
    90                 time.sleep(0.1)
    91                 delay += 0.1
    92         thread = threading.Thread(target=func)
    93         thread.start()

     修改 coco/connection.py

     1 class SSHConnection:
     2     def get_ssh_client(self, asset, system_user, password=''):  # 添加 password 参数 @ 周旺
     3         ssh = paramiko.SSHClient()
     4         ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
     5         sock = None
     6 
     7         # if not system_user.password and not system_user.private_key:    # 注释 @ 周旺
     8         #     self.get_system_user_auth(system_user)
     9 
    10         if asset.domain:
    11             sock = self.get_proxy_sock_v2(asset)
    12         try:
    13             ssh.connect(
    14                 asset.ip, port=asset.port, username=system_user.username,
    15                 #password=system_user.password, pkey=system_user.private_key,  # 注释 @ 周旺
    16                 password=password,                                             # password @ 周旺
    17                 timeout=TIMEOUT, compress=True, auth_timeout=TIMEOUT,
    18                 look_for_keys=False, sock=sock
    19             )
    20         except (paramiko.AuthenticationException,
    21                 paramiko.BadAuthenticationType,
    22                 SSHException) as e:
    23             # password_short = "None"                                       # 注释 @ 周旺  注:感觉没啥用
    24             # key_fingerprint = "None"
    25             # if system_user.password:
    26             #     password_short = system_user.password[:5] + 
    27             #                      (len(system_user.password) - 5) * '*'
    28             # if system_user.private_key:
    29             #     key_fingerprint = get_private_key_fingerprint(
    30             #         system_user.private_key
    31             #     )
    32             #
    33             # logger.error("Connect {}@{}:{} auth failed, password: 
    34             #                      {}, key: {}".format(
    35             #     system_user.username, asset.ip, asset.port,
    36             #     password_short, key_fingerprint,
    37             # ))
    38             return None, None, str(e)
    39         except (socket.error, TimeoutError) as e:
    40             return None, None, str(e)
    41         return ssh, sock, None
    42 
    43     def get_channel(self, asset, system_user, term="xterm", width=80, height=24, password=''):  # password 参数 @ 周旺
    44         ssh, sock, msg = self.get_ssh_client(asset, system_user, password) # password @ 周旺
    45         if ssh:
    46             chan = ssh.invoke_shell(term, width=width, height=height)
    47             return chan, sock, None
    48         else:
    49             return None, sock, msg

    效果展示

     后记

      以上仅适用 jumpserver 终端命令行,没有涉及对jumpserver web 终端及SFTP的修改。

          谢 jumpserver 团队:http://www.jumpserver.org/

          

  • 相关阅读:
    Effective C++ -----条款29:为“异常安全”而努力是值得的
    Effective C++ -----条款28:避免返回handles指向对象内部成分
    Effective C++ -----条款27:尽量少做转型动作
    Effective C++ -----条款26:尽可能延后变量定义式的出现时间
    Effective C++ -----条款25:考虑写出一个不抛异常的swap函数
    Effective C++ -----条款24:若所有参数皆需类型转换,请为此采用non-member函数
    HGE 第一个程序
    Help him http://acm.hdu.edu.cn/showproblem.php?pid=5059
    C 语言实例
    C 语言实例
  • 原文地址:https://www.cnblogs.com/wowoo1121/p/9309398.html
Copyright © 2011-2022 走看看