一:堡垒机需求分析
注意:
虽然我们在中间使用防火墙服务器对流量进行拦截和转发也可以起到过滤作用,但是我们无法去获取到完整,正确的操作记录。因为无论是客户端还是服务器端(管理员可能会去修改记录,而且可能会出现一个账号多人用,无法知道是谁操作了这台服务器)我们都无法完全控制。所以,我们可以使用中间件,替客户去执行命令,并且记录操作记录到堡垒机的数据库中
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" οnclick=
"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" οnclick=
"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" οnclick=
"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" οnchange=
"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" οnclick=
"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
转载于:https://www.cnblogs.com/ssyfj/p/9157586.html
相关资源:Python-开源运维管理系统堡垒机cmdb等功能