[index management] better privilege checking for enrich policies (#201717)

## Summary

Kibana roles with only `manage_enrich` or `monitor_enrich` will now have
access to the Enrich Policy tab in Index Management.

---

The `registerElasticsearchFeature` api is too restrictive to use for
index management as it only allows a single set of privileges to
determine whether a given management app is shown AND any stated
privilege is combined in an `AND` logic statement. We need `OR` - index
management may cover a number of different privileges that don't
overlap. The solution - use an observable to subscribe to the
capabilities api and register the management app based on that.

This pr focuses on Enrich Policies and removes UI elements as
appropriate based on `manage_enrich` or `monitor_enrich` and leaves
other index management tabs alone as these will be addressed in follow
up PRs.

Part of https://github.com/elastic/kibana/issues/178654
This commit is contained in:
Matthew Kime 2024-12-02 15:52:33 -06:00 committed by GitHub
parent b527373431
commit a68dbaf195
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 266 additions and 261 deletions

View file

@ -57,10 +57,6 @@ describe('Create enrich policy', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setGetMatchingIndices(getMatchingIndices());
httpRequestsMockHelpers.setGetPrivilegesResponse({
hasAllPrivileges: true,
missingPrivileges: { cluster: [] },
});
httpRequestsMockHelpers.setGetMatchingDataStreams(getMatchingDataStreams());
await act(async () => {

View file

@ -188,9 +188,6 @@ const registerHttpRequestMockHelpers = (
error
);
const setGetPrivilegesResponse = (response?: HttpResponse, error?: ResponseError) =>
mockResponse('GET', `${INTERNAL_API_BASE_PATH}/enrich_policies/privileges`, response, error);
const setCreateEnrichPolicy = (response?: HttpResponse, error?: ResponseError) =>
mockResponse('POST', `${INTERNAL_API_BASE_PATH}/enrich_policies`, response, error);
@ -253,7 +250,6 @@ const registerHttpRequestMockHelpers = (
setCreateIndexResponse,
setGetMatchingIndices,
setGetFieldsFromIndices,
setGetPrivilegesResponse,
setCreateEnrichPolicy,
setInferenceModels,
setGetMatchingDataStreams,

View file

@ -91,6 +91,11 @@ const appDependencies = {
overlays: {
openConfirm: jest.fn(),
},
privs: {
monitor: true,
manageEnrich: true,
monitorEnrich: true,
},
} as any;
export const kibanaVersion = new SemVer(MAJOR_VERSION);

View file

@ -38,11 +38,6 @@ describe('Enrich policies tab', () => {
describe('empty states', () => {
beforeEach(async () => {
setDelayResponse(false);
httpRequestsMockHelpers.setGetPrivilegesResponse({
hasAllPrivileges: true,
missingPrivileges: { cluster: [] },
});
});
test('displays a loading prompt', async () => {
@ -82,24 +77,6 @@ describe('Enrich policies tab', () => {
});
});
describe('permissions check', () => {
it('shows a permissions error when the user does not have sufficient privileges', async () => {
httpRequestsMockHelpers.setGetPrivilegesResponse({
hasAllPrivileges: false,
missingPrivileges: { cluster: ['manage_enrich'] },
});
testBed = await setup(httpSetup);
await act(async () => {
testBed.actions.goToEnrichPoliciesTab();
});
testBed.component.update();
expect(testBed.exists('enrichPoliciesInsuficientPrivileges')).toBe(true);
});
});
describe('policies list', () => {
let testPolicy: ReturnType<typeof createTestEnrichPolicy>;
beforeEach(async () => {
@ -110,11 +87,6 @@ describe('Enrich policies tab', () => {
createTestEnrichPolicy('policy-range', 'range'),
]);
httpRequestsMockHelpers.setGetPrivilegesResponse({
hasAllPrivileges: true,
missingPrivileges: { cluster: [] },
});
testBed = await setup(httpSetup);
await act(async () => {
testBed.actions.goToEnrichPoliciesTab();

View file

@ -168,6 +168,11 @@ describe('index table', () => {
enableIndexActions: true,
enableIndexStats: true,
},
privs: {
monitor: true,
manageEnrich: true,
monitorEnrich: true,
},
};
component = (

View file

@ -80,6 +80,11 @@ export interface AppDependencies {
kibanaVersion: SemVer;
overlays: OverlayStart;
canUseSyntheticSource: boolean;
privs: {
monitor: boolean;
manageEnrich: boolean;
monitorEnrich: boolean;
};
}
export const AppContextProvider = ({

View file

@ -1,9 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { EnrichPoliciesWithPrivileges } from './with_privileges';
export { EnrichPoliciesAuthProvider } from './auth_provider';

View file

@ -1,82 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FormattedMessage } from '@kbn/i18n-react';
import React, { FunctionComponent } from 'react';
import {
PageLoading,
PageError,
useAuthorizationContext,
WithPrivileges,
NotAuthorizedSection,
} from '../../../shared_imports';
import { ENRICH_POLICIES_REQUIRED_PRIVILEGES } from '../../constants';
export const EnrichPoliciesWithPrivileges: FunctionComponent<{
children?: React.ReactNode;
}> = ({ children }) => {
const { apiError } = useAuthorizationContext();
if (apiError) {
return (
<PageError
title={
<FormattedMessage
id="xpack.idxMgmt.home.enrichPolicies.checkingPrivilegesErrorMessage"
defaultMessage="Error fetching user privileges from the server."
/>
}
error={apiError}
/>
);
}
return (
<WithPrivileges
privileges={ENRICH_POLICIES_REQUIRED_PRIVILEGES.map((privilege) => `cluster.${privilege}`)}
>
{({ isLoading, hasPrivileges, privilegesMissing }) => {
if (isLoading) {
return (
<PageLoading>
<FormattedMessage
id="xpack.idxMgmt.home.enrichPolicies.checkingPrivilegesDescription"
defaultMessage="Checking privileges…"
/>
</PageLoading>
);
}
if (!hasPrivileges) {
return (
<NotAuthorizedSection
dataTestSubj="enrichPoliciesInsuficientPrivileges"
title={
<FormattedMessage
id="xpack.idxMgmt.home.enrichPolicies.deniedPrivilegeTitle"
defaultMessage="Manage enrich privileges required"
/>
}
message={
<FormattedMessage
id="xpack.idxMgmt.home.enrichPolicies.deniedPrivilegeDescription"
defaultMessage="To use Enrich Policies, you must have the following cluster privileges: {missingPrivileges}."
values={{
missingPrivileges: privilegesMissing.cluster!.join(', '),
}}
/>
}
/>
);
}
return <>{children}</>;
}}
</WithPrivileges>
);
};

View file

@ -73,6 +73,8 @@ export function getIndexManagementDependencies({
}): AppDependencies {
const { docLinks, application, uiSettings, settings } = core;
const { url } = startDependencies.share;
const { monitor, manageEnrich, monitorEnrich } = application.capabilities.index_management;
return {
core: {
getUrlForApp: application.getUrlForApp,
@ -103,6 +105,11 @@ export function getIndexManagementDependencies({
kibanaVersion,
overlays: core.overlays,
canUseSyntheticSource,
privs: {
monitor: !!monitor,
manageEnrich: !!manageEnrich,
monitorEnrich: !!monitorEnrich,
},
};
}

View file

@ -14,12 +14,8 @@ import { breadcrumbService, IndexManagementBreadcrumb } from '../../services/bre
import { CreatePolicyWizard } from './create_policy_wizard';
import { CreatePolicyContextProvider } from './create_policy_context';
import {
EnrichPoliciesAuthProvider,
EnrichPoliciesWithPrivileges,
} from '../../components/enrich_policies';
const CreateView: React.FunctionComponent<RouteComponentProps> = () => {
export const EnrichPolicyCreate: React.FunctionComponent<RouteComponentProps> = () => {
useEffect(() => {
breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.enrichPoliciesCreate);
}, []);
@ -64,11 +60,3 @@ const CreateView: React.FunctionComponent<RouteComponentProps> = () => {
</CreatePolicyContextProvider>
);
};
export const EnrichPolicyCreate: React.FunctionComponent<RouteComponentProps> = (props) => (
<EnrichPoliciesAuthProvider>
<EnrichPoliciesWithPrivileges>
<CreateView {...props} />
</EnrichPoliciesWithPrivileges>
</EnrichPoliciesAuthProvider>
);

View file

@ -17,10 +17,6 @@ import { APP_WRAPPER_CLASS, useExecutionContext } from '../../../../shared_impor
import { useAppContext } from '../../../app_context';
import { useRedirectPath } from '../../../hooks/redirect_path';
import {
EnrichPoliciesAuthProvider,
EnrichPoliciesWithPrivileges,
} from '../../../components/enrich_policies';
import { breadcrumbService, IndexManagementBreadcrumb } from '../../../services/breadcrumbs';
import { documentationService } from '../../../services/documentation';
import { useLoadEnrichPolicies } from '../../../services/api';
@ -34,9 +30,13 @@ const getEnrichPolicyNameFromLocation = (location: Location) => {
return policy;
};
const ListView: React.FunctionComponent<RouteComponentProps> = ({ history, location }) => {
export const EnrichPoliciesList: React.FunctionComponent<RouteComponentProps> = ({
history,
location,
}) => {
const {
core: { executionContext },
privs,
} = useAppContext();
const redirectTo = useRedirectPath(history);
@ -79,7 +79,7 @@ const ListView: React.FunctionComponent<RouteComponentProps> = ({ history, locat
return <ErrorState error={error} resendRequest={reloadPolicies} />;
}
if (policies?.length === 0) {
if (privs.manageEnrich && policies?.length === 0) {
return <EmptyState />;
}
@ -151,11 +151,3 @@ const ListView: React.FunctionComponent<RouteComponentProps> = ({ history, locat
</div>
);
};
export const EnrichPoliciesList: React.FunctionComponent<RouteComponentProps> = (props) => (
<EnrichPoliciesAuthProvider>
<EnrichPoliciesWithPrivileges>
<ListView {...props} />
</EnrichPoliciesWithPrivileges>
</EnrichPoliciesAuthProvider>
);

View file

@ -33,43 +33,84 @@ export const PoliciesTable: FunctionComponent<Props> = ({
onDeletePolicyClick,
onExecutePolicyClick,
}) => {
const { history } = useAppContext();
const { history, privs } = useAppContext();
const renderToolsRight = () => {
return [
<EuiButton
key="reloadPolicies"
data-test-subj="reloadPoliciesButton"
iconType="refresh"
color="success"
onClick={onReloadClick}
>
<FormattedMessage
id="xpack.idxMgmt.enrichPolicies.table.reloadButton"
defaultMessage="Reload"
/>
</EuiButton>,
<EuiButton
key="createPolicy"
fill
iconType="plusInCircle"
{...reactRouterNavigate(history, '/enrich_policies/create')}
>
<FormattedMessage
id="xpack.idxMgmt.enrichPolicies.table.createPolicyButton"
defaultMessage="Create enrich policy"
/>
</EuiButton>,
];
};
const createBtn = (
<EuiButton
key="createPolicy"
fill
iconType="plusInCircle"
data-test-subj="createPolicyButton"
{...reactRouterNavigate(history, '/enrich_policies/create')}
>
<FormattedMessage
id="xpack.idxMgmt.enrichPolicies.table.createPolicyButton"
defaultMessage="Create enrich policy"
/>
</EuiButton>
);
const toolsRight = [
<EuiButton
key="reloadPolicies"
data-test-subj="reloadPoliciesButton"
iconType="refresh"
color="success"
onClick={onReloadClick}
>
<FormattedMessage
id="xpack.idxMgmt.enrichPolicies.table.reloadButton"
defaultMessage="Reload"
/>
</EuiButton>,
];
if (privs.manageEnrich) {
toolsRight.push(createBtn);
}
const search: EuiSearchBarProps = {
toolsRight: renderToolsRight(),
toolsRight,
box: {
incremental: true,
},
};
const actions: EuiBasicTableColumn<SerializedEnrichPolicy> = {
name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.actionsField', {
defaultMessage: 'Actions',
}),
actions: [
{
isPrimary: true,
name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeAction', {
defaultMessage: 'Execute',
}),
description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeDescription', {
defaultMessage: 'Execute this enrich policy',
}),
type: 'icon',
icon: 'play',
'data-test-subj': 'executePolicyButton',
onClick: ({ name }) => onExecutePolicyClick(name),
},
{
isPrimary: true,
name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteAction', {
defaultMessage: 'Delete',
}),
description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteDescription', {
defaultMessage: 'Delete this enrich policy',
}),
type: 'icon',
icon: 'trash',
color: 'danger',
'data-test-subj': 'deletePolicyButton',
onClick: ({ name }) => onDeletePolicyClick(name),
},
],
};
const columns: Array<EuiBasicTableColumn<SerializedEnrichPolicy>> = [
{
field: 'name',
@ -120,42 +161,12 @@ export const PoliciesTable: FunctionComponent<Props> = ({
truncateText: true,
render: (fields: string[]) => <span className="eui-textTruncate">{fields.join(', ')}</span>,
},
{
name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.actionsField', {
defaultMessage: 'Actions',
}),
actions: [
{
isPrimary: true,
name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeAction', {
defaultMessage: 'Execute',
}),
description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.executeDescription', {
defaultMessage: 'Execute this enrich policy',
}),
type: 'icon',
icon: 'play',
'data-test-subj': 'executePolicyButton',
onClick: ({ name }) => onExecutePolicyClick(name),
},
{
isPrimary: true,
name: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteAction', {
defaultMessage: 'Delete',
}),
description: i18n.translate('xpack.idxMgmt.enrichPolicies.table.deleteDescription', {
defaultMessage: 'Delete this enrich policy',
}),
type: 'icon',
icon: 'trash',
color: 'danger',
'data-test-subj': 'deletePolicyButton',
onClick: ({ name }) => onDeletePolicyClick(name),
},
],
},
];
if (privs.manageEnrich) {
columns.push(actions);
}
const { pageSize, sorting, onTableChange } = useEuiTablePersist<SerializedEnrichPolicy>({
tableId: 'enrichPolicies',
initialPageSize: 50,

View file

@ -41,6 +41,7 @@ export const IndexManagementHome: React.FunctionComponent<RouteComponentProps<Ma
}) => {
const {
plugins: { console: consolePlugin },
privs,
} = useAppContext();
const tabs = [
{
@ -74,7 +75,10 @@ export const IndexManagementHome: React.FunctionComponent<RouteComponentProps<Ma
/>
),
},
{
];
if (privs.monitorEnrich) {
tabs.push({
id: Section.EnrichPolicies,
name: (
<FormattedMessage
@ -82,8 +86,8 @@ export const IndexManagementHome: React.FunctionComponent<RouteComponentProps<Ma
defaultMessage="Enrich Policies"
/>
),
},
];
});
}
const onSectionChange = (newSection: Section) => {
history.push(`/${newSection}`);
@ -143,7 +147,9 @@ export const IndexManagementHome: React.FunctionComponent<RouteComponentProps<Ma
]}
component={ComponentTemplateList}
/>
<Route exact path={`/${Section.EnrichPolicies}`} component={EnrichPoliciesList} />
{privs.monitorEnrich && (
<Route exact path={`/${Section.EnrichPolicies}`} component={EnrichPoliciesList} />
)}
</Routes>
{consolePlugin?.EmbeddableConsole ? <consolePlugin.EmbeddableConsole /> : null}
</>

View file

@ -6,6 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { Subject } from 'rxjs';
import SemVer from 'semver/classes/semver';
import {
@ -14,6 +15,7 @@ import {
Plugin,
PluginInitializerContext,
ScopedHistory,
Capabilities,
} from '@kbn/core/public';
import {
IndexManagementPluginSetup,
@ -61,6 +63,8 @@ export class IndexMgmtUIPlugin
private canUseSyntheticSource: boolean = false;
private licensingSubscription?: Subscription;
private capabilities$ = new Subject<Capabilities>();
constructor(ctx: PluginInitializerContext) {
// Temporary hack to provide the service instances in module files in order to avoid a big refactor
// For the selectors we should expose them through app dependencies and read them from there on each container component.
@ -98,29 +102,34 @@ export class IndexMgmtUIPlugin
coreSetup: CoreSetup<StartDependencies>,
plugins: SetupDependencies
): IndexManagementPluginSetup {
if (this.config.isIndexManagementUiEnabled) {
const { fleet, usageCollection, management, cloud } = plugins;
const { fleet, usageCollection, management, cloud } = plugins;
management.sections.section.data.registerApp({
id: PLUGIN.id,
title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }),
order: 0,
mount: async (params) => {
const { mountManagementSection } = await import('./application/mount_management_section');
return mountManagementSection({
coreSetup,
usageCollection,
params,
extensionsService: this.extensionsService,
isFleetEnabled: Boolean(fleet),
kibanaVersion: this.kibanaVersion,
config: this.config,
cloud,
canUseSyntheticSource: this.canUseSyntheticSource,
});
},
});
}
this.capabilities$.subscribe((capabilities) => {
const { monitor, manageEnrich, monitorEnrich } = capabilities.index_management;
if (this.config.isIndexManagementUiEnabled && (monitor || manageEnrich || monitorEnrich)) {
management.sections.section.data.registerApp({
id: PLUGIN.id,
title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }),
order: 0,
mount: async (params) => {
const { mountManagementSection } = await import(
'./application/mount_management_section'
);
return mountManagementSection({
coreSetup,
usageCollection,
params,
extensionsService: this.extensionsService,
isFleetEnabled: Boolean(fleet),
kibanaVersion: this.kibanaVersion,
config: this.config,
cloud,
canUseSyntheticSource: this.canUseSyntheticSource,
});
},
});
}
});
this.locator = plugins.share.url.locators.create(
new IndexManagementLocatorDefinition({
@ -138,6 +147,8 @@ export class IndexMgmtUIPlugin
public start(coreStart: CoreStart, plugins: StartDependencies): IndexManagementPluginStart {
const { fleet, usageCollection, cloud, share, console, ml, licensing } = plugins;
this.capabilities$.next(coreStart.application.capabilities);
this.licensingSubscription = licensing?.license$.subscribe((next) => {
this.canUseSyntheticSource = next.hasAtLeast('enterprise');
});

View file

@ -37,15 +37,20 @@ export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup,
): IndexManagementPluginSetup {
features.registerElasticsearchFeature({
id: PLUGIN.id,
management: {
data: ['index_management'],
},
privileges: [
{
requiredClusterPrivileges: ['monitor_enrich'],
ui: ['monitorEnrich'],
},
{
requiredClusterPrivileges: ['manage_enrich'],
ui: ['manageEnrich'],
},
{
// manage_index_templates is also required, but we will disable specific parts of the
// UI if this privilege is missing.
requiredClusterPrivileges: ['monitor'],
ui: [],
ui: ['monitor'],
},
],
});

View file

@ -22629,10 +22629,6 @@
"xpack.idxMgmt.home.componentTemplates.list.loadingMessage": "Chargement des modèles de composants en cours…",
"xpack.idxMgmt.home.componentTemplatesTabTitle": "Modèles de composants",
"xpack.idxMgmt.home.dataStreamsTabTitle": "Flux de données",
"xpack.idxMgmt.home.enrichPolicies.checkingPrivilegesDescription": "Vérification des privilèges…",
"xpack.idxMgmt.home.enrichPolicies.checkingPrivilegesErrorMessage": "Erreur lors de la récupération des privilèges utilisateur depuis le serveur.",
"xpack.idxMgmt.home.enrichPolicies.deniedPrivilegeDescription": "Pour utiliser les Politiques d'enrichissement, vous devez disposer des privilèges de cluster suivants : {missingPrivileges}.",
"xpack.idxMgmt.home.enrichPolicies.deniedPrivilegeTitle": "Gestion des privilèges d'enrichissement requise",
"xpack.idxMgmt.home.enrichPoliciesTabTitle": "Politiques d'enrichissement",
"xpack.idxMgmt.home.idxMgmtDescription": "Mettez à jour vos index Elasticsearch individuellement ou par lots. {learnMoreLink}",
"xpack.idxMgmt.home.idxMgmtDocsLinkText": "Documentation sur la gestion des index",

View file

@ -22600,10 +22600,6 @@
"xpack.idxMgmt.home.componentTemplates.list.loadingMessage": "コンポーネントテンプレートを読み込んでいます…",
"xpack.idxMgmt.home.componentTemplatesTabTitle": "コンポーネントテンプレート",
"xpack.idxMgmt.home.dataStreamsTabTitle": "データストリーム",
"xpack.idxMgmt.home.enrichPolicies.checkingPrivilegesDescription": "権限を確認中…",
"xpack.idxMgmt.home.enrichPolicies.checkingPrivilegesErrorMessage": "サーバーからユーザー特権を取得中にエラーが発生。",
"xpack.idxMgmt.home.enrichPolicies.deniedPrivilegeDescription": "エンリッチポリシーを使用するには、以下のクラスター権限が必要です:{missingPrivileges}。",
"xpack.idxMgmt.home.enrichPolicies.deniedPrivilegeTitle": "必要なエンリッチ権限を管理",
"xpack.idxMgmt.home.enrichPoliciesTabTitle": "エンリッチポリシー",
"xpack.idxMgmt.home.idxMgmtDescription": "Elasticsearch インデックスを個々に、または一斉に更新します。{learnMoreLink}",
"xpack.idxMgmt.home.idxMgmtDocsLinkText": "インデックス管理ドキュメント",

View file

@ -22208,10 +22208,6 @@
"xpack.idxMgmt.home.componentTemplates.list.loadingMessage": "正在加载组件模板……",
"xpack.idxMgmt.home.componentTemplatesTabTitle": "组件模板",
"xpack.idxMgmt.home.dataStreamsTabTitle": "数据流",
"xpack.idxMgmt.home.enrichPolicies.checkingPrivilegesDescription": "正在检查权限……",
"xpack.idxMgmt.home.enrichPolicies.checkingPrivilegesErrorMessage": "从服务器获取用户权限时出错。",
"xpack.idxMgmt.home.enrichPolicies.deniedPrivilegeDescription": "要使用扩充策略,必须具有以下集群权限:{missingPrivileges}。",
"xpack.idxMgmt.home.enrichPolicies.deniedPrivilegeTitle": "管理所需的扩充权限",
"xpack.idxMgmt.home.enrichPoliciesTabTitle": "扩充策略",
"xpack.idxMgmt.home.idxMgmtDescription": "分别或批量更新您的 Elasticsearch 索引。{learnMoreLink}",
"xpack.idxMgmt.home.idxMgmtDocsLinkText": "索引管理文档",

View file

@ -10,8 +10,90 @@ import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(require.resolve('../../config.base.js'));
const baseConfig = functionalConfig.getAll();
baseConfig.security.roles.index_management_manage_index_templates = {
elasticsearch: {
cluster: ['manage_index_templates'],
indices: [
{
names: ['*'],
privileges: ['all'],
},
],
},
kibana: [
{
feature: {
advancedSettings: ['read'],
},
spaces: ['*'],
},
],
};
baseConfig.security.roles.index_management_monitor_only = {
elasticsearch: {
cluster: ['monitor'],
indices: [
{
names: ['*'],
privileges: ['all'],
},
],
},
kibana: [
{
feature: {
advancedSettings: ['read'],
},
spaces: ['*'],
},
],
};
baseConfig.security.roles.index_management_manage_enrich_only = {
elasticsearch: {
cluster: ['manage_enrich'],
indices: [
{
names: ['*'],
privileges: ['all'],
},
],
},
kibana: [
{
feature: {
advancedSettings: ['read'],
},
spaces: ['*'],
},
],
};
baseConfig.security.roles.index_management_monitor_enrich_only = {
elasticsearch: {
cluster: ['monitor_enrich'],
indices: [
{
names: ['*'],
privileges: ['all'],
},
],
},
kibana: [
{
feature: {
advancedSettings: ['read'],
},
spaces: ['*'],
},
],
};
return {
...functionalConfig.getAll(),
...baseConfig,
testFiles: [require.resolve('.')],
};
}

View file

@ -23,6 +23,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
describe('Enrich policies tab', function () {
before(async () => {
await log.debug('Creating required index and enrich policy');
try {
await es.indices.delete({ index: ENRICH_INDEX_NAME });
// eslint-disable-next-line no-empty
} catch (e) {}
try {
await es.indices.create({
index: ENRICH_INDEX_NAME,
@ -94,7 +100,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(await successToast.getVisibleText()).to.contain(`Executed ${ENRICH_POLICY_NAME}`);
});
it('read only access', async () => {
await security.testUser.setRoles(['index_management_monitor_enrich_only']);
await pageObjects.common.navigateToApp('indexManagement');
await pageObjects.indexManagement.changeTabs('enrich_policiesTab');
await pageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.missingOrFail('createPolicyButton');
await testSubjects.missingOrFail('deletePolicyButton');
});
it('can delete a policy', async () => {
await security.testUser.setRoles(['index_management_user']);
await pageObjects.common.navigateToApp('indexManagement');
// Navigate to the enrich policies tab
await pageObjects.indexManagement.changeTabs('enrich_policiesTab');
await pageObjects.header.waitUntilLoadingHasFinished();
// Since we disabled wait_for_completion in the server request, we dont know when
// a given policy will finish executing. Until that happens the policy cannot
// be deleted. 2s seems to be plenty enough to guarantee that, at least for this
@ -104,8 +125,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.indexManagement.clickDeleteEnrichPolicyAt(0);
await pageObjects.indexManagement.clickConfirmModalButton();
const successToast = await toasts.getElementByIndex(2);
const successToast = await toasts.getElementByIndex(1);
expect(await successToast.getVisibleText()).to.contain(`Deleted ${ENRICH_POLICY_NAME}`);
});
it('no access', async () => {
await security.testUser.setRoles(['index_management_monitor_only']);
await pageObjects.common.navigateToApp('indexManagement');
await testSubjects.missingOrFail('enrich_policiesTab');
});
});
};