Skip to content

Commit

Permalink
Merge branch 'main' into controls/relevantDataViewId
Browse files Browse the repository at this point in the history
  • Loading branch information
kibanamachine committed Mar 24, 2022
2 parents 972f931 + 04691d7 commit 10c8a70
Show file tree
Hide file tree
Showing 264 changed files with 6,047 additions and 1,040 deletions.
2 changes: 1 addition & 1 deletion docs/developer/plugin-list.asciidoc
Expand Up @@ -432,7 +432,7 @@ security and spaces filtering.
|{kib-repo}blob/{branch}/x-pack/plugins/event_log/README.md[eventLog]
|The event log plugin provides a persistent history of alerting and action
actitivies.
activities.
|{kib-repo}blob/{branch}/x-pack/plugins/features/README.md[features]
Expand Down
9 changes: 9 additions & 0 deletions docs/user/security/audit-logging.asciidoc
Expand Up @@ -155,6 +155,15 @@ Refer to the corresponding {es} logs for potential write errors.
| `unknown` | User is updating an alert.
| `failure` | User is not authorized to update an alert.

.2+| `rule_snooze`
| `unknown` | User is snoozing a rule.
| `failure` | User is not authorized to snooze a rule.

.2+| `rule_unsnooze`
| `unknown` | User is unsnoozing a rule.
| `failure` | User is not authorized to unsnooze a rule.


3+a|
====== Type: deletion

Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -849,7 +849,7 @@
"mochawesome-merge": "^4.2.1",
"mock-fs": "^5.1.2",
"mock-http-server": "1.3.0",
"ms-chromium-edge-driver": "^0.4.3",
"ms-chromium-edge-driver": "^0.5.1",
"multimatch": "^4.0.0",
"mutation-observer": "^1.0.3",
"ncp": "^2.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-es-query/src/index.ts
Expand Up @@ -104,6 +104,7 @@ export {
nodeBuilder,
nodeTypes,
toElasticsearchQuery,
escapeKuery,
} from './kuery';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-es-query/src/kuery/index.ts
Expand Up @@ -23,5 +23,6 @@ export const toElasticsearchQuery = (...params: Parameters<typeof astToElasticse
export { KQLSyntaxError } from './kuery_syntax_error';
export { nodeTypes, nodeBuilder } from './node_types';
export { fromKueryExpression } from './ast';
export { escapeKuery } from './utils';
export type { FunctionTypeBuildNode, NodeTypes } from './node_types';
export type { DslQuery, KueryNode, KueryQueryOptions, KueryParseOptions } from './types';
60 changes: 60 additions & 0 deletions packages/kbn-es-query/src/kuery/utils/escape_kuery.test.ts
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { escapeKuery } from './escape_kuery';

describe('escapeKuery', () => {
test('should escape special characters', () => {
const value = `This \\ has (a lot of) <special> characters, don't you *think*? "Yes."`;
const expected = `This \\\\ has \\(a lot of\\) \\<special\\> characters, don't you \\*think\\*? \\"Yes.\\"`;

expect(escapeKuery(value)).toBe(expected);
});

test('should escape keywords', () => {
const value = 'foo and bar or baz not qux';
const expected = 'foo \\and bar \\or baz \\not qux';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape keywords next to each other', () => {
const value = 'foo and bar or not baz';
const expected = 'foo \\and bar \\or \\not baz';

expect(escapeKuery(value)).toBe(expected);
});

test('should not escape keywords without surrounding spaces', () => {
const value = 'And this has keywords, or does it not?';
const expected = 'And this has keywords, \\or does it not?';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape uppercase keywords', () => {
const value = 'foo AND bar';
const expected = 'foo \\AND bar';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape both keywords and special characters', () => {
const value = 'Hello, world, and <nice> to meet you!';
const expected = 'Hello, world, \\and \\<nice\\> to meet you!';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape newlines and tabs', () => {
const value = 'This\nhas\tnewlines\r\nwith\ttabs';
const expected = 'This\\nhas\\tnewlines\\r\\nwith\\ttabs';

expect(escapeKuery(value)).toBe(expected);
});
});
34 changes: 34 additions & 0 deletions packages/kbn-es-query/src/kuery/utils/escape_kuery.ts
@@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { flow } from 'lodash';

/**
* Escapes a Kuery node value to ensure that special characters, operators, and whitespace do not result in a parsing error or unintended
* behavior when using the value as an argument for the `buildNode` function.
*/
export const escapeKuery = flow(escapeSpecialCharacters, escapeAndOr, escapeNot, escapeWhitespace);

// See the SpecialCharacter rule in kuery.peg
function escapeSpecialCharacters(str: string) {
return str.replace(/[\\():<>"*]/g, '\\$&'); // $& means the whole matched string
}

// See the Keyword rule in kuery.peg
function escapeAndOr(str: string) {
return str.replace(/(\s+)(and|or)(\s+)/gi, '$1\\$2$3');
}

function escapeNot(str: string) {
return str.replace(/not(\s+)/gi, '\\$&');
}

// See the Space rule in kuery.peg
function escapeWhitespace(str: string) {
return str.replace(/\t/g, '\\t').replace(/\r/g, '\\r').replace(/\n/g, '\\n');
}
9 changes: 9 additions & 0 deletions packages/kbn-es-query/src/kuery/utils/index.ts
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { escapeKuery } from './escape_kuery';
Expand Up @@ -75,6 +75,7 @@ const previouslyRegisteredTypes = [
'ml-telemetry',
'monitoring-telemetry',
'osquery-pack',
'osquery-pack-asset',
'osquery-saved-query',
'osquery-usage-metric',
'osquery-manager-usage-metric',
Expand Down
Expand Up @@ -14,10 +14,7 @@ jest.mock('../../../../elasticsearch', () => {
return { getErrorMessage: mockGetEsErrorMessage };
});

// Mock these functions to return empty results, as this simplifies test cases and we don't need to exercise alternate code paths for these
jest.mock('@kbn/es-query', () => {
return { nodeTypes: { function: { buildNode: jest.fn() } } };
});
// Mock this function to return empty results, as this simplifies test cases and we don't need to exercise alternate code paths for these
jest.mock('../search_dsl', () => {
return { getSearchDsl: jest.fn() };
});
Expand Up @@ -32,8 +32,9 @@ describe('deleteLegacyUrlAliases', () => {
};
}

const type = 'obj-type';
const id = 'obj-id';
// Include KQL special characters in the object type/ID to implicitly assert that the kuery node builder handles it gracefully
const type = 'obj-type:"';
const id = 'id-1:"';

it('throws an error if namespaces includes the "all namespaces" string', async () => {
const namespaces = [ALL_NAMESPACES_STRING];
Expand Down
Expand Up @@ -62,11 +62,6 @@ export async function deleteLegacyUrlAliases(params: DeleteLegacyUrlAliasesParam
return;
}

const { buildNode } = esKuery.nodeTypes.function;
const match1 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.targetType`, type);
const match2 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.targetId`, id);
const kueryNode = buildNode('and', [match1, match2]);

try {
await client.updateByQuery(
{
Expand All @@ -75,7 +70,7 @@ export async function deleteLegacyUrlAliases(params: DeleteLegacyUrlAliasesParam
body: {
...getSearchDsl(mappings, registry, {
type: LEGACY_URL_ALIAS_TYPE,
kueryNode,
kueryNode: createKueryNode(type, id),
}),
script: {
// Intentionally use one script source with variable params to take advantage of ES script caching
Expand Down Expand Up @@ -107,3 +102,17 @@ export async function deleteLegacyUrlAliases(params: DeleteLegacyUrlAliasesParam
function throwError(type: string, id: string, message: string) {
throw new Error(`Failed to delete legacy URL aliases for ${type}/${id}: ${message}`);
}

function getKueryKey(attribute: string) {
// Note: these node keys do NOT include '.attributes' for type-level fields because we are using the query in the ES client (instead of the SO client)
return `${LEGACY_URL_ALIAS_TYPE}.${attribute}`;
}

export function createKueryNode(type: string, id: string) {
const { buildNode } = esKuery.nodeTypes.function;
// Escape Kuery values to prevent parsing errors and unintended behavior (object types/IDs can contain KQL special characters/operators)
const match1 = buildNode('is', getKueryKey('targetType'), esKuery.escapeKuery(type));
const match2 = buildNode('is', getKueryKey('targetId'), esKuery.escapeKuery(id));
const kueryNode = buildNode('and', [match1, match2]);
return kueryNode;
}
Expand Up @@ -51,7 +51,8 @@ describe('findLegacyUrlAliases', () => {
});
}

const obj1 = { type: 'obj-type', id: 'id-1' };
// Include KQL special characters in the object type/ID to implicitly assert that the kuery node builder handles it gracefully
const obj1 = { type: 'obj-type:"', id: 'id-1:"' };
const obj2 = { type: 'obj-type', id: 'id-2' };
const obj3 = { type: 'obj-type', id: 'id-3' };

Expand Down
Expand Up @@ -68,15 +68,20 @@ export async function findLegacyUrlAliases(

function createAliasKueryFilter(objects: Array<{ type: string; id: string }>) {
const { buildNode } = esKuery.nodeTypes.function;
// Note: these nodes include '.attributes' for type-level fields because these are eventually passed to `validateConvertFilterToKueryNode`, which requires it
const kueryNodes = objects.reduce<unknown[]>((acc, { type, id }) => {
const match1 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.attributes.targetType`, type);
const match2 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.attributes.sourceId`, id);
// Escape Kuery values to prevent parsing errors and unintended behavior (object types/IDs can contain KQL special characters/operators)
const match1 = buildNode('is', getKueryKey('targetType'), esKuery.escapeKuery(type));
const match2 = buildNode('is', getKueryKey('sourceId'), esKuery.escapeKuery(id));
acc.push(buildNode('and', [match1, match2]));
return acc;
}, []);
return buildNode('and', [
buildNode('not', buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.attributes.disabled`, true)), // ignore aliases that have been disabled
buildNode('not', buildNode('is', getKueryKey('disabled'), true)), // ignore aliases that have been disabled
buildNode('or', kueryNodes),
]);
}

function getKueryKey(attribute: string) {
// Note: these node keys include '.attributes' for type-level fields because these are eventually passed to `validateConvertFilterToKueryNode`, which requires it
return `${LEGACY_URL_ALIAS_TYPE}.attributes.${attribute}`;
}
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { escapeQuotes, escapeKuery } from './escape_kuery';
import { escapeQuotes } from './escape_kuery';

describe('Kuery escape', () => {
test('should escape quotes', () => {
Expand All @@ -22,53 +22,4 @@ describe('Kuery escape', () => {

expect(escapeQuotes(value)).toBe(expected);
});

test('should escape special characters', () => {
const value = `This \\ has (a lot of) <special> characters, don't you *think*? "Yes."`;
const expected = `This \\\\ has \\(a lot of\\) \\<special\\> characters, don't you \\*think\\*? \\"Yes.\\"`;

expect(escapeKuery(value)).toBe(expected);
});

test('should escape keywords', () => {
const value = 'foo and bar or baz not qux';
const expected = 'foo \\and bar \\or baz \\not qux';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape keywords next to each other', () => {
const value = 'foo and bar or not baz';
const expected = 'foo \\and bar \\or \\not baz';

expect(escapeKuery(value)).toBe(expected);
});

test('should not escape keywords without surrounding spaces', () => {
const value = 'And this has keywords, or does it not?';
const expected = 'And this has keywords, \\or does it not?';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape uppercase keywords', () => {
const value = 'foo AND bar';
const expected = 'foo \\AND bar';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape both keywords and special characters', () => {
const value = 'Hello, world, and <nice> to meet you!';
const expected = 'Hello, world, \\and \\<nice\\> to meet you!';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape newlines and tabs', () => {
const value = 'This\nhas\tnewlines\r\nwith\ttabs';
const expected = 'This\\nhas\\tnewlines\\r\\nwith\\ttabs';

expect(escapeKuery(value)).toBe(expected);
});
});
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { flow } from 'lodash';
import { escapeKuery } from '@kbn/es-query';

/**
* Escapes backslashes and double-quotes. (Useful when putting a string in quotes to use as a value
Expand All @@ -16,23 +16,5 @@ export function escapeQuotes(str: string) {
return str.replace(/[\\"]/g, '\\$&');
}

export const escapeKuery = flow(escapeSpecialCharacters, escapeAndOr, escapeNot, escapeWhitespace);

// See the SpecialCharacter rule in kuery.peg
function escapeSpecialCharacters(str: string) {
return str.replace(/[\\():<>"*]/g, '\\$&'); // $& means the whole matched string
}

// See the Keyword rule in kuery.peg
function escapeAndOr(str: string) {
return str.replace(/(\s+)(and|or)(\s+)/gi, '$1\\$2$3');
}

function escapeNot(str: string) {
return str.replace(/not(\s+)/gi, '\\$&');
}

// See the Space rule in kuery.peg
function escapeWhitespace(str: string) {
return str.replace(/\t/g, '\\t').replace(/\r/g, '\\r').replace(/\n/g, '\\n');
}
// Re-export this function from the @kbn/es-query package to avoid refactoring
export { escapeKuery };

0 comments on commit 10c8a70

Please sign in to comment.