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 = getpass.getpass("Password:").strip()
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
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
更改db文件的权限,方便sessioni日志的记录
omc@omc-virtual-machine:~$ cd CityHunter/ omc@omc-virtual-machine:~$ chmod 777 db.sqlite3 【更改文件属组为cityhunber也可以】
上传Django项目到服务器并解压
omc@omc-virtual-machine:~$ unzip CityHunter.zip 【解压Django的zip包】
创建登录堡垒机服务器[Ubuntun]的账户
Ubuntu上创建ciythunber用户:
omc@omc-virtual-machine:~$ sudo adduser cityhunter omc@omc-virtual-machine:~$ tail -1 /etc/passwd
cityhunter用户增加sudo权限
root@omc-virtual-machine:~# vim /etc/sudoers root@omc-virtual-machine:~# visudo -c root@omc-virtual-machine:~# grep 'cityhunter' /etc/sudoers
设置登录堡垒机服务器后自动执行user_enterpoint.py脚本且执行完成后自动退出服务器
cityhunter@omc-virtual-machine:~$ tail -3 /home/cityhunter/.bashrc 【仅添加3行内容】 # for cityhunter auditing: after user logged auto execute this python file /usr/bin/python3 /home/omc/CityHunter/user_enterpoint.py logout
另:新创建的用户没有Django的环境变量,需要手动添加才能执行脚本成功
Ps: 如果环境上有DJango环境变量则不用执行如下操作
查看omc用户的Django变量所在的位置
omc@omc-virtual-machine:~/CityHunter$ python3 Python 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.path
新创建的cityhunter用户添加py的环境变量
cityhunter@omc-virtual-machine:~$ pip3 install pika 【仅仅是为了添加Py变量方便】
查看新用户cityhunter的Py环境变量
cityhunter@omc-virtual-machine:~$ python3 Python 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.path
复制omc用户下的Django文件到cityhunter用户下
omc@omc-virtual-machine:~/CityHunter$ sudo cp -rf /home/omc/.local/lib/python3.5/site-packages/ /home/cityhunter/.local/lib/python3.5/
更改Django文件属组,让cityhunter用户可以访问
cityhunter用户下查看属组: cityhunter@omc-virtual-machine:~$ id cityhunter 【cityhunter用户下查看用户属组】 uid=1001(cityhunter) gid=1001(cityhunter) groups=1001(cityhunter)
cmc用户下更改属组[服务器是omc服务器]
omc@omc-virtual-machine:~/CityHunter$ sudo chown cityhunter:cityhunter -R /home/cityhunter/.local/
Ubuntu下cityhunter用户执行命令:
cityhunter@omc-virtual-machine:~/CityHunter$ python3 /home/omc/CityHunter/user_enterpoint.py Username:ftl@126.com Password:cnpXXX welcome login... 验证完成... ---------------------------------------------------------------------- host_groups: <QuerySet [<HostGroup: Webserver>]> host_groups_count: 1 ---------------------------------------------------------------------- 0. Webserver【Host Count: 2】 1. Ungrouped Hosts[1] Please Choose Group:0 selected_group: <QuerySet [<BindHost: Ubuntu(192.168.25.110)@omc:lem600XXX>, <BindHost: Redhat(192.168.25.133)@root:cnpXXX>]> selected_group_count: 2 0. Ubuntu(192.168.25.110 user:omc) 1. Redhat(192.168.25.133 user:root) Please Choose Host:1 --------------start logging -------------- Redhat(192.168.25.133)@root:cnp200XXX login_cmd: sshpass -p cnp200XXX ssh root@192.168.25.133 -o "StrictHostKeyChecking no" Last login: Mon May 7 07:44:00 2018 from 192.168.25.110 [root@localhost ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/sda2 18G 3.3G 14G 20% / tmpfs 250M 0 250M 0% /dev/shm /dev/sda1 291M 32M 245M 12% /boot
Linux服务器[Ubuntu]下DJango的运行:
后台启动Django: omc@omc-virtual-machine:~$ cd /home/omc/CityHunter/ omc@omc-virtual-machine:~/CityHunter$ python3 manage.py runserver 0.0.0.0:9000 omc@omc-virtual-machine:~$ netstat -an|grep 9000
前台Win7访问:
远程使用cityhunter用户登录结果演示:
终端登录:
问题记录
问题现象1:
django.core.exceptions.ImproperlyConfigured: Requested setting AUTHENTICATION_BACKENDS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings
我们在自己的Py脚本里面调用Django的数据库,没有添加环境变量导致验证失败【参考manage.py,添加环境变量解决】
问题解决:
后台结果:
问题现象2:
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
问题定位:
问题解决:
1. 添加Django的环境变量后导入DJango的东西
2. 删除导入信息[不实际]





















