python---堡垒机开发

 一:堡垒机需求分析

注意:

虽然我们在中间使用防火墙服务器对流量进行拦截和转发也可以起到过滤作用,但是我们无法去获取到完整,正确的操作记录。因为无论是客户端还是服务器端(管理员可能会去修改记录,而且可能会出现一个账号多人用,无法知道是谁操作了这台服务器)我们都无法完全控制。所以,我们可以使用中间件,替客户去执行命令,并且记录操作记录到堡垒机的数据库中

history        可以查看操作记录
history -c     清空记录

注意:

由于所有密码都存在堡垒机中,所以我们要保证其环境的安全性,,最好还有一个备份堡垒机

补充:远程登录服务器的方法

(1)账号+密码

(2)A将公钥放在对方B服务器,A就可以直接登录B服务器

公钥登录方法测试:

1.在本机生成密匙,公钥(后缀.pub)和私钥

2.将公钥远程拷贝或发生到对方服务器

 

 

3.要想公钥在对方服务器生效,需要将发送过来的公钥放在用户家目录下的.ssh隐藏目录,若是不存在该目录,需要先生成该目录,然后再加文件拷贝进入

4.将公钥在对方服务器中放入用户的家目录下的.ssh目录,并修改名为authorized_keys,才能生效

5.测试远程ssh登录

 二:数据表结构设计

from django.db import models
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser,PermissionsMixin
)
#主机表:含有主机名,ip地址,端口,外联IDC机房
class Host(models.Model):
    name = models.CharField(max_length=64,unique=True)
    ip_addr = models.GenericIPAddressField(unique=True)
    port = models.SmallIntegerField(default=22)
    idc = models.ForeignKey("IDC")

    def __str__(self):
        return self.name
#用户表,用于登录上面主机,若是只有用户密码,那么是一对多,但是包含公钥登录,所以结构变为多对多,第三张表我们需要自己去创建
class
RemoteUser(models.Model): '''远程登录用户:1.私钥,秘钥,2.用户密码''' auth_type_choices = ( (0,"ssh-password"), (1,"ssh-key"), ) auth_type = models.SmallIntegerField(choices=auth_type_choices) username = models.CharField(max_length=32) password = models.CharField(max_length=64,blank=True,null=True) class Meta: unique_together = ("auth_type","username","password") def __str__(self): return "%s:%s"%(self.username,self.password)
#主机对用户的多对多,第三张表,用于和堡垒机的账号表多对多关联。也会和主机组多对多关联
class
HostToRemoteUser(models.Model): host = models.ForeignKey("Host") remote_user = models.ForeignKey("RemoteUser") class Meta: unique_together = ("host","remote_user") def __str__(self): return "%s %s"%(self.host,self.remote_user)
#主机组,类似于角色分组。对各个主机进行分组管理
class
HostGroup(models.Model): """存储主机组""" name = models.CharField(max_length=64,unique=True) #hosts = models.ManyToManyField("Host") host_to_remote_users = models.ManyToManyField("HostToRemoteUser") def __str__(self): return self.name
#堡垒机的账号管理,包括了权限管理
class UserProfileManager(BaseUserManager): def create_user(self, email, name, password=None): """ Creates and saves a User with the given email, date of birth and password. """ if not email: raise ValueError('Users must have an email address') user = self.model( email=self.normalize_email(email), name=name, ) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, name, password): """ Creates and saves a superuser with the given email, date of birth and password. """ user = self.create_user( email, password=password, name=name, ) user.is_superuser = True user.save(using=self._db) return user #账号管理,关联主机分为单个主机用户表HostToRemoteUser(主要是对某些用户的分配主机只有一台或者过少,不需要分配主机用户组),和主机用户组HostGroup class UserProfile(AbstractBaseUser,PermissionsMixin): """堡垒机账号""" email = models.EmailField( verbose_name='email address', max_length=255, unique=True, ) name = models.CharField(max_length=64, verbose_name="姓名") is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=True) objects = UserProfileManager() host_to_remote_users = models.ManyToManyField("HostToRemoteUser",blank=True,null=True) host_groups = models.ManyToManyField("HostGroup",blank=True,null=True) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['name'] def get_full_name(self): # The user is identified by their email address return self.email def get_short_name(self): # The user is identified by their email address return self.email def __str__(self): # __unicode__ on Python 2 return self.email

 完善操作记录表

class AuditLog(models.Model):
    '''日志记录'''
    user = models.ForeignKey("UserProfile",verbose_name="堡垒机账号",blank=True,null=True)
    host_to_remote_user = models.ForeignKey("HostToRemoteUser",verbose_name="主机账号",blank=True,null=True)
    log_type_choices = (
        (0,"login"),
        (1,"cmd"),
        (2,"logout")
    )
    log_type = models.SmallIntegerField(choices=log_type_choices,default=0)
    content = models.CharField(max_length=255,blank=True,null=True)
    datetime = models.DateTimeField(auto_now_add=True,blank=True,null=True)

    def __str__(self):
        return "%s %s %s"%(self.user,self.host_to_remote_user,self.content)

 三:业务调用

(1)backend_manage.py:配置Django环境,便于使用

# coding:utf8
# __author:  Administrator
# date:      2018/6/9 0009
# /usr/bin/env python
import sys,os

if __name__ == "__main__":
    #Django环境配置
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "crazyeye.settings")
    import django
    django.setup()

    from backend import main  #注意项目中的模块导入,需要在Django环境配置之后,不然不会调用到Django环境而报错
    interactive_obj = main.ArgvHandler(sys.argv)

进入backend目录

(2)main.py:对用户参数进行解析

# coding:utf8
# __author:  Administrator
# date:      2018/6/9 0009
# /usr/bin/env python
from backend import ssh_interactive

class ArgvHandler(object):
    def __init__(self,sys_argv):
        self.sys_argv = sys_argv
        self.call()

    def help_msg(self,error=''):
        msg = '''
        %s
        run     启动用户交互程序
        '''
        exit(msg%error)

    def call(self):
        '''根据用户参数,调用对应方法'''
        if len(self.sys_argv) == 1:
            self.help_msg()
        if hasattr(self,self.sys_argv[1]):
            getattr(self,self.sys_argv[1])(self.sys_argv[2:])
        else:
            self.help_msg(error="调用方法[%s]不存在"%self.sys_argv[1])

    def run(self,*args,**kwargs):
        ssh_interactive.SshHandler(self)  #开始交互

(3)ssh_interactive.py:启动堡垒机交互脚本

# coding:utf8
# __author:  Administrator
# date:      2018/6/9 0009
# /usr/bin/env python
from django.contrib.auth import authenticate
from backend import paramiko_ssh
from repository import models
import getpass


class SshHandler(object):
    '''
    启动堡垒机交互脚本
    '''
    def __init__(self,argv_handler_instance):
        self.argv_handler_instance = argv_handler_instance
        self.interactive()

    def auth(self):
        '''登录堡垒机账号'''
        count = 0
        while count < 3:
            username = input("堡垒机账号>>>:").strip()
            password = getpass.getpass("Password>>>:")
            user = authenticate(username=username,password=password)
            if user:
                self.user = user
                return True
            count += 1
        return False

    def show_host_group(self):
        '''显示所有可以操作的主机组和未分组'''
        msg = '''
        HostGroup_List:
        %s
        '''
        Hgroup_list = self.user.host_groups.all()
        g_list = []
        for index,group in enumerate(Hgroup_list):
            g_list.append("[%s]\t%s(%s台)"%(index,group.name,group.host_to_remote_users.count()))

        g_list.append('[z]\t未分组主机(%s台)'%(self.user.host_to_remote_users.count()))

        while True:
            print(msg % ('\n\t'.join(g_list)))
            choice = input("请选择主机组>>>:").strip()
            if choice.isdigit():
                choice = int(choice)
                try:
                    selected_group = Hgroup_list[choice]
                except IndexError:
                    continue
                self.show_host_list(selected_group)
            elif choice == "z":
                self.show_host_list(None,False)
            elif choice == "exit":
                exit(0)

    def show_host_list(self,selected_group,group_type=True):
        '''显示主机组下面的主机'''
        if group_type:
            H2R_ulist = selected_group.host_to_remote_users.all()
        else:
            H2R_ulist = self.user.host_to_remote_users.all()

        msg = '''
        HostToRemoteUser_List:
        %s
        '''
        h_list = []
        for index,h2r in enumerate(H2R_ulist):
            h_list.append("[%s]\t%s"%(index,h2r))
        while True:
            print(msg % ('\n\t'.join(h_list)))
            choice = input("请选择操作主机>>>:").strip()
            if choice.isdigit():
                choice = int(choice)
                try:
                    selected_host = H2R_ulist[choice]
                except IndexError:
                    continue
                return self.link(selected_host)
            elif choice == "b":
                return True
            elif choice == "exit":
                exit(0)

    def link(self,selected_host):
        '''连接主机'''
        self.models = models
        paramiko_ssh.ssh_connect(self, selected_host)

    def interactive(self):
        '''启动交互脚本'''
        if self.auth():
            print("登录成功")
            self.show_host_group()
        else:
            print("登录失败")

(4)paramiko_ssh.py:开始连接远程主机,并将登陆,退出信息记录到数据库中

#!/usr/bin/env python

import base64
from binascii import hexlify
import getpass
import os
import select
import socket
import sys
import time
import traceback
from paramiko.py3compat import input

import paramiko

try:
    import interactive
except ImportError:
    from . 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,password,t):
    default_auth = "p"
    # auth = input(
    #     "Auth by (p)assword, (r)sa key, or (d)ss key? [%s] " % default_auth
    # )
    auth = default_auth

    if len(auth) == 0:
        auth = default_auth

    if auth == "r":
        default_path = os.path.join(os.environ["HOME"], ".ssh", "id_rsa")
        path = 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 = 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, password)


def ssh_connect(ssh_interactive_handler,selected_host):  #获取ssh_interactive_handler句柄,含有models等信息,selected_host是用户选择的主机账号
    '''动态获取hostname,port,name,password,ssh_key'''
    hostname = selected_host.host.ip_addr
    port = selected_host.host.port
    username = selected_host.remote_user.username
    password = selected_host.remote_user.password

    # now connect
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((hostname, port))
    except Exception as 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 hostname not in keys:
            print("*** WARNING: Unknown host key!")
        elif key.get_name() not in keys[hostname]:
            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.")

        if not t.is_authenticated():
            manual_auth(username, hostname,password,t)
        if not t.is_authenticated():
            print("*** Authentication failed. :(")
            t.close()
            sys.exit(1)

        chan = t.open_session()
        chan.get_pty()
        chan.invoke_shell()
        chan.ssh_handler = ssh_interactive_handler
        chan.selected_host = selected_host
        print("*** Here we go!\n")
     #登陆 ssh_interactive_handler.models.AuditLog.objects.create( user
=ssh_interactive_handler.user, host_to_remote_user=selected_host, log_type=0, content="*** Login ***" )
interactive.interactive_shell(chan)  #启动会话 chan.close() t.close()
     #退出 ssh_interactive_handler.models.AuditLog.objects.create( user
=chan.ssh_handler.user, host_to_remote_user=chan.selected_host, log_type=2, content="*** Logout ***" ) except Exception as e: print("*** Caught exception: " + str(e.__class__) + ": " + str(e)) traceback.print_exc() try: t.close() except: pass sys.exit(1)

(5)interactive.py:启动会话,和主机进行交互记录命令到数据库

# Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>
#
# This file is part of paramiko.
#
# Paramiko is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.


import socket
import sys
from paramiko.py3compat import u
import time

# windows does not have termios...
try:
    import termios
    import tty

    has_termios = True
except ImportError:
    has_termios = False


def interactive_shell(chan):
    if has_termios:
        posix_shell(chan)
    else:
        windows_shell(chan)


def posix_shell(chan):  #linux使用select框架循环
    import select

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

        cmd = []
        while True:
            r, w, e = select.select([chan, sys.stdin], [], [])
            if chan in r:
                try:
                    x = u(chan.recv(1024))
                    if len(x) == 0:
                        sys.stdout.write("\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 x == "\r":
                    print('input>',"".join(cmd))
                    chan.ssh_handler.models.AuditLog.objects.create(
                        user=chan.ssh_handler.user,
                        host_to_remote_user=chan.selected_host,
                        log_type=1,
                        content=''.join(cmd)
                    )
                    cmd.clear()
                if len(x) == 0:
                    break
                cmd.append(x)
                chan.send(x)
    except (EOFError,OSError):
        pass
    finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)


# thanks to Mike Looijmans for this code
def windows_shell(chan):  #windows使用多线程和socket方式交互
    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.decode())
            sys.stdout.flush()

    writer = threading.Thread(target=writeall, args=(chan,))
    writer.start()

    try:
        cmd = []
        while True:
            d = sys.stdin.read(1)
            if d == "\n":
                chan.ssh_handler.models.AuditLog.objects.create(
                    user=chan.ssh_handler.user,
                    host_to_remote_user=chan.selected_host,
                    log_type=1,
                    content=''.join(cmd)
                )
                cmd.clear()
            cmd.append(d)
            if not d:
                break
            chan.send(d)
    except (EOFError,OSError):
        pass

四:linux服务器上测试

(1)为项目创建一个公共用户,为堡垒机用户提供。密码设置简单

useradd crazyeye
passwd crazyeye
输入密码:123456

(2)正常启用项目

python backend_manage.py run

(3)需要用户登录账号后立即执行程序,减少用户权限

去操作用户家目录下的.bashrc文件,进行配置

# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

# User specific aliases and functions
python3 /home/crazyeye/crazyeye/backend_manage.py run  #用户登录后自动执行
exit  #防止用户出现不可预期的错误导致上面的程序退出,而去操作账号下的其他数据,我们需要让上面程序结束后,账号退出即可

 五.实现web页面对堡垒机进行操作

linux下安装shellinabox实现web登录服务器

{% extends "index.html" %}
{% block right-content-container %}
           <!--Page Title-->
                <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
                <div id="page-title">
                    <h1 class="page-header text-overflow">Web Ssh</h1>

                    <!--Searchbox-->
                    <div class="searchbox">
                        <div class="input-group custom-search-form">
                            <input type="text" class="form-control" placeholder="Search..">
                            <span class="input-group-btn">
                                <button class="text-muted" type="button"><i class="demo-pli-magnifi-glass"></i></button>
                            </span>
                        </div>
                    </div>
                </div>
                <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
                <!--End page title-->

                <!--Page content-->
                <!--===================================================-->
                <div id="page-content">
                    <div class="row">
                        <div class="col-lg-12">
                             <iframe src="http://192.168.218.129:4200/" width="100%" style="padding-bottom:100%;background-color:white;"></iframe>
                        </div>
                    </div>
                </div>
                <!--===================================================-->
                <!--End page content-->
{% endblock %}
前端代码:使用iframe

六.实现批量命令和文件功能

(0)添加数据表和业务流程图

1..数据表增加

class Task(models.Model):
    '''批量任务'''
    task_type_choice = (
        ('cmd',"批量命令"),
        ('file_transfer','文件传输'),
    )

    task_type = models.CharField(choices=task_type_choice,max_length=32)
    user = models.ForeignKey("UserProfile")
    content = models.CharField(max_length=255)

    date = models.DateTimeField(auto_now_add=True)
    def __str__(self):
        return "%s %s"%(self.task_type,self.content)

class TaskLogDetail(models.Model):
    '''存储每台主机执行结果'''
    task = models.ForeignKey("Task")
    host_to_remote_user = models.ForeignKey("HostToRemoteUser")
    result = models.TextField(verbose_name="任务结果")

    status_choices = ((0, 'initialized'), (1, 'sucess'), (2, 'failed'), (3, 'timeout'))
    status = models.SmallIntegerField(choices=status_choices)

    date = models.DateTimeField(auto_now_add=True)

2.流程图

(1)批量命令展示

(2)批量文件操作展示

 

 (3)全部代码展示

1.公共组件

<div class="col-lg-3">
                            <div class="panel">
                                <div class="panel-heading">
                                    <div class="panel-control">
                                        <a title="" data-html="true" data-container="body" data-original-title="<p class='h4 text-semibold'>Information</p><p style='width:150px'>This is an information bubble to help the user.</p>" href="#" class="demo-psi-information icon-lg icon-fw unselectable text-info add-tooltip"></a>
                                    </div>
                                    <h3 class="panel-title">主机列表</h3>
                                </div>
                                <div class="bord-btm">
                                    <div class="list-group bord-no">
                                        {% for hostGroup in request.user.host_groups.all %}
                                        <a class="list-group-item" onclick="toggel_host(this);" href="#">{{ hostGroup.name }}<span class="badge badge-success">{{ hostGroup.host_to_remote_users.count }}</span></a>
                                        <ul class="list-group" style="margin-bottom: 0px;display: none;">
                                            {% for hostUser in hostGroup.host_to_remote_users.all %}
                                            <li class="list-group-item">
                                                <input id="demo-form-checkbox-{{ hostUser.id }}" tag="host-selected" class="magic-checkbox" type="checkbox" value="{{ hostUser.id }}">
                                                <label for="demo-form-checkbox-{{ hostUser.id }}">{{ hostUser.host }}@{{ hostUser.remote_user.username }}</label>
                                            </li>
                                            {% endfor %}
                                        </ul>
                                        {% endfor %}
                                        <a class="list-group-item" onclick="toggel_host(this);" href="#">未分组主机<span class="badge badge-success">{{ request.user.host_to_remote_users.count }}</span></a>
                                        <ul class="list-group" style="margin-bottom: 0px;display: none;">
                                            {% for hostUser in request.user.host_to_remote_users.all %}
                                            <li class="list-group-item">
                                                <input id="demo-form-checkbox-{{ hostUser.id }}" tag="host-selected" class="magic-checkbox" type="checkbox" value="{{ hostUser.id }}">
                                                <label for="demo-form-checkbox-{{ hostUser.id }}">{{ hostUser.host }}@{{ hostUser.remote_user.username }}</label>
                                            </li>
                                            {% endfor %}
                                        </ul>
                                    </div>
                                </div>

                            </div>

                        </div>
host_list_compentent.html主机列表
<script>
    function toggel_host(ths){
        $(ths).next().toggle();
    }

    function ChangeFileType(ths){
        if ($(ths).val() == "send"){
            $("#local_file").show();
        }else{
            $("#local_file").hide();
        }
    }

    function ShowError(title,content,type) {
        $(".modal-toggle").find(".modal-title").html(title);
        $(".modal-toggle").find(".bootbox-body").html(content);
        $(".modal-toggle").find(type).show();
        $(".modal-toggle").find(".modal").show();
        $(".modal-toggle").show();
    }

    function submitData(ths,cmd_type){
        if ($(ths).hasClass('disabled')){
            return false;
        }
        var host_list = [];
        $("[tag='host-selected']:checked").each(function(){
            host_list.push($(this).val());
        })

        if (host_list.length == 0){
            ShowError("Error Before Submit","<h3>未选中主机</h1>",".btn-danger");
            return false;
        }

        var task_arguments = {};

        if (cmd_type == 'cmd'){
            var cmd = $("input[name='cmd']").val().trim()
            if(cmd.length == 0){
                ShowError("Error Before Submit","<h3>请输入要执行的命令</h1>",".btn-danger");
                return false;
            }

            task_arguments = {
                'task_type' : 'cmd',
                'cmd': cmd,
            }
        }else{
            task_arguments['task_type'] = 'file_transfer';

            if ($("#server_file_path").val().trim().length == 0){
                ShowError("Error Before Submit","<h3>请输入服务端文件路径</h1>",".btn-danger");
                return false;
            }

            if ($("#select_file_type").val() == "recv"){
                task_arguments['transfer_type'] = "recv";
                task_arguments['server_file_path'] = $("#server_file_path").val().trim();
            }else{
                if ($("#local_file_path").val().trim().length == 0){
                    ShowError("Error Before Submit","<h3>请输入本地文件路径</h1>",".btn-danger");
                    return false;
                }
                task_arguments['transfer_type'] = "send";
                task_arguments['server_file_path'] = $("#server_file_path").val().trim();
                task_arguments['local_file_path'] = $("#local_file_path").val().trim();
            }
        }

        task_arguments['selected_hosts'] = host_list;
        $(ths).addClass('disabled');

        $.post(
            "{% url 'batch_task_mgr' %}",
            {'task_data':JSON.stringify(task_arguments),'csrfmiddlewaretoken':'{{ csrf_token  }}'},
            function(callback){
                $("#task_result_container").empty();
                $.each(callback['selected_hosts'],function(index,obj){
                    var ul_inner = '<li class="list-group-item" for="'+obj['id']+'"><span class="badge badge-primary">wait</span>'+obj['host_to_remote_user__host__ip_addr']+' '+obj['host_to_remote_user__host__name']+' '+obj['host_to_remote_user__remote_user__username']+'</li><pre>initilize...</pre>';
                    $("#task_result_container").append(ul_inner);
                });
                TimerFlag = setInterval(GetTask,2000,callback['task_id']);
            },
            'json'
        )
    }

    function GetTask(task_id){
        $.get(
            '{% url "get_task" %}',
            {'id':task_id},
            function(callback){
                var All_get = true;
                $.each(callback,function(index,obj){
                    $ele = $("#task_result_container li[for='"+obj['id']+"']");
                    $ele.next().text(obj['result']);
                    if(obj['status'] == 1){
                        $ele.children().first().removeClass("badge-primary").addClass("badge-success").text("finished");
                    }else if (obj['status'] == 2){
                         $ele.children().first().removeClass("badge-primary").addClass("badge-warning").text("warning");
                    }else{
                        All_get = false;
                    }
                });
                if (All_get){
                    $("#submit_btn").removeClass("disabled");
                    clearInterval(TimerFlag);
                }
            },
            'json'
        );
    }

    $(function(){
        $(".modal-toggle").find(".btn-danger").click(function(){
            $(this).parents(".modal-toggle").hide();
        });
    })
</script>
multitask_js_compentent.html共用js
<div class="col-lg-7">
    <div class="panel">
        <div class="panel-heading">
            <h3 class="panel-title">批量执行命令结果展示</h3>
        </div>
        <div class="panel-body">
            <ul class="list-group" id="task_result_container">
            </ul>
        </div>
    </div>
</div>
task_result_compentent.html任务结果展示

2.前端展示:批量命令,批量文件

{% extends "index.html" %}
{% block right-content-container %}
           <!--Page Title-->
                <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
                <div id="page-title">
                    <h1 class="page-header text-overflow">Host Manage</h1>

                    <!--Searchbox-->
                    <div class="searchbox">
                        <div class="input-group custom-search-form">
                            <input type="text" class="form-control" placeholder="Search..">
                            <span class="input-group-btn">
                                <button class="text-muted" type="button"><i class="demo-pli-magnifi-glass"></i></button>
                            </span>
                        </div>
                    </div>
                </div>
                <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
                <!--End page title-->

                <!--Page content-->
                <!--===================================================-->
                <div id="page-content">
                    <div class="row">
                        {% include "include/host_list_compentent.html" %}
                        <div class="col-lg-7">
                            <div class="panel">
                                <div class="panel-heading">
                                    <h3 class="panel-title">批量执行命令</h3>
                                </div>
                                <div class="panel-body">
                                    <form class="form-inline">
                                        <div class="input-group mar-btm col-lg-12">
                                            <input type="text" name="cmd" placeholder="input your cmd...." class="form-control">
                                            <span class="input-group-btn" style="width:32px;">
                                                <button class="btn btn-mint" id="submit_btn" onclick="submitData(this,'cmd');" type="button">执行命令</button>
                                            </span>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>
                        {% include "include/task_result_compentent.html" %}
                    </div>
                </div>
                <!--===================================================-->
                <!--End page content-->

                <div class="modal-toggle" style="display: none;">
                    <div class="bootbox modal fade in" tabindex="-1" role="dialog" style="padding-right: 17px;">
                        <div class="modal-dialog">
                            <div class="modal-content">
                                <div class="modal-header">
                                    <button type="button" class="close" data-dismiss="modal">
                                        <i class="pci-cross pci-circle"></i>
                                    </button>
                                    <h4 class="modal-title"></h4>
                                </div>
                                <div class="modal-body">
                                    <div class="bootbox-body"></div>
                                </div>
                                <div class="modal-footer">
                                    <button data-bb-handler="success" type="button" class="btn btn-success" style="display: none;">Success!</button>
                                    <button data-bb-handler="danger" type="button" class="btn btn-danger" style="display: none;">Danger!</button>
                                    <button data-bb-handler="main" type="button" class="btn btn-primary" style="display: none;">Confirm</button>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="modal-backdrop fade in"></div>
                </div>

                {% include "include/multitask_js_compentent.html" %}
{% endblock %}
host_mgr.html前端批量命令
{% extends "index.html" %}
{% block right-content-container %}
           <!--Page Title-->
                <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
                <div id="page-title">
                    <h1 class="page-header text-overflow">File Tranfer</h1>

                    <!--Searchbox-->
                    <div class="searchbox">
                        <div class="input-group custom-search-form">
                            <input type="text" class="form-control" placeholder="Search..">
                            <span class="input-group-btn">
                                <button class="text-muted" type="button"><i class="demo-pli-magnifi-glass"></i></button>
                            </span>
                        </div>
                    </div>
                </div>
                <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
                <!--End page title-->

                <!--Page content-->
                <!--===================================================-->
                <div id="page-content">
                    <div class="row">
                        {% include "include/host_list_compentent.html" %}
                        <div class="col-lg-7">
                            <div class="panel">
                                <div class="panel-heading">
                                    <h3 class="panel-title">批量操作文件</h3>
                                </div>
                                <div class="panel-body">
                                    <form class="form-horizontal">
                                          <div class="form-group">
                                            <label class="col-sm-2 control-label">文件操作:</label>
                                            <div class="col-sm-10">
                                                <select class="form-control" onchange="ChangeFileType(this);" id="select_file_type">
                                                  <option value="recv">接收文件</option>
                                                  <option value="send">发送文件</option>
                                                </select>
                                            </div>
                                          </div>
                                        <div class="form-group" id="server_file">
                                            <label class="col-sm-2 control-label">服务器文件路径:</label>
                                            <div class="col-sm-10">
                                                <input type="text" class="form-control" id="server_file_path" placeholder="Service file path">
                                            </div>
                                        </div>
                                        <div class="form-group" style="display: none;" id="local_file" >
                                            <label class="col-sm-2 control-label">本地文件路径:</label>
                                            <div class="col-sm-10">
                                                <input type="text" class="form-control" id="local_file_path" placeholder="Local file path">
                                            </div>
                                        </div>
                                        <div class="panel-footer text-right">
                                            <button class="btn btn-success" onclick="submitData(this,'file_transfer');" id="submit_btn"  type="button">Submit</button>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>
                        {% include "include/task_result_compentent.html" %}
                    </div>
                </div>
                <!--===================================================-->
                <!--End page content-->

                <div class="modal-toggle" style="display: none;">
                    <div class="bootbox modal fade in" tabindex="-1" role="dialog" style="padding-right: 17px;">
                        <div class="modal-dialog">
                            <div class="modal-content">
                                <div class="modal-header">
                                    <button type="button" class="close" data-dismiss="modal">
                                        <i class="pci-cross pci-circle"></i>
                                    </button>
                                    <h4 class="modal-title"></h4>
                                </div>
                                <div class="modal-body">
                                    <div class="bootbox-body"></div>
                                </div>
                                <div class="modal-footer">
                                    <button data-bb-handler="success" type="button" class="btn btn-success" style="display: none;">Success!</button>
                                    <button data-bb-handler="danger" type="button" class="btn btn-danger" style="display: none;">Danger!</button>
                                    <button data-bb-handler="main" type="button" class="btn btn-primary" style="display: none;">Confirm</button>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="modal-backdrop fade in"></div>
                </div>

                {% include "include/multitask_js_compentent.html" %}
{% endblock %}
file_transfer.html批量文件

3.url和views视图文件

from django.conf.urls import url
from web import views


urlpatterns = [
    url(r"dashbroad.html",views.dashbroad),
    url(r"login.html", views.acc_login),
    url(r"web_ssh.html", views.web_ssh,name="web_ssh"),
    url(r"host_mgr.html", views.host_mgr, name="host_mgr"),
    url(r"file_transfer.html", views.file_transfer, name="file_transfer"),
    url(r"batch_task_mgr.html", views.batch_task_mgr, name="batch_task_mgr"),
    url(r"batch_task_mgr.html", views.batch_task_mgr, name="batch_task_mgr"),
    url(r"get_task.html", views.get_task, name="get_task"),
]
urls.py
from django.shortcuts import render,redirect,HttpResponse
from django.contrib.auth import authenticate,login,logout
from django.contrib.auth.decorators import login_required
from repository import models
import json
# Create your views here.

@login_required
def dashbroad(request):

    return render(request,"web/dashbroad.html")


def acc_login(request):
    error_msg = ""
    if request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password")
        user = authenticate(username=username,password=password)
        if user:
            login(request,user)
            return redirect("/web/dashbroad.html")
        else:
            error_msg = "Wrong Username Or Password"

    return render(request,"login.html",{"error_msg":error_msg,})


@login_required
def web_ssh(request):

    return render(request,"web/web_ssh.html")


@login_required
def host_mgr(request):

    return render(request,"web/host_mgr.html")


@login_required
def file_transfer(request):


    return render(request,"web/file_transfer.html")


def conv(date_obj):
        return date_obj.strftime("%Y-%m-%d %H:%M:%S")


@login_required
def batch_task_mgr(request):
    from backend.multitask import Multitask

    task_obj = Multitask(request)

    respone = {
        'task_id':task_obj.task_obj.id,
        'selected_hosts':list(task_obj.task_obj.tasklogdetail_set.all().values(
            'id',
            'host_to_remote_user__host__ip_addr',
            'host_to_remote_user__host__name',
            'host_to_remote_user__remote_user__username'
        ))
    }

    return HttpResponse(json.dumps(respone))


@login_required
def get_task(request):
    task_log_obj = models.TaskLogDetail.objects.filter(task_id=request.GET.get("id")).values("id","status","result",'date')

    log_data = json.dumps(list(task_log_obj),default=conv)

    return HttpResponse(log_data)
views.py

 4.后台backend模块的多任务管理类

# coding:utf8
# __author:  Administrator
# date:      2018/6/14 0014
# /usr/bin/env python
from repository import models
import json,subprocess
from django import conf

class Multitask(object):
    def __init__(self,request):
        self.request = request
        self.run_task()

    def run_task(self):
        '''解析参数,调用方法'''
        self.task_data = json.loads(self.request.POST.get("task_data"))
        task_type = self.task_data.get("task_type")
        if hasattr(self,task_type):
            task_func = getattr(self,task_type)
            task_func()
        else:
            print("cannot find task ",task_type)

    def cmd(self):
        '''执行批量命令'''
        #先将任务添加到Task中
        task_obj = models.Task.objects.create(
            task_type = "cmd",
            user = self.request.user,
            content=self.task_data.get('cmd')
        )
        #向TaskLogDetail中批量添加数据
        task_log_list = []
        for host_remote_user_id in set(self.task_data.get("selected_hosts")):
            task_log_list.append(
                models.TaskLogDetail(
                    task=task_obj,
                    host_to_remote_user_id=host_remote_user_id,
                    result="init...",
                    status=0
                )
            )


        models.TaskLogDetail.objects.bulk_create(task_log_list)

        shell_cmd = "python %s/backend/task_runner.py %s"%(conf.settings.BASE_DIR,task_obj.id)
        cmd_process = subprocess.Popen(shell_cmd,shell=True)

        self.task_obj = task_obj

    def file_transfer(self):
        '''批量操作文件'''
        # 先将任务添加到Task中
        task_obj = models.Task.objects.create(
            task_type="file_transfer",
            user=self.request.user,
            content=json.dumps(self.task_data)
        )
        # 向TaskLogDetail中批量添加数据
        task_log_list = []
        for host_remote_user_id in set(self.task_data.get("selected_hosts")):
            task_log_list.append(
                models.TaskLogDetail(
                    task=task_obj,
                    host_to_remote_user_id=host_remote_user_id,
                    result="init...",
                    status=0
                )
            )

        models.TaskLogDetail.objects.bulk_create(task_log_list)

        shell_cmd = "python %s/backend/task_runner.py %s" % (conf.settings.BASE_DIR, task_obj.id)
        cmd_process = subprocess.Popen(shell_cmd, shell=True)

        self.task_obj = task_obj
multitask.py

5.脚本task_runner.py

# coding:utf8
# __author:  Administrator
# date:      2018/6/14 0014
# /usr/bin/env python
import paramiko,os,sys,json
from concurrent.futures import ThreadPoolExecutor

def ssh_cmd(task_obj):
    host_to_user_obj = task_obj.host_to_remote_user

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

    try:
        ssh.connect(host_to_user_obj.host.ip_addr, host_to_user_obj.host.port, host_to_user_obj.remote_user.username, host_to_user_obj.remote_user.password,timeout=5)
        stdin, stdout, stderr = ssh.exec_command(task_obj.task.content)
        stdout_res = stdout.read()
        stderr_res = stderr.read()

        task_obj.result = stdout_res + stderr_res

        if stderr_res:
            task_obj.status = 2
        else:
            task_obj.status = 1
    except Exception as e:
        task_obj.status = 2
        task_obj.result = e
    finally:
        ssh.close()
        task_obj.save()


def ssh_file(task_log_obj,task_obj):
    file_cmd = json.loads(task_obj.content)
    opration_res = ''

    try:
        t = paramiko.Transport(
            (task_log_obj.host_to_remote_user.host.ip_addr, task_log_obj.host_to_remote_user.host.port))
        t.connect(username=task_log_obj.host_to_remote_user.remote_user.username,
                  password=task_log_obj.host_to_remote_user.remote_user.password)
        sftp = paramiko.SFTPClient.from_transport(t)
        if file_cmd.get("transfer_type") == "recv":
            file_name = "%s-%s-%s"%(task_log_obj.host_to_remote_user.host.ip_addr,task_log_obj.host_to_remote_user.remote_user.username,os.path.basename(file_cmd.get("server_file_path")))
            local_path = os.path.join(conf.settings.DOWN_FILE_PATH,str(task_obj.id),file_name)
            if not os.path.exists(local_path):
                try:
                    os.makedirs(os.path.dirname(local_path))
                except Exception:
                    pass
            sftp.get(file_cmd.get("server_file_path"), local_path)
            t.close()
            opration_res = "file [%s] recv success! path [%s]"%(file_cmd.get("server_file_path"),local_path)
        else:
            file_path = file_cmd.get("local_file_path")
            if os.path.isdir(file_path):
                file_path = os.path.join(file_path,os.path.basename(file_cmd.get("server_file_path")))
            sftp.put(file_path,file_cmd.get("server_file_path"))
            t.close()
            opration_res = "file [%s] send [%s] success!"%(file_path,file_cmd.get("server_file_path"))

        task_log_obj.result = opration_res
        task_log_obj.status = 1
    except Exception as e:
        print(e)
        task_log_obj.status = 2
        task_log_obj.result = e
    finally:
        task_log_obj.save()


if __name__ == "__main__":

    BaseDir = os.path.dirname(os.path.dirname(os.path.abspath(os.path.abspath(__file__))))
    #将路径放入系统路径
    sys.path.append(BaseDir)
    # 加载Django环境
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "crazyeye.settings")
    import django
    django.setup()

    from django import conf

    if len(sys.argv) == 1:
        exit("task id not provided!")
    else:
        task_id = sys.argv[1]
    from repository import models

    task_obj = models.Task.objects.get(id=task_id)

    pool = ThreadPoolExecutor(10)

    if task_obj.task_type == "cmd":
        for task_log_obj in task_obj.tasklogdetail_set.all():
            pool.submit(ssh_cmd,task_log_obj)
    else:
        for task_log_obj in task_obj.tasklogdetail_set.all():
            pool.submit(ssh_file,task_log_obj,task_obj)

    pool.shutdown(wait=True)
task_runner.py

 

posted @ 2018-06-08 21:10  山上有风景  阅读(878)  评论(0编辑  收藏  举报