tiger运维走向Python开发_堡垒机项目
堡垒机项目
一 堡垒机的实现
实现思路:

堡垒机执行流程:
- 管理员为用户在服务器上创建账号(将公钥放置服务器,或者使用用户名密码)
- 用户登陆堡垒机,输入堡垒机用户名密码,现实当前用户管理的服务器列表
- 用户选择服务器,并自动登陆
- 执行操作并同时将用户操作记录
注:配置.brashrc实现ssh登陆后自动执行脚本,如:/usr/bin/python /home/lxh/menu.py
二 创建Django项目
1 表结构设计

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") enabled = models.BooleanField(default=True) def __str__(self): return "%s(%s)" %(self.hostname, self.ip_addr) class Meta: verbose_name_plural = "主机信息" class IDC(models.Model): """机房信息""" name = models.CharField(max_length=64, unique=True) def __str__(self): return self.name class Meta: verbose_name_plural = "机房信息" class HostGroup(models.Model): """主机组""" name = models.CharField(max_length=64, unique=True) bind_hosts = models.ManyToManyField("BindHost", blank=True, null=True) def __str__(self): return self.name class Meta: verbose_name_plural = "主机组" class UserProfileManager(BaseUserManager): def create_user(self, email, name, password=None): """ 使用给定的电子邮件,出生日期和密码创建并保存用户。 """ if not email: raise ValueError('用户必须为Email格式') 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): """ 使用给定的电子邮件,出生日期和密码创建并保存超级用户。 """ user = self.create_user(email,password=password,name=name,) user.is_active = True user.is_superuser = 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?" # 最简单的答案:所有管理员都是工作人员 return self.is_active class Meta: verbose_name_plural = "堡垒机账号" 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') # 配置联合唯一字段 verbose_name_plural = "主机登录账户" class BindHost(models.Model): """绑定主机和主机账号""" host = models.ForeignKey("Host") host_user = models.ForeignKey("HostUser") def __str__(self): return "%s@%s"%(self.host, self.host_user) class Meta: unique_together = ('host', 'host_user') # 配置联合唯一字段 verbose_name_plural = "绑定主机和主机账号"
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 app_lxh import models # Register your models here. 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',) 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') 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)
2 在CrazyAss项目下创建堡垒机启动文件user_enterpoint.py
#__author: Tiger lee # -*- coding:utf-8 -*- #date: 2017/2/24 #IN Python 3.5 import os import getpass import subprocess from django.contrib.auth import authenticate class UserPortal(object): """用户命令行端交互入口""" def __init__(self): self.user = None def user_auth(self): """ 完成用户交互 :return: """ retry_count = 0 while retry_count < 3: username = input("请输入用户名: ").strip() if len(username) == 0:continue password = getpass.getpass("请输入密码: ").strip() if len(password) == 0: print ("密码不能为空") continue user = authenticate(username = username, password = password) if user: self.user = user # print("welcome login...") return else: print ("用户名或密码错误!") retry_count += 1 else: exit("尝试了太多次错误用户名与密码,请稍候再试") def interactive(self): """ 交互函数 :return: """ self.user_auth() if self.user: exit_flag = False while not exit_flag: # print (self.user.bind_hosts.select_related()) # 这个用户所关联的所有主机 # print (self.user.host_groups.select_related()) for index,host_group in enumerate(self.user.host_groups.all()): print ("%s. %s[%s]" %(index, host_group.name, host_group.bind_hosts.all().count())) print ("%s. 未分组主机[%s]" %(index+1,self.user.bind_hosts.select_related().count())) group_input = input("请输入分组: ").strip() if len(group_input) == 0:continue if group_input.isdigit(): group_input = int(group_input) if group_input >= 0 and group_input < self.user.host_groups.all().count(): selected_hostgroup = self.user.host_groups.all()[group_input] elif group_input == self.user.host_groups.all().count(): # 选中了未分组的那组主机 selected_hostgroup = self.user # 因为self.user里也有一个bind_hosts, 跟HostGroup.bind_hosts指向的表一样 else: print ("无效的主机组") continue while True: for index,bind_host in enumerate(selected_hostgroup.bind_hosts.all()): print ("%s. %s(IP: %s 用户名: %s)" %(index, bind_host.host.hostname, bind_host.host.ip_addr, bind_host.host_user.username)) host_input = input("请输入主机: ").strip() if len(host_input) == 0: continue if host_input.isdigit(): host_input = int(host_input) if host_input >= 0 and host_input < selected_hostgroup.bind_hosts.all().count(): selected_bindhost = selected_hostgroup.bind_hosts.all()[host_input] print ("登录主机", selected_bindhost) self.shell_login(selected_bindhost.host_user.password, selected_bindhost.host_user.username, selected_bindhost.host.ip_addr) if host_input == "b": break def shell_login(self, password, username, ip_addr): """ 远程登录主机函数 :param password: :param username: :param ip_addr: :return: """ login_cmd = 'sshpass -p {password} ssh {user}@{ip} -o "StrictHostKeyChecking no"'.format( password = password, user = username, ip = ip_addr ) print (login_cmd) ssh_instance = subprocess.run(login_cmd, shell=True) print (".........logout.........") return if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CrazyAss.settings") import django django.setup() from app_lxh import models portal = UserPortal() portal.interactive()
3 settings.py配置文件
""" Django settings for CrazyAss project. Generated by 'django-admin startproject' using Django 1.10.5. For more information on this file, see https://docs.djangoproject.com/en/1.10/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.10/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'hod5sh+!$4@n^iv-_(mvowy!0k^$(9sw-10)ns^m$q(t)=ku45' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = ["*"] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app_lxh', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'CrazyAss.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'CrazyAss.wsgi.application' # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ AUTH_USER_MODEL = "app_lxh.UserProfile" LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ STATIC_URL = '/static/'
三 堡垒机的配置
为了保证堡垒机上的安全,需要在linux堡垒机上创建堡垒机登录账户

由于此账户是新创建的,并不能运行在原来账户上安装的django等项目,所以需要将原用户上面的python3.5/site-packages复制一份到此用户目录下


然后就可以切换到此用户来执行CrazyAss项目里面的user_enterpoint.py脚本了

但是我们的目的是一旦登录到这个用户,就自动执行这个脚本,所以需要修改此用户的.bashrc文件,在最后添加此命令:
# 在crazyass用户目录下 crazyass@lxh-virtual-machine:~$ vim .bashrc # 在.bashrc配置文件最后添加以下代码 #for CrazyAss auditing python /home/lxh/CrazyAss/user_enterpoint.py
logout
.bashrc文件, 这个文件的作用是此用户登录后自动加载此配置文件,并执行里面的命令

完成! 效果如下

按Ctrl+C退出脚本时,也自动退出此用户
注: 此Django项目只能在linux机器上运行,如要进入admin修改配置,需要启动此项目时, 进入CrazyAss项目下,使用以下命令启动
lxh@lxh-virtual-machine:~/CrazyAss$ python manage.py runserver 0.0.0.0:8000 Performing system checks... System check identified some issues: WARNINGS: app_lxh.HostGroup.bind_hosts: (fields.W340) null has no effect on ManyToManyField. System check identified 1 issue (0 silenced). February 26, 2017 - 04:33:13 Django version 1.10.5, using settings 'CrazyAss.settings' Starting development server at http://0.0.0.0:8000/ Quit the server with CONTROL-C.
...
作者:TigerLee
出处:http://www.cnblogs.com/tiger666/
本文版权归作者和博客园所有,欢迎转载。转载请在留言板处留言给我,且在文章标明原文链接,谢谢!
如果您觉得本篇博文对您有所收获,觉得我还算用心,请点击右下角的 [推荐],谢谢!

浙公网安备 33010602011771号