From 4d4afa55b378deb09936259d826f05a7bfeead12 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Wed, 18 Sep 2024 10:55:00 -0700 Subject: [PATCH] [Cloud Security] User Name Misconfiguration Table and Preview Contextual Flyout (#192946) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR is the implementation of Misconfiguration Preview and Data table on user.name flyout in Alerts Page. Screenshot 2024-09-14 at 12 54 37 AM How to test: Pre req: In order to test this, you need to generate some fake alerts. This [repo](https://github.com/elastic/security-documents-generator) will help you do that 1. Generate Some Alerts 2. Use the Reindex API to get some Findings data in (change the host.name field to match the host.name from alerts generated if you want to test Findings table in the left panel flyout) 3. Turn on Risky Entity Score if you want to test if both Risk Contribution and Insights tabs shows up, follow this [guide](https://www.elastic.co/guide/en/security/current/turn-on-risk-engine.html) to turn on Risk Entity Score --- .../csp_details/insights_tab_csp.tsx | 41 +------------ ...isconfiguration_findings_details_table.tsx | 12 ++-- .../components/index.tsx | 18 +++++- .../misconfiguration_preview.test.tsx | 5 +- .../misconfiguration_preview.tsx | 61 ++++++++++++------- .../entity_details/host_right/content.tsx | 2 +- .../user_details_left/index.test.tsx | 42 +++++++++++++ .../user_details_left/index.tsx | 29 +++++++-- .../entity_details/user_details_left/tabs.tsx | 14 ++++- .../entity_details/user_right/content.tsx | 2 + .../entity_details/user_right/index.tsx | 32 +++++++++- .../security_solution/public/flyout/index.tsx | 4 +- 12 files changed, 175 insertions(+), 87 deletions(-) diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx index fa91a99c858a8..595aaf5127ca3 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx @@ -6,55 +6,16 @@ */ import React, { memo } from 'react'; -import { EuiButtonGroup, EuiSpacer } from '@elastic/eui'; -import type { EuiButtonGroupOptionProps } from '@elastic/eui/src/components/button/button_group/button_group'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useExpandableFlyoutState } from '@kbn/expandable-flyout'; +import { EuiSpacer } from '@elastic/eui'; import { MisconfigurationFindingsDetailsTable } from './misconfiguration_findings_details_table'; -enum InsightsTabCspTab { - MISCONFIGURATION = 'misconfigurationTabId', -} - -const insightsButtons: EuiButtonGroupOptionProps[] = [ - { - id: InsightsTabCspTab.MISCONFIGURATION, - label: ( - - ), - 'data-test-subj': 'misconfigurationTabDataTestId', - }, -]; - /** * Insights view displayed in the document details expandable flyout left section */ export const InsightsTabCsp = memo( ({ name, fieldName }: { name: string; fieldName: 'host.name' | 'user.name' }) => { - const panels = useExpandableFlyoutState(); - const activeInsightsId = panels.left?.path?.subTab ?? 'misconfigurationTabId'; - return ( <> - {}} - buttonSize="compressed" - isFullWidth - data-test-subj={'insightButtonGroupsTestId'} - /> diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx index 1362e0e42e6ba..ba413709d6cca 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx @@ -102,14 +102,14 @@ export const MisconfigurationFindingsDetailsTable = memo( const navToFindings = useNavigateFindings(); - const navToFindingsByHostName = (hostName: string) => { - navToFindings({ 'host.name': hostName }, ['rule.name']); - }; - const navToFindingsByRuleAndResourceId = (ruleId: string, resourceId: string) => { navToFindings({ 'rule.id': ruleId, 'resource.id': resourceId }); }; + const navToFindingsByName = (name: string, queryField: 'host.name' | 'user.name') => { + navToFindings({ [queryField]: name }, ['rule.name']); + }; + const columns: Array> = [ { field: 'rule', @@ -154,13 +154,13 @@ export const MisconfigurationFindingsDetailsTable = memo( { - navToFindingsByHostName(queryName); + navToFindingsByName(queryName, fieldName); }} > {i18n.translate( 'xpack.securitySolution.flyout.left.insights.misconfigurations.tableTitle', { - defaultMessage: 'Misconfigurations', + defaultMessage: 'Misconfigurations ', } )} diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx index 3058300036565..6045a8b8c9a5e 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx @@ -13,7 +13,15 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api'; import { MisconfigurationsPreview } from './misconfiguration/misconfiguration_preview'; -export const EntityInsight = ({ hostName }: { hostName: string }) => { +export const EntityInsight = ({ + name, + fieldName, + isPreviewMode, +}: { + name: string; + fieldName: 'host.name' | 'user.name'; + isPreviewMode?: boolean; +}) => { const { euiTheme } = useEuiTheme(); const getSetupStatus = useCspSetupStatusApi(); const hasMisconfigurationFindings = getSetupStatus.data?.hasMisconfigurationsFindings; @@ -22,7 +30,6 @@ export const EntityInsight = ({ hostName }: { hostName: string }) => { <> {hasMisconfigurationFindings && ( <> - ({ hostName }: { hostName: string }) => { } > - + + )} diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx index 1c4c2adb60218..2e10d481b9934 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx @@ -11,8 +11,9 @@ import { render } from '@testing-library/react'; import React from 'react'; import { MisconfigurationsPreview } from './misconfiguration_preview'; -const mockProps = { - hostName: 'testContextID', +const mockProps: { name: string; fieldName: 'host.name' | 'user.name' } = { + name: 'testContextID', + fieldName: 'host.name', }; describe('MisconfigurationsPreview', () => { diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx index f6ba0389f752a..e6c3950e81583 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx @@ -17,10 +17,12 @@ import { i18n } from '@kbn/i18n'; import { ExpandablePanel } from '@kbn/security-solution-common'; import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { UserDetailsPanelKey } from '../../../flyout/entity_details/user_details_left'; import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left'; import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score'; import { RiskScoreEntity } from '../../../../common/entity_analytics/risk_engine'; -import { buildHostNamesFilter } from '../../../../common/search_strategy'; +import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy'; +import { buildHostNamesFilter, buildUserNamesFilter } from '../../../../common/search_strategy'; const FIRST_RECORD_PAGINATION = { cursorStart: 0, @@ -120,46 +122,63 @@ const MisconfigurationPreviewScore = ({ ); }; -export const MisconfigurationsPreview = ({ hostName }: { hostName: string }) => { +export const MisconfigurationsPreview = ({ + name, + fieldName, + isPreviewMode, +}: { + name: string; + fieldName: 'host.name' | 'user.name'; + isPreviewMode?: boolean; +}) => { const { data } = useMisconfigurationPreview({ - query: buildEntityFlyoutPreviewQuery('host.name', hostName), + query: buildEntityFlyoutPreviewQuery(fieldName, name), sort: [], enabled: true, pageSize: 1, }); - + const isUsingHostName = fieldName === 'host.name'; const passedFindings = data?.count.passed || 0; const failedFindings = data?.count.failed || 0; const { euiTheme } = useEuiTheme(); const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; - const hostNameFilterQuery = useMemo( - () => (hostName ? buildHostNamesFilter([hostName]) : undefined), - [hostName] + + const buildFilterQuery = useMemo( + () => (isUsingHostName ? buildHostNamesFilter([name]) : buildUserNamesFilter([name])), + [isUsingHostName, name] ); const riskScoreState = useRiskScore({ - riskEntity: RiskScoreEntity.host, - filterQuery: hostNameFilterQuery, + riskEntity: isUsingHostName ? RiskScoreEntity.host : RiskScoreEntity.user, + filterQuery: buildFilterQuery, onlyLatest: false, pagination: FIRST_RECORD_PAGINATION, }); const { data: hostRisk } = riskScoreState; - const hostRiskData = hostRisk && hostRisk.length > 0 ? hostRisk[0] : undefined; - const isRiskScoreExist = !!hostRiskData?.host.risk; + const riskData = hostRisk?.[0]; + const isRiskScoreExist = isUsingHostName + ? !!(riskData as HostRiskScore)?.host.risk + : !!(riskData as UserRiskScore)?.user.risk; const { openLeftPanel } = useExpandableFlyoutApi(); - const isPreviewMode = false; const goToEntityInsightTab = useCallback(() => { openLeftPanel({ - id: HostDetailsPanelKey, - params: { - name: hostName, - isRiskScoreExist, - hasMisconfigurationFindings, - path: { tab: 'csp_insights' }, - }, + id: isUsingHostName ? HostDetailsPanelKey : UserDetailsPanelKey, + params: isUsingHostName + ? { + name, + isRiskScoreExist, + hasMisconfigurationFindings, + path: { tab: 'csp_insights' }, + } + : { + user: { name }, + isRiskScoreExist, + hasMisconfigurationFindings, + path: { tab: 'csp_insights' }, + }, }); - }, [hasMisconfigurationFindings, hostName, isRiskScoreExist, openLeftPanel]); + }, [hasMisconfigurationFindings, isRiskScoreExist, isUsingHostName, name, openLeftPanel]); const link = useMemo( () => !isPreviewMode @@ -178,7 +197,7 @@ export const MisconfigurationsPreview = ({ hostName }: { hostName: string }) => return ( + - ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.test.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.test.tsx index bdff465e0b982..9c4b9938d6daa 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.test.tsx @@ -51,4 +51,46 @@ describe('LeftPanel', () => { expect(tabElement).not.toBeInTheDocument(); }); + + it("doesn't render insights panel when there no misconfiguration findings", () => { + const { queryByText } = render( + , + { + wrapper: TestProviders, + } + ); + + const tabElement = queryByText('Insights'); + + expect(tabElement).not.toBeInTheDocument(); + }); + + it('render insights panel when there are misconfiguration findings', () => { + const { queryByText } = render( + , + { + wrapper: TestProviders, + } + ); + + const tabElement = queryByText('Insights'); + + expect(tabElement).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx index a04bd739eb299..ae3e99cc17cfe 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx @@ -28,6 +28,7 @@ export interface UserDetailsPanelProps extends Record { user: UserParam; path?: PanelPath; scopeId: string; + hasMisconfigurationFindings?: boolean; } export interface UserDetailsExpandableFlyoutProps extends FlyoutPanelProps { key: 'user_details'; @@ -40,10 +41,24 @@ export const UserDetailsPanel = ({ user, path, scopeId, + hasMisconfigurationFindings, }: UserDetailsPanelProps) => { const managedUser = useManagedUser(user.name, user.email); - const tabs = useTabs(managedUser.data, user.name, isRiskScoreExist, scopeId); - const { selectedTabId, setSelectedTabId } = useSelectedTab(isRiskScoreExist, user, tabs, path); + const tabs = useTabs( + managedUser.data, + user.name, + isRiskScoreExist, + scopeId, + hasMisconfigurationFindings + ); + + const { selectedTabId, setSelectedTabId } = useSelectedTab( + isRiskScoreExist, + user, + tabs, + path, + hasMisconfigurationFindings + ); if (managedUser.isLoading) return ; @@ -67,7 +82,8 @@ const useSelectedTab = ( isRiskScoreExist: boolean, user: UserParam, tabs: LeftPanelTabsType, - path: PanelPath | undefined + path: PanelPath | undefined, + hasMisconfigurationFindings?: boolean ) => { const { openLeftPanel } = useExpandableFlyoutApi(); @@ -81,12 +97,13 @@ const useSelectedTab = ( const setSelectedTabId = (tabId: EntityDetailsLeftPanelTab) => { openLeftPanel({ id: UserDetailsPanelKey, - path: { - tab: tabId, - }, params: { user, isRiskScoreExist, + hasMisconfigurationFindings, + path: { + tab: tabId, + }, }, }); }; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx index 3a6814a28e62c..6f27b054759f2 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx @@ -8,7 +8,10 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { getRiskInputTab } from '../../../entity_analytics/components/entity_details_flyout'; +import { + getInsightsInputTab, + getRiskInputTab, +} from '../../../entity_analytics/components/entity_details_flyout'; import { UserAssetTableType } from '../../../explore/users/store/model'; import { ManagedUserDatasetKey } from '../../../../common/search_strategy/security_solution/users/managed_details'; import type { @@ -26,7 +29,8 @@ export const useTabs = ( managedUser: ManagedUserHits, name: string, isRiskScoreExist: boolean, - scopeId: string + scopeId: string, + hasMisconfigurationFindings?: boolean ): LeftPanelTabsType => useMemo(() => { const tabs: LeftPanelTabsType = []; @@ -51,8 +55,12 @@ export const useTabs = ( tabs.push(getEntraTab(entraManagedUser)); } + if (hasMisconfigurationFindings) { + tabs.push(getInsightsInputTab({ name, fieldName: 'user.name' })); + } + return tabs; - }, [isRiskScoreExist, managedUser, name, scopeId]); + }, [hasMisconfigurationFindings, isRiskScoreExist, managedUser, name, scopeId]); const getOktaTab = (oktaManagedUser: ManagedUserHit) => ({ id: EntityDetailsLeftPanelTab.OKTA, diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx index 26945a12f8bd6..42b281d0c8d2b 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx @@ -23,6 +23,7 @@ import { ObservedEntity } from '../shared/components/observed_entity'; import type { ObservedEntityData } from '../shared/components/observed_entity/types'; import { useObservedUserItems } from './hooks/use_observed_user_items'; import type { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header'; +import { EntityInsight } from '../../../cloud_security_posture/components'; interface UserPanelContentProps { userName: string; @@ -72,6 +73,7 @@ export const UserPanelContent = ({ entity={{ name: userName, type: 'user' }} onChange={onAssetCriticalityChange} /> + 0 || failedFindings > 0; + useQueryInspector({ deleteQuery, inspect, @@ -119,11 +134,20 @@ export const UserPanel = ({ name: userName, email, }, + path: tab ? { tab } : undefined, + hasMisconfigurationFindings, }, - path: tab ? { tab } : undefined, }); }, - [telemetry, openLeftPanel, userRiskData?.user?.risk, userName, email, scopeId] + [ + telemetry, + openLeftPanel, + userRiskData?.user?.risk, + scopeId, + userName, + email, + hasMisconfigurationFindings, + ] ); const openPanelFirstTab = useCallback(() => openPanelTab(), [openPanelTab]); @@ -156,7 +180,9 @@ export const UserPanel = ({ return ( <> ( - + ), }, {