美文网首页IT
Python之SSH远程登录技术

Python之SSH远程登录技术

作者: 一只小青蛙 | 来源:发表于2019-08-01 15:46 被阅读0次

    一、SSH简介

    SSH(Secure Shell)属于在传输层上运行的用户层协议,相对于Telnet来说具有更高的安全性。SSH是专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH最初是UNIX系统上的一个程序,后来又迅速扩展到其他操作平台。SSH在正确使用时可弥补网络中的漏洞。SSH客户端适用于多种平台。几乎所有UNIX平台—包括HP-UXLinuxAIXSolarisDigital UNIXIrix,以及其他平台,都可运行SSH。

    二、SSH远程连接

    SSH远程连接有两种方式,一种是通过用户名和密码直接登录,另一种则是用过密钥登录。

    1、用户名和密码登录

    老王要在自己的主机登录老张的电脑,他可以通过运行以下代码来实现

    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 跳过了远程连接中选择‘是’的环节,
    ssh.connect('IP', 22, '用户名', '密码')
    stdin, stdout, stderr = ssh.exec_command('df') print stdout.read()
    

     
            在这里要用到paramiko模块,这是一个第三方模块,要自自己导入(要想使用paramiko模块,还要先导入pycrypto模块才能用)。

    查看并启动ssh服务
    service ssh status

    添加用户:useradd -d /home/zet zet
    passwd zet
    赋予ssh权限
    vi /etc/ssh/sshd_config
    添加
    AllowUsers:zet

    2、密钥登录

           老王要在自己的主机登录老张的电脑,老王用命令ssh.keygen -t rsa生成公钥和私钥,他将自己的公钥发给老张,使用ssh-copy-id -i ~/ssh/id_rsa.pub laozhang@IP命令

    然后运行以下代码来实现

    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 
    ssh.connect('IP', 22, '用户名', key)
    stdin, stdout, stderr = ssh.exec_command('df') print stdout.read()
    

           关于密钥登录,每个人都有一个公钥,一个私钥,公钥是给别人的,私钥是自己留着,只有自己的私钥能解开自己公钥加密的文件。

           老王有一个机密文件要发给老张,就要先下载老张的公钥进行加密,这样老张就能用自己私钥解开这份机密文件,获得内容。

           如果老张要确认是否是老王本人给他的机密文件,就去下载一个老王的公钥,随机写一些字符,用老王的公钥加密,发给老王,老王解密之后发回给老张,如果老张收到的解密后的字母和自己发出去的一样,对方就是老王无疑了。

    三、使用SSH连接服务器

    客户端代码:

    #-*- coding:utf8 -*-
     
    import threading
    import paramiko
    import subprocess
     
    def ssh_command(ip, user, passwd, command, port = 22):
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())    #设置自动添加和保存目标ssh服务器的ssh密钥
        client.connect(ip, port, username=user, password=passwd)  #连接
        ssh_session = client.get_transport().open_session() #打开会话
        if ssh_session.active:
            ssh_session.send(command)   #发送command这个字符串,并不是执行命令
            print ssh_session.recv(1024)    #返回命令执行结果(1024个字符)
            while True:
                command = ssh_session.recv(1024)    #从ssh服务器获取命令
                try:
                    cmd_output = subprocess.check_output(command, shell=True)
                    ssh_session.send(cmd_output)
                except Exception, e:
                    ssh_session.send(str(e))
            client.close()
        return
     
    ssh_command('127.0.0.1', 'zet', 'zet', 'clientconnected',8001)
    

    服务端代码:

    #-*- coding:utf8 -*-
     
    import socket
    import paramiko
    import threading
    import sys
     
    # 使用 Paramiko示例文件的密钥
    #host_key = paramiko.RSAKey(filename='test_rsa.key')
    host_key = paramiko.RSAKey(filename='/root/.ssh/id_rsa')
     
    class Server(paramiko.ServerInterface):
        def __init__(self):
            self.event = threading.Event()
        def check_channel_request(self, kind, chanid):
            if kind == 'session':
                 return paramiko.OPEN_SUCCEEDED
            return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
        def check_auth_password(self, username, password):
            if (username == 'qing') and (password == 'qing'):
                return paramiko.AUTH_SUCCESSFUL
            return paramiko.AUTH_FAILED
     
    server = sys.argv[1]
    ssh_port = int(sys.argv[2])
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    #TCP socket
        #这里value设置为1,表示将SO_REUSEADDR标记为TRUE,操作系统会在服务器socket被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind(('127.0.0.1', 8001))   #绑定ip和端口
        sock.listen(100)    #最大连接数为100
        print '[+] Listening for connection ...'
        client, addr = sock.accept()
    except Exception, e:
        print '[-] Listen failed: ' + str(e)
        sys.exit(1)
    print '[+] Got a connection!'
     
    try:
        bhSession = paramiko.Transport(client)
        bhSession.add_server_key(host_key)
        server = Server()
        try:
            bhSession.start_server(server=server)
        except paramiko.SSHException, x:
            print '[-] SSH negotiation failed'
        chan = bhSession.accept(20) #设置超时值为20
        print '[+] Authenticated!'
        print chan.recv(1024)
        chan.send("Welcome to bh_ssh")
        while True:
            try:
                command = raw_input("Enter command:").strip("\n")   #strip移除字符串头尾指定的字符(默认为空格),这里是换行
                if command != 'exit':
                    chan.send(command)
                    print chan.recv(1024) + '\n'
                else:
                    chan.send('exit')
                    print 'exiting'
                    bhSession.close()
                    raise Exception('exit')
            except KeyboardInterrupt:
                bhSession.close()
    except Exception, e:
        print '[-] Caught exception: ' + str(e)
        try:
            bhSession.close()
        except:
            pass
        sys.exit(1)
    

    四、接下来是了解一下进程的创建过程,用最原始的方式实现了一个ssh shell命令的执行。

    #coding=utf8
     
    '''
    用python实现了一个简单的shell,了解进程创建
    类unix 环境下 fork和exec 两个系统调用完成进程的创建
    '''
     
    import sys, os
     
     
    def myspawn(cmdline):
        argv = cmdline.split()
        if len(argv) == 0:
            return 
        program_file = argv[0]
        pid = os.fork()
        if pid < 0:
            sys.stderr.write("fork error")
        elif pid == 0:
            # child
            os.execvp(program_file, argv)
            sys.stderr.write("cannot exec: "+ cmdline)
            sys.exit(127)
        # parent
        pid, status = os.waitpid(pid, 0)
        ret = status >> 8  # 返回值是一个16位的二进制数字,高8位为退出状态码,低8位为程序结束系统信号的编号
        signal_num = status & 0x0F
        sys.stdout.write("ret: %s, signal: %s\n" % (ret, signal_num))
        return ret
     
     
    def ssh(host, user, port=22, password=None):
        if password:
            sys.stdout.write("password is: '%s' , plz paste it into ssh\n" % (password))
        cmdline = "ssh %s@%s -p %s " % (user, host, port)
        ret = myspawn(cmdline)
     
     
    if __name__ == "__main__":
        host = ''
        user = ''
        password = ''
        ssh(host, user, password=password)
    

    一个SSH项目,需要在客户端集成一个交互式ssh功能,大概就是客户端跟服务器申请个可用的机器,服务端返回个ip,端口,密码, 然后客户端就可以直接登录到机器上操做了。该程序基于paramiko模块。

           经查找,从paramiko的源码包demos目录下,可以看到交互式shell的实现,就是那个demo.py。但是用起来有些bug,于是我给修改了一下interactive.py(我把windows的代码删掉了,剩下的只能在linux下用)。代码如下:

    #coding=utf-8
    import socket
    import sys
    import os
    import termios
    import tty
    import fcntl
    import signal
    import struct
    import select
     
    now_channel = None
     
    def interactive_shell(chan):
        posix_shell(chan)
     
     
    def ioctl_GWINSZ(fd):
        try:
            cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'aaaa'))
        except:
            return
        return cr
     
     
    def getTerminalSize():
        cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
        return int(cr[1]), int(cr[0])
        
     
    def resize_pty(signum=0, frame=0):
        width, height = getTerminalSize()
        if now_channel is not None:
            now_channel.resize_pty(width=width, height=height)
     
     
     
    def posix_shell(chan):
        global now_channel
        now_channel = chan
        resize_pty()
        signal.signal(signal.SIGWINCH, resize_pty) # 终端大小改变时,修改pty终端大小
        stdin = os.fdopen(sys.stdin.fileno(), 'r', 0) # stdin buff置为空,否则粘贴多字节或者按方向键的时候显示不正确
        fd = stdin.fileno()
        oldtty = termios.tcgetattr(fd)
        newtty = termios.tcgetattr(fd)
        newtty[3] = newtty[3] | termios.ICANON
        try:
            termios.tcsetattr(fd, termios.TCSANOW, newtty)
            tty.setraw(fd)
            tty.setcbreak(fd)
            chan.settimeout(0.0)
            while True:
                try:
                    r, w, e = select.select([chan, stdin], [], [])
                except:
                    # 解决SIGWINCH信号将休眠的select系统调用唤醒引发的系统中断,忽略中断重新调用解决。
                    continue
                if chan in r:
                    try:
                        x = chan.recv(1024)
                        if len(x) == 0:
                            print 'rn*** EOFrn',
                            break
                        sys.stdout.write(x)
                        sys.stdout.flush()
                    except socket.timeout:
                        pass
                if stdin in r:
                    x = stdin.read(1)
                    if len(x) == 0:
                        break
                    chan.send(x)
        finally:
            termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
    

    使用示例:

    #coding=utf8
    import paramiko
    import interactive
     
     
    #记录日志
    paramiko.util.log_to_file('/tmp/aaa')
    #建立ssh连接
    ssh=paramiko.SSHClient()
    ssh.load_system_host_keys()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect('192.168.1.11',port=22,username='hahaha',password='********',compress=True)
     
    #建立交互式shell连接
    channel=ssh.invoke_shell()
    #建立交互式管道
    interactive.interactive_shell(channel)
    #关闭连接
    channel.close()
    ssh.close()
    
    interactive.py代码中主要修复了几个问题:

    1、当读取键盘输入时,方向键会有问题,因为按一次方向键会产生3个字节数据,我的理解是按键一次会被select捕捉一次标准输入有变化,但是我每次只处理1个字节的数据,其他的数据会存放在输入缓冲区中,等待下次按键的时候一起发过去。这就导致了本来3个字节才能完整定义一个方向键的行为,但是我只发过去一个字节,所以终端并不知道我要干什么。所以没有变化,当下次触发按键,才会把上一次的信息完整发过去,看起来就是按一下方向键有延迟。多字节的粘贴也是一个原理。解决办法是将输入缓冲区置为0,这样就没有缓冲,有多少发过去多少,这样就不会有那种显示的延迟问题了。

    2、终端大小适应。paramiko.channel会创建一个pty(伪终端),有个默认的大小(width=80, height=24),所以登录过去会发现能显示的区域很小,并且是固定的。编辑vim的时候尤其痛苦。channel中有resize_pty方法,但是需要获取到当前终端的大小。经查找,当终端窗口发生变化时,系统会给前台进程组发送SIGWINCH信号,也就是当进程收到该信号时,获取一下当前size,然后再同步到pty中,那pty中的进程等于也感受到了窗口变化,也会收到SIGWINCH信号。

    3、读写‘慢’设备(包括pipe,终端设备,网络连接等)。读时,数据不存在,需要等待;写时,缓冲区满或其他原因,需要等待。ssh通道属于这一类的。本来进程因为网络没有通信,select调用为阻塞中的状态,但是当终端窗口大小变化,接收到SIGWINCH信号被唤醒。此时select会出现异常,触发系统中断(4, 'Interrupted system call'),但是这种情况只会出现一次,当重新调用select方法又会恢复正常。所以捕获到select异常后重新进行select可以解决该问题。

    相关文章

      网友评论

        本文标题:Python之SSH远程登录技术

        本文链接:https://www.haomeiwen.com/subject/pizqdctx.html