From bf42128f1bfaea4a887e99d0dc3e1872f302a868 Mon Sep 17 00:00:00 2001 From: Karl Godard Date: Mon, 15 May 2023 13:00:23 -0700 Subject: [PATCH] [D4C] fix to targetFilePath/processExecutable regex, and null check (#157771) ## Summary Fixes the path regex to allow /* and /** values, also fixed a null pointer in yaml editor. ### Checklist - [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 --- .../cloud_defend/public/common/utils.ts | 13 ++++-- .../index.test.tsx | 42 ++++++++++++------- .../hooks/policy_schema.json | 6 ++- x-pack/plugins/cloud_defend/public/types.ts | 4 +- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/cloud_defend/public/common/utils.ts b/x-pack/plugins/cloud_defend/public/common/utils.ts index 2e10d8982f82e..8200bb866db8a 100644 --- a/x-pack/plugins/cloud_defend/public/common/utils.ts +++ b/x-pack/plugins/cloud_defend/public/common/utils.ts @@ -178,15 +178,22 @@ export function validateMaxSelectorsAndResponses(selectors: Selector[], response return errors; } -export function validateStringValuesForCondition(condition: SelectorCondition, values: string[]) { +export function validateStringValuesForCondition(condition: SelectorCondition, values?: string[]) { const errors: string[] = []; const maxValueBytes = SelectorConditionsMap[condition].maxValueBytes || MAX_CONDITION_VALUE_LENGTH_BYTES; const { pattern, patternError } = SelectorConditionsMap[condition]; - values.forEach((value) => { - if (pattern && !new RegExp(pattern).test(value)) { + values?.forEach((value) => { + if (value?.length === 0) { + errors.push( + i18n.translate('xpack.cloudDefend.errorGenericEmptyValue', { + defaultMessage: '"{condition}" values cannot be empty', + values: { condition }, + }) + ); + } else if (pattern && !new RegExp(pattern).test(value)) { if (patternError) { errors.push(patternError); } else { diff --git a/x-pack/plugins/cloud_defend/public/components/control_general_view_selector/index.test.tsx b/x-pack/plugins/cloud_defend/public/components/control_general_view_selector/index.test.tsx index e54047a169a6e..5ab2886de9604 100644 --- a/x-pack/plugins/cloud_defend/public/components/control_general_view_selector/index.test.tsx +++ b/x-pack/plugins/cloud_defend/public/components/control_general_view_selector/index.test.tsx @@ -300,22 +300,27 @@ describe('', () => { } updatedSelector = onChange.mock.calls[1][0]; + expect(updatedSelector.hasErrors).toBeFalsy(); rerender(); - expect(findByText(errorStr)).toMatchObject({}); userEvent.type(el, '/*{enter}'); updatedSelector = onChange.mock.calls[2][0]; + expect(updatedSelector.hasErrors).toBeFalsy(); rerender(); - expect(findByText(errorStr)).toMatchObject({}); userEvent.type(el, 'badpath{enter}'); updatedSelector = onChange.mock.calls[3][0]; - + expect(updatedSelector.hasErrors).toBeTruthy(); rerender(); - expect(getByText(errorStr)).toBeTruthy(); + + userEvent.type(el, ' {enter}'); + updatedSelector = onChange.mock.calls[4][0]; + expect(updatedSelector.hasErrors).toBeTruthy(); + rerender(); + expect(getByText('"targetFilePath" values cannot be empty')).toBeTruthy(); }); it('validates processExecutable conditions values', async () => { @@ -336,7 +341,7 @@ describe('', () => { 'input' ); - const errorStr = i18n.errorInvalidProcessExecutable; + const regexError = i18n.errorInvalidProcessExecutable; if (el) { userEvent.type(el, '/usr/bin/**{enter}'); @@ -345,28 +350,33 @@ describe('', () => { } updatedSelector = onChange.mock.calls[1][0]; + expect(updatedSelector.hasErrors).toBeFalsy(); rerender(); - - expect(findByText(errorStr)).toMatchObject({}); + expect(findByText(regexError)).toMatchObject({}); userEvent.type(el, '/*{enter}'); updatedSelector = onChange.mock.calls[2][0]; + expect(updatedSelector.hasErrors).toBeFalsy(); rerender(); - - expect(findByText(errorStr)).toMatchObject({}); + expect(findByText(regexError)).toMatchObject({}); userEvent.type(el, '/usr/bin/ls{enter}'); updatedSelector = onChange.mock.calls[3][0]; + expect(updatedSelector.hasErrors).toBeFalsy(); rerender(); - - expect(findByText(errorStr)).toMatchObject({}); + expect(findByText(regexError)).toMatchObject({}); userEvent.type(el, 'badpath{enter}'); updatedSelector = onChange.mock.calls[4][0]; - + expect(updatedSelector.hasErrors).toBeTruthy(); rerender(); + expect(getByText(regexError)).toBeTruthy(); - expect(getByText(errorStr)).toBeTruthy(); + userEvent.type(el, ' {enter}'); + updatedSelector = onChange.mock.calls[4][0]; + expect(updatedSelector.hasErrors).toBeTruthy(); + rerender(); + expect(getByText('"processExecutable" values cannot be empty')).toBeTruthy(); }); it('validates containerImageFullName conditions values', async () => { @@ -385,7 +395,7 @@ describe('', () => { 'input' ); - const errorStr = i18n.errorInvalidFullContainerImageName; + const regexError = i18n.errorInvalidFullContainerImageName; if (el) { userEvent.type(el, 'docker.io/nginx{enter}'); @@ -396,13 +406,13 @@ describe('', () => { updatedSelector = onChange.mock.calls[1][0]; rerender(); - expect(findByText(errorStr)).toMatchObject({}); + expect(findByText(regexError)).toMatchObject({}); userEvent.type(el, 'nginx{enter}'); updatedSelector = onChange.mock.calls[2][0]; rerender(); - expect(getByText(errorStr)).toBeTruthy(); + expect(getByText(regexError)).toBeTruthy(); }); it('validates kubernetesPodLabel conditions values', async () => { diff --git a/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json b/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json index 0849fcb912e79..33d4a1c010caa 100644 --- a/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json +++ b/x-pack/plugins/cloud_defend/public/components/control_yaml_view/hooks/policy_schema.json @@ -177,7 +177,8 @@ "minItems": 1, "items": { "type": "string", - "pattern": "^(?:\\/[^\\/\\*]+)+(?:\\/\\*|\\/\\*\\*)?$" + "pattern": "^(?:\\/[^\\/\\*]+)*(?:\\/\\*|\\/\\*\\*)?$", + "minLength": 1 } }, "ignoreVolumeMounts": { @@ -319,7 +320,8 @@ "minItems": 1, "items": { "type": "string", - "pattern": "^(?:\\/[^\\/\\*]+)+(?:\\/\\*|\\/\\*\\*)?$" + "pattern": "^(?:\\/[^\\/\\*]+)*(?:\\/\\*|\\/\\*\\*)?$", + "minLength": 1 } }, "processName": { diff --git a/x-pack/plugins/cloud_defend/public/types.ts b/x-pack/plugins/cloud_defend/public/types.ts index d8f23302b3baa..2e9b864db5a44 100755 --- a/x-pack/plugins/cloud_defend/public/types.ts +++ b/x-pack/plugins/cloud_defend/public/types.ts @@ -134,7 +134,7 @@ export const SelectorConditionsMap: SelectorConditionsMapProps = { selectorType: 'file', type: 'stringArray', maxValueBytes: 255, - pattern: '^(?:\\/[^\\/\\*]+)+(?:\\/\\*|\\/\\*\\*)?$', + pattern: '^(?:\\/[^\\/\\*]+)*(?:\\/\\*|\\/\\*\\*)?$', patternError: i18n.errorInvalidTargetFilePath, }, ignoreVolumeFiles: { selectorType: 'file', type: 'flag', not: ['ignoreVolumeMounts'] }, @@ -143,7 +143,7 @@ export const SelectorConditionsMap: SelectorConditionsMapProps = { selectorType: 'process', type: 'stringArray', not: ['processName'], - pattern: '^(?:\\/[^\\/\\*]+)+(?:\\/\\*|\\/\\*\\*)?$', + pattern: '^(?:\\/[^\\/\\*]+)*(?:\\/\\*|\\/\\*\\*)?$', patternError: i18n.errorInvalidProcessExecutable, }, processName: {