Skip to content

Commit

Permalink
[Event log] Use Alerts client & Actions client when fetching these ty…
Browse files Browse the repository at this point in the history
…pes of SOs (elastic#73257)

Introduces a pluggable API to Event Log which allows custom Providers for Saved Objects which is used to ensure a user is authorised to get the Saved Object referenced in the Event Log whenever the find api is called.
  • Loading branch information
gmmorris committed Aug 11, 2020
1 parent 59dd3a5 commit bfae087
Show file tree
Hide file tree
Showing 18 changed files with 319 additions and 62 deletions.
1 change: 1 addition & 0 deletions x-pack/.i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"xpack.actions": "plugins/actions",
"xpack.uiActionsEnhanced": ["plugins/ui_actions_enhanced", "examples/ui_actions_enhanced_examples"],
"xpack.alerts": "plugins/alerts",
"xpack.eventLog": "plugins/event_log",
"xpack.alertingBuiltins": "plugins/alerting_builtins",
"xpack.apm": ["legacy/plugins/apm", "plugins/apm"],
"xpack.beatsManagement": ["legacy/plugins/beats_management", "plugins/beats_management"],
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/actions/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
private licenseState: ILicenseState | null = null;
private spaces?: SpacesServiceSetup;
private security?: SecurityPluginSetup;
private eventLogService?: IEventLogService;
private eventLogger?: IEventLogger;
private isESOUsingEphemeralEncryptionKey?: boolean;
private readonly telemetryLogger: Logger;
Expand Down Expand Up @@ -160,6 +161,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
plugins.features.registerFeature(ACTIONS_FEATURE);
setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects);

this.eventLogService = plugins.eventLog;
plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS));
this.eventLogger = plugins.eventLog.getLogger({
event: { provider: EVENT_LOG_PROVIDER },
Expand Down Expand Up @@ -295,6 +297,11 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
});
};

this.eventLogService!.registerSavedObjectProvider('action', (request) => {
const client = getActionsClientWithRequest(request);
return async (type: string, id: string) => (await client).get({ id });
});

const getScopedSavedObjectsClientWithoutAccessToActions = (request: KibanaRequest) =>
core.savedObjects.getScopedClient(request);

Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/alerts/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export class AlertingPlugin {
private readonly alertsClientFactory: AlertsClientFactory;
private readonly telemetryLogger: Logger;
private readonly kibanaIndex: Promise<string>;
private eventLogService?: IEventLogService;
private eventLogger?: IEventLogger;

constructor(initializerContext: PluginInitializerContext) {
Expand Down Expand Up @@ -150,6 +151,7 @@ export class AlertingPlugin {

setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects);

this.eventLogService = plugins.eventLog;
plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS));
this.eventLogger = plugins.eventLog.getLogger({
event: { provider: EVENT_LOG_PROVIDER },
Expand Down Expand Up @@ -255,6 +257,11 @@ export class AlertingPlugin {
eventLogger: this.eventLogger!,
});

this.eventLogService!.registerSavedObjectProvider('alert', (request) => {
const client = getAlertsClientWithRequest(request);
return (type: string, id: string) => client.get({ id });
});

scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager);

return {
Expand Down
55 changes: 33 additions & 22 deletions x-pack/plugins/event_log/server/event_log_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@
import { KibanaRequest } from 'src/core/server';
import { EventLogClient } from './event_log_client';
import { contextMock } from './es/context.mock';
import { savedObjectsClientMock } from 'src/core/server/mocks';
import { merge } from 'lodash';
import moment from 'moment';

describe('EventLogStart', () => {
describe('findEventsBySavedObject', () => {
test('verifies that the user can access the specified saved object', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectGetter = jest.fn();

const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});

savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
Expand All @@ -31,19 +31,21 @@ describe('EventLogStart', () => {

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

expect(savedObjectsClient.get).toHaveBeenCalledWith('saved-object-type', 'saved-object-id');
expect(savedObjectGetter).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 savedObjectGetter = jest.fn();

const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});

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

expect(
eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id')
Expand All @@ -52,14 +54,16 @@ describe('EventLogStart', () => {

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

const savedObjectGetter = jest.fn();

const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});

savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
Expand Down Expand Up @@ -125,14 +129,16 @@ describe('EventLogStart', () => {

test('fetches all events in time frame that reference the saved object', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();

const savedObjectGetter = jest.fn();

const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});

savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
Expand Down Expand Up @@ -206,14 +212,16 @@ describe('EventLogStart', () => {

test('validates that the start date is valid', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();

const savedObjectGetter = jest.fn();

const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});

savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
Expand All @@ -236,14 +244,16 @@ describe('EventLogStart', () => {

test('validates that the end date is valid', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();

const savedObjectGetter = jest.fn();

const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});

savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
Expand Down Expand Up @@ -297,7 +307,8 @@ function fakeEvent(overrides = {}) {
}

function FakeRequest(): KibanaRequest {
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectGetter = jest.fn();

return ({
headers: {},
getBasePath: () => '',
Expand All @@ -311,6 +322,6 @@ function FakeRequest(): KibanaRequest {
url: '/',
},
},
getSavedObjectsClient: () => savedObjectsClient,
getSavedObjectsClient: () => savedObjectGetter,
} as unknown) as KibanaRequest;
}
18 changes: 7 additions & 11 deletions x-pack/plugins/event_log/server/event_log_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

import { Observable } from 'rxjs';
import { schema, TypeOf } from '@kbn/config-schema';
import { LegacyClusterClient, SavedObjectsClientContract, KibanaRequest } from 'src/core/server';
import { LegacyClusterClient, KibanaRequest } from 'src/core/server';
import { SpacesServiceSetup } from '../../spaces/server';

import { EsContext } from './es';
import { IEventLogClient } from './types';
import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter';
import { SavedObjectGetter } from './saved_object_provider_registry';
export type PluginClusterClient = Pick<LegacyClusterClient, 'callAsInternalUser' | 'asScoped'>;
export type AdminClusterClient$ = Observable<PluginClusterClient>;

Expand Down Expand Up @@ -58,26 +59,21 @@ export type FindOptionsType = Pick<

interface EventLogServiceCtorParams {
esContext: EsContext;
savedObjectsClient: SavedObjectsClientContract;
savedObjectGetter: SavedObjectGetter;
spacesService?: SpacesServiceSetup;
request: KibanaRequest;
}

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

constructor({
esContext,
savedObjectsClient,
spacesService,
request,
}: EventLogServiceCtorParams) {
constructor({ esContext, savedObjectGetter, spacesService, request }: EventLogServiceCtorParams) {
this.esContext = esContext;
this.savedObjectsClient = savedObjectsClient;
this.savedObjectGetter = savedObjectGetter;
this.spacesService = spacesService;
this.request = request;
}
Expand All @@ -93,7 +89,7 @@ export class EventLogClient implements IEventLogClient {
const namespace = space && this.spacesService?.spaceIdToNamespace(space.id);

// verify the user has the required permissions to view this saved object
await this.savedObjectsClient.get(type, id);
await this.savedObjectGetter(type, id);

return await this.esContext.esAdapter.queryEventsBySavedObject(
this.esContext.esNames.alias,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/event_log/server/event_log_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const createEventLogServiceMock = () => {
registerProviderActions: jest.fn(),
isProviderActionRegistered: jest.fn(),
getProviderActions: jest.fn(),
registerSavedObjectProvider: jest.fn(),
getLogger: jest.fn().mockReturnValue(eventLoggerMock.create()),
};
return mock;
Expand Down
25 changes: 25 additions & 0 deletions x-pack/plugins/event_log/server/event_log_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import { IEventLogConfig } from './types';
import { EventLogService } from './event_log_service';
import { contextMock } from './es/context.mock';
import { loggingSystemMock } from 'src/core/server/mocks';
import { savedObjectProviderRegistryMock } from './saved_object_provider_registry.mock';

const loggingService = loggingSystemMock.create();
const systemLogger = loggingService.get();
const savedObjectProviderRegistry = savedObjectProviderRegistryMock.create();

describe('EventLogService', () => {
const esContext = contextMock.create();
Expand All @@ -21,6 +23,7 @@ describe('EventLogService', () => {
esContext,
systemLogger,
kibanaUUID: '42',
savedObjectProviderRegistry,
config: {
enabled,
logEntries,
Expand Down Expand Up @@ -65,6 +68,7 @@ describe('EventLogService', () => {
esContext,
systemLogger,
kibanaUUID: '42',
savedObjectProviderRegistry,
config: {
enabled: true,
logEntries: true,
Expand Down Expand Up @@ -102,6 +106,7 @@ describe('EventLogService', () => {
esContext,
systemLogger,
kibanaUUID: '42',
savedObjectProviderRegistry,
config: {
enabled: true,
logEntries: true,
Expand All @@ -112,4 +117,24 @@ describe('EventLogService', () => {
const eventLogger = service.getLogger({});
expect(eventLogger).toBeTruthy();
});

describe('registerSavedObjectProvider', () => {
test('register SavedObject Providers in the registry', () => {
const params = {
esContext,
systemLogger,
kibanaUUID: '42',
savedObjectProviderRegistry,
config: {
enabled: true,
logEntries: true,
indexEntries: true,
},
};
const service = new EventLogService(params);
const provider = jest.fn();
service.registerSavedObjectProvider('myType', provider);
expect(savedObjectProviderRegistry.registerProvider).toHaveBeenCalledWith('myType', provider);
});
});
});
16 changes: 15 additions & 1 deletion x-pack/plugins/event_log/server/event_log_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Plugin } from './plugin';
import { EsContext } from './es';
import { IEvent, IEventLogger, IEventLogService, IEventLogConfig } from './types';
import { EventLogger } from './event_logger';
import { SavedObjectProvider, SavedObjectProviderRegistry } from './saved_object_provider_registry';
export type PluginClusterClient = Pick<LegacyClusterClient, 'callAsInternalUser' | 'asScoped'>;
export type AdminClusterClient$ = Observable<PluginClusterClient>;

Expand All @@ -21,6 +22,7 @@ interface EventLogServiceCtorParams {
esContext: EsContext;
kibanaUUID: string;
systemLogger: SystemLogger;
savedObjectProviderRegistry: SavedObjectProviderRegistry;
}

// note that clusterClient may be null, indicating we can't write to ES
Expand All @@ -29,15 +31,23 @@ export class EventLogService implements IEventLogService {
private esContext: EsContext;
private systemLogger: SystemLogger;
private registeredProviderActions: Map<string, Set<string>>;
private savedObjectProviderRegistry: SavedObjectProviderRegistry;

public readonly kibanaUUID: string;

constructor({ config, esContext, kibanaUUID, systemLogger }: EventLogServiceCtorParams) {
constructor({
config,
esContext,
kibanaUUID,
systemLogger,
savedObjectProviderRegistry,
}: EventLogServiceCtorParams) {
this.config = config;
this.esContext = esContext;
this.kibanaUUID = kibanaUUID;
this.systemLogger = systemLogger;
this.registeredProviderActions = new Map<string, Set<string>>();
this.savedObjectProviderRegistry = savedObjectProviderRegistry;
}

public isEnabled(): boolean {
Expand Down Expand Up @@ -77,6 +87,10 @@ export class EventLogService implements IEventLogService {
return new Map(this.registeredProviderActions.entries());
}

registerSavedObjectProvider(type: string, provider: SavedObjectProvider) {
return this.savedObjectProviderRegistry.registerProvider(type, provider);
}

getLogger(initialProperties: IEvent): IEventLogger {
return new EventLogger({
esContext: this.esContext,
Expand Down
Loading

0 comments on commit bfae087

Please sign in to comment.