自動化運維之paramiko詳解


一、paramiko介紹

  paramiko是基於Python實現的SSH2遠程安全連接,支持認證及密鑰方式。可以實現遠程命令執行、文件傳輸、中間SSH代理等功能,相對於Pexpect,封裝的層次更高,更貼近SSH協議的功能

官網地址:http://www.paramiko.org/installing.html

       http://docs.paramiko.org/en/2.4/

       https://pypi.org/project/paramiko/

二、paramiko安裝

root@localhost ~]# pip3 install paramiko

簡單實現遠程SSH運行命令示例

import paramiko

hostname = '192.168.1.5'
username = 'root'
password = '123123'
paramiko.util.log_to_file('syslogin.log')     #發送paramiko日志到syslogin.log文件

ssh = paramiko.SSHClient()          #創建一個SSH客戶端client對象
ssh.load_system_host_keys()         #獲取客戶端host_keys,默認~/.ssh/known_hosts,非默認路徑需指定ssh.load_system_host_keys(/xxx/xxx) 
ssh.connect(hostname=hostname,username=username,password=password)    #創建SSH連接
stdin,stdout,stderr = ssh.exec_command('free -h')      #調用遠程執行命令方法exec_command()
print(stdout.read().decode('utf-8'))        #打印命令執行結果,得到Python列表形式,可以使用stdout_readlines()
ssh.close()

 

程序運行結果如下圖所示:

DEB [20190809-13:44:34.792] thr=1   paramiko.transport: starting thread (client mode): 0x54a13f28
DEB [20190809-13:44:34.792] thr=1   paramiko.transport: Local version/idstring: SSH-2.0-paramiko_2.6.0
DEB [20190809-13:44:34.792] thr=1   paramiko.transport: Remote version/idstring: SSH-2.0-OpenSSH_7.4
INF [20190809-13:44:34.792] thr=1   paramiko.transport: Connected (version 2.0, client OpenSSH_7.4)
DEB [20190809-13:44:34.792] thr=1   paramiko.transport: kex algos:['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha1', 'diffie-hellman-group14-sha256', 'diffie-hellman-group14-sha1', 'diffie-hellman-group1-sha1'] server key:['ssh-rsa', 'rsa-sha2-512', 'rsa-sha2-256', 'ecdsa-sha2-nistp256', 'ssh-ed25519'] client encrypt:['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc', 'blowfish-cbc', 'cast128-cbc', '3des-cbc'] server encrypt:['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc', 'blowfish-cbc', 'cast128-cbc', '3des-cbc'] client mac:['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'] server mac:['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'] client compress:['none', 'zlib@openssh.com'] server compress:['none', 'zlib@openssh.com'] client lang:[''] server lang:[''] kex follows?False
DEB [20190809-13:44:34.807] thr=1   paramiko.transport: Kex agreed: curve25519-sha256@libssh.org
DEB [20190809-13:44:34.807] thr=1   paramiko.transport: HostKey agreed: ssh-rsa
DEB [20190809-13:44:34.807] thr=1   paramiko.transport: Cipher agreed: aes128-ctr
DEB [20190809-13:44:34.807] thr=1   paramiko.transport: MAC agreed: hmac-sha2-256
DEB [20190809-13:44:34.807] thr=1   paramiko.transport: Compression agreed: none
DEB [20190809-13:44:34.807] thr=1   paramiko.transport: kex engine KexCurve25519 specified hash_algo <built-in function openssl_sha256>
DEB [20190809-13:44:34.807] thr=1   paramiko.transport: Switch to new keys ...
DEB [20190809-13:44:34.839] thr=1   paramiko.transport: userauth is OK
INF [20190809-13:44:44.883] thr=1   paramiko.transport: Authentication (password) successful!
DEB [20190809-13:44:44.883] thr=2   paramiko.transport: [chan 0] Max packet in: 32768 bytes
DEB [20190809-13:44:44.883] thr=1   paramiko.transport: Received global request "hostkeys-00@openssh.com"
DEB [20190809-13:44:44.883] thr=1   paramiko.transport: Rejecting "hostkeys-00@openssh.com" global request from server.
DEB [20190809-13:44:44.946] thr=1   paramiko.transport: [chan 0] Max packet out: 32768 bytes
DEB [20190809-13:44:44.946] thr=1   paramiko.transport: Secsh channel 0 opened.
DEB [20190809-13:44:44.946] thr=1   paramiko.transport: [chan 0] Sesch channel 0 request ok
DEB [20190809-13:44:45.102] thr=1   paramiko.transport: [chan 0] EOF received (0)
DEB [20190809-13:44:45.102] thr=1   paramiko.transport: [chan 0] EOF sent (0)
syslogin.log

 

 

三、paramiko的核心組件

paramiko包含兩個核心組件,一個為SSHClient類,另一個為SFTPClient類。

1、SSHClient類

SSHClient類是SSH服務會話的高級表示,該類封裝了傳輸(transport)、通道(channel)及SFTPClient的校驗、建立的方法,通常用於執行遠程命令。

client = SSHClient()
client.load_system_host_keys()
client.connect('ssh.example.com')
stdin, stdout,stderr = client.exec_command('ls -l')

SSHClient常用的方法介紹

官方文檔:http://docs.paramiko.org/en/2.4/api/client.html?highlight=connect

1.1 connect方法

conect方法實現了遠程SSH連接並校驗

方法定義:

connect(hostname, port=22, username=None, password=None, pkey=None, key_filename=None, timeout=None, allow_agent=True, look_for_keys=True, compress=False, sock=None, gss_auth=False, gss_kex=False, gss_deleg_creds=True, gss_host=None, banner_timeout=None, auth_timeout=None, gss_trust_dns=True, passphrase=None)

參數說明:

  • hostname(str類型),連接的目標主機地址;
  • port(int類型),連接目標主機的端口,默認為22;
  • username(str類型),校驗的用戶名(默認為當前的本地用戶名);
  • password(str類型),密碼用於身份校驗或解鎖私鑰;
  • pkey(Pkey類型),私鑰方式用於身份驗證;
  • key_filename(str or list(str)類型),一個文件名或文件名列表,用於私鑰的身份驗證;
  • timeout(float類型),一個可選的超時時間(以秒為單位)的TCP連接;
  • allow_agent(bool類型),設置為False時用於禁用連接到SSH代理;
  • look_for_keys(bool類型),設置為False時用於來禁用在~/.ssh中搜索私鑰文件;
  • compress(bool類型),設置為True時打開壓縮。

 

1.2 exec_command方法

遠程命令執行方法,該命令的輸入與輸出流為標准輸入(stdin)、輸出(stdout)、錯誤(stderr)的Python文件對像。

方法定義:

exec_command(command, bufsize=-1, timeout=None, get_pty=False, environment=None)

參數說明:

  • command(str類型),執行的命令串;
  • bufsize(int類型),文件緩沖區大小,默認為-1(不限制)

 

1.3 load_system_host_keys方法

加載本地公鑰校驗文件,默認為~/.ssh/known_host,非默認路徑需要手工指定。(~/.ssh/known_hosts的作用:當ssh會把你每個你訪問過計算機的公鑰(public key)都記錄在~/.ssh/known_hosts。當下次訪問相同計算機時,OpenSSH會核對公鑰。如果公鑰不同,OpenSSH會發出警告)

方法定義:

load_system_host_keys(self,filename=None)

參數說明:

filename(str類型),指定遠程主機公鑰記錄文件。

 

1.4 set_missing_host_policy方法

敲入“yes”,key的信息將被保存到“known_hosts”文件中。這些密鑰很重要,因為它是與主機之間的信任機制。如果key被破壞或更改,那么客戶端會拒絕連接並不會通知你,而paramiko也采用相同的規則。如果在“hnown_hosts”中沒有保存相關的信息,SSHClient 默認行為是拒絕連接。如果是工作在系統反反復復安裝的實驗環境中時,這將變得無比的煩人。設置host key的規則調用的方法叫ssh client 實例的"set_missing_host_key_policy()",它設定了你所期望的方法來管理host key.如果你像我一樣懶惰,你可以用"paramiko.AutoAddPolicy()"方法來自動接收未知的key:
--------------------- 
版權聲明:本文為CSDN博主「y2701310012」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/y2701310012/article/details/41855171

 

  設置連接的遠程主機沒有主機密鑰或HostKeys對象時的策略,目前支持三種,分別是AutoAddPolicy、RejectPolicy(默認)、WarningPolicy,僅限用於SSHClient類。
  • AutoAddPolicy,目標添加主機名及主機密鑰到本地HostKeys對象,並將其保存,不依賴load_system_host_keys()的配置,即使~/.ssh/hnown_hosts不存在也不產生影響;
  • RejectPolicy,自動拒絕未知的主機名和密鑰,依賴load_system_host_keys()的配置;
  • WarningPolicy,用於記錄一個未知的主機密鑰的Python警告,並接收它,功能上AutoAddPolicy相似,但未知主機會有告警。

使用方法如下:

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

 

import paramiko
ssh = paramiko.SSHClient()
SSH_PRIVATE_KEY ='/root/.ssh/id_rsa'  #本地密鑰文件路徑

try:
    key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY) # 無解密密碼時
    #key = paramiko.RSAKey.from_private_key_file(SSH_PRIVATE_KEY, password='******')  # 有解密密碼時,

    ssh.load_system_host_keys() #通過known_hosts 方式進行認證可以用這個,如果known_hosts 文件未定義還需要定義 known_hosts
    #ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())  # 通過公共方式進行認證 (不需要在known_hosts 文件中存在)
    #以上兩條任選一條 如果沒有就會報錯:<class 'paramiko.ssh_exception.SSHException'>:Server '192.168.1.61' not found in known_hosts


    ssh.connect(hostname='192.168.1.61', port=22, username='gota', pkey=key)
    stdin, stdout, stderr = ssh.exec_command("df -h")
    # 獲取命令結果
    result = stdout.read()
    # 打印輸出
    print(result.decode())
except Exception as e:
    print("%s:%s" % (e.__class__, e))
finally:
    # 關閉
    ssh.close()


'''
注意:生成密碼的方法
A、進入本地 ssh文件夾 cd .ssh/
B、使用ssh-keygen生產本地公鑰和私鑰 ssh-keygen 
root@ubuntu:~/.ssh$ ls 
id_rsa id_rsa.pub
C、將生成的id_rsa.pub文件中的內容copy到目標機的.ssh/authorized_keys中就可以了,如果沒有authorized_keys,自己創建。但是要注意authorized_keys的權限一般是600

或者直接在本地使用一條命令也可以實現公鑰的復制,ssh-copy-id后面接入的用戶就是要支持免密登錄的用戶。
morra@ubuntu:~/.ssh$ ssh-copy-id "gota@192.168.1.61"
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/morra/.ssh/id_rsa.pub"
The authenticity of host '192.168.1.42 (192.168.1.42)' can't be established.
ECDSA key fingerprint is SHA256:/ufx+/OLtdsYy7vsdk4KDu9xJsBp6zHonRAf2jjT0GI.
Are you sure you want to continue connecting (yes/no)? n^H
Please type 'yes' or 'no': yes
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
Password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'morra@192.168.1.42'"   and check to make sure that only the key(s) you wanted were added.


#去目標機器下,檢查authorized_keys文件
localhost:.ssh gota$ cat authorized_keys
'''
免費登錄執行命令

 

 

 

 

 

SFTPClient類

  SFTPClient作為一個SFTP客戶端對象,根據SSH傳輸協議的sftp會話,實現遠程文件操作,比如文件上傳、下載、權限、狀態等操作。

SFTPClient類常用方法

官方文檔:http://docs.paramiko.org/en/2.4/api/sftp.html

from_transport方法

創建一個已連通的SFTP客戶端通道。

方法定義:

from_transport(cls,t)

參數說明:

t(transport),一個已通過驗證的傳輸對象。

示例說明:

import paramiko

t = paramiko.Transport(('192.168.56.132',22))
t.connect(username='root',password='1234567')
sftp = paramiko.SFTPClient.from_transport(t)

 put方法

上傳本地文件到遠程SFTP服務端

方法定義:

put(localpath, remotepath, callback=None, confirm=True)

參數說明:

  • localpath(str類型),需上傳的本地文件(源);
  • remotepath(str類型),遠程路徑(目標);
  • callback(funcation(int,int)),獲取已接收的字節數及總傳輸字節數,以便回調函數調用,默認為None;
  • confirm(bool類型),文件上傳完畢后是否調用stat()方法,以便確認文件的大小。

示例說明:

localpath='/home/access.log'
remotepath='/data/logs/access.log'
sftp.put(localpath,remotepath)

get方法

從遠程SFTP服務端下載文件到本地。

方法定義:

get(remotepath, localpath, callback=None)

參數說明:

  • remotepath(str類型),需要下載的遠程文件(源);
  • callback(funcation(int,int)),獲取已接收的字節數及總和傳輸字節數,以便回調函數調用,默認為None.

示例說明:

remotepath = '/data/logs/access.log'
localpath = '/home/access.log'
sftp.get(remotepath,localpath)

其它方法

SFTPClient類其它常用方法說明:

  • mkdir,在SFTP服務端創建目錄,如sftp.mkdir("/home/userdir",mode=0777),默認模式是0777(八進制),在某些系統上,mode被忽略。在使用它的地方,當前的umask值首先被屏蔽掉。
  • remove,刪除SFTP服務端指定目錄,如sftp.remove("/home/userdir")。
  • rename,重命名SFTP服務端文件或目錄,如sftp.rename("/home/test.sh","/home/testfile.sh")
  • stat,獲取遠程SFTP服務端指定文件信息,如sftp.stat("/home/testfile.sh")。
  • listdir,獲取遠程SFTP服務端指定目錄列表,以Python的列表(List)形式返回,如sftp.listdir("/home")。

SFTPClient類應用示例

下面為SFTPClient類的一個完整示例,實現了文件上傳、下載、創建與刪除目錄等,需要注意的是,put和get方法需要指定文件名,不能省略。

 

 

 

#coding:utf8

import paramiko
import os
import traceback

class SshConnectError(Exception):#
    pass

class controlHost:
    def __init__(self, host, username, password, port=22, key_file='/root/.ssh/id_rsa'):##本地密鑰文件路徑
        self.host = host
        self.pkey = paramiko.RSAKey.from_private_key_file(key_file)
        self.ssh = controlHost.__sshConn(self.host, username, password, self.pkey, int(port)) #調用類中的靜態方法__sshConn 返回ssh連接對象
        self.sftp = self.__sftpConn()


    def close(self):
        if hasattr(self.ssh, "close"):
            self.ssh.close()

    @staticmethod
    def __sshConn(host, username, password, pkey, port):
        ssh = paramiko.SSHClient()##創建一個SSH客戶端client對象
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        try:
            ssh.connect(hostname=host, port=int(port), username=username, pkey=pkey) #免密登陸方式
            # print('免密登陸方式')
        except:
            try:
                ssh.connect(hostname=host, port=int(port), username=username, password=password)#密碼認證
                # print('密碼認證')
            except:
                raise SshConnectError("SSH Connect %s Error!" %host)
            else:
                return ssh
        else:
            return ssh

#返回sftp通道實例對象 方法
    def __sftpConn(self):
        transport = self.ssh.get_transport() #1.先ssh連上,2.再建立通道
        sftp = paramiko.SFTPClient.from_transport(transport) #創建一個已連通的SFTP客戶端通道。
        return sftp


#執行命令方法
    def exeCommand(self, cmd, timeout=300):
        _, stdout, stderr = self.ssh.exec_command(cmd, timeout=timeout)
        try:
            channel = stdout.channel
            #print('channel',channel)
            exit_code = channel.recv_exit_status()
            #print('exit_code',exit_code)報錯返回碼是127,沒有報錯是0
            stdout = stdout.read().strip()
            stderr = stderr.read().strip()
            return {"status": 1, "stdout": stdout, "stderr": stderr, 'exit_code': exit_code}
        except:
            return {"status": 0, "stdout": stdout, "stderr": stderr, 'exit_code': 127}

#文件上傳下載方法
    def sftpFile(self, localpath, remotepath, action):
        try:
            if action == 'push':
                dirname = os.path.dirname(remotepath)
                self.exeCommand("mkdir -p %s" % dirname)
                self.sftp.put(localpath, remotepath)
                return {"status": 1, "message": 'sftp %s %s success!' % (self.host, action)}
            elif action == "pull":
                dirname = os.path.dirname(localpath)
                if not os.path.exists(dirname):
                    os.makedirs(dirname)
                # if os.path.exists(localpath):
                #     os.remove(localpath)
                self.sftp.get(remotepath, localpath)
                return {"status": 1, "stdout": 'sftp %s %s success!' % (self.host, action), "stderr": ""}
        except Exception as e:
            return {"status": 0, "stderr": 'sftp %s %s failed %s' % (self.host, action, str(e)), "stdout": ""}

    @staticmethod
    def iter_local_path(abs_path):
        '''遍歷本機該目錄中所以的文件,並返回'''
        result = set([])
        for j in os.walk(abs_path):
            print(j)
            base_path = j[0]
            file_list = j[2]
            for k in file_list:
                p = os.path.join(base_path, k)
                result.add(p)
        return result

    def iter_remote_path(self, abs_path):
        '''獲取遠程主機abs_path下的所以文件'''
        result = set([])
        try:
            stat = str(self.sftp.lstat(abs_path))
            print('stat',stat)
        except FileNotFoundError:
            return result
        else:
            if stat.startswith("d"):
                file_list = self.exeCommand("ls %s" %abs_path)["stdout"].decode(encoding='utf-8').strip().splitlines()
                #Python strip() 方法用於移除字符串頭尾指定的字符(默認為空格或換行符)或字符序列。注意:該方法只能刪除開頭或是結尾的字符,不能刪除中間部分的字符。
                 #Python splitlines() 按照行('\r', '\r\n', \n')分隔,返回一個包含各行作為元素的列表,如果參數 keepends 為(默認值) False,不包含換行符,如果為 True,則保留換行符。

                for j in file_list:
                    p = os.path.join(abs_path, j)
                    result.update(self.iter_remote_path(p))  #合並 並集U
            else:
                result.add(abs_path)
        return result





if __name__ == '__main__':
    x = controlHost("192.168.1.40", 'root', 'Gota34cc')

    #測試 獲取本地某個目錄的所有文件
    # w = x.iter_local_path("/root/test")
    # print(w)

    # 測試 獲取遠程主機某個目錄的所有文件
    # y = x.iter_remote_path("/root/test")
    # print(y)

    # 測試 命令執行方法
    # y = x.exeCommand("uname -r")
    # print(y)

    # 測試 上傳下載
    w = x.sftpFile("/tmp/ansible.txt", '/tmp/xx.txt', "push")  #將本地機器的/tmp/ansible.txt,上傳至遠程主機/tmp目錄下並命名為xx.sh
    # w = x.sftpFile('/tmp/aaaa.py', '/tmp/xyz.py', 'pull')
    print(w)

    x.close()
自動化備份平台項目公用類lib/sshConn.py

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM