mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* Mark incoming plugin members as readonly These cannot and should not be modifiable. * Use env var instead of EnvironmentMode * There doesn't appear to be an EnvMode in the new platform * We're only using envMode to check whether we're in production * We're already using process.env.NODE_ENV elsewhere We can revisit this, but for now I'm simplifying things under this assumption. * Pass our setup context to the compose function We're going to retrieve our router instance from this, for now. * Remove unused static files route I spent a few minutes trying to do this in the new platform, only to realize that this was cargo culted from another plugin's structure and never used. * WIP: convert main GraphQL endpoints to New Platform Splits the existing dual-method route into separate GET/POST routes, while converting it to the NP routing syntax TODO: * Full route schema declarations * Address context being moved off of the response object and into its own object; callWithRequest is currently broken for this reason. * Remove unnecesary Request type While the defaultIndex patterns can be retrieved on the request itself, that requires this special case of our FrameworkRequest. In my smoke testing, the incoming `indices` argument was never different from the one present on the request payload. Xavier had mentioned that these might be redundant and a relic of some quick prototyping, so I'm going to simplify this logic and delete that type under this assumption. * Retrieve Elasticsearch client from RequestHandlerContext In order to minimize the amount of noise on this refactor, I'm adding the RequestHandlerContext to the existing FrameworkRequest object that we already pass around. This also removes some adapter methods that were cribbed from infra but have since become unused. There are likely more. * Use uiSettings client from RequestHandlerContext Pulls from the new platform instead of from request.server. * Remove unused properties from RequestFacade One of these was obviated by the refactor to NP routing; the other may never have been necessary. * Remove unused interface This is a relic that is no longer used in the codebase. * Make error response code dynamic * Handle GraphQL errors Refactors to use new platform's responses instead of Boom. Unless we intentionally do not want isGraphQLError error headers, I saw no reason for the latter two branches of this method (and merged them). * Fix graphiQL route We needed to loosen the restriction on our main POST graphQL route, as the requests coming from graphiQL do not match our normal format. * Clean up logging * Remove unused var injection functionality I could not find a case where we were using these vars within the siem app. * Fix typo on config fetching * Migrate to NP IndexPatterns service * Removes unused extra parameter on callWithRequest * I think this was a relic from the infra code * Clean up typings of callWithRequest * GenericParams is, ironically, not generic enough to handle all ES client calls. Instead we type it as Record<string, any> but ensure that our function adheres to the APICaller interface. * Use savedObjects client in request context These resolvers already receive a request containing the NP context, so we can retrieve our client directly from that, now. * Rename dependencies -> plugins to match kibana.json * Remove unnecessary type annotation The type of callCluster is already checked due to being passed to the IndexPatternsFetcher constructor. * Add siem plugin to new platform For now this just generates a config observable with some defaults; everything still lives in the legacy plugin. * WIP: flattening out plugin initialization Rather than pass our legacy API around everywhere, let's be explicit about who needs what, and start flattening things out so that we can move the legacy-independent stuff over. * Pass our plugin context to initServerWithKibana We can get the NP equivalent of `pkg.version` from context.env.packageInfo.version, so let's do that and remove a usage of config(). * Simplify siem configuration As far as I can tell, the only siem config that we're using is `xpack.siem.enabled`. The `query` was a holdover from infra, and if we're using the `sources` queries at all, it's only with the default values. Since our config is not typed, trying to add `sources` config only results in runtime errors. This removes the KibanaConfigurationAdapter entirely, and instead passes what is effectively { sources: {} } to the SourcesConfigurationAdapter. * Run all legacy-free setup through our plugin Once this is vetted, we should be able to move the entire tree under the plugin into the new platform plugin. We can inline the compose and init_server calls into the plugin once things are vetted and stable; for now leaving them there cuts down on the diff. * Temporarily ignore our unused config declaration * Fix detection engine route tests While we're passing a properly bound route function in the app, the tests' interfaces needed to be updated. Adds a helper method for retrieving a bound route function from a Server object. * Add some rudimentary schema validation to our graphQL endpoints * Remove defunct server.config fn The last remaining usage of this config was removed in #51985. * Group our dev endpoints together The graphiQL endpoint is the only thing that currently uses the GET endpoint; everything else that talks to graphQL uses POST. For that reason, I'm putting them in the same scope (along with annotating here) to make that a bit clearer. * Determine environment from plugin context The kibana platform did and does provide this interface to check with environment we're running in. * Migrate xpack_main to NP features service * Fix some issues missed in the previous merge DE added some dependencies on both the server and request objects. Most have NP equivalents and can be converted, but for now let's just add them back to the Facades and convert in another PR. Also changes one function to pull plugins from the server object, rather than the server object living on the request (as this is how similar functions are structured right now). * Fix type resulting from bad merge resolution * Fix type error due to incorrect usage of Hapi.Request Pull elasticsearch service off our legacy server object, rather than indirectly off the request object. Still legacy, but it's one less step for later.
This commit is contained in:
parent
3c27237e59
commit
e7e9d019ec
37 changed files with 415 additions and 667 deletions
|
@ -29,6 +29,7 @@ import {
|
|||
DEFAULT_SIGNALS_INDEX_KEY,
|
||||
} from './common/constants';
|
||||
import { defaultIndexPattern } from './default_index_pattern';
|
||||
import { initServerWithKibana } from './server/kibana.index';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const siem = (kibana: any) => {
|
||||
|
@ -136,43 +137,24 @@ export const siem = (kibana: any) => {
|
|||
mappings: savedObjectMappings,
|
||||
},
|
||||
init(server: Server) {
|
||||
const {
|
||||
config,
|
||||
getInjectedUiAppVars,
|
||||
indexPatternsServiceFactory,
|
||||
injectUiAppVars,
|
||||
newPlatform,
|
||||
plugins,
|
||||
route,
|
||||
savedObjects,
|
||||
} = server;
|
||||
|
||||
const {
|
||||
env,
|
||||
coreContext: { logger },
|
||||
setup,
|
||||
} = newPlatform;
|
||||
const initializerContext = { logger, env };
|
||||
const { config, newPlatform, plugins, route } = server;
|
||||
const { coreContext, env, setup } = newPlatform;
|
||||
const initializerContext = { ...coreContext, env } as PluginInitializerContext;
|
||||
|
||||
const serverFacade = {
|
||||
config,
|
||||
getInjectedUiAppVars,
|
||||
indexPatternsServiceFactory,
|
||||
injectUiAppVars,
|
||||
plugins: {
|
||||
alerting: plugins.alerting,
|
||||
xpack_main: plugins.xpack_main,
|
||||
elasticsearch: plugins.elasticsearch,
|
||||
spaces: plugins.spaces,
|
||||
},
|
||||
route: route.bind(server),
|
||||
savedObjects,
|
||||
};
|
||||
|
||||
plugin(initializerContext as PluginInitializerContext).setup(
|
||||
setup.core,
|
||||
setup.plugins,
|
||||
serverFacade
|
||||
);
|
||||
// @ts-ignore-next-line: setup.plugins is too loosely typed
|
||||
plugin(initializerContext).setup(setup.core, setup.plugins);
|
||||
|
||||
initServerWithKibana(initializerContext, serverFacade);
|
||||
},
|
||||
config(Joi: Root) {
|
||||
return Joi.object()
|
||||
|
|
|
@ -55,15 +55,3 @@ export const schemas = [
|
|||
uncommonProcessesSchema,
|
||||
whoAmISchema,
|
||||
];
|
||||
|
||||
// The types from graphql-tools/src/mock.ts 'any' based. I add slightly
|
||||
// stricter types here, but these should go away when graphql-tools using something
|
||||
// other than "any" in the future for its types.
|
||||
// https://github.com/apollographql/graphql-tools/blob/master/src/mock.ts#L406
|
||||
export interface SiemContext {
|
||||
req: {
|
||||
payload: {
|
||||
operationName: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import { AppResolverOf, ChildResolverOf } from '../../lib/framework';
|
|||
import { IndexFields } from '../../lib/index_fields';
|
||||
import { SourceStatus } from '../../lib/source_status';
|
||||
import { QuerySourceResolver } from '../sources/resolvers';
|
||||
import { FrameworkFieldsRequest } from '../../lib/index_fields/types';
|
||||
|
||||
export type SourceStatusIndicesExistResolver = ChildResolverOf<
|
||||
AppResolverOf<SourceStatusResolvers.IndicesExistResolver>,
|
||||
|
@ -47,7 +46,7 @@ export const createSourceStatusResolvers = (libs: {
|
|||
) {
|
||||
return [];
|
||||
}
|
||||
return libs.fields.getFields(req as FrameworkFieldsRequest, args.defaultIndex);
|
||||
return libs.fields.getFields(req, args.defaultIndex);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,16 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Logger, EnvironmentMode } from 'src/core/server';
|
||||
import { initServer } from './init_server';
|
||||
import { compose } from './lib/compose/kibana';
|
||||
import {
|
||||
noteSavedObjectType,
|
||||
pinnedEventSavedObjectType,
|
||||
timelineSavedObjectType,
|
||||
} from './saved_objects';
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
|
||||
import { rulesAlertType } from './lib/detection_engine/alerts/rules_alert_type';
|
||||
import { isAlertExecutor } from './lib/detection_engine/alerts/types';
|
||||
|
@ -30,74 +21,33 @@ import { deleteIndexRoute } from './lib/detection_engine/routes/index/delete_ind
|
|||
|
||||
const APP_ID = 'siem';
|
||||
|
||||
export const initServerWithKibana = (
|
||||
kbnServer: ServerFacade,
|
||||
logger: Logger,
|
||||
mode: EnvironmentMode
|
||||
) => {
|
||||
if (kbnServer.plugins.alerting != null) {
|
||||
const version = kbnServer.config().get<string>('pkg.version');
|
||||
export const initServerWithKibana = (context: PluginInitializerContext, __legacy: ServerFacade) => {
|
||||
const logger = context.logger.get('plugins', APP_ID);
|
||||
const version = context.env.packageInfo.version;
|
||||
|
||||
if (__legacy.plugins.alerting != null) {
|
||||
const type = rulesAlertType({ logger, version });
|
||||
if (isAlertExecutor(type)) {
|
||||
kbnServer.plugins.alerting.setup.registerType(type);
|
||||
__legacy.plugins.alerting.setup.registerType(type);
|
||||
}
|
||||
}
|
||||
kbnServer.injectUiAppVars('siem', async () => kbnServer.getInjectedUiAppVars('kibana'));
|
||||
|
||||
const libs = compose(kbnServer, mode);
|
||||
initServer(libs);
|
||||
|
||||
// Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules
|
||||
// All REST rule creation, deletion, updating, etc...
|
||||
createRulesRoute(kbnServer);
|
||||
readRulesRoute(kbnServer);
|
||||
updateRulesRoute(kbnServer);
|
||||
deleteRulesRoute(kbnServer);
|
||||
findRulesRoute(kbnServer);
|
||||
createRulesRoute(__legacy);
|
||||
readRulesRoute(__legacy);
|
||||
updateRulesRoute(__legacy);
|
||||
deleteRulesRoute(__legacy);
|
||||
findRulesRoute(__legacy);
|
||||
|
||||
// Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals
|
||||
// POST /api/detection_engine/signals/status
|
||||
// Example usage can be found in siem/server/lib/detection_engine/scripts/signals
|
||||
setSignalsStatusRoute(kbnServer);
|
||||
setSignalsStatusRoute(__legacy);
|
||||
|
||||
// Detection Engine index routes that have the REST endpoints of /api/detection_engine/index
|
||||
// All REST index creation, policy management for spaces
|
||||
createIndexRoute(kbnServer);
|
||||
readIndexRoute(kbnServer);
|
||||
deleteIndexRoute(kbnServer);
|
||||
|
||||
const xpackMainPlugin = kbnServer.plugins.xpack_main;
|
||||
xpackMainPlugin.registerFeature({
|
||||
id: APP_ID,
|
||||
name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', {
|
||||
defaultMessage: 'SIEM',
|
||||
}),
|
||||
icon: 'securityAnalyticsApp',
|
||||
navLinkId: 'siem',
|
||||
app: ['siem', 'kibana'],
|
||||
catalogue: ['siem'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['siem'],
|
||||
savedObject: {
|
||||
all: [noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType],
|
||||
read: ['config'],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
read: {
|
||||
api: ['siem'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [
|
||||
'config',
|
||||
noteSavedObjectType,
|
||||
pinnedEventSavedObjectType,
|
||||
timelineSavedObjectType,
|
||||
],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
},
|
||||
});
|
||||
createIndexRoute(__legacy);
|
||||
readIndexRoute(__legacy);
|
||||
deleteIndexRoute(__legacy);
|
||||
};
|
||||
|
|
|
@ -4,13 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EnvironmentMode } from 'src/core/server';
|
||||
import { ServerFacade } from '../../types';
|
||||
import { CoreSetup, PluginInitializerContext } from 'src/core/server';
|
||||
import { Anomalies } from '../anomalies';
|
||||
import { ElasticsearchAnomaliesAdapter } from '../anomalies/elasticsearch_adapter';
|
||||
import { Authentications } from '../authentications';
|
||||
import { ElasticsearchAuthenticationAdapter } from '../authentications/elasticsearch_adapter';
|
||||
import { KibanaConfigurationAdapter } from '../configuration/kibana_configuration_adapter';
|
||||
import { ElasticsearchEventsAdapter, Events } from '../events';
|
||||
import { KibanaBackendFrameworkAdapter } from '../framework/kibana_framework_adapter';
|
||||
import { ElasticsearchHostsAdapter, Hosts } from '../hosts';
|
||||
|
@ -28,21 +26,20 @@ import { Overview } from '../overview';
|
|||
import { ElasticsearchOverviewAdapter } from '../overview/elasticsearch_adapter';
|
||||
import { ElasticsearchSourceStatusAdapter, SourceStatus } from '../source_status';
|
||||
import { ConfigurationSourcesAdapter, Sources } from '../sources';
|
||||
import { AppBackendLibs, AppDomainLibs, Configuration } from '../types';
|
||||
import { AppBackendLibs, AppDomainLibs } from '../types';
|
||||
import { ElasticsearchUncommonProcessesAdapter, UncommonProcesses } from '../uncommon_processes';
|
||||
import { Note } from '../note/saved_object';
|
||||
import { PinnedEvent } from '../pinned_event/saved_object';
|
||||
import { Timeline } from '../timeline/saved_object';
|
||||
|
||||
export function compose(server: ServerFacade, mode: EnvironmentMode): AppBackendLibs {
|
||||
const configuration = new KibanaConfigurationAdapter<Configuration>(server);
|
||||
const framework = new KibanaBackendFrameworkAdapter(server, mode);
|
||||
const sources = new Sources(new ConfigurationSourcesAdapter(configuration));
|
||||
export function compose(core: CoreSetup, env: PluginInitializerContext['env']): AppBackendLibs {
|
||||
const framework = new KibanaBackendFrameworkAdapter(core, env);
|
||||
const sources = new Sources(new ConfigurationSourcesAdapter());
|
||||
const sourceStatus = new SourceStatus(new ElasticsearchSourceStatusAdapter(framework));
|
||||
|
||||
const timeline = new Timeline({ savedObjects: framework.getSavedObjectsService() });
|
||||
const note = new Note({ savedObjects: framework.getSavedObjectsService() });
|
||||
const pinnedEvent = new PinnedEvent({ savedObjects: framework.getSavedObjectsService() });
|
||||
const timeline = new Timeline();
|
||||
const note = new Note();
|
||||
const pinnedEvent = new PinnedEvent();
|
||||
|
||||
const domainLibs: AppDomainLibs = {
|
||||
anomalies: new Anomalies(new ElasticsearchAnomaliesAdapter(framework)),
|
||||
|
@ -60,7 +57,6 @@ export function compose(server: ServerFacade, mode: EnvironmentMode): AppBackend
|
|||
};
|
||||
|
||||
const libs: AppBackendLibs = {
|
||||
configuration,
|
||||
framework,
|
||||
sourceStatus,
|
||||
sources,
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* 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 { KibanaConfigurationAdapter } from './kibana_configuration_adapter';
|
||||
|
||||
describe('the KibanaConfigurationAdapter', () => {
|
||||
test('queries the xpack.siem configuration of the server', async () => {
|
||||
const mockConfig = {
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
const configurationAdapter = new KibanaConfigurationAdapter({
|
||||
config: () => mockConfig,
|
||||
});
|
||||
|
||||
await configurationAdapter.get();
|
||||
|
||||
expect(mockConfig.get).toBeCalledWith('xpack.siem');
|
||||
});
|
||||
|
||||
test('applies the query defaults', async () => {
|
||||
const configurationAdapter = new KibanaConfigurationAdapter({
|
||||
config: () => ({
|
||||
get: () => ({}),
|
||||
}),
|
||||
});
|
||||
|
||||
const configuration = await configurationAdapter.get();
|
||||
|
||||
expect(configuration).toMatchObject({
|
||||
query: {
|
||||
partitionSize: expect.any(Number),
|
||||
partitionFactor: expect.any(Number),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* 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 Joi from 'joi';
|
||||
|
||||
import { ConfigurationAdapter } from './adapter_types';
|
||||
|
||||
export class KibanaConfigurationAdapter<Configuration>
|
||||
implements ConfigurationAdapter<Configuration> {
|
||||
private readonly server: ServerWithConfig;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(server: any) {
|
||||
if (!isServerWithConfig(server)) {
|
||||
throw new Error('Failed to find configuration on server.');
|
||||
}
|
||||
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public async get() {
|
||||
const config = this.server.config();
|
||||
|
||||
if (!isKibanaConfiguration(config)) {
|
||||
throw new Error('Failed to access configuration of server.');
|
||||
}
|
||||
|
||||
const configuration = config.get('xpack.siem') || {};
|
||||
const configurationWithDefaults = {
|
||||
enabled: true,
|
||||
query: {
|
||||
partitionSize: 75,
|
||||
partitionFactor: 1.2,
|
||||
...(configuration.query || {}),
|
||||
},
|
||||
sources: {},
|
||||
...configuration,
|
||||
} as Configuration;
|
||||
|
||||
// we assume this to be the configuration because Kibana would have already validated it
|
||||
return configurationWithDefaults;
|
||||
}
|
||||
}
|
||||
|
||||
interface ServerWithConfig {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
config(): any;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function isServerWithConfig(maybeServer: any): maybeServer is ServerWithConfig {
|
||||
return (
|
||||
Joi.validate(
|
||||
maybeServer,
|
||||
Joi.object({
|
||||
config: Joi.func().required(),
|
||||
}).unknown()
|
||||
).error === null
|
||||
);
|
||||
}
|
||||
|
||||
interface KibanaConfiguration {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
get(key: string): any;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function isKibanaConfiguration(maybeConfiguration: any): maybeConfiguration is KibanaConfiguration {
|
||||
return (
|
||||
Joi.validate(
|
||||
maybeConfiguration,
|
||||
Joi.object({
|
||||
get: Joi.func().required(),
|
||||
}).unknown()
|
||||
).error === null
|
||||
);
|
||||
}
|
|
@ -67,7 +67,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute =
|
|||
|
||||
try {
|
||||
const finalIndex = outputIndex != null ? outputIndex : getIndex(request, server);
|
||||
const callWithRequest = callWithRequestFactory(request);
|
||||
const callWithRequest = callWithRequestFactory(request, server);
|
||||
const indexExists = await getIndexExists(callWithRequest, finalIndex);
|
||||
if (!indexExists) {
|
||||
return new Boom(
|
||||
|
@ -118,6 +118,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute =
|
|||
};
|
||||
};
|
||||
|
||||
export const createRulesRoute = (server: ServerFacade) => {
|
||||
export const createRulesRoute = (server: ServerFacade): void => {
|
||||
server.route(createCreateRulesRoute(server));
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@ export const createCreateIndexRoute = (server: ServerFacade): Hapi.ServerRoute =
|
|||
async handler(request: RequestFacade) {
|
||||
try {
|
||||
const index = getIndex(request, server);
|
||||
const callWithRequest = callWithRequestFactory(request);
|
||||
const callWithRequest = callWithRequestFactory(request, server);
|
||||
const indexExists = await getIndexExists(callWithRequest, index);
|
||||
if (indexExists) {
|
||||
return new Boom(`index: "${index}" already exists`, { statusCode: 409 });
|
||||
|
|
|
@ -42,7 +42,7 @@ export const createDeleteIndexRoute = (server: ServerFacade): Hapi.ServerRoute =
|
|||
async handler(request: RequestFacade) {
|
||||
try {
|
||||
const index = getIndex(request, server);
|
||||
const callWithRequest = callWithRequestFactory(request);
|
||||
const callWithRequest = callWithRequestFactory(request, server);
|
||||
const indexExists = await getIndexExists(callWithRequest, index);
|
||||
if (!indexExists) {
|
||||
return new Boom(`index: "${index}" does not exist`, { statusCode: 404 });
|
||||
|
|
|
@ -27,7 +27,7 @@ export const createReadIndexRoute = (server: ServerFacade): Hapi.ServerRoute =>
|
|||
async handler(request: RequestFacade, headers) {
|
||||
try {
|
||||
const index = getIndex(request, server);
|
||||
const callWithRequest = callWithRequestFactory(request);
|
||||
const callWithRequest = callWithRequestFactory(request, server);
|
||||
const indexExists = await getIndexExists(callWithRequest, index);
|
||||
if (indexExists) {
|
||||
// head request is used for if you want to get if the index exists
|
||||
|
|
|
@ -27,7 +27,7 @@ export const setSignalsStatusRouteDef = (server: ServerFacade): Hapi.ServerRoute
|
|||
async handler(request: SignalsRequest, headers) {
|
||||
const { signal_ids: signalIds, query, status } = request.payload;
|
||||
const index = getIndex(request, server);
|
||||
const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data');
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
|
||||
let queryObject;
|
||||
if (signalIds) {
|
||||
queryObject = { ids: { values: signalIds } };
|
||||
|
|
|
@ -96,8 +96,8 @@ export const getIndex = (request: RequestFacade, server: ServerFacade): string =
|
|||
return `${signalsIndex}-${spaceId}`;
|
||||
};
|
||||
|
||||
export const callWithRequestFactory = (request: RequestFacade) => {
|
||||
const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data');
|
||||
export const callWithRequestFactory = (request: RequestFacade, server: ServerFacade) => {
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
|
||||
return <T, U>(endpoint: string, params: T, options?: U) => {
|
||||
return callWithRequest(request, endpoint, params, options);
|
||||
};
|
||||
|
|
|
@ -521,10 +521,8 @@ describe('events elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
|
|
|
@ -4,14 +4,19 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { GenericParams } from 'elasticsearch';
|
||||
import * as GraphiQL from 'apollo-server-module-graphiql';
|
||||
import Boom from 'boom';
|
||||
import { ResponseToolkit } from 'hapi';
|
||||
import { EnvironmentMode } from 'kibana/public';
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
import { runHttpQuery } from 'apollo-server-core';
|
||||
import { ServerFacade, RequestFacade } from '../../types';
|
||||
import { schema as configSchema } from '@kbn/config-schema';
|
||||
import {
|
||||
CoreSetup,
|
||||
IRouter,
|
||||
KibanaResponseFactory,
|
||||
RequestHandlerContext,
|
||||
PluginInitializerContext,
|
||||
} from 'src/core/server';
|
||||
import { IndexPatternsFetcher } from '../../../../../../../src/plugins/data/server';
|
||||
import { RequestFacade } from '../../types';
|
||||
|
||||
import {
|
||||
FrameworkAdapter,
|
||||
|
@ -21,125 +26,119 @@ import {
|
|||
WrappableRequest,
|
||||
} from './types';
|
||||
|
||||
interface CallWithRequestParams extends GenericParams {
|
||||
max_concurrent_shard_requests?: number;
|
||||
}
|
||||
|
||||
export class KibanaBackendFrameworkAdapter implements FrameworkAdapter {
|
||||
public version: string;
|
||||
public envMode: EnvironmentMode;
|
||||
private isProductionMode: boolean;
|
||||
private router: IRouter;
|
||||
|
||||
constructor(private server: ServerFacade, mode: EnvironmentMode) {
|
||||
this.version = server.config().get('pkg.version');
|
||||
this.envMode = mode;
|
||||
constructor(core: CoreSetup, env: PluginInitializerContext['env']) {
|
||||
this.version = env.packageInfo.version;
|
||||
this.isProductionMode = env.mode.prod;
|
||||
this.router = core.http.createRouter();
|
||||
}
|
||||
|
||||
public async callWithRequest(
|
||||
req: FrameworkRequest,
|
||||
endpoint: string,
|
||||
params: CallWithRequestParams,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
...rest: any[]
|
||||
params: Record<string, any>
|
||||
) {
|
||||
const internalRequest = req[internalFrameworkRequest];
|
||||
const { elasticsearch } = internalRequest.server.plugins;
|
||||
const { callWithRequest } = elasticsearch.getCluster('data');
|
||||
const includeFrozen = await internalRequest.getUiSettingsService().get('search:includeFrozen');
|
||||
const { elasticsearch, uiSettings } = req.context.core;
|
||||
const includeFrozen = await uiSettings.client.get('search:includeFrozen');
|
||||
const maxConcurrentShardRequests =
|
||||
endpoint === 'msearch'
|
||||
? await internalRequest.getUiSettingsService().get('courier:maxConcurrentShardRequests')
|
||||
? await uiSettings.client.get('courier:maxConcurrentShardRequests')
|
||||
: 0;
|
||||
const fields = await callWithRequest(
|
||||
internalRequest,
|
||||
endpoint,
|
||||
{
|
||||
...params,
|
||||
ignore_throttled: !includeFrozen,
|
||||
...(maxConcurrentShardRequests > 0
|
||||
? { max_concurrent_shard_requests: maxConcurrentShardRequests }
|
||||
: {}),
|
||||
},
|
||||
...rest
|
||||
);
|
||||
return fields;
|
||||
}
|
||||
|
||||
public exposeStaticDir(urlPath: string, dir: string): void {
|
||||
this.server.route({
|
||||
handler: {
|
||||
directory: {
|
||||
path: dir,
|
||||
},
|
||||
},
|
||||
method: 'GET',
|
||||
path: urlPath,
|
||||
return elasticsearch.dataClient.callAsCurrentUser(endpoint, {
|
||||
...params,
|
||||
ignore_throttled: !includeFrozen,
|
||||
...(maxConcurrentShardRequests > 0
|
||||
? { max_concurrent_shard_requests: maxConcurrentShardRequests }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void {
|
||||
this.server.route({
|
||||
options: {
|
||||
tags: ['access:siem'],
|
||||
},
|
||||
handler: async (request: RequestFacade, h: ResponseToolkit) => {
|
||||
try {
|
||||
const query =
|
||||
request.method === 'post'
|
||||
? (request.payload as Record<string, any>) // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
: (request.query as Record<string, any>); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
const gqlResponse = await runHttpQuery([request], {
|
||||
method: request.method.toUpperCase(),
|
||||
options: (req: RequestFacade) => ({
|
||||
context: { req: wrapRequest(req) },
|
||||
schema,
|
||||
}),
|
||||
|
||||
query,
|
||||
});
|
||||
|
||||
return h.response(gqlResponse).type('application/json');
|
||||
} catch (error) {
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
const queryError = Boom.boomify(error);
|
||||
|
||||
queryError.output.payload.message = error.message;
|
||||
|
||||
return queryError;
|
||||
}
|
||||
|
||||
if (error.isGraphQLError === true) {
|
||||
return h
|
||||
.response(error.message)
|
||||
.code(error.statusCode)
|
||||
.type('application/json');
|
||||
}
|
||||
|
||||
const genericError = new Boom(error.message, { statusCode: error.statusCode });
|
||||
|
||||
if (error.headers) {
|
||||
Object.keys(error.headers).forEach(header => {
|
||||
genericError.output.headers[header] = error.headers[header];
|
||||
});
|
||||
}
|
||||
|
||||
// Boom hides the error when status code is 500
|
||||
genericError.output.payload.message = error.message;
|
||||
|
||||
throw genericError;
|
||||
}
|
||||
},
|
||||
method: ['GET', 'POST'],
|
||||
path: routePath,
|
||||
vhost: undefined,
|
||||
});
|
||||
|
||||
if (!this.envMode.prod) {
|
||||
this.server.route({
|
||||
this.router.post(
|
||||
{
|
||||
path: routePath,
|
||||
validate: {
|
||||
body: configSchema.object({
|
||||
operationName: configSchema.string(),
|
||||
query: configSchema.string(),
|
||||
variables: configSchema.object({}, { allowUnknowns: true }),
|
||||
}),
|
||||
},
|
||||
options: {
|
||||
tags: ['access:siem'],
|
||||
},
|
||||
handler: async (request: RequestFacade, h: ResponseToolkit) => {
|
||||
},
|
||||
async (context, request, response) => {
|
||||
try {
|
||||
const gqlResponse = await runHttpQuery([request], {
|
||||
method: 'POST',
|
||||
options: (req: RequestFacade) => ({
|
||||
context: { req: wrapRequest(req, context) },
|
||||
schema,
|
||||
}),
|
||||
query: request.body,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: gqlResponse,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return this.handleError(error, response);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!this.isProductionMode) {
|
||||
this.router.get(
|
||||
{
|
||||
path: routePath,
|
||||
validate: { query: configSchema.object({}, { allowUnknowns: true }) },
|
||||
options: {
|
||||
tags: ['access:siem'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
try {
|
||||
const { query } = request;
|
||||
const gqlResponse = await runHttpQuery([request], {
|
||||
method: 'GET',
|
||||
options: (req: RequestFacade) => ({
|
||||
context: { req: wrapRequest(req, context) },
|
||||
schema,
|
||||
}),
|
||||
query,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: gqlResponse,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return this.handleError(error, response);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
{
|
||||
path: `${routePath}/graphiql`,
|
||||
validate: false,
|
||||
options: {
|
||||
tags: ['access:siem'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const graphiqlString = await GraphiQL.resolveGraphiQLString(
|
||||
request.query,
|
||||
{
|
||||
|
@ -149,42 +148,60 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter {
|
|||
request
|
||||
);
|
||||
|
||||
return h.response(graphiqlString).type('text/html');
|
||||
},
|
||||
method: 'GET',
|
||||
path: `${routePath}/graphiql`,
|
||||
});
|
||||
return response.ok({
|
||||
body: graphiqlString,
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public getIndexPatternsService(request: FrameworkRequest): FrameworkIndexPatternsService {
|
||||
return this.server.indexPatternsServiceFactory({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
callCluster: async (method: string, args: [GenericParams], ...rest: any[]) => {
|
||||
const fieldCaps = await this.callWithRequest(
|
||||
request,
|
||||
method,
|
||||
{ ...args, allowNoIndices: true } as GenericParams,
|
||||
...rest
|
||||
);
|
||||
return fieldCaps;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private handleError(error: any, response: KibanaResponseFactory) {
|
||||
if (error.name !== 'HttpQueryError') {
|
||||
return response.internalError({
|
||||
body: error.message,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return response.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: error.message,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
...error.headers,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public getSavedObjectsService() {
|
||||
return this.server.savedObjects;
|
||||
public getIndexPatternsService(request: FrameworkRequest): FrameworkIndexPatternsService {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callCluster = async (endpoint: string, params?: Record<string, any>) =>
|
||||
this.callWithRequest(request, endpoint, {
|
||||
...params,
|
||||
allowNoIndices: true,
|
||||
});
|
||||
|
||||
return new IndexPatternsFetcher(callCluster);
|
||||
}
|
||||
}
|
||||
|
||||
export function wrapRequest<InternalRequest extends WrappableRequest>(
|
||||
req: InternalRequest
|
||||
req: InternalRequest,
|
||||
context: RequestHandlerContext
|
||||
): FrameworkRequest<InternalRequest> {
|
||||
const { auth, params, payload, query } = req;
|
||||
|
||||
return {
|
||||
[internalFrameworkRequest]: req,
|
||||
auth,
|
||||
context,
|
||||
params,
|
||||
payload,
|
||||
query,
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
import { IndicesGetMappingParams } from 'elasticsearch';
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
import { RequestAuth } from 'hapi';
|
||||
import { Legacy } from 'kibana';
|
||||
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { ESQuery } from '../../../common/typed_json';
|
||||
import {
|
||||
PaginationInput,
|
||||
|
@ -25,7 +25,6 @@ export const internalFrameworkRequest = Symbol('internalFrameworkRequest');
|
|||
|
||||
export interface FrameworkAdapter {
|
||||
version: string;
|
||||
exposeStaticDir(urlPath: string, dir: string): void;
|
||||
registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void;
|
||||
callWithRequest<Hit = {}, Aggregation = undefined>(
|
||||
req: FrameworkRequest,
|
||||
|
@ -37,27 +36,17 @@ export interface FrameworkAdapter {
|
|||
method: 'msearch',
|
||||
options?: object
|
||||
): Promise<DatabaseMultiResponse<Hit, Aggregation>>;
|
||||
callWithRequest(
|
||||
req: FrameworkRequest,
|
||||
method: 'indices.existsAlias',
|
||||
options?: object
|
||||
): Promise<boolean>;
|
||||
callWithRequest(
|
||||
req: FrameworkRequest,
|
||||
method: 'indices.getMapping',
|
||||
options?: IndicesGetMappingParams // eslint-disable-line
|
||||
): Promise<MappingResponse>;
|
||||
callWithRequest(
|
||||
req: FrameworkRequest,
|
||||
method: 'indices.getAlias' | 'indices.get', // eslint-disable-line
|
||||
options?: object
|
||||
): Promise<DatabaseGetIndicesResponse>;
|
||||
getIndexPatternsService(req: FrameworkRequest): FrameworkIndexPatternsService;
|
||||
getSavedObjectsService(): Legacy.SavedObjectsService;
|
||||
}
|
||||
|
||||
export interface FrameworkRequest<InternalRequest extends WrappableRequest = RequestFacade> {
|
||||
[internalFrameworkRequest]: InternalRequest;
|
||||
context: RequestHandlerContext;
|
||||
payload: InternalRequest['payload'];
|
||||
params: InternalRequest['params'];
|
||||
query: InternalRequest['query'];
|
||||
|
@ -132,22 +121,6 @@ export interface FrameworkIndexPatternsService {
|
|||
}): Promise<FrameworkIndexFieldDescriptor[]>;
|
||||
}
|
||||
|
||||
interface Alias {
|
||||
settings: {
|
||||
index: {
|
||||
uuid: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface DatabaseGetIndicesResponse {
|
||||
[indexName: string]: {
|
||||
aliases: {
|
||||
[aliasName: string]: Alias;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface RequestBasicOptions {
|
||||
sourceConfiguration: SourceConfiguration;
|
||||
timerange: TimerangeInput;
|
||||
|
|
|
@ -161,10 +161,8 @@ describe('hosts elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest }));
|
||||
|
||||
|
@ -184,10 +182,8 @@ describe('hosts elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest }));
|
||||
|
||||
|
@ -207,10 +203,8 @@ describe('hosts elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest }));
|
||||
|
||||
|
|
|
@ -14,22 +14,20 @@ import {
|
|||
hasDocumentation,
|
||||
IndexAlias,
|
||||
} from '../../utils/beat_schema';
|
||||
import { FrameworkAdapter } from '../framework';
|
||||
import { FieldsAdapter, IndexFieldDescriptor, FrameworkFieldsRequest } from './types';
|
||||
import { FrameworkAdapter, FrameworkRequest } from '../framework';
|
||||
import { FieldsAdapter, IndexFieldDescriptor } from './types';
|
||||
|
||||
type IndexesAliasIndices = Record<string, string[]>;
|
||||
|
||||
export class ElasticsearchIndexFieldAdapter implements FieldsAdapter {
|
||||
constructor(private readonly framework: FrameworkAdapter) {}
|
||||
|
||||
public async getIndexFields(
|
||||
request: FrameworkFieldsRequest,
|
||||
indices: string[]
|
||||
): Promise<IndexField[]> {
|
||||
public async getIndexFields(request: FrameworkRequest, indices: string[]): Promise<IndexField[]> {
|
||||
const indexPatternsService = this.framework.getIndexPatternsService(request);
|
||||
const indexesAliasIndices: IndexesAliasIndices = indices.reduce(
|
||||
(accumulator: IndexesAliasIndices, indice: string) => {
|
||||
const key: string = getIndexAlias(request.payload.variables.defaultIndex, indice);
|
||||
const key = getIndexAlias(indices, indice);
|
||||
|
||||
if (get(key, accumulator)) {
|
||||
accumulator[key] = [...accumulator[key], indice];
|
||||
} else {
|
||||
|
|
|
@ -6,16 +6,14 @@
|
|||
|
||||
import { IndexField } from '../../graphql/types';
|
||||
|
||||
import { FieldsAdapter, FrameworkFieldsRequest } from './types';
|
||||
import { FieldsAdapter } from './types';
|
||||
import { FrameworkRequest } from '../framework';
|
||||
export { ElasticsearchIndexFieldAdapter } from './elasticsearch_adapter';
|
||||
|
||||
export class IndexFields {
|
||||
constructor(private readonly adapter: FieldsAdapter) {}
|
||||
|
||||
public async getFields(
|
||||
request: FrameworkFieldsRequest,
|
||||
defaultIndex: string[]
|
||||
): Promise<IndexField[]> {
|
||||
public async getFields(request: FrameworkRequest, defaultIndex: string[]): Promise<IndexField[]> {
|
||||
return this.adapter.getIndexFields(request, defaultIndex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,20 +6,9 @@
|
|||
|
||||
import { IndexField } from '../../graphql/types';
|
||||
import { FrameworkRequest } from '../framework';
|
||||
import { RequestFacade } from '../../types';
|
||||
|
||||
type IndexFieldsRequest = RequestFacade & {
|
||||
payload: {
|
||||
variables: {
|
||||
defaultIndex: string[];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type FrameworkFieldsRequest = FrameworkRequest<IndexFieldsRequest>;
|
||||
|
||||
export interface FieldsAdapter {
|
||||
getIndexFields(req: FrameworkFieldsRequest, indices: string[]): Promise<IndexField[]>;
|
||||
getIndexFields(req: FrameworkRequest, indices: string[]): Promise<IndexField[]>;
|
||||
}
|
||||
|
||||
export interface IndexFieldDescriptor {
|
||||
|
|
|
@ -55,10 +55,8 @@ describe('getKpiHosts', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
let EsKpiHosts: ElasticsearchKpiHostsAdapter;
|
||||
|
||||
|
@ -171,10 +169,8 @@ describe('getKpiHostDetails', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
let EsKpiHosts: ElasticsearchKpiHostsAdapter;
|
||||
|
||||
|
|
|
@ -50,10 +50,8 @@ describe('Network Kpi elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
|
||||
let EsKpiNetwork: ElasticsearchKpiNetworkAdapter;
|
||||
|
|
|
@ -37,9 +37,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () =
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
|
@ -65,10 +63,8 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () =
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
|
@ -107,9 +103,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () =
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
|
@ -140,10 +134,8 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () =
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
|
@ -165,9 +157,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () =
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import { failure } from 'io-ts/lib/PathReporter';
|
||||
import { RequestAuth } from 'hapi';
|
||||
import { Legacy } from 'kibana';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import uuid from 'uuid';
|
||||
|
||||
|
@ -15,7 +14,6 @@ import { SavedObjectsFindOptions } from 'src/core/server';
|
|||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { map, fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
import { Pick3 } from '../../../common/utility_types';
|
||||
import {
|
||||
PageInfoNote,
|
||||
ResponseNote,
|
||||
|
@ -31,20 +29,11 @@ import { pickSavedTimeline } from '../timeline/pick_saved_timeline';
|
|||
import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline';
|
||||
|
||||
export class Note {
|
||||
constructor(
|
||||
private readonly libs: {
|
||||
savedObjects: Pick<Legacy.SavedObjectsService, 'getScopedSavedObjectsClient'> &
|
||||
Pick3<Legacy.SavedObjectsService, 'SavedObjectsClient', 'errors', 'isConflictError'>;
|
||||
}
|
||||
) {}
|
||||
|
||||
public async deleteNote(request: FrameworkRequest, noteIds: string[]) {
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
|
||||
await Promise.all(
|
||||
noteIds.map(noteId =>
|
||||
this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.delete(noteSavedObjectType, noteId)
|
||||
)
|
||||
noteIds.map(noteId => savedObjectsClient.delete(noteSavedObjectType, noteId))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -55,11 +44,11 @@ export class Note {
|
|||
searchFields: ['timelineId'],
|
||||
};
|
||||
const notesToBeDeleted = await this.getAllSavedNote(request, options);
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
|
||||
await Promise.all(
|
||||
notesToBeDeleted.notes.map(note =>
|
||||
this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.delete(noteSavedObjectType, note.noteId)
|
||||
savedObjectsClient.delete(noteSavedObjectType, note.noteId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -119,17 +108,17 @@ export class Note {
|
|||
note: SavedNote
|
||||
): Promise<ResponseNote> {
|
||||
try {
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
|
||||
if (noteId == null) {
|
||||
const timelineVersionSavedObject =
|
||||
note.timelineId == null
|
||||
? await (async () => {
|
||||
const timelineResult = convertSavedObjectToSavedTimeline(
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.create(
|
||||
timelineSavedObjectType,
|
||||
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
|
||||
)
|
||||
await savedObjectsClient.create(
|
||||
timelineSavedObjectType,
|
||||
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
|
||||
)
|
||||
);
|
||||
note.timelineId = timelineResult.savedObjectId;
|
||||
return timelineResult.version;
|
||||
|
@ -141,12 +130,10 @@ export class Note {
|
|||
code: 200,
|
||||
message: 'success',
|
||||
note: convertSavedObjectToSavedNote(
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.create(
|
||||
noteSavedObjectType,
|
||||
pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null)
|
||||
),
|
||||
await savedObjectsClient.create(
|
||||
noteSavedObjectType,
|
||||
pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null)
|
||||
),
|
||||
timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined
|
||||
),
|
||||
};
|
||||
|
@ -157,16 +144,14 @@ export class Note {
|
|||
code: 200,
|
||||
message: 'success',
|
||||
note: convertSavedObjectToSavedNote(
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.update(
|
||||
noteSavedObjectType,
|
||||
noteId,
|
||||
pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null),
|
||||
{
|
||||
version: version || undefined,
|
||||
}
|
||||
)
|
||||
await savedObjectsClient.update(
|
||||
noteSavedObjectType,
|
||||
noteId,
|
||||
pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null),
|
||||
{
|
||||
version: version || undefined,
|
||||
}
|
||||
)
|
||||
),
|
||||
};
|
||||
} catch (err) {
|
||||
|
@ -189,20 +174,14 @@ export class Note {
|
|||
}
|
||||
|
||||
private async getSavedNote(request: FrameworkRequest, NoteId: string) {
|
||||
const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient(
|
||||
request[internalFrameworkRequest]
|
||||
);
|
||||
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
const savedObject = await savedObjectsClient.get(noteSavedObjectType, NoteId);
|
||||
|
||||
return convertSavedObjectToSavedNote(savedObject);
|
||||
}
|
||||
|
||||
private async getAllSavedNote(request: FrameworkRequest, options: SavedObjectsFindOptions) {
|
||||
const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient(
|
||||
request[internalFrameworkRequest]
|
||||
);
|
||||
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
const savedObjects = await savedObjectsClient.find(options);
|
||||
|
||||
return {
|
||||
|
|
|
@ -38,10 +38,8 @@ describe('Siem Overview elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
|
@ -74,10 +72,8 @@ describe('Siem Overview elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
|
@ -114,10 +110,8 @@ describe('Siem Overview elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
|
@ -155,10 +149,8 @@ describe('Siem Overview elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
jest.doMock('../framework', () => ({
|
||||
callWithRequest: mockCallWithRequest,
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import { failure } from 'io-ts/lib/PathReporter';
|
||||
import { RequestAuth } from 'hapi';
|
||||
import { Legacy } from 'kibana';
|
||||
import { getOr } from 'lodash/fp';
|
||||
|
||||
import { SavedObjectsFindOptions } from 'src/core/server';
|
||||
|
@ -14,7 +13,6 @@ import { SavedObjectsFindOptions } from 'src/core/server';
|
|||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { map, fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
import { Pick3 } from '../../../common/utility_types';
|
||||
import { FrameworkRequest, internalFrameworkRequest } from '../framework';
|
||||
import {
|
||||
PinnedEventSavedObject,
|
||||
|
@ -27,24 +25,18 @@ import { pickSavedTimeline } from '../timeline/pick_saved_timeline';
|
|||
import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline';
|
||||
|
||||
export class PinnedEvent {
|
||||
constructor(
|
||||
private readonly libs: {
|
||||
savedObjects: Pick<Legacy.SavedObjectsService, 'getScopedSavedObjectsClient'> &
|
||||
Pick3<Legacy.SavedObjectsService, 'SavedObjectsClient', 'errors', 'isConflictError'>;
|
||||
}
|
||||
) {}
|
||||
|
||||
public async deletePinnedEventOnTimeline(request: FrameworkRequest, pinnedEventIds: string[]) {
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
|
||||
await Promise.all(
|
||||
pinnedEventIds.map(pinnedEventId =>
|
||||
this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.delete(pinnedEventSavedObjectType, pinnedEventId)
|
||||
savedObjectsClient.delete(pinnedEventSavedObjectType, pinnedEventId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async deleteAllPinnedEventsOnTimeline(request: FrameworkRequest, timelineId: string) {
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
const options: SavedObjectsFindOptions = {
|
||||
type: pinnedEventSavedObjectType,
|
||||
search: timelineId,
|
||||
|
@ -53,9 +45,7 @@ export class PinnedEvent {
|
|||
const pinnedEventToBeDeleted = await this.getAllSavedPinnedEvents(request, options);
|
||||
await Promise.all(
|
||||
pinnedEventToBeDeleted.map(pinnedEvent =>
|
||||
this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.delete(pinnedEventSavedObjectType, pinnedEvent.pinnedEventId)
|
||||
savedObjectsClient.delete(pinnedEventSavedObjectType, pinnedEvent.pinnedEventId)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -103,18 +93,18 @@ export class PinnedEvent {
|
|||
eventId: string,
|
||||
timelineId: string | null
|
||||
): Promise<PinnedEventResponse | null> {
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
|
||||
try {
|
||||
if (pinnedEventId == null) {
|
||||
const timelineVersionSavedObject =
|
||||
timelineId == null
|
||||
? await (async () => {
|
||||
const timelineResult = convertSavedObjectToSavedTimeline(
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.create(
|
||||
timelineSavedObjectType,
|
||||
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
|
||||
)
|
||||
await savedObjectsClient.create(
|
||||
timelineSavedObjectType,
|
||||
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
|
||||
)
|
||||
);
|
||||
timelineId = timelineResult.savedObjectId; // eslint-disable-line no-param-reassign
|
||||
return timelineResult.version;
|
||||
|
@ -133,16 +123,14 @@ export class PinnedEvent {
|
|||
};
|
||||
// create Pinned Event on Timeline
|
||||
return convertSavedObjectToSavedPinnedEvent(
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.create(
|
||||
pinnedEventSavedObjectType,
|
||||
pickSavedPinnedEvent(
|
||||
pinnedEventId,
|
||||
savedPinnedEvent,
|
||||
request[internalFrameworkRequest].auth || null
|
||||
)
|
||||
),
|
||||
await savedObjectsClient.create(
|
||||
pinnedEventSavedObjectType,
|
||||
pickSavedPinnedEvent(
|
||||
pinnedEventId,
|
||||
savedPinnedEvent,
|
||||
request[internalFrameworkRequest].auth || null
|
||||
)
|
||||
),
|
||||
timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined
|
||||
);
|
||||
}
|
||||
|
@ -177,10 +165,7 @@ export class PinnedEvent {
|
|||
}
|
||||
|
||||
private async getSavedPinnedEvent(request: FrameworkRequest, pinnedEventId: string) {
|
||||
const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient(
|
||||
request[internalFrameworkRequest]
|
||||
);
|
||||
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
const savedObject = await savedObjectsClient.get(pinnedEventSavedObjectType, pinnedEventId);
|
||||
|
||||
return convertSavedObjectToSavedPinnedEvent(savedObject);
|
||||
|
@ -190,10 +175,7 @@ export class PinnedEvent {
|
|||
request: FrameworkRequest,
|
||||
options: SavedObjectsFindOptions
|
||||
) {
|
||||
const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient(
|
||||
request[internalFrameworkRequest]
|
||||
);
|
||||
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
const savedObjects = await savedObjectsClient.find(options);
|
||||
|
||||
return savedObjects.saved_objects.map(savedObject =>
|
||||
|
|
|
@ -4,38 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { DatabaseGetIndicesResponse, FrameworkAdapter, FrameworkRequest } from '../framework';
|
||||
|
||||
import { FrameworkAdapter, FrameworkRequest } from '../framework';
|
||||
import { SourceStatusAdapter } from './index';
|
||||
|
||||
export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter {
|
||||
constructor(private readonly framework: FrameworkAdapter) {}
|
||||
public async getIndexNames(request: FrameworkRequest, aliasName: string | string[]) {
|
||||
const indexMaps = await Promise.all([
|
||||
this.framework
|
||||
.callWithRequest(request, 'indices.getAlias', {
|
||||
name: aliasName,
|
||||
filterPath: '*.settings.index.uuid', // to keep the response size as small as possible
|
||||
})
|
||||
.catch(withDefaultIfNotFound<DatabaseGetIndicesResponse>({})),
|
||||
this.framework
|
||||
.callWithRequest(request, 'indices.get', {
|
||||
index: aliasName,
|
||||
filterPath: '*.settings.index.uuid', // to keep the response size as small as possible
|
||||
})
|
||||
.catch(withDefaultIfNotFound<DatabaseGetIndicesResponse>({})),
|
||||
]);
|
||||
return indexMaps.reduce(
|
||||
(indexNames, indexMap) => [...indexNames, ...Object.keys(indexMap)],
|
||||
[] as string[]
|
||||
);
|
||||
}
|
||||
|
||||
public async hasAlias(request: FrameworkRequest, aliasName: string): Promise<boolean> {
|
||||
return this.framework.callWithRequest(request, 'indices.existsAlias', {
|
||||
name: aliasName,
|
||||
});
|
||||
}
|
||||
|
||||
public async hasIndices(request: FrameworkRequest, indexNames: string | string[]) {
|
||||
return this.framework
|
||||
|
@ -56,13 +29,3 @@ export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const withDefaultIfNotFound = <DefaultValue>(defaultValue: DefaultValue) => (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
error: any
|
||||
): DefaultValue => {
|
||||
if (error && error.status === 404) {
|
||||
return defaultValue;
|
||||
}
|
||||
throw error;
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { ConfigurationAdapter } from '../configuration';
|
||||
import { InmemoryConfigurationAdapter } from '../configuration/inmemory_configuration_adapter';
|
||||
|
||||
import { SourcesAdapter, SourceConfiguration } from './index';
|
||||
import { PartialSourceConfigurations } from './types';
|
||||
|
@ -15,7 +16,11 @@ interface ConfigurationWithSources {
|
|||
export class ConfigurationSourcesAdapter implements SourcesAdapter {
|
||||
private readonly configuration: ConfigurationAdapter<ConfigurationWithSources>;
|
||||
|
||||
constructor(configuration: ConfigurationAdapter<ConfigurationWithSources>) {
|
||||
constructor(
|
||||
configuration: ConfigurationAdapter<
|
||||
ConfigurationWithSources
|
||||
> = new InmemoryConfigurationAdapter({ sources: {} })
|
||||
) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Legacy } from 'kibana';
|
||||
import { getOr } from 'lodash/fp';
|
||||
|
||||
import { SavedObjectsFindOptions } from 'src/core/server';
|
||||
|
||||
import { Pick3 } from '../../../common/utility_types';
|
||||
import {
|
||||
ResponseTimeline,
|
||||
PageInfoTimeline,
|
||||
|
@ -34,17 +32,8 @@ interface ResponseTimelines {
|
|||
}
|
||||
|
||||
export class Timeline {
|
||||
private readonly note: Note;
|
||||
private readonly pinnedEvent: PinnedEvent;
|
||||
constructor(
|
||||
private readonly libs: {
|
||||
savedObjects: Pick<Legacy.SavedObjectsService, 'getScopedSavedObjectsClient'> &
|
||||
Pick3<Legacy.SavedObjectsService, 'SavedObjectsClient', 'errors', 'isConflictError'>;
|
||||
}
|
||||
) {
|
||||
this.note = new Note({ savedObjects: this.libs.savedObjects });
|
||||
this.pinnedEvent = new PinnedEvent({ savedObjects: this.libs.savedObjects });
|
||||
}
|
||||
private readonly note = new Note();
|
||||
private readonly pinnedEvent = new PinnedEvent();
|
||||
|
||||
public async getTimeline(
|
||||
request: FrameworkRequest,
|
||||
|
@ -149,6 +138,8 @@ export class Timeline {
|
|||
version: string | null,
|
||||
timeline: SavedTimeline
|
||||
): Promise<ResponseTimeline> {
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
|
||||
try {
|
||||
if (timelineId == null) {
|
||||
// Create new timeline
|
||||
|
@ -156,40 +147,33 @@ export class Timeline {
|
|||
code: 200,
|
||||
message: 'success',
|
||||
timeline: convertSavedObjectToSavedTimeline(
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.create(
|
||||
timelineSavedObjectType,
|
||||
pickSavedTimeline(
|
||||
timelineId,
|
||||
timeline,
|
||||
request[internalFrameworkRequest].auth || null
|
||||
)
|
||||
await savedObjectsClient.create(
|
||||
timelineSavedObjectType,
|
||||
pickSavedTimeline(
|
||||
timelineId,
|
||||
timeline,
|
||||
request[internalFrameworkRequest].auth || null
|
||||
)
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
// Update Timeline
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.update(
|
||||
timelineSavedObjectType,
|
||||
timelineId,
|
||||
pickSavedTimeline(timelineId, timeline, request[internalFrameworkRequest].auth || null),
|
||||
{
|
||||
version: version || undefined,
|
||||
}
|
||||
);
|
||||
await savedObjectsClient.update(
|
||||
timelineSavedObjectType,
|
||||
timelineId,
|
||||
pickSavedTimeline(timelineId, timeline, request[internalFrameworkRequest].auth || null),
|
||||
{
|
||||
version: version || undefined,
|
||||
}
|
||||
);
|
||||
return {
|
||||
code: 200,
|
||||
message: 'success',
|
||||
timeline: await this.getSavedTimeline(request, timelineId),
|
||||
};
|
||||
} catch (err) {
|
||||
if (
|
||||
timelineId != null &&
|
||||
this.libs.savedObjects.SavedObjectsClient.errors.isConflictError(err)
|
||||
) {
|
||||
if (timelineId != null && savedObjectsClient.errors.isConflictError(err)) {
|
||||
return {
|
||||
code: 409,
|
||||
message: err.message,
|
||||
|
@ -212,12 +196,12 @@ export class Timeline {
|
|||
}
|
||||
|
||||
public async deleteTimeline(request: FrameworkRequest, timelineIds: string[]) {
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
|
||||
await Promise.all(
|
||||
timelineIds.map(timelineId =>
|
||||
Promise.all([
|
||||
this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
|
||||
.delete(timelineSavedObjectType, timelineId),
|
||||
savedObjectsClient.delete(timelineSavedObjectType, timelineId),
|
||||
this.note.deleteNoteByTimelineId(request, timelineId),
|
||||
this.pinnedEvent.deleteAllPinnedEventsOnTimeline(request, timelineId),
|
||||
])
|
||||
|
@ -226,10 +210,7 @@ export class Timeline {
|
|||
}
|
||||
|
||||
private async getBasicSavedTimeline(request: FrameworkRequest, timelineId: string) {
|
||||
const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient(
|
||||
request[internalFrameworkRequest]
|
||||
);
|
||||
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
const savedObject = await savedObjectsClient.get(timelineSavedObjectType, timelineId);
|
||||
|
||||
return convertSavedObjectToSavedTimeline(savedObject);
|
||||
|
@ -238,10 +219,7 @@ export class Timeline {
|
|||
private async getSavedTimeline(request: FrameworkRequest, timelineId: string) {
|
||||
const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth);
|
||||
|
||||
const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient(
|
||||
request[internalFrameworkRequest]
|
||||
);
|
||||
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
const savedObject = await savedObjectsClient.get(timelineSavedObjectType, timelineId);
|
||||
const timelineSaveObject = convertSavedObjectToSavedTimeline(savedObject);
|
||||
const timelineWithNotesAndPinnedEvents = await Promise.all([
|
||||
|
@ -257,10 +235,7 @@ export class Timeline {
|
|||
|
||||
private async getAllSavedTimeline(request: FrameworkRequest, options: SavedObjectsFindOptions) {
|
||||
const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth);
|
||||
|
||||
const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient(
|
||||
request[internalFrameworkRequest]
|
||||
);
|
||||
const savedObjectsClient = request.context.core.savedObjects.client;
|
||||
if (options.searchFields != null && options.searchFields.includes('favorite.keySearch')) {
|
||||
options.search = `${options.search != null ? options.search : ''} ${
|
||||
userName != null ? convertStringToBase64(userName) : null
|
||||
|
|
|
@ -24,10 +24,8 @@ describe('elasticsearch_adapter', () => {
|
|||
const mockFramework: FrameworkAdapter = {
|
||||
version: 'mock',
|
||||
callWithRequest: mockCallWithRequest,
|
||||
exposeStaticDir: jest.fn(),
|
||||
registerGraphQLEndpoint: jest.fn(),
|
||||
getIndexPatternsService: jest.fn(),
|
||||
getSavedObjectsService: jest.fn(),
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { ConfigType as Configuration } from '../../../../../plugins/siem/server';
|
||||
import { Anomalies } from './anomalies';
|
||||
import { Authentications } from './authentications';
|
||||
import { ConfigurationAdapter } from './configuration';
|
||||
import { Events } from './events';
|
||||
import { FrameworkAdapter, FrameworkRequest } from './framework';
|
||||
import { Hosts } from './hosts';
|
||||
|
@ -17,7 +17,7 @@ import { KpiNetwork } from './kpi_network';
|
|||
import { Network } from './network';
|
||||
import { Overview } from './overview';
|
||||
import { SourceStatus } from './source_status';
|
||||
import { Sources, SourceConfiguration } from './sources';
|
||||
import { Sources } from './sources';
|
||||
import { UncommonProcesses } from './uncommon_processes';
|
||||
import { Note } from './note/saved_object';
|
||||
import { PinnedEvent } from './pinned_event/saved_object';
|
||||
|
@ -43,7 +43,6 @@ export interface AppDomainLibs {
|
|||
}
|
||||
|
||||
export interface AppBackendLibs extends AppDomainLibs {
|
||||
configuration: ConfigurationAdapter<Configuration>;
|
||||
framework: FrameworkAdapter;
|
||||
sources: Sources;
|
||||
sourceStatus: SourceStatus;
|
||||
|
@ -52,15 +51,6 @@ export interface AppBackendLibs extends AppDomainLibs {
|
|||
pinnedEvent: PinnedEvent;
|
||||
}
|
||||
|
||||
export interface Configuration {
|
||||
enabled: boolean;
|
||||
query: {
|
||||
partitionSize: number;
|
||||
partitionFactor: number;
|
||||
};
|
||||
sources: Record<string, SourceConfiguration>;
|
||||
}
|
||||
|
||||
export interface SiemContext {
|
||||
req: FrameworkRequest;
|
||||
}
|
||||
|
|
|
@ -4,27 +4,71 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CoreSetup, EnvironmentMode, PluginInitializerContext, Logger } from 'src/core/server';
|
||||
import { ServerFacade } from './types';
|
||||
import { initServerWithKibana } from './kibana.index';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server';
|
||||
import { PluginSetupContract as FeaturesSetupContract } from '../../../../plugins/features/server';
|
||||
import { initServer } from './init_server';
|
||||
import { compose } from './lib/compose/kibana';
|
||||
import {
|
||||
noteSavedObjectType,
|
||||
pinnedEventSavedObjectType,
|
||||
timelineSavedObjectType,
|
||||
} from './saved_objects';
|
||||
|
||||
export interface PluginsSetup {
|
||||
features: FeaturesSetupContract;
|
||||
}
|
||||
|
||||
export class Plugin {
|
||||
name = 'siem';
|
||||
private mode: EnvironmentMode;
|
||||
private logger: Logger;
|
||||
readonly name = 'siem';
|
||||
private readonly logger: Logger;
|
||||
private context: PluginInitializerContext;
|
||||
|
||||
constructor({ env, logger }: PluginInitializerContext) {
|
||||
this.logger = logger.get('plugins', this.name);
|
||||
this.mode = env.mode;
|
||||
constructor(context: PluginInitializerContext) {
|
||||
this.context = context;
|
||||
this.logger = context.logger.get('plugins', this.name);
|
||||
|
||||
this.logger.info('NP plugin initialized');
|
||||
this.logger.debug('Shim plugin initialized');
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup, dependencies: {}, __legacy: ServerFacade) {
|
||||
this.logger.info('NP plugin setup');
|
||||
public setup(core: CoreSetup, plugins: PluginsSetup) {
|
||||
this.logger.debug('Shim plugin setup');
|
||||
|
||||
initServerWithKibana(__legacy, this.logger, this.mode);
|
||||
plugins.features.registerFeature({
|
||||
id: this.name,
|
||||
name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', {
|
||||
defaultMessage: 'SIEM',
|
||||
}),
|
||||
icon: 'securityAnalyticsApp',
|
||||
navLinkId: 'siem',
|
||||
app: ['siem', 'kibana'],
|
||||
catalogue: ['siem'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['siem'],
|
||||
savedObject: {
|
||||
all: [noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType],
|
||||
read: ['config'],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
read: {
|
||||
api: ['siem'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [
|
||||
'config',
|
||||
noteSavedObjectType,
|
||||
pinnedEventSavedObjectType,
|
||||
timelineSavedObjectType,
|
||||
],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.info('NP plugin setup complete');
|
||||
const libs = compose(core, this.context.env);
|
||||
initServer(libs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,29 +8,21 @@ import { Legacy } from 'kibana';
|
|||
|
||||
export interface ServerFacade {
|
||||
config: Legacy.Server['config'];
|
||||
getInjectedUiAppVars: Legacy.Server['getInjectedUiAppVars'];
|
||||
indexPatternsServiceFactory: Legacy.Server['indexPatternsServiceFactory'];
|
||||
injectUiAppVars: Legacy.Server['injectUiAppVars'];
|
||||
plugins: {
|
||||
alerting?: Legacy.Server['plugins']['alerting'];
|
||||
elasticsearch: Legacy.Server['plugins']['elasticsearch'];
|
||||
spaces: Legacy.Server['plugins']['spaces'];
|
||||
xpack_main: Legacy.Server['plugins']['xpack_main'];
|
||||
};
|
||||
route: Legacy.Server['route'];
|
||||
savedObjects: Legacy.Server['savedObjects'];
|
||||
}
|
||||
|
||||
export interface RequestFacade {
|
||||
auth: Legacy.Request['auth'];
|
||||
getAlertsClient?: Legacy.Request['getAlertsClient'];
|
||||
getActionsClient?: Legacy.Request['getActionsClient'];
|
||||
getUiSettingsService: Legacy.Request['getUiSettingsService'];
|
||||
headers: Legacy.Request['headers'];
|
||||
method: Legacy.Request['method'];
|
||||
params: Legacy.Request['params'];
|
||||
payload: unknown;
|
||||
query: Legacy.Request['query'];
|
||||
server: {
|
||||
plugins: { elasticsearch: Legacy.Request['server']['plugins']['elasticsearch'] };
|
||||
};
|
||||
}
|
||||
|
|
8
x-pack/plugins/siem/kibana.json
Normal file
8
x-pack/plugins/siem/kibana.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"id": "siem",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["xpack", "siem"],
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
20
x-pack/plugins/siem/server/config.ts
Normal file
20
x-pack/plugins/siem/server/config.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.
|
||||
*/
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
|
||||
export const configSchema = schema.object({
|
||||
enabled: schema.boolean({ defaultValue: true }),
|
||||
});
|
||||
|
||||
export const createConfig$ = (context: PluginInitializerContext) =>
|
||||
context.config.create<TypeOf<typeof configSchema>>();
|
||||
|
||||
export type ConfigType = ReturnType<typeof createConfig$> extends Observable<infer T>
|
||||
? T
|
||||
: ReturnType<typeof createConfig$>;
|
17
x-pack/plugins/siem/server/index.ts
Normal file
17
x-pack/plugins/siem/server/index.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 { PluginInitializerContext } from 'src/core/server';
|
||||
import { Plugin } from './plugin';
|
||||
import { configSchema, ConfigType } from './config';
|
||||
|
||||
export const plugin = (context: PluginInitializerContext) => {
|
||||
return new Plugin(context);
|
||||
};
|
||||
|
||||
export const config = { schema: configSchema };
|
||||
|
||||
export { ConfigType };
|
37
x-pack/plugins/siem/server/plugin.ts
Normal file
37
x-pack/plugins/siem/server/plugin.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 { Observable } from 'rxjs';
|
||||
|
||||
import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server';
|
||||
import { createConfig$, ConfigType } from './config';
|
||||
|
||||
export class Plugin {
|
||||
readonly name = 'siem';
|
||||
private readonly logger: Logger;
|
||||
// @ts-ignore-next-line TODO(rylnd): use it or lose it
|
||||
private readonly config$: Observable<ConfigType>;
|
||||
|
||||
constructor(context: PluginInitializerContext) {
|
||||
const { logger } = context;
|
||||
this.logger = logger.get();
|
||||
this.logger.debug('plugin initialized');
|
||||
|
||||
this.config$ = createConfig$(context);
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup, plugins: {}) {
|
||||
this.logger.debug('plugin setup');
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.logger.debug('plugin started');
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.logger.debug('plugin stopped');
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue