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 ec9a01f280d0e..d34a67087e40e 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); }); @@ -190,16 +201,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/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/public/exceptions/translations/shared_list.ts b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts index eb18283577369..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 @@ -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: 'Multiple exception lists for Endpoint Security are not allowed.', + } +); + export const READ_ONLY_BADGE_TOOLTIP = i18n.translate( 'xpack.securitySolution.exceptions.badge.readOnly.tooltip', { 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'); +};