diff --git a/jperm/ansible_api.py b/jperm/ansible_api.py index 1b0e5ce38..0f8036453 100644 --- a/jperm/ansible_api.py +++ b/jperm/ansible_api.py @@ -43,7 +43,7 @@ class CommandValueError(AnsibleError): super(CommandValueError, self).__init__('value:invalid', field, message) -class MyInventory(object): +class MyInventory(Inventory): """ this is my ansible inventory object. """ @@ -64,7 +64,7 @@ class MyInventory(object): self.inventory = Inventory(host_list=[]) self.gen_inventory() - def add_group(self, hosts, groupname, groupvars=None): + def my_add_group(self, hosts, groupname, groupvars=None): """ add hosts to a group """ @@ -90,6 +90,7 @@ class MyInventory(object): my_host.set_variable('ansible_ssh_user', username) my_host.set_variable('ansible_ssh_pass', password) my_host.set_variable('ansible_ssh_private_key_file', ssh_key) + # set other variables for key, value in host.iteritems(): if key not in ["hostname", "port", "username", "password"]: @@ -104,10 +105,10 @@ class MyInventory(object): add hosts to inventory. """ if isinstance(self.resource, list): - self.add_group(self.resource, 'default_group') + self.my_add_group(self.resource, 'default_group') elif isinstance(self.resource, dict): for groupname, hosts_and_vars in self.resource.iteritems(): - self.add_group(hosts_and_vars.get("hosts"), groupname, hosts_and_vars.get("vars")) + self.my_add_group(hosts_and_vars.get("hosts"), groupname, hosts_and_vars.get("vars")) class MyRunner(MyInventory): @@ -168,6 +169,7 @@ class Command(MyInventory): ) self.results = hoc.run() + ret = {} if self.stdout: data['ok'] = self.stdout if self.stderr: @@ -177,6 +179,7 @@ class Command(MyInventory): return data + @property def raw_results(self): """ @@ -238,7 +241,14 @@ class Tasks(Command): def __init__(self, *args, **kwargs): super(Tasks, self).__init__(*args, **kwargs) - def __run(self, module_args, module_name="command", timeout=5, forks=10, group='default_group', pattern='*'): + def __run(self, + module_args, + module_name="command", + timeout=5, + forks=10, + group='default_group', + pattern='*', + ): """ run command from andible ad-hoc. command : 必须是一个需要执行的命令字符串, 比如 @@ -251,9 +261,11 @@ class Tasks(Command): subset=group, pattern=pattern, forks=forks, + become=False, ) self.results = hoc.run() + return {"msg": self.msg, "result": self.results} @property def msg(self): @@ -304,7 +316,7 @@ class Tasks(Command): module_args = 'user="%s" key="{{ lookup("file", "%s") }}" state="absent"' % (user, key_path) self.__run(module_args, "authorized_key") - return {"status": "failed","msg": self.msg} if self.msg else {"status": "ok"} + return {"status": "failed", "msg": self.msg} if self.msg else {"status": "ok"} def add_user(self, username, password): """ @@ -342,7 +354,8 @@ class Tasks(Command): delete a host user. """ module_args = 'name=%s state=absent remove=yes move_home=yes force=yes' % (username) - self.__run(module_args, "user") + self.__run(module_args, + "user",) return {"status": "failed","msg": self.msg} if self.msg else {"status": "ok"} @@ -418,8 +431,31 @@ class Tasks(Command): "product_sn": setup.get("ansible_product_serial") } - return {"status": "failed", "msg": self.msg} if self.msg else {"status": "ok", "result": result} + return {"failed": self.msg, "ok": result} + def push_sudo_file(self, file_path): + """ + use template to render pushed sudoers file + :return: + """ + module_args1 = 'test' + ret1 = self.__run(module_args1, "script") + module_args2 = 'visudo -c | grep "parsed OK" &> /dev/null && echo "ok" || echo "failed"' + ret2 = self.__run(module_args2, "shell") + ret2_status = [host_value.get("stdout") for host_value in ret2["result"]["contacted"].values()] + + result = {} + if not ret1["msg"]: + result["step1"] = "ok" + else: + result["step1"] = "failed" + + if not ret2["msg"] and "failed" not in ret2_status: + result["step2"] = "ok" + else: + result["step2"] = "failed" + + return result class CustomAggregateStats(callbacks.AggregateStats): """ @@ -469,12 +505,12 @@ class MyPlaybook(MyInventory): playbook_path = os.path.join(ANSIBLE_DIR, playbook_relational_path) pb = PlayBook( - playbook = playbook_path, - stats = stats, - callbacks = playbook_cb, - runner_callbacks = runner_cb, - inventory = self.inventory, - extra_vars = extra_vars, + playbook=playbook_path, + stats=stats, + callbacks=playbook_cb, + runner_callbacks=runner_cb, + inventory=self.inventory, + extra_vars=extra_vars, check=False) self.results = pb.run() @@ -504,9 +540,14 @@ if __name__ == "__main__": # }, # } - resource = [{"hostname": "127.0.0.1", "port": "22", "username": "yumaojun", "password": "yusky0902"}] - command = Command(resource) - print command.run("who") + resource = [{"hostname": "127.0.0.1", "port": "22", "username": "yumaojun", "password": "yusky0902", + # "ansible_become": "yes", + # "ansible_become_method": "sudo", + # # "ansible_become_user": "root", + # "ansible_become_pass": "yusky0902", + }] + cmd = Command(resource) + print cmd.run('ls') # resource = [{"hostname": "192.168.10.148", "port": "22", "username": "root", "password": "xxx"}] # task = Tasks(resource) diff --git a/jperm/models.py b/jperm/models.py index 0a01f7593..d8c0052fd 100644 --- a/jperm/models.py +++ b/jperm/models.py @@ -5,12 +5,37 @@ from jasset.models import Asset, AssetGroup from juser.models import User, UserGroup +class PermLog(models.Model): + datetime = models.DateTimeField(auto_now_add=True) + action = models.CharField(max_length=100, null=True, blank=True, default='') + results = models.CharField(max_length=1000, null=True, blank=True, default='') + is_success = models.BooleanField(default=False) + is_finish = models.BooleanField(default=False) + + +class SysUser(models.Model): + username = models.CharField(max_length=100) + password = models.CharField(max_length=100) + comment = models.CharField(max_length=100, null=True, blank=True, default='') + + +class PermSudo(models.Model): + name = models.CharField(max_length=100, unique=True) + date_added = models.DateTimeField(auto_now=True) + commands = models.TextField() + comment = models.CharField(max_length=100, null=True, blank=True, default='') + + def __unicode__(self): + return self.name + + class PermRole(models.Model): name = models.CharField(max_length=100, unique=True) comment = models.CharField(max_length=100, null=True, blank=True, default='') password = models.CharField(max_length=100) key_path = models.CharField(max_length=100) date_added = models.DateTimeField(auto_now=True) + sudo = models.ManyToManyField(PermSudo, related_name='perm_role') def __unicode__(self): return self.name @@ -28,3 +53,13 @@ class PermRule(models.Model): def __unicode__(self): return self.name + + +class PermPush(models.Model): + date_added = models.DateTimeField(auto_now=True) + asset = models.ManyToManyField(Asset, related_name='perm_push') + asset_group = models.ManyToManyField(AssetGroup, related_name='perm_push') + role = models.ManyToManyField(PermRole, related_name='perm_push') + is_public_key = models.BooleanField(default=False) + is_password = models.BooleanField(default=False) + diff --git a/jperm/perm_api.py b/jperm/perm_api.py index 90fa544ac..f2f6e903e 100644 --- a/jperm/perm_api.py +++ b/jperm/perm_api.py @@ -266,6 +266,40 @@ def get_role_info(role_id, type="all"): return u"不支持的查询" +def get_role_push_host(role): + """ + get the role push host + :return: the asset object + """ + # 计算该role 所有push记录 总共推送的主机 + assets = [] + asset_groups = [] + for push in role.perm_push.all(): + assets.extend(push.asset.all()) + asset_groups.extend(push.asset_group.all()) + group_assets = [] + for asset_group in asset_groups: + group_assets.extend(asset_group.asset_set.all()) + cacl_assets = set(assets) | set(group_assets) + + # 计算所有主机 在push记录里面的 使用密码和使用秘钥状况 + result = [] + for asset in cacl_assets: + all_push = asset.perm_push.all() + if True in [push.is_password for push in all_push if role in push.role.all()]: + is_password = u"是" + else: + is_password = u"否" + if True in [push.is_public_key for push in all_push if role in push.role.all()]: + is_public_key = u"是" + else: + is_public_key = u"否" + result.append({"ip": asset.ip, + "group": ','.join([group.name for group in asset.group.all()]), + "password": is_password, + "pubkey": is_public_key}) + return result + if __name__ == "__main__": print get_role_info(1) diff --git a/jperm/urls.py b/jperm/urls.py index fd40dc1cd..4d84ed325 100644 --- a/jperm/urls.py +++ b/jperm/urls.py @@ -13,4 +13,9 @@ urlpatterns = patterns('jperm.views', (r'^role/perm_role_detail/$', perm_role_detail), (r'^role/perm_role_edit/$', perm_role_edit), (r'^role/perm_role_push/$', perm_role_push), + (r'^sudo/$', perm_sudo_list), + (r'^sudo/perm_sudo_add/$', perm_sudo_add), + (r'^sudo/perm_sudo_delete/$', perm_sudo_delete), + (r'^sudo/perm_sudo_edit/$', perm_sudo_edit), + ) diff --git a/jperm/utils.py b/jperm/utils.py index a894f4453..9ba37efb3 100644 --- a/jperm/utils.py +++ b/jperm/utils.py @@ -8,6 +8,12 @@ from paramiko.rsakey import RSAKey from jumpserver.api import mkdir from uuid import uuid4 from jumpserver.api import CRYPTOR +from os import makedirs + +from django.template.loader import get_template +from django.template import Context +from tempfile import NamedTemporaryFile + from jumpserver.settings import KEY_DIR @@ -66,6 +72,47 @@ def gen_keys(key="", key_path_dir=""): return key_path_dir +def gen_sudo(role_custom, role_name, role_chosen): + """ + 生成sudo file, 仅测试了cenos7 + role_custom: 自定义支持的sudo 命令 格式: 'CMD1, CMD2, CMD3, ...' + role_name: role name + role_chosen: 选择那些sudo的命令别名: + NETWORKING, SOFTWARE, SERVICES, STORAGE, + DELEGATING, PROCESSES, LOCATE, DRIVERS + :return: + """ + sudo_file_basename = os.path.join(os.path.dirname(KEY_DIR), 'role_sudo_file') + makedirs(sudo_file_basename) + sudo_file_path = os.path.join(sudo_file_basename, role_name) + + t = get_template('role_sudo.j2') + content = t.render(Context({"role_custom": role_custom, + "role_name": role_name, + "role_chosen": role_chosen, + })) + with open(sudo_file_path, 'w') as f: + f.write(content) + return sudo_file_path + + +def get_add_sudo_script(sudo_chosen_aliase, sudo_chosen_obj): + """ + get the sudo file + :param kwargs: + :return: + """ + sudo_j2 = get_template('jperm/role_sudo.j2') + sudo_content = sudo_j2.render(Context({"sudo_chosen_aliase": sudo_chosen_aliase, + "sudo_chosen_obj": sudo_chosen_obj})) + sudo_file = NamedTemporaryFile(delete=False) + sudo_file.write(sudo_content) + sudo_file.close() + + return sudo_file.name + + + if __name__ == "__main__": print gen_keys() diff --git a/jperm/views.py b/jperm/views.py index 3873dd974..e095c8a0a 100644 --- a/jperm/views.py +++ b/jperm/views.py @@ -5,15 +5,14 @@ from paramiko import SSHException from jperm.perm_api import * from juser.user_api import gen_ssh_key - from juser.models import User, UserGroup from jasset.models import Asset, AssetGroup -from jperm.models import PermRole, PermRule +from jperm.models import PermRole, PermRule, PermSudo, PermPush from jumpserver.models import Setting -from jperm.utils import updates_dict, gen_keys, get_rand_pass +from jperm.utils import updates_dict, gen_keys, get_rand_pass, get_add_sudo_script from jperm.ansible_api import Tasks -from jperm.perm_api import get_role_info +from jperm.perm_api import get_role_info, get_role_push_host from jumpserver.api import my_render, get_object, CRYPTOR @@ -256,37 +255,36 @@ def perm_role_add(request): # 渲染数据 header_title, path1, path2 = "系统角色", "角色管理", "添加角色" - if request.method == "POST": - # 获取参数: name, comment - name = request.POST.get("role_name", "") - comment = request.POST.get("role_comment", "") - password = request.POST.get("role_password", "") - key_content = request.POST.get("role_key", "") - try: - if get_object(PermRole, name=name): - raise ServerError('已经存在该用户 %s' % name) + if request.method == "GET": + default_password = get_rand_pass() + sudos = PermSudo.objects.all() + return my_render('jperm/perm_role_add.html', locals(), request) + + elif request.method == "POST": + # 获取参数: name, comment, sudo + name = request.POST.get("role_name") + comment = request.POST.get("role_comment") + password = request.POST.get("role_password") + sudos_name = request.POST.getlist("sudo_name") + sudos_obj = [PermSudo.objects.get(name=sudo_name) for sudo_name in sudos_name] + encrypt_pass = CRYPTOR.encrypt(password) + # 生成随机密码,生成秘钥对 + + key_path = gen_keys() + role = PermRole(name=name, comment=comment, password=encrypt_pass, key_path=key_path) + role.save() + role.sudo = sudos_obj + role.save() + + msg = u"添加角色: %s" % name + # 渲染 刷新数据 + header_title, path1, path2 = "系统角色", "角色管理", "查看角色" + roles_list = PermRole.objects.all() + # TODO: 搜索和分页 + keyword = request.GET.get('search', '') + if keyword: + roles_list = roles_list.filter(Q(name=keyword)) - if '' == password and '' == key_content: - raise ServerError('账号和密码必填一项') - if password: - encrypt_pass = CRYPTOR.encrypt(password) - else: - encrypt_pass = CRYPTOR.encrypt(CRYPTOR.gen_rand_pass(20)) - # 生成随机密码,生成秘钥对 - if key_content: - try: - key_path = gen_keys(key=key_content) - except SSHException: - raise ServerError('输入的密钥不合法') - else: - key_path = gen_keys() - logger.debug('generate role key: %s' % key_path) - role = PermRole(name=name, comment=comment, password=encrypt_pass, key_path=key_path) - role.save() - msg = u"添加角色: %s" % name - return HttpResponseRedirect('/jperm/role/') - except ServerError, e: - error = e return my_render('jperm/perm_role_add.html', locals(), request) @@ -337,6 +335,7 @@ def perm_role_detail(request): asset_groups = role_info.get("asset_groups") users = role_info.get("users") user_groups = role_info.get("user_groups") + push_info = get_role_push_host(PermRole.objects.get(id=role_id)) return my_render('jperm/perm_role_detail.html', locals(), request) @@ -351,13 +350,20 @@ def perm_role_edit(request): # 渲染数据 role_id = request.GET.get("id") - role = get_object(PermRole, id=role_id) + role = PermRole.objects.get(id=role_id) + role_pass = CRYPTOR.decrypt(role.password) + role_sudos = role.sudo.all() + if request.method == "GET": + return my_render('jperm/perm_role_edit.html', locals(), request) + if request.method == "POST": # 获取 POST 数据 role_name = request.POST.get("role_name") role_password = request.POST.get("role_password") role_comment = request.POST.get("role_comment") + role_sudo_names = request.POST.getlist("sudo_name") + role_sudos = [PermSudo.objects.get(name=sudo_name) for sudo_name in role_sudo_names] key_content = request.POST.get("role_key", "") try: if not role: @@ -375,7 +381,9 @@ def perm_role_edit(request): logger.debug('Recreate role key: %s' % role.key_path) # 写入数据库 role.name = role_name + role.password = encrypt_role_pass role.comment = role_comment + role.sudo = role_sudos role.save() msg = u"更新系统角色: %s" % role.name @@ -386,6 +394,7 @@ def perm_role_edit(request): return my_render('jperm/perm_role_edit.html', locals(), request) + @require_role('admin') def perm_role_push(request): """ @@ -445,22 +454,184 @@ def perm_role_push(request): key_push = request.POST.get("use_publicKey") task = Tasks(push_resource) ret = {} - ret_failed = [] + ret_failed = {} - # 因为要先建立用户,所以password 是必选项, - # 而push key是在 password也完成的情况下的 可选项 - ret["password_push"] = task.add_multi_user(**role_pass) - if ret["password_push"].get("status") != "success": - ret_failed.append(1) + # 因为要先建立用户,所以password 是必选项,而push key是在 password也完成的情况下的 可选项 + # 1. 以password 方式推送角色 + if password_push: + ret["password_push"] = task.add_multi_user(**role_pass) + if ret["password_push"].get("status") != "success": + ret_failed["step1"] == "failed" + # 2. 以秘钥 方式推送角色 if key_push: + ret["password_push"] = task.add_multi_user(**role_pass) + if ret["password_push"].get("status") != "success": + ret_failed["step2-1"] = "failed" ret["key_push"] = task.push_multi_key(**role_key) if ret["key_push"].get("status") != "success": - ret_failed.append(1) + ret_failed["step2-2"] = "failed" - print ret + # 3. 推送sudo配置文件 + sudo_chosen_aliase = {} + sudo_alias = [] + for role in roles_obj: + role_alias = [sudo.name for sudo in role.sudo.all()] + sudo_alias.extend(role_alias) + sudo_chosen_aliase[role.name] = ','.join(role_alias) + sudo_chosen_obj = [PermSudo.objects.get(name=sudo_name) for sudo_name in set(sudo_alias)] + + add_sudo_script = get_add_sudo_script(sudo_chosen_aliase, sudo_chosen_obj) + ret_sudo = task.push_sudo_file(add_sudo_script) + + if ret_sudo["step1"] != "ok" or ret_sudo["step2"] != "ok": + ret_failed["step3"] = "failed" + os.remove(add_sudo_script) + + + # 结果汇总统计 if ret_failed: - return HttpResponse(u"推送失败") + # 推送失败 + error = u"推送失败, 原因: %s 失败" % ','.join(ret_failed.keys()) else: - return HttpResponse(u"推送系统角色: %s" % ','.join(role_names)) + # 推送成功 回写push表 + msg = u"推送系统角色: %s" % ','.join(role_names) + push = PermPush(is_public_key=bool(key_push), is_password=bool(password_push)) + push.save() + push.asset_group = asset_groups_obj + push.asset = calc_assets + push.role = roles_obj + push.save() + + # 渲染 刷新数据 + header_title, path1, path2 = "系统角色", "角色管理", "查看角色" + roles_list = PermRole.objects.all() + # TODO: 搜索和分页 + keyword = request.GET.get('search', '') + if keyword: + roles_list = roles_list.filter(Q(name=keyword)) + + roles_list, p, roles, page_range, current_page, show_first, show_end = pages(roles_list, request) + return my_render('jperm/perm_role_list.html', locals(), request) + + +@require_role('admin') +def perm_sudo_list(request): + """ + list sudo commands alias + :param request: + :return: + """ + # 渲染数据 + header_title, path1, path2 = "Sudo命令", "别名管理", "查看别名" + + # 获取所有sudo 命令别名 + sudos_list = PermSudo.objects.all() + + # TODO: 搜索和分页 + keyword = request.GET.get('search', '') + if keyword: + sudos_list = sudos_list.filter(Q(name=keyword)) + + sudos_list, p, sudos, page_range, current_page, show_first, show_end = pages(sudos_list, request) + + return my_render('jperm/perm_sudo_list.html', locals(), request) + + +@require_role('admin') +def perm_sudo_add(request): + """ + list sudo commands alias + :param request: + :return: + """ + # 渲染数据 + header_title, path1, path2 = "Sudo命令", "别名管理", "添加别名" + + if request.method == "GET": + return my_render('jperm/perm_sudo_add.html', locals(), request) + + elif request.method == "POST": + # 获取参数: name, comment + name = request.POST.get("sudo_name") + comment = request.POST.get("sudo_comment") + commands = request.POST.get("sudo_commands") + + sudo = PermSudo(name=name.strip(), comment=comment, commands=commands.strip()) + sudo.save() + + msg = u"添加Sudo命令别名: %s" % name + # 渲染数据 + header_title, path1, path2 = "Sudo命令", "别名管理", "查看别名" + # 获取所有sudo 命令别名 + sudos_list = PermSudo.objects.all() + + # TODO: 搜索和分页 + keyword = request.GET.get('search', '') + if keyword: + roles_list = sudos_list.filter(Q(name=keyword)) + + sudos_list, p, sudos, page_range, current_page, show_first, show_end = pages(sudos_list, request) + + return my_render('jperm/perm_sudo_list.html', locals(), request) + else: + return HttpResponse(u"不支持该操作") + + +@require_role('admin') +def perm_sudo_edit(request): + """ + list sudo commands alias + :param request: + :return: + """ + # 渲染数据 + header_title, path1, path2 = "Sudo命令", "别名管理", "编辑别名" + + sudo_id = request.GET.get("id") + sudo = PermSudo.objects.get(id=sudo_id) + if request.method == "GET": + return my_render('jperm/perm_sudo_edit.html', locals(), request) + + if request.method == "POST": + name = request.POST.get("sudo_name") + commands = request.POST.get("sudo_commands") + comment = request.POST.get("sudo_comment") + sudo.name = name.strip() + sudo.commands = commands.strip() + sudo.comment = comment + sudo.save() + + msg = u"更新命令别名: %s" % name + # 渲染数据 + header_title, path1, path2 = "Sudo命令", "别名管理", "查看别名" + # 获取所有sudo 命令别名 + sudos_list = PermSudo.objects.all() + # TODO: 搜索和分页 + keyword = request.GET.get('search', '') + if keyword: + sudos_list = sudos_list.filter(Q(name=keyword)) + sudos_list, p, sudos, page_range, current_page, show_first, show_end = pages(sudos_list, request) + return my_render('jperm/perm_sudo_list.html', locals(), request) + + +@require_role('admin') +def perm_sudo_delete(request): + """ + list sudo commands alias + :param request: + :return: + """ + if request.method == "POST": + # 获取参数删除的role对象 + sudo_id = request.POST.get("id") + sudo = PermSudo.objects.get(id=sudo_id) + # 数据库里删除记录 + sudo.delete() + return HttpResponse(u"删除角色: %s" % sudo.name) + else: + return HttpResponse(u"不支持该操作") + + + diff --git a/jumpserver/templatetags/mytags.py b/jumpserver/templatetags/mytags.py index 2759838f2..8adfb724a 100644 --- a/jumpserver/templatetags/mytags.py +++ b/jumpserver/templatetags/mytags.py @@ -250,3 +250,12 @@ def check_role(asset_id, user): """ return user + +@register.filter(name='role_contain_which_sudos') +def role_contain_which_sudos(role): + """ + get role sudo commands + """ + sudo_names = [sudo.name for sudo in role.sudo.all()] + return ','.join(sudo_names) + diff --git a/templates/jperm/perm_role_add.html b/templates/jperm/perm_role_add.html index 4fb774a41..78c33b38e 100644 --- a/templates/jperm/perm_role_add.html +++ b/templates/jperm/perm_role_add.html @@ -55,6 +55,17 @@
+