堡垒机续-堡垒机实现终端交互、堡垒机信息从数据库里读

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()
原文地址:https://www.cnblogs.com/fuckily/p/6061485.html