Python修改paramiko模块开发运维审计保垒机

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python修改paramiko模块开发运维审计保垒机相关的知识,希望对你有一定的参考价值。

需求:

1、保垒机有一个公共账号,所有人都是用这个账号登陆保垒机,root用户只能由堡垒机管理员有权限可以登陆

2、公共账号登陆保垒机->选择主机组->选择主机->选择你的普通用户->输入你的后端服务器的密码登陆

3、登陆的后端服务器所操作过的命令要记录到文件



拓扑图:

技术分享



第三方模块paramiko下载

paramiko-1.10.1.tar.gz http://pan.baidu.com/s/1hrWzg7y



paramiko源码包远程演示(未修改源码)

技术分享



修改parpakio源码

主要修改paramiko这两个脚本文件,解压paramiko后,进去解压出来的目录把这两个文件单独拷出来到一个文件下

- demo.py

- interactive.py


目录和文件介绍

[[email protected] tmp]# tree

.

├── hosts_user

│   ├── qqandroid.txt       #qqandroid服的ip

│   ├── qqios.txt         #qqios服的ip

│   └── users.txt         #有权限登陆的用户

├── record_comm

│   └── record.txt           #记录用户操作过的命令文件

└── script

    ├── demo.py             #主程序文件

    ├── interactive.py        #被调用的记录用户操作过的命令的脚本

    └── zj.py              #登陆菜脚本


3 directories, 7 files



修改interactive.py脚本标红色的为我修改过的内容

#coding:utf-8


import socket

import sys

import time


# windows does not have termios...

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)

    f=file(‘/tmp/record_comm/record.txt‘,‘ab+‘)   #把命令追加进此文件

    try:

        tty.setraw(sys.stdin.fileno())

        tty.setcbreak(sys.stdin.fileno())

        chan.settimeout(0.0)

        records = []   #定义一个空列表

        while True:

            r, w, e = select.select([chan, sys.stdin], [], [])

            if chan in r:

                try:

                    x = chan.recv(1024)

                    if len(x) == 0:

                        print ‘\r\n*** EOF\r\n‘,

                        break

                    sys.stdout.write(x)

                    sys.stdout.flush()

                except socket.timeout:

                    pass

            if sys.stdin in r:          #屏幕接收

                x = sys.stdin.read(1)    #每次只接收一个字符

                records.append(x)

                if x == ‘\r‘:         #每个命令输完回车都有一个\r

                    c_time = time.strftime(‘%Y-%m-%d %H:%M:%S‘)

                    cmd = ‘‘.join(records).replace(‘\r‘,‘\n‘)#\r win换行,\n Linux的换行

                    log = ‘%s   %s‘ %(c_time,cmd)

                    f.write(log)    

              f.flush() 

                    records = []

                if len(x) == 0:

                    break

                chan.send(x)


    finally:

        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)

        f.close()

    

# thanks to Mike Looijmans for this code

def windows_shell(chan):

    import threading


    sys.stdout.write("Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n")

        

    def writeall(sock):

        while True:

            data = sock.recv(256)

            if not data:

                sys.stdout.write(‘\r\n*** EOF ***\r\n\r\n‘)

                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



编写zj.py(登陆菜单)脚本

[[email protected] script]# cat zj.py 

#!/usr/bin/python

#coding:utf-8

import os


def hosts_group():

    os.system(‘clear‘)

    print ‘‘‘

            【主机组列表】

             1、qqandroid组

             2、qqios组


           ‘‘‘

    global x_1

    x_1 = raw_input( ‘请输入你要选择的主机组编号:‘)


def hosts():

    global lines

    if x_1 == ‘1‘:

            os.system(‘clear‘)

            print ‘‘‘

            【qqandiod主机组列表】

             1、qqandroid1服

             2、qqandroid2服

             3、返回上一页 

            ‘‘‘

            x_21 = raw_input( ‘请输入你要登陆的服务器编号:‘)

            f = file(‘/tmp/hosts_user/qqandroid.txt‘)

            for line in f.readlines():

                line = line.strip(‘\n‘).split(‘ ‘)

                if line[0] == x_21:

                           lines = line[1]

            if x_21 == ‘3‘:

                os.system(‘/usr/bin/python /tmp/script/demo.py‘)

    else:

             os.system(‘clear‘)

             print ‘‘‘

            【qqios主机组列表】

             1、qqios1服

             2、qqios2服

             3、返回上一页

            ‘‘‘

             x_22 = raw_input( ‘请输入你要登陆的服务器:‘)

             f = file(‘/tmp/hosts_user/qqios.txt‘)

             for line in f.readlines():

                line = line.strip(‘\n‘).split(‘ ‘)

                if line[0] == x_22:

                           lines = line[1]

             if x_22 == ‘3‘:

                os.system(‘/usr/bin/python /tmp/script/demo.py‘)


def yh():

    os.system(‘clear‘)

    print ‘‘‘

         【用户列表】

          1、wsyht

          2、jenkins

          3、peter

          4、返回首页

    ‘‘‘

    xyh = raw_input( ‘请输入你要登陆的用户编号:‘)


    global youruser

    f = file(‘/tmp/hosts_user/users.txt‘)

    for line in f.readlines():

            line = line.strip(‘\n‘)

            line = line.split(‘ ‘)

            if line[0] == xyh:

                    youruser = line[1]

    if xyh == ‘4‘:

            os.system(‘/usr/bin/python /tmp/script/demo.py‘)



修改paramiko脚本标红色的为我修改过的内容

cat /tmp/script/demo.py

#!/usr/bin/env python


import base64

from binascii import hexlify

import getpass

import os

import select

import socket

import sys

import threading

import time

import traceback

import paramiko

import interactive

import zj


def agent_auth(transport, username):

    """

    Attempt to authenticate to the given transport using any of the private

    keys available from an SSH agent.

    """

    

    agent = paramiko.Agent()

    agent_keys = agent.get_keys()

    if len(agent_keys) == 0:

        return

        

    for key in agent_keys:

        print ‘Trying ssh-agent key %s‘ % hexlify(key.get_fingerprint()),

        try:

            transport.auth_publickey(username, key)

            print ‘... success!‘

            return

        except paramiko.SSHException:

            print ‘... nope.‘



def manual_auth(username, hostname):

    default_auth = ‘p‘

    auth = raw_input(‘Auth by (p)assword, (r)sa key, or (d)ss 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 = raw_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)

        t.auth_publickey(username, key)

    elif auth == ‘d‘:

        default_path = os.path.join(os.environ[‘HOME‘], ‘.ssh‘, ‘id_dsa‘)

        path = raw_input(‘DSS key [%s]: ‘ % default_path)

        if len(path) == 0:

            path = default_path

        try:

            key = paramiko.DSSKey.from_private_key_file(path)

        except paramiko.PasswordRequiredException:

            password = getpass.getpass(‘DSS key password: ‘)

            key = paramiko.DSSKey.from_private_key_file(path, password)

        t.auth_publickey(username, key)

    else:

        pw = getpass.getpass(‘Password for %[email protected]%s: ‘ % (username, hostname))

        t.auth_password(username, pw)



# setup logging

paramiko.util.log_to_file(‘demo.log‘)


username = ‘‘

if len(sys.argv) > 1:

    hostname = sys.argv[1]

    if hostname.find(‘@‘) >= 0:

        username, hostname = hostname.split(‘@‘)

else:

    zj.hosts_group()

    zj.hosts()

    #hostname = raw_input(‘Hostname: ‘)

    hostname = zj.lines

if len(hostname) == 0:

    print ‘*** Hostname required.‘

    sys.exit(1)

port = 22

if hostname.find(‘:‘) >= 0:

    hostname, portstr = hostname.split(‘:‘)

    port = int(portstr)


# now connect

try:

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    sock.connect((hostname, port))

except Exception, e:

    print ‘*** Connect failed: ‘ + str(e)

    traceback.print_exc()

    sys.exit(1)


try:

    t = paramiko.Transport(sock)

    try:

        t.start_client()

    except paramiko.SSHException:

        print ‘*** SSH negotiation failed.‘

        sys.exit(1)


    try:

        keys = paramiko.util.load_host_keys(os.path.expanduser(‘~/.ssh/known_hosts‘))

    except IOError:

        try:

            keys = paramiko.util.load_host_keys(os.path.expanduser(‘~/ssh/known_hosts‘))

        except IOError:

            print ‘*** Unable to open host keys file‘

            keys = {}


    # check server‘s host key -- this is important.

    key = t.get_remote_server_key()

    if not keys.has_key(hostname):

        print ‘*** WARNING: Unknown host key!‘

    elif not keys[hostname].has_key(key.get_name()):

        print ‘*** WARNING: Unknown host key!‘

    elif keys[hostname][key.get_name()] != key:

        print ‘*** WARNING: Host key has changed!!!‘

        sys.exit(1)

    else:

        print ‘*** Host key OK.‘


    # get username

    if username == ‘‘:

        default_username = getpass.getuser()

        #username = raw_input(‘Username [%s]: ‘ % default_username)

        zj.yh()

        username = zj.youruser

        if len(username) == 0:

            username = default_username

    agent_auth(t, username)

    if not t.is_authenticated():

        manual_auth(username, hostname)

    if not t.is_authenticated():

        print ‘*** Authentication failed. :(‘

        t.close()

        sys.exit(1)


    chan = t.open_session()

    chan.get_pty()

    chan.invoke_shell()

    print ‘*** Here we go!‘

    print

    f = file(‘/tmp/record_comm/record.txt‘,‘ab+‘)

    sj = time.strftime(‘%c‘)

    f.write(‘\n‘)

    f.write(username + ‘ ‘+ ‘login:‘ + ‘ ‘ + sj + ‘ ‘ + ‘from‘ + ‘ ‘ + ‘192.168.200.10‘ + ‘\n‘)

    f.close()

    interactive.interactive_shell(chan)

    f = file(‘record.txt‘,‘ab+‘)

    f.write(username + ‘ ‘+ ‘exit:‘ + ‘ ‘ + sj  + ‘\n‘)

    chan.close()


except Exception, e:

    print ‘*** Caught exception: ‘ + str(e.__class__) + ‘: ‘ + str(e)

    traceback.print_exc()

    try:

        t.close()

    except:

        pass

    sys.exit(1)



登陆后端的IP和用户(标红色为一会要登陆的用户IP)

[[email protected] tmp]# cat hosts_user/qqandroid.txt 

1 192.168.200.10

2 192.168.200.20

3 192.168.200.30

[[email protected] tmp]# cat hosts_user/qqios.txt 

1 192.168.200.11        

2 192.168.200.21

3 192.168.200.31

[[email protected] tmp]# cat hosts_user/users.txt 

1 wsyht

2 jenkins

3 peter



准备用户环境

192.168.200.11后端服务机器添加一个普通账户wsyht,并设置好密码作为登陆服务器之用,生产环境应该用ssh+key,我这里演示就不用了,后端服务器不允许root用户登陆

保垒机添加一个公共用户gg123,gg123的家目录.bashrc最下面添加以下两行代码

[[email protected] tmp]# tail -2 /home/gg123/.bashrc 

/usr/bin/python /tmp/script/demo.py   #公共用户登陆则执行跳板脚本,脚本停止则注销用户,除了保垒机管理员可以登陆保垒机,普通用户不允许登陆该机器

login



记录命令文件的目录(该目录普通用户要有可写权限,不然用户登陆后端务器所操作的命令无法记录下来,我这里直接给他设777)

技术分享



保垒机IP:192.168.200.10

后端服务器IP:192.168.200.11



登陆演示:

技术分享


技术分享


技术分享


技术分享


技术分享


技术分享



保垒机管理员(root)登陆查看用户操作过的命令

技术分享



本文出自 “wsyht90的博客” 博客,请务必保留此出处http://wsyht90.blog.51cto.com/9014030/1844026

以上是关于Python修改paramiko模块开发运维审计保垒机的主要内容,如果未能解决你的问题,请参考以下文章

Python学习总结 paramiko 项目运维

python的paramiko源码修改了一下,写了个操作命令的日志审计 bug修改

Python中paramiko模块在linux运维中应用

Python_oldboy_自动化运维之路_paramiko,mysql

Python:跳板机审计服务器

基于SQLAlchemy实现的堡垒机