[8.x] [Security Solution][Detection Engine] Avoid creating list items for empty lines in import list API (#192681) (#194470)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution][Detection Engine] Avoid creating list items for
empty lines in import list API
(#192681)](https://github.com/elastic/kibana/pull/192681)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Marshall
Main","email":"55718608+marshallmain@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-09-30T18:07:39Z","message":"[Security
Solution][Detection Engine] Avoid creating list items for empty lines in
import list API (#192681)\n\n## Summary\r\n\r\nThe quickstart tooling
introduced in\r\nhttps://github.com/elastic/kibana/pull/190634 uses
axios under the hood\r\nto make requests to Kibana. When attaching file
data to the axios\r\nrequest with `FormData`, axios adds an extra empty
line after the end\r\ncontent boundary. The logic in `buffer_lines.ts`
assumes that there are\r\nno more lines after the end content boundary
line, so importing a list\r\nwith the quickstart tooling would create a
list with an extra empty\r\nitem. This empty item fails validation when
retrieved through other\r\nAPIs.\r\n\r\nThis PR prevents lines after the
end content boundary from being turned\r\ninto list items in the import
list
API.","sha":"5f83ac05991cd980ef5b205acd19c997b60045a3","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","Team:Detection
Engine","v8.16.0"],"title":"[Security Solution][Detection Engine] Avoid
creating list items for empty lines in import list
API","number":192681,"url":"https://github.com/elastic/kibana/pull/192681","mergeCommit":{"message":"[Security
Solution][Detection Engine] Avoid creating list items for empty lines in
import list API (#192681)\n\n## Summary\r\n\r\nThe quickstart tooling
introduced in\r\nhttps://github.com/elastic/kibana/pull/190634 uses
axios under the hood\r\nto make requests to Kibana. When attaching file
data to the axios\r\nrequest with `FormData`, axios adds an extra empty
line after the end\r\ncontent boundary. The logic in `buffer_lines.ts`
assumes that there are\r\nno more lines after the end content boundary
line, so importing a list\r\nwith the quickstart tooling would create a
list with an extra empty\r\nitem. This empty item fails validation when
retrieved through other\r\nAPIs.\r\n\r\nThis PR prevents lines after the
end content boundary from being turned\r\ninto list items in the import
list
API.","sha":"5f83ac05991cd980ef5b205acd19c997b60045a3"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/192681","number":192681,"mergeCommit":{"message":"[Security
Solution][Detection Engine] Avoid creating list items for empty lines in
import list API (#192681)\n\n## Summary\r\n\r\nThe quickstart tooling
introduced in\r\nhttps://github.com/elastic/kibana/pull/190634 uses
axios under the hood\r\nto make requests to Kibana. When attaching file
data to the axios\r\nrequest with `FormData`, axios adds an extra empty
line after the end\r\ncontent boundary. The logic in `buffer_lines.ts`
assumes that there are\r\nno more lines after the end content boundary
line, so importing a list\r\nwith the quickstart tooling would create a
list with an extra empty\r\nitem. This empty item fails validation when
retrieved through other\r\nAPIs.\r\n\r\nThis PR prevents lines after the
end content boundary from being turned\r\ninto list items in the import
list
API.","sha":"5f83ac05991cd980ef5b205acd19c997b60045a3"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Marshall Main <55718608+marshallmain@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-10-01 05:37:00 +10:00 committed by GitHub
parent 08d5c08021
commit 4c6f2e9317
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 63 deletions

View file

@ -23,72 +23,15 @@ describe('buffer_lines', () => {
}).toThrow('bufferSize must be greater than zero');
});
test('it can read a single line', (done) => {
const input = new TestReadable();
input.push('line one\n');
input.push(null);
const bufferedLine = new BufferLines({ bufferSize: IMPORT_BUFFER_SIZE, input });
let linesToTest: string[] = [];
bufferedLine.on('lines', (lines: string[]) => {
linesToTest = [...linesToTest, ...lines];
});
bufferedLine.on('close', () => {
expect(linesToTest).toEqual(['line one']);
done();
});
});
test('it can read a single line using a buffer size of 1', (done) => {
const input = new TestReadable();
input.push('line one\n');
input.push(null);
const bufferedLine = new BufferLines({ bufferSize: 1, input });
let linesToTest: string[] = [];
bufferedLine.on('lines', (lines: string[]) => {
linesToTest = [...linesToTest, ...lines];
});
bufferedLine.on('close', () => {
expect(linesToTest).toEqual(['line one']);
done();
});
});
test('it can read two lines', (done) => {
const input = new TestReadable();
input.push('line one\n');
input.push('line two\n');
input.push(null);
const bufferedLine = new BufferLines({ bufferSize: IMPORT_BUFFER_SIZE, input });
let linesToTest: string[] = [];
bufferedLine.on('lines', (lines: string[]) => {
linesToTest = [...linesToTest, ...lines];
});
bufferedLine.on('close', () => {
expect(linesToTest).toEqual(['line one', 'line two']);
done();
});
});
test('it can read two lines using a buffer size of 1', (done) => {
const input = new TestReadable();
input.push('line one\n');
input.push('line two\n');
input.push(null);
const bufferedLine = new BufferLines({ bufferSize: 1, input });
let linesToTest: string[] = [];
bufferedLine.on('lines', (lines: string[]) => {
linesToTest = [...linesToTest, ...lines];
});
bufferedLine.on('close', () => {
expect(linesToTest).toEqual(['line one', 'line two']);
done();
});
});
test('two identical lines are collapsed into just one line without duplicates', (done) => {
const input = new TestReadable();
input.push('--boundary\n');
input.push('Content-type: text/plain\n');
input.push('Content-Disposition: form-data; name="fieldName"; filename="filename.text"\n');
input.push('\n');
input.push('line one\n');
input.push('line one\n');
input.push('--boundary--\n');
input.push(null);
const bufferedLine = new BufferLines({ bufferSize: IMPORT_BUFFER_SIZE, input });
let linesToTest: string[] = [];
@ -118,9 +61,14 @@ describe('buffer_lines', () => {
test('it can read 200 lines', (done) => {
const input = new TestReadable();
const bufferedLine = new BufferLines({ bufferSize: IMPORT_BUFFER_SIZE, input });
input.push('--boundary\n');
input.push('Content-type: text/plain\n');
input.push('Content-Disposition: form-data; name="fieldName"; filename="filename.text"\n');
input.push('\n');
let linesToTest: string[] = [];
const size200: string[] = new Array(200).fill(null).map((_, index) => `${index}\n`);
size200.forEach((element) => input.push(element));
input.push('--boundary--\n');
input.push(null);
bufferedLine.on('lines', (lines: string[]) => {
linesToTest = [...linesToTest, ...lines];
@ -154,6 +102,30 @@ describe('buffer_lines', () => {
});
});
test('it does not create empty values for lines after the end boundary', (done) => {
const input = new TestReadable();
input.push('--boundary\n');
input.push('Content-type: text/plain\n');
input.push('Content-Disposition: form-data; name="fieldName"; filename="filename.text"\n');
input.push('\n');
input.push('127.0.0.1\n');
input.push('127.0.0.2\n');
input.push('127.0.0.3\n');
input.push('\n');
input.push('--boundary--\n');
input.push('\n');
input.push(null);
const bufferedLine = new BufferLines({ bufferSize: IMPORT_BUFFER_SIZE, input });
let linesToTest: string[] = [];
bufferedLine.on('lines', (lines: string[]) => {
linesToTest = [...linesToTest, ...lines];
});
bufferedLine.on('close', () => {
expect(linesToTest).toEqual(['127.0.0.1', '127.0.0.2', '127.0.0.3']);
done();
});
});
test('it can read an empty multi-part message', (done) => {
const input = new TestReadable();
input.push('--boundary\n');

View file

@ -48,7 +48,7 @@ export class BufferLines extends Readable {
// we are at the end of the stream
this.boundary = null;
this.readableText = false;
} else {
} else if (this.readableText) {
// we have actual content to push
this.push(line);
}

View file

@ -49,6 +49,12 @@ describe('write_lines_to_bulk_list_items', () => {
test('It imports a set of items to a write buffer by calling "getListItemByValues" with a single value given', async () => {
const options = getImportListItemsToStreamOptionsMock();
const promise = importListItemsToStream(options);
options.stream.push('--boundary\n');
options.stream.push('Content-type: text/plain\n');
options.stream.push(
'Content-Disposition: form-data; name="fieldName"; filename="filename.text"\n'
);
options.stream.push('\n');
options.stream.push('127.0.0.1\n');
options.stream.push(null);
await promise;
@ -58,6 +64,12 @@ describe('write_lines_to_bulk_list_items', () => {
test('It imports a set of items to a write buffer by calling "getListItemByValues" with two values given', async () => {
const options = getImportListItemsToStreamOptionsMock();
const promise = importListItemsToStream(options);
options.stream.push('--boundary\n');
options.stream.push('Content-type: text/plain\n');
options.stream.push(
'Content-Disposition: form-data; name="fieldName"; filename="filename.text"\n'
);
options.stream.push('\n');
options.stream.push('127.0.0.1\n');
options.stream.push('127.0.0.2\n');
options.stream.push(null);