堡垒机

一、深入理解 paramiko

封装前奏

SSHClient() 部分源码

千锋云计算西瓜甜

千锋云计算西瓜甜

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

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

封装高潮

建立互信

 ssh-keygen -t ecdsa -N "" 
ssh-copy-id -i ~/.ssh/id_ecdsa.pub -f root@172.16.153.10

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()


©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 产品别名:运维管理审计系统。 1 产品概述 运维圈内有这么一句话:70%故障来自内部人员的操作失误。 ...
    得奕阅读 1,900评论 0 3
  • 今年下半年更换工作后,接到的第一个任务居然是堡垒机实施,虽然之前项目中有用到过同样的堡垒机产品,但是这一次实施面向...
    自书其果阅读 2,299评论 0 2
  • 《属于你的我的初恋》是一部日本的纯爱电影, 故事情节矫情到让人犯尴尬癌却又喜欢到停不下来。 故事要从两人才八岁的时...
    第一影评人阅读 364评论 0 2
  • 今天上午,读到了笑来老师在《通往财富自由之路》的一篇名为《什么是朋友》的文章,满满的感动,也深深的折服。这也让我重...
    沫葵阅读 558评论 0 0
  • 生命的过程,无论是阳春白雪,青菜豆腐,我都得尝尝是什么滋味,才不枉来走这么一遭! —— 三毛《撒哈拉的故事》 只看...
    阿弥陀佛哒么么阅读 895评论 0 5

友情链接更多精彩内容