mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[EA][PrivMon] Tile Visualisations on Privileged User Monitoring Dashboard (#223092)
## Summary The PR adds code for displaying the visualisations for key insights panel of Privileged user monitoring dashboard. It comprises of 6 tiles. 1. Active Privileged Users 2. Alerts Triggered 3. Anomalies Detected 4. Granted Rights 5. Account Switches 6. Authentications All the tiles have been created using the Lens visualisation for ease of use and also to streamline visualisations across the security solution. Screenshots : Privileged User Monitoring Dashboard  Lens visualisation for a tile :  ### Adding Data for desk testing : 1. On the `main` branch of "The Data Yeeter" (https://github.com/elastic/security-documents-generator/), run `yarn start privileged_access_detection`. This primarily adds data for anomalies. 2. Then on the same `main` branch, run `yarn start privileged_user_monitoring`. This will add data for the privileged user index 3. On the dev console execute the following : ``` POST kbn:/api/entity_analytics/monitoring/engine/init POST kbn:/api/entity_analytics/monitoring/users { "user": {"name": "john.smith"} } POST kbn:/api/entity_analytics/monitoring/users { "user": {"name": "stacy_armstrong"} } POST kbn:/api/entity_analytics/monitoring/users { "user": {"name": "john_smith"} } POST kbn:/api/entity_analytics/monitoring/users { "user": {"name": "randy.carlisle"} } POST kbn:/api/entity_analytics/monitoring/users { "user": {"name": "root"} } ``` ### Testing Steps : 1. Enable privilegedUserMonitoring feature flag. 2. Navigate to entity_analytics/privileged_user_monitoring page 3. Click on "Go to Dashboards" on the top left corner. 4. You will be able to see the tiles with name and number. 5. Click on the three dots when cursor is hovered over the tile and click on Inspect to check the query executed, click on More -> Open in Lens to check if the tile opens up fine in the lens visualiastion link. 6. Check the data view in the lens visualisation. For anomalies, the data view should be `.ml-anomalies-*`. For other it would either be `.alerts-*` or `logs-*`. ### Not part of this PR : 1. The trendline on the tile did not work as i am yet to figure out a way, if it exists, to show a trendline with ES|QL query as it works fine with KQL queries but similar lens attribites do not function. 2. Load testing where the local environment does not have much data to show. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: jaredburgettelastic <jared.burgett@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
fa7f9c7030
commit
0fad96fa37
23 changed files with 646 additions and 4 deletions
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { getAccountSwitchesEsqlCount } from './esql_query';
|
||||
import { KeyInsightsTile } from '../common/key_insights_tile';
|
||||
|
||||
export const AccountSwitchesTile: React.FC<{ spaceId: string; sourcerDataView: DataViewSpec }> = ({
|
||||
spaceId,
|
||||
sourcerDataView,
|
||||
}) => {
|
||||
return (
|
||||
<KeyInsightsTile
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.accountSwitches.title"
|
||||
defaultMessage="Account Switches"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.accountSwitches.label"
|
||||
defaultMessage="Account Switches"
|
||||
/>
|
||||
}
|
||||
getEsqlQuery={(namespace) => getAccountSwitchesEsqlCount(namespace, sourcerDataView)}
|
||||
id="privileged-user-monitoring-account-switches"
|
||||
spaceId={spaceId}
|
||||
inspectTitle={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.accountSwitches.inspectTitle"
|
||||
defaultMessage="Account switches"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { getAccountSwitchesEsqlSource } from '../../../queries/account_switches_esql_query';
|
||||
|
||||
export const getAccountSwitchesEsqlCount = (namespace: string, sourcerDataView: DataViewSpec) => {
|
||||
const indexPattern = sourcerDataView?.title ?? '';
|
||||
const fields = sourcerDataView?.fields ?? {};
|
||||
return `${getAccountSwitchesEsqlSource(namespace, indexPattern, fields)}
|
||||
| STATS COUNT(*)`;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 * from './account_switches_tile';
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { getActivePrivilegedUsersEsqlCount } from './esql_query';
|
||||
import { KeyInsightsTile } from '../common/key_insights_tile';
|
||||
|
||||
export const ActivePrivilegedUsersTile: React.FC<{
|
||||
spaceId: string;
|
||||
sourcerDataView: DataViewSpec;
|
||||
}> = ({ spaceId, sourcerDataView }) => {
|
||||
return (
|
||||
<KeyInsightsTile
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.activePrivilegedUsers.title"
|
||||
defaultMessage="Active Privileged Users"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.activePrivilegedUsers.label"
|
||||
defaultMessage="Active Privileged Users"
|
||||
/>
|
||||
}
|
||||
getEsqlQuery={(namespace: string) =>
|
||||
getActivePrivilegedUsersEsqlCount(namespace, sourcerDataView)
|
||||
}
|
||||
id="privileged-user-monitoring-active-users"
|
||||
spaceId={spaceId}
|
||||
inspectTitle={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.activePrivilegedUsers.inspectTitle"
|
||||
defaultMessage="Active privileged users"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { getPrivilegedMonitorUsersJoin } from '../../../queries/helpers';
|
||||
|
||||
export const getActivePrivilegedUsersEsqlCount = (
|
||||
namespace: string,
|
||||
sourcerDataView: DataViewSpec
|
||||
) => {
|
||||
const indexPattern = sourcerDataView?.title ?? '';
|
||||
return `FROM ${indexPattern} METADATA _id, _index
|
||||
${getPrivilegedMonitorUsersJoin(namespace)}
|
||||
| STATS \`COUNT(*)\` = COUNT_DISTINCT(user.name)`;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 * from './active_privileged_users_tile';
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { getAlertsTriggeredEsqlCount } from './esql_query';
|
||||
import { KeyInsightsTile } from '../common/key_insights_tile';
|
||||
import { useSignalIndex } from '../../../../../../detections/containers/detection_engine/alerts/use_signal_index';
|
||||
|
||||
export const AlertsTriggeredTile: React.FC<{ spaceId: string }> = ({ spaceId }) => {
|
||||
const { signalIndexName: alertsIndexName } = useSignalIndex();
|
||||
return (
|
||||
<KeyInsightsTile
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.alertsTriggered.title"
|
||||
defaultMessage="Alerts Triggered"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.alertsTriggered.label"
|
||||
defaultMessage="Alerts Triggered"
|
||||
/>
|
||||
}
|
||||
getEsqlQuery={(namespace) => getAlertsTriggeredEsqlCount(namespace, alertsIndexName)}
|
||||
id="privileged-user-monitoring-alerts-triggered"
|
||||
spaceId={spaceId}
|
||||
inspectTitle={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.alertsTriggered.inspectTitle"
|
||||
defaultMessage="Alerts Triggered"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { getPrivilegedMonitorUsersJoin } from '../../../queries/helpers';
|
||||
|
||||
export const getAlertsTriggeredEsqlCount = (namespace: string, alertsIndexName: string | null) => {
|
||||
if (!alertsIndexName) return '';
|
||||
return `FROM ${alertsIndexName} METADATA _id, _index
|
||||
${getPrivilegedMonitorUsersJoin(namespace)}
|
||||
| STATS COUNT(*)`;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 * from './alerts_triggered_tile';
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { getAnomaliesDetectedEsqlQuery } from './esql_query';
|
||||
import { KeyInsightsTile } from '../common/key_insights_tile';
|
||||
|
||||
export const AnomaliesDetectedTile: React.FC<{ spaceId: string }> = ({ spaceId }) => {
|
||||
return (
|
||||
<KeyInsightsTile
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.anomaliesDetected.title"
|
||||
defaultMessage="Anomalies Detected"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.anomaliesDetected.label"
|
||||
defaultMessage="Anomalies Detected"
|
||||
/>
|
||||
}
|
||||
getEsqlQuery={(namespace) => getAnomaliesDetectedEsqlQuery(namespace)}
|
||||
id="privileged-user-monitoring-anomalies-detected"
|
||||
spaceId={spaceId}
|
||||
inspectTitle={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.anomaliesDetected.inspectTitle"
|
||||
defaultMessage="Anomalies detected"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { getPrivilegedMonitorUsersJoin } from '../../../queries/helpers';
|
||||
|
||||
export const getAnomaliesDetectedEsqlQuery = (namespace: string) => {
|
||||
return `FROM .ml-anomalies-shared
|
||||
| WHERE record_score IS NOT NULL AND record_score > 0
|
||||
| WHERE user.name IS NOT NULL
|
||||
${getPrivilegedMonitorUsersJoin(namespace)}
|
||||
| STATS COUNT(*)`;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 * from './anomalies_detected_tile';
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { getAuthenticationsEsqlCount } from './esql_query';
|
||||
import { KeyInsightsTile } from '../common/key_insights_tile';
|
||||
|
||||
export const AuthenticationsTile: React.FC<{ spaceId: string; sourcerDataView: DataViewSpec }> = ({
|
||||
spaceId,
|
||||
sourcerDataView,
|
||||
}) => {
|
||||
return (
|
||||
<KeyInsightsTile
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.authentications.title"
|
||||
defaultMessage="Authentications"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.authentications.label"
|
||||
defaultMessage="Authentications"
|
||||
/>
|
||||
}
|
||||
getEsqlQuery={(namespace) => getAuthenticationsEsqlCount(namespace, sourcerDataView)}
|
||||
id="privileged-user-monitoring-authentications"
|
||||
spaceId={spaceId}
|
||||
inspectTitle={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.authentications.inspectTitle"
|
||||
defaultMessage="Authentications"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { getAuthenticationsEsqlSource } from '../../../queries/authentications_esql_query';
|
||||
|
||||
export const getAuthenticationsEsqlCount = (namespace: string, sourcerDataView: DataViewSpec) => {
|
||||
const indexPattern = sourcerDataView?.title ?? '';
|
||||
const fields = sourcerDataView?.fields ?? {};
|
||||
return `${getAuthenticationsEsqlSource(namespace, indexPattern, fields)}
|
||||
| STATS COUNT(*)`;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { AuthenticationsTile } from './authentications_tile';
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiFlexItem, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import type { ReactElement } from 'react';
|
||||
import { createKeyInsightsPanelLensAttributes } from './lens_attributes';
|
||||
import { VisualizationEmbeddable } from '../../../../../../common/components/visualization_actions/visualization_embeddable';
|
||||
import { useEsqlGlobalFilterQuery } from '../../../../../../common/hooks/esql/use_esql_global_filter';
|
||||
import { useGlobalTime } from '../../../../../../common/containers/use_global_time';
|
||||
import { useSpaceId } from '../../../../../../common/hooks/use_space_id';
|
||||
|
||||
const LENS_VISUALIZATION_HEIGHT = 126;
|
||||
const LENS_VISUALIZATION_MIN_WIDTH = 160;
|
||||
|
||||
interface KeyInsightsTileProps {
|
||||
/** The title of the tile (i18n FormattedMessage element) */
|
||||
title: ReactElement;
|
||||
/** The label for the visualization (i18n FormattedMessage element) */
|
||||
label: ReactElement;
|
||||
/** Function that returns the ESQL query for the given namespace */
|
||||
getEsqlQuery: (namespace: string) => string;
|
||||
/** Unique ID for the visualization */
|
||||
id: string;
|
||||
/** The inspect title element for the visualization */
|
||||
inspectTitle: ReactElement;
|
||||
/** Optional override for space ID (if not provided, will use useSpaceId hook) */
|
||||
spaceId?: string;
|
||||
}
|
||||
|
||||
export const KeyInsightsTile: React.FC<KeyInsightsTileProps> = ({
|
||||
title,
|
||||
label,
|
||||
getEsqlQuery,
|
||||
id,
|
||||
inspectTitle,
|
||||
spaceId: propSpaceId,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const filterQuery = useEsqlGlobalFilterQuery();
|
||||
const timerange = useGlobalTime();
|
||||
const hookSpaceId = useSpaceId();
|
||||
|
||||
// Use prop spaceId if provided, otherwise use hook spaceId, fallback to 'default'
|
||||
const effectiveSpaceId = propSpaceId || hookSpaceId || 'default';
|
||||
|
||||
// Extract the defaultMessage from FormattedMessage elements
|
||||
const titleString = title.props.defaultMessage;
|
||||
const labelString = label.props.defaultMessage;
|
||||
|
||||
const lensAttributes = createKeyInsightsPanelLensAttributes({
|
||||
title: titleString,
|
||||
label: labelString,
|
||||
esqlQuery: getEsqlQuery(effectiveSpaceId),
|
||||
dataViewId: 'default-dataview',
|
||||
filterQuery,
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<div
|
||||
css={css`
|
||||
height: ${LENS_VISUALIZATION_HEIGHT}px;
|
||||
min-width: ${LENS_VISUALIZATION_MIN_WIDTH}px;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
background: ${euiTheme.colors.lightestShade};
|
||||
border-radius: ${euiTheme.border.radius.medium};
|
||||
`}
|
||||
>
|
||||
<VisualizationEmbeddable
|
||||
applyGlobalQueriesAndFilters={true}
|
||||
applyPageAndTabsFilters={true}
|
||||
lensAttributes={lensAttributes}
|
||||
id={id}
|
||||
timerange={timerange}
|
||||
width="auto"
|
||||
height={LENS_VISUALIZATION_HEIGHT}
|
||||
disableOnClickFilter
|
||||
inspectTitle={inspectTitle}
|
||||
/>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 type { LensAttributes } from '@kbn/lens-embeddable-utils';
|
||||
import type { ESBoolQuery } from '../../../../../../../common/typed_json';
|
||||
|
||||
interface KeyInsightsPanelParams {
|
||||
title: string;
|
||||
label: string;
|
||||
esqlQuery: string;
|
||||
dataViewId: string;
|
||||
filterQuery: ESBoolQuery | undefined;
|
||||
}
|
||||
|
||||
export const createKeyInsightsPanelLensAttributes = ({
|
||||
title,
|
||||
label,
|
||||
esqlQuery,
|
||||
dataViewId,
|
||||
filterQuery,
|
||||
}: KeyInsightsPanelParams): LensAttributes => {
|
||||
return {
|
||||
title,
|
||||
description: '',
|
||||
visualizationType: 'lnsMetric',
|
||||
state: {
|
||||
visualization: {
|
||||
layerId: 'layer1',
|
||||
layerType: 'data',
|
||||
metricAccessor: 'count',
|
||||
},
|
||||
query: {
|
||||
query: esqlQuery,
|
||||
language: 'esql',
|
||||
},
|
||||
filters: [{ query: filterQuery, meta: {} }],
|
||||
datasourceStates: {
|
||||
textBased: {
|
||||
layers: {
|
||||
layer1: {
|
||||
columns: [
|
||||
{
|
||||
columnId: 'count',
|
||||
fieldName: 'COUNT(*)',
|
||||
label,
|
||||
customLabel: true,
|
||||
params: {
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {
|
||||
decimals: 0,
|
||||
compact: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
query: {
|
||||
esql: esqlQuery,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
adHocDataViews: {
|
||||
[dataViewId]: {
|
||||
id: dataViewId,
|
||||
},
|
||||
},
|
||||
},
|
||||
references: [],
|
||||
};
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { getGrantedRightsEsqlSource } from '../../../queries/granted_rights_esql_query';
|
||||
|
||||
export const getGrantedRightsEsqlCount = (namespace: string, sourcerDataView: DataViewSpec) => {
|
||||
const indexPattern = sourcerDataView?.title ?? '';
|
||||
const fields = sourcerDataView?.fields ?? {};
|
||||
return `${getGrantedRightsEsqlSource(namespace, indexPattern, fields)}
|
||||
| STATS COUNT(*)`;
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { getGrantedRightsEsqlCount } from './esql_query';
|
||||
import { KeyInsightsTile } from '../common/key_insights_tile';
|
||||
|
||||
export const GrantedRightsTile: React.FC<{ spaceId: string; sourcerDataView: DataViewSpec }> = ({
|
||||
spaceId,
|
||||
sourcerDataView,
|
||||
}) => {
|
||||
return (
|
||||
<KeyInsightsTile
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.grantedRights.title"
|
||||
defaultMessage="Granted Rights"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.grantedRights.label"
|
||||
defaultMessage="Granted Rights"
|
||||
/>
|
||||
}
|
||||
getEsqlQuery={(namespace) => getGrantedRightsEsqlCount(namespace, sourcerDataView)}
|
||||
id="privileged-user-monitoring-granted-rights"
|
||||
spaceId={spaceId}
|
||||
inspectTitle={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.privmon.grantedRights.inspectTitle"
|
||||
defaultMessage="Granted rights"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 * from './granted_rights_tile';
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
|
||||
import { ActivePrivilegedUsersTile } from './active_privileged_users_tile';
|
||||
import { AlertsTriggeredTile } from './alerts_triggered_tile';
|
||||
import { AnomaliesDetectedTile } from './anomalies_detected_tile';
|
||||
import { GrantedRightsTile } from './granted_rights_tile';
|
||||
import { AccountSwitchesTile } from './account_switches_tile';
|
||||
import { AuthenticationsTile } from './authentications_tile';
|
||||
|
||||
const tileStyles = css`
|
||||
border: 1px solid #d3dae6;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const KeyInsightsPanel: React.FC<{ spaceId: string; sourcerDataView: DataViewSpec }> = ({
|
||||
spaceId,
|
||||
sourcerDataView,
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
wrap
|
||||
css={css`
|
||||
width: 100%;
|
||||
gap: 16px;
|
||||
& > * {
|
||||
min-width: calc(33.33% - 11px) !important;
|
||||
max-width: calc(33.33% - 11px) !important;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div css={tileStyles}>
|
||||
<ActivePrivilegedUsersTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div css={tileStyles}>
|
||||
<AlertsTriggeredTile spaceId={spaceId} />
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div css={tileStyles}>
|
||||
<AnomaliesDetectedTile spaceId={spaceId} />
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div css={tileStyles}>
|
||||
<GrantedRightsTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div css={tileStyles}>
|
||||
<AccountSwitchesTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div css={tileStyles}>
|
||||
<AuthenticationsTile spaceId={spaceId} sourcerDataView={sourcerDataView} />
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -11,6 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import { useSpaceId } from '../../../common/hooks/use_space_id';
|
||||
import { RiskLevelsPrivilegedUsersPanel } from './components/risk_level_panel';
|
||||
import { KeyInsightsPanel } from './components/key_insights_panel';
|
||||
import { UserActivityPrivilegedUsersPanel } from './components/privileged_user_activity';
|
||||
import { PrivilegedAccessDetectionsPanel } from './components/privileged_access_detection';
|
||||
|
||||
|
@ -30,6 +31,7 @@ export const PrivilegedUserMonitoring = ({
|
|||
sourcererDataView: DataViewSpec;
|
||||
}) => {
|
||||
const spaceId = useSpaceId();
|
||||
|
||||
const [dismissCallout, setDismissCallout] = useState(false);
|
||||
const handleDismiss = useCallback(() => {
|
||||
setDismissCallout(true);
|
||||
|
@ -96,9 +98,7 @@ export const PrivilegedUserMonitoring = ({
|
|||
{spaceId && <RiskLevelsPrivilegedUsersPanel spaceId={spaceId} />}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false} hasBorder={true}>
|
||||
<span>{'TODO: Top risky privileged users'}</span>
|
||||
</EuiPanel>
|
||||
{spaceId && <KeyInsightsPanel spaceId={spaceId} sourcerDataView={sourcererDataView} />}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -16,6 +16,6 @@ export const getAccountSwitchesEsqlSource = (
|
|||
return `FROM ${indexPattern} METADATA _id, _index
|
||||
${getPrivilegedMonitorUsersJoin(namespace)}
|
||||
| WHERE to_lower(process.command_line) RLIKE "(su|sudo su|sudo -i|sudo -s|ssh [^@]+@[^\s]+)"
|
||||
| RENAME to_lower(process.command_line) AS command_process, process.group_leader.user.name AS target_user, process.parent.real_group.name AS group_name, process.real_user.name as privileged_user, host.ip AS host_ip
|
||||
| RENAME process.command_line AS command_process, process.group_leader.user.name AS target_user, process.parent.real_group.name AS group_name, process.real_user.name as privileged_user, host.ip AS host_ip
|
||||
| KEEP @timestamp, privileged_user, host_ip, target_user, group_name, command_process, _id, _index`;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue