利用Python Paramiko開發linux堡壘機

1.Paramiko模塊下的demo.py程序python

    前面利用Python中的Paramiko模塊能夠進行SSH的鏈接,以及用來傳送文件(SFTP),可是不管是哪種方式,鏈接都是短暫的,並不是是長連的,即一次執行一個命令或上傳與下載一個文件,顯然效率是不如直接使用Linux shell下的ssh鏈接命令來進行鏈接。其實在將Paramiko的源碼解壓出來後,裏面有一個demo的文件夾,裏面有一個demo.py的程序,利用它,咱們就能夠進行長鏈接,即像ssh同樣鏈接遠程主機:mysql

xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6/sorftwares/demp_test/paramiko-1.7.7.1$ ls
demos  LICENSE      paramiko           PKG-INFO  setup.cfg        setup.py  tests
docs   MANIFEST.in  paramiko.egg-info  README    setup_helper.py  test.py
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6/sorftwares/demp_test/paramiko-1.7.7.1$ cd demos/
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6/sorftwares/demp_test/paramiko-1.7.7.1/demos$ ls -l demo.py
-rwxrwxrwx 1 root root 5340  6月 16  2010 demo.py

    利用demo.py程序,咱們能夠進行ssh的長鏈接,好比這裏有一臺IP地址爲192.168.1.124的遠程主機須要進行鏈接,使用遠程主機的帳戶名爲xpleaf,以下:ios

xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6/sorftwares/demp_test/paramiko-1.7.7.1/demos$ python demo.py 
Hostname: 192.168.1.124
*** Host key OK.
Username [xpleaf]: xpleaf
Auth by (p)assword, (r)sa key, or (d)ss key? [p] 
Password for xpleaf@192.168.1.124: 
*** Here we go!

Last login: Fri Oct  9 17:19:42 2015 from 192.168.1.13
[xpleaf@moban ~]$ pwd
/home/xpleaf

    這樣咱們就能夠像ssh鏈接同樣在遠程主機上執行相關的命令了,下面,咱們就是經過觀察demo.py程序的源代碼來對相關的程序模塊做修改,而後實現簡單的堡壘主機監控程序的開發。
web


2.經過修改與demo.py相關的模塊來達到堡壘主機監控的功能sql

    堡壘主機能夠監控運維人員在服務器上作的命令操做,這裏要作的,只是能夠監控運維人員在Linux服務器上執行命令的操做,下面先給出這個監控程序的示意圖:
shell

運維人員登錄認證示意圖:數據庫

wKioL1YXtrjiuvFFAAMvP_erInQ280.jpg

運維人員命令監控記錄示意圖:小程序

wKioL1YXtvex6g2sAAF1xGkmXWA643.jpg

    

    基於上面兩個圖示的說明,以及對Paramiko模塊中demo.py程序代碼的理解,能夠對demo.py模塊以及相關的模塊程序源代碼做相應的修改,至於這個引導的過程,由於是Alex老師引導過來的,根據Alex老師修改源代碼的一些思想,而後本身再進一步修改其它部分的源代碼,因此這整一個探索的過程若是要講出來,篇幅比較大,這裏就不說起了,下面直接給代碼:windows


修改後的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


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,pw):
    '''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 %s@%s: ' % (username, hostname))
        t.auth_password(username, pw)'''
    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:
    hostname = raw_input('Hostname: ')
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)
        if len(username) == 0:
            username = default_username'''
    #changed by xpleaf at 2015.10.9
    username = sys.argv[2]
    password = sys.argv[3]
    sa_username = sys.argv[4]

    agent_auth(t, username)
    if not t.is_authenticated():
        manual_auth(username, hostname,password)
    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
    interactive.interactive_shell(chan,hostname,username,sa_username)
    chan.close()
    t.close()

except Exception, e:
    print '*** Caught exception: ' + str(e.__class__) + ': ' + str(e)
    traceback.print_exc()
    try:
        t.close()
    except:
        pass
    sys.exit(1)


修改後的interactive.py源代碼:

import socket
import sys,time

# windows does not have termios...
try:
    import termios
    import tty
    has_termios = True
except ImportError:
    has_termios = False


def interactive_shell(chan,hostname,username,sa_username):
    if has_termios:
        posix_shell(chan,hostname,username,sa_username)
    else:
        windows_shell(chan)


def posix_shell(chan,hostname,username,sa_username):
    import select
    date = time.strftime('%Y_%m_%d') #Here is changed!
    f = file('/tmp/%s_%s_record.log' % (sa_username,date),'a+') #Here is changed!
    record = [] #Here is changed!

    oldtty = termios.tcgetattr(sys.stdin)
    try:
        tty.setraw(sys.stdin.fileno())
        tty.setcbreak(sys.stdin.fileno())
        chan.settimeout(0.0)

        while True:
            date = time.strftime('%Y_%m_%d %H:%M:%S') #Here is changed!
            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)
                if len(x) == 0:
                    break
                #print x
                record.append(x)
                chan.send(x)

            if x == '\r':    #Here is changed!Follow:
                #print record
                cmd = ''.join(record).split('\r')[-2]
                log = "%s | %s | %s | %s\n" % (hostname,date,sa_username,cmd)
                f.write(log)
                f.flush()
        f.close()    #Here is changed!Above:

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

    
# 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


存放在堡壘主機下的Menus程序,這裏命名爲run_demo.py:

#!/usr/bin/env python
import os,MySQLdb

os.system('clear')
print '='*35
print '''\033[32;1mWelcome to the Connecting System!\033[0m
Choose the Server to connect:
1.DNS Server:  192.168.1.124
2.DHCP Server: 192.168.1.134'''
print '='*35
choice = raw_input('Your choice:')

	

if choice == '1':
        address = '192.168.1.124'
elif choice == '2':
        address = '192.168.1.134'

sa_user = 'yonghaoye'


try:
        conn = MySQLdb.connect(host = 'localhost', user = 'root', \
                passwd = '123456', db = 'Server_list', port = 3306)
        cur = conn.cursor()
        cur.execute("select * from users where sa = '%s'" % sa_user)
        qur_result = cur.fetchall()

        for record in qur_result:
                if record[3] == address:
                        hostname = record[3]
                        username = record[4]
                        password = record[5]
        cur.close()
        conn.close()
except MySQLdb.Error,e:
        print 'Mysql Error Msg:',e

cmd = 'python /mnt/hgfs/Python/day6/sorftwares/paramiko-1.7.7.1/demos/demo.py %s %s %s %s' % (hostname,username,password,sa_user)
os.system(cmd)


在堡壘主機上添加數據庫:

添加了下面這樣的數據庫
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| Server_list        |
| ftp_user           |
| linkman            |
| mysql              |
| performance_schema |
| s6py               |
+--------------------+
7 rows in set (0.01 sec)

mysql> use Server_list
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+-----------------------+
| Tables_in_Server_list |
+-----------------------+
| users                 |
+-----------------------+
1 row in set (0.00 sec)

mysql> describe users;
+-----------------+------------------+------+-----+---------+----------------+
| Field           | Type             | Null | Key | Default | Extra          |
+-----------------+------------------+------+-----+---------+----------------+
| id              | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| sa              | char(20)         | NO   |     | NULL    |                |
| server_name     | char(20)         | NO   |     | NULL    |                |
| server_address  | char(20)         | NO   |     | NULL    |                |
| server_username | char(20)         | NO   |     | NULL    |                |
| server_password | char(20)         | NO   |     | NULL    |                |
+-----------------+------------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)

mysql> selec * from users;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'selec * from users' at line 1
mysql> select * from users;
+----+-----------+-------------+----------------+-----------------+-----------------+
| id | sa        | server_name | server_address | server_username | server_password |
+----+-----------+-------------+----------------+-----------------+-----------------+
|  1 | yonghaoye | DNS Server  | 192.168.1.124  | xpleaf          | 123456          |
|  2 | yonghaoye | DHCP Server | 192.168.1.134  | public          | 123456          |
+----+-----------+-------------+----------------+-----------------+-----------------+
2 rows in set (0.00 sec)

就不對數據庫中的內容作解釋說明了,其實看了前面的示意圖,再看這裏的代碼就能夠理解了。


3.監控程序演示

    演示的網絡環境以下:

wKiom1YX6Hqh83g9AACrMe-qv88213.jpg

    因爲我在堡壘主機上安裝了shellinabox程序,因此在運維人員主機上,能夠直接在web界面輸入堡壘主機的IP地址進行遠程鏈接,來看下面操做:

(1)運維人員主機登錄堡壘主機

wKiom1YX6gjxakHcAAJTCWbW1cw906.jpg


(2)輸入堡壘主機帳號密碼

wKioL1YX6pzzjycKAAHCjTRCIFU430.jpg


(3)登錄成功並進入服務器鏈接列表選擇界面

wKiom1YX68_zfdPcAANYG-EGNyQ242.jpg


(4)選擇鏈接相應服務器

wKioL1YX7Omx4QqnAAME0UKu7gU217.jpg


(5)運維人員執行相關命令

wKiom1YX7aXBHkGSAAOANMMjiEQ254.jpg


(6)在堡壘主機上查看運維人員的命令操做

xpleaf@xpleaf-machine:/tmp$ tail -f yonghaoye_2015_10_10_record.log 
192.168.1.124 | 2015_10_10 00:36:44 | yonghaoye | pwd
192.168.1.124 | 2015_10_10 00:36:48 | yonghaoye | whoami
192.168.1.124 | 2015_10_10 00:37:13 | yonghaoye | echo $PATH

    能夠看到,在堡壘主機上生成了一個相對應用戶的命令記錄日誌文件,這裏能夠查看用戶執行的每個命令,須要注意的是,這裏記錄了用戶名「yonghaoye」,是堡壘主機上的用戶,並非Linux服務器上面的,該用戶是分配給運維人員的,所以,也再一次看到,運維人員並不知道Linux服務器的帳戶和密碼,這樣就比較安全了。


3.不足與優化思路

    經過上面的操做,這樣的一個程序確實是能夠記錄運維人員在Linux服務器上作的操做,可是不足的是:

(1)程序還存在很是多的細節問題和Bug

(2)界面操做不夠人性化

    但無論怎麼說,這個小程序只是做爲學習過程當中的一個練習程序而已,但思路基本上是沒有問題的,根據上面的兩個缺點,日後能夠進一步修改源代碼以保證程序運行的穩定性,同時對於界面問題,日後應該是要作成Web界面的,而不是藉助shellinabox程序,這就須要調用Python中的Django模塊來作Web方面的開發,固然還有其它技術。

    剛過國慶放假期間,看到Alex老師開發了一個開源的堡壘機監控程序,你們能夠去看看,而我這裏所的這個小程序,做爲入門來學習,其實也是很是不錯的。

    Alex老師開發的開源軟件:http://3060674.blog.51cto.com/3050674/1700814

    真的,那就很是了不起了,目前本身也在努力學習過程當中,堅持下來就必定能夠學到不少!Python不會讓咱們失望的!


4.對於堡壘主機監控程序的進一步開發計劃

    因爲如今知道的真的是太少,日後會不斷學習,但願之後也能以這裏這個小程序的思路本身開發一個開源的堡壘主機監控系統,雖然目前已經有開源的了,但做爲本身來練手我想也是很是不錯的。


    文章的思路寫得有點唐突,由於實在是很難把這其中學習的一個完整的過程寫下來,由於所花費時間很是多。因此我選擇了在演示操做裏進行了更多的說明,至於源代碼的修改,有興趣的朋友能夠對比修改前的代碼進行比對的。

相關文章
相關標籤/搜索