openstack-horizon管理页面屏蔽

openstack的用户有role的属性,horizon根据用户的role不同展示不同的页面,如果role为admin,除了项目页面外,还会展示管理员页面。

这是很正常的逻辑,一个项目一个管理员,其他人的role都是member。但是实际开发中可能遇到这样的问题:horizon提供的功能很简单,可能不能满足你的要求,于是你使用了opencontrail来补充,但是,登录opencontrail的时候要求用户具有admin权限,我们必须要给所有人都加上admin权限,而opencontrail又缺乏管理项目和用户的功能,还是需要使用openstack,这样诸多拥有admin权限的人都能看到管理员界面,如果对系统不了解,误删了一些项目或者用户,会让你排查到抓狂。

怎么解决这个问题呢?我的想法是,只有用户名和role均为admin的用户才能看到管理页面,role满足但是用户名不满足的用户只能看到project。据此得到的解决方案有两种,下面分别叙述。

##方法一:openstack_auth的permission

horizon左侧显示的导航栏是一个个dashboard,这些dashboard里面的子项目称为panel,代码中判断显示或者不显示一个dashboard取决于用户的permission,那permission在哪里定义呢?在openstack_auth这个模块中。

/usr/lib/python2.7/dist-packages/openstack_auth/backend.py

backend.py文件中的类KeystoneBackend中的函数get_all_permissions中定义了用户的权限:

def get_all_permissions(self, user, obj=None):
    """Returns a set of permission strings that this user has through
       his/her Keystone "roles".

       The permissions are returned as ``"openstack.{{ role.name }}"``.
    """
    if user.is_anonymous() or obj is not None:
        return set()
    # TODO(gabrielhurley): Integrate policy-driven RBAC
    #                      when supported by Keystone.
    #print user.username
    role_perms = set(["openstack.roles.%s" % role['name'].lower()
                      for role in user.roles])
    service_perms = set(["openstack.services.%s" % service['type'].lower()
                      for service in user.service_catalog])
    return role_perms | service_perms

可以看到我们有的权限是role_permsservice_perms,不妨将它们打印出来看一下,倒数第二行加个打印:

print role_perms | service_perms

内容为:

set([u'openstack.services.compute',  u'openstack.services.orchestration', u'openstack.services.identity', u'openstack.roles
.keystoneserviceadmin', u'openstack.services.network', u'openstack.roles.keystoneadmin', u'openstack.services.volume', u'openstack.services.ec2', u'openstack.roles.admin', u'openstack.services.image'])

如果我们想通过用户名来限制dashboard和panel的显示,那就要增加name_perm这个字段,修改后的函数如下:

def get_all_permissions(self, user, obj=None):
    """Returns a set of permission strings that this user has through
       his/her Keystone "roles".

       The permissions are returned as ``"openstack.{{ role.name }}"``.
    """
    if user.is_anonymous() or obj is not None:
        return set()
    # TODO(gabrielhurley): Integrate policy-driven RBAC
    #                      when supported by Keystone.
    #print user.username
    role_perms = set(["openstack.roles.%s" % role['name'].lower()
                      for role in user.roles])
    service_perms = set(["openstack.services.%s" % service['type'].lower()
                      for service in user.service_catalog])
    name_perms = set(["openstack.username.%s" % user.username.lower()])
    return role_perms | service_perms | name_perms

这样就增加了name_perms,打印出来如下:

set([u'openstack.services.compute', u'openstack.username.youwangqiu', u'openstack.services.orchestration', u'openstack.services.identity', u'openstack.roles
.keystoneserviceadmin', u'openstack.services.network', u'openstack.roles.keystoneadmin', u'openstack.services.volume', u'openstack.services.ec2', u'openstack.roles.admin', u'openstack.services.image'])

这样只是让用户有了这个权限字段,怎么让dashboard和panel知道呢?这就要到

/usr/share/openstack-dashboard/openstack_dashboard/dashboards

这个目录下来看。

root@contrail-you:/usr/share/openstack-dashboard/openstack_dashboard/dashboards# ls
admin  __init__.py  __init__.pyc  project  router  settings

这里的admin,project,router和setting就是4个独立的dashboard,因为是限制管理员界面,很明显,应该是admin这个dashboard。

这个dashboard的类似于config文件在dashboard.py文件中

import horizon


class SystemPanels(horizon.PanelGroup):
    slug = "admin"
    name = _("System Panel")
    panels = ('overview', 'metering', 'hypervisors', 'aggregates',
              'instances', 'volumes', 'flavors', 'images',
              'networks', 'routers', 'info')

class IdentityPanels(horizon.PanelGroup):
    slug = "identity"
    name = _("Identity Panel")
    panels = ('domains', 'projects', 'users', 'groups', 'roles')        

class Admin(horizon.Dashboard):
    name = _("Admin")
    slug = "admin"
    panels = (SystemPanels, IdentityPanels)

    default_panel = 'overview'
    permissions = ('openstack.roles.admin',)


horizon.register(Admin)

可以看到显示admin这个dashboard的权限是roles为admin(permissions = ('openstack.roles.admin',)),非常容易想到我们把permission这么改不就行了:

permissions = ('openstack.username.admin','openstack.roles.admin',)

非常遗憾,如果这么修改,SystemPanelsIdentityPanels对于非admin用户名的用户都不会显示,会导致用户无法访问。那能不能单独给IdentityPanels增加permission呢?答案也是否定的,IdentityPanels继承的类并不支持permission字段,如果你硬是要给他加上支持,那应该会有很大的工作量。

其实2014版的dashboard已经解决了这个问题,在2014版里,identity是作为一个单独的dashboard存在的,这样我们就可以增加permission的内容而无需担心影响别的用户了,笔者用的是2012版

如何是好呢?我们还有一个笨方法!

在dashboard层面不能限制的话我们可以在panel层面限制啊!

用户这个panel为例,修改

/usr/share/openstack-dashboard/openstack_dashboard/dashboards/admin/users/panel.py

将这个文件修改为:

from django.utils.translation import ugettext_lazy as _

import horizon

from openstack_dashboard.dashboards.admin import dashboard


class Users(horizon.Panel):
    name = _("Users")
    slug = 'users'
    permissions = ('openstack.username.admin',) #增加这一行


dashboard.Admin.register(Users)

其实就是增加了permission这一行,现在可以登录看一下了,你会发现用户这个子项目已经不见了,大功告成,如果要把管理员这整个dashboard搞消失,只需要将这个dashboard里的所有panel都加上permission做限制就可以了。

这个方法比较烦,但是应用了permission的思想,我比较倾向于这个方法。方法二比较简单,只需要改动一处。

##方法二:在生成页面之前

毕竟是个web,总是要生成html的,在这些html里搜索一下会发现,左边的导航栏是用horizon_nav这个函数生成的。路径如下:

/usr/lib/python2.7/dist-packages/horizon/templatetags/horizon.py

这个函数是作为参数传入到模版中去的,只要在这里修改dashboard就能让admin栏不显示

原理很简单,对非admin用户名的用户,将dashboard里的admin这个dashboard删掉,只保留project这个dashboard。当然少不了一些调试,如果你有一些django的开发经验,应该很容易搞定,修改后的horizon_nav函数如下:

@register.inclusion_tag('horizon/_accordion_nav.html', takes_context=True)
def horizon_nav(context):
    if 'request' not in context:
        return {}
    current_dashboard = context['request'].horizon.get('dashboard', None)
    current_panel = context['request'].horizon.get('panel', None)
    dashboards = []
    for dash in Horizon.get_dashboards():
        panel_groups = dash.get_panel_groups()
        non_empty_groups = []
        for group in panel_groups.values():
            allowed_panels = []
            for panel in group:
                if callable(panel.nav) and panel.nav(context):
                    allowed_panels.append(panel)
                elif not callable(panel.nav) and panel.nav:
                    allowed_panels.append(panel)
            if allowed_panels:
                non_empty_groups.append((group.name, allowed_panels))
        if callable(dash.nav) and dash.nav(context):
            dashboards.append((dash, SortedDict(non_empty_groups)))
        elif not callable(dash.nav) and dash.nav:
            dashboards.append((dash, SortedDict(non_empty_groups)))
    username = context['request'].user.username  #**new**
    if username != 'admin':  #**new**
        dashboards = [dashboard for dashboard in dashboards if dashboards[0].slug != 'admin']    #**new**
    return {'components': dashboards,
            'user': context['request'].user,
            'current': current_dashboard,
            'current_panel': current_panel.slug if current_panel else '', 
            'request': context['request']}

NOTE:注意修改之后务必重启web

有不明白之处,可以联系:wq_you@163.com

##参考资料

Horizon Is Easy, Horizon Is Complex