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
運維人員登錄認證示意圖:數據庫
運維人員命令監控記錄示意圖:小程序
基於上面兩個圖示的說明,以及對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.監控程序演示
演示的網絡環境以下:
因爲我在堡壘主機上安裝了shellinabox程序,因此在運維人員主機上,能夠直接在web界面輸入堡壘主機的IP地址進行遠程鏈接,來看下面操做:
(1)運維人員主機登錄堡壘主機
(2)輸入堡壘主機帳號密碼
(3)登錄成功並進入服務器鏈接列表選擇界面
(4)選擇鏈接相應服務器
(5)運維人員執行相關命令
(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.對於堡壘主機監控程序的進一步開發計劃
因爲如今知道的真的是太少,日後會不斷學習,但願之後也能以這裏這個小程序的思路本身開發一個開源的堡壘主機監控系統,雖然目前已經有開源的了,但做爲本身來練手我想也是很是不錯的。
文章的思路寫得有點唐突,由於實在是很難把這其中學習的一個完整的過程寫下來,由於所花費時間很是多。因此我選擇了在演示操做裏進行了更多的說明,至於源代碼的修改,有興趣的朋友能夠對比修改前的代碼進行比對的。