一:堡垒机需求分析
注意:
虽然我们在中间使用防火墙服务器对流量进行拦截和转发也可以起到过滤作用,但是我们无法去获取到完整,正确的操作记录。因为无论是客户端还是服务器端(管理员可能会去修改记录,而且可能会出现一个账号多人用,无法知道是谁操作了这台服务器)我们都无法完全控制。所以,我们可以使用中间件,替客户去执行命令,并且记录操作记录到堡垒机的数据库中
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] %s(%s台)"%(index,group.name,group.host_to_remote_users.count())) g_list.append('[z] 未分组主机(%s台)'%(self.user.host_to_remote_users.count())) while True: print(msg % (' '.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] %s"%(index,h2r)) while True: print(msg % (' '.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! ")
#登陆 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(" *** EOF ") break sys.stdout.write(x) sys.stdout.flush() except socket.timeout: pass if sys.stdin in r: x = sys.stdin.read(1) if x == " ": 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. " ) def writeall(sock): while True: data = sock.recv(256) if not data: sys.stdout.write(" *** EOF *** ") 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 == " ": 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 %}
六.实现批量命令和文件功能
(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='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>
<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>
<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>
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="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 %}
{% 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 %}
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"), ]
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)
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
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)