parent
06429091cf
commit
2f06b32f11
26
.eslintrc.js
26
.eslintrc.js
|
@ -1196,6 +1196,32 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Osquery overrides
|
||||
*/
|
||||
{
|
||||
extends: ['eslint:recommended', 'plugin:react/recommended'],
|
||||
plugins: ['react'],
|
||||
files: ['x-pack/plugins/osquery/**/*.{js,mjs,ts,tsx}'],
|
||||
rules: {
|
||||
'arrow-body-style': ['error', 'as-needed'],
|
||||
'prefer-arrow-callback': 'error',
|
||||
'no-unused-vars': 'off',
|
||||
'react/prop-types': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// typescript and javascript for front end react performance
|
||||
files: ['x-pack/plugins/osquery/public/**/!(*.test).{js,mjs,ts,tsx}'],
|
||||
plugins: ['react', 'react-perf'],
|
||||
rules: {
|
||||
'react-perf/jsx-no-new-object-as-prop': 'error',
|
||||
'react-perf/jsx-no-new-array-as-prop': 'error',
|
||||
'react-perf/jsx-no-new-function-as-prop': 'error',
|
||||
'react/jsx-no-bind': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Prettier disables all conflicting rules, listing as last override so it takes precedence
|
||||
*/
|
||||
|
|
|
@ -456,6 +456,10 @@ Elastic.
|
|||
|This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/osquery/README.md[osquery]
|
||||
|This plugin adds extended support to Security Solution Fleet Osquery integration
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/painless_lab/README.md[painlessLab]
|
||||
|This plugin helps users learn how to use the Painless scripting language.
|
||||
|
||||
|
|
|
@ -103,4 +103,5 @@ pageLoadAssetSize:
|
|||
stackAlerts: 29684
|
||||
presentationUtil: 28545
|
||||
spacesOss: 18817
|
||||
osquery: 107090
|
||||
mapsFileUpload: 23775
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
"xpack.maps": ["plugins/maps"],
|
||||
"xpack.ml": ["plugins/ml"],
|
||||
"xpack.monitoring": ["plugins/monitoring"],
|
||||
"xpack.osquery": ["plugins/osquery"],
|
||||
"xpack.painlessLab": "plugins/painless_lab",
|
||||
"xpack.remoteClusters": "plugins/remote_clusters",
|
||||
"xpack.reporting": ["plugins/reporting"],
|
||||
|
|
9
x-pack/plugins/osquery/README.md
Executable file
9
x-pack/plugins/osquery/README.md
Executable file
|
@ -0,0 +1,9 @@
|
|||
# osquery
|
||||
|
||||
This plugin adds extended support to Security Solution Fleet Osquery integration
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment.
|
8
x-pack/plugins/osquery/common/constants.ts
Normal file
8
x-pack/plugins/osquery/common/constants.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000;
|
||||
export const DEFAULT_DARK_MODE = 'theme:darkMode';
|
9
x-pack/plugins/osquery/common/ecs/agent/index.ts
Normal file
9
x-pack/plugins/osquery/common/ecs/agent/index.ts
Normal file
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface AgentEcs {
|
||||
type?: string[];
|
||||
}
|
33
x-pack/plugins/osquery/common/ecs/auditd/index.ts
Normal file
33
x-pack/plugins/osquery/common/ecs/auditd/index.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface AuditdEcs {
|
||||
result?: string[];
|
||||
session?: string[];
|
||||
data?: AuditdDataEcs;
|
||||
summary?: SummaryEcs;
|
||||
sequence?: string[];
|
||||
}
|
||||
|
||||
export interface AuditdDataEcs {
|
||||
acct?: string[];
|
||||
terminal?: string[];
|
||||
op?: string[];
|
||||
}
|
||||
|
||||
export interface SummaryEcs {
|
||||
actor?: PrimarySecondaryEcs;
|
||||
object?: PrimarySecondaryEcs;
|
||||
how?: string[];
|
||||
message_type?: string[];
|
||||
sequence?: string[];
|
||||
}
|
||||
|
||||
export interface PrimarySecondaryEcs {
|
||||
primary?: string[];
|
||||
secondary?: string[];
|
||||
type?: string[];
|
||||
}
|
20
x-pack/plugins/osquery/common/ecs/cloud/index.ts
Normal file
20
x-pack/plugins/osquery/common/ecs/cloud/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface CloudEcs {
|
||||
instance?: CloudInstanceEcs;
|
||||
machine?: CloudMachineEcs;
|
||||
provider?: string[];
|
||||
region?: string[];
|
||||
}
|
||||
|
||||
export interface CloudMachineEcs {
|
||||
type?: string[];
|
||||
}
|
||||
|
||||
export interface CloudInstanceEcs {
|
||||
id?: string[];
|
||||
}
|
16
x-pack/plugins/osquery/common/ecs/destination/index.ts
Normal file
16
x-pack/plugins/osquery/common/ecs/destination/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { GeoEcs } from '../geo';
|
||||
|
||||
export interface DestinationEcs {
|
||||
bytes?: number[];
|
||||
ip?: string[];
|
||||
port?: number[];
|
||||
domain?: string[];
|
||||
geo?: GeoEcs;
|
||||
packets?: number[];
|
||||
}
|
16
x-pack/plugins/osquery/common/ecs/dns/index.ts
Normal file
16
x-pack/plugins/osquery/common/ecs/dns/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface DnsEcs {
|
||||
question?: DnsQuestionEcs;
|
||||
resolved_ip?: string[];
|
||||
response_code?: string[];
|
||||
}
|
||||
|
||||
export interface DnsQuestionEcs {
|
||||
name?: string[];
|
||||
type?: string[];
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { extendMap } from './extend_map';
|
||||
|
||||
describe('ecs_fields test', () => {
|
||||
describe('extendMap', () => {
|
||||
test('it should extend a record', () => {
|
||||
const osFieldsMap: Readonly<Record<string, string>> = {
|
||||
'os.platform': 'os.platform',
|
||||
'os.full': 'os.full',
|
||||
'os.family': 'os.family',
|
||||
'os.version': 'os.version',
|
||||
'os.kernel': 'os.kernel',
|
||||
};
|
||||
const expected: Record<string, string> = {
|
||||
'host.os.family': 'host.os.family',
|
||||
'host.os.full': 'host.os.full',
|
||||
'host.os.kernel': 'host.os.kernel',
|
||||
'host.os.platform': 'host.os.platform',
|
||||
'host.os.version': 'host.os.version',
|
||||
};
|
||||
expect(extendMap('host', osFieldsMap)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it should extend a sample hosts record', () => {
|
||||
const hostMap: Record<string, string> = {
|
||||
'host.id': 'host.id',
|
||||
'host.ip': 'host.ip',
|
||||
'host.name': 'host.name',
|
||||
};
|
||||
const osFieldsMap: Readonly<Record<string, string>> = {
|
||||
'os.platform': 'os.platform',
|
||||
'os.full': 'os.full',
|
||||
'os.family': 'os.family',
|
||||
'os.version': 'os.version',
|
||||
'os.kernel': 'os.kernel',
|
||||
};
|
||||
const expected: Record<string, string> = {
|
||||
'host.id': 'host.id',
|
||||
'host.ip': 'host.ip',
|
||||
'host.name': 'host.name',
|
||||
'host.os.family': 'host.os.family',
|
||||
'host.os.full': 'host.os.full',
|
||||
'host.os.kernel': 'host.os.kernel',
|
||||
'host.os.platform': 'host.os.platform',
|
||||
'host.os.version': 'host.os.version',
|
||||
};
|
||||
const output = { ...hostMap, ...extendMap('host', osFieldsMap) };
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
14
x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.ts
Normal file
14
x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const extendMap = (
|
||||
path: string,
|
||||
map: Readonly<Record<string, string>>
|
||||
): Readonly<Record<string, string>> =>
|
||||
Object.entries(map).reduce<Record<string, string>>((accum, [key, value]) => {
|
||||
accum[`${path}.${key}`] = `${path}.${value}`;
|
||||
return accum;
|
||||
}, {});
|
358
x-pack/plugins/osquery/common/ecs/ecs_fields/index.ts
Normal file
358
x-pack/plugins/osquery/common/ecs/ecs_fields/index.ts
Normal file
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { extendMap } from './extend_map';
|
||||
|
||||
export const auditdMap: Readonly<Record<string, string>> = {
|
||||
'auditd.result': 'auditd.result',
|
||||
'auditd.session': 'auditd.session',
|
||||
'auditd.data.acct': 'auditd.data.acct',
|
||||
'auditd.data.terminal': 'auditd.data.terminal',
|
||||
'auditd.data.op': 'auditd.data.op',
|
||||
'auditd.summary.actor.primary': 'auditd.summary.actor.primary',
|
||||
'auditd.summary.actor.secondary': 'auditd.summary.actor.secondary',
|
||||
'auditd.summary.object.primary': 'auditd.summary.object.primary',
|
||||
'auditd.summary.object.secondary': 'auditd.summary.object.secondary',
|
||||
'auditd.summary.object.type': 'auditd.summary.object.type',
|
||||
'auditd.summary.how': 'auditd.summary.how',
|
||||
'auditd.summary.message_type': 'auditd.summary.message_type',
|
||||
'auditd.summary.sequence': 'auditd.summary.sequence',
|
||||
};
|
||||
|
||||
export const cloudFieldsMap: Readonly<Record<string, string>> = {
|
||||
'cloud.account.id': 'cloud.account.id',
|
||||
'cloud.availability_zone': 'cloud.availability_zone',
|
||||
'cloud.instance.id': 'cloud.instance.id',
|
||||
'cloud.instance.name': 'cloud.instance.name',
|
||||
'cloud.machine.type': 'cloud.machine.type',
|
||||
'cloud.provider': 'cloud.provider',
|
||||
'cloud.region': 'cloud.region',
|
||||
};
|
||||
|
||||
export const fileMap: Readonly<Record<string, string>> = {
|
||||
'file.name': 'file.name',
|
||||
'file.path': 'file.path',
|
||||
'file.target_path': 'file.target_path',
|
||||
'file.extension': 'file.extension',
|
||||
'file.type': 'file.type',
|
||||
'file.device': 'file.device',
|
||||
'file.inode': 'file.inode',
|
||||
'file.uid': 'file.uid',
|
||||
'file.owner': 'file.owner',
|
||||
'file.gid': 'file.gid',
|
||||
'file.group': 'file.group',
|
||||
'file.mode': 'file.mode',
|
||||
'file.size': 'file.size',
|
||||
'file.mtime': 'file.mtime',
|
||||
'file.ctime': 'file.ctime',
|
||||
};
|
||||
|
||||
export const osFieldsMap: Readonly<Record<string, string>> = {
|
||||
'os.platform': 'os.platform',
|
||||
'os.name': 'os.name',
|
||||
'os.full': 'os.full',
|
||||
'os.family': 'os.family',
|
||||
'os.version': 'os.version',
|
||||
'os.kernel': 'os.kernel',
|
||||
};
|
||||
|
||||
export const hostFieldsMap: Readonly<Record<string, string>> = {
|
||||
'host.architecture': 'host.architecture',
|
||||
'host.id': 'host.id',
|
||||
'host.ip': 'host.ip',
|
||||
'host.mac': 'host.mac',
|
||||
'host.name': 'host.name',
|
||||
...extendMap('host', osFieldsMap),
|
||||
};
|
||||
|
||||
export const processFieldsMap: Readonly<Record<string, string>> = {
|
||||
'process.hash.md5': 'process.hash.md5',
|
||||
'process.hash.sha1': 'process.hash.sha1',
|
||||
'process.hash.sha256': 'process.hash.sha256',
|
||||
'process.pid': 'process.pid',
|
||||
'process.name': 'process.name',
|
||||
'process.ppid': 'process.ppid',
|
||||
'process.args': 'process.args',
|
||||
'process.entity_id': 'process.entity_id',
|
||||
'process.executable': 'process.executable',
|
||||
'process.title': 'process.title',
|
||||
'process.thread': 'process.thread',
|
||||
'process.working_directory': 'process.working_directory',
|
||||
};
|
||||
|
||||
export const agentFieldsMap: Readonly<Record<string, string>> = {
|
||||
'agent.type': 'agent.type',
|
||||
};
|
||||
|
||||
export const userFieldsMap: Readonly<Record<string, string>> = {
|
||||
'user.domain': 'user.domain',
|
||||
'user.id': 'user.id',
|
||||
'user.name': 'user.name',
|
||||
// NOTE: This field is not tested and available from ECS. Please remove this tag once it is
|
||||
'user.full_name': 'user.full_name',
|
||||
// NOTE: This field is not tested and available from ECS. Please remove this tag once it is
|
||||
'user.email': 'user.email',
|
||||
// NOTE: This field is not tested and available from ECS. Please remove this tag once it is
|
||||
'user.hash': 'user.hash',
|
||||
// NOTE: This field is not tested and available from ECS. Please remove this tag once it is
|
||||
'user.group': 'user.group',
|
||||
};
|
||||
|
||||
export const winlogFieldsMap: Readonly<Record<string, string>> = {
|
||||
'winlog.event_id': 'winlog.event_id',
|
||||
};
|
||||
|
||||
export const suricataFieldsMap: Readonly<Record<string, string>> = {
|
||||
'suricata.eve.flow_id': 'suricata.eve.flow_id',
|
||||
'suricata.eve.proto': 'suricata.eve.proto',
|
||||
'suricata.eve.alert.signature': 'suricata.eve.alert.signature',
|
||||
'suricata.eve.alert.signature_id': 'suricata.eve.alert.signature_id',
|
||||
};
|
||||
|
||||
export const tlsFieldsMap: Readonly<Record<string, string>> = {
|
||||
'tls.client_certificate.fingerprint.sha1': 'tls.client_certificate.fingerprint.sha1',
|
||||
'tls.fingerprints.ja3.hash': 'tls.fingerprints.ja3.hash',
|
||||
'tls.server_certificate.fingerprint.sha1': 'tls.server_certificate.fingerprint.sha1',
|
||||
};
|
||||
|
||||
export const urlFieldsMap: Readonly<Record<string, string>> = {
|
||||
'url.original': 'url.original',
|
||||
'url.domain': 'url.domain',
|
||||
'user.username': 'user.username',
|
||||
'user.password': 'user.password',
|
||||
};
|
||||
|
||||
export const httpFieldsMap: Readonly<Record<string, string>> = {
|
||||
'http.version': 'http.version',
|
||||
'http.request': 'http.request',
|
||||
'http.request.method': 'http.request.method',
|
||||
'http.request.body.bytes': 'http.request.body.bytes',
|
||||
'http.request.body.content': 'http.request.body.content',
|
||||
'http.request.referrer': 'http.request.referrer',
|
||||
'http.response.status_code': 'http.response.status_code',
|
||||
'http.response.body': 'http.response.body',
|
||||
'http.response.body.bytes': 'http.response.body.bytes',
|
||||
'http.response.body.content': 'http.response.body.content',
|
||||
};
|
||||
|
||||
export const zeekFieldsMap: Readonly<Record<string, string>> = {
|
||||
'zeek.session_id': 'zeek.session_id',
|
||||
'zeek.connection.local_resp': 'zeek.connection.local_resp',
|
||||
'zeek.connection.local_orig': 'zeek.connection.local_orig',
|
||||
'zeek.connection.missed_bytes': 'zeek.connection.missed_bytes',
|
||||
'zeek.connection.state': 'zeek.connection.state',
|
||||
'zeek.connection.history': 'zeek.connection.history',
|
||||
'zeek.notice.suppress_for': 'zeek.notice.suppress_for',
|
||||
'zeek.notice.msg': 'zeek.notice.msg',
|
||||
'zeek.notice.note': 'zeek.notice.note',
|
||||
'zeek.notice.sub': 'zeek.notice.sub',
|
||||
'zeek.notice.dst': 'zeek.notice.dst',
|
||||
'zeek.notice.dropped': 'zeek.notice.dropped',
|
||||
'zeek.notice.peer_descr': 'zeek.notice.peer_descr',
|
||||
'zeek.dns.AA': 'zeek.dns.AA',
|
||||
'zeek.dns.qclass_name': 'zeek.dns.qclass_name',
|
||||
'zeek.dns.RD': 'zeek.dns.RD',
|
||||
'zeek.dns.qtype_name': 'zeek.dns.qtype_name',
|
||||
'zeek.dns.qtype': 'zeek.dns.qtype',
|
||||
'zeek.dns.query': 'zeek.dns.query',
|
||||
'zeek.dns.trans_id': 'zeek.dns.trans_id',
|
||||
'zeek.dns.qclass': 'zeek.dns.qclass',
|
||||
'zeek.dns.RA': 'zeek.dns.RA',
|
||||
'zeek.dns.TC': 'zeek.dns.TC',
|
||||
'zeek.http.resp_mime_types': 'zeek.http.resp_mime_types',
|
||||
'zeek.http.trans_depth': 'zeek.http.trans_depth',
|
||||
'zeek.http.status_msg': 'zeek.http.status_msg',
|
||||
'zeek.http.resp_fuids': 'zeek.http.resp_fuids',
|
||||
'zeek.http.tags': 'zeek.http.tags',
|
||||
'zeek.files.session_ids': 'zeek.files.session_ids',
|
||||
'zeek.files.timedout': 'zeek.files.timedout',
|
||||
'zeek.files.local_orig': 'zeek.files.local_orig',
|
||||
'zeek.files.tx_host': 'zeek.files.tx_host',
|
||||
'zeek.files.source': 'zeek.files.source',
|
||||
'zeek.files.is_orig': 'zeek.files.is_orig',
|
||||
'zeek.files.overflow_bytes': 'zeek.files.overflow_bytes',
|
||||
'zeek.files.sha1': 'zeek.files.sha1',
|
||||
'zeek.files.duration': 'zeek.files.duration',
|
||||
'zeek.files.depth': 'zeek.files.depth',
|
||||
'zeek.files.analyzers': 'zeek.files.analyzers',
|
||||
'zeek.files.mime_type': 'zeek.files.mime_type',
|
||||
'zeek.files.rx_host': 'zeek.files.rx_host',
|
||||
'zeek.files.total_bytes': 'zeek.files.total_bytes',
|
||||
'zeek.files.fuid': 'zeek.files.fuid',
|
||||
'zeek.files.seen_bytes': 'zeek.files.seen_bytes',
|
||||
'zeek.files.missing_bytes': 'zeek.files.missing_bytes',
|
||||
'zeek.files.md5': 'zeek.files.md5',
|
||||
'zeek.ssl.cipher': 'zeek.ssl.cipher',
|
||||
'zeek.ssl.established': 'zeek.ssl.established',
|
||||
'zeek.ssl.resumed': 'zeek.ssl.resumed',
|
||||
'zeek.ssl.version': 'zeek.ssl.version',
|
||||
};
|
||||
|
||||
export const sourceFieldsMap: Readonly<Record<string, string>> = {
|
||||
'source.bytes': 'source.bytes',
|
||||
'source.ip': 'source.ip',
|
||||
'source.packets': 'source.packets',
|
||||
'source.port': 'source.port',
|
||||
'source.domain': 'source.domain',
|
||||
'source.geo.continent_name': 'source.geo.continent_name',
|
||||
'source.geo.country_name': 'source.geo.country_name',
|
||||
'source.geo.country_iso_code': 'source.geo.country_iso_code',
|
||||
'source.geo.city_name': 'source.geo.city_name',
|
||||
'source.geo.region_iso_code': 'source.geo.region_iso_code',
|
||||
'source.geo.region_name': 'source.geo.region_name',
|
||||
};
|
||||
|
||||
export const destinationFieldsMap: Readonly<Record<string, string>> = {
|
||||
'destination.bytes': 'destination.bytes',
|
||||
'destination.ip': 'destination.ip',
|
||||
'destination.packets': 'destination.packets',
|
||||
'destination.port': 'destination.port',
|
||||
'destination.domain': 'destination.domain',
|
||||
'destination.geo.continent_name': 'destination.geo.continent_name',
|
||||
'destination.geo.country_name': 'destination.geo.country_name',
|
||||
'destination.geo.country_iso_code': 'destination.geo.country_iso_code',
|
||||
'destination.geo.city_name': 'destination.geo.city_name',
|
||||
'destination.geo.region_iso_code': 'destination.geo.region_iso_code',
|
||||
'destination.geo.region_name': 'destination.geo.region_name',
|
||||
};
|
||||
|
||||
export const networkFieldsMap: Readonly<Record<string, string>> = {
|
||||
'network.bytes': 'network.bytes',
|
||||
'network.community_id': 'network.community_id',
|
||||
'network.direction': 'network.direction',
|
||||
'network.packets': 'network.packets',
|
||||
'network.protocol': 'network.protocol',
|
||||
'network.transport': 'network.transport',
|
||||
};
|
||||
|
||||
export const geoFieldsMap: Readonly<Record<string, string>> = {
|
||||
'geo.region_name': 'destination.geo.region_name',
|
||||
'geo.country_iso_code': 'destination.geo.country_iso_code',
|
||||
};
|
||||
|
||||
export const dnsFieldsMap: Readonly<Record<string, string>> = {
|
||||
'dns.question.name': 'dns.question.name',
|
||||
'dns.question.type': 'dns.question.type',
|
||||
'dns.resolved_ip': 'dns.resolved_ip',
|
||||
'dns.response_code': 'dns.response_code',
|
||||
};
|
||||
|
||||
export const endgameFieldsMap: Readonly<Record<string, string>> = {
|
||||
'endgame.exit_code': 'endgame.exit_code',
|
||||
'endgame.file_name': 'endgame.file_name',
|
||||
'endgame.file_path': 'endgame.file_path',
|
||||
'endgame.logon_type': 'endgame.logon_type',
|
||||
'endgame.parent_process_name': 'endgame.parent_process_name',
|
||||
'endgame.pid': 'endgame.pid',
|
||||
'endgame.process_name': 'endgame.process_name',
|
||||
'endgame.subject_domain_name': 'endgame.subject_domain_name',
|
||||
'endgame.subject_logon_id': 'endgame.subject_logon_id',
|
||||
'endgame.subject_user_name': 'endgame.subject_user_name',
|
||||
'endgame.target_domain_name': 'endgame.target_domain_name',
|
||||
'endgame.target_logon_id': 'endgame.target_logon_id',
|
||||
'endgame.target_user_name': 'endgame.target_user_name',
|
||||
};
|
||||
|
||||
export const eventBaseFieldsMap: Readonly<Record<string, string>> = {
|
||||
'event.action': 'event.action',
|
||||
'event.category': 'event.category',
|
||||
'event.code': 'event.code',
|
||||
'event.created': 'event.created',
|
||||
'event.dataset': 'event.dataset',
|
||||
'event.duration': 'event.duration',
|
||||
'event.end': 'event.end',
|
||||
'event.hash': 'event.hash',
|
||||
'event.id': 'event.id',
|
||||
'event.kind': 'event.kind',
|
||||
'event.module': 'event.module',
|
||||
'event.original': 'event.original',
|
||||
'event.outcome': 'event.outcome',
|
||||
'event.risk_score': 'event.risk_score',
|
||||
'event.risk_score_norm': 'event.risk_score_norm',
|
||||
'event.severity': 'event.severity',
|
||||
'event.start': 'event.start',
|
||||
'event.timezone': 'event.timezone',
|
||||
'event.type': 'event.type',
|
||||
};
|
||||
|
||||
export const systemFieldsMap: Readonly<Record<string, string>> = {
|
||||
'system.audit.package.arch': 'system.audit.package.arch',
|
||||
'system.audit.package.entity_id': 'system.audit.package.entity_id',
|
||||
'system.audit.package.name': 'system.audit.package.name',
|
||||
'system.audit.package.size': 'system.audit.package.size',
|
||||
'system.audit.package.summary': 'system.audit.package.summary',
|
||||
'system.audit.package.version': 'system.audit.package.version',
|
||||
'system.auth.ssh.signature': 'system.auth.ssh.signature',
|
||||
'system.auth.ssh.method': 'system.auth.ssh.method',
|
||||
};
|
||||
|
||||
export const signalFieldsMap: Readonly<Record<string, string>> = {
|
||||
'signal.original_time': 'signal.original_time',
|
||||
'signal.rule.id': 'signal.rule.id',
|
||||
'signal.rule.saved_id': 'signal.rule.saved_id',
|
||||
'signal.rule.timeline_id': 'signal.rule.timeline_id',
|
||||
'signal.rule.timeline_title': 'signal.rule.timeline_title',
|
||||
'signal.rule.output_index': 'signal.rule.output_index',
|
||||
'signal.rule.from': 'signal.rule.from',
|
||||
'signal.rule.index': 'signal.rule.index',
|
||||
'signal.rule.language': 'signal.rule.language',
|
||||
'signal.rule.query': 'signal.rule.query',
|
||||
'signal.rule.to': 'signal.rule.to',
|
||||
'signal.rule.filters': 'signal.rule.filters',
|
||||
'signal.rule.rule_id': 'signal.rule.rule_id',
|
||||
'signal.rule.false_positives': 'signal.rule.false_positives',
|
||||
'signal.rule.max_signals': 'signal.rule.max_signals',
|
||||
'signal.rule.risk_score': 'signal.rule.risk_score',
|
||||
'signal.rule.description': 'signal.rule.description',
|
||||
'signal.rule.name': 'signal.rule.name',
|
||||
'signal.rule.immutable': 'signal.rule.immutable',
|
||||
'signal.rule.references': 'signal.rule.references',
|
||||
'signal.rule.severity': 'signal.rule.severity',
|
||||
'signal.rule.tags': 'signal.rule.tags',
|
||||
'signal.rule.threat': 'signal.rule.threat',
|
||||
'signal.rule.type': 'signal.rule.type',
|
||||
'signal.rule.size': 'signal.rule.size',
|
||||
'signal.rule.enabled': 'signal.rule.enabled',
|
||||
'signal.rule.created_at': 'signal.rule.created_at',
|
||||
'signal.rule.updated_at': 'signal.rule.updated_at',
|
||||
'signal.rule.created_by': 'signal.rule.created_by',
|
||||
'signal.rule.updated_by': 'signal.rule.updated_by',
|
||||
'signal.rule.version': 'signal.rule.version',
|
||||
'signal.rule.note': 'signal.rule.note',
|
||||
'signal.rule.threshold': 'signal.rule.threshold',
|
||||
'signal.rule.exceptions_list': 'signal.rule.exceptions_list',
|
||||
};
|
||||
|
||||
export const ruleFieldsMap: Readonly<Record<string, string>> = {
|
||||
'rule.reference': 'rule.reference',
|
||||
};
|
||||
|
||||
export const eventFieldsMap: Readonly<Record<string, string>> = {
|
||||
timestamp: '@timestamp',
|
||||
'@timestamp': '@timestamp',
|
||||
message: 'message',
|
||||
...{ ...agentFieldsMap },
|
||||
...{ ...auditdMap },
|
||||
...{ ...destinationFieldsMap },
|
||||
...{ ...dnsFieldsMap },
|
||||
...{ ...endgameFieldsMap },
|
||||
...{ ...eventBaseFieldsMap },
|
||||
...{ ...fileMap },
|
||||
...{ ...geoFieldsMap },
|
||||
...{ ...hostFieldsMap },
|
||||
...{ ...networkFieldsMap },
|
||||
...{ ...ruleFieldsMap },
|
||||
...{ ...signalFieldsMap },
|
||||
...{ ...sourceFieldsMap },
|
||||
...{ ...suricataFieldsMap },
|
||||
...{ ...systemFieldsMap },
|
||||
...{ ...tlsFieldsMap },
|
||||
...{ ...zeekFieldsMap },
|
||||
...{ ...httpFieldsMap },
|
||||
...{ ...userFieldsMap },
|
||||
...{ ...winlogFieldsMap },
|
||||
...{ ...processFieldsMap },
|
||||
};
|
21
x-pack/plugins/osquery/common/ecs/endgame/index.ts
Normal file
21
x-pack/plugins/osquery/common/ecs/endgame/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface EndgameEcs {
|
||||
exit_code?: number[];
|
||||
file_name?: string[];
|
||||
file_path?: string[];
|
||||
logon_type?: number[];
|
||||
parent_process_name?: string[];
|
||||
pid?: number[];
|
||||
process_name?: string[];
|
||||
subject_domain_name?: string[];
|
||||
subject_logon_id?: string[];
|
||||
subject_user_name?: string[];
|
||||
target_domain_name?: string[];
|
||||
target_logon_id?: string[];
|
||||
target_user_name?: string[];
|
||||
}
|
27
x-pack/plugins/osquery/common/ecs/event/index.ts
Normal file
27
x-pack/plugins/osquery/common/ecs/event/index.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface EventEcs {
|
||||
action?: string[];
|
||||
category?: string[];
|
||||
code?: string[];
|
||||
created?: string[];
|
||||
dataset?: string[];
|
||||
duration?: number[];
|
||||
end?: string[];
|
||||
hash?: string[];
|
||||
id?: string[];
|
||||
kind?: string[];
|
||||
module?: string[];
|
||||
original?: string[];
|
||||
outcome?: string[];
|
||||
risk_score?: number[];
|
||||
risk_score_norm?: number[];
|
||||
severity?: number[];
|
||||
start?: string[];
|
||||
timezone?: string[];
|
||||
type?: string[];
|
||||
}
|
36
x-pack/plugins/osquery/common/ecs/file/index.ts
Normal file
36
x-pack/plugins/osquery/common/ecs/file/index.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface CodeSignature {
|
||||
subject_name: string[];
|
||||
trusted: string[];
|
||||
}
|
||||
export interface Ext {
|
||||
code_signature: CodeSignature[] | CodeSignature;
|
||||
}
|
||||
export interface Hash {
|
||||
sha256: string[];
|
||||
}
|
||||
|
||||
export interface FileEcs {
|
||||
name?: string[];
|
||||
path?: string[];
|
||||
target_path?: string[];
|
||||
extension?: string[];
|
||||
Ext?: Ext;
|
||||
type?: string[];
|
||||
device?: string[];
|
||||
inode?: string[];
|
||||
uid?: string[];
|
||||
owner?: string[];
|
||||
gid?: string[];
|
||||
group?: string[];
|
||||
mode?: string[];
|
||||
size?: number[];
|
||||
mtime?: string[];
|
||||
ctime?: string[];
|
||||
hash?: Hash;
|
||||
}
|
20
x-pack/plugins/osquery/common/ecs/geo/index.ts
Normal file
20
x-pack/plugins/osquery/common/ecs/geo/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface GeoEcs {
|
||||
city_name?: string[];
|
||||
continent_name?: string[];
|
||||
country_iso_code?: string[];
|
||||
country_name?: string[];
|
||||
location?: Location;
|
||||
region_iso_code?: string[];
|
||||
region_name?: string[];
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
lon?: number[];
|
||||
lat?: number[];
|
||||
}
|
24
x-pack/plugins/osquery/common/ecs/host/index.ts
Normal file
24
x-pack/plugins/osquery/common/ecs/host/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface HostEcs {
|
||||
architecture?: string[];
|
||||
id?: string[];
|
||||
ip?: string[];
|
||||
mac?: string[];
|
||||
name?: string[];
|
||||
os?: OsEcs;
|
||||
type?: string[];
|
||||
}
|
||||
|
||||
export interface OsEcs {
|
||||
platform?: string[];
|
||||
name?: string[];
|
||||
full?: string[];
|
||||
family?: string[];
|
||||
version?: string[];
|
||||
kernel?: string[];
|
||||
}
|
29
x-pack/plugins/osquery/common/ecs/http/index.ts
Normal file
29
x-pack/plugins/osquery/common/ecs/http/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface HttpEcs {
|
||||
version?: string[];
|
||||
request?: HttpRequestData;
|
||||
response?: HttpResponseData;
|
||||
}
|
||||
|
||||
export interface HttpRequestData {
|
||||
method?: string[];
|
||||
body?: HttpBodyData;
|
||||
referrer?: string[];
|
||||
bytes?: number[];
|
||||
}
|
||||
|
||||
export interface HttpBodyData {
|
||||
content?: string[];
|
||||
bytes?: number[];
|
||||
}
|
||||
|
||||
export interface HttpResponseData {
|
||||
status_code?: number[];
|
||||
body?: HttpBodyData;
|
||||
bytes?: number[];
|
||||
}
|
57
x-pack/plugins/osquery/common/ecs/index.ts
Normal file
57
x-pack/plugins/osquery/common/ecs/index.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AgentEcs } from './agent';
|
||||
import { AuditdEcs } from './auditd';
|
||||
import { DestinationEcs } from './destination';
|
||||
import { DnsEcs } from './dns';
|
||||
import { EndgameEcs } from './endgame';
|
||||
import { EventEcs } from './event';
|
||||
import { FileEcs } from './file';
|
||||
import { GeoEcs } from './geo';
|
||||
import { HostEcs } from './host';
|
||||
import { NetworkEcs } from './network';
|
||||
import { RuleEcs } from './rule';
|
||||
import { SignalEcs } from './signal';
|
||||
import { SourceEcs } from './source';
|
||||
import { SuricataEcs } from './suricata';
|
||||
import { TlsEcs } from './tls';
|
||||
import { ZeekEcs } from './zeek';
|
||||
import { HttpEcs } from './http';
|
||||
import { UrlEcs } from './url';
|
||||
import { UserEcs } from './user';
|
||||
import { WinlogEcs } from './winlog';
|
||||
import { ProcessEcs } from './process';
|
||||
import { SystemEcs } from './system';
|
||||
|
||||
export interface Ecs {
|
||||
_id: string;
|
||||
_index?: string;
|
||||
agent?: AgentEcs;
|
||||
auditd?: AuditdEcs;
|
||||
destination?: DestinationEcs;
|
||||
dns?: DnsEcs;
|
||||
endgame?: EndgameEcs;
|
||||
event?: EventEcs;
|
||||
geo?: GeoEcs;
|
||||
host?: HostEcs;
|
||||
network?: NetworkEcs;
|
||||
rule?: RuleEcs;
|
||||
signal?: SignalEcs;
|
||||
source?: SourceEcs;
|
||||
suricata?: SuricataEcs;
|
||||
tls?: TlsEcs;
|
||||
zeek?: ZeekEcs;
|
||||
http?: HttpEcs;
|
||||
url?: UrlEcs;
|
||||
timestamp?: string;
|
||||
message?: string[];
|
||||
user?: UserEcs;
|
||||
winlog?: WinlogEcs;
|
||||
process?: ProcessEcs;
|
||||
file?: FileEcs;
|
||||
system?: SystemEcs;
|
||||
}
|
14
x-pack/plugins/osquery/common/ecs/network/index.ts
Normal file
14
x-pack/plugins/osquery/common/ecs/network/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface NetworkEcs {
|
||||
bytes?: number[];
|
||||
community_id?: string[];
|
||||
direction?: string[];
|
||||
packets?: number[];
|
||||
protocol?: string[];
|
||||
transport?: string[];
|
||||
}
|
29
x-pack/plugins/osquery/common/ecs/process/index.ts
Normal file
29
x-pack/plugins/osquery/common/ecs/process/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface ProcessEcs {
|
||||
entity_id?: string[];
|
||||
hash?: ProcessHashData;
|
||||
pid?: number[];
|
||||
name?: string[];
|
||||
ppid?: number[];
|
||||
args?: string[];
|
||||
executable?: string[];
|
||||
title?: string[];
|
||||
thread?: Thread;
|
||||
working_directory?: string[];
|
||||
}
|
||||
|
||||
export interface ProcessHashData {
|
||||
md5?: string[];
|
||||
sha1?: string[];
|
||||
sha256?: string[];
|
||||
}
|
||||
|
||||
export interface Thread {
|
||||
id?: number[];
|
||||
start?: string[];
|
||||
}
|
45
x-pack/plugins/osquery/common/ecs/rule/index.ts
Normal file
45
x-pack/plugins/osquery/common/ecs/rule/index.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface RuleEcs {
|
||||
id?: string[];
|
||||
rule_id?: string[];
|
||||
name?: string[];
|
||||
false_positives: string[];
|
||||
saved_id?: string[];
|
||||
timeline_id?: string[];
|
||||
timeline_title?: string[];
|
||||
max_signals?: number[];
|
||||
risk_score?: string[];
|
||||
output_index?: string[];
|
||||
description?: string[];
|
||||
from?: string[];
|
||||
immutable?: boolean[];
|
||||
index?: string[];
|
||||
interval?: string[];
|
||||
language?: string[];
|
||||
query?: string[];
|
||||
references?: string[];
|
||||
severity?: string[];
|
||||
tags?: string[];
|
||||
threat?: unknown;
|
||||
threshold?: {
|
||||
field: string;
|
||||
value: number;
|
||||
};
|
||||
type?: string[];
|
||||
size?: string[];
|
||||
to?: string[];
|
||||
enabled?: boolean[];
|
||||
filters?: unknown;
|
||||
created_at?: string[];
|
||||
updated_at?: string[];
|
||||
created_by?: string[];
|
||||
updated_by?: string[];
|
||||
version?: string[];
|
||||
note?: string[];
|
||||
building_block_type?: string[];
|
||||
}
|
16
x-pack/plugins/osquery/common/ecs/signal/index.ts
Normal file
16
x-pack/plugins/osquery/common/ecs/signal/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RuleEcs } from '../rule';
|
||||
|
||||
export interface SignalEcs {
|
||||
rule?: RuleEcs;
|
||||
original_time?: string[];
|
||||
status?: string[];
|
||||
group?: {
|
||||
id?: string[];
|
||||
};
|
||||
}
|
16
x-pack/plugins/osquery/common/ecs/source/index.ts
Normal file
16
x-pack/plugins/osquery/common/ecs/source/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { GeoEcs } from '../geo';
|
||||
|
||||
export interface SourceEcs {
|
||||
bytes?: number[];
|
||||
ip?: string[];
|
||||
port?: number[];
|
||||
domain?: string[];
|
||||
geo?: GeoEcs;
|
||||
packets?: number[];
|
||||
}
|
20
x-pack/plugins/osquery/common/ecs/suricata/index.ts
Normal file
20
x-pack/plugins/osquery/common/ecs/suricata/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface SuricataEcs {
|
||||
eve?: SuricataEveData;
|
||||
}
|
||||
|
||||
export interface SuricataEveData {
|
||||
alert?: SuricataAlertData;
|
||||
flow_id?: number[];
|
||||
proto?: string[];
|
||||
}
|
||||
|
||||
export interface SuricataAlertData {
|
||||
signature?: string[];
|
||||
signature_id?: number[];
|
||||
}
|
32
x-pack/plugins/osquery/common/ecs/system/index.ts
Normal file
32
x-pack/plugins/osquery/common/ecs/system/index.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface SystemEcs {
|
||||
audit?: AuditEcs;
|
||||
auth?: AuthEcs;
|
||||
}
|
||||
|
||||
export interface AuditEcs {
|
||||
package?: PackageEcs;
|
||||
}
|
||||
|
||||
export interface PackageEcs {
|
||||
arch?: string[];
|
||||
entity_id?: string[];
|
||||
name?: string[];
|
||||
size?: number[];
|
||||
summary?: string[];
|
||||
version?: string[];
|
||||
}
|
||||
|
||||
export interface AuthEcs {
|
||||
ssh?: SshEcs;
|
||||
}
|
||||
|
||||
export interface SshEcs {
|
||||
method?: string[];
|
||||
signature?: string[];
|
||||
}
|
31
x-pack/plugins/osquery/common/ecs/tls/index.ts
Normal file
31
x-pack/plugins/osquery/common/ecs/tls/index.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface TlsEcs {
|
||||
client_certificate?: TlsClientCertificateData;
|
||||
fingerprints?: TlsFingerprintsData;
|
||||
server_certificate?: TlsServerCertificateData;
|
||||
}
|
||||
|
||||
export interface TlsClientCertificateData {
|
||||
fingerprint?: FingerprintData;
|
||||
}
|
||||
|
||||
export interface FingerprintData {
|
||||
sha1?: string[];
|
||||
}
|
||||
|
||||
export interface TlsFingerprintsData {
|
||||
ja3?: TlsJa3Data;
|
||||
}
|
||||
|
||||
export interface TlsJa3Data {
|
||||
hash?: string[];
|
||||
}
|
||||
|
||||
export interface TlsServerCertificateData {
|
||||
fingerprint?: FingerprintData;
|
||||
}
|
12
x-pack/plugins/osquery/common/ecs/url/index.ts
Normal file
12
x-pack/plugins/osquery/common/ecs/url/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface UrlEcs {
|
||||
domain?: string[];
|
||||
original?: string[];
|
||||
username?: string[];
|
||||
password?: string[];
|
||||
}
|
15
x-pack/plugins/osquery/common/ecs/user/index.ts
Normal file
15
x-pack/plugins/osquery/common/ecs/user/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface UserEcs {
|
||||
domain?: string[];
|
||||
id?: string[];
|
||||
name?: string[];
|
||||
full_name?: string[];
|
||||
email?: string[];
|
||||
hash?: string[];
|
||||
group?: string[];
|
||||
}
|
9
x-pack/plugins/osquery/common/ecs/winlog/index.ts
Normal file
9
x-pack/plugins/osquery/common/ecs/winlog/index.ts
Normal file
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface WinlogEcs {
|
||||
event_id?: number[];
|
||||
}
|
83
x-pack/plugins/osquery/common/ecs/zeek/index.ts
Normal file
83
x-pack/plugins/osquery/common/ecs/zeek/index.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface ZeekEcs {
|
||||
session_id?: string[];
|
||||
connection?: ZeekConnectionData;
|
||||
notice?: ZeekNoticeData;
|
||||
dns?: ZeekDnsData;
|
||||
http?: ZeekHttpData;
|
||||
files?: ZeekFileData;
|
||||
ssl?: ZeekSslData;
|
||||
}
|
||||
|
||||
export interface ZeekConnectionData {
|
||||
local_resp?: boolean[];
|
||||
local_orig?: boolean[];
|
||||
missed_bytes?: number[];
|
||||
state?: string[];
|
||||
history?: string[];
|
||||
}
|
||||
|
||||
export interface ZeekNoticeData {
|
||||
suppress_for?: number[];
|
||||
msg?: string[];
|
||||
note?: string[];
|
||||
sub?: string[];
|
||||
dst?: string[];
|
||||
dropped?: boolean[];
|
||||
peer_descr?: string[];
|
||||
}
|
||||
|
||||
export interface ZeekDnsData {
|
||||
AA?: boolean[];
|
||||
qclass_name?: string[];
|
||||
RD?: boolean[];
|
||||
qtype_name?: string[];
|
||||
rejected?: boolean[];
|
||||
qtype?: string[];
|
||||
query?: string[];
|
||||
trans_id?: number[];
|
||||
qclass?: string[];
|
||||
RA?: boolean[];
|
||||
TC?: boolean[];
|
||||
}
|
||||
|
||||
export interface ZeekHttpData {
|
||||
resp_mime_types?: string[];
|
||||
trans_depth?: string[];
|
||||
status_msg?: string[];
|
||||
resp_fuids?: string[];
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export interface ZeekFileData {
|
||||
session_ids?: string[];
|
||||
timedout?: boolean[];
|
||||
local_orig?: boolean[];
|
||||
tx_host?: string[];
|
||||
source?: string[];
|
||||
is_orig?: boolean[];
|
||||
overflow_bytes?: number[];
|
||||
sha1?: string[];
|
||||
duration?: number[];
|
||||
depth?: number[];
|
||||
analyzers?: string[];
|
||||
mime_type?: string[];
|
||||
rx_host?: string[];
|
||||
total_bytes?: number[];
|
||||
fuid?: string[];
|
||||
seen_bytes?: number[];
|
||||
missing_bytes?: number[];
|
||||
md5?: string[];
|
||||
}
|
||||
|
||||
export interface ZeekSslData {
|
||||
cipher?: string[];
|
||||
established?: boolean[];
|
||||
resumed?: boolean[];
|
||||
version?: string[];
|
||||
}
|
10
x-pack/plugins/osquery/common/index.ts
Normal file
10
x-pack/plugins/osquery/common/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './constants';
|
||||
|
||||
export const PLUGIN_ID = 'osquery';
|
||||
export const PLUGIN_NAME = 'osquery';
|
125
x-pack/plugins/osquery/common/search_strategy/common/index.ts
Normal file
125
x-pack/plugins/osquery/common/search_strategy/common/index.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IEsSearchResponse } from '../../../../../../src/plugins/data/common';
|
||||
|
||||
export type Maybe<T> = T | null;
|
||||
|
||||
export type SearchHit = IEsSearchResponse<object>['rawResponse']['hits']['hits'][0];
|
||||
|
||||
export interface TotalValue {
|
||||
value: number;
|
||||
relation: string;
|
||||
}
|
||||
|
||||
export interface Inspect {
|
||||
dsl: string[];
|
||||
}
|
||||
|
||||
export interface PageInfoPaginated {
|
||||
activePage: number;
|
||||
fakeTotalCount: number;
|
||||
showMorePagesIndicator: boolean;
|
||||
}
|
||||
|
||||
export interface CursorType {
|
||||
value?: Maybe<string>;
|
||||
tiebreaker?: Maybe<string>;
|
||||
}
|
||||
|
||||
export enum Direction {
|
||||
asc = 'asc',
|
||||
desc = 'desc',
|
||||
}
|
||||
|
||||
export interface SortField<Field = string> {
|
||||
field: Field;
|
||||
direction: Direction;
|
||||
}
|
||||
|
||||
export interface TimerangeInput {
|
||||
/** The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan. */
|
||||
interval: string;
|
||||
/** The end of the timerange */
|
||||
to: string;
|
||||
/** The beginning of the timerange */
|
||||
from: string;
|
||||
}
|
||||
|
||||
export interface PaginationInput {
|
||||
/** The limit parameter allows you to configure the maximum amount of items to be returned */
|
||||
limit: number;
|
||||
/** The cursor parameter defines the next result you want to fetch */
|
||||
cursor?: Maybe<string>;
|
||||
/** The tiebreaker parameter allow to be more precise to fetch the next item */
|
||||
tiebreaker?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface PaginationInputPaginated {
|
||||
/** The activePage parameter defines the page of results you want to fetch */
|
||||
activePage: number;
|
||||
/** The cursorStart parameter defines the start of the results to be displayed */
|
||||
cursorStart: number;
|
||||
/** The fakePossibleCount parameter determines the total count in order to show 5 additional pages */
|
||||
fakePossibleCount: number;
|
||||
/** The querySize parameter is the number of items to be returned */
|
||||
querySize: number;
|
||||
}
|
||||
|
||||
export interface DocValueFields {
|
||||
field: string;
|
||||
format?: string | null;
|
||||
}
|
||||
|
||||
export interface Explanation {
|
||||
value: number;
|
||||
description: string;
|
||||
details: Explanation[];
|
||||
}
|
||||
|
||||
export interface ShardsResponse {
|
||||
total: number;
|
||||
successful: number;
|
||||
failed: number;
|
||||
skipped: number;
|
||||
}
|
||||
|
||||
export interface TotalHit {
|
||||
value: number;
|
||||
relation: string;
|
||||
}
|
||||
|
||||
export interface Hit {
|
||||
_index: string;
|
||||
_type: string;
|
||||
_id: string;
|
||||
_score: number | null;
|
||||
}
|
||||
|
||||
export interface Hits<T, U> {
|
||||
hits: {
|
||||
total: T;
|
||||
max_score: number | null;
|
||||
hits: U[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface GenericBuckets {
|
||||
key: string;
|
||||
doc_count: number;
|
||||
}
|
||||
|
||||
export type StringOrNumber = string | number;
|
||||
|
||||
export interface TimerangeFilter {
|
||||
range: {
|
||||
[timestamp: string]: {
|
||||
gte: string;
|
||||
lte: string;
|
||||
format: string;
|
||||
};
|
||||
};
|
||||
}
|
8
x-pack/plugins/osquery/common/search_strategy/index.ts
Normal file
8
x-pack/plugins/osquery/common/search_strategy/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './common';
|
||||
export * from './osquery';
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
import { Inspect, Maybe, PageInfoPaginated } from '../../common';
|
||||
import { RequestOptions, RequestOptionsPaginated } from '../..';
|
||||
|
||||
export type ActionEdges = SearchResponse<object>['hits']['hits'];
|
||||
|
||||
export type ActionResultEdges = SearchResponse<object>['hits']['hits'];
|
||||
export interface ActionsStrategyResponse extends IEsSearchResponse {
|
||||
edges: ActionEdges;
|
||||
totalCount: number;
|
||||
pageInfo: PageInfoPaginated;
|
||||
inspect?: Maybe<Inspect>;
|
||||
}
|
||||
|
||||
export type ActionsRequestOptions = RequestOptionsPaginated<{}>;
|
||||
|
||||
export interface ActionDetailsStrategyResponse extends IEsSearchResponse {
|
||||
actionDetails: Record<string, any>;
|
||||
inspect?: Maybe<Inspect>;
|
||||
}
|
||||
|
||||
export interface ActionDetailsRequestOptions extends RequestOptions {
|
||||
actionId: string;
|
||||
}
|
||||
|
||||
export interface ActionResultsStrategyResponse extends IEsSearchResponse {
|
||||
edges: ActionResultEdges;
|
||||
totalCount: number;
|
||||
pageInfo: PageInfoPaginated;
|
||||
inspect?: Maybe<Inspect>;
|
||||
}
|
||||
|
||||
export interface ActionResultsRequestOptions extends RequestOptionsPaginated {
|
||||
actionId: string;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
import { Inspect, Maybe, PageInfoPaginated } from '../../common';
|
||||
import { RequestOptionsPaginated } from '../..';
|
||||
import { Agent } from '../../../shared_imports';
|
||||
|
||||
export interface AgentsStrategyResponse extends IEsSearchResponse {
|
||||
edges: Agent[];
|
||||
totalCount: number;
|
||||
pageInfo: PageInfoPaginated;
|
||||
inspect?: Maybe<Inspect>;
|
||||
}
|
||||
|
||||
export type AgentsRequestOptions = RequestOptionsPaginated<{}>;
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CloudEcs } from '../../../ecs/cloud';
|
||||
import { HostEcs, OsEcs } from '../../../ecs/host';
|
||||
import { Hit, Hits, Maybe, SearchHit, StringOrNumber, TotalValue } from '../../common';
|
||||
|
||||
export enum HostPolicyResponseActionStatus {
|
||||
success = 'success',
|
||||
failure = 'failure',
|
||||
warning = 'warning',
|
||||
}
|
||||
|
||||
export enum HostsFields {
|
||||
lastSeen = 'lastSeen',
|
||||
hostName = 'hostName',
|
||||
}
|
||||
|
||||
export interface EndpointFields {
|
||||
endpointPolicy?: Maybe<string>;
|
||||
sensorVersion?: Maybe<string>;
|
||||
policyStatus?: Maybe<HostPolicyResponseActionStatus>;
|
||||
}
|
||||
|
||||
export interface HostItem {
|
||||
_id?: Maybe<string>;
|
||||
cloud?: Maybe<CloudEcs>;
|
||||
endpoint?: Maybe<EndpointFields>;
|
||||
host?: Maybe<HostEcs>;
|
||||
lastSeen?: Maybe<string>;
|
||||
}
|
||||
|
||||
export interface HostValue {
|
||||
value: number;
|
||||
value_as_string: string;
|
||||
}
|
||||
|
||||
export interface HostBucketItem {
|
||||
key: string;
|
||||
doc_count: number;
|
||||
timestamp: HostValue;
|
||||
}
|
||||
|
||||
export interface HostBuckets {
|
||||
buckets: HostBucketItem[];
|
||||
}
|
||||
|
||||
export interface HostOsHitsItem {
|
||||
hits: {
|
||||
total: TotalValue | number;
|
||||
max_score: number | null;
|
||||
hits: Array<{
|
||||
_source: { host: { os: Maybe<OsEcs> } };
|
||||
sort?: [number];
|
||||
_index?: string;
|
||||
_type?: string;
|
||||
_id?: string;
|
||||
_score?: number | null;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HostAggEsItem {
|
||||
cloud_instance_id?: HostBuckets;
|
||||
cloud_machine_type?: HostBuckets;
|
||||
cloud_provider?: HostBuckets;
|
||||
cloud_region?: HostBuckets;
|
||||
firstSeen?: HostValue;
|
||||
host_architecture?: HostBuckets;
|
||||
host_id?: HostBuckets;
|
||||
host_ip?: HostBuckets;
|
||||
host_mac?: HostBuckets;
|
||||
host_name?: HostBuckets;
|
||||
host_os_name?: HostBuckets;
|
||||
host_os_version?: HostBuckets;
|
||||
host_type?: HostBuckets;
|
||||
key?: string;
|
||||
lastSeen?: HostValue;
|
||||
os?: HostOsHitsItem;
|
||||
}
|
||||
|
||||
export interface HostEsData extends SearchHit {
|
||||
sort: string[];
|
||||
aggregations: {
|
||||
host_count: {
|
||||
value: number;
|
||||
};
|
||||
host_data: {
|
||||
buckets: HostAggEsItem[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface HostAggEsData extends SearchHit {
|
||||
sort: string[];
|
||||
aggregations: HostAggEsItem;
|
||||
}
|
||||
|
||||
export interface HostHit extends Hit {
|
||||
_source: {
|
||||
'@timestamp'?: string;
|
||||
host: HostEcs;
|
||||
};
|
||||
cursor?: string;
|
||||
firstSeen?: string;
|
||||
sort?: StringOrNumber[];
|
||||
}
|
||||
|
||||
export type HostHits = Hits<number, HostHit>;
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IEsSearchRequest } from '../../../../../../src/plugins/data/common';
|
||||
import { ESQuery } from '../../typed_json';
|
||||
import {
|
||||
ActionsStrategyResponse,
|
||||
ActionsRequestOptions,
|
||||
ActionDetailsStrategyResponse,
|
||||
ActionDetailsRequestOptions,
|
||||
ActionResultsStrategyResponse,
|
||||
ActionResultsRequestOptions,
|
||||
} from './actions';
|
||||
import { AgentsStrategyResponse, AgentsRequestOptions } from './agents';
|
||||
import { ResultsStrategyResponse, ResultsRequestOptions } from './results';
|
||||
|
||||
import { DocValueFields, SortField, PaginationInputPaginated } from '../common';
|
||||
|
||||
export * from './actions';
|
||||
export * from './agents';
|
||||
export * from './results';
|
||||
|
||||
export enum OsqueryQueries {
|
||||
actions = 'actions',
|
||||
actionDetails = 'actionDetails',
|
||||
actionResults = 'actionResults',
|
||||
agents = 'agents',
|
||||
results = 'results',
|
||||
}
|
||||
|
||||
export type FactoryQueryTypes = OsqueryQueries;
|
||||
|
||||
export interface RequestBasicOptions extends IEsSearchRequest {
|
||||
filterQuery: ESQuery | string | undefined;
|
||||
docValueFields?: DocValueFields[];
|
||||
factoryQueryType?: FactoryQueryTypes;
|
||||
}
|
||||
|
||||
/** A mapping of semantic fields to their document counterparts */
|
||||
|
||||
export type RequestOptions = RequestBasicOptions;
|
||||
|
||||
export interface RequestOptionsPaginated<Field = string> extends RequestBasicOptions {
|
||||
pagination: PaginationInputPaginated;
|
||||
sort: SortField<Field>;
|
||||
}
|
||||
|
||||
export type StrategyResponseType<T extends FactoryQueryTypes> = T extends OsqueryQueries.actions
|
||||
? ActionsStrategyResponse
|
||||
: T extends OsqueryQueries.actionDetails
|
||||
? ActionDetailsStrategyResponse
|
||||
: T extends OsqueryQueries.actionResults
|
||||
? ActionResultsStrategyResponse
|
||||
: T extends OsqueryQueries.agents
|
||||
? AgentsStrategyResponse
|
||||
: T extends OsqueryQueries.results
|
||||
? ResultsStrategyResponse
|
||||
: never;
|
||||
|
||||
export type StrategyRequestType<T extends FactoryQueryTypes> = T extends OsqueryQueries.actions
|
||||
? ActionsRequestOptions
|
||||
: T extends OsqueryQueries.actionDetails
|
||||
? ActionDetailsRequestOptions
|
||||
: T extends OsqueryQueries.actionResults
|
||||
? ActionResultsRequestOptions
|
||||
: T extends OsqueryQueries.agents
|
||||
? AgentsRequestOptions
|
||||
: T extends OsqueryQueries.results
|
||||
? ResultsRequestOptions
|
||||
: never;
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
import { Inspect, Maybe, PageInfoPaginated } from '../../common';
|
||||
import { RequestOptionsPaginated } from '../..';
|
||||
|
||||
export type ResultEdges = SearchResponse<unknown>['hits']['hits'];
|
||||
|
||||
export interface ResultsStrategyResponse extends IEsSearchResponse {
|
||||
edges: ResultEdges;
|
||||
totalCount: number;
|
||||
pageInfo: PageInfoPaginated;
|
||||
inspect?: Maybe<Inspect>;
|
||||
}
|
||||
|
||||
export interface ResultsRequestOptions extends RequestOptionsPaginated<{}> {
|
||||
actionId: string;
|
||||
}
|
7
x-pack/plugins/osquery/common/shared_imports.ts
Normal file
7
x-pack/plugins/osquery/common/shared_imports.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { Agent } from '../../fleet/common';
|
57
x-pack/plugins/osquery/common/typed_json.ts
Normal file
57
x-pack/plugins/osquery/common/typed_json.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { DslQuery, Filter } from 'src/plugins/data/common';
|
||||
|
||||
import { JsonObject } from '../../../../src/plugins/kibana_utils/common';
|
||||
|
||||
export type ESQuery =
|
||||
| ESRangeQuery
|
||||
| ESQueryStringQuery
|
||||
| ESMatchQuery
|
||||
| ESTermQuery
|
||||
| ESBoolQuery
|
||||
| JsonObject;
|
||||
|
||||
export interface ESRangeQuery {
|
||||
range: {
|
||||
[name: string]: {
|
||||
gte: number;
|
||||
lte: number;
|
||||
format: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface ESMatchQuery {
|
||||
match: {
|
||||
[name: string]: {
|
||||
query: string;
|
||||
operator: string;
|
||||
zero_terms_query: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface ESQueryStringQuery {
|
||||
query_string: {
|
||||
query: string;
|
||||
analyze_wildcard: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ESTermQuery {
|
||||
term: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ESBoolQuery {
|
||||
bool: {
|
||||
must: DslQuery[];
|
||||
filter: Filter[];
|
||||
should: never[];
|
||||
must_not: Filter[];
|
||||
};
|
||||
}
|
46
x-pack/plugins/osquery/common/utility_types.ts
Normal file
46
x-pack/plugins/osquery/common/utility_types.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as runtimeTypes from 'io-ts';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
// This type is for typing EuiDescriptionList
|
||||
export interface DescriptionList {
|
||||
title: NonNullable<ReactNode>;
|
||||
description: NonNullable<ReactNode>;
|
||||
}
|
||||
|
||||
export const unionWithNullType = <T extends runtimeTypes.Mixed>(type: T) =>
|
||||
runtimeTypes.union([type, runtimeTypes.null]);
|
||||
|
||||
export const stringEnum = <T>(enumObj: T, enumName = 'enum') =>
|
||||
new runtimeTypes.Type<T[keyof T], string>(
|
||||
enumName,
|
||||
(u): u is T[keyof T] => Object.values(enumObj).includes(u),
|
||||
(u, c) =>
|
||||
Object.values(enumObj).includes(u)
|
||||
? runtimeTypes.success(u as T[keyof T])
|
||||
: runtimeTypes.failure(u, c),
|
||||
(a) => (a as unknown) as string
|
||||
);
|
||||
|
||||
/**
|
||||
* Unreachable Assertion helper for scenarios like exhaustive switches.
|
||||
* For references see: https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript
|
||||
* This "x" should _always_ be a type of "never" and not change to "unknown" or any other type. See above link or the generic
|
||||
* concept of exhaustive checks in switch blocks.
|
||||
*
|
||||
* Optionally you can avoid the use of this by using early returns and TypeScript will clear your type checking without complaints
|
||||
* but there are situations and times where this function might still be needed.
|
||||
* @param x Unreachable field
|
||||
* @param message Message of error thrown
|
||||
*/
|
||||
export const assertUnreachable = (
|
||||
x: never, // This should always be a type of "never"
|
||||
message = 'Unknown Field in switch statement'
|
||||
): never => {
|
||||
throw new Error(`${message}: ${x}`);
|
||||
};
|
12
x-pack/plugins/osquery/common/utils/build_query/filters.ts
Normal file
12
x-pack/plugins/osquery/common/utils/build_query/filters.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEmpty, isString } from 'lodash/fp';
|
||||
|
||||
import { ESQuery } from '../../../common/typed_json';
|
||||
|
||||
export const createQueryFilterClauses = (filterQuery: ESQuery | string | undefined) =>
|
||||
!isEmpty(filterQuery) ? [isString(filterQuery) ? JSON.parse(filterQuery) : filterQuery] : [];
|
15
x-pack/plugins/osquery/common/utils/build_query/index.ts
Normal file
15
x-pack/plugins/osquery/common/utils/build_query/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './filters';
|
||||
|
||||
export const inspectStringifyObject = (obj: unknown) => {
|
||||
try {
|
||||
return JSON.stringify(obj, null, 2);
|
||||
} catch {
|
||||
return 'Sorry about that, something went wrong.';
|
||||
}
|
||||
};
|
11
x-pack/plugins/osquery/jest.config.js
Normal file
11
x-pack/plugins/osquery/jest.config.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../..',
|
||||
roots: ['<rootDir>/x-pack/plugins/osquery'],
|
||||
};
|
29
x-pack/plugins/osquery/kibana.json
Normal file
29
x-pack/plugins/osquery/kibana.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"configPath": [
|
||||
"xpack",
|
||||
"osquery"
|
||||
],
|
||||
"extraPublicDirs": [
|
||||
"common"
|
||||
],
|
||||
"id": "osquery",
|
||||
"kibanaVersion": "kibana",
|
||||
"optionalPlugins": [
|
||||
"home"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"esUiShared",
|
||||
"kibanaUtils",
|
||||
"kibanaReact",
|
||||
"kibanaUtils"
|
||||
],
|
||||
"requiredPlugins": [
|
||||
"data",
|
||||
"dataEnhanced",
|
||||
"fleet",
|
||||
"navigation"
|
||||
],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"version": "8.0.0"
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEmpty, isEqual, keys, map } from 'lodash/fp';
|
||||
import { EuiDataGrid, EuiDataGridProps, EuiDataGridColumn, EuiDataGridSorting } from '@elastic/eui';
|
||||
import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
|
||||
|
||||
import { useAllResults } from './use_action_results';
|
||||
import { Direction, ResultEdges } from '../../common/search_strategy';
|
||||
|
||||
const DataContext = createContext<ResultEdges>([]);
|
||||
|
||||
interface ActionResultsTableProps {
|
||||
actionId: string;
|
||||
}
|
||||
|
||||
const ActionResultsTableComponent: React.FC<ActionResultsTableProps> = ({ actionId }) => {
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 });
|
||||
const onChangeItemsPerPage = useCallback(
|
||||
(pageSize) =>
|
||||
setPagination((currentPagination) => ({
|
||||
...currentPagination,
|
||||
pageSize,
|
||||
pageIndex: 0,
|
||||
})),
|
||||
[setPagination]
|
||||
);
|
||||
const onChangePage = useCallback(
|
||||
(pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })),
|
||||
[setPagination]
|
||||
);
|
||||
|
||||
const [columns, setColumns] = useState<EuiDataGridColumn[]>([]);
|
||||
|
||||
// ** Sorting config
|
||||
const [sortingColumns, setSortingColumns] = useState<EuiDataGridSorting['columns']>([]);
|
||||
|
||||
const [, { results, totalCount }] = useAllResults({
|
||||
actionId,
|
||||
activePage: pagination.pageIndex,
|
||||
limit: pagination.pageSize,
|
||||
direction: Direction.asc,
|
||||
sortField: '@timestamp',
|
||||
});
|
||||
|
||||
// Column visibility
|
||||
const [visibleColumns, setVisibleColumns] = useState<string[]>([]); // initialize to the full set of columns
|
||||
|
||||
const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [
|
||||
visibleColumns,
|
||||
setVisibleColumns,
|
||||
]);
|
||||
|
||||
const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo(
|
||||
() => ({ rowIndex, columnId, setCellProps }) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const data = useContext(DataContext);
|
||||
const value = data[rowIndex].fields[columnId];
|
||||
|
||||
return !isEmpty(value) ? value : '-';
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const tableSorting: EuiDataGridSorting = useMemo(
|
||||
() => ({ columns: sortingColumns, onSort: setSortingColumns }),
|
||||
[sortingColumns]
|
||||
);
|
||||
|
||||
const tablePagination = useMemo(
|
||||
() => ({
|
||||
...pagination,
|
||||
pageSizeOptions: [10, 50, 100],
|
||||
onChangeItemsPerPage,
|
||||
onChangePage,
|
||||
}),
|
||||
[onChangeItemsPerPage, onChangePage, pagination]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const newColumns = keys(results[0]?.fields)
|
||||
.sort()
|
||||
.map((fieldName) => ({
|
||||
id: fieldName,
|
||||
displayAsText: fieldName.split('.')[1],
|
||||
defaultSortDirection: Direction.asc,
|
||||
}));
|
||||
|
||||
if (!isEqual(columns, newColumns)) {
|
||||
setColumns(newColumns);
|
||||
setVisibleColumns(map('id', newColumns));
|
||||
}
|
||||
}, [columns, results]);
|
||||
|
||||
return (
|
||||
<DataContext.Provider value={results}>
|
||||
<EuiDataGrid
|
||||
aria-label="Osquery results"
|
||||
columns={columns}
|
||||
columnVisibility={columnVisibility}
|
||||
rowCount={totalCount}
|
||||
renderCellValue={renderCellValue}
|
||||
sorting={tableSorting}
|
||||
pagination={tablePagination}
|
||||
/>
|
||||
</DataContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const ActionResultsTable = React.memo(ActionResultsTableComponent);
|
37
x-pack/plugins/osquery/public/action_results/helpers.ts
Normal file
37
x-pack/plugins/osquery/public/action_results/helpers.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
PaginationInputPaginated,
|
||||
FactoryQueryTypes,
|
||||
StrategyResponseType,
|
||||
Inspect,
|
||||
} from '../../common/search_strategy';
|
||||
|
||||
export type InspectResponse = Inspect & { response: string[] };
|
||||
|
||||
export const generateTablePaginationOptions = (
|
||||
activePage: number,
|
||||
limit: number,
|
||||
isBucketSort?: boolean
|
||||
): PaginationInputPaginated => {
|
||||
const cursorStart = activePage * limit;
|
||||
return {
|
||||
activePage,
|
||||
cursorStart,
|
||||
fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5,
|
||||
querySize: isBucketSort ? limit : limit + cursorStart,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInspectResponse = <T extends FactoryQueryTypes>(
|
||||
response: StrategyResponseType<T>,
|
||||
prevResponse: InspectResponse
|
||||
): InspectResponse => ({
|
||||
dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [],
|
||||
response:
|
||||
response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response,
|
||||
});
|
15
x-pack/plugins/osquery/public/action_results/translations.ts
Normal file
15
x-pack/plugins/osquery/public/action_results/translations.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ERROR_ALL_RESULTS = i18n.translate('xpack.osquery.results.errorSearchDescription', {
|
||||
defaultMessage: `An error has occurred on all results search`,
|
||||
});
|
||||
|
||||
export const FAIL_ALL_RESULTS = i18n.translate('xpack.osquery.results.failSearchDescription', {
|
||||
defaultMessage: `Failed to fetch results`,
|
||||
});
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { createFilter } from '../common/helpers';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import {
|
||||
ResultEdges,
|
||||
PageInfoPaginated,
|
||||
DocValueFields,
|
||||
OsqueryQueries,
|
||||
ResultsRequestOptions,
|
||||
ResultsStrategyResponse,
|
||||
Direction,
|
||||
} from '../../common/search_strategy';
|
||||
import { ESTermQuery } from '../../common/typed_json';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
|
||||
import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
|
||||
import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers';
|
||||
|
||||
const ID = 'resultsAllQuery';
|
||||
|
||||
export interface ResultsArgs {
|
||||
results: ResultEdges;
|
||||
id: string;
|
||||
inspect: InspectResponse;
|
||||
isInspected: boolean;
|
||||
pageInfo: PageInfoPaginated;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
interface UseAllResults {
|
||||
actionId: string;
|
||||
activePage: number;
|
||||
direction: Direction;
|
||||
limit: number;
|
||||
sortField: string;
|
||||
docValueFields?: DocValueFields[];
|
||||
filterQuery?: ESTermQuery | string;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export const useAllResults = ({
|
||||
actionId,
|
||||
activePage,
|
||||
direction,
|
||||
limit,
|
||||
sortField,
|
||||
docValueFields,
|
||||
filterQuery,
|
||||
skip = false,
|
||||
}: UseAllResults): [boolean, ResultsArgs] => {
|
||||
const { data, notifications } = useKibana().services;
|
||||
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [resultsRequest, setHostRequest] = useState<ResultsRequestOptions | null>(null);
|
||||
|
||||
const [resultsResponse, setResultsResponse] = useState<ResultsArgs>({
|
||||
results: [],
|
||||
id: ID,
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
isInspected: false,
|
||||
pageInfo: {
|
||||
activePage: 0,
|
||||
fakeTotalCount: 0,
|
||||
showMorePagesIndicator: false,
|
||||
},
|
||||
totalCount: -1,
|
||||
});
|
||||
|
||||
const resultsSearch = useCallback(
|
||||
(request: ResultsRequestOptions | null) => {
|
||||
if (request == null || skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
let didCancel = false;
|
||||
const asyncSearch = async () => {
|
||||
abortCtrl.current = new AbortController();
|
||||
setLoading(true);
|
||||
|
||||
const searchSubscription$ = data.search
|
||||
.search<ResultsRequestOptions, ResultsStrategyResponse>(request, {
|
||||
strategy: 'osquerySearchStrategy',
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
if (isCompleteResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
setResultsResponse((prevResponse) => ({
|
||||
...prevResponse,
|
||||
results: response.edges,
|
||||
inspect: getInspectResponse(response, prevResponse.inspect),
|
||||
pageInfo: response.pageInfo,
|
||||
totalCount: response.totalCount,
|
||||
}));
|
||||
}
|
||||
searchSubscription$.unsubscribe();
|
||||
} else if (isErrorResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
}
|
||||
// TODO: Make response error status clearer
|
||||
notifications.toasts.addWarning(i18n.ERROR_ALL_RESULTS);
|
||||
searchSubscription$.unsubscribe();
|
||||
}
|
||||
},
|
||||
error: (msg) => {
|
||||
if (!(msg instanceof AbortError)) {
|
||||
notifications.toasts.addDanger({ title: i18n.FAIL_ALL_RESULTS, text: msg.message });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
abortCtrl.current.abort();
|
||||
asyncSearch();
|
||||
return () => {
|
||||
didCancel = true;
|
||||
abortCtrl.current.abort();
|
||||
};
|
||||
},
|
||||
[data.search, notifications.toasts, skip]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setHostRequest((prevRequest) => {
|
||||
const myRequest = {
|
||||
...(prevRequest ?? {}),
|
||||
actionId,
|
||||
docValueFields: docValueFields ?? [],
|
||||
factoryQueryType: OsqueryQueries.actionResults,
|
||||
filterQuery: createFilter(filterQuery),
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort: {
|
||||
direction,
|
||||
field: sortField,
|
||||
},
|
||||
};
|
||||
if (!deepEqual(prevRequest, myRequest)) {
|
||||
return myRequest;
|
||||
}
|
||||
return prevRequest;
|
||||
});
|
||||
}, [actionId, activePage, direction, docValueFields, filterQuery, limit, sortField]);
|
||||
|
||||
useEffect(() => {
|
||||
resultsSearch(resultsRequest);
|
||||
}, [resultsRequest, resultsSearch]);
|
||||
|
||||
return [loading, resultsResponse];
|
||||
};
|
107
x-pack/plugins/osquery/public/actions/actions_table.tsx
Normal file
107
x-pack/plugins/osquery/public/actions/actions_table.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEmpty, isEqual, keys, map } from 'lodash/fp';
|
||||
import { EuiDataGrid, EuiDataGridProps, EuiDataGridColumn, EuiDataGridSorting } from '@elastic/eui';
|
||||
import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
|
||||
|
||||
import { useAllActions } from './use_all_actions';
|
||||
import { ActionEdges, Direction } from '../../common/search_strategy';
|
||||
|
||||
const DataContext = createContext<ActionEdges>([]);
|
||||
|
||||
const ActionsTableComponent = () => {
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 });
|
||||
const onChangeItemsPerPage = useCallback(
|
||||
(pageSize) =>
|
||||
setPagination((currentPagination) => ({
|
||||
...currentPagination,
|
||||
pageSize,
|
||||
pageIndex: 0,
|
||||
})),
|
||||
[setPagination]
|
||||
);
|
||||
const onChangePage = useCallback(
|
||||
(pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })),
|
||||
[setPagination]
|
||||
);
|
||||
|
||||
const [columns, setColumns] = useState<EuiDataGridColumn[]>([]);
|
||||
|
||||
// ** Sorting config
|
||||
const [sortingColumns, setSortingColumns] = useState<EuiDataGridSorting['columns']>([]);
|
||||
|
||||
const [, { actions, totalCount }] = useAllActions({
|
||||
activePage: pagination.pageIndex,
|
||||
limit: pagination.pageSize,
|
||||
direction: Direction.asc,
|
||||
sortField: '@timestamp',
|
||||
});
|
||||
|
||||
// Column visibility
|
||||
const [visibleColumns, setVisibleColumns] = useState<string[]>([]); // initialize to the full set of columns
|
||||
|
||||
const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [
|
||||
visibleColumns,
|
||||
setVisibleColumns,
|
||||
]);
|
||||
|
||||
const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo(() => {
|
||||
return ({ rowIndex, columnId, setCellProps }) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const data = useContext(DataContext);
|
||||
const value = data[rowIndex].fields[columnId];
|
||||
|
||||
return !isEmpty(value) ? value : '-';
|
||||
};
|
||||
}, []);
|
||||
|
||||
const tableSorting: EuiDataGridSorting = useMemo(
|
||||
() => ({ columns: sortingColumns, onSort: setSortingColumns }),
|
||||
[setSortingColumns, sortingColumns]
|
||||
);
|
||||
|
||||
const tablePagination = useMemo(
|
||||
() => ({
|
||||
...pagination,
|
||||
pageSizeOptions: [10, 50, 100],
|
||||
onChangeItemsPerPage,
|
||||
onChangePage,
|
||||
}),
|
||||
[onChangeItemsPerPage, onChangePage, pagination]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const newColumns = keys(actions[0]?.fields)
|
||||
.sort()
|
||||
.map((fieldName) => ({
|
||||
id: fieldName,
|
||||
displayAsText: fieldName.split('.')[1],
|
||||
defaultSortDirection: Direction.asc,
|
||||
}));
|
||||
|
||||
if (!isEqual(columns, newColumns)) {
|
||||
setColumns(newColumns);
|
||||
setVisibleColumns(map('id', newColumns));
|
||||
}
|
||||
}, [columns, actions]);
|
||||
|
||||
return (
|
||||
<DataContext.Provider value={actions}>
|
||||
<EuiDataGrid
|
||||
aria-label="Osquery actions"
|
||||
columns={columns}
|
||||
columnVisibility={columnVisibility}
|
||||
rowCount={totalCount}
|
||||
renderCellValue={renderCellValue}
|
||||
sorting={tableSorting}
|
||||
pagination={tablePagination}
|
||||
/>
|
||||
</DataContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const ActionsTable = React.memo(ActionsTableComponent);
|
37
x-pack/plugins/osquery/public/actions/helpers.ts
Normal file
37
x-pack/plugins/osquery/public/actions/helpers.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
PaginationInputPaginated,
|
||||
FactoryQueryTypes,
|
||||
StrategyResponseType,
|
||||
Inspect,
|
||||
} from '../../common/search_strategy';
|
||||
|
||||
export type InspectResponse = Inspect & { response: string[] };
|
||||
|
||||
export const generateTablePaginationOptions = (
|
||||
activePage: number,
|
||||
limit: number,
|
||||
isBucketSort?: boolean
|
||||
): PaginationInputPaginated => {
|
||||
const cursorStart = activePage * limit;
|
||||
return {
|
||||
activePage,
|
||||
cursorStart,
|
||||
fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5,
|
||||
querySize: isBucketSort ? limit : limit + cursorStart,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInspectResponse = <T extends FactoryQueryTypes>(
|
||||
response: StrategyResponseType<T>,
|
||||
prevResponse: InspectResponse
|
||||
): InspectResponse => ({
|
||||
dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [],
|
||||
response:
|
||||
response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response,
|
||||
});
|
43
x-pack/plugins/osquery/public/actions/translations.ts
Normal file
43
x-pack/plugins/osquery/public/actions/translations.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ERROR_ALL_ACTIONS = i18n.translate('xpack.osquery.actions.errorSearchDescription', {
|
||||
defaultMessage: `An error has occurred on all actions search`,
|
||||
});
|
||||
|
||||
export const FAIL_ALL_ACTIONS = i18n.translate('xpack.osquery.actions.failSearchDescription', {
|
||||
defaultMessage: `Failed to fetch actions`,
|
||||
});
|
||||
|
||||
export const ERROR_ACTION_DETAILS = i18n.translate(
|
||||
'xpack.osquery.actionDetails.errorSearchDescription',
|
||||
{
|
||||
defaultMessage: `An error has occurred on action details search`,
|
||||
}
|
||||
);
|
||||
|
||||
export const FAIL_ACTION_DETAILS = i18n.translate(
|
||||
'xpack.osquery.actionDetails.failSearchDescription',
|
||||
{
|
||||
defaultMessage: `Failed to fetch action details`,
|
||||
}
|
||||
);
|
||||
|
||||
export const ERROR_ACTION_RESULTS = i18n.translate(
|
||||
'xpack.osquery.actionResults.errorSearchDescription',
|
||||
{
|
||||
defaultMessage: `An error has occurred on action results search`,
|
||||
}
|
||||
);
|
||||
|
||||
export const FAIL_ACTION_RESULTS = i18n.translate(
|
||||
'xpack.osquery.actionResults.failSearchDescription',
|
||||
{
|
||||
defaultMessage: `Failed to fetch action results`,
|
||||
}
|
||||
);
|
141
x-pack/plugins/osquery/public/actions/use_action_details.ts
Normal file
141
x-pack/plugins/osquery/public/actions/use_action_details.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { createFilter } from '../common/helpers';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import {
|
||||
DocValueFields,
|
||||
OsqueryQueries,
|
||||
ActionDetailsRequestOptions,
|
||||
ActionDetailsStrategyResponse,
|
||||
} from '../../common/search_strategy';
|
||||
import { ESTermQuery } from '../../common/typed_json';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
|
||||
import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
|
||||
import { getInspectResponse, InspectResponse } from './helpers';
|
||||
|
||||
const ID = 'actionDetailsQuery';
|
||||
|
||||
export interface ActionDetailsArgs {
|
||||
actionDetails: Record<string, string>;
|
||||
id: string;
|
||||
inspect: InspectResponse;
|
||||
isInspected: boolean;
|
||||
}
|
||||
|
||||
interface UseActionDetails {
|
||||
actionId: string;
|
||||
docValueFields?: DocValueFields[];
|
||||
filterQuery?: ESTermQuery | string;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export const useActionDetails = ({
|
||||
actionId,
|
||||
docValueFields,
|
||||
filterQuery,
|
||||
skip = false,
|
||||
}: UseActionDetails): [boolean, ActionDetailsArgs] => {
|
||||
const { data, notifications } = useKibana().services;
|
||||
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [actionDetailsRequest, setHostRequest] = useState<ActionDetailsRequestOptions | null>(null);
|
||||
|
||||
const [actionDetailsResponse, setActionDetailsResponse] = useState<ActionDetailsArgs>({
|
||||
actionDetails: {},
|
||||
id: ID,
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
isInspected: false,
|
||||
});
|
||||
|
||||
const actionDetailsSearch = useCallback(
|
||||
(request: ActionDetailsRequestOptions | null) => {
|
||||
if (request == null || skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
let didCancel = false;
|
||||
const asyncSearch = async () => {
|
||||
abortCtrl.current = new AbortController();
|
||||
setLoading(true);
|
||||
|
||||
const searchSubscription$ = data.search
|
||||
.search<ActionDetailsRequestOptions, ActionDetailsStrategyResponse>(request, {
|
||||
strategy: 'osquerySearchStrategy',
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
if (isCompleteResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
setActionDetailsResponse((prevResponse) => ({
|
||||
...prevResponse,
|
||||
actionDetails: response.actionDetails,
|
||||
inspect: getInspectResponse(response, prevResponse.inspect),
|
||||
}));
|
||||
}
|
||||
searchSubscription$.unsubscribe();
|
||||
} else if (isErrorResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
}
|
||||
// TODO: Make response error status clearer
|
||||
notifications.toasts.addWarning(i18n.ERROR_ACTION_DETAILS);
|
||||
searchSubscription$.unsubscribe();
|
||||
}
|
||||
},
|
||||
error: (msg) => {
|
||||
if (!(msg instanceof AbortError)) {
|
||||
notifications.toasts.addDanger({
|
||||
title: i18n.FAIL_ACTION_DETAILS,
|
||||
text: msg.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
abortCtrl.current.abort();
|
||||
asyncSearch();
|
||||
return () => {
|
||||
didCancel = true;
|
||||
abortCtrl.current.abort();
|
||||
};
|
||||
},
|
||||
[data.search, notifications.toasts, skip]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setHostRequest((prevRequest) => {
|
||||
const myRequest = {
|
||||
...(prevRequest ?? {}),
|
||||
actionId,
|
||||
docValueFields: docValueFields ?? [],
|
||||
factoryQueryType: OsqueryQueries.actionDetails,
|
||||
filterQuery: createFilter(filterQuery),
|
||||
};
|
||||
if (!deepEqual(prevRequest, myRequest)) {
|
||||
return myRequest;
|
||||
}
|
||||
return prevRequest;
|
||||
});
|
||||
}, [actionId, docValueFields, filterQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
actionDetailsSearch(actionDetailsRequest);
|
||||
}, [actionDetailsRequest, actionDetailsSearch]);
|
||||
|
||||
return [loading, actionDetailsResponse];
|
||||
};
|
161
x-pack/plugins/osquery/public/actions/use_all_actions.ts
Normal file
161
x-pack/plugins/osquery/public/actions/use_all_actions.ts
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { createFilter } from '../common/helpers';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import {
|
||||
ActionEdges,
|
||||
PageInfoPaginated,
|
||||
DocValueFields,
|
||||
OsqueryQueries,
|
||||
ActionsRequestOptions,
|
||||
ActionsStrategyResponse,
|
||||
Direction,
|
||||
} from '../../common/search_strategy';
|
||||
import { ESTermQuery } from '../../common/typed_json';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
|
||||
import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
|
||||
import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers';
|
||||
|
||||
const ID = 'actionsAllQuery';
|
||||
|
||||
export interface ActionsArgs {
|
||||
actions: ActionEdges;
|
||||
id: string;
|
||||
inspect: InspectResponse;
|
||||
isInspected: boolean;
|
||||
pageInfo: PageInfoPaginated;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
interface UseAllActions {
|
||||
activePage: number;
|
||||
direction: Direction;
|
||||
limit: number;
|
||||
sortField: string;
|
||||
docValueFields?: DocValueFields[];
|
||||
filterQuery?: ESTermQuery | string;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export const useAllActions = ({
|
||||
activePage,
|
||||
direction,
|
||||
limit,
|
||||
sortField,
|
||||
docValueFields,
|
||||
filterQuery,
|
||||
skip = false,
|
||||
}: UseAllActions): [boolean, ActionsArgs] => {
|
||||
const { data, notifications } = useKibana().services;
|
||||
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [actionsRequest, setHostRequest] = useState<ActionsRequestOptions | null>(null);
|
||||
|
||||
const [actionsResponse, setActionsResponse] = useState<ActionsArgs>({
|
||||
actions: [],
|
||||
id: ID,
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
isInspected: false,
|
||||
pageInfo: {
|
||||
activePage: 0,
|
||||
fakeTotalCount: 0,
|
||||
showMorePagesIndicator: false,
|
||||
},
|
||||
totalCount: -1,
|
||||
});
|
||||
|
||||
const actionsSearch = useCallback(
|
||||
(request: ActionsRequestOptions | null) => {
|
||||
if (request == null || skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
let didCancel = false;
|
||||
const asyncSearch = async () => {
|
||||
abortCtrl.current = new AbortController();
|
||||
setLoading(true);
|
||||
|
||||
const searchSubscription$ = data.search
|
||||
.search<ActionsRequestOptions, ActionsStrategyResponse>(request, {
|
||||
strategy: 'osquerySearchStrategy',
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
if (isCompleteResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
setActionsResponse((prevResponse) => ({
|
||||
...prevResponse,
|
||||
actions: response.edges,
|
||||
inspect: getInspectResponse(response, prevResponse.inspect),
|
||||
pageInfo: response.pageInfo,
|
||||
totalCount: response.totalCount,
|
||||
}));
|
||||
}
|
||||
searchSubscription$.unsubscribe();
|
||||
} else if (isErrorResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
}
|
||||
// TODO: Make response error status clearer
|
||||
notifications.toasts.addWarning(i18n.ERROR_ALL_ACTIONS);
|
||||
searchSubscription$.unsubscribe();
|
||||
}
|
||||
},
|
||||
error: (msg) => {
|
||||
if (!(msg instanceof AbortError)) {
|
||||
notifications.toasts.addDanger({ title: i18n.FAIL_ALL_ACTIONS, text: msg.message });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
abortCtrl.current.abort();
|
||||
asyncSearch();
|
||||
return () => {
|
||||
didCancel = true;
|
||||
abortCtrl.current.abort();
|
||||
};
|
||||
},
|
||||
[data.search, notifications.toasts, skip]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setHostRequest((prevRequest) => {
|
||||
const myRequest = {
|
||||
...(prevRequest ?? {}),
|
||||
docValueFields: docValueFields ?? [],
|
||||
factoryQueryType: OsqueryQueries.actions,
|
||||
filterQuery: createFilter(filterQuery),
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort: {
|
||||
direction,
|
||||
field: sortField,
|
||||
},
|
||||
};
|
||||
if (!deepEqual(prevRequest, myRequest)) {
|
||||
return myRequest;
|
||||
}
|
||||
return prevRequest;
|
||||
});
|
||||
}, [activePage, direction, docValueFields, filterQuery, limit, sortField]);
|
||||
|
||||
useEffect(() => {
|
||||
actionsSearch(actionsRequest);
|
||||
}, [actionsRequest, actionsSearch]);
|
||||
|
||||
return [loading, actionsResponse];
|
||||
};
|
149
x-pack/plugins/osquery/public/agents/agents_table.tsx
Normal file
149
x-pack/plugins/osquery/public/agents/agents_table.tsx
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { find } from 'lodash/fp';
|
||||
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiBasicTableProps,
|
||||
EuiTableSelectionType,
|
||||
EuiHealth,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { useAllAgents } from './use_all_agents';
|
||||
import { Direction } from '../../common/search_strategy';
|
||||
import { Agent } from '../../common/shared_imports';
|
||||
|
||||
interface AgentsTableProps {
|
||||
selectedAgents: string[];
|
||||
onChange: (payload: string[]) => void;
|
||||
}
|
||||
|
||||
const AgentsTableComponent: React.FC<AgentsTableProps> = ({ selectedAgents, onChange }) => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
const [sortField, setSortField] = useState<keyof Agent>('id');
|
||||
const [sortDirection, setSortDirection] = useState<Direction>(Direction.asc);
|
||||
const [selectedItems, setSelectedItems] = useState([]);
|
||||
const tableRef = useRef<EuiBasicTable<Agent>>(null);
|
||||
|
||||
const onTableChange: EuiBasicTableProps<Agent>['onChange'] = useCallback(
|
||||
({ page = {}, sort = {} }) => {
|
||||
const { index: newPageIndex, size: newPageSize } = page;
|
||||
|
||||
const { field: newSortField, direction: newSortDirection } = sort;
|
||||
|
||||
setPageIndex(newPageIndex);
|
||||
setPageSize(newPageSize);
|
||||
setSortField(newSortField);
|
||||
setSortDirection(newSortDirection);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const onSelectionChange: EuiTableSelectionType<{}>['onSelectionChange'] = useCallback(
|
||||
(newSelectedItems) => {
|
||||
setSelectedItems(newSelectedItems);
|
||||
// @ts-expect-error
|
||||
onChange(newSelectedItems.map((item) => item._id));
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const renderStatus = (online: string) => {
|
||||
const color = online ? 'success' : 'danger';
|
||||
const label = online ? 'Online' : 'Offline';
|
||||
return <EuiHealth color={color}>{label}</EuiHealth>;
|
||||
};
|
||||
|
||||
const [, { agents, totalCount }] = useAllAgents({
|
||||
activePage: pageIndex,
|
||||
limit: pageSize,
|
||||
direction: sortDirection,
|
||||
sortField,
|
||||
});
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<{}>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
field: 'local_metadata.elastic.agent.id',
|
||||
name: 'id',
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'local_metadata.host.name',
|
||||
name: 'hostname',
|
||||
truncateText: true,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'active',
|
||||
name: 'Online',
|
||||
dataType: 'boolean',
|
||||
render: (active: string) => renderStatus(active),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const pagination = useMemo(
|
||||
() => ({
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalItemCount: totalCount,
|
||||
pageSizeOptions: [3, 5, 8],
|
||||
}),
|
||||
[pageIndex, pageSize, totalCount]
|
||||
);
|
||||
|
||||
const sorting = useMemo(
|
||||
() => ({
|
||||
sort: {
|
||||
field: sortField,
|
||||
direction: sortDirection,
|
||||
},
|
||||
}),
|
||||
[sortDirection, sortField]
|
||||
);
|
||||
|
||||
const selection: EuiBasicTableProps<Agent>['selection'] = useMemo(
|
||||
() => ({
|
||||
selectable: (agent: Agent) => agent.active,
|
||||
selectableMessage: (selectable: boolean) => (!selectable ? 'User is currently offline' : ''),
|
||||
onSelectionChange,
|
||||
initialSelected: selectedItems,
|
||||
}),
|
||||
[onSelectionChange, selectedItems]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAgents?.length && agents.length && selectedItems.length !== selectedAgents.length) {
|
||||
tableRef?.current?.setSelection(
|
||||
// @ts-expect-error
|
||||
selectedAgents.map((agentId) => find({ _id: agentId }, agents))
|
||||
);
|
||||
}
|
||||
}, [selectedAgents, agents, selectedItems.length]);
|
||||
|
||||
return (
|
||||
<EuiBasicTable<Agent>
|
||||
ref={tableRef}
|
||||
items={agents}
|
||||
itemId="_id"
|
||||
columns={columns}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
isSelectable={true}
|
||||
selection={selection}
|
||||
onChange={onTableChange}
|
||||
rowHeader="firstName"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const AgentsTable = React.memo(AgentsTableComponent);
|
37
x-pack/plugins/osquery/public/agents/helpers.ts
Normal file
37
x-pack/plugins/osquery/public/agents/helpers.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
PaginationInputPaginated,
|
||||
FactoryQueryTypes,
|
||||
StrategyResponseType,
|
||||
Inspect,
|
||||
} from '../../common/search_strategy';
|
||||
|
||||
export type InspectResponse = Inspect & { response: string[] };
|
||||
|
||||
export const generateTablePaginationOptions = (
|
||||
activePage: number,
|
||||
limit: number,
|
||||
isBucketSort?: boolean
|
||||
): PaginationInputPaginated => {
|
||||
const cursorStart = activePage * limit;
|
||||
return {
|
||||
activePage,
|
||||
cursorStart,
|
||||
fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5,
|
||||
querySize: isBucketSort ? limit : limit + cursorStart,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInspectResponse = <T extends FactoryQueryTypes>(
|
||||
response: StrategyResponseType<T>,
|
||||
prevResponse: InspectResponse
|
||||
): InspectResponse => ({
|
||||
dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [],
|
||||
response:
|
||||
response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response,
|
||||
});
|
15
x-pack/plugins/osquery/public/agents/translations.ts
Normal file
15
x-pack/plugins/osquery/public/agents/translations.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ERROR_ALL_AGENTS = i18n.translate('xpack.osquery.agents.errorSearchDescription', {
|
||||
defaultMessage: `An error has occurred on all agents search`,
|
||||
});
|
||||
|
||||
export const FAIL_ALL_AGENTS = i18n.translate('xpack.osquery.agents.failSearchDescription', {
|
||||
defaultMessage: `Failed to fetch agents`,
|
||||
});
|
161
x-pack/plugins/osquery/public/agents/use_all_agents.ts
Normal file
161
x-pack/plugins/osquery/public/agents/use_all_agents.ts
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { createFilter } from '../common/helpers';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import {
|
||||
PageInfoPaginated,
|
||||
DocValueFields,
|
||||
OsqueryQueries,
|
||||
AgentsRequestOptions,
|
||||
AgentsStrategyResponse,
|
||||
Direction,
|
||||
} from '../../common/search_strategy';
|
||||
import { ESTermQuery } from '../../common/typed_json';
|
||||
import { Agent } from '../../common/shared_imports';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
|
||||
import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
|
||||
import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers';
|
||||
|
||||
const ID = 'agentsAllQuery';
|
||||
|
||||
export interface AgentsArgs {
|
||||
agents: Agent[];
|
||||
id: string;
|
||||
inspect: InspectResponse;
|
||||
isInspected: boolean;
|
||||
pageInfo: PageInfoPaginated;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
interface UseAllAgents {
|
||||
activePage: number;
|
||||
direction: Direction;
|
||||
limit: number;
|
||||
sortField: string;
|
||||
docValueFields?: DocValueFields[];
|
||||
filterQuery?: ESTermQuery | string;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export const useAllAgents = ({
|
||||
activePage,
|
||||
direction,
|
||||
limit,
|
||||
sortField,
|
||||
docValueFields,
|
||||
filterQuery,
|
||||
skip = false,
|
||||
}: UseAllAgents): [boolean, AgentsArgs] => {
|
||||
const { data, notifications } = useKibana().services;
|
||||
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [agentsRequest, setHostRequest] = useState<AgentsRequestOptions | null>(null);
|
||||
|
||||
const [agentsResponse, setAgentsResponse] = useState<AgentsArgs>({
|
||||
agents: [],
|
||||
id: ID,
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
isInspected: false,
|
||||
pageInfo: {
|
||||
activePage: 0,
|
||||
fakeTotalCount: 0,
|
||||
showMorePagesIndicator: false,
|
||||
},
|
||||
totalCount: -1,
|
||||
});
|
||||
|
||||
const agentsSearch = useCallback(
|
||||
(request: AgentsRequestOptions | null) => {
|
||||
if (request == null || skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
let didCancel = false;
|
||||
const asyncSearch = async () => {
|
||||
abortCtrl.current = new AbortController();
|
||||
setLoading(true);
|
||||
|
||||
const searchSubscription$ = data.search
|
||||
.search<AgentsRequestOptions, AgentsStrategyResponse>(request, {
|
||||
strategy: 'osquerySearchStrategy',
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
if (isCompleteResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
setAgentsResponse((prevResponse) => ({
|
||||
...prevResponse,
|
||||
agents: response.edges,
|
||||
inspect: getInspectResponse(response, prevResponse.inspect),
|
||||
pageInfo: response.pageInfo,
|
||||
totalCount: response.totalCount,
|
||||
}));
|
||||
}
|
||||
searchSubscription$.unsubscribe();
|
||||
} else if (isErrorResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
}
|
||||
// TODO: Make response error status clearer
|
||||
notifications.toasts.addWarning(i18n.ERROR_ALL_AGENTS);
|
||||
searchSubscription$.unsubscribe();
|
||||
}
|
||||
},
|
||||
error: (msg) => {
|
||||
if (!(msg instanceof AbortError)) {
|
||||
notifications.toasts.addDanger({ title: i18n.FAIL_ALL_AGENTS, text: msg.message });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
abortCtrl.current.abort();
|
||||
asyncSearch();
|
||||
return () => {
|
||||
didCancel = true;
|
||||
abortCtrl.current.abort();
|
||||
};
|
||||
},
|
||||
[data.search, notifications.toasts, skip]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setHostRequest((prevRequest) => {
|
||||
const myRequest = {
|
||||
...(prevRequest ?? {}),
|
||||
docValueFields: docValueFields ?? [],
|
||||
factoryQueryType: OsqueryQueries.agents,
|
||||
filterQuery: createFilter(filterQuery),
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort: {
|
||||
direction,
|
||||
field: sortField,
|
||||
},
|
||||
};
|
||||
if (!deepEqual(prevRequest, myRequest)) {
|
||||
return myRequest;
|
||||
}
|
||||
return prevRequest;
|
||||
});
|
||||
}, [activePage, direction, docValueFields, filterQuery, limit, sortField]);
|
||||
|
||||
useEffect(() => {
|
||||
agentsSearch(agentsRequest);
|
||||
}, [agentsRequest, agentsSearch]);
|
||||
|
||||
return [loading, agentsResponse];
|
||||
};
|
70
x-pack/plugins/osquery/public/application.tsx
Normal file
70
x-pack/plugins/osquery/public/application.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import React, { useMemo } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { useUiSetting$ } from '../../../../src/plugins/kibana_react/public';
|
||||
import { Storage } from '../../../../src/plugins/kibana_utils/public';
|
||||
import { AppMountParameters, CoreStart } from '../../../../src/core/public';
|
||||
import { AppPluginStartDependencies } from './types';
|
||||
import { OsqueryApp } from './components/app';
|
||||
import { DEFAULT_DARK_MODE, PLUGIN_NAME } from '../common';
|
||||
import { KibanaContextProvider } from './common/lib/kibana';
|
||||
|
||||
const OsqueryAppContext = () => {
|
||||
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
|
||||
const theme = useMemo(
|
||||
() => ({
|
||||
eui: darkMode ? euiDarkVars : euiLightVars,
|
||||
darkMode,
|
||||
}),
|
||||
[darkMode]
|
||||
);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<OsqueryApp />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const renderApp = (
|
||||
core: CoreStart,
|
||||
services: AppPluginStartDependencies,
|
||||
{ element, history }: AppMountParameters,
|
||||
storage: Storage,
|
||||
kibanaVersion: string
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<KibanaContextProvider
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
services={{
|
||||
appName: PLUGIN_NAME,
|
||||
...core,
|
||||
...services,
|
||||
storage,
|
||||
}}
|
||||
>
|
||||
<EuiErrorBoundary>
|
||||
<Router history={history}>
|
||||
<I18nProvider>
|
||||
<OsqueryAppContext />
|
||||
</I18nProvider>
|
||||
</Router>
|
||||
</EuiErrorBoundary>
|
||||
</KibanaContextProvider>,
|
||||
element
|
||||
);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
29
x-pack/plugins/osquery/public/common/helpers.test.ts
Normal file
29
x-pack/plugins/osquery/public/common/helpers.test.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ESQuery } from '../../common/typed_json';
|
||||
|
||||
import { createFilter } from './helpers';
|
||||
|
||||
describe('Helpers', () => {
|
||||
describe('#createFilter', () => {
|
||||
test('if it is a string it returns untouched', () => {
|
||||
const filter = createFilter('even invalid strings return the same');
|
||||
expect(filter).toBe('even invalid strings return the same');
|
||||
});
|
||||
|
||||
test('if it is an ESQuery object it will be returned as a string', () => {
|
||||
const query: ESQuery = { term: { 'host.id': 'host-value' } };
|
||||
const filter = createFilter(query);
|
||||
expect(filter).toBe(JSON.stringify(query));
|
||||
});
|
||||
|
||||
test('if it is undefined, then undefined is returned', () => {
|
||||
const filter = createFilter(undefined);
|
||||
expect(filter).toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
12
x-pack/plugins/osquery/public/common/helpers.ts
Normal file
12
x-pack/plugins/osquery/public/common/helpers.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isString } from 'lodash/fp';
|
||||
|
||||
import { ESQuery } from '../../common/typed_json';
|
||||
|
||||
export const createFilter = (filterQuery: ESQuery | string | undefined) =>
|
||||
isString(filterQuery) ? filterQuery : JSON.stringify(filterQuery);
|
7
x-pack/plugins/osquery/public/common/index.ts
Normal file
7
x-pack/plugins/osquery/public/common/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './helpers';
|
7
x-pack/plugins/osquery/public/common/lib/kibana/index.ts
Normal file
7
x-pack/plugins/osquery/public/common/lib/kibana/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './kibana_react';
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
KibanaContextProvider,
|
||||
KibanaReactContextValue,
|
||||
useKibana,
|
||||
useUiSetting,
|
||||
useUiSetting$,
|
||||
withKibana,
|
||||
} from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { StartServices } from '../../../types';
|
||||
|
||||
export type KibanaContext = KibanaReactContextValue<StartServices>;
|
||||
export interface WithKibanaProps {
|
||||
kibana: KibanaContext;
|
||||
}
|
||||
|
||||
const useTypedKibana = () => useKibana<StartServices>();
|
||||
|
||||
export {
|
||||
KibanaContextProvider,
|
||||
useTypedKibana as useKibana,
|
||||
useUiSetting,
|
||||
useUiSetting$,
|
||||
withKibana,
|
||||
};
|
58
x-pack/plugins/osquery/public/components/app.tsx
Normal file
58
x-pack/plugins/osquery/public/components/app.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPageContent,
|
||||
EuiPageContentBody,
|
||||
EuiPageHeader,
|
||||
EuiTitle,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { PLUGIN_NAME } from '../../common';
|
||||
import { LiveQuery } from '../live_query';
|
||||
|
||||
export const OsqueryAppComponent = () => {
|
||||
return (
|
||||
<EuiPage restrictWidth="1000px">
|
||||
<EuiPageBody>
|
||||
<EuiPageHeader>
|
||||
<EuiTitle size="l">
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.helloWorldText"
|
||||
defaultMessage="{name}"
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
values={{ name: PLUGIN_NAME }}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
</EuiPageHeader>
|
||||
<EuiPageContent>
|
||||
<EuiPageContentBody>
|
||||
<EuiSpacer />
|
||||
|
||||
<Switch>
|
||||
<Route path={`/live_query`}>
|
||||
<LiveQuery />
|
||||
</Route>
|
||||
</Switch>
|
||||
|
||||
<EuiSpacer />
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
};
|
||||
|
||||
export const OsqueryApp = React.memo(OsqueryAppComponent);
|
49
x-pack/plugins/osquery/public/editor/index.tsx
Normal file
49
x-pack/plugins/osquery/public/editor/index.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiCodeEditor } from '@elastic/eui';
|
||||
import 'brace/mode/sql';
|
||||
import 'brace/theme/tomorrow';
|
||||
import 'brace/ext/language_tools';
|
||||
|
||||
const EDITOR_SET_OPTIONS = {
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
};
|
||||
|
||||
const EDITOR_PROPS = {
|
||||
$blockScrolling: true,
|
||||
};
|
||||
|
||||
interface OsqueryEditorProps {
|
||||
defaultValue: string;
|
||||
onChange: (newValue: string) => void;
|
||||
}
|
||||
|
||||
const OsqueryEditorComponent: React.FC<OsqueryEditorProps> = ({ defaultValue, onChange }) => {
|
||||
const handleChange = useCallback(
|
||||
(newValue) => {
|
||||
onChange(newValue);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiCodeEditor
|
||||
value={defaultValue}
|
||||
mode="sql"
|
||||
theme="tomorrow"
|
||||
onChange={handleChange}
|
||||
name="osquery_editor"
|
||||
setOptions={EDITOR_SET_OPTIONS}
|
||||
editorProps={EDITOR_PROPS}
|
||||
height="200px"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const OsqueryEditor = React.memo(OsqueryEditorComponent);
|
15
x-pack/plugins/osquery/public/index.ts
Normal file
15
x-pack/plugins/osquery/public/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from 'src/core/public';
|
||||
import { OsqueryPlugin } from './plugin';
|
||||
|
||||
// This exports static code and TypeScript types,
|
||||
// as well as, Kibana Platform `plugin()` initializer.
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new OsqueryPlugin(initializerContext);
|
||||
}
|
||||
export { OsqueryPluginSetup, OsqueryPluginStart } from './types';
|
37
x-pack/plugins/osquery/public/live_query/edit/index.tsx
Normal file
37
x-pack/plugins/osquery/public/live_query/edit/index.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useActionDetails } from '../../actions/use_action_details';
|
||||
import { ResultTabs } from './tabs';
|
||||
import { LiveQueryForm } from '../form';
|
||||
|
||||
const EditLiveQueryPageComponent = () => {
|
||||
const { actionId } = useParams<{ actionId: string }>();
|
||||
const [loading, { actionDetails }] = useActionDetails({ actionId });
|
||||
|
||||
const handleSubmit = useCallback(() => Promise.resolve(), []);
|
||||
|
||||
if (loading) {
|
||||
return <>{'Loading...'}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isEmpty(actionDetails) && (
|
||||
<LiveQueryForm actionDetails={actionDetails} onSubmit={handleSubmit} />
|
||||
)}
|
||||
<EuiSpacer />
|
||||
<ResultTabs />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const EditLiveQueryPage = React.memo(EditLiveQueryPageComponent);
|
57
x-pack/plugins/osquery/public/live_query/edit/tabs.tsx
Normal file
57
x-pack/plugins/osquery/public/live_query/edit/tabs.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiTabbedContent, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { ResultsTable } from '../../results/results_table';
|
||||
import { ActionResultsTable } from '../../action_results/action_results_table';
|
||||
|
||||
const ResultTabsComponent = () => {
|
||||
const { actionId } = useParams<{ actionId: string }>();
|
||||
const tabs = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'status',
|
||||
name: 'Status',
|
||||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ActionResultsTable actionId={actionId} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'results',
|
||||
name: 'Results',
|
||||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ResultsTable actionId={actionId} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
[actionId]
|
||||
);
|
||||
|
||||
const handleTabClick = useCallback((tab) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('clicked tab', tab);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EuiTabbedContent
|
||||
tabs={tabs}
|
||||
initialSelectedTab={tabs[0]}
|
||||
autoFocus="selected"
|
||||
onTabClick={handleTabClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ResultTabs = React.memo(ResultTabsComponent);
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { FieldHook } from '../../shared_imports';
|
||||
import { AgentsTable } from '../../agents/agents_table';
|
||||
|
||||
interface AgentsTableFieldProps {
|
||||
field: FieldHook<string[]>;
|
||||
}
|
||||
|
||||
const AgentsTableFieldComponent: React.FC<AgentsTableFieldProps> = ({ field }) => {
|
||||
const { value, setValue } = field;
|
||||
const handleChange = useCallback(
|
||||
(props) => {
|
||||
if (props !== value) {
|
||||
return setValue(props);
|
||||
}
|
||||
},
|
||||
[value, setValue]
|
||||
);
|
||||
|
||||
return <AgentsTable selectedAgents={value} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
export const AgentsTableField = React.memo(AgentsTableFieldComponent);
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { OsqueryEditor } from '../../editor';
|
||||
import { FieldHook } from '../../shared_imports';
|
||||
|
||||
interface CodeEditorFieldProps {
|
||||
field: FieldHook<{ query: string }>;
|
||||
}
|
||||
|
||||
const CodeEditorFieldComponent: React.FC<CodeEditorFieldProps> = ({ field }) => {
|
||||
const { value, setValue } = field;
|
||||
const handleChange = useCallback(
|
||||
(newQuery) => {
|
||||
setValue({
|
||||
...value,
|
||||
query: newQuery,
|
||||
});
|
||||
},
|
||||
[value, setValue]
|
||||
);
|
||||
|
||||
return <OsqueryEditor defaultValue={value.query} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
export const CodeEditorField = React.memo(CodeEditorFieldComponent);
|
56
x-pack/plugins/osquery/public/live_query/form/index.tsx
Normal file
56
x-pack/plugins/osquery/public/live_query/form/index.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { UseField, Form, useForm } from '../../shared_imports';
|
||||
import { AgentsTableField } from './agents_table_field';
|
||||
import { CodeEditorField } from './code_editor_field';
|
||||
|
||||
const FORM_ID = 'liveQueryForm';
|
||||
|
||||
interface LiveQueryFormProps {
|
||||
actionDetails?: Record<string, string>;
|
||||
onSubmit: (payload: Record<string, string>) => Promise<void>;
|
||||
}
|
||||
|
||||
const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({ actionDetails, onSubmit }) => {
|
||||
const handleSubmit = useCallback(
|
||||
(payload) => {
|
||||
onSubmit(payload);
|
||||
return Promise.resolve();
|
||||
},
|
||||
[onSubmit]
|
||||
);
|
||||
|
||||
const { form } = useForm({
|
||||
id: FORM_ID,
|
||||
// schema: formSchema,
|
||||
onSubmit: handleSubmit,
|
||||
options: {
|
||||
stripEmptyFields: false,
|
||||
},
|
||||
defaultValue: actionDetails,
|
||||
deserializer: ({ fields, _source }) => ({
|
||||
agents: fields?.agents,
|
||||
command: _source?.data?.commands[0],
|
||||
}),
|
||||
});
|
||||
|
||||
const { submit } = form;
|
||||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<UseField path="agents" component={AgentsTableField} />
|
||||
<EuiSpacer />
|
||||
<UseField path="command" component={CodeEditorField} />
|
||||
<EuiButton onClick={submit}>Send query</EuiButton>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export const LiveQueryForm = React.memo(LiveQueryFormComponent);
|
17
x-pack/plugins/osquery/public/live_query/form/schema.ts
Normal file
17
x-pack/plugins/osquery/public/live_query/form/schema.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FIELD_TYPES, FormSchema } from '../../shared_imports';
|
||||
|
||||
export const formSchema: FormSchema = {
|
||||
agents: {
|
||||
type: FIELD_TYPES.MULTI_SELECT,
|
||||
},
|
||||
command: {
|
||||
type: FIELD_TYPES.TEXTAREA,
|
||||
validations: [],
|
||||
},
|
||||
};
|
32
x-pack/plugins/osquery/public/live_query/index.tsx
Normal file
32
x-pack/plugins/osquery/public/live_query/index.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Switch, Route, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import { QueriesPage } from './queries';
|
||||
import { NewLiveQueryPage } from './new';
|
||||
import { EditLiveQueryPage } from './edit';
|
||||
|
||||
const LiveQueryComponent = () => {
|
||||
const match = useRouteMatch();
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${match.url}/queries/new`}>
|
||||
<NewLiveQueryPage />
|
||||
</Route>
|
||||
<Route path={`${match.url}/queries/:actionId`}>
|
||||
<EditLiveQueryPage />
|
||||
</Route>
|
||||
<Route path={`${match.url}/queries`}>
|
||||
<QueriesPage />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
export const LiveQuery = React.memo(LiveQueryComponent);
|
29
x-pack/plugins/osquery/public/live_query/new/index.tsx
Normal file
29
x-pack/plugins/osquery/public/live_query/new/index.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { LiveQueryForm } from '../form';
|
||||
|
||||
const NewLiveQueryPageComponent = () => {
|
||||
const { http } = useKibana().services;
|
||||
const history = useHistory();
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (props) => {
|
||||
const response = await http.post('/api/osquery/queries', { body: JSON.stringify(props) });
|
||||
const requestParamsActionId = JSON.parse(response.meta.request.params.body).action_id;
|
||||
history.push(`/live_query/queries/${requestParamsActionId}`);
|
||||
},
|
||||
[history, http]
|
||||
);
|
||||
|
||||
return <LiveQueryForm onSubmit={handleSubmit} />;
|
||||
};
|
||||
|
||||
export const NewLiveQueryPage = React.memo(NewLiveQueryPageComponent);
|
24
x-pack/plugins/osquery/public/live_query/queries/index.tsx
Normal file
24
x-pack/plugins/osquery/public/live_query/queries/index.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { ActionsTable } from '../../actions/actions_table';
|
||||
|
||||
const QueriesPageComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<EuiTitle>
|
||||
<h1>{'Queries'}</h1>
|
||||
</EuiTitle>
|
||||
<EuiSpacer />
|
||||
<ActionsTable />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const QueriesPage = React.memo(QueriesPageComponent);
|
64
x-pack/plugins/osquery/public/plugin.ts
Normal file
64
x-pack/plugins/osquery/public/plugin.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
AppMountParameters,
|
||||
CoreSetup,
|
||||
Plugin,
|
||||
PluginInitializerContext,
|
||||
CoreStart,
|
||||
} from 'src/core/public';
|
||||
import { Storage } from '../../../../src/plugins/kibana_utils/public';
|
||||
import { OsqueryPluginSetup, OsqueryPluginStart, AppPluginStartDependencies } from './types';
|
||||
import { PLUGIN_NAME } from '../common';
|
||||
|
||||
export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginStart> {
|
||||
private kibanaVersion: string;
|
||||
private storage = new Storage(localStorage);
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.kibanaVersion = this.initializerContext.env.packageInfo.version;
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup): OsqueryPluginSetup {
|
||||
const config = this.initializerContext.config.get<{ enabled: boolean }>();
|
||||
|
||||
if (!config.enabled) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const storage = this.storage;
|
||||
const kibanaVersion = this.kibanaVersion;
|
||||
// Register an application into the side navigation menu
|
||||
core.application.register({
|
||||
id: 'osquery',
|
||||
title: PLUGIN_NAME,
|
||||
async mount(params: AppMountParameters) {
|
||||
// Get start services as specified in kibana.json
|
||||
const [coreStart, depsStart] = await core.getStartServices();
|
||||
// Load application bundle
|
||||
const { renderApp } = await import('./application');
|
||||
// Render the application
|
||||
return renderApp(
|
||||
coreStart,
|
||||
depsStart as AppPluginStartDependencies,
|
||||
params,
|
||||
storage,
|
||||
kibanaVersion
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Return methods that should be available to other plugins
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart): OsqueryPluginStart {
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
37
x-pack/plugins/osquery/public/results/helpers.ts
Normal file
37
x-pack/plugins/osquery/public/results/helpers.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
PaginationInputPaginated,
|
||||
FactoryQueryTypes,
|
||||
StrategyResponseType,
|
||||
Inspect,
|
||||
} from '../../common/search_strategy';
|
||||
|
||||
export type InspectResponse = Inspect & { response: string[] };
|
||||
|
||||
export const generateTablePaginationOptions = (
|
||||
activePage: number,
|
||||
limit: number,
|
||||
isBucketSort?: boolean
|
||||
): PaginationInputPaginated => {
|
||||
const cursorStart = activePage * limit;
|
||||
return {
|
||||
activePage,
|
||||
cursorStart,
|
||||
fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5,
|
||||
querySize: isBucketSort ? limit : limit + cursorStart,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInspectResponse = <T extends FactoryQueryTypes>(
|
||||
response: StrategyResponseType<T>,
|
||||
prevResponse: InspectResponse
|
||||
): InspectResponse => ({
|
||||
dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [],
|
||||
response:
|
||||
response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response,
|
||||
});
|
119
x-pack/plugins/osquery/public/results/results_table.tsx
Normal file
119
x-pack/plugins/osquery/public/results/results_table.tsx
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEmpty, isEqual, keys, map } from 'lodash/fp';
|
||||
import { EuiDataGrid, EuiDataGridProps, EuiDataGridColumn } from '@elastic/eui';
|
||||
import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react';
|
||||
|
||||
import { EuiDataGridSorting } from '@elastic/eui';
|
||||
import { useAllResults } from './use_all_results';
|
||||
import { Direction, ResultEdges } from '../../common/search_strategy';
|
||||
|
||||
const DataContext = createContext<ResultEdges>([]);
|
||||
|
||||
interface ResultsTableComponentProps {
|
||||
actionId: string;
|
||||
}
|
||||
|
||||
const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({ actionId }) => {
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 });
|
||||
const onChangeItemsPerPage = useCallback(
|
||||
(pageSize) =>
|
||||
setPagination((currentPagination) => ({
|
||||
...currentPagination,
|
||||
pageSize,
|
||||
pageIndex: 0,
|
||||
})),
|
||||
[setPagination]
|
||||
);
|
||||
const onChangePage = useCallback(
|
||||
(pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })),
|
||||
[setPagination]
|
||||
);
|
||||
|
||||
const [columns, setColumns] = useState<EuiDataGridColumn[]>([]);
|
||||
|
||||
// ** Sorting config
|
||||
const [sortingColumns, setSortingColumns] = useState<EuiDataGridSorting['columns']>([]);
|
||||
const onSort = useCallback(
|
||||
(newSortingColumns) => {
|
||||
setSortingColumns(newSortingColumns);
|
||||
},
|
||||
[setSortingColumns]
|
||||
);
|
||||
|
||||
const [, { results, totalCount }] = useAllResults({
|
||||
actionId,
|
||||
activePage: pagination.pageIndex,
|
||||
limit: pagination.pageSize,
|
||||
direction: Direction.asc,
|
||||
sortField: '@timestamp',
|
||||
});
|
||||
|
||||
const [visibleColumns, setVisibleColumns] = useState<string[]>([]);
|
||||
const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [
|
||||
visibleColumns,
|
||||
setVisibleColumns,
|
||||
]);
|
||||
|
||||
const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo(
|
||||
() => ({ rowIndex, columnId, setCellProps }) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const data = useContext(DataContext);
|
||||
|
||||
const value = data[rowIndex].fields[columnId];
|
||||
|
||||
return !isEmpty(value) ? value : '-';
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const tableSorting = useMemo(() => ({ columns: sortingColumns, onSort }), [
|
||||
onSort,
|
||||
sortingColumns,
|
||||
]);
|
||||
|
||||
const tablePagination = useMemo(
|
||||
() => ({
|
||||
...pagination,
|
||||
pageSizeOptions: [10, 50, 100],
|
||||
onChangeItemsPerPage,
|
||||
onChangePage,
|
||||
}),
|
||||
[onChangeItemsPerPage, onChangePage, pagination]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const newColumns: EuiDataGridColumn[] = keys(results[0]?.fields)
|
||||
.sort()
|
||||
.map((fieldName) => ({
|
||||
id: fieldName,
|
||||
displayAsText: fieldName.split('.')[1],
|
||||
defaultSortDirection: 'asc',
|
||||
}));
|
||||
|
||||
if (!isEqual(columns, newColumns)) {
|
||||
setColumns(newColumns);
|
||||
setVisibleColumns(map('id', newColumns));
|
||||
}
|
||||
}, [columns, results]);
|
||||
|
||||
return (
|
||||
<DataContext.Provider value={results}>
|
||||
<EuiDataGrid
|
||||
aria-label="Osquery results"
|
||||
columns={columns}
|
||||
columnVisibility={columnVisibility}
|
||||
rowCount={totalCount}
|
||||
renderCellValue={renderCellValue}
|
||||
sorting={tableSorting}
|
||||
pagination={tablePagination}
|
||||
/>
|
||||
</DataContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const ResultsTable = React.memo(ResultsTableComponent);
|
15
x-pack/plugins/osquery/public/results/translations.ts
Normal file
15
x-pack/plugins/osquery/public/results/translations.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ERROR_ALL_RESULTS = i18n.translate('xpack.osquery.results.errorSearchDescription', {
|
||||
defaultMessage: `An error has occurred on all results search`,
|
||||
});
|
||||
|
||||
export const FAIL_ALL_RESULTS = i18n.translate('xpack.osquery.results.failSearchDescription', {
|
||||
defaultMessage: `Failed to fetch results`,
|
||||
});
|
164
x-pack/plugins/osquery/public/results/use_all_results.ts
Normal file
164
x-pack/plugins/osquery/public/results/use_all_results.ts
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { createFilter } from '../common/helpers';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import {
|
||||
ResultEdges,
|
||||
PageInfoPaginated,
|
||||
DocValueFields,
|
||||
OsqueryQueries,
|
||||
ResultsRequestOptions,
|
||||
ResultsStrategyResponse,
|
||||
Direction,
|
||||
} from '../../common/search_strategy';
|
||||
import { ESTermQuery } from '../../common/typed_json';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common';
|
||||
import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
|
||||
import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers';
|
||||
|
||||
const ID = 'resultsAllQuery';
|
||||
|
||||
export interface ResultsArgs {
|
||||
results: ResultEdges;
|
||||
id: string;
|
||||
inspect: InspectResponse;
|
||||
isInspected: boolean;
|
||||
pageInfo: PageInfoPaginated;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
interface UseAllResults {
|
||||
actionId: string;
|
||||
activePage: number;
|
||||
direction: Direction;
|
||||
limit: number;
|
||||
sortField: string;
|
||||
docValueFields?: DocValueFields[];
|
||||
filterQuery?: ESTermQuery | string;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export const useAllResults = ({
|
||||
actionId,
|
||||
activePage,
|
||||
direction,
|
||||
limit,
|
||||
sortField,
|
||||
docValueFields,
|
||||
filterQuery,
|
||||
skip = false,
|
||||
}: UseAllResults): [boolean, ResultsArgs] => {
|
||||
const { data, notifications } = useKibana().services;
|
||||
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [resultsRequest, setHostRequest] = useState<ResultsRequestOptions | null>(null);
|
||||
|
||||
const [resultsResponse, setResultsResponse] = useState<ResultsArgs>({
|
||||
results: [],
|
||||
id: ID,
|
||||
inspect: {
|
||||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
isInspected: false,
|
||||
pageInfo: {
|
||||
activePage: 0,
|
||||
fakeTotalCount: 0,
|
||||
showMorePagesIndicator: false,
|
||||
},
|
||||
totalCount: -1,
|
||||
});
|
||||
|
||||
const resultsSearch = useCallback(
|
||||
(request: ResultsRequestOptions | null) => {
|
||||
if (request == null || skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
let didCancel = false;
|
||||
const asyncSearch = async () => {
|
||||
abortCtrl.current = new AbortController();
|
||||
setLoading(true);
|
||||
|
||||
const searchSubscription$ = data.search
|
||||
.search<ResultsRequestOptions, ResultsStrategyResponse>(request, {
|
||||
strategy: 'osquerySearchStrategy',
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
})
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
if (isCompleteResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
setResultsResponse((prevResponse) => ({
|
||||
...prevResponse,
|
||||
results: response.edges,
|
||||
inspect: getInspectResponse(response, prevResponse.inspect),
|
||||
pageInfo: response.pageInfo,
|
||||
totalCount: response.totalCount,
|
||||
}));
|
||||
}
|
||||
searchSubscription$.unsubscribe();
|
||||
} else if (isErrorResponse(response)) {
|
||||
if (!didCancel) {
|
||||
setLoading(false);
|
||||
}
|
||||
// TODO: Make response error status clearer
|
||||
notifications.toasts.addWarning(i18n.ERROR_ALL_RESULTS);
|
||||
searchSubscription$.unsubscribe();
|
||||
}
|
||||
},
|
||||
error: (msg) => {
|
||||
if (!(msg instanceof AbortError)) {
|
||||
notifications.toasts.addDanger({ title: i18n.FAIL_ALL_RESULTS, text: msg.message });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
abortCtrl.current.abort();
|
||||
asyncSearch();
|
||||
return () => {
|
||||
didCancel = true;
|
||||
abortCtrl.current.abort();
|
||||
};
|
||||
},
|
||||
[data.search, notifications.toasts, skip]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setHostRequest((prevRequest) => {
|
||||
const myRequest = {
|
||||
...(prevRequest ?? {}),
|
||||
actionId,
|
||||
docValueFields: docValueFields ?? [],
|
||||
factoryQueryType: OsqueryQueries.results,
|
||||
filterQuery: createFilter(filterQuery),
|
||||
pagination: generateTablePaginationOptions(activePage, limit),
|
||||
sort: {
|
||||
direction,
|
||||
field: sortField,
|
||||
},
|
||||
};
|
||||
if (!deepEqual(prevRequest, myRequest)) {
|
||||
return myRequest;
|
||||
}
|
||||
return prevRequest;
|
||||
});
|
||||
}, [actionId, activePage, direction, docValueFields, filterQuery, limit, sortField]);
|
||||
|
||||
useEffect(() => {
|
||||
resultsSearch(resultsRequest);
|
||||
}, [resultsRequest, resultsSearch]);
|
||||
|
||||
return [loading, resultsResponse];
|
||||
};
|
29
x-pack/plugins/osquery/public/shared_imports.ts
Normal file
29
x-pack/plugins/osquery/public/shared_imports.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export {
|
||||
getUseField,
|
||||
getFieldValidityAndErrorMessage,
|
||||
FieldHook,
|
||||
FieldValidateResponse,
|
||||
FIELD_TYPES,
|
||||
Form,
|
||||
FormData,
|
||||
FormDataProvider,
|
||||
FormHook,
|
||||
FormSchema,
|
||||
UseField,
|
||||
UseMultiFields,
|
||||
useForm,
|
||||
useFormContext,
|
||||
useFormData,
|
||||
ValidationError,
|
||||
ValidationFunc,
|
||||
VALIDATION_TYPES,
|
||||
} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
|
||||
export { Field, SelectField } from '../../../../src/plugins/es_ui_shared/static/forms/components';
|
||||
export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers';
|
||||
export { ERROR_CODE } from '../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types';
|
26
x-pack/plugins/osquery/public/types.ts
Normal file
26
x-pack/plugins/osquery/public/types.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import { FleetStart } from '../../fleet/public';
|
||||
import { CoreStart } from '../../../../src/core/public';
|
||||
import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface OsqueryPluginSetup {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface OsqueryPluginStart {}
|
||||
|
||||
export interface AppPluginStartDependencies {
|
||||
navigation: NavigationPublicPluginStart;
|
||||
}
|
||||
|
||||
export interface StartPlugins {
|
||||
data: DataPublicPluginStart;
|
||||
fleet?: FleetStart;
|
||||
}
|
||||
|
||||
export type StartServices = CoreStart & StartPlugins;
|
13
x-pack/plugins/osquery/server/config.ts
Normal file
13
x-pack/plugins/osquery/server/config.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { TypeOf, schema } from '@kbn/config-schema';
|
||||
|
||||
export const ConfigSchema = schema.object({
|
||||
enabled: schema.boolean({ defaultValue: false }),
|
||||
});
|
||||
|
||||
export type ConfigType = TypeOf<typeof ConfigSchema>;
|
17
x-pack/plugins/osquery/server/create_config.ts
Normal file
17
x-pack/plugins/osquery/server/create_config.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { map } from 'rxjs/operators';
|
||||
import { PluginInitializerContext } from 'kibana/server';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { ConfigType } from './config';
|
||||
|
||||
export const createConfig$ = (
|
||||
context: PluginInitializerContext
|
||||
): Observable<Readonly<ConfigType>> => {
|
||||
return context.config.create<ConfigType>().pipe(map((config) => config));
|
||||
};
|
21
x-pack/plugins/osquery/server/index.ts
Normal file
21
x-pack/plugins/osquery/server/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from '../../../../src/core/server';
|
||||
import { OsqueryPlugin } from './plugin';
|
||||
import { ConfigSchema } from './config';
|
||||
|
||||
export const config = {
|
||||
schema: ConfigSchema,
|
||||
exposeToBrowser: {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new OsqueryPlugin(initializerContext);
|
||||
}
|
||||
|
||||
export { OsqueryPluginSetup, OsqueryPluginStart } from './types';
|
56
x-pack/plugins/osquery/server/plugin.ts
Normal file
56
x-pack/plugins/osquery/server/plugin.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { first } from 'rxjs/operators';
|
||||
import {
|
||||
PluginInitializerContext,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
Logger,
|
||||
} from '../../../../src/core/server';
|
||||
|
||||
import { createConfig$ } from './create_config';
|
||||
import { OsqueryPluginSetup, OsqueryPluginStart, SetupPlugins, StartPlugins } from './types';
|
||||
import { defineRoutes } from './routes';
|
||||
import { osquerySearchStrategyProvider } from './search_strategy/osquery';
|
||||
|
||||
export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginStart> {
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.logger = this.initializerContext.logger.get();
|
||||
}
|
||||
|
||||
public async setup(core: CoreSetup<StartPlugins, OsqueryPluginStart>, plugins: SetupPlugins) {
|
||||
this.logger.debug('osquery: Setup');
|
||||
const config = await createConfig$(this.initializerContext).pipe(first()).toPromise();
|
||||
|
||||
if (!config.enabled) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const router = core.http.createRouter();
|
||||
|
||||
// Register server side APIs
|
||||
defineRoutes(router);
|
||||
|
||||
core.getStartServices().then(([_, depsStart]) => {
|
||||
const osquerySearchStrategy = osquerySearchStrategyProvider(depsStart.data);
|
||||
|
||||
plugins.data.search.registerSearchStrategy('osquerySearchStrategy', osquerySearchStrategy);
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
this.logger.debug('osquery: Started');
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
50
x-pack/plugins/osquery/server/routes/index.ts
Normal file
50
x-pack/plugins/osquery/server/routes/index.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import uuid from 'uuid';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import moment from 'moment';
|
||||
|
||||
import { IRouter } from '../../../../../src/core/server';
|
||||
|
||||
export function defineRoutes(router: IRouter) {
|
||||
router.post(
|
||||
{
|
||||
path: '/api/osquery/queries',
|
||||
validate: {
|
||||
params: schema.object({}, { unknowns: 'allow' }),
|
||||
body: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
const query = await esClient.index<{}, {}>({
|
||||
index: '.fleet-actions-new',
|
||||
body: {
|
||||
action_id: uuid.v4(),
|
||||
'@timestamp': moment().toISOString(),
|
||||
expiration: moment().add(2, 'days').toISOString(),
|
||||
type: 'APP_ACTION',
|
||||
input_id: 'osquery',
|
||||
// @ts-expect-error
|
||||
agents: request.body.agents,
|
||||
data: {
|
||||
commands: [
|
||||
{
|
||||
id: uuid.v4(),
|
||||
// @ts-expect-error
|
||||
query: request.body.command.query,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
return response.ok({
|
||||
body: query,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
|
||||
import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants';
|
||||
import {
|
||||
ActionsStrategyResponse,
|
||||
ActionsRequestOptions,
|
||||
OsqueryQueries,
|
||||
} from '../../../../../../common/search_strategy/osquery';
|
||||
import { inspectStringifyObject } from '../../../../../../common/utils/build_query';
|
||||
import { OsqueryFactory } from '../../types';
|
||||
import { buildActionsQuery } from './query.all_actions.dsl';
|
||||
|
||||
export const allActions: OsqueryFactory<OsqueryQueries.actions> = {
|
||||
buildDsl: (options: ActionsRequestOptions) => {
|
||||
if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) {
|
||||
throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`);
|
||||
}
|
||||
return buildActionsQuery(options);
|
||||
},
|
||||
parse: async (
|
||||
options: ActionsRequestOptions,
|
||||
response: IEsSearchResponse<object>
|
||||
): Promise<ActionsStrategyResponse> => {
|
||||
const { activePage } = options.pagination;
|
||||
const inspect = {
|
||||
dsl: [inspectStringifyObject(buildActionsQuery(options))],
|
||||
};
|
||||
|
||||
return {
|
||||
...response,
|
||||
inspect,
|
||||
edges: response.rawResponse.hits.hits,
|
||||
totalCount: response.rawResponse.hits.total,
|
||||
pageInfo: {
|
||||
activePage: activePage ?? 0,
|
||||
fakeTotalCount: 0,
|
||||
showMorePagesIndicator: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common';
|
||||
import { AgentsRequestOptions } from '../../../../../../common/search_strategy';
|
||||
import { createQueryFilterClauses } from '../../../../../../common/utils/build_query';
|
||||
|
||||
export const buildActionsQuery = ({
|
||||
docValueFields,
|
||||
filterQuery,
|
||||
pagination: { activePage, querySize },
|
||||
sort,
|
||||
}: AgentsRequestOptions): ISearchRequestParams => {
|
||||
const filter = [...createQueryFilterClauses(filterQuery)];
|
||||
|
||||
const dslQuery = {
|
||||
allowNoIndices: true,
|
||||
index: '.fleet-actions',
|
||||
ignoreUnavailable: true,
|
||||
body: {
|
||||
query: { bool: { filter } },
|
||||
from: activePage * querySize,
|
||||
size: querySize,
|
||||
track_total_hits: true,
|
||||
fields: ['*'],
|
||||
},
|
||||
};
|
||||
|
||||
return dslQuery;
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
|
||||
import {
|
||||
ActionDetailsStrategyResponse,
|
||||
ActionDetailsRequestOptions,
|
||||
OsqueryQueries,
|
||||
} from '../../../../../../common/search_strategy/osquery';
|
||||
|
||||
import { inspectStringifyObject } from '../../../../../../common/utils/build_query';
|
||||
import { OsqueryFactory } from '../../types';
|
||||
import { buildActionDetailsQuery } from './query.action_details.dsl';
|
||||
|
||||
export const actionDetails: OsqueryFactory<OsqueryQueries.actionDetails> = {
|
||||
buildDsl: (options: ActionDetailsRequestOptions) => {
|
||||
return buildActionDetailsQuery(options);
|
||||
},
|
||||
parse: async (
|
||||
options: ActionDetailsRequestOptions,
|
||||
response: IEsSearchResponse<unknown>
|
||||
): Promise<ActionDetailsStrategyResponse> => {
|
||||
const inspect = {
|
||||
dsl: [inspectStringifyObject(buildActionDetailsQuery(options))],
|
||||
};
|
||||
|
||||
return {
|
||||
...response,
|
||||
inspect,
|
||||
actionDetails: response.rawResponse.hits.hits[0],
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common';
|
||||
import { ActionDetailsRequestOptions } from '../../../../../../common/search_strategy';
|
||||
import { createQueryFilterClauses } from '../../../../../../common/utils/build_query';
|
||||
|
||||
export const buildActionDetailsQuery = ({
|
||||
actionId,
|
||||
docValueFields,
|
||||
filterQuery,
|
||||
}: ActionDetailsRequestOptions): ISearchRequestParams => {
|
||||
const filter = [
|
||||
...createQueryFilterClauses(filterQuery),
|
||||
{
|
||||
match_phrase: {
|
||||
action_id: actionId,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const dslQuery = {
|
||||
allowNoIndices: true,
|
||||
index: '.fleet-actions',
|
||||
ignoreUnavailable: true,
|
||||
body: {
|
||||
query: { bool: { filter } },
|
||||
size: 1,
|
||||
fields: ['*'],
|
||||
},
|
||||
};
|
||||
|
||||
return dslQuery;
|
||||
};
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './all';
|
||||
export * from './details';
|
||||
export * from './results';
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
|
||||
import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants';
|
||||
import {
|
||||
ActionResultsStrategyResponse,
|
||||
ActionResultsRequestOptions,
|
||||
OsqueryQueries,
|
||||
} from '../../../../../../common/search_strategy/osquery';
|
||||
|
||||
import { inspectStringifyObject } from '../../../../../../common/utils/build_query';
|
||||
import { OsqueryFactory } from '../../types';
|
||||
import { buildActionResultsQuery } from './query.action_results.dsl';
|
||||
|
||||
export const actionResults: OsqueryFactory<OsqueryQueries.actionResults> = {
|
||||
buildDsl: (options: ActionResultsRequestOptions) => {
|
||||
if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) {
|
||||
throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`);
|
||||
}
|
||||
return buildActionResultsQuery(options);
|
||||
},
|
||||
parse: async (
|
||||
options: ActionResultsRequestOptions,
|
||||
response: IEsSearchResponse<object>
|
||||
): Promise<ActionResultsStrategyResponse> => {
|
||||
const { activePage } = options.pagination;
|
||||
const inspect = {
|
||||
dsl: [inspectStringifyObject(buildActionResultsQuery(options))],
|
||||
};
|
||||
|
||||
return {
|
||||
...response,
|
||||
inspect,
|
||||
edges: response.rawResponse.hits.hits,
|
||||
totalCount: response.rawResponse.hits.total,
|
||||
pageInfo: {
|
||||
activePage: activePage ?? 0,
|
||||
fakeTotalCount: 0,
|
||||
showMorePagesIndicator: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common';
|
||||
import { ActionResultsRequestOptions } from '../../../../../../common/search_strategy';
|
||||
import { createQueryFilterClauses } from '../../../../../../common/utils/build_query';
|
||||
|
||||
export const buildActionResultsQuery = ({
|
||||
actionId,
|
||||
docValueFields,
|
||||
filterQuery,
|
||||
pagination: { activePage, querySize },
|
||||
sort,
|
||||
}: ActionResultsRequestOptions): ISearchRequestParams => {
|
||||
const filter = [
|
||||
...createQueryFilterClauses(filterQuery),
|
||||
{
|
||||
match_phrase: {
|
||||
action_id: actionId,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const dslQuery = {
|
||||
allowNoIndices: true,
|
||||
index: '.fleet-actions-results',
|
||||
ignoreUnavailable: true,
|
||||
body: {
|
||||
query: { bool: { filter } },
|
||||
from: activePage * querySize,
|
||||
size: querySize,
|
||||
track_total_hits: true,
|
||||
fields: ['*'],
|
||||
},
|
||||
};
|
||||
|
||||
return dslQuery;
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common';
|
||||
import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../common/constants';
|
||||
import {
|
||||
AgentsStrategyResponse,
|
||||
AgentsRequestOptions,
|
||||
OsqueryQueries,
|
||||
} from '../../../../../common/search_strategy/osquery';
|
||||
|
||||
import { Agent } from '../../../../../common/shared_imports';
|
||||
import { inspectStringifyObject } from '../../../../../common/utils/build_query';
|
||||
import { OsqueryFactory } from '../types';
|
||||
import { buildAgentsQuery } from './query.all_agents.dsl';
|
||||
|
||||
export const allAgents: OsqueryFactory<OsqueryQueries.agents> = {
|
||||
buildDsl: (options: AgentsRequestOptions) => {
|
||||
if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) {
|
||||
throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`);
|
||||
}
|
||||
return buildAgentsQuery(options);
|
||||
},
|
||||
parse: async (
|
||||
options: AgentsRequestOptions,
|
||||
response: IEsSearchResponse<Agent>
|
||||
): Promise<AgentsStrategyResponse> => {
|
||||
const { activePage } = options.pagination;
|
||||
const inspect = {
|
||||
dsl: [inspectStringifyObject(buildAgentsQuery(options))],
|
||||
};
|
||||
|
||||
return {
|
||||
...response,
|
||||
inspect,
|
||||
edges: response.rawResponse.hits.hits.map((hit) => ({ _id: hit._id, ...hit._source })),
|
||||
totalCount: response.rawResponse.hits.total,
|
||||
pageInfo: {
|
||||
activePage: activePage ?? 0,
|
||||
fakeTotalCount: 0,
|
||||
showMorePagesIndicator: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { ISearchRequestParams } from '../../../../../../../../src/plugins/data/common';
|
||||
import { AgentsRequestOptions } from '../../../../../common/search_strategy';
|
||||
import { createQueryFilterClauses } from '../../../../../common/utils/build_query';
|
||||
|
||||
export const buildAgentsQuery = ({
|
||||
docValueFields,
|
||||
filterQuery,
|
||||
pagination: { querySize },
|
||||
sort,
|
||||
}: AgentsRequestOptions): ISearchRequestParams => {
|
||||
const filter = [...createQueryFilterClauses(filterQuery)];
|
||||
|
||||
const dslQuery = {
|
||||
allowNoIndices: true,
|
||||
index: '.fleet-agents',
|
||||
ignoreUnavailable: true,
|
||||
body: {
|
||||
...(!isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}),
|
||||
query: { bool: { filter } },
|
||||
track_total_hits: true,
|
||||
},
|
||||
};
|
||||
|
||||
return dslQuery;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue