Skip to content

Commit

Permalink
[Security Solution][Endpoint] Add beta badge to sentinel one connecto…
Browse files Browse the repository at this point in the history
…r cards/flyout and responder/isolation action flyouts (#176228)

## Summary

Replaces Technical Preview badge with a Beta badge for Sentinel One
connector cards and connector form flyouts.
Additionally it shows Beta badge on Responder for sentinel one alerts as
well as Isolate/Release action flyouts.

### TODO/DONE
- [x] Update beta badge tooltip text on all used instances
- [x] beta badges on alerts flyout/responder behind feature flag

### Screens

### Connectors list flyout
<img width="2054" alt="Screenshot 2024-02-05 at 2 14 12 PM"
src="https://github.com/elastic/kibana/assets/1849116/8aa3215a-ebc0-4935-92fe-5d234f78e17b">

### Sentinel one connector forms
#### Create
<img width="2056" alt="Screenshot 2024-02-05 at 2 14 26 PM"
src="https://github.com/elastic/kibana/assets/1849116/fae12ae7-5301-4bc0-8934-3010b823ec5b">

#### Edit
<img width="2054" alt="Screenshot 2024-02-05 at 2 31 04 PM"
src="https://github.com/elastic/kibana/assets/1849116/637f1d01-fe59-47b4-bb8e-714dd9304c58">

#### Isolate/Release flyout forms for SentinelOne
![Screenshot 2024-02-07 at 1 03
32 PM](https://github.com/elastic/kibana/assets/1849116/92f238e7-4d9c-45aa-8b73-40fdc24dea43)
![Screenshot 2024-02-07 at 1 03
13 PM](https://github.com/elastic/kibana/assets/1849116/ec0b5afc-88f9-424b-992c-5d65076d2490)


#### Response Console for SentinelOne
![Screenshot 2024-02-07 at 11 51
22 AM](https://github.com/elastic/kibana/assets/1849116/4b5b9a52-5493-4ae2-8f77-65bed1a7d182)

### Checklist

- [x] 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/packages/kbn-i18n/README.md)
- [x] [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
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
ashokaditya and kibanamachine authored Feb 12, 2024
1 parent 509248b commit b965b4c
Show file tree
Hide file tree
Showing 17 changed files with 259 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export const BETA = i18n.translate('xpack.securitySolution.pages.common.beta', {
defaultMessage: 'Beta',
});

export const BETA_TOOLTIP = i18n.translate('xpack.securitySolution.pages.common.beta.tooltip', {
defaultMessage:
'This functionality is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.',
});

export const UPDATE_ALERT_STATUS_FAILED = (conflicts: number) =>
i18n.translate('xpack.securitySolution.pages.common.updateAlertStatusFailed', {
values: { conflicts },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@ import React from 'react';
import { render } from '@testing-library/react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { useIsolateHostPanelContext } from './context';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import { PanelHeader } from './header';
import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids';
import { isAlertFromSentinelOneEvent } from '../../../common/utils/sentinelone_alert_check';

jest.mock('../../../common/hooks/use_experimental_features');
jest.mock('../../../common/utils/sentinelone_alert_check');
jest.mock('./context');

const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock;
const mockIsAlertFromSentinelOneEvent = isAlertFromSentinelOneEvent as jest.Mock;

const renderPanelHeader = () =>
render(
<IntlProvider locale="en">
Expand All @@ -22,21 +29,57 @@ const renderPanelHeader = () =>
);

describe('<PanelHeader />', () => {
(useIsolateHostPanelContext as jest.Mock).mockReturnValue({ isolateAction: 'isolateHost' });
beforeEach(() => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(false);
});

it.each([
{
isolateAction: 'isolateHost',
title: 'Isolate host',
},
{
isolateAction: 'unisolateHost',
title: 'Release host',
},
])('should display release host message', ({ isolateAction, title }) => {
(useIsolateHostPanelContext as jest.Mock).mockReturnValue({ isolateAction });

it('should display isolate host message', () => {
const { getByTestId } = renderPanelHeader();

expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Isolate host');
expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent(title);
});

it('should display release host message', () => {
(useIsolateHostPanelContext as jest.Mock).mockReturnValue({ isolateAction: 'unisolateHost' });
it.each(['isolateHost', 'unisolateHost'])(
'should display beta badge on %s host message for SentinelOne alerts',
(action) => {
(useIsolateHostPanelContext as jest.Mock).mockReturnValue({
isolateAction: action,
});
mockUseIsExperimentalFeatureEnabled.mockReturnValue(true);
mockIsAlertFromSentinelOneEvent.mockReturnValue(true);

const { getByTestId } = renderPanelHeader();
const { getByTestId } = renderPanelHeader();

expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Release host');
});
expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Beta');
}
);

it.each(['isolateHost', 'unisolateHost'])(
'should not display beta badge on %s host message for non-SentinelOne alerts',
(action) => {
(useIsolateHostPanelContext as jest.Mock).mockReturnValue({
isolateAction: action,
});
mockUseIsExperimentalFeatureEnabled.mockReturnValue(true);
mockIsAlertFromSentinelOneEvent.mockReturnValue(false);

const { getByTestId } = renderPanelHeader();

expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).not.toHaveTextContent('Beta');
}
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
* 2.0.
*/

import { EuiTitle } from '@elastic/eui';
import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import type { FC } from 'react';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import { BETA, BETA_TOOLTIP } from '../../../common/translations';
import { isAlertFromSentinelOneEvent } from '../../../common/utils/sentinelone_alert_check';
import { useIsolateHostPanelContext } from './context';
import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids';
import { FlyoutHeader } from '../../shared/components/flyout_header';
Expand All @@ -17,20 +20,34 @@ import { FlyoutHeader } from '../../shared/components/flyout_header';
* Document details expandable right section header for the isolate host panel
*/
export const PanelHeader: FC = () => {
const { isolateAction } = useIsolateHostPanelContext();
const { isolateAction, dataFormattedForFieldBrowser: data } = useIsolateHostPanelContext();
const isSentinelOneAlert = isAlertFromSentinelOneEvent({ data });
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
'responseActionsSentinelOneV1Enabled'
);

const title =
isolateAction === 'isolateHost' ? (
<FormattedMessage
id="xpack.securitySolution.flyout.isolateHost.isolateTitle"
defaultMessage="Isolate host"
/>
) : (
<FormattedMessage
id="xpack.securitySolution.flyout.isolateHost.releaseTitle"
defaultMessage="Release host"
/>
);
const title = (
<EuiFlexGroup responsive gutterSize="s">
<EuiFlexItem grow={false}>
{isolateAction === 'isolateHost' ? (
<FormattedMessage
id="xpack.securitySolution.flyout.isolateHost.isolateTitle"
defaultMessage="Isolate host"
/>
) : (
<FormattedMessage
id="xpack.securitySolution.flyout.isolateHost.releaseTitle"
defaultMessage="Release host"
/>
)}
</EuiFlexItem>
{isSentinelOneV1Enabled && isSentinelOneAlert && (
<EuiFlexItem grow={false}>
<EuiBetaBadge label={BETA} tooltipContent={BETA_TOOLTIP} />
</EuiFlexItem>
)}
</EuiFlexGroup>
);

return (
<FlyoutHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/

import React, { useCallback } from 'react';
import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { BETA, BETA_TOOLTIP } from '../../common/translations';
import { useLicense } from '../../common/hooks/use_license';
import type { ImmutableArray } from '../../../common/endpoint/types';
import {
Expand All @@ -26,6 +28,7 @@ import {
import { useConsoleManager } from '../components/console';
import { MissingEncryptionKeyCallout } from '../components/missing_encryption_key_callout';
import { RESPONDER_PAGE_TITLE } from './translations';
import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';

type ShowResponseActionsConsole = (props: ResponderInfoProps) => void;

Expand All @@ -50,6 +53,9 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
const consoleManager = useConsoleManager();
const endpointPrivileges = useUserPrivileges().endpointPrivileges;
const isEnterpriseLicense = useLicense().isEnterprise();
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
'responseActionsSentinelOneV1Enabled'
);

return useCallback(
(props: ResponderInfoProps) => {
Expand Down Expand Up @@ -126,7 +132,19 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
hostName,
},
consoleProps,
PageTitleComponent: () => <>{RESPONDER_PAGE_TITLE}</>,
PageTitleComponent: () => {
if (isSentinelOneV1Enabled && agentType === 'sentinel_one') {
return (
<EuiFlexGroup>
<EuiFlexItem>{RESPONDER_PAGE_TITLE}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBetaBadge label={BETA} tooltipContent={BETA_TOOLTIP} />
</EuiFlexItem>
</EuiFlexGroup>
);
}
return <>{RESPONDER_PAGE_TITLE}</>;
},
ActionComponents: endpointPrivileges.canReadActionsLogManagement
? [ActionLogButton]
: undefined,
Expand All @@ -140,6 +158,6 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => {
.show();
}
},
[endpointPrivileges, isEnterpriseLicense, consoleManager]
[endpointPrivileges, isEnterpriseLicense, isSentinelOneV1Enabled, consoleManager]
);
};
2 changes: 1 addition & 1 deletion x-pack/plugins/security_solution/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,6 @@
"@kbn/elastic-assistant-common",
"@kbn/core-elasticsearch-server-mocks",
"@kbn/lens-embeddable-utils",
"@kbn/esql-utils"
"@kbn/esql-utils",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues;
*/
export const allowedExperimentalValues = Object.freeze({
isMustacheAutocompleteOn: false,
// set to true to show tech preview badge on sentinel one connector
sentinelOneConnectorOn: true,
// set to true to show beta badge on sentinel one connector
// TODO: set to true when 8.13 is ready
sentinelOneConnectorOnBeta: false,
});

export type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,14 @@ export function registerConnectorTypes({
connectorTypeRegistry.register(getTinesConnectorType());
connectorTypeRegistry.register(getD3SecurityConnectorType());

if (ExperimentalFeaturesService.get().sentinelOneConnectorOn) {
// get sentinelOne connector type
// when either feature flag is enabled
if (
// 8.12
ExperimentalFeaturesService.get().sentinelOneConnectorOn ||
// 8.13
ExperimentalFeaturesService.get().sentinelOneConnectorOnBeta
) {
connectorTypeRegistry.register(getSentinelOneConnectorType());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ import type {
ActionTypeModel as ConnectorTypeModel,
GenericValidationResult,
} from '@kbn/triggers-actions-ui-plugin/public';
import { getIsExperimentalFeatureEnabled } from '../../common/get_experimental_features';
import {
SENTINELONE_CONNECTOR_ID,
SENTINELONE_TITLE,
SUB_ACTION,
} from '../../../common/sentinelone/constants';
import type {
SentinelOneActionParams,
SentinelOneConfig,
SentinelOneSecrets,
SentinelOneActionParams,
} from '../../../common/sentinelone/types';

interface ValidationErrors {
Expand All @@ -31,11 +32,16 @@ export function getConnectorType(): ConnectorTypeModel<
SentinelOneSecrets,
SentinelOneActionParams
> {
const isSentinelOneBetaBadgeEnabled = getIsExperimentalFeatureEnabled(
'sentinelOneConnectorOnBeta'
);

return {
id: SENTINELONE_CONNECTOR_ID,
actionTypeTitle: SENTINELONE_TITLE,
iconClass: lazy(() => import('./logo')),
isExperimental: true,
isBeta: isSentinelOneBetaBadgeEnabled ? true : undefined,
isExperimental: isSentinelOneBetaBadgeEnabled ? undefined : true,
selectMessage: i18n.translate(
'xpack.stackConnectors.security.sentinelone.config.selectMessageText',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
*/

import React, { useEffect, useState } from 'react';
import { EuiFlexItem, EuiCard, EuiIcon, EuiFlexGrid, EuiSpacer } from '@elastic/eui';
import { EuiCard, EuiFlexGrid, EuiFlexItem, EuiIcon, EuiSpacer, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { ActionType, ActionTypeIndex, ActionTypeRegistryContract } from '../../../types';
import { loadActionTypes } from '../../lib/action_connector_api';
import { actionTypeCompare } from '../../lib/action_type_compare';
import { checkActionTypeEnabled } from '../../lib/check_action_type_enabled';
import { useKibana } from '../../../common/lib/kibana';
import { SectionLoading } from '../../components/section_loading';
import { betaBadgeProps } from './beta_badge_props';
import { betaBadgeProps, technicalPreviewBadgeProps } from './beta_badge_props';

interface Props {
onActionTypeChange: (actionType: ActionType) => void;
Expand Down Expand Up @@ -77,12 +76,13 @@ export const ActionTypeMenu = ({
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const registeredActionTypes = Object.entries(actionTypesIndex ?? [])
.filter(
([id, details]) =>
actionTypeRegistry.has(id) &&
details.enabledInConfig === true &&
!actionTypeRegistry.get(id).hideInUi
!actionTypeRegistry.get(id).hideInUi &&
details.enabledInConfig
)
.map(([id, actionType]) => {
const actionTypeModel = actionTypeRegistry.get(id);
Expand All @@ -91,6 +91,7 @@ export const ActionTypeMenu = ({
selectMessage: actionTypeModel ? actionTypeModel.selectMessage : '',
actionType,
name: actionType.name,
isBeta: actionTypeModel.isBeta,
isExperimental: actionTypeModel.isExperimental,
};
});
Expand All @@ -101,7 +102,13 @@ export const ActionTypeMenu = ({
const checkEnabledResult = checkActionTypeEnabled(item.actionType);
const card = (
<EuiCard
betaBadgeProps={item.isExperimental ? betaBadgeProps : undefined}
betaBadgeProps={
item.isBeta
? betaBadgeProps
: item.isExperimental
? technicalPreviewBadgeProps
: undefined
}
titleSize="xs"
data-test-subj={`${item.actionType.id}-card`}
icon={<EuiIcon size="xl" type={item.iconClass} />}
Expand All @@ -117,7 +124,7 @@ export const ActionTypeMenu = ({
return (
<EuiFlexItem key={index}>
{checkEnabledResult.isEnabled && card}
{checkEnabledResult.isEnabled === false && (
{!checkEnabledResult.isEnabled && (
<EuiToolTip position="top" content={checkEnabledResult.message}>
{card}
</EuiToolTip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@
import { i18n } from '@kbn/i18n';

export const betaBadgeProps = {
label: i18n.translate('xpack.triggersActionsUI.betaBadgeLabel', {
defaultMessage: 'Beta',
}),
tooltipContent: i18n.translate('xpack.triggersActionsUI.betaBadgeDescription', {
defaultMessage:
'This functionality is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.',
}),
};

export const technicalPreviewBadgeProps = {
label: i18n.translate('xpack.triggersActionsUI.technicalPreviewBadgeLabel', {
defaultMessage: 'Technical preview',
}),
Expand Down
Loading

0 comments on commit b965b4c

Please sign in to comment.