2018-06-20 时隔一个多月,忘记了之前的所有操作,重拾起来还是听不容易的,想过放弃,但还是想坚持一下,加油、 世界杯今天葡萄牙1:0战胜摩洛哥,C 罗的一个头球拯救了时间,目前有4个射球,居2018俄罗斯世界杯榜首。

settings.py

INSTALLED_APPS = [
...
'app01', # 注册app
]
MIDDLEWARE = [
...
# 'django.middleware.csrf.CsrfViewMiddleware',
...
]
ALLOWED_HOSTS = ["*"] # Linux下启动用0.0.0.0 添加访问的host即可在Win7下访问 STATICFILES_DIRS = (os.path.join(BASE_DIR, "statics"),) # 现添加的配置,这里是元组,注意逗号
TEMPLATES = [
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
]
# 自定义账户生效
AUTH_USER_MODEL = "app01.UserProfile" # app名.表名 # 监测脚本
SESSION_TRACKER_SCRIPT = "%s/backend/session_trackor.sh" %BASE_DIR
AUDIT_LOG_PATH = "%s/logs/audit" % BASE_DIR

user_enterpoint.py

import getpass
import os
import hashlib, time
import subprocess
from django.contrib.auth import authenticate
# 用户输入命令行端交互入口
class UserPortal(object):
def __init__(self):
self.user = None # 用户交互认证
def user_auth(self):
retry_count = 0
while retry_count < 3:
username = input("Username:").strip()
if (len(username) == 0): continue
password = input("Password:").strip()
if (len(password) == 0):
print("password must not be null")
continue
user = authenticate(username=username, password=password)
if(user):
self.user = user
print("welcome login...")
return
else:
print("invalid password or username...")
retry_count += 1
else:
exit("Too many atttempts....") # 交互函数
def interactive(self):
self.user_auth()
print("验证完成...")
if self.user:
exit_flag = False
while not exit_flag:
# 显示用户可以访问的用户组信息信息
host_groups = self.user.host_groups.all()
host_groups_count = self.user.host_groups.all().count()
print('----------------------------------------------------------------------')
print("host_groups: ", host_groups)
print('host_groups_count:', host_groups_count)
print('----------------------------------------------------------------------')
# 记录主机组所关联的全部主机信息
for index, hostGroup in enumerate(host_groups):
# 0, Webserver【Host Count: 2】
print("%s. %s【Host Count: %s】" % (index, hostGroup.name, hostGroup.bind_hosts.all().count()))
# 用户直接关联的主机信息
# 1. Ungrouped Hosts[1]
# Py特性,这里的index并未被释放,在循环完成后index值还存在,且值为最后循环的最后一个值
print("%s. Ungrouped Hosts[%s]" % (index + 1, self.user.bind_hosts.select_related().count()))
# 用户选择需要访问的组信息
user_input = input("Please Choose Group:").strip()
if len(user_input) == 0:
print('please try again...')
continue
if user_input.isdigit():
user_input = int(user_input)
# 在列表范围之内
if user_input >= 0 and user_input < host_groups_count:
selected_group = self.user.host_groups.all()[user_input]
# 选中了未分组的那组主机
elif user_input == self.user.host_groups.all().count():
# 之所以可以这样,是因为self.user里也有一个bind_hosts,跟HostGroup.bind_hosts指向的表一样
selected_group = self.user # 相当于更改了变量的值,但期内都有bind_hosts的属性,所以循环是OK的
else:
print("invalid host group")
continue
print('selected_group:', selected_group.bind_hosts.all())
print('selected_group_count:', selected_group.bind_hosts.all().count())
while True:
for index, bind_host in enumerate(selected_group.bind_hosts.all()):
print("%s. %s(%s user:%s)" % (index,
bind_host.host.hostname,
bind_host.host.ip_addr,
bind_host.host_user.username))
user_input2 = input("Please Choose Host:").strip()
if len(user_input2) == 0:
print('please try again...')
continue
if user_input2.isdigit():
user_input2 = int(user_input2)
if user_input2 >= 0 and user_input2 < selected_group.bind_hosts.all().count():
selected_bindhost = selected_group.bind_hosts.all()[user_input2]
print("--------------start logging -------------- ", selected_bindhost)
md5_str = hashlib.md5(str(time.time()).encode()).hexdigest()
login_cmd = 'sshpass -p {password} /usr/local/openssh7/bin/ssh {user}@{ip_addr} -o "StrictHostKeyChecking no" -Z {md5_str}'.format(
password=selected_bindhost.host_user.password,
user=selected_bindhost.host_user.username,
ip_addr=selected_bindhost.host.ip_addr,
md5_str=md5_str
)
print('login_cmd:', login_cmd)
# 这里的ssh_instance在subprocess的run执行完之前是拿不到的
# 因为run会进入终端界面
# 问题来了? 怎么拿到进程PID进行strace呢? 重启一个监测进程
# start session tracker script
session_tracker_script = settings.SESSION_TRACKER_SCRIPT
print('session_tracker_script:', session_tracker_script)
tracker_obj = subprocess.Popen("%s %s" % (session_tracker_script, md5_str), shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
# 这个cwd命名式指定python运行的路径的
cwd=settings.BASE_DIR)
# time.sleep(15) # 测试网络延时情况
#create session log
models.SessionLog.objects.create(user=self.user, bind_host=selected_bindhost,
session_tag=md5_str) ssh_instance = subprocess.run(login_cmd, shell=True)
print("------------logout---------")
#print("session tracker output", tracker_obj.stdout.read().decode(),
# tracker_obj.stderr.read().decode()) # 不解码显示的是二进制
print("--------------end logging ------------- ")
# 退出循环
if user_input2 == 'b':
break if __name__ == '__main__':
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CityHunter.settings")
import django
django.setup()
from django.conf import settings
from app01 import models
portal = UserPortal()
portal.interactive()

admin.py

from django.contrib import admin
from django import forms
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField from app01 import models
class UserCreationForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) class Meta:
model = models.UserProfile
fields = ('email', 'name') def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Passwords don't match")
return password2 def save(self, commit=True):
# Save the provided password in hashed format
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user class UserChangeForm(forms.ModelForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
password hash display field.
"""
password = ReadOnlyPasswordHashField() class Meta:
model = models.UserProfile
fields = ('email', 'password', 'name', 'is_active', 'is_superuser') def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the
# field does not have access to the initial value
return self.initial["password"] class UserProfileAdmin(BaseUserAdmin):
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm # The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ('email', 'name', "is_active", 'is_superuser')
list_filter = ('is_superuser',) # 不添加会报错,因为BaseAdmin里面有一个list_filter字段,不覆盖会报错
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal', {'fields': ('name',)}),
('Permissions', {'fields': ('is_superuser',"is_active","bind_hosts","host_groups","user_permissions","groups")}),
)
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'name', 'password1', 'password2')}
),
)
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ("bind_hosts","host_groups","user_permissions","groups") class HostUserAdmin(admin.ModelAdmin):
list_display = ('username','auth_type','password') class SessionLogAdmin(admin.ModelAdmin):
list_display = ('id','session_tag','user','bind_host','date') admin.site.register(models.UserProfile, UserProfileAdmin)
admin.site.register(models.Host)
admin.site.register(models.HostGroup)
admin.site.register(models.HostUser,HostUserAdmin)
admin.site.register(models.BindHost)
admin.site.register(models.IDC)
admin.site.register(models.SessionLog,SessionLogAdmin)

models.py

from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser,PermissionsMixin
)
from django.utils.translation import ugettext_lazy as _
from django.utils.safestring import mark_safe
# Create your models here. class Host(models.Model):
"""主机信息"""
hostname = models.CharField(max_length=64)
ip_addr = models.GenericIPAddressField(unique=True)
port = models.PositiveIntegerField(default=22)
idc = models.ForeignKey("IDC", on_delete=True)
enabled = models.BooleanField(default=True) def __str__(self):
return "%s(%s)"%(self.hostname,self.ip_addr) class IDC(models.Model):
"""机房信息"""
name = models.CharField(max_length=64,unique=True)
def __str__(self):
return self.name class HostGroup(models.Model):
"""主机组"""
name = models.CharField(max_length=64,unique=True)
bind_hosts = models.ManyToManyField("BindHost",blank=True,) 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)
self.is_active = True
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_active = True
user.is_superuser = True
#user.is_admin = True
user.save(using=self._db)
return user class UserProfile(AbstractBaseUser,PermissionsMixin):
"""堡垒机账号"""
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
null=True
)
password = models.CharField(_('password'), max_length=128,
help_text=mark_safe('''<a href='password/'>修改密码</a>'''))
name = models.CharField(max_length=32)
is_active = models.BooleanField(default=True)
#is_admin = models.BooleanField(default=False) bind_hosts = models.ManyToManyField("BindHost",blank=True)
host_groups = models.ManyToManyField("HostGroup",blank=True) objects = UserProfileManager() 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
@property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_active class HostUser(models.Model):
"""主机登录账户"""
auth_type_choices = ((0,'ssh-password'),(1,'ssh-key'))
auth_type = models.SmallIntegerField(choices=auth_type_choices,default=0)
username = models.CharField(max_length=64)
password = models.CharField(max_length=128,blank=True,null=True)
def __str__(self):
return "%s:%s" %(self.username,self.password)
class Meta:
unique_together = ('auth_type','username','password') class BindHost(models.Model):
"""绑定主机和主机账号"""
host = models.ForeignKey("Host", on_delete=True)
host_user = models.ForeignKey("HostUser", on_delete=True)
def __str__(self):
return "%s@%s"%(self.host,self.host_user) class Meta:
unique_together = ('host', 'host_user')
class SessionLog(models.Model):
"""存储session日志"""
# 堡垒机用户 主机信息 唯一标示
user = models.ForeignKey("UserProfile", on_delete=True)
bind_host = models.ForeignKey("BindHost", on_delete=True)
session_tag = models.CharField(max_length=128,unique=True)
date = models.DateTimeField(auto_now_add=True) def __str__(self):
return self.session_tag

session_trackor.sh

#!/bin/bash
md5_str=$1 for i in $(seq 1 30);do
ssh_pid=`ps -ef |grep $md5_str |grep '/usr/local/openssh7/bin/ssh'|grep -v grep |grep -v session_tracker.sh|grep -v sshpass |awk '{print $2}'|sed -n '1p'`
echo "ssh session pid:$ssh_pid"
if [ "$ssh_pid" = "" ];then
sleep 1
continue
else
today=`date "+%Y_%m_%d"`
today_audit_dir="logs/audit/$today"
echo "today_audit_dir: $today_audit_dir"
if [ -d $today_audit_dir ]
then
echo " ----start tracking log---- "
else
echo "dir not exist"
echo " today dir: $today_audit_dir"
sudo mkdir -p $today_audit_dir
fi;
echo "FTL600@HH" | sudo -S /usr/bin/strace -ttt -p $ssh_pid -o "$today_audit_dir/$md5_str.log"
break
fi;
done;

audit.py

#_*_coding:utf-8_*_
import re
class AuditLogHandler(object):
'''分析audit log日志'''
def __init__(self, log_file):
self.log_file_obj = self._get_file(log_file)
def _get_file(self,log_file):
return open(log_file) def parse(self):
cmd_list = []
cmd_str = ''
catch_write5_flag = False #for tab complication
for line in self.log_file_obj:
#print(line.split())
line = line.split()
try:
pid,time_clock,io_call,char = line[0:4]
if io_call.startswith('write(9'):
if char == '"\\177",':#回退
char = '[1<-del]'
if char == '"\\33OB",': #vim中下箭头
char = '[down 1]'
if char == '"\\33OA",': #vim中下箭头
char = '[up 1]'
if char == '"\\33OC",': #vim中右移
char = '[->1]'
if char == '"\\33OD",': #vim中左移
char = '[1<-]'
if char == '"\33[2;2R",': #进入vim模式
continue
if char == '"\\33[>1;95;0c",': # 进入vim模式
char = '[----enter vim mode-----]'
if char == '"\\33[A",': #命令行向上箭头
char = '[up 1]'
catch_write5_flag = True #取到向上按键拿到的历史命令
if char == '"\\33[B",': # 命令行向上箭头
char = '[down 1]'
catch_write5_flag = True # 取到向下按键拿到的历史命令
if char == '"\\33[C",': # 命令行向右移动1位
char = '[->1]'
if char == '"\\33[D",': # 命令行向左移动1位
char = '[1<-]' cmd_str += char.strip('"",')
if char == '"\\t",':
catch_write5_flag = True
continue
if char == '"\\r",':
cmd_list.append([time_clock,cmd_str])
cmd_str = '' # 重置
if char == '"':#space
cmd_str += ' '
if catch_write5_flag: # to catch tab completion
if io_call.startswith('write(5'):
if io_call == '"\7",': # 空键,不是空格,是回退不了就是这个键
pass
else:
cmd_str += char.strip('"",')
catch_write5_flag = False
except ValueError as e:
print("\033[031;1mSession log record err,please contact your IT admin,\033[0m",e)
#print(cmd_list)
for cmd in cmd_list:
print(cmd)
# return cmd_list
if __name__ == "__main__":
parser = AuditLogHandler('ssh.log')
parser.parse()

项目启动:

omc@omc-virtual-machine:~/CityHunter$ python3 manage.py  runserver 0.0.0.0:9000

后台的登录:

前台显示的日志:

后台显示的日志:

从堡垒机登录服务器执行命令

生成的日志文件在堡垒机服务器:

问题解决

问题1: user_enterpoint.py文件无法访问数据库

答案: 文件夹的权限不正确

sudo chown cityhunter:cityhunter CityHunter -R

问题2: 登录其他服务器后需要频发的cityhunter用户输入密码

答:需要给cityhunter用户提权,免密执行特定的指令[这里简化,可以免密执行任何命令]

问题3:界面打不开我们的数据库文件

答: 更改属组为omc用户即可,因为omc用户启动的文件

sudo chown omc /home/omc/CityHunter –R

问题4: 无法生成日志文件

答: 根本原因在于没有执行session_trackor.sh脚本

可能的原因是: 获取到PID不正确,导致有多个PID匹配了出来

本没有执行的权限

问题5: /home/omc/CityHunter/backend/session_trackor.sh: Syntax error: word unexpected (expecting "do")

答: 代码没有问题,可能是文件的格式不是UNIX格式的,转换一下格式

dos2unix session_trackor.sh 

问题6:有文件,但是文件的内容不对,无法解析

答:进行追踪的PID不正确,必须是访问服务器的那个ssh脚本[更改匹配规则,]

ps -ef |grep '2b0db58b7476fda234448d614bdaa790' |grep '/usr/local/openssh7/bin/ssh'|grep -v grep |grep -v session_tracker.sh|grep -v sshpass |awk '{print $2}'|sed -n '1p'

问题8:

# 这里的ssh_instance在subprocess的run执行完之前是拿不到的
# 因为run会进入另一个终端界面
# 问题来了? 怎么拿到进程PID进行strace呢?
ssh_instance = subprocess.run(login_cmd, shell=True)

问题9: 多个ssh终端连接的时候,如何正确的进行区分?

答案: sshpass进行连接的时候,添加唯一标示符解决【修改ssh源码解决】

【但是ssh连接的参数是固定的~~】  ---> 【更改ssh的源码解决】

审计系统---堡垒机项目之用户交互+session日志写入数据库[完整版]的更多相关文章

  1. 审计系统---堡垒机项目之strace追踪ssh

    strace 追踪ssh的进程ID,记录操作的命令[实际上是内核里面记录的东西],进行操作日志的Py解析达到效果. 修改ssh源码添加访问标志位 源码下载:[本文示例:openssh-7.4p1.ta ...

  2. 审计系统---堡垒机python下ssh的使用

    堡垒机python下ssh的使用 [堡垒机更多参考]http://www.cnblogs.com/alex3714/articles/5286889.html [paramiko的Demo实例]htt ...

  3. 审计系统---初识堡垒机180501【all】

    堡垒机背景[审计系统] SRE是指Site Reliability Engineer (/运维工程师=运行维护 业务系统) 运维: 维护系统,维护业务,跟业务去走 防火墙: 禁止不必要的访问[直接访问 ...

  4. (转)用Python写堡垒机项目

    原文:https://blog.csdn.net/ywq935/article/details/78816860 前言 堡垒机是一种运维安全审计系统.主要的功能是对运维人员的运维操作进行审计和权限控制 ...

  5. 一个100%Go语言的Web-Term-SSH 堡垒机项目

    SSH-Fortress 1. What does it do? Make your cluster servers be more safe by expose your SSH connectio ...

  6. Python 13 简单项目-堡垒机

    本节内容 项目实战:运维堡垒机开发 前景介绍 到目前为止,很多公司对堡垒机依然不太感冒,其实是没有充分认识到堡垒机在IT管理中的重要作用的,很多人觉得,堡垒机就是跳板机,其实这个认识是不全面的,跳板功 ...

  7. 主机管理+堡垒机系统开发:webssh(十)

    一.安装shellinabox 1.安装依赖工具 yum install git openssl-devel pam-devel zlib-devel autoconf automake libtoo ...

  8. day11 堡垒机

    项目实战:运维堡垒机开发 前景介绍 到目前为止,很多公司对堡垒机依然不太感冒,其实是没有充分认识到堡垒机在IT管理中的重要作用的,很多人觉得,堡垒机就是跳板机,其实这个认识是不全面的,跳板功能只是堡垒 ...

  9. Python之路,Day12 - 那就做个堡垒机吧

    Python之路,Day12 - 那就做个堡垒机吧   本节内容 项目实战:运维堡垒机开发 前景介绍 到目前为止,很多公司对堡垒机依然不太感冒,其实是没有充分认识到堡垒机在IT管理中的重要作用的,很多 ...

随机推荐

  1. 共识算法:PBFT、RAFT

    转自:https://www.cnblogs.com/davidwang456/articles/9001331.html 区块链技术中,共识算法是其中核心的一个组成部分.首先我们来思考一个问题:什么 ...

  2. python迭代器、生成器、装饰器

    1 迭代器 这里我们先来回顾一下什么是可迭代对象(Iterable)? 可以直接作用于for循环的对象统称为可迭代对象,即Iterable. # 一是集合数据类型,如list.tuple.dict.s ...

  3. springMVC对于Controller返回值的可选类型

    2018-01-11 对于springMVC处理方法支持支持一系列的返回方式:  (1)ModelAndView (2)Model (3)ModelMap (4)Map (5)View (6)Stri ...

  4. MyEclipse2014破解方法

    之前一直使用的MyEclipse2014过期了,无奈之下只能在网上搜怎么破解,结果很管用,在这里记录并和大家分享 https://jingyan.baidu.com/article/fdbd42771 ...

  5. spring-boot前端参数单位转换

    import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import ja ...

  6. SpringBoot源码分析之SpringBoot的启动过程

    SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30   |   分类于 springboot  |   0 Comments  |   阅读次数 SpringB ...

  7. 阿里云提示ECS服务器存在漏洞处理方法

    1.阿里云提供生成修复命令,但是这个只提供给企业版,即收费的: 2.自己手动修复的话, 采用软件升级一般都可以解决.除了提示带kernel的高危漏洞的,其他的不需要重启实例即可修复. 有kernel的 ...

  8. 网络基础1_TCP和HTTP

    TCP/IP 是互联网相关的各类协议族的总称,并且进行分层,分为应用层,传输层,网络层,数据链路层这四层协议,分层的好处,是便于后期的优化与改进,扩展性好 应用层:主要为客户提供应用服务,       ...

  9. 【转】分布式环境下5种session处理策略(大型网站技术架构:核心原理与案例分析 里面的方案)

    前言 在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理.如果不做任何处理的话,用户将出现频繁登录的现象,比如集群中存在A.B两台服务器,用户在第一次访问网站时,Ngin ...

  10. Microservices与DDD的关系

    Microservices(微服务架构)和DDD(领域驱动设计)是时下最炙手可热的两个技术词汇.在最近两年的咨询工作中总是会被不同的团队和角色询问,由此也促使我思考为什么这两个技术词汇被这么深入人心的 ...