[Security Solution] File paths for Blocklist Windows and Mac should be case insensitive (#164200)

## Summary

This fixes a bug where Windows and Mac Blocklist file path entries
should be passed as case insensitive. This is because Mac and Windows
are caseless for most use cases.

Bug ticket: https://github.com/elastic/kibana/issues/158581

Here is how it will be displayed in the UI:
<img width="1728" alt="image"
src="a3006397-f49e-4de0-818d-94e2de20dba3">

Here are the breakdown of the artifacts after the fix:

Linux:
```
-------------------------------------------------------------------
Policy:   Protect
Manifest: 1.0.6 | v1
Artifact: endpoint-blocklist-linux-v1
          Relative URL:   /api/fleet/artifacts/endpoint-blocklist-linux-v1/f33e6890aeced00861c26a08121dd42d2d29ba08abfeb3c065d0447e32e18640
          Encoded SHA256: a907835be40af89b8b7aa23a6efc66c01ceaa5a19622edd378139319f3ca5fa0
          Decoded SHA256: f33e6890aeced00861c26a08121dd42d2d29ba08abfeb3c065d0447e32e18640
-------------------------------------------------------------------

{
  "entries": [
    {
      "type": "simple",
      "entries": [
        {
          "field": "file.path",
          "operator": "included",
          "type": "exact_cased_any",
          "value": [
            "/opt/bin/bin.exe"
          ]
        }
      ]
    }
  ]
}
```

Mac:
```
-------------------------------------------------------------------
Policy:   Protect
Manifest: 1.0.6 | v1
Artifact: endpoint-blocklist-macos-v1
          Relative URL:   /api/fleet/artifacts/endpoint-blocklist-macos-v1/b28e7978da4314ebc2c94770e0638fc4b2270f9dc17a11d6d32b8634b1fbec0f
          Encoded SHA256: 4f3e80d688f5cae4bf6a88b0704e37909f9fa4f47fe8325b7b154cddd46a2db9
          Decoded SHA256: b28e7978da4314ebc2c94770e0638fc4b2270f9dc17a11d6d32b8634b1fbec0f
-------------------------------------------------------------------

{
  "entries": [
    {
      "type": "simple",
      "entries": [
        {
          "field": "file.path",
          "operator": "included",
          "type": "exact_caseless_any",
          "value": [
            "/opt/exe.exe"
          ]
        }
      ]
    }
```

Windows:
```
-------------------------------------------------------------------
Policy:   Protect
Manifest: 1.0.6 | v1
Artifact: endpoint-blocklist-windows-v1
          Relative URL:   /api/fleet/artifacts/endpoint-blocklist-windows-v1/2a6fcc67c696ad4e29d91f8b685bff46977198cd34b9a61e8003d55b78dff6ac
          Encoded SHA256: c6e045fce97651336eeb400f0123541475b940e3aa38ce721f299585683da288
          Decoded SHA256: 2a6fcc67c696ad4e29d91f8b685bff46977198cd34b9a61e8003d55b78dff6ac
-------------------------------------------------------------------

{
  "entries": [
    {
      "type": "simple",
      "entries": [
        {
          "field": "file.path",
          "operator": "included",
          "type": "exact_caseless_any",
          "value": [
            "C:\\path\\path.exe"
          ]
        }
      ]
    }
  ]
}
```

### Checklist

Delete any items that are not applicable to this PR.

- [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

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Kevin Logan 2023-08-21 10:00:53 -04:00 committed by GitHub
parent 011ae97061
commit 88bd71c077
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 83 additions and 14 deletions

View file

@ -33,7 +33,11 @@ export type TrustedAppConditionEntryField =
| 'process.hash.*'
| 'process.executable.caseless'
| 'process.Ext.code_signature';
export type BlocklistConditionEntryField = 'file.hash.*' | 'file.path' | 'file.Ext.code_signature';
export type BlocklistConditionEntryField =
| 'file.hash.*'
| 'file.path'
| 'file.Ext.code_signature'
| 'file.path.caseless';
export type AllConditionEntryFields =
| TrustedAppConditionEntryField
| BlocklistConditionEntryField

View file

@ -358,7 +358,7 @@ export const getArtifactsListTestsData = (): ArtifactsFixtureType[] => [
},
{
type: 'click',
selector: 'blocklist-form-file.path',
selector: 'blocklist-form-file.path.caseless',
},
{
type: 'click',
@ -379,7 +379,7 @@ export const getArtifactsListTestsData = (): ArtifactsFixtureType[] => [
{
selector: 'blocklistPage-card-criteriaConditions',
value:
'OSIS WindowsAND file.pathis one of\nc:\\randomFolder\\randomFile.exe\nc:\\randomFolder\\randomFile2.exe',
'OSIS WindowsAND file.path.caselessis one of\nc:\\randomFolder\\randomFile.exe\nc:\\randomFolder\\randomFile2.exe',
},
{
selector: 'blocklistPage-card-header-title',

View file

@ -76,6 +76,12 @@ export const CONDITION_FIELD_TITLE: { [K in BlocklistConditionEntryField]: strin
'file.path': i18n.translate('xpack.securitySolution.blocklist.entry.field.path', {
defaultMessage: 'Path',
}),
'file.path.caseless': i18n.translate(
'xpack.securitySolution.blocklist.entry.field.path.caseless',
{
defaultMessage: 'Path',
}
),
'file.Ext.code_signature': i18n.translate(
'xpack.securitySolution.blocklist.entry.field.signature',
{ defaultMessage: 'Signature' }
@ -89,6 +95,12 @@ export const CONDITION_FIELD_DESCRIPTION: { [K in BlocklistConditionEntryField]:
'file.path': i18n.translate('xpack.securitySolution.blocklist.entry.field.description.path', {
defaultMessage: 'The full path of the application',
}),
'file.path.caseless': i18n.translate(
'xpack.securitySolution.blocklist.entry.field.description.path.caseless',
{
defaultMessage: 'The full path of the application (case insenstive)',
}
),
'file.Ext.code_signature': i18n.translate(
'xpack.securitySolution.blocklist.entry.field.description.signature',
{ defaultMessage: 'The signer of the application' }

View file

@ -225,6 +225,38 @@ describe('blocklist form', () => {
userEvent.click(screen.getByRole('option', { name: /path/i }));
const expected = createOnChangeArgs({
item: createItem({
entries: [createEntry('file.path.caseless', [])],
}),
});
expect(onChangeSpy).toHaveBeenCalledWith(expected);
});
it('should correctly create `file.path.caseless` when Mac OS is selected', async () => {
render(createProps({ item: createItem({ os_types: [OperatingSystem.MAC] }) }));
expect(screen.getByTestId('blocklist-form-os-select').textContent).toEqual('Mac');
userEvent.click(screen.getByTestId('blocklist-form-field-select'));
await waitForEuiPopoverOpen();
userEvent.click(screen.getByRole('option', { name: /path/i }));
const expected = createOnChangeArgs({
item: createItem({
os_types: [OperatingSystem.MAC],
entries: [createEntry('file.path.caseless', [])],
}),
});
expect(onChangeSpy).toHaveBeenCalledWith(expected);
});
it('should correctly create `file.path` when Linux is selected', async () => {
render(createProps({ item: createItem({ os_types: [OperatingSystem.LINUX] }) }));
expect(screen.getByTestId('blocklist-form-os-select').textContent).toEqual('Linux');
userEvent.click(screen.getByTestId('blocklist-form-field-select'));
await waitForEuiPopoverOpen();
userEvent.click(screen.getByRole('option', { name: /path/i }));
const expected = createOnChangeArgs({
item: createItem({
os_types: [OperatingSystem.LINUX],
entries: [createEntry('file.path', [])],
}),
});

View file

@ -178,14 +178,31 @@ export const BlockListForm = memo<ArtifactFormComponentProps>(
);
const fieldOptions: Array<EuiSuperSelectOption<BlocklistConditionEntryField>> = useMemo(() => {
const selectableFields: Array<EuiSuperSelectOption<BlocklistConditionEntryField>> = (
['file.hash.*', 'file.path'] as BlocklistConditionEntryField[]
).map((field) => ({
value: field,
inputDisplay: CONDITION_FIELD_TITLE[field],
dropdownDisplay: getDropdownDisplay(field),
'data-test-subj': getTestId(field),
}));
const selectableFields: Array<EuiSuperSelectOption<BlocklistConditionEntryField>> = [];
selectableFields.push({
value: 'file.hash.*',
inputDisplay: CONDITION_FIELD_TITLE['file.hash.*'],
dropdownDisplay: getDropdownDisplay('file.hash.*'),
'data-test-subj': getTestId('file.hash.*'),
});
if (selectedOs === OperatingSystem.LINUX) {
selectableFields.push({
value: 'file.path',
inputDisplay: CONDITION_FIELD_TITLE['file.path'],
dropdownDisplay: getDropdownDisplay('file.path'),
'data-test-subj': getTestId('file.path'),
});
} else {
selectableFields.push({
value: 'file.path.caseless',
inputDisplay: CONDITION_FIELD_TITLE['file.path.caseless'],
dropdownDisplay: getDropdownDisplay('file.path.caseless'),
'data-test-subj': getTestId('file.path.caseless'),
});
}
if (selectedOs === OperatingSystem.WINDOWS) {
selectableFields.push({
value: 'file.Ext.code_signature',

View file

@ -21,12 +21,16 @@ import { isValidHash } from '../../../../common/endpoint/service/artifacts/valid
import { EndpointArtifactExceptionValidationError } from './errors';
const allowedHashes: Readonly<string[]> = ['file.hash.md5', 'file.hash.sha1', 'file.hash.sha256'];
const allowedFilePaths: Readonly<string[]> = ['file.path', 'file.path.caseless'];
const FileHashField = schema.oneOf(
allowedHashes.map((hash) => schema.literal(hash)) as [Type<string>]
);
const FilePath = schema.literal('file.path');
const FilePath = schema.oneOf(
allowedFilePaths.map((path) => schema.literal(path)) as [Type<string>]
);
const FileCodeSigner = schema.literal('file.Ext.code_signature');
const ConditionEntryTypeSchema = schema.literal('match_any');

View file

@ -423,7 +423,7 @@ export const getArtifactsListTestsData = () => [
{
selector: 'blocklistPage-card-criteriaConditions',
value:
'OSIS Windows\nAND file.pathIS ONE OF\nc:\\randomFolder\\randomFile.exe\nc:\\randomFolder\\randomFile2.exe',
'OSIS Windows\nAND file.path.caselessIS ONE OF\nc:\\randomFolder\\randomFile.exe\nc:\\randomFolder\\randomFile2.exe',
},
{
selector: 'blocklistPage-card-header-title',
@ -499,7 +499,7 @@ export const getArtifactsListTestsData = () => [
{
field: 'file.path',
operator: 'included',
type: 'exact_cased_any',
type: 'exact_caseless_any',
value: ['c:\\randomFolder\\randomFile.exe', ' c:\\randomFolder\\randomFile2.exe'],
},
],