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_perms
和service_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',)
非常遗憾,如果这么修改,SystemPanels
和IdentityPanels
对于非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
##参考资料