From 99f5e32fafe603d36ffd1b2f7556358a2855f2b6 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:27:54 -0500 Subject: [PATCH] [ResponseOps] Granular Connector RBAC - adding back UIs for testing connectors in stack management (#204804) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of https://github.com/elastic/kibana/issues/180908 ## Summary As part of the changes for EDR Connector Execution for Testing, this PRs updates the SentinelOne and Crowdstrike params UIs to limit to a single sub-action. SentinelOne: Screenshot 2024-12-18 at 11 16 49 AM Crowdstrike: Screenshot 2024-12-18 at 11 17 01 AM ### Checklist Check the PR satisfies following conditions. - [ ] [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 ### To test 1. In Stack Management create SentinelOne and Crowdstrike connectors (They don't need to work, you can use fake values for the url and token) 2. Test the new connectors. Verify that you only see one action type and that it can't be changed and that the params are correctly sent in the request. --- .../crowdstrike/crowdstrike.ts | 4 +- .../crowdstrike/crowdstrike_params.test.tsx | 38 ++++++ .../crowdstrike/crowdstrike_params.tsx | 112 ++++++++++++++++++ .../crowdstrike/crowdstrike_params_empty.tsx | 13 -- .../crowdstrike/translations.ts | 21 ++++ .../sentinelone/sentinelone.ts | 2 +- .../sentinelone/sentinelone_params.test.tsx | 35 ++++++ .../sentinelone/sentinelone_params.tsx | 51 ++++++++ .../sentinelone/sentinelone_params_empty.tsx | 13 -- .../sentinelone/translations.ts | 9 +- 10 files changed, 267 insertions(+), 31 deletions(-) create mode 100644 x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params.test.tsx create mode 100644 x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params.tsx delete mode 100644 x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params_empty.tsx create mode 100644 x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params.test.tsx create mode 100644 x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params.tsx delete mode 100644 x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params_empty.tsx diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike.ts b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike.ts index 2b5550b157239..2a928ed7d64de 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike.ts @@ -60,8 +60,6 @@ export function getConnectorType(): ConnectorTypeModel< return { errors }; }, actionConnectorFields: lazy(() => import('./crowdstrike_connector')), - actionParamsFields: lazy(() => import('./crowdstrike_params_empty')), - // TODO: Enable once we add support for automated response actions - // actionParamsFields: lazy(() => import('./crowdstrike_params')), + actionParamsFields: lazy(() => import('./crowdstrike_params')), }; } diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params.test.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params.test.tsx new file mode 100644 index 0000000000000..11e474e6a7ac9 --- /dev/null +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params.test.tsx @@ -0,0 +1,38 @@ +/* + * 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 { mountWithIntl } from '@kbn/test-jest-helpers'; +import { SUB_ACTION } from '../../../common/crowdstrike/constants'; +import type { CrowdstrikeActionParams } from '../../../common/crowdstrike/types'; +import CrowdstrikeParamsFields from './crowdstrike_params'; + +const actionParams = { + subAction: SUB_ACTION.GET_AGENT_DETAILS, + subActionParams: { + ids: ['test'], + }, +} as unknown as CrowdstrikeActionParams; + +describe('CrowdstrikeParamsFields renders', () => { + test('all params fields are rendered', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="actionTypeSelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="actionTypeSelect"]').first().prop('readOnly')).toEqual( + true + ); + expect(wrapper.find('[data-test-subj="agentIdSelect"]').length > 0).toBeTruthy(); + }); +}); diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params.tsx new file mode 100644 index 0000000000000..a53843f683567 --- /dev/null +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params.tsx @@ -0,0 +1,112 @@ +/* + * 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, { useMemo, useCallback, useState, useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSuperSelect, EuiComboBox } from '@elastic/eui'; +import { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { SUB_ACTION } from '../../../common/crowdstrike/constants'; +import type { CrowdstrikeActionParams } from '../../../common/crowdstrike/types'; +import * as i18n from './translations'; + +const actionTypeOptions = [ + { + value: SUB_ACTION.GET_AGENT_DETAILS, + inputDisplay: i18n.GET_AGENT_DETAILS_ACTION_LABEL, + }, +]; + +const CrowdstrikeParamsFields: React.FunctionComponent< + ActionParamsProps +> = ({ actionParams, editAction, index, errors }) => { + const [subActionValue] = useState(SUB_ACTION.GET_AGENT_DETAILS); + + const { ids } = useMemo( + () => + actionParams.subActionParams ?? + ({ + ids: [], + } as unknown as CrowdstrikeActionParams['subActionParams']), + [actionParams.subActionParams] + ); + + const labelOptions = useMemo(() => (ids ? ids.map((label: string) => ({ label })) : []), [ids]); + + const editSubActionParams = useCallback( + (value: any) => { + return editAction( + 'subActionParams', + { + ids: value, + }, + index + ); + }, + [editAction, index] + ); + + useEffect(() => { + if (!actionParams.subAction) { + editAction('subAction', SUB_ACTION.GET_AGENT_DETAILS, index); + } + if (!actionParams.subActionParams) { + editAction( + 'subActionParams', + { + ids: [], + }, + index + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [actionParams]); + + return ( + + + + + + + + + { + const newOptions = [...labelOptions, { label: searchValue }]; + editSubActionParams(newOptions.map((newOption) => newOption.label)); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + editSubActionParams(selectedOptions.map((selectedOption) => selectedOption.label)); + }} + onBlur={() => { + if (!ids) { + editSubActionParams([]); + } + }} + isClearable={true} + data-test-subj="agentIdSelect" + /> + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { CrowdstrikeParamsFields as default }; diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params_empty.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params_empty.tsx deleted file mode 100644 index 9b99e68368405..0000000000000 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/crowdstrike_params_empty.tsx +++ /dev/null @@ -1,13 +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 React from 'react'; - -const CrowdstrikeParamsFields = () => <>; - -// eslint-disable-next-line import/no-default-export -export { CrowdstrikeParamsFields as default }; diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/translations.ts b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/translations.ts index e10226f532914..8d1a3ad0bbe12 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/translations.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/crowdstrike/translations.ts @@ -43,3 +43,24 @@ export const INVALID_ACTION = i18n.translate( defaultMessage: 'Invalid action name.', } ); + +export const ACTION_TYPE_LABEL = i18n.translate( + 'xpack.stackConnectors.security.crowdstrike.params.actionTypeFieldLabel', + { + defaultMessage: 'Action type', + } +); + +export const GET_AGENT_DETAILS_ACTION_LABEL = i18n.translate( + 'xpack.stackConnectors.security.crowdstrike.params.getAgentDetailsActionLabel', + { + defaultMessage: 'Get agent details', + } +); + +export const AGENT_IDS_LABEL = i18n.translate( + 'xpack.stackConnectors.security.crowdstrike.params.agentIdsLabel', + { + defaultMessage: 'Agent IDs', + } +); diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone.ts b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone.ts index 2344833ceb0c7..a65fc6a4f011c 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone.ts @@ -60,6 +60,6 @@ export function getConnectorType(): ConnectorTypeModel< return { errors }; }, actionConnectorFields: lazy(() => import('./sentinelone_connector')), - actionParamsFields: lazy(() => import('./sentinelone_params_empty')), + actionParamsFields: lazy(() => import('./sentinelone_params')), }; } diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params.test.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params.test.tsx new file mode 100644 index 0000000000000..dcac22ada7809 --- /dev/null +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params.test.tsx @@ -0,0 +1,35 @@ +/* + * 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 { mountWithIntl } from '@kbn/test-jest-helpers'; +import { SUB_ACTION } from '../../../common/sentinelone/constants'; +import type { SentinelOneActionParams } from '../../../common/sentinelone/types'; +import SentinelOneParamsFields from './sentinelone_params'; + +const actionParams = { + subAction: SUB_ACTION.GET_AGENTS, + subActionParams: {}, +} as unknown as SentinelOneActionParams; + +describe('SentinelOneParamsFields renders', () => { + test('all params fields are rendered', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="actionTypeSelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="actionTypeSelect"]').first().prop('readOnly')).toEqual( + true + ); + }); +}); diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params.tsx new file mode 100644 index 0000000000000..00ba91c83e25f --- /dev/null +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params.tsx @@ -0,0 +1,51 @@ +/* + * 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, { useState, useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSuperSelect } from '@elastic/eui'; +import { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { SUB_ACTION } from '../../../common/sentinelone/constants'; +import type { SentinelOneActionParams } from '../../../common/sentinelone/types'; +import * as i18n from './translations'; + +const actionTypeOptions = [ + { + value: SUB_ACTION.GET_AGENTS, + inputDisplay: i18n.GET_AGENT_ACTION_LABEL, + }, +]; + +const SentinelOneParamsFields: React.FunctionComponent< + ActionParamsProps +> = ({ editAction, index }) => { + const [subAction] = useState(SUB_ACTION.GET_AGENTS); + + useEffect(() => { + editAction('subActionParams', {}, index); + editAction('subAction', subAction, index); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { SentinelOneParamsFields as default }; diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params_empty.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params_empty.tsx deleted file mode 100644 index 35895d35bcebf..0000000000000 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/sentinelone_params_empty.tsx +++ /dev/null @@ -1,13 +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 React from 'react'; - -const SentinelOneParamsFields = () => <>; - -// eslint-disable-next-line import/no-default-export -export { SentinelOneParamsFields as default }; diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/translations.ts b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/translations.ts index a5b9a274857c3..f2b44172850d6 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/translations.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/sentinelone/translations.ts @@ -73,6 +73,13 @@ export const RELEASE_AGENT_ACTION_LABEL = i18n.translate( } ); +export const GET_AGENT_ACTION_LABEL = i18n.translate( + 'xpack.stackConnectors.security.sentinelone.params.getAgentActionLabel', + { + defaultMessage: 'Get agent details', + } +); + export const AGENTS_FIELD_LABEL = i18n.translate( 'xpack.stackConnectors.security.sentinelone.params.agentsFieldLabel', { @@ -90,7 +97,7 @@ export const AGENTS_FIELD_PLACEHOLDER = i18n.translate( export const ACTION_TYPE_LABEL = i18n.translate( 'xpack.stackConnectors.security.sentinelone.params.actionTypeFieldLabel', { - defaultMessage: 'Action Type', + defaultMessage: 'Action type', } );