[SIEM] Server NP Followup (#64010)

* Remove unused file

This was moved to a constant in common/constants.

* Remove unused types

Omit is now part of Typescript, and Pick3 is unused.

* Define and export SIEM's plugin contracts

They're empty for now.

* Import config type from config file

Instead of our plugin index, which could only cause circular
dependencies.

* SiemClient API uses getter function instead of direct property access

* Add public mock for SiemClient

* Fix typo in extract-mitre-attacks script

We were backgrounding the process (&) instead of ANDing it with the
linting. Whoops!

* Remove missed instance of old siemClient API

I missed this one when grepping, but typescript and CI saved me.

* Use our client mock in our test suite

This was causing some test failures as I forgot to update the client mock

* Update script following updates to the output's usage

This was changed in https://github.com/elastic/kibana/pull/55883 but the
script was not updated accordingly.
This commit is contained in:
Ryland Herrick 2020-04-21 14:27:10 -05:00 committed by GitHub
parent 5a55c9a8f6
commit ff5971ac17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 61 additions and 51 deletions

View file

@ -1,15 +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.
*/
/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */
export const defaultIndexPattern = [
'apm-*-transaction*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
'packetbeat-*',
'winlogbeat-*',
];

View file

@ -6,12 +6,6 @@
import { ReactNode } from 'react';
export type Pick3<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]> = {
[P1 in K1]: { [P2 in K2]: { [P3 in K3]: T[K1][K2][P3] } };
};
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// This type is for typing EuiDescriptionList
export interface DescriptionList {
title: NonNullable<ReactNode>;

View file

@ -5,7 +5,7 @@
"private": true,
"license": "Elastic-License",
"scripts": {
"extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js & node ../../../scripts/eslint ../../legacy/plugins/siem/public/pages/detection_engine/mitre/mitre_tactics_techniques.ts --fix",
"extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js && node ../../../scripts/eslint ../../legacy/plugins/siem/public/pages/detection_engine/mitre/mitre_tactics_techniques.ts --fix",
"build-graphql-types": "node scripts/generate_types_from_graphql.js",
"cypress:open": "cypress open --config-file ./cypress/cypress.json",
"cypress:run": "cypress run --spec ./cypress/integration/**/*.spec.ts --config-file ./cypress/cypress.json --reporter ../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json; status=$?; ../../node_modules/.bin/mochawesome-merge --reportDir ../../../target/kibana-siem/cypress/results > ../../../target/kibana-siem/cypress/results/output.json; ../../../node_modules/.bin/marge ../../../target/kibana-siem/cypress/results/output.json --reportDir ../../../target/kibana-siem/cypress/results; mkdir -p ../../../target/junit && cp ../../../target/kibana-siem/cypress/results/*.xml ../../../target/junit/ && exit $status;",

View file

@ -123,7 +123,7 @@ async function main() {
.replace(/}"/g, '}')
.replace(/"{/g, '{')};
export const techniques = ${JSON.stringify(techniques, null, 2)};
export const technique = ${JSON.stringify(techniques, null, 2)};
export const techniquesOptions: MitreTechniquesOptions[] =
${JSON.stringify(getTechniquesOptions(techniques), null, 2)

View file

@ -9,7 +9,7 @@ import { createMockConfig } from '../lib/detection_engine/routes/__mocks__';
import { SiemClient } from './client';
describe('SiemClient', () => {
describe('#signalsIndex', () => {
describe('#getSignalsIndex', () => {
it('returns the index scoped to the specified spaceId', () => {
const mockConfig = {
...createMockConfig(),
@ -18,7 +18,7 @@ describe('SiemClient', () => {
const spaceId = 'fooSpace';
const client = new SiemClient(spaceId, mockConfig);
expect(client.signalsIndex).toEqual('mockSignalsIndex-fooSpace');
expect(client.getSignalsIndex()).toEqual('mockSignalsIndex-fooSpace');
});
});
});

View file

@ -4,14 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ConfigType } from '..';
import { ConfigType } from '../config';
export class SiemClient {
public readonly signalsIndex: string;
private readonly signalsIndex: string;
constructor(private spaceId: string, private config: ConfigType) {
const configuredSignalsIndex = this.config.signalsIndex;
this.signalsIndex = `${configuredSignalsIndex}-${this.spaceId}`;
}
public getSignalsIndex = (): string => this.signalsIndex;
}

View file

@ -6,7 +6,7 @@
import { KibanaRequest } from '../../../../../src/core/server';
import { SiemClient } from './client';
import { ConfigType } from '..';
import { ConfigType } from '../config';
interface SetupDependencies {
getSpaceId?: (request: KibanaRequest) => string | undefined;

View file

@ -5,7 +5,7 @@
*/
import { PluginInitializerContext } from '../../../../src/core/server';
import { Plugin } from './plugin';
import { Plugin, PluginSetup, PluginStart } from './plugin';
import { configSchema, ConfigType } from './config';
export const plugin = (context: PluginInitializerContext) => {
@ -14,4 +14,4 @@ export const plugin = (context: PluginInitializerContext) => {
export const config = { schema: configSchema };
export { ConfigType };
export { ConfigType, Plugin, PluginSetup, PluginStart };

View file

@ -13,6 +13,7 @@ import {
import { alertsClientMock } from '../../../../../../alerting/server/mocks';
import { actionsClientMock } from '../../../../../../actions/server/mocks';
import { licensingMock } from '../../../../../../licensing/server/mocks';
import { siemMock } from '../../../../mocks';
const createMockClients = () => ({
actionsClient: actionsClientMock.create(),
@ -20,7 +21,7 @@ const createMockClients = () => ({
clusterClient: elasticsearchServiceMock.createScopedClusterClient(),
licensing: { license: licensingMock.createLicenseMock() },
savedObjectsClient: savedObjectsClientMock.create(),
siemClient: { signalsIndex: 'mockSignalsIndex' },
siemClient: siemMock.createClient(),
});
const createRequestContextMock = (

View file

@ -37,7 +37,7 @@ export const createIndexRoute = (router: IRouter) => {
return siemResponse.error({ statusCode: 404 });
}
const index = siemClient.signalsIndex;
const index = siemClient.getSignalsIndex();
const indexExists = await getIndexExists(callCluster, index);
if (indexExists) {
return siemResponse.error({

View file

@ -45,7 +45,7 @@ export const deleteIndexRoute = (router: IRouter) => {
}
const callCluster = clusterClient.callAsCurrentUser;
const index = siemClient.signalsIndex;
const index = siemClient.getSignalsIndex();
const indexExists = await getIndexExists(callCluster, index);
if (!indexExists) {

View file

@ -29,7 +29,7 @@ export const readIndexRoute = (router: IRouter) => {
return siemResponse.error({ statusCode: 404 });
}
const index = siemClient.signalsIndex;
const index = siemClient.getSignalsIndex();
const indexExists = await getIndexExists(clusterClient.callAsCurrentUser, index);
if (indexExists) {

View file

@ -36,7 +36,7 @@ export const readPrivilegesRoute = (
return siemResponse.error({ statusCode: 404 });
}
const index = siemClient.signalsIndex;
const index = siemClient.getSignalsIndex();
const clusterPrivileges = await readPrivileges(clusterClient.callAsCurrentUser, index);
const privileges = merge(clusterPrivileges, {
is_authenticated: security?.authc.isAuthenticated(request) ?? false,

View file

@ -49,7 +49,7 @@ export const addPrepackedRulesRoute = (router: IRouter) => {
const rulesToInstall = getRulesToInstall(rulesFromFileSystem, prepackagedRules);
const rulesToUpdate = getRulesToUpdate(rulesFromFileSystem, prepackagedRules);
const { signalsIndex } = siemClient;
const signalsIndex = siemClient.getSignalsIndex();
if (rulesToInstall.length !== 0 || rulesToUpdate.length !== 0) {
const signalsIndexExists = await getIndexExists(
clusterClient.callAsCurrentUser,

View file

@ -92,7 +92,7 @@ export const createRulesBulkRoute = (router: IRouter) => {
try {
validateLicenseForRuleType({ license: context.licensing.license, ruleType: type });
const finalIndex = outputIndex ?? siemClient.signalsIndex;
const finalIndex = outputIndex ?? siemClient.getSignalsIndex();
const indexExists = await getIndexExists(clusterClient.callAsCurrentUser, finalIndex);
if (!indexExists) {
return createBulkErrorObject({

View file

@ -82,7 +82,7 @@ export const createRulesRoute = (router: IRouter): void => {
return siemResponse.error({ statusCode: 404 });
}
const finalIndex = outputIndex ?? siemClient.signalsIndex;
const finalIndex = outputIndex ?? siemClient.getSignalsIndex();
const indexExists = await getIndexExists(clusterClient.callAsCurrentUser, finalIndex);
if (!indexExists) {
return siemResponse.error({

View file

@ -6,7 +6,7 @@
import { IRouter } from '../../../../../../../../src/core/server';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
import { ConfigType } from '../../../..';
import { ConfigType } from '../../../../config';
import { ExportRulesRequestParams } from '../../rules/types';
import { getNonPackagedRulesCount } from '../../rules/get_existing_prepackaged_rules';
import { exportRulesSchema, exportRulesQuerySchema } from '../schemas/export_rules_schema';

View file

@ -126,6 +126,7 @@ describe('import_rules_route', () => {
});
test('returns an error if the index does not exist', async () => {
clients.siemClient.getSignalsIndex.mockReturnValue('mockSignalsIndex');
clients.clusterClient.callAsCurrentUser.mockResolvedValue(getEmptyIndex());
const response = await server.inject(request, context);
expect(response.status).toEqual(200);

View file

@ -10,7 +10,7 @@ import { extname } from 'path';
import { IRouter } from '../../../../../../../../src/core/server';
import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils/streams';
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
import { ConfigType } from '../../../..';
import { ConfigType } from '../../../../config';
import { createRules } from '../../rules/create_rules';
import { ImportRulesRequestParams } from '../../rules/types';
import { readRules } from '../../rules/read_rules';
@ -147,7 +147,7 @@ export const importRulesRoute = (router: IRouter, config: ConfigType) => {
ruleType: type,
});
const signalsIndex = siemClient.signalsIndex;
const signalsIndex = siemClient.getSignalsIndex();
const indexExists = await getIndexExists(
clusterClient.callAsCurrentUser,
signalsIndex

View file

@ -83,7 +83,7 @@ export const updateRulesBulkRoute = (router: IRouter) => {
version,
exceptions_list,
} = payloadRule;
const finalIndex = outputIndex ?? siemClient.signalsIndex;
const finalIndex = outputIndex ?? siemClient.getSignalsIndex();
const idOrRuleIdOrUnknown = id ?? ruleId ?? '(unknown id)';
try {
validateLicenseForRuleType({ license: context.licensing.license, ruleType: type });

View file

@ -83,7 +83,7 @@ export const updateRulesRoute = (router: IRouter) => {
return siemResponse.error({ statusCode: 404 });
}
const finalIndex = outputIndex ?? siemClient.signalsIndex;
const finalIndex = outputIndex ?? siemClient.getSignalsIndex();
const rule = await updateRules({
alertsClient,
actionsClient,

View file

@ -44,7 +44,7 @@ export const setSignalsStatusRoute = (router: IRouter) => {
}
try {
const result = await clusterClient.callAsCurrentUser('updateByQuery', {
index: siemClient.signalsIndex,
index: siemClient.getSignalsIndex(),
body: {
script: {
source: `ctx._source.signal.status = '${status}'`,

View file

@ -29,7 +29,7 @@ export const querySignalsRoute = (router: IRouter) => {
try {
const result = await clusterClient.callAsCurrentUser('search', {
index: siemClient.signalsIndex,
index: siemClient.getSignalsIndex(),
body: { query, aggs, _source, track_total_hits, size },
ignoreUnavailable: true,
});

View file

@ -8,7 +8,7 @@ import { set as _set } from 'lodash/fp';
import { TIMELINE_EXPORT_URL } from '../../../../common/constants';
import { IRouter } from '../../../../../../../src/core/server';
import { ConfigType } from '../../..';
import { ConfigType } from '../../../config';
import { transformError, buildSiemResponse } from '../../detection_engine/routes/utils';
import { getExportTimelineByObjectIds } from './utils/export_timelines';

View file

@ -32,7 +32,7 @@ import { IRouter } from '../../../../../../../src/core/server';
import { SetupPlugins } from '../../../plugin';
import { ImportTimelinesPayloadSchemaRt } from './schemas/import_timelines_schema';
import { importRulesSchema } from '../../detection_engine/routes/schemas/response/import_rules_schema';
import { ConfigType } from '../../..';
import { ConfigType } from '../../../config';
import { Timeline } from '../saved_object';
import { validate } from '../../detection_engine/routes/rules/validate';

View file

@ -6,7 +6,7 @@
import { AuthenticatedUser } from '../../../security/public';
import { RequestHandlerContext } from '../../../../../src/core/server';
export { ConfigType as Configuration } from '../';
export { ConfigType as Configuration } from '../config';
import { Authentications } from './authentications';
import { Events } from './events';

View 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 { SiemClient } from './types';
type SiemClientMock = jest.Mocked<SiemClient>;
const createSiemClientMock = (): SiemClientMock =>
(({
getSignalsIndex: jest.fn(),
} as unknown) as SiemClientMock);
export const siemMock = {
createClient: createSiemClientMock,
};

View file

@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
import {
CoreSetup,
CoreStart,
Plugin as IPlugin,
PluginInitializerContext,
Logger,
} from '../../../../src/core/server';
@ -61,7 +62,12 @@ export interface StartPlugins {
alerting: AlertingStart;
}
export class Plugin {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PluginStart {}
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
readonly name = 'siem';
private readonly logger: Logger;
private readonly config$: Observable<ConfigType>;
@ -204,7 +210,11 @@ export class Plugin {
const libs = compose(core, plugins, this.context.env.mode.prod);
initServer(libs);
return {};
}
public start(core: CoreStart, plugins: StartPlugins) {}
public start(core: CoreStart, plugins: StartPlugins) {
return {};
}
}

View file

@ -31,7 +31,7 @@ import { getPrepackagedRulesStatusRoute } from '../lib/detection_engine/routes/r
import { importTimelinesRoute } from '../lib/timeline/routes/import_timelines_route';
import { exportTimelinesRoute } from '../lib/timeline/routes/export_timelines_route';
import { SetupPlugins } from '../plugin';
import { ConfigType } from '..';
import { ConfigType } from '../config';
export const initRoutes = (
router: IRouter,