堡垒机

一、深入理解 paramiko

封装前奏

SSHClient() 部分源码

千锋云计算西瓜甜

千锋云计算西瓜甜

从上面的源码中不难看出,SSHClient() 的实例对象,最终也是使用了 Tansport() 的实例对象。

所以,我们也可以创建一个 Transport 的连接对象让 SSHClint 的对象使用

封装高潮

建立互信

 ssh-keygen -t ecdsa -N "" 
ssh-copy-id -i ~/.ssh/id_ecdsa.pub -f [email protected]

Centos7 系统采用了新的密钥算法,所以需要使用新的算法生成密钥对儿。
之后再用这个指定的公钥建立免密信任关系。

执行命令

import paramiko

# 创建一个本地当前用户的私钥对象
private_key = paramiko.ECDSAKey.from_private_key_file(
    '/Users/yanshunjun/.ssh/id_ecdsa')')

# 创建一个传输对象
transport = paramiko.Transport(('172.16.153.10', 22))
transport.connect(username='root', pkey=private_key)

# 创建一个 ssh 客户端对象
ssh = paramiko.SSHClient()

# 使用传输对象
ssh._transport = transport

# 执行相关命令
stdin, stdout, stderr = ssh.exec_command('df')

# 获取正确的命令结果,要在关闭传输对象之前把命令的结果赋值给一个变量
r = str(stdout.read(),encoding='utf-8')

# 关闭传输对象
transport.close()

print(r)

传输文件

import paramiko


# 创建一个本地当前用户的私钥对象
private_key = paramiko.ECDSAKey.from_private_key_file(
    '/Users/yanshunjun/.ssh/id_ecdsa')


# 创建一个传输对象
transport = paramiko.Transport(('172.16.153.10', 22))


# 使用刚才的传输对象创建一个传输文件的的连接对象
transport.connect(username='root', pkey=private_key)

sftp = paramiko.SFTPClient.from_transport(transport)

# 将 client.py 上传至服务器 /tmp/test.py
sftp.put('client.py', '/tmp/test.py')

# 将远程主机的文件 /tmp/test.py 下载到本地并命名为  some.txt
sftp.get('/tmp/test.py', 'some.txt')

transport.close()

合并

利用上面的共同点,我们可以写一个类,让执行命令和传输文件使用同一个传输对象。
这样我们就可以只需要和服务器建立一次连接,就可以同时实现执行命令和传输文件了。、

import paramiko

class MySSHClient(object):
    def __init__(self,host, username, password=None, port=22, pkey_path=None):
        self.username = username
        self.password = password
        self.host = host
        self.port = port
        self.pkey = pkey_path
        self.private_key = None
        self.__transport = None
        self.sftp = None
        self.ssh = None

    def connect(self):
        """
        创建一个传输对象
        :return:
        """
        if not self.host:
            raise ConnectionError("服务器 ip 空")
        self.__transport = paramiko.Transport((self.host, self.port))

        if any([self.password,self.pkey]):
            pass
        else:
            raise ConnectionError("密码或密钥为空")

        if self.pkey:
            self.private_key = paramiko.ECDSAKey.from_private_key_file(self.pkey)

            # 使用刚才的传输对象创建一个传输文件的的连接对象
            self.__transport.connect(username=self.username, pkey=self.private_key)
        else:
            self.__transport.connect(username=self.username, password=self.password)

    def get_sftp(self):
        self.sftp = paramiko.SFTPClient.from_transport(self.__transport)

    def get_ssh(self):
        """
        创建一个 ssh 客户端对象
        :return:
        """
        self.ssh = paramiko.SSHClient()

        # 使用传输对象
        self.ssh._transport = self.__transport

    def put_file(self,local_file, remote):
        if not self.__transport:
            self.connect()
        if not self.sftp:
            self.get_sftp()

        self.sftp.put(local_file, remote)

    def get_file(self, remote, local_file):
        if not self.__transport:
            self.connect()
        if not self.sftp:
            self.get_sftp()

        self.sftp.get(remote, local_file)

    def exec_cmd(self, cmd):
        # 执行相关命令

        if not self.__transport:
            self.connect()
        if not self.ssh:
            self.get_ssh()

        # 执行命令
        stdin, stdout, stderr = self.ssh.exec_command(cmd)

        if stdout:
            r = str(stdout.read(),encoding='utf-8')
        else:
            r = str(stderr.read(),encoding='utf-8')
        print(r)

    def close(self):
        self.__transport.close()


使用示例

ssh = MySSHClient(
    username='root',
    pkey_path='/Users/yanshunjun/.ssh/id_ecdsa',
    host='172.16.153.10'
)

ssh.exec_cmd('df')
ssh.put_file('some.txt', '/tmp/a.py')
ssh.exec_cmd('cat /tmp/a.py')
ssh.close()

二、堡垒机简介

image.png

三、设计

  1. 运维人员在每个服务器上创建不用权限的系统用户。
  2. 运维人员登录到堡垒机上,配置并建立和每个服务器的免密登录信任关系。
  3. 运维人员根据权限,在堡垒机上创建出来每个相关人员需要的账户,这个账号专门登录堡垒机。
  4. 相关人员使用自己授权的账户登录(输入用户名密码)到堡垒机,现实当前用户有权管理的服务器列表。
  5. 用户选择服务器,并自动登陆
  6. 执行操作并同时将用户操作记录

四、实现

输入回车时发送命令到远程服务器

# coding:utf-8
# 2019/3/19 15:15
import select,sys, socket
import paramiko
from paramiko.py3compat import u
private_key = paramiko.ECDSAKey.from_private_key_file('/Users/yanshunjun/.ssh/id_ecdsa')

transport = paramiko.Transport(('172.16.153.10', 22))
transport.start_client()
transport.auth_publickey(username='root', key=private_key)


# 打开一个通道
chanel = transport.open_session()
# 获取一个终端
chanel.get_pty()
# 激活器
chanel.invoke_shell()

while True:
    # 监视用户输入和服务器返回数据
    # sys.stdin 处理用户输入
    # chan 是之前创建的通道,用于接收服务器返回信息
    readable, writeable, error = select.select([chanel, sys.stdin, ], [], [], 1)
    if chanel in readable:
        try:
            # 将 bytes 转换为 字符串
            x = u(chanel.recv(1024))
            if len(x) == 0:
                print('\r\n******退出堡垒机******\r\n')
                break
            sys.stdout.write(x)
            sys.stdout.flush()
        except socket.timeout:
            pass
    if sys.stdin in readable:
        inp = sys.stdin.readline()
        chanel.sendall(inp)

chanel.close()
transport.close()

每次输入一个字符就发送到远程服务器

# coding:utf-8
# 2019/4/14 14:39
import select,sys, socket, termios, tty
import paramiko
from paramiko.py3compat import u

private_key = paramiko.ECDSAKey.from_private_key_file('/Users/yanshunjun/.ssh/id_ecdsa')

transport = paramiko.Transport(('172.16.153.10', 22))
transport.start_client()
transport.auth_publickey(username='root', key=private_key)


# 打开一个通道
chanel = transport.open_session()
# 获取一个终端
chanel.get_pty()
# 激活器
chanel.invoke_shell()

# 获取原tty属性
old_tty = termios.tcgetattr(sys.stdin)

try:
    # 为tty设置新属性
    tty.setraw(sys.stdin.fileno())
    chanel.settimeout(0.0)

    while True:
        # 监视 用户输入 和 远程服务器返回数据(socket)
        # 阻塞,直到句柄可读
        r, w, e = select.select([chanel, sys.stdin], [], [], 1)
        if chanel in r:
            try:
                x = u(chanel.recv(1024))
                if len(x) == 0:
                    print('\r\n******退出堡垒机******\r\n')
                    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
            chanel.send(x)

finally:
    # 最后设置终端属性为最初的属性
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)

五、完整版及使用方法

将下面的内容保存为一个 shark_paramiko.py 的文件,之后放到 /root/ 目录下

~/bashrc 文件的最后添加如下内容

/usr/bin/env python ~/shark_paramiko.py
logout
# coding:utf-8
# 2019/4/14 14:39
import os, select,sys, socket, termios, tty, getpass
import glob
import paramiko
from paramiko.py3compat import u
from paramiko.ssh_exception import SSHException
username = getpass.getuser()
inp_user = input("User:")
hostname = input("Host Name/IP:")
pwd = input("Password:")

if inp_user:
    username = inp_user

user_home = os.environ['HOME']
pkey_dir = os.path.join(user_home, '.ssh')

pkey_path_li = glob.glob(pkey_dir+'/id*')
pkey_path_li = [i for i in pkey_path_li if not i.endswith('pub')]
pkey_path = pkey_path_li[0]

try:
    private_key = paramiko.RSAKey.from_private_key_file(pkey_path)

except SSHException as e:
    private_key = paramiko.ECDSAKey.from_private_key_file(pkey_path)



transport = paramiko.Transport(hostname)
transport.start_client()
if not pwd:
    transport.auth_publickey(username=username, key=private_key)
else:
    transport.auth_password(username=username, password=pwd)

# 打开一个通道
chanel = transport.open_session()
# 获取一个终端
chanel.get_pty()
# 激活器
chanel.invoke_shell()

# 获取原tty属性
old_tty = termios.tcgetattr(sys.stdin)

try:
    # 为tty设置新属性
    tty.setraw(sys.stdin.fileno())
    chanel.settimeout(0.0)

    while True:
        # 监视 用户输入 和 远程服务器返回数据(socket)
        # 阻塞,直到句柄可读
        r, w, e = select.select([chanel, sys.stdin], [], [], 1)
        if chanel in r:
            try:
                x = u(chanel.recv(1024))
                if len(x) == 0:
                    print('\r\n******退出堡垒机******\r\n')
                    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
            chanel.send(x)

finally:
    # 最后设置终端属性为最初的属性
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
chanel.close()
transport.close()

记录用户的操作

# coding:utf-8
# 2019/4/14 14:39
import os, select,sys, socket, termios, tty, getpass
import glob
import paramiko
from paramiko.py3compat import u
from paramiko.ssh_exception import SSHException
username = getpass.getuser()
inp_user = input("User:")
hostname = input("Host Name/IP:")
pwd = input("Password:")

if inp_user:
    username = inp_user

user_home = os.environ['HOME']
pkey_dir = os.path.join(user_home, '.ssh')

pkey_path_li = glob.glob(pkey_dir+'/id*')
pkey_path_li = [i for i in pkey_path_li if not i.endswith('pub')]
pkey_path = pkey_path_li[0]

try:
    private_key = paramiko.RSAKey.from_private_key_file(pkey_path)

except SSHException as e:
    private_key = paramiko.ECDSAKey.from_private_key_file(pkey_path)



transport = paramiko.Transport(hostname)
transport.start_client()
if not pwd:
    transport.auth_publickey(username=username, key=private_key)
else:
    transport.auth_password(username=username, password=pwd)

# 打开一个通道
chanel = transport.open_session()
# 获取一个终端
chanel.get_pty()
# 激活器
chanel.invoke_shell()

# 获取原tty属性
old_tty = termios.tcgetattr(sys.stdin)
log = open('handler.log', 'a', encoding='utf-8')
try:
    # 为tty设置新属性
    tty.setraw(sys.stdin.fileno())
    tty.setcbreak(sys.stdin.fileno())
    chanel.settimeout(0.0)
    tab_flag = False

    while True:
        # 监视 用户输入 和 远程服务器返回数据(socket)
        # 阻塞,直到句柄可读
        r, w, e = select.select([chanel, sys.stdin], [], [], 1)
        if chanel in r:
            try:
                x = u(chanel.recv(1024))
                if len(x) == 0:
                    print('\r\n******退出堡垒机******\r\n')
                    break
                if tab_flag:
                    if x.startswith('\r\n'):
                        print('yan>>>', [x])
                        pass
                    else:
                        log.write(x)
                        log.flush()
                    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 x == '\t':
                tab_flag = True
            else:
                log.write(x)
                log.flush()
            chanel.send(x)

finally:
    # 最后设置终端属性为最初的属性
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
    log.close()
chanel.close()
transport.close()


你可能感兴趣的:(堡垒机)