diff --git a/Dockerfile b/Dockerfile
index 939b012..a01ae41 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,19 +18,19 @@ COPY --from=builder /data/apps/cmdb-ui/dist /etc/nginx/html/
 
 
 # ================================= API ================================
-FROM centos:7.6.1810 AS cmdb-api
+FROM python:3.7-alpine AS cmdb-api
 
-LABEL description="Python2.7.5,cmdb"
+LABEL description="Python3.7,cmdb"
 
 COPY . /data/apps/cmdb
 
 WORKDIR /data/apps/cmdb
 
-RUN yum install -y epel-release && yum clean all \
-	&&  yum install -y python-pip \
-    && pip install  --no-cache-dir -r docs/requirements.txt \
+RUN apk add --no-cache gcc musl-dev libffi-dev
+
+RUN pip install  --no-cache-dir -r docs/requirements.txt \
     && cp ./api/settings.py.example ./api/settings.py \
     && sed -i "s#{user}:{password}@127.0.0.1:3306/{db}#cmdb:123456@mysql:3306/cmdb#g" api/settings.py \
     && sed -i "s/127.0.0.1/redis/g" api/settings.py
 
-CMD ["bash", "-c", "flask run"]
+CMD ["bash", "-c", "flask run"]
\ No newline at end of file
diff --git a/api/lib/perm/acl/cache.py b/api/lib/perm/acl/cache.py
index 0de541f..030f4d2 100644
--- a/api/lib/perm/acl/cache.py
+++ b/api/lib/perm/acl/cache.py
@@ -1,6 +1,7 @@
 # -*- coding:utf-8 -*-
 
 from api.extensions import cache
+from api.models.acl import Permission
 from api.models.acl import Role
 
 
@@ -46,8 +47,8 @@ class RoleRelationCache(object):
     def get_parent_ids(cls, rid):
         parent_ids = cache.get(cls.PREFIX_PARENT.format(rid))
         if not parent_ids:
-            from api.lib.perm.acl.role import RoleCRUD
-            parent_ids = RoleCRUD.get_parent_ids(rid)
+            from api.lib.perm.acl.role import RoleRelationCRUD
+            parent_ids = RoleRelationCRUD.get_parent_ids(rid)
             cache.set(cls.PREFIX_PARENT.format(rid), parent_ids, timeout=0)
 
         return parent_ids
@@ -56,8 +57,8 @@ class RoleRelationCache(object):
     def get_child_ids(cls, rid):
         child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid))
         if not child_ids:
-            from api.lib.perm.acl.role import RoleCRUD
-            child_ids = RoleCRUD.get_child_ids(rid)
+            from api.lib.perm.acl.role import RoleRelationCRUD
+            child_ids = RoleRelationCRUD.get_child_ids(rid)
             cache.set(cls.PREFIX_CHILDREN.format(rid), child_ids, timeout=0)
 
         return child_ids
@@ -89,3 +90,25 @@ class RoleRelationCache(object):
         cache.delete(cls.PREFIX_PARENT.format(rid))
         cache.delete(cls.PREFIX_CHILDREN.format(rid))
         cache.delete(cls.PREFIX_RESOURCES.format(rid))
+
+
+class PermissionCache(object):
+    PREFIX_ID = "Permission::id::{0}"
+    PREFIX_NAME = "Permission::name::{0}"
+
+    @classmethod
+    def get(cls, key):
+        perm = cache.get(cls.PREFIX_ID.format(key))
+        perm = perm or cache.get(cls.PREFIX_NAME.format(key))
+        if perm is None:
+            perm = Permission.get_by_id(key)
+            perm = perm or Permission.get_by(name=key, first=True, to_dict=False)
+            if perm is not None:
+                cache.set(cls.PREFIX_ID.format(key), perm)
+
+        return perm
+
+    @classmethod
+    def clean(cls, key):
+        cache.delete(cls.PREFIX_ID.format(key))
+        cache.delete(cls.PREFIX_NAME.format(key))
diff --git a/api/lib/perm/acl/permission.py b/api/lib/perm/acl/permission.py
new file mode 100644
index 0000000..c06a09a
--- /dev/null
+++ b/api/lib/perm/acl/permission.py
@@ -0,0 +1,41 @@
+# -*- coding:utf-8 -*-
+
+
+from api.lib.perm.acl.cache import PermissionCache
+from api.lib.perm.acl.cache import RoleCache
+from api.models.acl import RolePermission
+
+
+class PermissionCRUD(object):
+    @staticmethod
+    def get_all(resource_id=None, group_id=None):
+        result = dict()
+        if resource_id is not None:
+            perms = RolePermission.get_by(resource_id=resource_id, to_dict=False)
+        else:
+            perms = RolePermission.get_by(group_id=group_id, to_dict=False)
+
+        for perm in perms:
+            result.setdefault((perm.rid, RoleCache.get(perm.rid).name), []).append(
+                PermissionCache.get(perm.perm_id).to_dict())
+
+        return result
+
+    @staticmethod
+    def grant(rid, perms, resource_id=None, group_id=None):
+        for perm in perms:
+            perm = PermissionCache.get(perm)
+            existed = RolePermission.get_by(rid=rid, perm_id=perm.id, group_id=group_id, resource_id=resource_id)
+            existed or RolePermission.create(rid=rid, perm_id=perm.id, group_id=group_id, resource_id=resource_id)
+
+    @staticmethod
+    def revoke(rid, perms, resource_id=None, group_id=None):
+        for perm in perms:
+            perm = PermissionCache.get(perm)
+            existed = RolePermission.get_by(rid=rid,
+                                            perm_id=perm.id,
+                                            group_id=group_id,
+                                            resource_id=resource_id,
+                                            first=True,
+                                            to_dict=False)
+            existed and existed.soft_delete()
diff --git a/api/lib/perm/acl/resource.py b/api/lib/perm/acl/resource.py
index 6800820..9826d65 100644
--- a/api/lib/perm/acl/resource.py
+++ b/api/lib/perm/acl/resource.py
@@ -156,3 +156,6 @@ class ResourceCRUD(object):
         resource = Resource.get_by_id(_id) or abort(404, "Resource <{0}> is not found".format(_id))
 
         resource.soft_delete()
+
+
+
diff --git a/api/lib/perm/acl/role.py b/api/lib/perm/acl/role.py
index 76f4462..8bbc5e0 100644
--- a/api/lib/perm/acl/role.py
+++ b/api/lib/perm/acl/role.py
@@ -1,6 +1,7 @@
 # -*- coding:utf-8 -*-
 
 
+import six
 from flask import abort
 
 from api.extensions import db
@@ -16,75 +17,16 @@ from api.tasks.acl import role_rebuild
 
 
 class RoleRelationCRUD(object):
-    pass
-
-
-class RoleCRUD(object):
     @staticmethod
-    def search(q, app_id, page=1, page_size=None):
-        query = db.session.query(Role).filter(Role.deleted.is_(False)).filter(
-            Role.app_id == app_id).filter(Role.uid.is_(None))
-        if q:
-            query = query.filter(Role.name.ilike('%{0}%'.format(q)))
-
-        numfound = query.count()
-
-        return numfound, query.offset((page - 1) * page_size).limit(page_size)
-
-    @staticmethod
-    def add_role(name, app_id, is_app_admin=False, uid=None):
-        Role.get_by(name=name, app_id=app_id) and abort(400, "Role <{0}> is already existed".format(name))
-
-        return Role.create(name=name,
-                           app_id=app_id,
-                           is_app_admin=is_app_admin,
-                           uid=uid)
-
-    @staticmethod
-    def update_role(rid, **kwargs):
-        role = Role.get_by_id(rid) or abort(404, "Role <{0}> does not exist".format(rid))
-
-        RoleCache.clean(rid)
-
-        return role.update(**kwargs)
-
-    @classmethod
-    def delete_role(cls, rid):
-        role = Role.get_by_id(rid) or abort(404, "Role <{0}> does not exist".format(rid))
-
-        parent_ids = cls.get_parent_ids(rid)
-        child_ids = cls.get_child_ids(rid)
-
-        for i in RoleRelation.get_by(parent_id=rid, to_dict=False):
-            i.soft_delete()
-        for i in RoleRelation.get_by(child_id=rid, to_dict=False):
-            i.soft_delete()
-
-        for i in RolePermission.get_by(rid=rid, to_dict=False):
-            i.soft_delete()
-
-        role_rebuild.apply_async(args=(parent_ids + child_ids,), queue=ACL_QUEUE)
-
-        RoleCache.clean(rid)
-        RoleRelationCache.clean(rid)
-
-        role.soft_delete()
-
-    @staticmethod
-    def get_resources(rid):
-        res = RolePermission.get_by(rid=rid, to_dict=False)
-        id2perms = dict(id2perms={}, group2perms={})
+    def get_parents(rids):
+        rids = [rids] if isinstance(rids, six.integer_types) else rids
+        res = db.session.query(RoleRelation).filter(
+            RoleRelation.child_id.in_(rids)).filter(RoleRelation.deleted.is_(False))
+        id2parents = {}
         for i in res:
-            if i.resource_id:
-                id2perms['id2perms'].setdefault(i.resource_id, []).append(i.perm.name)
-            elif i.group_id:
-                id2perms['group2perms'].setdefault(i.group_id, []).append(i.perm.name)
+            id2parents.setdefault(i.child_id, []).append(RoleCache.get(i.parent_id).to_dict())
 
-        return id2perms
-
-    @staticmethod
-    def get_group_ids(resource_id):
-        return [i.group_id for i in ResourceGroupItems.get_by(resource_id, to_dict=False)]
+        return id2parents
 
     @staticmethod
     def get_parent_ids(rid):
@@ -126,12 +68,99 @@ class RoleCRUD(object):
 
         return all_child_ids
 
+    @staticmethod
+    def add(parent_id, child_id):
+        RoleRelation.get_by(parent_id=parent_id, child_id=child_id) and abort(400, "It's already existed")
+
+        return RoleRelation.create(parent_id=parent_id, child_id=child_id)
+
+    @staticmethod
+    def delete(_id):
+        existed = RoleRelation.get_by_id(_id) or abort(400, "RoleRelation <{0}> does not exist".format(_id))
+
+        existed.soft_delete()
+
+    @staticmethod
+    def delete2(parent_id, child_id):
+        existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id)
+        existed or abort(400, "RoleRelation < {0} -> {1} > does not exist".format(parent_id, child_id))
+
+        existed.soft_delete()
+
+
+class RoleCRUD(object):
+    @staticmethod
+    def search(q, app_id, page=1, page_size=None):
+        query = db.session.query(Role).filter(Role.deleted.is_(False)).filter(
+            Role.app_id == app_id).filter(Role.uid.is_(None))
+        if q:
+            query = query.filter(Role.name.ilike('%{0}%'.format(q)))
+
+        numfound = query.count()
+
+        return numfound, query.offset((page - 1) * page_size).limit(page_size)
+
+    @staticmethod
+    def add_role(name, app_id, is_app_admin=False, uid=None):
+        Role.get_by(name=name, app_id=app_id) and abort(400, "Role <{0}> is already existed".format(name))
+
+        return Role.create(name=name,
+                           app_id=app_id,
+                           is_app_admin=is_app_admin,
+                           uid=uid)
+
+    @staticmethod
+    def update_role(rid, **kwargs):
+        role = Role.get_by_id(rid) or abort(404, "Role <{0}> does not exist".format(rid))
+
+        RoleCache.clean(rid)
+
+        return role.update(**kwargs)
+
+    @classmethod
+    def delete_role(cls, rid):
+        role = Role.get_by_id(rid) or abort(404, "Role <{0}> does not exist".format(rid))
+
+        parent_ids = RoleRelationCRUD.get_parent_ids(rid)
+        child_ids = RoleRelationCRUD.get_child_ids(rid)
+
+        for i in RoleRelation.get_by(parent_id=rid, to_dict=False):
+            i.soft_delete()
+        for i in RoleRelation.get_by(child_id=rid, to_dict=False):
+            i.soft_delete()
+
+        for i in RolePermission.get_by(rid=rid, to_dict=False):
+            i.soft_delete()
+
+        role_rebuild.apply_async(args=(parent_ids + child_ids,), queue=ACL_QUEUE)
+
+        RoleCache.clean(rid)
+        RoleRelationCache.clean(rid)
+
+        role.soft_delete()
+
+    @staticmethod
+    def get_resources(rid):
+        res = RolePermission.get_by(rid=rid, to_dict=False)
+        id2perms = dict(id2perms={}, group2perms={})
+        for i in res:
+            if i.resource_id:
+                id2perms['id2perms'].setdefault(i.resource_id, []).append(i.perm.name)
+            elif i.group_id:
+                id2perms['group2perms'].setdefault(i.group_id, []).append(i.perm.name)
+
+        return id2perms
+
+    @staticmethod
+    def get_group_ids(resource_id):
+        return [i.group_id for i in ResourceGroupItems.get_by(resource_id, to_dict=False)]
+
     @classmethod
     def has_permission(cls, rid, resource_name, perm):
         resource = Resource.get_by(name=resource_name, first=True, to_dict=False)
         resource = resource or abort(403, "Resource <{0}> is not in ACL".format(resource_name))
 
-        parent_ids = cls.recursive_parent_ids(rid)
+        parent_ids = RoleRelationCRUD.recursive_parent_ids(rid)
 
         group_ids = cls.get_group_ids(resource.id)
 
@@ -154,7 +183,7 @@ class RoleCRUD(object):
         resource = Resource.get_by(name=resource_name, first=True, to_dict=False)
         resource = resource or abort(403, "Resource <{0}> is not in ACL".format(resource_name))
 
-        parent_ids = cls.recursive_parent_ids(rid)
+        parent_ids = RoleRelationCRUD.recursive_parent_ids(rid)
         group_ids = cls.get_group_ids(resource.id)
 
         perms = []
diff --git a/api/views/__init__.py b/api/views/__init__.py
index 4d8a2ee..c507414 100644
--- a/api/views/__init__.py
+++ b/api/views/__init__.py
@@ -32,6 +32,6 @@ register_resources(os.path.join(HERE, "cmdb"), rest)
 
 
 # acl
-blueprint_acl_v1 = Blueprint('cmdb_acl_v1', __name__, url_prefix='/api/v1/acl')
+blueprint_acl_v1 = Blueprint('acl_api_v1', __name__, url_prefix='/api/v1/acl')
 rest = Api(blueprint_acl_v1)
 register_resources(os.path.join(HERE, "acl"), rest)
diff --git a/api/views/acl/permission.py b/api/views/acl/permission.py
new file mode 100644
index 0000000..e750712
--- /dev/null
+++ b/api/views/acl/permission.py
@@ -0,0 +1,40 @@
+# -*- coding:utf-8 -*-
+
+
+from flask import request
+
+from api.lib.decorator import args_required
+from api.lib.perm.acl.permission import PermissionCRUD
+from api.lib.utils import handle_arg_list
+from api.resource import APIView
+
+
+class ResourcePermissionView(APIView):
+    url_prefix = ("/resources/<int:resource_id>/permissions", "/resource_groups/<int:group_id>/permissions")
+
+    def get(self, resource_id=None, group_id=None):
+        return self.jsonify(PermissionCRUD.get_all(resource_id, group_id))
+
+
+class RolePermissionGrantView(APIView):
+    url_prefix = ('/roles/<int:rid>/resources/<int:resource_id>/grant',
+                  '/roles/<int:rid>/resource_groups/<int:group_id>/grant')
+
+    @args_required('perms')
+    def post(self, rid, resource_id=None, group_id=None):
+        perms = handle_arg_list(request.values.get("perms"))
+        PermissionCRUD.grant(rid, perms, resource_id=resource_id, group_id=group_id)
+
+        return self.jsonify(rid=rid, resource_id=resource_id, group_id=group_id, perms=perms)
+
+
+class RolePermissionRevokeView(APIView):
+    url_prefix = ('/roles/<int:rid>/resources/<int:resource_id>/revoke',
+                  '/roles/<int:rid>/resource_groups/<int:group_id>/revoke')
+
+    @args_required('perms')
+    def post(self, rid, resource_id=None, group_id=None):
+        perms = handle_arg_list(request.values.get("perms"))
+        PermissionCRUD.revoke(rid, perms, resource_id=resource_id, group_id=group_id)
+
+        return self.jsonify(rid=rid, resource_id=resource_id, group_id=group_id, perms=perms)
diff --git a/api/views/acl/role.py b/api/views/acl/role.py
index 9a2b5cc..ca59e5a 100644
--- a/api/views/acl/role.py
+++ b/api/views/acl/role.py
@@ -4,6 +4,7 @@ from flask import request
 
 from api.lib.decorator import args_required
 from api.lib.perm.acl.role import RoleCRUD
+from api.lib.perm.acl.role import RoleRelationCRUD
 from api.lib.utils import get_page
 from api.lib.utils import get_page_size
 from api.resource import APIView
@@ -21,9 +22,12 @@ class RoleView(APIView):
 
         numfound, roles = RoleCRUD.search(q, app_id, page, page_size)
 
+        id2parents = RoleRelationCRUD.get_parents([i.id for i in roles])
+
         return self.jsonify(numfound=numfound,
                             page=page,
                             page_size=page_size,
+                            id2parents=id2parents,
                             roles=[i.to_dict() for i in roles])
 
     @args_required('name')
@@ -46,3 +50,22 @@ class RoleView(APIView):
         RoleCRUD.delete_role(rid)
 
         return self.jsonify(rid=rid)
+
+
+class RoleRelationView(APIView):
+    url_prefix = "/roles/<int:child_id>/parents"
+
+    @args_required('parent_id')
+    def post(self, child_id):
+        parent_id = request.values.get('parent_id')
+        res = RoleRelationCRUD.add(parent_id, child_id)
+
+        return self.jsonify(res.to_dict())
+
+    @args_required('parent_id')
+    def delete(self, child_id):
+        parent_id = request.values.get('parent_id')
+
+        RoleRelationCRUD.delete2(parent_id, child_id)
+
+        return self.jsonify(parent_id=parent_id, child_id=child_id)
diff --git a/docker-compose.yml b/docker-compose.yml
index cf47042..61519ca 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -72,4 +72,4 @@ volumes:
   db-data:
 
 networks:
-  new:
+  new:
\ No newline at end of file