Skip to content

Commit

Permalink
[8.x] [Logs ML] Check permissions before granting access to Logs ML p…
Browse files Browse the repository at this point in the history
…ages (#195278) (#195759)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Logs ML] Check permissions before granting access to Logs ML pages
(#195278)](#195278)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Marco Antonio
Ghiani","email":"marcoantonio.ghiani01@gmail.com"},"sourceCommit":{"committedDate":"2024-10-10T12:33:18Z","message":"[Logs
ML] Check permissions before granting access to Logs ML pages
(#195278)\n\n## 📓 Summary\r\n\r\nCloses #191206 \r\n\r\nThis work fixes
issues while accessing the Logs Anomalies and Logs\r\nCategories pages
due to a lack of user privileges.\r\n\r\nThe privileges were correctly
handled until\r\nhttps://github.com//pull/168234 was
merged, which\r\nintroduced a call to retrieve ml formats information
higher in the React\r\nhierarchy before the privileges could be asserted
for the logged user.\r\nThis was resulting in the call failing and
letting the user stack in\r\nloading states or erroneous error
pages.\r\n\r\nThese changes lift the license + ML read privileges checks
higher in the\r\nhierarchy so we can display the right prompts before
calling the ml\r\nformats API, which will resolve correctly if the user
has the right\r\nprivileges.\r\n\r\n### User without valid
license\r\n\r\n<img width=\"3008\" alt=\"Screenshot 2024-10-07 at 17 01
17\"\r\nsrc=\"https://github.com/user-attachments/assets/bf6478ce-b007-4f15-9538-c7959c497e8a\">\r\n\r\n###
User with a valid license (or Trial), but no ML privileges\r\n\r\n<img
width=\"3003\" alt=\"Screenshot 2024-10-07 at 17 03
48\"\r\nsrc=\"https://github.com/user-attachments/assets/c5a82159-b4e8-4f22-9531-23d5e5a9377f\">\r\n\r\n###
User with a valid license (or Trial) and only Read ML
privileges\r\n\r\n<img width=\"3003\" alt=\"Screenshot 2024-10-07 at 17
04
21\"\r\nsrc=\"https://github.com/user-attachments/assets/990f4695-e07e-46a2-9214-d0de3628caf7\">\r\n\r\n###
User with a valid license (or Trial) and All ML privileges, which\r\nare
the requirements to work with ML Logs features\r\n\r\n<img
width=\"3000\" alt=\"Screenshot 2024-10-07 at 17 04
52\"\r\nsrc=\"https://github.com/user-attachments/assets/c9b4d832-d3c8-4337-9e17-8a220e7be084\">\r\n\r\n---------\r\n\r\nCo-authored-by:
Marco Antonio Ghiani
<marcoantonio.ghiani@elastic.co>","sha":"e0e4ec10e3c329f933bed0a01dbeaecdf79cfa99","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Logs
UI","release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-logs"],"title":"[Logs
ML] Check permissions before granting access to Logs ML
pages","number":195278,"url":"https://github.com/elastic/kibana/pull/195278","mergeCommit":{"message":"[Logs
ML] Check permissions before granting access to Logs ML pages
(#195278)\n\n## 📓 Summary\r\n\r\nCloses #191206 \r\n\r\nThis work fixes
issues while accessing the Logs Anomalies and Logs\r\nCategories pages
due to a lack of user privileges.\r\n\r\nThe privileges were correctly
handled until\r\nhttps://github.com//pull/168234 was
merged, which\r\nintroduced a call to retrieve ml formats information
higher in the React\r\nhierarchy before the privileges could be asserted
for the logged user.\r\nThis was resulting in the call failing and
letting the user stack in\r\nloading states or erroneous error
pages.\r\n\r\nThese changes lift the license + ML read privileges checks
higher in the\r\nhierarchy so we can display the right prompts before
calling the ml\r\nformats API, which will resolve correctly if the user
has the right\r\nprivileges.\r\n\r\n### User without valid
license\r\n\r\n<img width=\"3008\" alt=\"Screenshot 2024-10-07 at 17 01
17\"\r\nsrc=\"https://github.com/user-attachments/assets/bf6478ce-b007-4f15-9538-c7959c497e8a\">\r\n\r\n###
User with a valid license (or Trial), but no ML privileges\r\n\r\n<img
width=\"3003\" alt=\"Screenshot 2024-10-07 at 17 03
48\"\r\nsrc=\"https://github.com/user-attachments/assets/c5a82159-b4e8-4f22-9531-23d5e5a9377f\">\r\n\r\n###
User with a valid license (or Trial) and only Read ML
privileges\r\n\r\n<img width=\"3003\" alt=\"Screenshot 2024-10-07 at 17
04
21\"\r\nsrc=\"https://github.com/user-attachments/assets/990f4695-e07e-46a2-9214-d0de3628caf7\">\r\n\r\n###
User with a valid license (or Trial) and All ML privileges, which\r\nare
the requirements to work with ML Logs features\r\n\r\n<img
width=\"3000\" alt=\"Screenshot 2024-10-07 at 17 04
52\"\r\nsrc=\"https://github.com/user-attachments/assets/c9b4d832-d3c8-4337-9e17-8a220e7be084\">\r\n\r\n---------\r\n\r\nCo-authored-by:
Marco Antonio Ghiani
<marcoantonio.ghiani@elastic.co>","sha":"e0e4ec10e3c329f933bed0a01dbeaecdf79cfa99"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195278","number":195278,"mergeCommit":{"message":"[Logs
ML] Check permissions before granting access to Logs ML pages
(#195278)\n\n## 📓 Summary\r\n\r\nCloses #191206 \r\n\r\nThis work fixes
issues while accessing the Logs Anomalies and Logs\r\nCategories pages
due to a lack of user privileges.\r\n\r\nThe privileges were correctly
handled until\r\nhttps://github.com//pull/168234 was
merged, which\r\nintroduced a call to retrieve ml formats information
higher in the React\r\nhierarchy before the privileges could be asserted
for the logged user.\r\nThis was resulting in the call failing and
letting the user stack in\r\nloading states or erroneous error
pages.\r\n\r\nThese changes lift the license + ML read privileges checks
higher in the\r\nhierarchy so we can display the right prompts before
calling the ml\r\nformats API, which will resolve correctly if the user
has the right\r\nprivileges.\r\n\r\n### User without valid
license\r\n\r\n<img width=\"3008\" alt=\"Screenshot 2024-10-07 at 17 01
17\"\r\nsrc=\"https://github.com/user-attachments/assets/bf6478ce-b007-4f15-9538-c7959c497e8a\">\r\n\r\n###
User with a valid license (or Trial), but no ML privileges\r\n\r\n<img
width=\"3003\" alt=\"Screenshot 2024-10-07 at 17 03
48\"\r\nsrc=\"https://github.com/user-attachments/assets/c5a82159-b4e8-4f22-9531-23d5e5a9377f\">\r\n\r\n###
User with a valid license (or Trial) and only Read ML
privileges\r\n\r\n<img width=\"3003\" alt=\"Screenshot 2024-10-07 at 17
04
21\"\r\nsrc=\"https://github.com/user-attachments/assets/990f4695-e07e-46a2-9214-d0de3628caf7\">\r\n\r\n###
User with a valid license (or Trial) and All ML privileges, which\r\nare
the requirements to work with ML Logs features\r\n\r\n<img
width=\"3000\" alt=\"Screenshot 2024-10-07 at 17 04
52\"\r\nsrc=\"https://github.com/user-attachments/assets/c9b4d832-d3c8-4337-9e17-8a220e7be084\">\r\n\r\n---------\r\n\r\nCo-authored-by:
Marco Antonio Ghiani
<marcoantonio.ghiani@elastic.co>","sha":"e0e4ec10e3c329f933bed0a01dbeaecdf79cfa99"}}]}]
BACKPORT-->

Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani01@gmail.com>
  • Loading branch information
kibanamachine and tonyghiani authored Oct 10, 2024
1 parent 79bd103 commit c5bc4dd
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { UserManagementLink } from './user_management_link';

export const MissingResultsPrivilegesPrompt: React.FunctionComponent = () => (
<EmptyPrompt
data-test-subj="logsMissingMLReadPrivileges"
title={<h2>{missingMlPrivilegesTitle}</h2>}
body={<p>{missingMlResultsPrivilegesDescription}</p>}
actions={<UserManagementLink />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { UserManagementLink } from './user_management_link';

export const MissingSetupPrivilegesPrompt: React.FunctionComponent = () => (
<EmptyPrompt
data-test-subj="logsMissingMLAllPrivileges"
title={<h2>{missingMlPrivilegesTitle}</h2>}
body={<p>{missingMlSetupPrivilegesDescription}</p>}
actions={<UserManagementLink />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@

import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
import { MissingResultsPrivilegesPrompt } from '../../../components/logging/log_analysis_setup';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { SubscriptionSplashPage } from '../../../components/subscription_splash_content';
import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs';
import { LogEntryCategoriesPageContent } from './page_content';
import { CategoriesPageTemplate, LogEntryCategoriesPageContent } from './page_content';
import { LogEntryCategoriesPageProviders } from './page_providers';
import { logCategoriesTitle } from '../../../translations';
import { LogMlJobIdFormatsShimProvider } from '../shared/use_log_ml_job_id_formats_shim';
Expand All @@ -20,6 +23,28 @@ export const LogEntryCategoriesPage = () => {
},
]);

const { hasLogAnalysisReadCapabilities, hasLogAnalysisCapabilites } =
useLogAnalysisCapabilitiesContext();

if (!hasLogAnalysisCapabilites) {
return (
<SubscriptionSplashPage
data-test-subj="logsLogEntryCategoriesPage"
pageHeader={{
pageTitle: logCategoriesTitle,
}}
/>
);
}

if (!hasLogAnalysisReadCapabilities) {
return (
<CategoriesPageTemplate isEmptyState={true}>
<MissingResultsPrivilegesPrompt />
</CategoriesPageTemplate>
);
}

return (
<EuiErrorBoundary>
<LogMlJobIdFormatsShimProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ import { isJobStatusWithResults, logEntryCategoriesJobType } from '../../../../c
import { LoadingPage } from '../../../components/loading_page';
import {
LogAnalysisSetupStatusUnknownPrompt,
MissingResultsPrivilegesPrompt,
MissingSetupPrivilegesPrompt,
} from '../../../components/logging/log_analysis_setup';
import {
LogAnalysisSetupFlyout,
useLogAnalysisSetupFlyoutStateContext,
} from '../../../components/logging/log_analysis_setup/setup_flyout';
import { SubscriptionSplashPage } from '../../../components/subscription_splash_content';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { LogsPageTemplate } from '../shared/page_template';
Expand All @@ -33,11 +31,8 @@ const logCategoriesTitle = i18n.translate('xpack.infra.logs.logCategoriesTitle',
});

export const LogEntryCategoriesPageContent = () => {
const {
hasLogAnalysisCapabilites,
hasLogAnalysisReadCapabilities,
hasLogAnalysisSetupCapabilities,
} = useLogAnalysisCapabilitiesContext();
const { hasLogAnalysisReadCapabilities, hasLogAnalysisSetupCapabilities } =
useLogAnalysisCapabilitiesContext();

const { fetchJobStatus, setupStatus, jobStatus } = useLogEntryCategoriesModuleContext();

Expand All @@ -55,22 +50,7 @@ export const LogEntryCategoriesPageContent = () => {

const { idFormats } = useLogMlJobIdFormatsShimContext();

if (!hasLogAnalysisCapabilites) {
return (
<SubscriptionSplashPage
data-test-subj="logsLogEntryCategoriesPage"
pageHeader={{
pageTitle: logCategoriesTitle,
}}
/>
);
} else if (!hasLogAnalysisReadCapabilities) {
return (
<CategoriesPageTemplate isEmptyState={true}>
<MissingResultsPrivilegesPrompt />
</CategoriesPageTemplate>
);
} else if (setupStatus.type === 'initializing') {
if (setupStatus.type === 'initializing') {
return (
<LoadingPage
message={i18n.translate('xpack.infra.logs.logEntryCategories.jobStatusLoadingMessage', {
Expand Down Expand Up @@ -115,7 +95,7 @@ export const LogEntryCategoriesPageContent = () => {

const allowedSetupModules = ['logs_ui_categories' as const];

const CategoriesPageTemplate: React.FC<LazyObservabilityPageTemplateProps> = ({
export const CategoriesPageTemplate: React.FC<LazyObservabilityPageTemplateProps> = ({
children,
...rest
}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
import { LogEntryRatePageContent } from './page_content';
import { SubscriptionSplashPage } from '../../../components/subscription_splash_content';
import { MissingResultsPrivilegesPrompt } from '../../../components/logging/log_analysis_setup';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { AnomaliesPageTemplate, LogEntryRatePageContent } from './page_content';
import { LogEntryRatePageProviders } from './page_providers';
import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs';
import { logsAnomaliesTitle } from '../../../translations';
Expand All @@ -19,6 +22,29 @@ export const LogEntryRatePage = () => {
text: logsAnomaliesTitle,
},
]);

const { hasLogAnalysisReadCapabilities, hasLogAnalysisCapabilites } =
useLogAnalysisCapabilitiesContext();

if (!hasLogAnalysisCapabilites) {
return (
<SubscriptionSplashPage
data-test-subj="logsLogEntryRatePage"
pageHeader={{
pageTitle: logsAnomaliesTitle,
}}
/>
);
}

if (!hasLogAnalysisReadCapabilities) {
return (
<AnomaliesPageTemplate isEmptyState={true}>
<MissingResultsPrivilegesPrompt />
</AnomaliesPageTemplate>
);
}

return (
<EuiErrorBoundary>
<LogMlJobIdFormatsShimProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ import {
import { LoadingPage } from '../../../components/loading_page';
import {
LogAnalysisSetupStatusUnknownPrompt,
MissingResultsPrivilegesPrompt,
MissingSetupPrivilegesPrompt,
} from '../../../components/logging/log_analysis_setup';
import {
LogAnalysisSetupFlyout,
useLogAnalysisSetupFlyoutStateContext,
} from '../../../components/logging/log_analysis_setup/setup_flyout';
import { SubscriptionSplashPage } from '../../../components/subscription_splash_content';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
Expand All @@ -41,11 +39,8 @@ const logsAnomaliesTitle = i18n.translate('xpack.infra.logs.anomaliesPageTitle',
});

export const LogEntryRatePageContent = memo(() => {
const {
hasLogAnalysisCapabilites,
hasLogAnalysisReadCapabilities,
hasLogAnalysisSetupCapabilities,
} = useLogAnalysisCapabilitiesContext();
const { hasLogAnalysisReadCapabilities, hasLogAnalysisSetupCapabilities } =
useLogAnalysisCapabilitiesContext();

const {
fetchJobStatus: fetchLogEntryCategoriesJobStatus,
Expand Down Expand Up @@ -96,22 +91,7 @@ export const LogEntryRatePageContent = memo(() => {

const { idFormats } = useLogMlJobIdFormatsShimContext();

if (!hasLogAnalysisCapabilites) {
return (
<SubscriptionSplashPage
data-test-subj="logsLogEntryRatePage"
pageHeader={{
pageTitle: logsAnomaliesTitle,
}}
/>
);
} else if (!hasLogAnalysisReadCapabilities) {
return (
<AnomaliesPageTemplate isEmptyState={true}>
<MissingResultsPrivilegesPrompt />
</AnomaliesPageTemplate>
);
} else if (
if (
logEntryCategoriesSetupStatus.type === 'initializing' ||
logEntryRateSetupStatus.type === 'initializing'
) {
Expand Down Expand Up @@ -159,7 +139,7 @@ export const LogEntryRatePageContent = memo(() => {
}
});

const AnomaliesPageTemplate: React.FC<LazyObservabilityPageTemplateProps> = ({
export const AnomaliesPageTemplate: React.FC<LazyObservabilityPageTemplateProps> = ({
children,
...rest
}) => {
Expand Down
82 changes: 75 additions & 7 deletions x-pack/test/functional/apps/infra/logs/log_entry_categories_tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,54 @@ import expect from '@kbn/expect';

import { FtrProviderContext } from '../../../ftr_provider_context';

export default ({ getService }: FtrProviderContext) => {
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const PageObjects = getPageObjects(['security']);
const esArchiver = getService('esArchiver');
const logsUi = getService('logsUi');
const retry = getService('retry');
const security = getService('security');

describe('Log Entry Categories Tab', function () {
this.tags('includeFirefox');

const loginWithMLPrivileges = async (privileges: Record<string, string[]>) => {
await security.role.create('global_logs_role', {
elasticsearch: {
cluster: ['all'],
indices: [{ names: ['*'], privileges: ['read', 'view_index_metadata'] }],
},
kibana: [
{
feature: {
logs: ['read'],
...privileges,
},
spaces: ['*'],
},
],
});

await security.user.create('global_logs_read_user', {
password: 'global_logs_read_user-password',
roles: ['global_logs_role'],
full_name: 'logs test user',
});

await PageObjects.security.forceLogout();

await PageObjects.security.login('global_logs_read_user', 'global_logs_read_user-password', {
expectSpaceSelector: false,
});
};

const logoutAndDeleteUser = async () => {
await PageObjects.security.forceLogout();
await Promise.all([
security.role.delete('global_logs_role'),
security.user.delete('global_logs_read_user'),
]);
};

describe('with a trial license', () => {
it('Shows no data page when indices do not exist', async () => {
await logsUi.logEntryCategoriesPage.navigateTo();
Expand All @@ -26,14 +66,42 @@ export default ({ getService }: FtrProviderContext) => {
});
});

it('shows setup page when indices exist', async () => {
await esArchiver.load('x-pack/test/functional/es_archives/infra/simple_logs');
await logsUi.logEntryCategoriesPage.navigateTo();
describe('when indices exists', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs');
});

await retry.try(async () => {
expect(await logsUi.logEntryCategoriesPage.getSetupScreen()).to.be.ok();
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs');
});

it('shows setup page when indices exist', async () => {
await logsUi.logEntryCategoriesPage.navigateTo();

await retry.try(async () => {
expect(await logsUi.logEntryCategoriesPage.getSetupScreen()).to.be.ok();
});
});

it('shows required ml read privileges prompt when the user has not any ml privileges', async () => {
await loginWithMLPrivileges({});
await logsUi.logEntryCategoriesPage.navigateTo();

await retry.try(async () => {
expect(await logsUi.logEntryCategoriesPage.getNoMlReadPrivilegesPrompt()).to.be.ok();
});
await logoutAndDeleteUser();
});

it('shows required ml all privileges prompt when the user has only ml read privileges', async () => {
await loginWithMLPrivileges({ ml: ['read'] });
await logsUi.logEntryCategoriesPage.navigateTo();

await retry.try(async () => {
expect(await logsUi.logEntryCategoriesPage.getNoMlAllPrivilegesPrompt()).to.be.ok();
});
await logoutAndDeleteUser();
});
await esArchiver.unload('x-pack/test/functional/es_archives/infra/simple_logs');
});
});
});
Expand Down
Loading

0 comments on commit c5bc4dd

Please sign in to comment.