From 1a0c9cd4e6a9d68be1cded0a64b05a02513b88e8 Mon Sep 17 00:00:00 2001
From: yumaojun <719118794@qq.com>
Date: Tue, 24 Nov 2015 22:03:58 +0800
Subject: [PATCH] =?UTF-8?q?1.=20=E6=96=B0=E5=A2=9E=20PermSudo=E8=A1=A8,=20?=
 =?UTF-8?q?=E7=94=A8=E4=BA=8E=E8=AE=B0=E5=BD=95=20sudo=E5=88=AB=E5=90=8D?=
 =?UTF-8?q?=202.=20=E5=AE=9E=E7=8E=B0Sudo=E8=A1=A8=E5=AF=B9=E5=BA=94?=
 =?UTF-8?q?=E7=9A=84=20=E6=B7=BB=E5=8A=A0=EF=BC=8C=E6=98=BE=E7=A4=BA?=
 =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=EF=BC=8C=E5=88=A0=E9=99=A4=E9=A1=B5?=
 =?UTF-8?q?=E9=9D=A2=203.=20=E6=B7=BB=E5=8A=A0=E8=A7=92=E8=89=B2=E6=97=B6?=
 =?UTF-8?q?=EF=BC=8C=E9=9C=80=E8=A6=81=E9=80=89=E6=8B=A9=E5=AF=B9=E5=BA=94?=
 =?UTF-8?q?=E7=9A=84=20sudo=E5=88=AB=E5=90=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 jperm/ansible_api.py                |  67 +++++++++-----
 jperm/models.py                     |  17 +++-
 jperm/urls.py                       |   5 +-
 jperm/utils.py                      |  25 +++++
 jperm/views.py                      | 136 +++++++++++++++++++++++++++-
 jumpserver/templatetags/mytags.py   |   9 ++
 templates/jperm/perm_role_add.html  |  11 +++
 templates/jperm/perm_role_edit.html |  11 +++
 templates/jperm/perm_role_list.html |   3 +
 templates/jperm/perm_sudo_add.html  | 120 ++++++++++++++++++++++++
 templates/jperm/perm_sudo_edit.html | 120 ++++++++++++++++++++++++
 templates/jperm/perm_sudo_list.html | 112 +++++++++++++++++++++++
 templates/jperm/role_sudo.j2        | 126 ++++++++++++++++++++++++++
 templates/nav.html                  |   4 +-
 14 files changed, 737 insertions(+), 29 deletions(-)
 create mode 100644 templates/jperm/perm_sudo_add.html
 create mode 100644 templates/jperm/perm_sudo_edit.html
 create mode 100644 templates/jperm/perm_sudo_list.html
 create mode 100644 templates/jperm/role_sudo.j2

diff --git a/jperm/ansible_api.py b/jperm/ansible_api.py
index 3a8b50c6..ee7c2dde 100644
--- a/jperm/ansible_api.py
+++ b/jperm/ansible_api.py
@@ -20,7 +20,6 @@ API_DIR = os.path.dirname(os.path.abspath(__file__))
 ANSIBLE_DIR = os.path.join(API_DIR, 'playbooks')
 
 
-
 class AnsibleError(StandardError):
     """
     the base AnsibleError which contains error(required),
@@ -44,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.
     """
@@ -65,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
         """
@@ -83,11 +82,13 @@ class MyInventory(object):
             hostport = host.get("port")
             username = host.get("username")
             password = host.get("password")
+            sudo_password = host.get("sudo_password")
             my_host = Host(name=hostname, port=hostport)
             my_host.set_variable('ansible_ssh_host', hostname)
             my_host.set_variable('ansible_ssh_port', hostport)
             my_host.set_variable('ansible_ssh_user', username)
             my_host.set_variable('ansible_ssh_pass', password)
+
             # set other variables 
             for key, value in host.iteritems():
                 if key not in ["hostname", "port", "username", "password"]:
@@ -102,10 +103,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 Command(MyInventory):
@@ -125,7 +126,7 @@ class Command(MyInventory):
 
         if module_name not in ["raw", "command", "shell"]:
             raise CommandValueError("module_name",
-                                     "module_name must be of the 'raw, command, shell'")
+                                    "module_name must be of the 'raw, command, shell'")
         hoc = Runner(module_name=module_name,
                      module_args=command,
                      timeout=timeout,
@@ -136,15 +137,17 @@ class Command(MyInventory):
                      )
         self.results = hoc.run()
 
+        ret = {}
         if self.stdout:
-            return {"ok": self.stdout}
+            ret["ok"] = self.stdout
         else:
             msg = []
             if self.stderr:
                 msg.append(self.stderr)
             if self.dark:
                 msg.append(self.dark)
-            return {"failed": msg}
+            ret["failed"] = msg
+        return ret
 
     @property
     def raw_results(self):
@@ -206,7 +209,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  : 必须是一个需要执行的命令字符串, 比如 
@@ -219,6 +229,7 @@ class Tasks(Command):
                      subset=group,
                      pattern=pattern,
                      forks=forks,
+                     become=False,
                      )
 
         self.results = hoc.run()
@@ -272,7 +283,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):
         """
@@ -310,7 +321,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"}
 
@@ -386,9 +398,15 @@ 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(self, role_custo, role_name, role_chosen):
+        """
+        use template to render pushed sudoers file
+        :return:
+        """
+        module_args = 'src=%s dest=%s owner=root group=root mode=0440' % (username, encrypt_pass)
+        self.__run(module_args, "template")
 
 
 class CustomAggregateStats(callbacks.AggregateStats):
@@ -440,12 +458,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()
@@ -475,9 +493,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 dc8643b6..019411f9 100644
--- a/jperm/models.py
+++ b/jperm/models.py
@@ -19,12 +19,23 @@ class SysUser(models.Model):
     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
@@ -41,4 +52,8 @@ class PermRule(models.Model):
     role = models.ManyToManyField(PermRole, related_name='perm_rule')
 
     def __unicode__(self):
-        return self.name
\ No newline at end of file
+        return self.name
+
+
+
+
diff --git a/jperm/urls.py b/jperm/urls.py
index aa80f7f7..e382a2a2 100644
--- a/jperm/urls.py
+++ b/jperm/urls.py
@@ -13,7 +13,10 @@ 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),
 
                        (r'^log/$', log),
                        (r'^sys_user_add/$', sys_user_add),
diff --git a/jperm/utils.py b/jperm/utils.py
index 63054b30..ea68488a 100644
--- a/jperm/utils.py
+++ b/jperm/utils.py
@@ -6,6 +6,8 @@ import os.path
 from paramiko.rsakey import RSAKey
 from os import chmod, makedirs
 from uuid import uuid4
+from django.template.loader import get_template
+from django.template import Context
 
 from jumpserver.settings import KEY_DIR
 
@@ -62,6 +64,29 @@ def gen_keys():
     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
+
 
 
 if __name__ == "__main__":
diff --git a/jperm/views.py b/jperm/views.py
index f961a8e0..7c4f59c8 100644
--- a/jperm/views.py
+++ b/jperm/views.py
@@ -9,7 +9,7 @@ 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
 from jumpserver.models import Setting
 
 from jperm.utils       import updates_dict, gen_keys, get_rand_pass
@@ -259,19 +259,24 @@ def perm_role_add(request):
 
     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
+        # 获取参数: 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
         # 渲染 刷新数据
@@ -350,6 +355,7 @@ def perm_role_edit(request):
     role_id = request.GET.get("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)
 
@@ -359,11 +365,14 @@ def perm_role_edit(request):
         role_password = request.POST.get("role_password")
         encrypt_role_pass = CRYPTOR.encrypt(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]
 
         # 写入数据库
         role.name = role_name
         role.password = encrypt_role_pass
         role.comment = role_comment
+        role.sudo = role_sudos
 
         role.save()
         msg = u"更新系统角色: %s" % role.name
@@ -380,8 +389,6 @@ def perm_role_edit(request):
         return my_render('jperm/perm_role_list.html', locals(), request)
 
 
-
-
 @require_role('admin')
 def perm_role_push(request):
     """
@@ -461,6 +468,127 @@ def perm_role_push(request):
             return HttpResponse(u"推送系统角色: %s" % ','.join(role_names))
 
 
+@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, comment=comment, commands=commands)
+        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
+        sudo.commands = commands
+        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 8dcdf377..e99c30f0 100644
--- a/jumpserver/templatetags/mytags.py
+++ b/jumpserver/templatetags/mytags.py
@@ -226,3 +226,12 @@ def ip_str_to_list(ip_str):
     ip str to list
     """
     return ip_str.split(',')
+
+
+@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 243452db..1f534ac2 100644
--- a/templates/jperm/perm_role_add.html
+++ b/templates/jperm/perm_role_add.html
@@ -47,6 +47,17 @@
                                 </div>
                             </div>
                             <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <label for="sudo" class="col-sm-2 control-label">角色Sudo命令<span class="red-fonts">*</span></label>
+                                <div class="col-sm-8" id="sudo_name">
+                                    <select name="sudo_name" data-placeholder="请选择Sudo别名" class="chosen-select form-control m-b" multiple  tabindex="2">
+                                        {% for sudo in sudos %}
+                                            <option >{{ sudo.name }}</option>
+                                        {% endfor %}
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="hr-line-dashed"></div>
                             <div class="form-group">
                                 <label for="role_comment" class="col-sm-2 control-label">备注</label>
                                 <div class="col-sm-8">
diff --git a/templates/jperm/perm_role_edit.html b/templates/jperm/perm_role_edit.html
index 3b563756..b1a5cb50 100644
--- a/templates/jperm/perm_role_edit.html
+++ b/templates/jperm/perm_role_edit.html
@@ -47,6 +47,17 @@
                                 </div>
                             </div>
                             <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <label for="sudo" class="col-sm-2 control-label">角色Sudo命令<span class="red-fonts">*</span></label>
+                                <div class="col-sm-8" id="sudo_name">
+                                    <select name="sudo_name" data-placeholder="请选择Sudo别名" class="chosen-select form-control m-b" multiple  tabindex="2">
+                                        {% for sudo in role_sudos %}
+                                            <option selected >{{ sudo.name }}</option>
+                                        {% endfor %}
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="hr-line-dashed"></div>
                             <div class="form-group">
                                 <label for="role_comment" class="col-sm-2 control-label">备注</label>
                                 <div class="col-sm-8">
diff --git a/templates/jperm/perm_role_list.html b/templates/jperm/perm_role_list.html
index 045012cc..060af176 100644
--- a/templates/jperm/perm_role_list.html
+++ b/templates/jperm/perm_role_list.html
@@ -53,6 +53,7 @@
                                 <th class="text-center">名称 </th>
                                 <th class="text-center">备注</th>
                                 <th class="text-center">创建时间</th>
+                                <th class="text-center">sudo别名</th>
                                 <th class="text-center">操作</th>
                             </tr>
                         </thead>
@@ -62,6 +63,7 @@
                                 <td class="text-center"> {{ role.name }} </td>
                                 <td class="text-center"> {{ role.comment }} </td>
                                 <td class="text-center"> {{ role.date_added | date:"Y-m-d H:i:s"}} </td>
+                                <td class="text-center"> {{ role | role_contain_which_sudos }} </td>
                                 <td class="text-center">
                                     <a href="/jperm/role/perm_role_detail/?id={{ role.id }}" class="btn btn-xs btn-primary">详情</a>
                                     <a href="/jperm/role/perm_role_edit/?id={{ role.id }}" class="btn btn-xs btn-info">编辑</a>
@@ -99,6 +101,7 @@ function remove_role(role_id){
                del_row.remove()
            },
            error: function (msg) {
+               console.log(msg)
                alert("失败: " + msg)
            }
         });
diff --git a/templates/jperm/perm_sudo_add.html b/templates/jperm/perm_sudo_add.html
new file mode 100644
index 00000000..8df34a5c
--- /dev/null
+++ b/templates/jperm/perm_sudo_add.html
@@ -0,0 +1,120 @@
+{% extends 'base.html' %}
+{% block self_head_css_js %}
+    <link href="/static/css/plugins/datapicker/datepicker3.css" rel="stylesheet">
+    <link href="/static/css/plugins/chosen/chosen.css" rel="stylesheet">
+    <script src="/static/js/plugins/chosen/chosen.jquery.js"></script>
+{% endblock %}
+{% load mytags %}
+{% block content %}
+    {% include 'nav_cat_bar.html' %}
+    <div class="wrapper wrapper-content animated fadeInRight">
+        <div class="row">
+            <div class="col-lg-10">
+                <div class="ibox float-e-margins">
+                    <div class="ibox-title">
+                        <h5>填写基本信息</h5>
+                        <div class="ibox-tools">
+                            <a class="collapse-link">
+                                <i class="fa fa-chevron-up"></i>
+                            </a>
+                            <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+                                <i class="fa fa-wrench"></i>
+                            </a>
+                            <a class="close-link">
+                                <i class="fa fa-times"></i>
+                            </a>
+                        </div>
+                    </div>
+                    <div class="ibox-content">
+                        <form method="post" id="sudoForm" class="form-horizontal" action="">
+                            {% if error %}
+                                <div class="alert alert-warning text-center">{{ error }}</div>
+                            {% endif %}
+                            {% if msg %}
+                                <div class="alert alert-success text-center">{{ msg }}</div>
+                            {% endif %}
+                            <div class="form-group">
+                                <label for="sudo_name" class="col-sm-2 control-label">命令别名<span class="red-fonts">*</span></label>
+                                <div class="col-sm-8">
+                                    <input id="sudo_name" name="sudo_name" placeholder="Sudo Command Alias" type="text" class="form-control">
+                                </div>
+                            </div>
+                            <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <label for="sudo_commands_label" class="col-sm-2 control-label">系统命令<span class="red-fonts">*</span></label>
+                                <div class="col-sm-8">
+                                    <textarea id="sudo_commands" name="sudo_commands" class="form-control" rows="3"></textarea>
+                                </div>
+                            </div>
+                            <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <label for="sudo_comment" class="col-sm-2 control-label">备注</label>
+                                <div class="col-sm-8">
+                                    <input id="sudo_comment" name="sudo_comment" placeholder="Sudo Comment" type="text" class="form-control">
+                                </div>
+                            </div>
+                            <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <div class="col-sm-4 col-sm-offset-2">
+                                    <button class="btn btn-white" type="reset">取消</button>
+                                    <button id="submit_button" class="btn btn-primary" type="submit">确认保存</button>
+                                </div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+{% endblock %}
+{% block self_footer_js %}
+<script>
+$(document).ready(function(){
+    $("input.role").click(function(){
+        if($("input.role[value=GA]").is( ":checked" )){
+            $("#admin_groups").css("display", 'none');
+        }
+        else {
+
+            $("#admin_groups").css("display", 'block');
+        }
+    });
+
+    $('#use_password').click(function(){
+        if ($(this).is(':checked')){
+            $('#admin_account_password').css('display', 'block')
+        }
+        else {
+
+            $('#admin_account_password').css('display', 'none')
+        }
+    });
+
+    $('#use_publicKey').click(function(){
+        if ($(this).is(':checked')){
+
+            $('#admin_account_publicKey').css('display', 'block')
+        }
+        else {
+            $('#admin_account_publicKey').css('display', 'none')
+        }
+    });
+});
+
+var config = {
+                '.chosen-select'           : {},
+                '.chosen-select-deselect'  : {allow_single_deselect:true},
+                '.chosen-select-no-single' : {disable_search_threshold:10},
+                '.chosen-select-no-results': {no_results_text:'Oops, nothing found!'},
+                '.chosen-select-width'     : {width:"95%"}
+            };
+
+for (var selector in config) {
+    $(selector).chosen(config[selector]);
+}
+
+</script>
+    <script src="/static/js/cropper/cropper.min.js"></script>
+    <script src="/static/js/datapicker/bootstrap-datepicker.js"></script>
+{% endblock %}
+
diff --git a/templates/jperm/perm_sudo_edit.html b/templates/jperm/perm_sudo_edit.html
new file mode 100644
index 00000000..42621b93
--- /dev/null
+++ b/templates/jperm/perm_sudo_edit.html
@@ -0,0 +1,120 @@
+{% extends 'base.html' %}
+{% block self_head_css_js %}
+    <link href="/static/css/plugins/datapicker/datepicker3.css" rel="stylesheet">
+    <link href="/static/css/plugins/chosen/chosen.css" rel="stylesheet">
+    <script src="/static/js/plugins/chosen/chosen.jquery.js"></script>
+{% endblock %}
+{% load mytags %}
+{% block content %}
+    {% include 'nav_cat_bar.html' %}
+    <div class="wrapper wrapper-content animated fadeInRight">
+        <div class="row">
+            <div class="col-lg-10">
+                <div class="ibox float-e-margins">
+                    <div class="ibox-title">
+                        <h5>填写基本信息</h5>
+                        <div class="ibox-tools">
+                            <a class="collapse-link">
+                                <i class="fa fa-chevron-up"></i>
+                            </a>
+                            <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+                                <i class="fa fa-wrench"></i>
+                            </a>
+                            <a class="close-link">
+                                <i class="fa fa-times"></i>
+                            </a>
+                        </div>
+                    </div>
+                    <div class="ibox-content">
+                        <form method="post" id="sudoForm" class="form-horizontal" action="">
+                            {% if error %}
+                                <div class="alert alert-warning text-center">{{ error }}</div>
+                            {% endif %}
+                            {% if msg %}
+                                <div class="alert alert-success text-center">{{ msg }}</div>
+                            {% endif %}
+                            <div class="form-group">
+                                <label for="sudo_name" class="col-sm-2 control-label">命令别名<span class="red-fonts">*</span></label>
+                                <div class="col-sm-8">
+                                    <input id="sudo_name" name="sudo_name" placeholder="Sudo Command Alias" type="text" class="form-control" value={{ sudo.name }}>
+                                </div>
+                            </div>
+                            <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <label for="sudo_commands_label" class="col-sm-2 control-label">系统命令<span class="red-fonts">*</span></label>
+                                <div class="col-sm-8">
+                                    <textarea id="sudo_commands" name="sudo_commands" class="form-control" rows="3">{{ sudo.commands }}</textarea>
+                                </div>
+                            </div>
+                            <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <label for="sudo_comment" class="col-sm-2 control-label">备注</label>
+                                <div class="col-sm-8">
+                                    <input id="sudo_comment" name="sudo_comment" placeholder="Sudo Comment" type="text" class="form-control" value={{ sudo.commnet }}>
+                                </div>
+                            </div>
+                            <div class="hr-line-dashed"></div>
+                            <div class="form-group">
+                                <div class="col-sm-4 col-sm-offset-2">
+                                    <button class="btn btn-white" type="reset">取消</button>
+                                    <button id="submit_button" class="btn btn-primary" type="submit">确认保存</button>
+                                </div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+{% endblock %}
+{% block self_footer_js %}
+<script>
+$(document).ready(function(){
+    $("input.role").click(function(){
+        if($("input.role[value=GA]").is( ":checked" )){
+            $("#admin_groups").css("display", 'none');
+        }
+        else {
+
+            $("#admin_groups").css("display", 'block');
+        }
+    });
+
+    $('#use_password').click(function(){
+        if ($(this).is(':checked')){
+            $('#admin_account_password').css('display', 'block')
+        }
+        else {
+
+            $('#admin_account_password').css('display', 'none')
+        }
+    });
+
+    $('#use_publicKey').click(function(){
+        if ($(this).is(':checked')){
+
+            $('#admin_account_publicKey').css('display', 'block')
+        }
+        else {
+            $('#admin_account_publicKey').css('display', 'none')
+        }
+    });
+});
+
+var config = {
+                '.chosen-select'           : {},
+                '.chosen-select-deselect'  : {allow_single_deselect:true},
+                '.chosen-select-no-single' : {disable_search_threshold:10},
+                '.chosen-select-no-results': {no_results_text:'Oops, nothing found!'},
+                '.chosen-select-width'     : {width:"95%"}
+            };
+
+for (var selector in config) {
+    $(selector).chosen(config[selector]);
+}
+
+</script>
+    <script src="/static/js/cropper/cropper.min.js"></script>
+    <script src="/static/js/datapicker/bootstrap-datepicker.js"></script>
+{% endblock %}
+
diff --git a/templates/jperm/perm_sudo_list.html b/templates/jperm/perm_sudo_list.html
new file mode 100644
index 00000000..43f13ec1
--- /dev/null
+++ b/templates/jperm/perm_sudo_list.html
@@ -0,0 +1,112 @@
+{% extends 'base.html' %}
+{% load mytags %}
+{% block content %}
+{% include 'nav_cat_bar.html' %}
+
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-lg-10">
+            <div class="ibox float-e-margins">
+                <div>
+                    {% if error %}
+                        <div class="alert alert-warning text-center">{{ error }}</div>
+                    {% endif %}
+                    {% if msg %}
+                        <div class="alert alert-success text-center">{{ msg }}</div>
+                    {% endif %}
+                </div>
+                <div class="ibox-title">
+                    <h5> 所有Sudo命令别名</h5>
+                    <div class="ibox-tools">
+                        <a class="collapse-link">
+                            <i class="fa fa-chevron-up"></i>
+                        </a>
+                        <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+                            <i class="fa fa-wrench"></i>
+                        </a>
+                        <a class="close-link">
+                            <i class="fa fa-times"></i>
+                        </a>
+                    </div>
+                </div>
+
+                <div class="ibox-content">
+                    <div class="">
+                    <a  href="/jperm/sudo/perm_sudo_add/" class="btn btn-sm btn-primary "> 添加别名 </a>
+                    <a id="del_btn" class="btn btn-sm btn-danger "> 删除所选 </a>
+                    <form id="search_form" method="get" action="" class="pull-right mail-search">
+                        <div class="input-group">
+                            <input type="text" class="form-control input-sm" id="search_input" name="search" placeholder="Search">
+                            <div class="input-group-btn">
+                                <button id='search_btn' type="submit" class="btn btn-sm btn-primary">
+                                    - 搜索 -
+                                </button>
+                            </div>
+                        </div>
+                    </form>
+                    </div>
+
+                    <table class="table table-striped table-bordered table-hover " id="editable" >
+                        <thead>
+                            <tr>
+                                <th class="text-center">命令别名 </th>
+                                <th class="text-center">系统命令</th>
+                                <th class="text-center">创建时间</th>
+                                <th class="text-center">操作</th>
+                            </tr>
+                        </thead>
+                        <tbody id="edittbody">
+                        {% for sudo in sudos %}
+                            <tr class="gradeX" id={{ sudo.id }}>
+                                <td class="text-center"> {{ sudo.name }} </td>
+                                <td class="text-center"> {{ sudo.commands }} </td>
+                                <td class="text-center"> {{ sudo.date_added | date:"Y-m-d H:i:s"}} </td>
+                                <td class="text-center">
+{#                                    <a href="/jperm/sudo/perm_sudo_detail/?id={{ sudo.id }}" class="btn btn-xs btn-primary">详情</a>#}
+                                    <a href="/jperm/sudo/perm_sudo_edit/?id={{ sudo.id }}" class="btn btn-xs btn-info">编辑</a>
+                                    <button onclick="remove_sudo({{ sudo.id }})" class="btn btn-xs btn-danger">删除</button>
+                                </td>
+                            </tr>
+                        {% endfor %}
+                        </tbody>
+                    </table>
+                    <div class="row">
+                        <div class="col-sm-6">
+                            <div class="dataTables_info" id="editable_info" role="status" aria-live="polite">
+                                Showing {{ users.start_index }} to {{ users.end_index }} of {{ p.count }} entries
+                            </div>
+                        </div>
+                        {% include 'paginator.html' %}
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+
+<script>
+function remove_sudo(sudo_id){
+    if (confirm("确认删除")) {
+        $.ajax({
+           type: "POST",
+           url: "/jperm/sudo/perm_sudo_delete/",
+           data: "id=" + sudo_id,
+           success: function(msg){
+               alert( "成功: " + msg );
+               var del_row = $('tbody#edittbody>tr#' + sudo_id);
+               del_row.remove()
+           },
+           error: function (msg) {
+               alert("失败: " + msg)
+           }
+        });
+    }
+
+}
+</script>
+
+
+{% endblock %}
+
+
diff --git a/templates/jperm/role_sudo.j2 b/templates/jperm/role_sudo.j2
new file mode 100644
index 00000000..ae6e5924
--- /dev/null
+++ b/templates/jperm/role_sudo.j2
@@ -0,0 +1,126 @@
+## Sudoers allows particular users to run various commands as
+## the root user, without needing the root password.
+##
+## Examples are provided at the bottom of the file for collections
+## of related commands, which can then be delegated out to particular
+## users or groups.
+##
+## This file must be edited with the 'visudo' command.
+
+## Host Aliases
+## Groups of machines. You may prefer to use hostnames (perhaps using
+## wildcards for entire domains) or IP addresses instead.
+# Host_Alias     FILESERVERS = fs1, fs2
+# Host_Alias     MAILSERVERS = smtp, smtp2
+
+## User Aliases
+## These aren't often necessary, as you can use regular groups
+## (ie, from files, LDAP, NIS, etc) in this file - just use %groupname
+## rather than USERALIAS
+# User_Alias ADMINS = jsmith, mikem
+
+
+## Command Aliases
+## These are groups of related commands...
+
+## Networking
+Cmnd_Alias NETWORKING = /sbin/route, /sbin/ifconfig, /bin/ping, /sbin/dhclient, /usr/bin/net, /sbin/iptables, /usr/bin/rfcomm, /usr/bin/wvdial, /sbin/iwconfig, /sbin/mii-tool
+
+## Installation and management of software
+Cmnd_Alias SOFTWARE = /bin/rpm, /usr/bin/up2date, /usr/bin/yum
+
+## Services
+Cmnd_Alias SERVICES = /sbin/service, /sbin/chkconfig
+
+## Updating the locate database
+Cmnd_Alias LOCATE = /usr/bin/updatedb
+
+## Storage
+Cmnd_Alias STORAGE = /sbin/fdisk, /sbin/sfdisk, /sbin/parted, /sbin/partprobe, /bin/mount, /bin/umount
+
+## Delegating permissions
+Cmnd_Alias DELEGATING = /bin/chown, /bin/chmod, /bin/chgrp
+
+## Processes
+Cmnd_Alias PROCESSES = /bin/nice, /bin/kill, /usr/bin/kill, /usr/bin/killall
+
+## Drivers
+Cmnd_Alias DRIVERS = /sbin/modprobe
+
+## Custom
+{% if {{ role_custom }} %}
+{% Cmnd_Alias CUSTOM = {{ role_custom }} %}
+{% endif %}
+
+# Defaults specification
+
+#
+# Disable "ssh hostname sudo <cmd>", because it will show the password in clear.
+#         You have to run "ssh -t hostname sudo <cmd>".
+#
+Defaults    requiretty
+
+#
+# Refuse to run if unable to disable echo on the tty. This setting should also be
+# changed in order to be able to use sudo without a tty. See requiretty above.
+#
+Defaults   !visiblepw
+
+#
+# Preserving HOME has security implications since many programs
+# use it when searching for configuration files. Note that HOME
+# is already set when the the env_reset option is enabled, so
+# this option is only effective for configurations where either
+# env_reset is disabled or HOME is present in the env_keep list.
+#
+Defaults    always_set_home
+
+Defaults    env_reset
+Defaults    env_keep =  "COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR LS_COLORS"
+Defaults    env_keep += "MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE"
+Defaults    env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES"
+Defaults    env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE"
+Defaults    env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY"
+
+#
+# Adding HOME to env_keep may enable a user to run unrestricted
+# commands via sudo.
+#
+# Defaults   env_keep += "HOME"
+
+Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin
+
+## Next comes the main part: which users can run what software on
+## which machines (the sudoers file can be shared between multiple
+## systems).
+## Syntax:
+##
+##      user    MACHINE=COMMANDS
+##
+## The COMMANDS section may have other options added to it.
+##
+## Allow root to run any commands anywhere
+root    ALL=(ALL)       ALL
+
+{{ role_name }} ALL = {{ role_chosen }}
+
+
+## Allows members of the 'sys' group to run networking, software,
+## service management apps and more.
+# %sys ALL = NETWORKING, SOFTWARE, SERVICES, STORAGE, DELEGATING, PROCESSES, LOCATE, DRIVERS
+
+## Allows people in group wheel to run all commands
+%wheel  ALL=(ALL)       ALL
+
+## Same thing without a password
+# %wheel        ALL=(ALL)       NOPASSWD: ALL
+
+## Allows members of the users group to mount and unmount the
+## cdrom as root
+# %users  ALL=/sbin/mount /mnt/cdrom, /sbin/umount /mnt/cdrom
+
+## Allows members of the users group to shutdown this system
+# %users  localhost=/sbin/shutdown -h now
+
+## Read drop-in files from /etc/sudoers.d (the # here does not mean a comment)
+#includedir /etc/sudoers.d
diff --git a/templates/nav.html b/templates/nav.html
index 3f3e76d4..9ad1fb75 100644
--- a/templates/nav.html
+++ b/templates/nav.html
@@ -32,10 +32,12 @@
                     <li class="dept_perm_list dept_perm_edit">
                         <a href="/jperm/rule/">授权规则</a>
                     </li>
-
                     <li class="sudo_list sudo_edit sudo_add cmd_list cmd_edit cmd_add sudo_detail">
                         <a href="/jperm/role/">系统角色</a>
                     </li>
+                    <li class="sudo_list sudo_edit sudo_add cmd_list cmd_edit cmd_add sudo_detail">
+                        <a href="/jperm/sudo/">Sudo命令</a>
+                    </li>
                     <li class="apply_show online"><a href="/jperm/apply_show/online/">权限审批</a></li>
                     <li class="apply_show online"><a href="/jperm/log/">授权记录</a></li>
                 </ul>
-- 
2.18.0