堡垒机

一、深入理解 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()


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容

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