mirror of https://github.com/jumpserver/jumpserver
516 lines
18 KiB
Python
516 lines
18 KiB
Python
#coding: utf-8
|
|
|
|
from django.http import HttpResponse
|
|
from django.template import RequestContext
|
|
from django.shortcuts import render_to_response
|
|
from django.http import HttpResponseRedirect
|
|
from UserManage.models import User, Group
|
|
from Assets.models import Assets, AssetsUser
|
|
import subprocess
|
|
from Crypto.Cipher import AES
|
|
from binascii import b2a_hex, a2b_hex
|
|
import random
|
|
import ConfigParser
|
|
import pam
|
|
import os
|
|
import ldap
|
|
import ldap.modlist as modlist
|
|
import crypt
|
|
from UserManage.forms import UserAddForm, GroupAddForm
|
|
|
|
|
|
base_dir = "/opt/jumpserver/"
|
|
cf = ConfigParser.ConfigParser()
|
|
cf.read('%s/jumpserver.conf' % base_dir)
|
|
|
|
key = cf.get('jumpserver', 'key')
|
|
rsa_dir = cf.get('jumpserver', 'rsa_dir')
|
|
useradd_shell = cf.get('jumpserver', 'useradd_shell')
|
|
userdel_shell = cf.get('jumpserver', 'userdel_shell')
|
|
sudoadd_shell = cf.get('jumpserver', 'sudoadd_shell')
|
|
sudodel_shell = cf.get('jumpserver', 'sudodel_shell')
|
|
keygen_shell = cf.get('jumpserver', 'keygen_shell')
|
|
chgpass_shell = cf.get('jumpserver', 'chgpass_shell')
|
|
admin = ['admin']
|
|
ldap_host = cf.get('jumpserver', 'ldap_host')
|
|
ldap_base_dn = cf.get('jumpserver', 'ldap_base_dn')
|
|
admin_cn = cf.get('jumpserver', 'admin_cn')
|
|
admin_pass = cf.get('jumpserver', 'admin_pass')
|
|
|
|
|
|
def keygen(num):
|
|
"""生成随机密码"""
|
|
seed = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
sa = []
|
|
for i in range(num):
|
|
sa.append(random.choice(seed))
|
|
salt = ''.join(sa)
|
|
return salt
|
|
|
|
|
|
def bash(cmd):
|
|
return subprocess.call(cmd, shell=True)
|
|
|
|
|
|
class PyCrypt(object):
|
|
"""对称加密解密"""
|
|
def __init__(self, key):
|
|
self.key = key
|
|
self.mode = AES.MODE_CBC
|
|
|
|
def encrypt(self, text):
|
|
cryptor = AES.new(self.key, self.mode, b'0000000000000000')
|
|
length = 16
|
|
count = len(text)
|
|
if count < length:
|
|
add = (length - count)
|
|
text += ('\0' * add)
|
|
elif count > length:
|
|
add = (length - (count % length))
|
|
text += ('\0' * add)
|
|
ciphertext = cryptor.encrypt(text)
|
|
return b2a_hex(ciphertext)
|
|
|
|
def decrypt(self, text):
|
|
cryptor = AES.new(self.key, self.mode, b'0000000000000000')
|
|
plain_text = cryptor.decrypt(a2b_hex(text))
|
|
return plain_text.rstrip('\0')
|
|
|
|
|
|
def rsa_gen(username, key_pass, rsa_dir=rsa_dir):
|
|
rsa_file = '%s/%s' % (rsa_dir, username)
|
|
pub_file = '%s.pub' % rsa_file
|
|
authorized_file = '/home/%s/.ssh/authorized_keys' % username
|
|
if os.path.exists(rsa_file):
|
|
os.unlink(rsa_file)
|
|
ret = bash('ssh-keygen -t rsa -f %s -P %s &> /dev/null && echo "######## rsa_gen Ok."' % (rsa_file, key_pass))
|
|
if not ret:
|
|
try:
|
|
if not os.path.isdir('/home/%s/.ssh' % username):
|
|
os.mkdir('/home/%s/.ssh' % username)
|
|
pub = open(pub_file, 'r')
|
|
authorized = open(authorized_file, 'w')
|
|
authorized.write(pub.read())
|
|
pub.close()
|
|
authorized.close()
|
|
except Exception:
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
|
|
class LDAPMgmt():
|
|
def __init__(self,
|
|
ldap_host=ldap_host,
|
|
ldap_base_dn=ldap_base_dn,
|
|
admin_cn=admin_cn,
|
|
admin_pass=admin_pass):
|
|
self.ldap_host = ldap_host
|
|
self.ldap_base_dn = ldap_base_dn
|
|
self.admin_cn = admin_cn
|
|
self.admin_pass = admin_pass
|
|
self.conn = ldap.initialize(ldap_host)
|
|
self.conn.set_option(ldap.OPT_REFERRALS, 0)
|
|
self.conn.protocol_version = ldap.VERSION3
|
|
self.conn.simple_bind_s(admin_cn, admin_pass)
|
|
|
|
def list(self, filter, scope=ldap.SCOPE_SUBTREE, attr=None):
|
|
try:
|
|
ldap_result = self.conn.search_s(self.ldap_base_dn, scope, filter, attr)
|
|
print 'Here is the result: '
|
|
for entry in ldap_result:
|
|
name, data = entry
|
|
print '#'*20, name, '#'*20
|
|
for k, v in data.items():
|
|
print '%s: %s' % (k,v)
|
|
except ldap.LDAPError, e:
|
|
print e
|
|
|
|
def add(self, dn, attrs):
|
|
try:
|
|
ldif = modlist.addModlist(attrs)
|
|
self.conn.add_s(dn, ldif)
|
|
except ldap.LDAPError, e:
|
|
print e
|
|
|
|
def modify(self, dn, attrs):
|
|
try:
|
|
attr_s = []
|
|
for k, v in attrs.items():
|
|
attr_s.append((2, k, v))
|
|
self.conn.modify_s(dn, attr_s)
|
|
except ldap.LDAPError, e:
|
|
print e
|
|
|
|
def delete(self, dn):
|
|
try:
|
|
self.conn.delete_s(dn)
|
|
except ldap.LDAPError, e:
|
|
print e
|
|
|
|
|
|
def gen_sha512(salt, password):
|
|
return crypt.crypt(password, '$6$%s$' % salt)
|
|
|
|
|
|
def login(request):
|
|
"""登录界面"""
|
|
if request.session.get('username'):
|
|
return HttpResponseRedirect('/')
|
|
if request.method == 'GET':
|
|
return render_to_response('login.html')
|
|
if request.method == 'POST':
|
|
username = request.POST.get('username')
|
|
password = request.POST.get('password')
|
|
if pam.authenticate(username, password):
|
|
if username in admin:
|
|
request.session['username'] = username
|
|
request.session['admin'] = 1
|
|
else:
|
|
request.session['username'] = username
|
|
request.session['admin'] = 0
|
|
return HttpResponseRedirect('/')
|
|
else:
|
|
error = '密码错误,请重新输入。'
|
|
|
|
return render_to_response('login.html',{'error': error})
|
|
|
|
|
|
def login_required(func):
|
|
"""要求登录的装饰器"""
|
|
def _deco(request, *args, **kwargs):
|
|
if not request.session.get('username'):
|
|
return HttpResponseRedirect('/login/')
|
|
return func(request, *args, **kwargs)
|
|
return _deco
|
|
|
|
|
|
def admin_required(func):
|
|
"""要求用户是admin的装饰器"""
|
|
def _deco(request, *args, **kwargs):
|
|
if not request.session.get('admin'):
|
|
return HttpResponseRedirect('/')
|
|
return func(request, *args, **kwargs)
|
|
return _deco
|
|
|
|
|
|
def logout(request):
|
|
"""注销登录调用"""
|
|
if request.session.get('username'):
|
|
del request.session['username']
|
|
return HttpResponseRedirect('/login/')
|
|
|
|
|
|
@login_required
|
|
def downKey(request):
|
|
"""下载key"""
|
|
username = request.session.get('username')
|
|
filename = '%s/keys/%s' % (base_dir, username)
|
|
f = open(filename)
|
|
data = f.read()
|
|
f.close()
|
|
response = HttpResponse(data, content_type='application/octet-stream')
|
|
response['Content-Disposition'] = 'attachment; filename=%s.rsa' % username
|
|
return response
|
|
|
|
|
|
@login_required
|
|
def index(request):
|
|
"""主页"""
|
|
username = request.session.get('username')
|
|
name = User.objects.filter(username=username)
|
|
assets = []
|
|
if name:
|
|
user_assets = AssetsUser.objects.filter(uid=name[0])
|
|
if user_assets:
|
|
for user_asset in user_assets:
|
|
assets.append(user_asset.aid)
|
|
return render_to_response('index.html', {'index': 'active', 'assets': assets},
|
|
context_instance=RequestContext(request))
|
|
|
|
|
|
@admin_required
|
|
def showUser(request):
|
|
"""查看所有用户"""
|
|
users = User.objects.all()
|
|
info = ''
|
|
error = ''
|
|
if request.method == 'POST':
|
|
selected_user = request.REQUEST.getlist('selected')
|
|
if selected_user:
|
|
for id in selected_user:
|
|
try:
|
|
user_del = User.objects.get(id=id)
|
|
username = user_del.username
|
|
user_del.delete()
|
|
except Exception,e:
|
|
error = u'数据库中用户删除错误' + unicode(e)
|
|
bash_del = bash("userdel -r %s" % username)
|
|
if bash_del != 0:
|
|
error = u'bash中用户删除错误'
|
|
|
|
try:
|
|
ldap_del = LDAPMgmt()
|
|
user_dn = "uid=%s,ou=People,%s" % (username, ldap_base_dn)
|
|
ldap_del.delete(user_dn)
|
|
except Exception, e:
|
|
error = u'ldap中用户删除错误' + unicode(e)
|
|
|
|
if not error:
|
|
info = '用户删除成功'
|
|
|
|
return render_to_response('showUser.html',
|
|
{'users': users, 'info': info, 'error': error, 'user_menu': 'active'},
|
|
context_instance=RequestContext(request))
|
|
|
|
|
|
@admin_required
|
|
def addUser(request):
|
|
"""添加用户"""
|
|
msg = ''
|
|
form = UserAddForm()
|
|
jm = PyCrypt(key)
|
|
if request.method == 'GET':
|
|
return render_to_response('addUser.html', {'user_menu': 'active', 'form': form},
|
|
context_instance=RequestContext(request))
|
|
else:
|
|
form = UserAddForm(request.POST)
|
|
if form.is_valid():
|
|
user = form.cleaned_data
|
|
username = user['username']
|
|
password = user['password']
|
|
key_pass = user['key_pass']
|
|
name = user['name']
|
|
is_admin = user['is_admin']
|
|
is_superuser = user['is_superuser']
|
|
ldap_password = keygen(16)
|
|
group_post = user['group']
|
|
groups = []
|
|
for group_name in group_post:
|
|
groups.append(Group.objects.get(name=group_name))
|
|
|
|
u = User(
|
|
username=username,
|
|
password=password,
|
|
key_pass=key_pass,
|
|
name=name,
|
|
is_admin=is_admin,
|
|
is_superuser=is_superuser,
|
|
ldap_password=ldap_password)
|
|
try:
|
|
u.save()
|
|
u.group = groups
|
|
u.save()
|
|
except Exception, e:
|
|
error = u'数据库插入用户错误' + unicode(e)
|
|
return render_to_response('addUser.html', {'user_menu': 'active', 'form': form, 'error': error},
|
|
context_instance=RequestContext(request))
|
|
|
|
ret_add = bash('useradd %s' % username)
|
|
ret_passwd = bash('echo %s | passwd --stdin %s' % (password, username))
|
|
ret_rsa = rsa_gen(username, key_pass)
|
|
|
|
if [ret_add, ret_passwd, ret_rsa].count(0) < 3:
|
|
error = u'跳板机添加用户失败'
|
|
ret_del = bash('userdel -r %s' % username)
|
|
u.delete()
|
|
return render_to_response('addUser.html', {'user_menu': 'active', 'form': form, 'error': error},
|
|
context_instance=RequestContext(request))
|
|
|
|
user_dn = "uid=%s,ou=People,%s" % (username, ldap_base_dn)
|
|
userPassword = gen_sha512(keygen(6), ldap_password)
|
|
user_attr = {
|
|
'uid': [str(username)],
|
|
'cn': [str(username)],
|
|
'objectClass': ['account', 'posixAccount', 'top', 'shadowAccount'],
|
|
'userPassword': ['{crypt}%s' % userPassword],
|
|
'shadowLastChange': ['16328'],
|
|
'shadowMin': ['0'],
|
|
'shadowMax': ['99999'],
|
|
'shadowWarning': ['7'],
|
|
'loginShell': ['/bin/bash'],
|
|
'uidNumber': [str(u.id)],
|
|
'gidNumber': [str(u.id)],
|
|
'homeDirectory': [str('/home/%s' % username)]}
|
|
|
|
group_dn = "cn=%s,out=Group,%s" % (username, ldap_base_dn)
|
|
group_attr = {
|
|
'objectClass': ['posixGroup', 'top'],
|
|
'cn': [str(username)],
|
|
'userPassword': ['{crypt}x'],
|
|
'gidNumber': [str(u.id)]
|
|
}
|
|
|
|
try:
|
|
ldap_user = LDAPMgmt()
|
|
ldap_user.add(user_dn, user_attr)
|
|
ldap_user.add(group_dn, group_attr)
|
|
except Exception, e:
|
|
error = u'添加ladp用户失败' + unicode(e)
|
|
try:
|
|
bash('userdel -r %s' % username)
|
|
u.delete()
|
|
ldap_user.delete(user_dn)
|
|
ldap_user.delete(group_dn)
|
|
except:
|
|
pass
|
|
return render_to_response('addUser.html', {'user_menu': 'active', 'form': form, 'error': error},
|
|
context_instance=RequestContext(request))
|
|
msg = u'添加用户成功'
|
|
return render_to_response('addUser.html', {'user_menu': 'active', 'form': form, 'msg': msg},
|
|
context_instance=RequestContext(request))
|
|
|
|
|
|
@admin_required
|
|
def showAssets(request):
|
|
"""查看服务器"""
|
|
info = ''
|
|
assets = Assets.objects.all()
|
|
if request.method == 'POST':
|
|
assets_del = request.REQUEST.getlist('selected')
|
|
for asset_id in assets_del:
|
|
asset_del = Assets.objects.get(id=asset_id)
|
|
asset_del.delete()
|
|
info = '主机信息删除成功!'
|
|
return render_to_response('showAssets.html', {'assets': assets, 'info': info, 'asset_menu': 'active'},
|
|
context_instance=RequestContext(request))
|
|
|
|
|
|
@admin_required
|
|
def addAssets(request):
|
|
"""添加服务器"""
|
|
error = ''
|
|
msg = ''
|
|
if request.method == 'POST':
|
|
ip = request.POST.get('ip')
|
|
port = request.POST.get('port')
|
|
comment = request.POST.get('comment')
|
|
|
|
if '' in (ip, port):
|
|
error = '带*号内容不能为空。'
|
|
elif Assets.objects.filter(ip=ip):
|
|
error = '主机已存在。'
|
|
if not error:
|
|
asset = Assets(ip=ip, port=port, comment=comment)
|
|
asset.save()
|
|
msg = u'%s 添加成功' % ip
|
|
|
|
return render_to_response('addAssets.html', {'msg': msg, 'error': error, 'asset_menu': 'active'},
|
|
context_instance=RequestContext(request))
|
|
|
|
|
|
@admin_required
|
|
def showPerm(request):
|
|
"""查看权限"""
|
|
users = User.objects.all()
|
|
if request.method == 'POST':
|
|
assets_del = request.REQUEST.getlist('selected')
|
|
username = request.POST.get('username')
|
|
user = User.objects.get(username=username)
|
|
|
|
for asset_id in assets_del:
|
|
asset = Assets.objects.get(id=asset_id)
|
|
asset_user_del = AssetsUser.objects.get(uid=user, aid=asset)
|
|
asset_user_del.delete()
|
|
return HttpResponseRedirect('/showPerm/?username=%s' % username)
|
|
|
|
elif request.method == 'GET':
|
|
if request.GET.get('username'):
|
|
username = request.GET.get('username')
|
|
user = User.objects.filter(username=username)[0]
|
|
assets_user = AssetsUser.objects.filter(uid=user.id)
|
|
return render_to_response('perms.html',
|
|
{'user': user, 'assets': assets_user, 'perm_menu': 'active'},
|
|
context_instance=RequestContext(request))
|
|
return render_to_response('showPerm.html', {'users': users, 'perm_menu': 'active'},
|
|
context_instance=RequestContext(request))
|
|
|
|
|
|
@admin_required
|
|
def addPerm(request):
|
|
"""增加授权"""
|
|
users = User.objects.all()
|
|
have_assets = []
|
|
if request.method == 'POST':
|
|
username = request.POST.get('username')
|
|
assets_id = request.REQUEST.getlist('asset')
|
|
user = User.objects.get(username=username)
|
|
for asset_id in assets_id:
|
|
asset = Assets.objects.get(id=asset_id)
|
|
asset_user = AssetsUser(uid=user, aid=asset)
|
|
asset_user.save()
|
|
return HttpResponseRedirect('/addPerm/?username=%s' % username)
|
|
elif request.method == 'GET':
|
|
if request.GET.get('username'):
|
|
username = request.GET.get('username')
|
|
user = User.objects.get(username=username)
|
|
assets_user = AssetsUser.objects.filter(uid=user.id)
|
|
for asset_user in assets_user:
|
|
have_assets.append(asset_user.aid)
|
|
all_assets = Assets.objects.all()
|
|
other_assets = list(set(all_assets) - set(have_assets))
|
|
return render_to_response('addUserPerm.html',
|
|
{'user': user, 'assets': other_assets, 'perm_menu': 'active'},
|
|
context_instance=RequestContext(request))
|
|
return render_to_response('addPerm.html',
|
|
{'users': users, 'perm_menu': 'active'},
|
|
context_instance=RequestContext(request))
|
|
|
|
|
|
@login_required
|
|
def chgPass(request):
|
|
"""修改登录系统的密码"""
|
|
error = ''
|
|
msg = ''
|
|
if request.method == 'POST':
|
|
username = request.session.get('username')
|
|
oldpass = request.POST.get('oldpass')
|
|
password = request.POST.get('password')
|
|
password_confirm = request.POST.get('password_confirm')
|
|
if '' in [oldpass, password, password_confirm]:
|
|
error = '带*内容不能为空'
|
|
elif not pam.authenticate(username, oldpass):
|
|
error = '密码不正确'
|
|
elif password != password_confirm:
|
|
error = '两次密码不匹配'
|
|
|
|
if not error:
|
|
ret = subprocess.call('%s %s %s' % (chgpass_shell, username, password), shell=True)
|
|
if ret:
|
|
error = '密码修改失败'
|
|
else:
|
|
msg = '修改密码成功'
|
|
|
|
return render_to_response('chgPass.html', {'msg': msg, 'error': error, 'pass_menu': 'active'},
|
|
context_instance=RequestContext(request))
|
|
|
|
|
|
@login_required
|
|
def chgKey(request):
|
|
"""修改密钥密码"""
|
|
error = ''
|
|
msg = ''
|
|
username = request.session.get('username')
|
|
oldpass = request.POST.get('oldpass')
|
|
password = request.POST.get('password')
|
|
password_confirm = request.POST.get('password_confirm')
|
|
keyfile = '%s/keys/%s' % (base_dir, username)
|
|
|
|
if request.method == 'POST':
|
|
if '' in [oldpass, password, password_confirm]:
|
|
error = '带*内容不能为空'
|
|
elif password != password_confirm:
|
|
error = '两次密码不匹配'
|
|
|
|
if not error:
|
|
ret = subprocess.call('ssh-keygen -p -P %s -N %s -f %s' % (oldpass, password, keyfile), shell=True)
|
|
if not ret:
|
|
error = '原来密码不正确'
|
|
else:
|
|
msg = '修改密码成功'
|
|
|
|
return render_to_response('chgKey.html',
|
|
{'error': error, 'msg': msg},
|
|
context_instance=RequestContext(request))
|
|
|