Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Event Log] adds query support to the Event Log #62015

Merged
merged 26 commits into from
Apr 6, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2d3f783
added Start api on Event Log plugin
gmmorris Mar 31, 2020
0263507
added empty skeleton for Event Log FTs
gmmorris Mar 31, 2020
a0e1dcd
Merge branch 'master' into event-log/query-support
gmmorris Mar 31, 2020
e4b601d
added functional test to public find events api
gmmorris Apr 1, 2020
cbb3a14
added test for pagination
gmmorris Apr 1, 2020
7805cc1
Merge branch 'master' into event-log/query-support
gmmorris Apr 1, 2020
1a32a00
fixed unit tests
gmmorris Apr 2, 2020
3cc9ed4
Merge branch 'master' into event-log/query-support
gmmorris Apr 2, 2020
66efd01
added support for date ranges
gmmorris Apr 2, 2020
cff6041
removed unused code
gmmorris Apr 2, 2020
711c098
replaces valdiation typing
gmmorris Apr 2, 2020
3eada98
Merge branch 'master' into event-log/query-support
gmmorris Apr 2, 2020
e893e01
Revert "replaces valdiation typing"
gmmorris Apr 3, 2020
b6ac43a
replaces match with term
gmmorris Apr 3, 2020
2bbac1a
added sorting
gmmorris Apr 3, 2020
a492453
Merge branch 'master' into event-log/query-support
gmmorris Apr 3, 2020
ea9f1fc
fixed saved objects nested query
gmmorris Apr 3, 2020
2b6a895
Merge branch 'master' into event-log/query-support
elasticmachine Apr 3, 2020
e166aa2
Merge branch 'master' into event-log/query-support
elasticmachine Apr 5, 2020
da25c0b
updated plugin FTs path
gmmorris Apr 6, 2020
503bea6
Merge branch 'event-log/query-support' of github.com:gmmorris/kibana …
gmmorris Apr 6, 2020
0fc6116
Update x-pack/plugins/encrypted_saved_objects/README.md
gmmorris Apr 6, 2020
8582fca
Update x-pack/plugins/encrypted_saved_objects/README.md
gmmorris Apr 6, 2020
2aaa21f
remofed validation from tests
gmmorris Apr 6, 2020
4b0d3c2
Merge branch 'event-log/query-support' of github.com:gmmorris/kibana …
gmmorris Apr 6, 2020
8cb2ee6
fixed typos
gmmorris Apr 6, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@
"examples/*",
"test/plugin_functional/plugins/*",
"test/interpreter_functional/plugins/*",
"x-pack/test/functional_with_es_ssl/fixtures/plugins/*"
"x-pack/test/functional_with_es_ssl/fixtures/plugins/*",
"x-pack/test/plugin_api_integration/plugins/*"
],
"nohoist": [
"**/@types/*",
Expand Down
1 change: 1 addition & 0 deletions test/scripts/jenkins_xpack_build_kibana.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ echo " -> building kibana platform plugins"
node scripts/build_kibana_platform_plugins \
--scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \
--scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \
--scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \
--verbose;

# doesn't persist, also set in kibanaPipeline.groovy
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/event_log/common/index.ts
Original file line number Diff line number Diff line change
@@ -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 const BASE_EVENT_LOG_API_PATH = '/api/event_log';
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const createClusterClientMock = () => {
createIndexTemplate: jest.fn(),
doesAliasExist: jest.fn(),
createIndex: jest.fn(),
queryEventsBySavedObject: jest.fn(),
};
return mock;
};
Expand Down
35 changes: 35 additions & 0 deletions x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,38 @@ describe('createIndex', () => {
await clusterClientAdapter.createIndex('foo');
});
});

describe('queryEventsBySavedObject', () => {
test('should call cluster with proper arguments', async () => {
clusterClient.callAsInternalUser.mockResolvedValue({
hits: {
hits: [],
},
});
await clusterClientAdapter.queryEventsBySavedObject(
'index-name',
'saved-object-type',
'saved-object-id',
{ page: 10, per_page: 10 }
);
expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('search', {
index: 'index-name',
body: {
from: 90,
size: 10,
query: {
bool: {
must: [
{ match: { 'kibana.saved_objects.type.keyword': 'saved-object-type' } },
{
match: {
'kibana.saved_objects.id.keyword': 'saved-object-id',
},
},
],
},
},
},
});
});
});
38 changes: 38 additions & 0 deletions x-pack/plugins/event_log/server/es/cluster_client_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*/

import { Logger, ClusterClient } from '../../../../../src/core/server';
import { IEvent } from '../types';
import { FindOptionsType } from '../event_log_client';

export type EsClusterClient = Pick<ClusterClient, 'callAsInternalUser' | 'asScoped'>;
export type IClusterClientAdapter = PublicMethodsOf<ClusterClientAdapter>;
Expand Down Expand Up @@ -107,6 +109,42 @@ export class ClusterClientAdapter {
}
}

public async queryEventsBySavedObject(
index: string,
type: string,
id: string,
{ page, per_page: size }: FindOptionsType
): Promise<any[]> {
try {
const {
hits: { hits },
} = await this.callEs('search', {
index,
body: {
gmmorris marked this conversation as resolved.
Show resolved Hide resolved
size,
from: (page - 1) * size,
query: {
bool: {
must: [
{ match: { 'kibana.saved_objects.type.keyword': type } },
{
match: {
'kibana.saved_objects.id.keyword': id,
},
},
],
},
},
},
});
return hits.map((hit: any) => hit._source) as IEvent[];
gmmorris marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
throw new Error(
`querying for Event Log by for type "${type}" and id "${id}" failed with: ${err.message}`
);
}
}

private async callEs(operation: string, body?: any): Promise<any> {
try {
this.debug(`callEs(${operation}) calls:`, body);
Expand Down
18 changes: 18 additions & 0 deletions x-pack/plugins/event_log/server/event_log_client.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { IEventLogClient } from './types';

const createEventLogClientMock = () => {
const mock: jest.Mocked<IEventLogClient> = {
findEventsBySavedObject: jest.fn(),
};
return mock;
};

export const eventLogClientMock = {
create: createEventLogClientMock,
};
142 changes: 142 additions & 0 deletions x-pack/plugins/event_log/server/event_log_client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* 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 { EventLogClient } from './event_log_client';
import { contextMock } from './es/context.mock';
import { savedObjectsClientMock } from 'src/core/server/mocks';
import { merge } from 'lodash';

describe('EventLogStart', () => {
describe('findEventsBySavedObject', () => {
test('verifies that the user can to access the specified saved object', async () => {
gmmorris marked this conversation as resolved.
Show resolved Hide resolved
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
});

savedObjectsClient.get.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
references: [],
});

await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id');

expect(savedObjectsClient.get).toHaveBeenCalledWith('saved-object-type', 'saved-object-id');
});

test('throws when the user doesnt have permission to access the specified saved object', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
});

savedObjectsClient.get.mockRejectedValue(new Error('Fail'));

expect(
eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id')
).rejects.toMatchInlineSnapshot(`[Error: Fail]`);
});

test('fetches all event that reference the saved object', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
});

savedObjectsClient.get.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
references: [],
});

const expectedEvents = [
fakeEvent({
kibana: {
saved_objects: [
{
id: 'saved-object-id',
type: 'saved-object-type',
},
{
type: 'action',
id: '1',
},
],
},
}),
fakeEvent({
kibana: {
saved_objects: [
{
id: 'saved-object-id',
type: 'saved-object-type',
},
{
type: 'action',
id: '2',
},
],
},
}),
];

esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(expectedEvents);

expect(
await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id')
).toEqual(expectedEvents);

expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith(
esContext.esNames.alias,
'saved-object-type',
'saved-object-id',
{
page: 1,
per_page: 10,
}
);
});
});
});

function fakeEvent(overrides = {}) {
return merge(
{
event: {
provider: 'actions',
action: 'execute',
start: new Date('2020-03-30T14:55:47.054Z'),
end: new Date('2020-03-30T14:55:47.055Z'),
duration: 1000000,
},
kibana: {
namespace: 'default',
saved_objects: [
{
type: 'action',
id: '968f1b82-0414-4a10-becc-56b6473e4a29',
},
],
server_uuid: '5b2de169-2785-441b-ae8c-186a1936b17d',
},
message: 'action executed: .server-log:968f1b82-0414-4a10-becc-56b6473e4a29: logger',
'@timestamp': new Date('2020-03-30T14:55:47.055Z'),
ecs: {
version: '1.3.1',
},
},
overrides
);
}
50 changes: 50 additions & 0 deletions x-pack/plugins/event_log/server/event_log_client.ts
Original file line number Diff line number Diff line change
@@ -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 { Observable } from 'rxjs';
import { ClusterClient, SavedObjectsClientContract } from 'src/core/server';

import { schema, TypeOf } from '@kbn/config-schema';
import { EsContext } from './es';
import { IEventLogClient, IEvent } from './types';
export type PluginClusterClient = Pick<ClusterClient, 'callAsInternalUser' | 'asScoped'>;
export type AdminClusterClient$ = Observable<PluginClusterClient>;

interface EventLogServiceCtorParams {
esContext: EsContext;
savedObjectsClient: SavedObjectsClientContract;
}

export const findOptionsSchema = schema.object({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably should support a few more things like sorting and searching at the API level to support the searchbox and table in the UI?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to leave that for when those features were being built, keep the PR smaller 🤷‍♂

But sure, I've now added sorting.
I'm not sure how we'd want to support searching with a searchbox so I'll leave that for the PR that actually specifies that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, thanks for adding the sorting 👍

per_page: schema.number({ defaultValue: 10, min: 0 }),
page: schema.number({ defaultValue: 1, min: 1 }),
});
export type FindOptionsType = TypeOf<typeof findOptionsSchema>;

// note that clusterClient may be null, indicating we can't write to ES
export class EventLogClient implements IEventLogClient {
private esContext: EsContext;
private savedObjectsClient: SavedObjectsClientContract;

constructor({ esContext, savedObjectsClient }: EventLogServiceCtorParams) {
this.esContext = esContext;
this.savedObjectsClient = savedObjectsClient;
}

async findEventsBySavedObject(
type: string,
id: string,
options?: Partial<FindOptionsType>
): Promise<IEvent[]> {
await this.savedObjectsClient.get(type, id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth a comment here to explain why it's important to do this even though we're not doing anything with the response. It would be nice to have api integration tests for this as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, though there's an explicit unit test that validates this and I consider that more reliable documentation than a comment, but happy to add one 🤷‍♂

return (await this.esContext.esAdapter.queryEventsBySavedObject(
this.esContext.esNames.alias,
type,
id,
findOptionsSchema.validate(options ?? {})
)) as IEvent[];
}
}
18 changes: 18 additions & 0 deletions x-pack/plugins/event_log/server/event_log_client_service.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { IEventLogClientService } from './types';

const createEventLogStartMock = () => {
const mock: jest.Mocked<IEventLogClientService> = {
getClient: jest.fn(),
};
return mock;
};

export const eventLogStartMock = {
gmmorris marked this conversation as resolved.
Show resolved Hide resolved
create: createEventLogStartMock,
};
Loading