1. 打开会话
import paramiko import sys import os import socket import select import getpass #创建连接 tran = paramiko.Transport(('10.211.55.4', 22,)) tran.start_client() tran.auth_password('zsc', '123') # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() #####这段代码就是让你肆意妄为的#### # 利用sys.stdin收到用户的输入;利用select监听对端的socket变化 # 用户在终端输入内容,并将内容发送至远程服务器 # 远程服务器执行命令,并将结果返回 # 用户终端显示内容 ######### chan.close() tran.close()
2.加上sys.stdin和select实现堡垒机基础功能
import paramiko import sys import os import socket import select import getpass from paramiko.py3compat import u tran = paramiko.Transport(('10.211.55.4', 22,)) tran.start_client() tran.auth_password('zsc', '123') # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() while True: # 监视用户输入和服务器返回数据 # sys.stdin 处理用户输入 # chan 是之前创建的通道,用于接收服务器返回信息 # select.select([chan, sys.stdin, ],[],[],1)的第一个参数是监听变化,只要列表里的变量有变化,就会将变化的参数放到readable里面,如果都没有变化,readable就是空值;当用户输入值按下回车后,就监听到了sys.stdin有变化,那么readable就等于sys.stdin;当对端socket返回命令结果时,select就监听到chan有变化,那么readable就等于chan。 readable, writeable, error = select.select([chan, sys.stdin, ],[],[],1) if chan in readable: try: x = u(chan.recv(1024)) if len(x) == 0: print(' *** EOF ') break # 将数据写入缓冲区 sys.stdout.write(x) # 将数据显示到屏幕 sys.stdout.flush() except socket.timeout: pass if sys.stdin in readable: # 将用户的输入存为inp inp = sys.stdin.readline() # 将inp值通过chan通道传给对端机器 chan.sendall(inp) chan.close() tran.close()
上面的代码就实现了创建一个终端来与对端服务器交互数据的效果,不过select依赖终端,所以此代码只能在linux下运行。
上面的效果是每次输入一行按下了回车后,才会把命令发到对端服务器,所以连上对端服务器后不能用tab补全命令,要实现只要按下回车上的任意字符都会传到对端服务器的话,需要做点额外的操作。
3.实现将单个字符传给对端机器和tab补全
之所以需要按下回车才能将命令传给对端机器,是因为linux终端有一个设置:只有按下回车,才会触发sys.stdin,又只有触发了sys.stdin才会将命令传给对端机器,所以必须按了回车数据才会传给对端机器,也没法使用tab补全。
接下来要做的就是,修改终端的属性,使其每输入一个字符,就触发一次sys.stdin,而不是只有摁下回车才出发sys.stdin。输入一个“l”就把“l”发到对端机器,输入一个“s”就把“s”发到对端机器,输入一个回车,在对端机器就相当于执行了一个“ls”;实现了单个字符实时发送,所以也就实现了tab补全。
import paramiko import sys import os import socket import select import getpass import termios import tty from paramiko.py3compat import u tran = paramiko.Transport(('10.211.55.4', 22,)) tran.start_client() tran.auth_password('zsc', '123') # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() # 获取原tty(终端)属性 oldtty = termios.tcgetattr(sys.stdin) try: # 为tty设置新属性 # 默认当前tty设备属性: # 输入一行回车,执行 # CTRL+C 进程退出,遇到特殊字符,特殊处理。 # 这是为原始模式,不认识所有特殊符号 # 放置特殊字符应用在当前终端,如此设置,将所有的用户输入均发送到远程服务器 tty.setraw(sys.stdin.fileno()) chan.settimeout(0.0) while True: # 监视 用户输入 和 远程服务器返回数据(socket) # 阻塞,直到句柄可读 r, w, e = select.select([chan, sys.stdin], [], [], 1) if chan in r: try: x = u(chan.recv(1024)) if len(x) == 0: print(' *** EOF ') break sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if len(x) == 0: break chan.send(x) finally: # 将终端属性恢复为默认 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) chan.close() tran.close()
登录目标机器后,紧跟着进入docker容器
import paramiko import sys import os import socket import select import getpass import termios import tty import time from paramiko.py3compat import u container_name = str(sys.argv[1]) tran = paramiko.Transport(('10.102.36.154', 9880,)) tran.start_client() tran.auth_password('dockerj', 'dockerj') # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() # 获取原tty(终端)属性 oldtty = termios.tcgetattr(sys.stdin) try: # 为tty设置新属性 # 默认当前tty设备属性: # 输入一行回车,执行 # CTRL+C 进程退出,遇到特殊字符,特殊处理。 # 这是为原始模式,不认识所有特殊符号 # 放置特殊字符应用在当前终端,如此设置,将所有的用户输入均发送到远程服务器 tty.setraw(sys.stdin.fileno()) chan.settimeout(0.0) tag_num = 1 while True: # 监视 用户输入 和 远程服务器返回数据(socket) # 阻塞,直到句柄可读 r, w, e = select.select([chan, sys.stdin], [], [], 1) if chan in r: try: x = u(chan.recv(1024)) if len(x) == 0: print(' *** EOF ') break sys.stdout.write(x) sys.stdout.flush() if tag_num == 1 and x.startswith('[dockerj'): x = 'docker exec -it ' + container_name + ' bash' + ' ' sys.stdout.write(x) sys.stdout.flush() # 如果不是第一次连接,并且用户退出了容器,则强制用户退回到最初状态,即不让用户停留在容器的宿主机上;这台宿主机的shell提示符是“[dockerj@t-caiwu-36-154 ~]$”,当检测到返回的字符串以'[dockerj'开头时,就表示是回到宿主机shell上了,就强制退出宿主机shell。 if tag_num != 1 and x.startswith('[dockerj'): print(' *** EOF ') break except socket.timeout: pass if sys.stdin in r: # 当用户第一次连目标机器时,自动输入一个进入容器的命令,这样就能保证,用户连上宿主机后第一时间进入相应容器,而不是停留在宿主机上操作。 if tag_num == 1: x = 'docker exec -it ' + container_name + ' bash' + ' ' tag_num = 0 chan.send(x) else: x = sys.stdin.read(1) if len(x) == 0: break chan.send(x) finally: # 将终端属性恢复为默认 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) chan.close() tran.close()
4.堡垒机较完整版本
import paramiko import sys import os import socket import getpass from paramiko.py3compat import u # windows 终端没有termios,根据模块来判断是linux终端还是windows终端 try: import termios import tty has_termios = True except ImportError: has_termios = False def interactive_shell(chan): if has_termios: posix_shell(chan) else: windows_shell(chan) def posix_shell(chan): import select oldtty = termios.tcgetattr(sys.stdin) try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) chan.settimeout(0.0) while True: r, w, e = select.select([chan, sys.stdin], [], []) if chan in r: try: x = u(chan.recv(1024)) if len(x) == 0: sys.stdout.write(' *** EOF ') break sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if len(x) == 0: break chan.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) def windows_shell(chan): import threading sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF. ") def writeall(sock): while True: data = sock.recv(256) if not data: sys.stdout.write(' *** EOF *** ') sys.stdout.flush() break sys.stdout.write(data) sys.stdout.flush() writer = threading.Thread(target=writeall, args=(chan,)) writer.start() try: while True: d = sys.stdin.read(1) if not d: break chan.send(d) except EOFError: # user hit ^Z or F6 pass def run(): # 获取当前登录用户 default_username = getpass.getuser() username = input('Username [%s]: ' % default_username) if len(username) == 0: username = default_username hostname = input('Hostname: ') if len(hostname) == 0: print('*** Hostname required.') sys.exit(1) tran = paramiko.Transport((hostname, 22,)) tran.start_client() default_auth = "p" auth = input('Auth by (p)assword or (r)sa key[%s] ' % default_auth) if len(auth) == 0: auth = default_auth if auth == 'r': default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') path = input('RSA key [%s]: ' % default_path) if len(path) == 0: path = default_path try: key = paramiko.RSAKey.from_private_key_file(path) except paramiko.PasswordRequiredException: password = getpass.getpass('RSA key password: ') key = paramiko.RSAKey.from_private_key_file(path, password) tran.auth_publickey(username, key) else: pw = getpass.getpass('Password for %s@%s: ' % (username, hostname)) tran.auth_password(username, pw) # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() interactive_shell(chan) chan.close() tran.close() if __name__ == '__main__': run()
上面的堡垒机代码可以说功能上比较完善了,但是没有记录日志的功能。
5.记录日志的堡垒机
5.1 记录对端返回日志
会把命令和命令的结果都记录到日志
import paramiko import sys import os import socket import getpass from paramiko.py3compat import u # windows 没有termios,根据模块来判断是linux还是windows try: import termios import tty has_termios = True except ImportError: has_termios = False def interactive_shell(chan): if has_termios: posix_shell(chan) else: windows_shell(chan) def posix_shell(chan): import select oldtty = termios.tcgetattr(sys.stdin) try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) chan.settimeout(0.0) while True: r, w, e = select.select([chan, sys.stdin], [], []) if chan in r: try: x = u(chan.recv(1024)) if len(x) == 0: sys.stdout.write(' *** EOF ') break # 将返回信息写入日志文件 with open('baolei.log','a+') as baoleilog: baoleilog.write(x) sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if len(x) == 0: break chan.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) def windows_shell(chan): import threading sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF. ") def writeall(sock): while True: data = sock.recv(256) if not data: sys.stdout.write(' *** EOF *** ') sys.stdout.flush() break sys.stdout.write(data) sys.stdout.flush() writer = threading.Thread(target=writeall, args=(chan,)) writer.start() try: while True: d = sys.stdin.read(1) if not d: break chan.send(d) except EOFError: # user hit ^Z or F6 pass def run(): # 获取当前登录用户 default_username = getpass.getuser() username = input('Username [%s]: ' % default_username) if len(username) == 0: username = default_username hostname = input('Hostname: ') if len(hostname) == 0: print('*** Hostname required.') sys.exit(1) tran = paramiko.Transport((hostname, 22,)) tran.start_client() default_auth = "p" auth = input('Auth by (p)assword or (r)sa key[%s] ' % default_auth) if len(auth) == 0: auth = default_auth if auth == 'r': default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') path = input('RSA key [%s]: ' % default_path) if len(path) == 0: path = default_path try: key = paramiko.RSAKey.from_private_key_file(path) except paramiko.PasswordRequiredException: password = getpass.getpass('RSA key password: ') key = paramiko.RSAKey.from_private_key_file(path, password) tran.auth_publickey(username, key) else: pw = getpass.getpass('Password for %s@%s: ' % (username, hostname)) tran.auth_password(username, pw) # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() interactive_shell(chan) chan.close() tran.close() if __name__ == '__main__': run()
5.2 记录用户操作日志
import paramiko import sys import os import socket import getpass from paramiko.py3compat import u # windows 没有termios,根据模块来判断是linux还是windows try: import termios import tty has_termios = True except ImportError: has_termios = False def interactive_shell(chan): if has_termios: posix_shell(chan) else: windows_shell(chan) def posix_shell(chan): import select oldtty = termios.tcgetattr(sys.stdin) try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) chan.settimeout(0.0) baoleilog = open('baolei.log','a+') while True: r, w, e = select.select([chan, sys.stdin], [], []) if chan in r: try: x = u(chan.recv(1024)) if len(x) == 0: sys.stdout.write(' *** EOF ') break sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if len(x) == 0: break # 记录用户操作日志 baoleilog.write(x) baoleilog.flush() chan.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) def windows_shell(chan): import threading sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF. ") def writeall(sock): while True: data = sock.recv(256) if not data: sys.stdout.write(' *** EOF *** ') sys.stdout.flush() break sys.stdout.write(data) sys.stdout.flush() writer = threading.Thread(target=writeall, args=(chan,)) writer.start() try: while True: d = sys.stdin.read(1) if not d: break chan.send(d) except EOFError: # user hit ^Z or F6 pass def run(): # 获取当前登录用户 default_username = getpass.getuser() username = input('Username [%s]: ' % default_username) if len(username) == 0: username = default_username hostname = input('Hostname: ') if len(hostname) == 0: print('*** Hostname required.') sys.exit(1) tran = paramiko.Transport((hostname, 22,)) tran.start_client() default_auth = "p" auth = input('Auth by (p)assword or (r)sa key[%s] ' % default_auth) if len(auth) == 0: auth = default_auth if auth == 'r': default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') path = input('RSA key [%s]: ' % default_path) if len(path) == 0: path = default_path try: key = paramiko.RSAKey.from_private_key_file(path) except paramiko.PasswordRequiredException: password = getpass.getpass('RSA key password: ') key = paramiko.RSAKey.from_private_key_file(path, password) tran.auth_publickey(username, key) else: pw = getpass.getpass('Password for %s@%s: ' % (username, hostname)) tran.auth_password(username, pw) # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() interactive_shell(chan) chan.close() tran.close() if __name__ == '__main__': run()
按照上面的方法记录用户操作日志有个问题,如果用户使用了tab补全,那tab键会直接记录到日志里,而tab出来的命令不会记录到日志里,见下,
ip a^Mls^Mid^Mif c o ^M^D,用户在输入ifcon后按下tab,然后记录到日志里就是ifcon和一个tab符,并没有记录ifconfig。事实上我们并不希望记录tab符。
tab补全的两种情况:
1.输入的命令信息太少,返回以if开头的所有命令,并且返回以“回车”开头
[root@python python]# if
if ifcfg ifconfig ifdown ifenslave ifnames ifstat ifup
2.输入的命令信息足够多,直接补全命令,不换行
[root@python python]# ifconfig
sys.stdin检测用户是否输入了tab键,如果输入了tab键,就不记录到日志,并且就把tab_flag置为True,因为输了tab键,所以对端会返回结果,chan里检测tab_flag,如果为True就表示用户输入了tab键,然后查看chan返回的内容是否以“换行符”开头,如果是以“换行符”开头,说明这个tab键获取到了很多以if开头的命令,如果不是以“换行符”开头,说明chan返回了具体的命令,则记录到日志里,记录后再把tab_flag置为False。
处理tab后的代码:
import paramiko import sys import os import socket import getpass from paramiko.py3compat import u # windows 没有termios,根据模块来判断是linux还是windows try: import termios import tty has_termios = True except ImportError: has_termios = False def interactive_shell(chan): if has_termios: posix_shell(chan) else: windows_shell(chan) def posix_shell(chan): import select oldtty = termios.tcgetattr(sys.stdin) tab_flag = False mingling = [] try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) chan.settimeout(0.0) baoleilog = open('baolei.log','a+') while True: r, w, e = select.select([chan, sys.stdin], [], []) if chan in r: try: x = u(chan.recv(1024)) if len(x) == 0: sys.stdout.write(' *** EOF ') break # tab_flag为真,则表示用户输入了tab键 if tab_flag: # 如果返回数据以换行符开头,则不记录到日志 if not x.startswith(' '): baoleilog.write(x) # 把tab_flag重新置为False tab_flag = False sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if len(x) == 0: break # 如果用户输入的tab符,就把记录日志的操作转交给chan代码处理 if x == ' ': tab_flag = True else: baoleilog.write(x) baoleilog.flush() chan.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) def windows_shell(chan): import threading sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF. ") def writeall(sock): while True: data = sock.recv(256) if not data: sys.stdout.write(' *** EOF *** ') sys.stdout.flush() break sys.stdout.write(data) sys.stdout.flush() writer = threading.Thread(target=writeall, args=(chan,)) writer.start() try: while True: d = sys.stdin.read(1) if not d: break chan.send(d) except EOFError: # user hit ^Z or F6 pass def run(): # 获取当前登录用户 default_username = getpass.getuser() #username = input('Username [%s]: ' % default_username) username = 'sa' if len(username) == 0: username = default_username #hostname = input('Hostname: ') hostname = '192.168.0.30' if len(hostname) == 0: print('*** Hostname required.') sys.exit(1) tran = paramiko.Transport((hostname, 22,)) tran.start_client() default_auth = "p" #auth = input('Auth by (p)assword or (r)sa key[%s] ' % default_auth) auth = 'p' if len(auth) == 0: auth = default_auth if auth == 'r': default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') path = input('RSA key [%s]: ' % default_path) if len(path) == 0: path = default_path try: key = paramiko.RSAKey.from_private_key_file(path) except paramiko.PasswordRequiredException: password = getpass.getpass('RSA key password: ') key = paramiko.RSAKey.from_private_key_file(path, password) tran.auth_publickey(username, key) else: #pw = getpass.getpass('Password for %s@%s: ' % (username, hostname)) pw = 'xi' tran.auth_password(username, pw) # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() interactive_shell(chan) chan.close() tran.close() if __name__ == '__main__': run()
完成上面的代码后,日志还有一点点问题,见下,
ip^Mls^Mifc^Gonfig ^G^M^D,记录的日志包含一些特殊字符,如“^M”是回车,下面我们需要对“^M”处理一下,其他字符暂时没找到对应的ord值。
import paramiko import sys import os import socket import getpass from paramiko.py3compat import u # windows 没有termios,根据模块来判断是linux还是windows try: import termios import tty has_termios = True except ImportError: has_termios = False def interactive_shell(chan): if has_termios: posix_shell(chan) else: windows_shell(chan) def posix_shell(chan): import select oldtty = termios.tcgetattr(sys.stdin) tab_flag = False mingling = [] try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) chan.settimeout(0.0) baoleilog = open('baolei.log','a+') while True: r, w, e = select.select([chan, sys.stdin], [], []) if chan in r: try: x = u(chan.recv(1024)) if len(x) == 0: sys.stdout.write(' *** EOF ') break if tab_flag: if not x.startswith(' '): baoleilog.write(x) tab_flag = False sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if len(x) == 0: break # 如果是用户输入的是回车,则往日志文件里写入一个回车。 if ord(x) == 13: baoleilog.write(' ') else: if x == ' ': tab_flag = True else: baoleilog.write(x) baoleilog.flush() chan.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) def windows_shell(chan): import threading sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF. ") def writeall(sock): while True: data = sock.recv(256) if not data: sys.stdout.write(' *** EOF *** ') sys.stdout.flush() break sys.stdout.write(data) sys.stdout.flush() writer = threading.Thread(target=writeall, args=(chan,)) writer.start() try: while True: d = sys.stdin.read(1) if not d: break chan.send(d) except EOFError: # user hit ^Z or F6 pass def run(): # 获取当前登录用户 default_username = getpass.getuser() #username = input('Username [%s]: ' % default_username) username = 'sa' if len(username) == 0: username = default_username #hostname = input('Hostname: ') hostname = '192.168.0.30' if len(hostname) == 0: print('*** Hostname required.') sys.exit(1) tran = paramiko.Transport((hostname, 22,)) tran.start_client() default_auth = "p" #auth = input('Auth by (p)assword or (r)sa key[%s] ' % default_auth) auth = 'p' if len(auth) == 0: auth = default_auth if auth == 'r': default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') path = input('RSA key [%s]: ' % default_path) if len(path) == 0: path = default_path try: key = paramiko.RSAKey.from_private_key_file(path) except paramiko.PasswordRequiredException: password = getpass.getpass('RSA key password: ') key = paramiko.RSAKey.from_private_key_file(path, password) tran.auth_publickey(username, key) else: #pw = getpass.getpass('Password for %s@%s: ' % (username, hostname)) pw = 'xi' tran.auth_password(username, pw) # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() interactive_shell(chan) chan.close() tran.close() if __name__ == '__main__': run()
6. 让用户选择登录哪台服务器
import paramiko import sys import os import socket import getpass from paramiko.py3compat import u # windows 没有termios,根据模块来判断是linux还是windows try: import termios import tty has_termios = True except ImportError: has_termios = False def interactive_shell(chan): if has_termios: posix_shell(chan) else: windows_shell(chan) def posix_shell(chan): import select oldtty = termios.tcgetattr(sys.stdin) tab_flag = False mingling = [] try: tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) chan.settimeout(0.0) baoleilog = open('baolei.log','a+') while True: r, w, e = select.select([chan, sys.stdin], [], []) if chan in r: try: x = u(chan.recv(1024)) if len(x) == 0: sys.stdout.write(' *** EOF ') break if tab_flag: if not x.startswith(' '): baoleilog.write(x) tab_flag = False sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if len(x) == 0: break if ord(x) == 13: baoleilog.write(' ') else: if x == ' ': tab_flag = True else: baoleilog.write(x) baoleilog.flush() chan.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) def windows_shell(chan): import threading sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF. ") def writeall(sock): while True: data = sock.recv(256) if not data: sys.stdout.write(' *** EOF *** ') sys.stdout.flush() break sys.stdout.write(data) sys.stdout.flush() writer = threading.Thread(target=writeall, args=(chan,)) writer.start() try: while True: d = sys.stdin.read(1) if not d: break chan.send(d) except EOFError: # user hit ^Z or F6 pass def run(): # 打印该用户可用的服务器并选择进入 host_info=[ {'host':'192.168.0.30','username':'sa','pw':'xi','auth':'p'}, {'host':'192.168.0.55','username':'sa','pw':'xi','auth':'p'} ] for key,item in enumerate(host_info,1): print(key,item['host']) sle_num = input('enter a number:') sle_num = int(sle_num) - 1 hostname = host_info[sle_num]['host'] username = host_info[sle_num]['username'] pw = host_info[sle_num]['pw'] auth = host_info[sle_num]['auth'] tran = paramiko.Transport((hostname, 22,)) tran.start_client() if auth == 'r': default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') path = input('RSA key [%s]: ' % default_path) if len(path) == 0: path = default_path try: key = paramiko.RSAKey.from_private_key_file(path) except paramiko.PasswordRequiredException: password = getpass.getpass('RSA key password: ') key = paramiko.RSAKey.from_private_key_file(path, password) tran.auth_publickey(username, key) else: #pw = getpass.getpass('Password for %s@%s: ' % (username, hostname)) pw = 'xi' tran.auth_password(username, pw) # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() interactive_shell(chan) chan.close() tran.close() if __name__ == '__main__': run()
7. 将堡垒机和数据库相连
从数据库读取数据,将日志记录到数据库。
7.1 建表
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://sqlalchemy:123456@192.168.0.57:3306/sqlalchemy", max_overflow=5) Base = declarative_base() #机器列表 class Hostname(Base): __tablename__ = 'hostname' id = Column(Integer,primary_key=True) host_name = Column(String(32)) host_port = Column(String(32)) #用户列表 class Username(Base): __tablename__ = 'username' id = Column(Integer,primary_key=True) username = Column(String(32)) password = Column(String(32)) #用户与机器的对应表 class HostnametoUsername(Base): __tablename__ = 'hosttouser' id = Column(Integer,primary_key=True) hostname_id = Column(Integer,ForeignKey('hostname.id')) username_id = Column(Integer,ForeignKey('username.id')) #创建表 def init_db(): Base.metadata.create_all(engine) #删除表 def drop_db(): Base.metadata.drop_all(engine) #init_db() Session = sessionmaker(bind=engine) session = Session() #添加机器 session.add_all([ Hostname(host_name='192.168.0.30',host_port='22'), Hostname(host_name='192.168.0.55',host_port='22'), Hostname(host_name='192.168.0.56',host_port='22'), Hostname(host_name='192.168.0.57',host_port='22'), ]) #添加用户 session.add_all([ Username(username='root',password='xi'), Username(username='sa',password='xi'), Username(username='xi',password='xi') ]) #添加对应关系 session.add_all([ HostnametoUsername(hostname_id=3,username_id=1), HostnametoUsername(hostname_id=4,username_id=1), HostnametoUsername(hostname_id=4,username_id=2), ]) session.commit()
7.2 堡垒机
#!/usr/bin/env python import paramiko import sys import os import socket import getpass import termios import tty import select from paramiko.py3compat import u from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://sqlalchemy:123456@192.168.0.57:3306/sqlalchemy", max_overflow=5) Base = declarative_base() class Hostname(Base): __tablename__ = 'hostname' id = Column(Integer,primary_key=True) host_name = Column(String(32)) host_port = Column(String(32)) class Username(Base): __tablename__ = 'username' id = Column(Integer,primary_key=True) username = Column(String(32)) password = Column(String(32)) class HostnametoUsername(Base): __tablename__ = 'hosttouser' id = Column(Integer,primary_key=True) hostname_id = Column(Integer,ForeignKey('hostname.id')) username_id = Column(Integer,ForeignKey('username.id')) #建立数据库连接会话 Session = sessionmaker(bind=engine) session = Session() def posix_shell(chan): #获取linux终端属性 oldtty = termios.tcgetattr(sys.stdin) #定义一个flag后面会用到 tab_flag = False try: # 下面三行是设置属性,使linux终端能实时触发sys.stdin() tty.setraw(sys.stdin.fileno()) tty.setcbreak(sys.stdin.fileno()) chan.settimeout(0.0) # 打开记录日志的文件 baoleilog = open('baolei.log','a+') while True: # 只要chan或sys.stdin有变化,就将变化写入到r r, w, e = select.select([chan, sys.stdin], [], []) # 如果r里有chan,代表chan有变化,执行下面代码 if chan in r: try: x = u(chan.recv(1024)) if len(x) == 0: sys.stdout.write(' *** EOF ') break # 如果用户输入“tab”键,会将tab_flag置为True if tab_flag: if not x.startswith(' '): baoleilog.write(x) tab_flag = False sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if len(x) == 0: break # 如果用户输入回车,则不将回车符“^M”记录到日志,而是主动写入一个“ ” if ord(x) == 13: baoleilog.write(' ') else: # 如果用户输入的tab键,则将是否记录日志的活交给chan的代码处理 if x == ' ': tab_flag = True else: baoleilog.write(x) baoleilog.flush() chan.send(x) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) def run(): # 下面的代码就是从数据库里取到hostname、username、password、port,然后展示、认证等。 ret = session.query(Hostname.host_name).all() ret1 = zip(*ret) ret2 = list(list(ret1)[0]) for key,item in enumerate(ret2,1): print(key,item) select_hostname = int(input('enter a number:')) hostname_query = session.query(Hostname.host_name).filter_by(id=select_hostname).all() hostname = list(hostname_query[0])[0] #hostname = select_hostname hostport_query = session.query(Hostname.host_port).filter_by(host_name=hostname).all() port = int(list(hostport_query[0])[0]) users = session.query(HostnametoUsername.username_id).filter_by(hostname_id=select_hostname).all() print('users:',users) users1 = list(list(zip(*users))[0]) #for key,item in enumerate(users1,1): # print(key,item) user_list = [] for num in users1: user = session.query(Username.username).filter_by(id=num).all() user1 = list(list(zip(*user))[0]) user_list.append(user1[0]) for key,item in enumerate(user_list,1): print(key,item) select_username = int(input('enter a number:')) username_query = session.query(Username.username).filter_by(id=select_username).all() username = list(username_query[0])[0] print('username:',username) password_query = session.query(Username.password).filter_by(id=select_username).all() password = list(password_query[0])[0] tran = paramiko.Transport((hostname, port,)) tran.start_client() auth = 'p' if auth == 'r': default_path = os.path.join(os.environ['HOME'], '.ssh', 'id_rsa') path = input('RSA key [%s]: ' % default_path) if len(path) == 0: path = default_path try: key = paramiko.RSAKey.from_private_key_file(path) except paramiko.PasswordRequiredException: password = getpass.getpass('RSA key password: ') key = paramiko.RSAKey.from_private_key_file(path, password) tran.auth_publickey(username, key) else: tran.auth_password(username, password) # 打开一个通道 chan = tran.open_session() # 获取一个终端 chan.get_pty() # 激活器 chan.invoke_shell() posix_shell(chan) chan.close() tran.close() if __name__ == '__main__': run()