安全
Kibana 通常能够对核心和插件开发者透明地实现安全性,并且在很大程度上仍然是这样。Kibana 利用 Elasticsearch 提供的两种方法:callWithRequest 和 callWithInternalUser。
callWithRequest 使用 Kibana 终端用户的身份验证凭据对 Elasticsearch 执行请求。因此,如果使用 `callWithRequest` 时,您使用用户 `foo` 登录 Kibana,Kibana 将以用户 `foo` 的身份对 Elasticsearch 执行请求。从历史上看,`callWithRequest` 已被广泛用于执行由 Kibana 终端用户发起的请求。
callWithInternalUser 使用 Kibana 内部服务器用户对 Elasticsearch 执行请求,并且历史上一直用于执行非由 Kibana 终端用户发起的操作;例如,创建初始的 `.kibana` 索引或对 Elasticsearch 执行健康检查。
但是,随着基于角色的访问控制 (RBAC) 的引入,情况不再是这样简单了。Kibana 现在要求所有对 `.kibana` 索引的访问都通过 `SavedObjectsClient`。这曾经是一个最佳实践,因为 `SavedObjectsClient` 负责在 Elasticsearch 中存储的文档与 Saved Objects 之间进行转换,但 RBAC 现在利用此抽象来实现访问控制,并确定何时使用 `callWithRequest` 而不是 `callWithInternalUser`。
Kibana 中的基于角色的访问控制 (RBAC) 依赖于 Elasticsearch 公开的应用程序权限。这允许 Kibana 定义 Kibana 希望授予用户的权限,使用角色将它们分配给相关用户,然后授权用户执行特定操作。这在一个安全的 `SavedObjectsClient` 实例中处理,并且在使用 `request.getSavedObjectsClient()` 或 `savedObjects.getScopedSavedObjectsClient()` 时对使用者透明可用。
当 Kibana 首次启动时,它会对 Elasticsearch 执行以下 POST 请求。这会将权限定义与各种 actions 同步,后者稍后用于授权用户。
POST /_security/privilege
Content-Type: application/json
Authorization: Basic {kib} changeme
{
"kibana-.kibana":{
"all":{
"application":"kibana-.kibana",
"name":"all",
"actions":[
"version:7.0.0-alpha1-SNAPSHOT",
"action:login",
"action:*"
],
"metadata":{}
},
"read":{
"application":"kibana-.kibana",
"name":"read",
"actions":[
"version:7.0.0-alpha1-SNAPSHOT",
"action:login",
"saved_object:dashboard/get",
"saved_object:dashboard/bulk_get",
"saved_object:dashboard/find",
...
],"metadata":{}}
}
}
应用程序是通过将 `kibana-` 的前缀与 `kibana.yml` 中的 `kibana.index` 值连接起来创建的,因此不同的 Kibana 租户彼此隔离。
Kibana 权限使用 `applications` 元素分配给特定角色。例如,以下角色将默认 Kibana application 的 `*` resources(将来将用于保护 Spaces)上的所有权限授予默认 Kibana application。
"new_kibana_user": {
"applications": [
{
"application": "kibana-.kibana",
"privileges": [
"all"
],
"resources": [
"*"
]
}
]
}
授予Kibana 权限的角色应使用角色 API 或 **Management → Security → Roles** 页面进行管理,而不是直接使用 Elasticsearch 的角色管理 API。然后可以使用 Elasticsearch 的用户管理 API 将此角色分配给用户。
Elasticsearch 的has privileges API 确定用户是否有权执行特定操作。
POST /_security/user/_has_privileges
Content-Type: application/json
Authorization: Basic foo_read_only_user password
{
"applications":[
{
"application":"kibana-.kibana",
"resources":["*"],
"privileges":[
"saved_object:dashboard/save",
]
}
]
}
Elasticsearch 会检查用户是否被授予了特定的操作。如果用户被分配了授予权限的角色,Elasticsearch 会使用Kibana 权限定义将其与操作关联起来,这使得以编程方式授权用户更加直观和灵活。
一旦我们授权用户执行特定操作,我们就可以使用 `callWithInternalUser` 来执行请求。
如果您的插件将与 Kibana 的默认分发版一起使用,那么您就可以注册插件提供的功能。功能通常是 Kibana 中的应用;注册后,您可以随时通过 Spaces 切换它们,并在启用安全功能时通过 Roles 进行保护。
注册功能还可以让您的插件访问“UI 功能”。这些功能是布尔标志,您可以使用它们根据当前用户的权限有条件地渲染您的界面。例如,如果当前用户未授权,您可以隐藏或禁用“保存”按钮。
功能注册由内置的 `features` 插件控制。要注册一个功能,请在插件的 `setup` 生命周期函数中调用 `features` 的 `registerKibanaFeature` 函数,并提供适当的详细信息。
setup(core, { features }) {
features.registerKibanaFeature({
// feature details here.
});
}
注册一个功能包括以下字段。有关更多信息,请参阅功能注册接口。
| 字段名称 | 数据类型 | 示例 | 描述 |
|---|---|---|---|
id (必需) |
string |
"sample_feature" |
功能的唯一标识符。通常,插件的 ID 就足够了。 |
name (必需) |
string |
"Sample Feature" |
功能的易读名称。 |
category (必需) |
AppCategory |
DEFAULT_APP_CATEGORIES.kibana |
最能代表您功能的 AppCategory。用于在管理屏幕中组织功能的显示。 |
app (必需) |
string[] |
["sample_app", "kibana"] |
此功能启用的应用程序数组。通常,您插件的所有应用(来自 uiExports)都将包含在此处。 |
privileges (必需) |
KibanaFeatureConfig. |
请参阅示例 1和示例 2。 |
此功能运行所需的一组权限。 |
subFeatures (可选) |
KibanaFeatureConfig. |
请参阅示例 3。 |
一组子功能,可提供比 all 和 read 功能权限更精细化的访问控制。这些选项仅在 Gold 订阅级别及以上可用。 |
scope (可选) |
string[] |
["spaces", "security"] |
默认 security。Scope 确定功能是仅出现在 Security Feature Privileges 中,还是同时出现在 Spaces Visibility Toggles 和 Security Feature Privileges 中。 |
功能注册的 privileges 部分允许插件为其应用程序实现读/写模式和只读模式。
有关字段和选项的完整说明,请参阅功能注册接口。
UI 功能可用于您的公共(客户端)插件代码。这些功能是只读的,用于通知 UI。此对象按功能 ID 命名空间。例如,如果您的功能 ID 是“foo”,则您的 UI 功能存储在 uiCapabilities.foo。可以从插件的 start 生命周期中的 core.application 服务访问功能。
public start(core) {
const { capabilities } = core.application;
const canUserSave = capabilities.foo.save;
if (canUserSave) {
// show save button
}
}
public setup(core, { features }) {
features.registerKibanaFeature({
id: 'canvas',
name: 'Canvas',
category: DEFAULT_APP_CATEGORIES.kibana,
app: ['canvas', 'kibana'],
catalogue: ['canvas'],
privileges: {
all: {
savedObject: {
all: ['canvas-workpad'],
read: ['index-pattern'],
},
ui: ['save'],
},
read: {
savedObject: {
all: [],
read: ['index-pattern', 'canvas-workpad'],
},
ui: [],
},
},
});
}
这显示了 Canvas 应用如何将自身注册为 Kibana 功能。请注意,它为每个权限指定了不同的 savedObject 访问级别。
- 具有读/写访问权限(
all权限)的用户需要能够读/写canvas-workpad保存的对象,并且需要对index-pattern保存的对象具有只读访问权限。 - 具有只读访问权限(
read权限)的用户不需要对任何保存的对象具有读/写访问权限,而是对index-pattern和canvas-workpad保存的对象具有只读访问权限。
此外,Canvas 还注册了 canvas UI 应用和 canvas 目录条目。这告诉 Kibana,这些实体可供具有 read 或 all 权限的用户使用。
all 权限定义了一个单一的“保存”UI 功能。要在 UI 中访问此功能,Canvas 可以
public start(core) {
const { capabilities } = core.application;
const canUserSave = capabilities.canvas.save;
if (canUserSave) {
// show save button
}
}
由于 read 权限未定义 save 功能,因此具有只读访问权限的用户将把 uiCapabilities.canvas.save 标志设置为 false。
public setup(core, { features }) {
features.registerKibanaFeature({
id: 'dev_tools',
name: i18n.translate('xpack.features.devToolsFeatureName', {
defaultMessage: 'Dev Tools',
}),
category: DEFAULT_APP_CATEGORIES.management,
app: ['kibana'],
catalogue: ['console', 'searchprofiler', 'grokdebugger'],
privileges: {
all: {
api: ['console'],
savedObject: {
all: [],
read: [],
},
ui: ['show'],
},
read: {
api: ['console'],
savedObject: {
all: [],
read: [],
},
ui: ['show'],
},
},
privilegesTooltip: i18n.translate('xpack.features.devToolsPrivilegesTooltip', {
defaultMessage:
'User should also be granted the appropriate {es} cluster and index privileges',
}),
});
}
与 Canvas 示例不同,Dev Tools 不需要访问任何保存的对象即可运行。但是,Dev Tools 会指定一个 API 端点。配置此项后,Security 插件将自动授权访问任何标记为 access:console 的服务器 API 路由,如下所示:
server.route({
path: '/api/console/proxy',
method: 'POST',
config: {
tags: ['access:console'],
handler: async (req, h) => {
// ...
}
}
});
Discover 利用子功能权限来实现细粒度的访问控制。在此示例中,定义了两个子功能权限:“创建短 URL”和“生成 PDF 报告”。这允许用户授予对此功能的访问权限,而无需授予 Discover 的 all 权限。换句话说,您可以授予 Discover read 访问权限,并同时授予创建短 URL 或生成 PDF 报告的能力。
请注意,“生成 PDF 报告”子功能权限有一个额外的 minimumPrivilege 选项。仅当满足许可证要求时,Kibana 才会提供此子功能权限。
public setup(core, { features }) {
features.registerKibanaFeature({
{
id: 'discover',
name: i18n.translate('xpack.features.discoverFeatureName', {
defaultMessage: 'Discover',
}),
order: 100,
category: DEFAULT_APP_CATEGORIES.kibana,
app: ['kibana'],
catalogue: ['discover'],
privileges: {
all: {
app: ['kibana'],
catalogue: ['discover'],
savedObject: {
all: ['search', 'query'],
read: ['index-pattern'],
},
ui: ['show', 'save', 'saveQuery'],
},
read: {
app: ['kibana'],
catalogue: ['discover'],
savedObject: {
all: [],
read: ['index-pattern', 'search', 'query'],
},
ui: ['show'],
},
},
subFeatures: [
{
name: i18n.translate('xpack.features.ossFeatures.discoverShortUrlSubFeatureName', {
defaultMessage: 'Short URLs',
}),
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'url_create',
name: i18n.translate(
'xpack.features.ossFeatures.discoverCreateShortUrlPrivilegeName',
{
defaultMessage: 'Create Short URLs',
}
),
includeIn: 'all',
savedObject: {
all: ['url'],
read: [],
},
ui: ['createShortUrl'],
},
],
},
{
groupType: 'independent',
privileges: [
{
id: 'pdf_generate',
name: i18n.translate(
'xpack.features.ossFeatures.discoverGeneratePDFReportsPrivilegeName',
{
defaultMessage: 'Generate PDF Reports',
}
),
minimumLicense: 'platinum',
includeIn: 'all',
savedObject: {
all: [],
read: [],
},
api: ['generatePDFReports'],
ui: ['generatePDFReports'],
},
],
},
],
},
],
}
});
}