Twisted SSH
Twisted是一個網絡應用程序框架。在這篇文章里,你會學習到如何在twisted中使用Secure shell(SSH)來完成各種實用的工作任務。這篇文章摘自《wisted Network Programming Essentials》第十章,作者是 Abe Fettig (O’Reilly, 2007; ISBN: 0596100329). Copyright ? 2007 O’Reilly Media, Inc. All rights reserved。
SSH, Secure SHell是一個許多開發人員和網絡管理員必備的工具。SSH提供一個加密的通道,并且需要授權才可以訪問的。當然最常用的是用來使用shell操作遠程服務器,除此之外還可以通過SSH傳輸文件和隧道連接。
Twisted.conch包給twisted提供了SSH支持。這章文章內容會為你展示使用twisted.conch模塊來建立ssh服務和ssh客戶端。
設置一個自定義的SSH服務
命令行對于某些任務是非常有效的接口。系統管理人員喜歡使用敲命令來管理服務器應用而不是使用點擊圖形界面。SSH shell更是極好的,因為它可以從網絡上任何地方訪問這個功能
如何做?
編寫一個twisted.conch.recvline.HistoricRecvLine子類來實現你的shell協議。HistoricRecvLine 類似twisted.protocols.basic.LineReceiver,但是對于控制終端它有更高級別的特性
為了實現SSH shell功能,你需要你需要使用一個不同類型的類twisted.conch來構建ssh服務。第一,你需要twisred.cred認證類:是一個portal,憑證校驗,返回用戶真實身份的類。使用twisted.conch.avatar.ConchUser基類來實現你的虛擬化。你的虛擬化類需要實現twisted.conch.interfaces.ISession,包含一個OPENshell方法,該方法創建一種協議來管理交互會話。最后,創建twisted.conch.ssh.factory.SSHFactory對象并且將其portal 屬性設置為你自己的portal 實例
Example 10-1 演示了一個典型的利用用戶名和密碼來驗證的ssh服務。它給每個用戶一個shell用來執行幾種命令。
Example 10-1 sshserver.py
from twisted.cred import portal, checkers, credentials from twisted.conch import error, avatar, recvline, interfaces as conchinterfaces from twisted.conch.ssh import factory, userauth, connection, keys, session, common from twisted.conch.insults import insultsfrom twisted.application import service, internet from zope.interface import implements import osclass SSHDemoProtocol(recvline.HistoricRecvLine):def __init__(self, user):self.user = userdef connectionMade(self) :recvline.HistoricRecvLine.connectionMade(self)self.terminal.write("Welcome to my test SSH server.")self.terminal.nextLine()self.do_help()self.showPrompt()def showPrompt(self):self.terminal.write("$ ")def getCommandFunc(self, cmd):return getattr(self, ‘do_’ + cmd, None)def lineReceived(self, line):line = line.strip()if line:cmdAndArgs = line.split()cmd = cmdAndArgs[0]args = cmdAndArgs[1:]func = self.getCommandFunc(cmd)if func:try:func(*args)except Exception, e:self.terminal.write("Error: %s" % e)self.terminal.nextLine()else:self.terminal.write("No such command.")self.terminal.nextLine()self.showPrompt()def do_help(self, cmd=”):"Get help on a command. Usage: help command"if cmd:func = self.getCommandFunc(cmd)if func:self.terminal.write(func.__doc__)self.terminal.nextLine()returnpublicMethods = filter(lambda funcname: funcname.startswith(‘do_’), dir(self))commands = [cmd.replace(‘do_’, ”, 1) for cmd in publicMethods]self.terminal.write("Commands: " + " ".join(commands))self.terminal.nextLine()def do_echo(self, *args):"Echo a string. Usage: echo my line of text"self.terminal.write(" ".join(args))self.terminal.nextLine()def do_whoami(self):"Prints your user name. Usage: whoami"self.terminal.write(self.user.username)self.terminal.nextLine()def do_quit(self):"Ends your session. Usage: quit"self.terminal.write("Thanks for playing!")self.terminal.nextLine()self.terminal.loseConnection()def do_clear(self):"Clears the screen. Usage: clear"self.terminal.reset()class SSHDemoAvatar(avatar.ConchUser):implements(conchinterfaces.ISession)def __init__(self, username):avatar.ConchUser.__init__(self)self.username = usernameself.channelLookup.update({‘session’:session.SSHSession})def openShell(self, protocol):serverProtocol = insults.ServerProtocol(SSHDemoProtocol, self)serverProtocol.makeConnection(protocol)protocol.makeConnection(session.wrapProtocol(serverProtocol))def getPty(self, terminal, windowSize, attrs):return Nonedef execCommand(self, protocol, cmd):raise NotImplementedErrordef closed(self):passclass SSHDemoRealm:implements(portal.IRealm)def requestAvatar(self, avatarId, mind, *interfaces):if conchinterfaces.IConchUser in interfaces:return interfaces[0], SSHDemoAvatar(avatarId), lambda: Noneelse:raise Exception, "No supported interfaces found."def getRSAKeys():if not (os.path.exists(‘public.key’) and os.path.exists(‘private.key’)):# generate a RSA keypairprint "Generating RSA keypair…"from Crypto.PublicKey import RSAKEY_LENGTH = 1024rsaKey = RSA.generate(KEY_LENGTH, common.entropy.get_bytes)publicKeyString = keys.makePublicKeyString(rsaKey)privateKeyString = keys.makePrivateKeyString(rsaKey)# save keys for next timefile(‘public.key’, ‘w+b’).write(publicKeyString)file(‘private.key’, ‘w+b’).write(privateKeyString)print "done."else:publicKeyString = file(‘public.key’).read()privateKeyString = file(‘private.key’).read()return publicKeyString, privateKeyStringif __name__ == "__main__":sshFactory = factory.SSHFactory()sshFactory.portal = portal.Portal(SSHDemoRealm())users = {‘admin’: ‘aaa’, ‘guest’: ‘bbb’}sshFactory.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))pubKeyString, privKeyString = getRSAKeys()sshFactory.publicKeys = {‘ssh-rsa’: keys.getPublicKeyString(data=pubKeyString)}sshFactory.privateKeys = {‘ssh-rsa’: keys.getPrivateKeyObject(data=privKeyString)}from twisted.internet import reactorreactor.listenTCP(2222, sshFactory)reactor.run()python3
#!/usr/bin/env python # --*-- coding:UTF-8 --*--from __future__ import absolute_import, division import twisted from twisted import cred from twisted.cred import portal, checkers, credentials from twisted.conch import error, avatar, recvline, interfaces as conchinterfaces from twisted.conch.ssh import factory, userauth, connection, keys, session, common from twisted.conch.insults import insultsfrom twisted.application import service, internet from zope.interface import implementer import osclass SSHDemoProtocol(recvline.HistoricRecvLine):def __init__(self, user):self.user = userdef connectionMade(self):recvline.HistoricRecvLine.connectionMade(self)self.terminal.write("Welcome to my test SSH server.")self.terminal.nextLine()self.do_help()self.showPrompt()def showPrompt(self):self.terminal.write("$ ")def getCommandFunc(self, cmd):return getattr(self, 'do_' + cmd, None)def lineReceived(self, line):line = line.strip()if line:cmdAndArgs = line.split()cmd = cmdAndArgs[0]args = cmdAndArgs[1:]func = self.getCommandFunc(cmd)if func:try:func(*args)except Exception as e:self.terminal.write("Error: %s" % e)self.terminal.nextLine()else:self.terminal.write("No such command.")self.terminal.nextLine()self.showPrompt()def do_help(self, cmd=''):"Get help on a command. Usage: help command"if cmd:func = self.getCommandFunc(cmd)if func:self.terminal.write(func.__doc__)self.terminal.nextLine()returnpublicMethods = filter(lambda funcname: funcname.startswith('do_'), dir(self))commands = [cmd.replace('do_','', 1) for cmd in publicMethods]self.terminal.write("Commands: " + " ".join(commands))self.terminal.nextLine()def do_echo(self, *args):"Echo a string. Usage: echo my line of text"self.terminal.write(" ".join(args))self.terminal.nextLine()def do_whoami(self):"Prints your user name. Usage: whoami"self.terminal.write(self.user.username)self.terminal.nextLine()def do_quit(self):"Ends your session. Usage: quit"self.terminal.write("Thanks for playing!")self.terminal.nextLine()self.terminal.loseConnection()def do_clear(self):"Clears the screen. Usage: clear"self.terminal.reset()@implementer(conchinterfaces.ISession) class SSHDemoAvatar(avatar.ConchUser):#implements(conchinterfaces.ISession)def __init__(self, username):avatar.ConchUser.__init__(self)self.username = usernameself.channelLookup.update({'session':session.SSHSession})def openShell(self, protocol):serverProtocol = insults.ServerProtocol(SSHDemoProtocol, self)serverProtocol.makeConnection(protocol)protocol.makeConnection(session.wrapProtocol(serverProtocol))def getPty(self, terminal, windowSize, attrs):return Nonedef execCommand(self, protocol, cmd):raise NotImplementedErrordef closed(self):pass @implementer(portal.IRealm) class SSHDemoRealm:#implements(portal.IRealm)def requestAvatar(self, avatarId, mind, *interfaces):if conchinterfaces.IConchUser in interfaces:return interfaces[0], SSHDemoAvatar(avatarId), lambda: Noneelse:#raise Exception as "No supported interfaces found."print (11111)def getRSAKeys():if not (os.path.exists('public.key') and os.path.exists('private.key')):# generate a RSA keypairprint ("Generating RSA keypair…")KEY_LENGTH = 1024from cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives.asymmetric import rsarsaKey = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())publicKeyString = keys.Key(rsaKey).public().toString('openssh')privateKeyString = keys.Key(rsaKey).toString('openssh')# save keys for next timeopen('public.key', 'w+b').write(publicKeyString)open('private.key', 'w+b').write(privateKeyString)print ("done.")else:publicKeyString = open('public.key').read()privateKeyString = open('private.key').read()return publicKeyString, privateKeyStringif __name__ == "__main__":sshFactory = factory.SSHFactory()sshFactory.portal = portal.Portal(SSHDemoRealm())users = {'admin': 'aaa', 'guest': 'bbb'}sshFactory.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))pubKeyString, privKeyString = getRSAKeys()sshFactory.publicKeys = {b'ssh-rsa': keys.Key.fromString(data=pubKeyString)}sshFactory.privateKeys = {b'ssh-rsa': keys.Key.fromString(data=privKeyString)}from twisted.internet import reactorreactor.listenTCP(3333, sshFactory)reactor.run(){mospagebreak title=Setting Up a Custom SSH Server continued}
sshserver.py將會運行ssh在 2222 端口. 使用用戶名密碼都是aaa連接該ssh服務器并且嘗試輸入一些命令:
?
?如果你本地機器已經存在一個ssh服務,使用這個例子的代碼時候你可能會看到一些報錯信息,
? 類似"Remote host identification has changed” or “Host key verification failed,”
? 并且你的ssh客戶端拒絕連接
? 造成這種報錯的原因是你的ssh客戶端已經有存儲記憶了你通常使用的本地ssh服務器的public key。
? 使用example10-1的代碼開啟的ssh服務有一個它自己的key,并且當客戶端連接的時候會檢測到這個keys和本地不同,
? 它會懷疑這個新的ssh服務是一個假冒本地服務器的ssh服務器。為了解決這個問題,編輯你的 ~/.ssh/known_hosts文件(或者你的ssh客戶端保存識別服務器的文件)并且清除本地主機這條
?
? 它是如何工作的?
? 在例1的SSHDemoProtocol類從twisted.conch.recvline.HistoricRecvline繼承而來,HistoricRecvLine 是一個具有構建命令行shell的內置功能的協議。它提供了當下大多數人所使用的shell命令行常用功能
? ,包括backspacing,可以使用方向鍵控制光標向前與向后,并且可以使用上下鍵顯示歷史命令記錄,twisted.conch.recvline也可以提供了一個簡單的RecvLine類,但它的工作方式比較簡單
? 不可以顯示命令歷史紀錄
? HistoricRecvLine中的lineReceived方法在用戶輸入一行時被調用。例10-1顯示了如何重寫這個方法來解析和執行命令。在HistoricRecvLine和一個常規協議之間有一些區別,它們來自于這樣一個事實,即使用HistoricRecvLine,你實際上是在操縱用戶終端窗口的當前內容,而不是僅僅打印文本。要打印一行輸出,請使用self.terminal.write;去下一行,使用self.nextLine。
twisted.conch.avatar.ConchUser類表示可用于經過身份驗證的SSH用戶的操作。默認情況下,ConchUser不允許客戶端執行任何操作。為了使用戶能夠得到一個shell,使他的頭像實現twisted.conch.interfaces.ISession。例10-1中的SSHDemoAvatar類實際上并不實現所有的ISession;它只為實現用戶使用shell。 openShell方法將使用twisted.conch.ssh.session來調用。 SSHSessionProcessProtocol object that represents the encrypted client’s end of the encrypted channel。您必須執行一些步驟才能將客戶端的協議連接到您的shell協議,以便它們可以相互通信。首先,將您的協議類包裝在twisted.conch.insults.insults.ServerProtocol對象中。您可以將額外的參數傳遞給insult.ServerProtocol,它將使用它們來初始化您的協議對象。這是為了使用虛擬終端而設置您的協議。然后使用makeConnection將兩個協議相互連接。客戶端的協議實際上期望makeConnection被一個實現了底層的twisted.internet.interfaces.ITransport接口的對象所調用,而不是一個協議。 twisted.conch.session.wrapProtocol函數將協議包裝在最小的ITransport接口中。
--------------------------------------------------------------
傳統上用于操作Unix終端的庫稱為curses。所以Twisted開發者從來不愿意放棄在模塊名稱中使用雙關語的機會,而是選擇了這個類的終端編程庫的名字。
要為SSH服務器創建realm ,請編寫一個具有requestAvatar方法的類。 SSH服務器將調用requestAvatar,用戶名為avatarId,twisted.conch.interfaces.IAvatar作為接口之一。返回你的twisted.conch的子類。 avatar.ConchUser。
還有一件事你需要有一個完整的SSH服務器:一套獨特的公鑰和私鑰。例10-1演示了如何使用Crypto.PublicKey.RSA模塊來生成這些密鑰。 RSA.generate以密鑰長度作為第一個參數,entropy-generating函數為第二個參數; twisted.conch.ssh.common模塊為此提供了entropy.get_bytes函數。 RSA.generate返回一個Crypto.PublicKey.RSA.RSAobj對象。您從RSAobj中提取公鑰和私鑰字符串,然后將其傳遞給twisted.conch.ssh.keys模塊中的getPublicKeyString和getPrivateKeyString函數。示例10-1在第一次生成密鑰時將其密鑰保存到磁盤:您需要在客戶端之間保留這些密鑰,以便客戶端可以識別和信任服務器。
??? 請注意,在程序進入Twisted事件循環后,您不想調用RSA.generate。 RSA.generate是一個阻塞函數,可能需要一段時間才能完成。
要運行SSH服務器,請創建一個twisted.conch.ssh.factory.SSHFactory對象。使用您的realm 將其portal 屬性設置為portal ,并注冊一個可以處理twisted.cred.credentials.IUsernamePassword憑據的憑證檢查器。將SSHFactory的publicKeys屬性設置為將加密算法與密鑰字符串對象相匹配的字典。要獲得RSA密鑰字符串對象,請將您的公鑰作為data關鍵字傳遞給keys.getPublicKeyString。然后將privateKeys屬性設置為匹配關鍵對象的協議的字典。要獲得RSA私鑰對象,請將您的私鑰作為data關鍵字傳遞給keys.getPrivateKey。 getPublicKeyString和getPrivateKey都可以使用文件名關鍵字來直接從文件加載密鑰。一旦SSHFactory有密鑰,就可以開始了。調用reactor.listenTCP讓它開始監聽一個端口,然后你有一個SSH服務器了。
{mospagebreak title =使用公共密鑰進行身份驗證}
示例10-1中的SSH服務器使用用戶名和密碼進行身份驗證。但是大量的SSH用戶會告訴你,SSH最好的功能之一是支持基于密鑰的認證。使用基于密鑰的身份驗證時,服務器會獲得用戶私鑰的副本。當用戶嘗試登錄時,服務器要求她通過用她的私鑰簽署一些數據來證明自己的身份。然后,服務器將簽署的數據與其用戶公鑰的副本進行核對。
在實踐中,使用公共密鑰進行身份驗證是很好的,因為它使用戶不必管理大量的密碼。用戶可以將同一個密鑰用于多個服務器。她可以選擇密碼保護她的密鑰以獲得額外的安全性,或者她可以使用沒有密碼的密鑰進行完全透明的登錄過程。
本實驗將向您介紹如何設置Twisted SSH服務器來使用公鑰認證。它使用與例10-1相同的服務器代碼,但后端具有新的身份驗證方式。
我怎么做?
為每個用戶存儲一個公鑰。編寫一個接受執行twisted.conch.credentials.ISSHPrivateKey的憑據的憑證檢查器。通過檢查確認用戶的憑證,以確保其公用密鑰與您存儲的密鑰匹配,并且簽名證明用戶擁有匹配的私鑰。例10-2顯示了如何做到這一點。
Example 10-2. pubkeyssh.py
from sshserver import SSHDemoRealm, getRSAKeys from twisted.conch import credentials, error from twisted.conch.ssh import keys, factory from twisted.cred import checkers, portal from twisted.python import failure from zope.interface import implements import base64class PublicKeyCredentialsChecker:implements(checkers.ICredentialsChecker)credentialInterfaces = (credentials.ISSHPrivateKey,)def __init__(self, authorizedKeys) :self.authorizedKeys = authorizedKeysdef requestAvatarId(self, credentials):if self.authorizedKeys.has_key(credentials.username):userKey = self.authorizedKeys[credentials.username]if not credentials.blob == base64.decodestring(userKey):raise failure.failure(error.ConchError("I don’t recognize that key"))if not credentials.signature:return failure.Failure(error.ValidPublicKey())pubKey = keys.getPublicKeyObject(data=credentials.blob)if keys.verifySignature(pubKey, credentials.signature,credentials.sigData):return credentials.usernameelse:return failure.Failure(error.ConchError("Incorrect signature"))else:return failure.Failure(error.ConchError("No such user"))if __name__ == "__main__":sshFactory = factory.SSHFactory()sshFactory.portal = portal.Portal(SSHDemoRealm())authorizedKeys = {"admin": "AAAAB3NzaC1yc2EAAAABIwAAAIEAxIfv4ICpuKFaGA/ r2cJsQjUZsZ4VAsA1c9TXPYEc2Ue1lp78lq0rm/ nQTlK9lg+YEbRxCPcgymaz60cjGspqqoQ35qPiwJ4xg VUeYKfxs+ZSl3YGIODVfsqLYxLl33b6yCnE0bfBjEPmb9P OkL2TA1owlBfTL2+t+Hbx+clDCwE="}sshFactory.portal.registerChecker(PublicKeyCredentialsChecker(authorizedKeys))pubKeyString, privKeyString = getRSAKeys()sshFactory.publicKeys = {‘ssh-rsa’: keys.getPublicKeyString(data=pubKeyString)}sshFactory.privateKeys = {‘ssh-rsa’: keys.getPrivateKeyObject(data=privKeyString)}from twisted.internet import reactor reactor.listenTCP(2222, sshFactory) reactor.run()為了測試這個例子,你需要生成一個公鑰,如果你還沒有的話。 大多數Linux附帶的OpenSSH SSH程序有
(也包括Mac OS X)包含一個名為ssh-keygen的命令行實用程序,您可以使用該實用程序來生成新的私鑰/公鑰對:
Windows用戶可以使用PuTTYgen生成密鑰,PuTTYgen與流行的免費PuTTY SSH客戶端(http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)一起分發。
生成密鑰后,可以從~/.ssh / id_rsa.pub文件中獲取公鑰。 編輯例10-2在authorizedKeys字典中使用管理員用戶的公用密鑰。 然后運行pubkeyssh.py會在端口2222上啟動服務器。您應該直接登錄而不提示輸入密碼:
$ ssh admin@localhost -p 2222>>> Welcome to my test SSH server.Commands: clear echo help quit whoami$? 如果您嘗試以不具有匹配的私鑰的用戶身份登錄,您將被拒絕訪問:
$ ssh admin@localhost -p 2222Permission denied (publickey).這是如何運作的?
例10-2重用了例10-1中的大多數SSH服務器類。為了支持公鑰認證,它使用一個名為PublicKeyCredentialsChecker的新的憑證檢查器類。 PublicKeyCredentialsChecker接受實現ISSHPrivateKey的憑證,ISSHPrivateKey具有屬性username,blob,signature和sigData。要驗證密鑰,PublicKeyCredentialsChecker需要經過三次測試。首先,它確保它具有用于用戶用戶名的公鑰文件。接下來,它驗證blob中提供的公鑰是否匹配與它為該用戶提供的公鑰。
用戶可能在這個時候提供了公鑰而不是簽名的令牌。如果公鑰是有效的,但沒有提供簽名,則PublicKeyCredentialsChecker.requestAvatar引發特殊異常twisted.conch.error。 ValidPublicKey。 SSH服務器會理解這個異常的含義,并要求客戶端丟失簽名。
最后,PublicKeyCredentialsChecker使用函數twisted.conch.ssh.keys.verifySignature來檢查簽名中的數據是否真的是用用戶的私鑰簽名的sigData中的數據。如果verifySignature返回true值,則認證成功,requestAvatarId返回用戶名作為avatar ID。
??? 您可以在SSH服務器中同時支持用戶名/密碼和基于密鑰的身份驗證。只需在您的portal上注冊兩個憑證檢查器。
{mospagebreak title =提供管理Python Shell}
例10-1演示了如何通過SSH提供交互式shell。這個例子用一小組命令來實現自己的語言。但是還有另外一種可以通過SSH運行的shell:您可以從命令行了解到你喜愛的交互式的Python提示符窗口。
我怎么做?
twisted.conch.manhole和twisted.conch.manhole_ssh模塊具有為運行中的服務器提供遠程交互式Python shell的類。創建一個manhole_ssh.TerminalRealm對象,并將其chainedProtocolFactory.protocolFactory屬性設置為將返回manhole.Manhole對象的函數。例10-3演示了一個可以使用SSH和twisted.conch.manhole實時修改的Web服務器。
Example 10-3. manholeserver.py
from twisted.internet import reactor from twisted.web import server, resource from twisted.cred import portal, checkers from twisted.conch import manhole, manhole_sshclass LinksPage(resource.Resource):isLeaf = 1def __init__(self, links) :resource.Resource.__init__(self)self.links = linksdef render(self, request):return "<ul>" + "".join(["<li><a href=’%s’>%s</a></li>" % (link, title)for title, link in self.links.items()]) + "</ul>"links = {‘Twisted’: ‘http://twistedmatrix.com/’,‘Python’: ‘http://python.org’} site = server.Site(LinksPage(links)) reactor.listenTCP(8000, site)def getManholeFactory(namespace, **passwords):realm = manhole_ssh.TerminalRealm()def getManhole(_): return manhole.Manhole(namespace)realm.chainedProtocolFactory.protocolFactory = getManholep = portal.Portal(realm)p.registerChecker(checkers.InMemoryUsernamePassword DatabaseDontUse(**passwords))f = manhole_ssh.ConchFactory(p)return freactor.listenTCP(2222, getManholeFactory(globals(), admin=’aaa’)) reactor.run()manholeserver.py將啟動端口8000上的Web服務器和端口2222上的SSH服務器。圖10-1顯示了服務器啟動時主頁的外觀。
現在使用SSH登錄。 您將得到一個Python提示符窗口,并可以完全訪問服務器中的所有對象。 嘗試修改鏈接字典:
$ ssh admin@localhost -p 2222admin@localhost’s password: aaa>>> dir()[‘LinksPage’, ‘__builtins__’, ‘__doc__’, ‘__file__’, ‘__name_ _’, ‘checkers’,‘getManholeFactory’, ‘links’, ‘manhole’, ‘manhole_ssh’, ‘portal’, ‘reactor’,‘resource’, ‘server’, ‘site’]>>> links{‘Python’: ‘http://python.org’, ‘Twisted’: ‘http://twistedmatrix.com/’}>>> links["Abe Fettig"] = http://fettig.net>>> links["O’Reilly"] = http://oreilly.com>>> links{‘Python’: ‘http://python.org’, "O’Reilly": ‘http://oreilly.com’, ‘Twisted’: ‘http:// twistedmatrix.com/’, ‘Abe Fettig’: ‘http://fettig.net’}>>>然后刷新Web服務器的主頁。 圖10-2顯示了您的更改將如何反映在網站上。
? 這是如何運作的?
例10-3定義了一個名為getManholeFactory的函數,它非常方便的運行了一個manhole? SSH服務器。 getManholeFactory接受一個名為namespace的參數,這個參數是一個字典,它定義了哪些Python對象可用,然后是一些表示用戶名和密碼的關鍵字參數。它構造一個manhole_ssh.TerminalRealm并將其chainedProtocolFactory.protocolFactory屬性設置為一個匿名函數,該函數返回所請求的名稱空間的manhole.Manhole對象。然后使用realm 和用戶名和密碼字典設置portal ,將portal 連接到一個manhole_ssh.ConchFactory,并返回factory。
就像它的名字所暗示的那樣,manhole提供了一個入口,允許她進入禁區 ,她可以做任何她想做的事情。為了方便起見,您可以將Python對象的字典作為名稱空間傳遞(限制用戶可查看的對象集合),而不是為了安全。只有管理用戶才有權使用manhole服務器。
例10-3使用內置的globals()函數創建一個manhole factory,該函數返回當前全局名稱空間中所有對象的字典。當您通過SSH登錄時,可以看到manholeserver.py中的所有全局對象,包括鏈接字典。因為這個字典也被用來生成網站的主頁,所以你通過SSH所做的任何更改都會立即反映在Web上。
??? manhole_ssh.ConchFactory類包含自己的默認公鑰/私鑰對。對于你自己的項目,你不應該依賴這些內置的密鑰。相反,生成你自己的并設置ConchFactory的publicKeys和privateKeys屬性。有關如何執行此操作的示例,請參閱本章前面的示例10-1。
{mospagebreak title =在遠程服務器上運行命令}
本實驗演示如何編寫SSH客戶端。您可以使用twisted.conch與使用SSH的服務器通信:登錄,執行命令和
捕獲輸出。
我怎么做?
有幾個類共同組成一個twisted.conch.ssh SSH客戶端。 transport.SSHClientTransport類設置連接并驗證服務器的身份。 userauth.SSHUserAuthClient使用您的認證憑證登錄。 connection.SSHConnection類將在您登錄后接管,并創建一個或多個channel.SSHChannel對象,然后通過安全通道與服務器進行通信。例10-4顯示了如何使用這些類來創建一個登錄到服務器的SSH客戶端,運行命令并打印輸出。
Example 10-4. sshclient.py
from twisted.conch import error from twisted.conch.ssh import transport, connection, keys, userauth, channel, common from twisted.internet import defer, protocol, reactorclass ClientCommandTransport(transport.SSHClientTransport):def __init__(self, username, password, command) :self.username = usernameself.password = password self.command = commanddef verifyHostKey(self, pubKey, fingerprint):# in a real app, you should verify that the fingerprint matches# the one you expected to get from this serverreturn defer.succeed(True)def connectionSecure(self):self.requestService(PasswordAuth(self.username, self.password,ClientConnection(self.command)))class PasswordAuth(userauth.SSHUserAuthClient):def __init__(self, user, password, connection):userauth.SSHUserAuthClient.__init__(self, user, connection)self.password = passworddef getPassword(self, prompt=None):return defer.succeed(self.password)class ClientConnection(connection.SSHConnection):def __init__(self, cmd, *args, **kwargs):connection.SSHConnection.__init__(self)self.command = cmddef serviceStarted(self):self.openChannel(CommandChannel(self.command, conn=self))class CommandChannel(channel.SSHChannel):name = ‘session’def __init__(self, command, *args, **kwargs):channel.SSHChannel.__init__(self, *args, **kwargs)self.command = commanddef channelOpen(self, data):self.conn.sendRequest(self, ‘exec’, common.NS(self.command), wantReply=True).addCallback(self._gotResponse)def _gotResponse(self, _):self.conn.sendEOF(self)def dataReceived(self, data):print datadef closed(self):reactor.stop()class ClientCommandFactory(protocol.ClientFactory):def __init__(self, username, password, command):self.username = usernameself.password = passwordself.command = commanddef buildProtocol(self, addr):protocol = ClientCommandTransport(self.username, self.password, self.command)return protocolif __name__ == "__main__":import sys, getpassserver = sys.argv[1]command = sys.argv[2]username = raw_input("Username: ")password = getpass.getpass("Password: ")factory = ClientCommandFactory(username, password, command)reactor.connectTCP(server, 22, factory)reactor.run()用兩個參數運行sshclient.py:一個主機名和一個命令。 它會詢問您的用戶名和密碼,登錄到服務器,執行命令并打印輸出。 例如,您可以運行who命令來獲取當前登錄到服務器的用戶列表:
$ python sshclient.py myserver.example.com whoUsername: abePassword: passwordroot pts/0 Jun 11 21:35 (192.168.0.13)phil pts/2 Jun 22 13:58 (192.168.0.1)phil pts/3 Jun 22 13:58 (192.168.0.1)這是如何運作的?
示例10-4中的ClientCommandTransport處理到SSH服務器的初始連接。其verifyHostKey方法檢查以確保服務器的公鑰與您的期望相符。通常情況下,您會在第一次連接時記住每臺服務器,然后檢查后續連接,以確保另一臺服務器不會像您期望的服務器那樣惡意嘗試自行關閉。在這里,它只是返回一個真值,而不會檢查密鑰。 connectionSecure方法在初始加密連接建立之后立即被調用。當userauth.SSHUserAuthClient指向self.requestService應該傳遞您的登錄憑據,以及一個connection.SSHConnection對象,該對象應在身份驗證成功后管理連接。
PasswordAuth繼承自userauth.SSHUserAuthClient。它只需要實現一個方法getPassword,它返回它將用于登錄的密碼。如果你想使用公鑰認證,你需要實現方法getPublicKey和getPrivateKey,而不是返回相應的鍵作為字符串每個案例。
?
客戶端成功登錄后,示例10-4中的ClientConnection類將調用其serviceStarted方法。它使用CommandChannel對象(它是channel.SSHChannel的子類)調用self.openChannel。該對象用于與SSH服務器的已認證通道一起使用。 channelOpen方法在通道準備就緒時調用。此時,您可以調用self.conn.sendRequest向服務器發送命令。您必須編碼通過SSH發送的數據作為特定格式的網絡字符串;以這種格式獲取字符串,將其傳遞給twisted.conch.common.NS函數。如果您有興趣從命令獲取響應,請將關鍵字參數wantReply設置為True;這個設置將導致sendRequest返回一個Deferred,當命令完成時會被調用。 (如果您沒有將wantReply設置為True,則sendRequest將返回None。)從服務器接收數據時,將傳遞給dataReceived。一旦你完成了通道的使用,通過調用self.conn.sendEOF關閉它。關閉的方法將被調用,讓你知道什么時候通道已經成功關閉。
原鏈接 http://www.devshed.com/c/a/Python/SSH-with-Twisted/
?
總結
以上是生活随笔為你收集整理的Twisted SSH的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 16进制与字符串的转换、二
- 下一篇: Cowrie蜜罐的部署(ubt亲测)