From 9c774492b4ca6c4df55b3984f23f77f2de16e443 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Wed, 13 Mar 2024 19:19:10 +0100 Subject: [PATCH 1/3] [Security Solution] action not allowed (405) is shown for Duplicating Shared Exception Lists (#177814) --- .../exceptions/hooks/use_import_exception_list/index.tsx | 1 + .../endpoint/handlers/exceptions_pre_import_handler.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_import_exception_list/index.tsx b/x-pack/plugins/security_solution/public/exceptions/hooks/use_import_exception_list/index.tsx index ca766c102efd3..536d558b371a2 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_import_exception_list/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_import_exception_list/index.tsx @@ -34,6 +34,7 @@ export const importExceptionList = async ({ formData.append('file', file as Blob); const res = await http.post(`${EXCEPTION_LIST_URL}/_import`, { + version: '2023-10-31', body: formData, query: { overwrite, overwrite_exceptions: overwriteExceptions, as_new_list: asNewList }, headers: { 'Content-Type': undefined }, diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_import_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_import_handler.ts index 48832f6e315a0..c71ae16a6e085 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_import_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_import_handler.ts @@ -6,6 +6,7 @@ */ import type { ExceptionsListPreImportServerExtension } from '@kbn/lists-plugin/server'; +import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import { EndpointArtifactExceptionValidationError } from '../validators/errors'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../common/endpoint/service/artifacts/constants'; @@ -14,7 +15,9 @@ export const getExceptionsPreImportHandler = (): ValidatorCallback => { return async ({ data }) => { const hasEndpointArtifactListOrListItems = [...data.lists, ...data.items].some((item) => { if ('list_id' in item) { - return ALL_ENDPOINT_ARTIFACT_LIST_IDS.includes(item.list_id); + return ( + item.list_id === ENDPOINT_LIST_ID || ALL_ENDPOINT_ARTIFACT_LIST_IDS.includes(item.list_id) + ); } return false; From 950340fb172b720b8f044b80425f38fe1c1ea7a4 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Wed, 10 Apr 2024 11:40:00 +0200 Subject: [PATCH 2/3] Disabled create new list checkbox --- .../import_exceptions_list_flyout/index.tsx | 37 +++++--- .../exceptions/translations/shared_list.ts | 7 ++ .../handlers/exceptions_pre_import_handler.ts | 5 +- .../import_lists.cy.ts | 86 ++++++++++++------- .../fixtures/endpoint_exception_list.ndjson | 2 + .../cypress/tasks/exceptions_table.ts | 4 + 6 files changed, 96 insertions(+), 45 deletions(-) create mode 100644 x-pack/test/security_solution_cypress/cypress/fixtures/endpoint_exception_list.ndjson diff --git a/x-pack/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx index 38619519f0c28..1ca74654b3d61 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx @@ -24,11 +24,13 @@ import { EuiFlyoutFooter, EuiTextColor, EuiFlyout, + EuiToolTip, } from '@elastic/eui'; import type { BulkErrorSchema, ImportExceptionsResponseSchema, } from '@kbn/securitysolution-io-ts-list-types'; +import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import type { HttpSetup } from '@kbn/core-http-browser'; import type { ToastInput, Toast, ErrorToastOptions } from '@kbn/core-notifications-browser'; @@ -58,6 +60,7 @@ export const ImportExceptionListFlyout = React.memo( const [overwrite, setOverwrite] = useState(false); const [asNewList, setAsNewList] = useState(false); const [alreadyExistingItem, setAlreadyExistingItem] = useState(false); + const [endpointListImporting, setEndpointListImporting] = useState(false); const resetForm = useCallback(() => { if (filePickerRef.current?.fileInput) { @@ -66,6 +69,7 @@ export const ImportExceptionListFlyout = React.memo( } setFiles(null); setAlreadyExistingItem(false); + setEndpointListImporting(false); setAsNewList(false); setOverwrite(false); }, []); @@ -128,6 +132,13 @@ export const ImportExceptionListFlyout = React.memo( importExceptionListState?.result?.errors.forEach((err) => { if (err.error.message.includes('already exists')) { setAlreadyExistingItem(true); + if ( + err.error.message.includes( + `Found that list_id: "${ENDPOINT_LIST_ID}" already exists` + ) + ) { + setEndpointListImporting(true); + } } errorsToDisplay.push(err); }); @@ -180,16 +191,22 @@ export const ImportExceptionListFlyout = React.memo( setAsNewList(false); }} /> - { - setAsNewList(!asNewList); - setOverwrite(false); - }} - /> + + { + setAsNewList(!asNewList); + setOverwrite(false); + }} + /> + )} diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts index eb18283577369..d53ff40d35fc3 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts @@ -267,6 +267,13 @@ export const IMPORT_EXCEPTION_LIST_AS_NEW_LIST = i18n.translate( } ); +export const IMPORT_EXCEPTION_ENDPOINT_LIST_WARNING = i18n.translate( + 'xpack.securitySolution.exceptionsTable.importExceptionEndpointListWarning', + { + defaultMessage: 'We only allow one Exception List for Endpoint Security.', + } +); + export const READ_ONLY_BADGE_TOOLTIP = i18n.translate( 'xpack.securitySolution.exceptions.badge.readOnly.tooltip', { diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_import_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_import_handler.ts index c71ae16a6e085..48832f6e315a0 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_import_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_import_handler.ts @@ -6,7 +6,6 @@ */ import type { ExceptionsListPreImportServerExtension } from '@kbn/lists-plugin/server'; -import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import { EndpointArtifactExceptionValidationError } from '../validators/errors'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../common/endpoint/service/artifacts/constants'; @@ -15,9 +14,7 @@ export const getExceptionsPreImportHandler = (): ValidatorCallback => { return async ({ data }) => { const hasEndpointArtifactListOrListItems = [...data.lists, ...data.items].some((item) => { if ('list_id' in item) { - return ( - item.list_id === ENDPOINT_LIST_ID || ALL_ENDPOINT_ARTIFACT_LIST_IDS.includes(item.list_id) - ); + return ALL_ENDPOINT_ARTIFACT_LIST_IDS.includes(item.list_id); } return false; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts index 17ec79fdbd3a7..bdd65ae5561ad 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/import_lists.cy.ts @@ -16,6 +16,7 @@ import { importExceptionListWithSelectingCreateNewOption, validateImportExceptionListWentSuccessfully, validateImportExceptionListFailedBecauseExistingListFound, + validateImportExceptionListCreateNewOptionDisabled, } from '../../../../../../tasks/exceptions_table'; import { login } from '../../../../../../tasks/login'; import { visit } from '../../../../../../tasks/navigation'; @@ -23,6 +24,7 @@ import { EXCEPTIONS_URL } from '../../../../../../urls/navigation'; describe('Import Lists', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { const LIST_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_exception_list.ndjson'; + const ENDPOINT_LIST_TO_IMPORT_FILENAME = 'cypress/fixtures/endpoint_exception_list.ndjson'; beforeEach(() => { login(); visit(EXCEPTIONS_URL); @@ -30,54 +32,76 @@ describe('Import Lists', { tags: ['@ess', '@serverless', '@skipInServerless'] }, cy.intercept(/(\/api\/exception_lists\/_import)/).as('import'); }); - it('Should import exception list successfully if the list does not exist', () => { - importExceptionLists(LIST_TO_IMPORT_FILENAME); + describe('Exception Lists', () => { + it('Should import exception list successfully if the list does not exist', () => { + importExceptionLists(LIST_TO_IMPORT_FILENAME); - validateImportExceptionListWentSuccessfully(); + validateImportExceptionListWentSuccessfully(); - cy.get(IMPORT_SHARED_EXCEPTION_LISTS_CLOSE_BTN).click(); + cy.get(IMPORT_SHARED_EXCEPTION_LISTS_CLOSE_BTN).click(); - // Validate table items count - cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); - }); + // Validate table items count + cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); + }); - it('Should not import exception list if it exists', () => { - importExceptionLists(LIST_TO_IMPORT_FILENAME); + it('Should not import exception list if it exists', () => { + importExceptionLists(LIST_TO_IMPORT_FILENAME); - validateImportExceptionListFailedBecauseExistingListFound(); + validateImportExceptionListFailedBecauseExistingListFound(); - // Validate table items count - cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); - }); + // Validate table items count + cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); + }); + + it('Should import exception list if it exists but the user selected overwrite checkbox', () => { + importExceptionLists(LIST_TO_IMPORT_FILENAME); + + validateImportExceptionListFailedBecauseExistingListFound(); + + // Validate table items count + cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); + + importExceptionListWithSelectingOverwriteExistingOption(); + + validateImportExceptionListWentSuccessfully(); - it('Should import exception list if it exists but the user selected overwrite checkbox', () => { - importExceptionLists(LIST_TO_IMPORT_FILENAME); + // Validate table items count + cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); + }); - validateImportExceptionListFailedBecauseExistingListFound(); + it('Should import exception list if it exists but the user selected create new checkbox', () => { + importExceptionLists(LIST_TO_IMPORT_FILENAME); - // Validate table items count - cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); + validateImportExceptionListFailedBecauseExistingListFound(); - importExceptionListWithSelectingOverwriteExistingOption(); + // Validate table items count + cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); - validateImportExceptionListWentSuccessfully(); + importExceptionListWithSelectingCreateNewOption(); - // Validate table items count - cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); + validateImportExceptionListWentSuccessfully(); + // Validate table items count + cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2'); + }); }); - it('Should import exception list if it exists but the user selected create new checkbox', () => { - importExceptionLists(LIST_TO_IMPORT_FILENAME); + describe('Endpoint Security Exception List', () => { + before(() => { + login(); + visit(EXCEPTIONS_URL); - validateImportExceptionListFailedBecauseExistingListFound(); + // Make sure we have Endpoint Security Exception List + importExceptionLists(ENDPOINT_LIST_TO_IMPORT_FILENAME); + }); - // Validate table items count - cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1'); + it('Should not allow to import or create a second Endpoint Security Exception List', () => { + // Try to import another Endpoint Security Exception List + importExceptionLists(ENDPOINT_LIST_TO_IMPORT_FILENAME); - importExceptionListWithSelectingCreateNewOption(); + validateImportExceptionListFailedBecauseExistingListFound(); - validateImportExceptionListWentSuccessfully(); - // Validate table items count - cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2'); + // Validate that "Create new list" option is disabled + validateImportExceptionListCreateNewOptionDisabled(); + }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/fixtures/endpoint_exception_list.ndjson b/x-pack/test/security_solution_cypress/cypress/fixtures/endpoint_exception_list.ndjson new file mode 100644 index 0000000000000..ebd1cbc34d25e --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/fixtures/endpoint_exception_list.ndjson @@ -0,0 +1,2 @@ +{"_version":"WzUxOTM4LDE1XQ==","created_at":"2024-03-18T14:11:18.125Z","created_by":"kibana","description":"Endpoint Security Exception List","id":"endpoint_list","immutable":false,"list_id":"endpoint_list","name":"Endpoint Security Exception List","namespace_type":"agnostic","os_types":[],"tags":[],"tie_breaker_id":"04deda68-7162-4349-8e34-c315bb9f896f","type":"endpoint","updated_at":"2024-03-19T12:57:31.911Z","updated_by":"elastic","version":1} +{"exported_exception_list_count":1,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0} diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions_table.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions_table.ts index 710526bdff18d..86e8fd1597667 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions_table.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions_table.ts @@ -295,3 +295,7 @@ export const validateImportExceptionListFailedBecauseExistingListFound = () => { cy.get(TOASTER_BODY).should('contain', 'Found that list_id'); }); }; + +export const validateImportExceptionListCreateNewOptionDisabled = () => { + cy.get(IMPORT_SHARED_EXCEPTION_LISTS_OVERWRITE_CREATE_NEW_CHECKBOX).should('be.disabled'); +}; From 32039459a8c9f472590514ad44fb489d530da578 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Thu, 25 Apr 2024 09:58:46 +0200 Subject: [PATCH 3/3] Adjust the message to follow guidelines --- .../public/exceptions/translations/shared_list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts index d53ff40d35fc3..4f95d28f578a6 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts @@ -270,7 +270,7 @@ export const IMPORT_EXCEPTION_LIST_AS_NEW_LIST = i18n.translate( export const IMPORT_EXCEPTION_ENDPOINT_LIST_WARNING = i18n.translate( 'xpack.securitySolution.exceptionsTable.importExceptionEndpointListWarning', { - defaultMessage: 'We only allow one Exception List for Endpoint Security.', + defaultMessage: 'Multiple exception lists for Endpoint Security are not allowed.', } );