mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Entity Analytics] Implement Asset Criticality Create, Read & Delete APIs (#172073)
## Summary Adds upsert, read and delete APIs for asset criticality records. I have used the OpenAPI code generation to create the types and zod schemas. The APIs added are as follows: **POST /internal/risk_score/criticality** Request Body: ``` { id_value: "host-1", id_field: "host.name", criticality_level: "very_important" } ``` If the record already exists it will be overwritten, otherwise created **GET /internal/risk_score/criticality?id_field=host.name&id_value=host-1** Response body: ``` { id_value: "host-1", id_field: "host.name", criticality_level: "very_important" @timestamp: "2023-11-29T11:43:43.175Z" } ``` **DELETE /internal/risk_score/criticality?id_field=host.name&id_value=host-1** --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
823552fea5
commit
991b5f6f8c
18 changed files with 659 additions and 15 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -1474,6 +1474,7 @@ x-pack/plugins/security_solution/server/lib/entity_analytics @elastic/security-e
|
|||
x-pack/plugins/security_solution/server/lib/risk_score @elastic/security-entity-analytics
|
||||
x-pack/test/security_solution_api_integration/test_suites/entity_analytics @elastic/security-entity-analytics
|
||||
x-pack/plugins/security_solution/public/flyout/entity_details @elastic/security-entity-analytics
|
||||
x-pack/plugins/security_solution/common/api/asset_criticality @elastic/security-entity-analytics
|
||||
/x-pack/plugins/security_solution/public/entity_analytics @elastic/security-entity-analytics
|
||||
/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics @elastic/security-entity-analytics
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/*
|
||||
* NOTICE: Do not edit this file manually.
|
||||
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
|
||||
*/
|
||||
|
||||
export type IdField = z.infer<typeof IdField>;
|
||||
export const IdField = z.enum(['host.name', 'user.name']);
|
||||
export type IdFieldEnum = typeof IdField.enum;
|
||||
export const IdFieldEnum = IdField.enum;
|
||||
|
||||
export type AssetCriticalityRecordIdParts = z.infer<typeof AssetCriticalityRecordIdParts>;
|
||||
export const AssetCriticalityRecordIdParts = z.object({
|
||||
/**
|
||||
* The ID value of the asset.
|
||||
*/
|
||||
id_value: z.string(),
|
||||
/**
|
||||
* The field representing the ID.
|
||||
*/
|
||||
id_field: IdField,
|
||||
});
|
||||
|
||||
export type CreateAssetCriticalityRecord = z.infer<typeof CreateAssetCriticalityRecord>;
|
||||
export const CreateAssetCriticalityRecord = AssetCriticalityRecordIdParts.merge(
|
||||
z.object({
|
||||
/**
|
||||
* The criticality level of the asset.
|
||||
*/
|
||||
criticality_level: z.enum(['very_important', 'important', 'normal', 'not_important']),
|
||||
})
|
||||
);
|
||||
|
||||
export type AssetCriticalityRecord = z.infer<typeof AssetCriticalityRecord>;
|
||||
export const AssetCriticalityRecord = CreateAssetCriticalityRecord.merge(
|
||||
z.object({
|
||||
/**
|
||||
* The time the record was created or updated.
|
||||
*/
|
||||
'@timestamp': z.string().datetime(),
|
||||
})
|
||||
);
|
|
@ -0,0 +1,66 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
title: Asset Criticality Common Schema
|
||||
description: Common schema for asset criticality
|
||||
version: 1.0.0
|
||||
paths: { }
|
||||
components:
|
||||
parameters:
|
||||
id_value:
|
||||
name: id_value
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The ID value of the asset.
|
||||
id_field:
|
||||
name: id_field
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/IdField'
|
||||
example: 'host.name'
|
||||
description: The field representing the ID.
|
||||
|
||||
schemas:
|
||||
IdField:
|
||||
type: string
|
||||
enum:
|
||||
- 'host.name'
|
||||
- 'user.name'
|
||||
AssetCriticalityRecordIdParts:
|
||||
type: object
|
||||
properties:
|
||||
id_value:
|
||||
type: string
|
||||
description: The ID value of the asset.
|
||||
id_field:
|
||||
$ref: '#/components/schemas/IdField'
|
||||
example: 'host.name'
|
||||
description: The field representing the ID.
|
||||
required:
|
||||
- id_value
|
||||
- id_field
|
||||
CreateAssetCriticalityRecord:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AssetCriticalityRecordIdParts'
|
||||
- type: object
|
||||
properties:
|
||||
criticality_level:
|
||||
type: string
|
||||
enum: [very_important, important, normal, not_important]
|
||||
description: The criticality level of the asset.
|
||||
required:
|
||||
- criticality_level
|
||||
AssetCriticalityRecord:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/CreateAssetCriticalityRecord'
|
||||
- type: object
|
||||
properties:
|
||||
"@timestamp":
|
||||
type: string
|
||||
format: 'date-time'
|
||||
example: '2017-07-21T17:32:28Z'
|
||||
description: The time the record was created or updated.
|
||||
required:
|
||||
- "@timestamp"
|
|
@ -0,0 +1,23 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
version: 1.0.0
|
||||
title: Asset Criticality Create Record Schema
|
||||
paths:
|
||||
/internal/asset_criticality:
|
||||
post:
|
||||
summary: Create Criticality Record
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateAssetCriticalityRecord'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SingleAssetCriticality'
|
||||
'400':
|
||||
description: Invalid request
|
|
@ -0,0 +1,16 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
version: 1.0.0
|
||||
title: Asset Criticality Delete Record Schema
|
||||
paths:
|
||||
/internal/asset_criticality:
|
||||
delete:
|
||||
summary: Delete Criticality Record
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/id_value'
|
||||
- $ref: '#/components/parameters/id_field'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
'400':
|
||||
description: Invalid request
|
|
@ -0,0 +1,22 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
version: 1.0.0
|
||||
title: Asset Criticality Get Record Schema
|
||||
paths:
|
||||
/internal/asset_criticality:
|
||||
get:
|
||||
summary: Get Criticality Record
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/id_value'
|
||||
- $ref: '#/components/parameters/id_field'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SingleAssetCriticality'
|
||||
'400':
|
||||
description: Invalid request
|
||||
'404':
|
||||
description: Criticality record not found
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/*
|
||||
* NOTICE: Do not edit this file manually.
|
||||
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
|
||||
*/
|
||||
|
||||
export type AssetCriticalityStatusResponse = z.infer<typeof AssetCriticalityStatusResponse>;
|
||||
export const AssetCriticalityStatusResponse = z.object({
|
||||
asset_criticality_resources_installed: z.boolean().optional(),
|
||||
});
|
|
@ -15,7 +15,6 @@ paths:
|
|||
$ref: '#/components/schemas/AssetCriticalityStatusResponse'
|
||||
'400':
|
||||
description: Invalid request
|
||||
responses:
|
||||
|
||||
components:
|
||||
schemas:
|
||||
|
@ -23,4 +22,4 @@ components:
|
|||
type: object
|
||||
properties:
|
||||
asset_criticality_resources_installed:
|
||||
type: boolean
|
||||
type: boolean
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './common.gen';
|
||||
export * from './get_asset_criticality_status.gen';
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
import type { Logger, ElasticsearchClient } from '@kbn/core/server';
|
||||
import { mappingFromFieldMap } from '@kbn/alerting-plugin/common';
|
||||
import type { AssetCriticalityRecord } from '../../../../common/api/asset_criticality';
|
||||
import { createOrUpdateIndex } from '../utils/create_or_update_index';
|
||||
import { getAssetCriticalityIndex } from '../../../../common/asset_criticality';
|
||||
import { assetCriticalityFieldMap } from './configurations';
|
||||
|
@ -16,6 +17,15 @@ interface AssetCriticalityClientOpts {
|
|||
namespace: string;
|
||||
}
|
||||
|
||||
interface AssetCriticalityUpsert {
|
||||
idField: AssetCriticalityRecord['id_field'];
|
||||
idValue: AssetCriticalityRecord['id_value'];
|
||||
criticalityLevel: AssetCriticalityRecord['criticality_level'];
|
||||
}
|
||||
|
||||
type AssetCriticalityIdParts = Pick<AssetCriticalityUpsert, 'idField' | 'idValue'>;
|
||||
|
||||
const createId = ({ idField, idValue }: AssetCriticalityIdParts) => `${idField}:${idValue}`;
|
||||
export class AssetCriticalityDataClient {
|
||||
constructor(private readonly options: AssetCriticalityClientOpts) {}
|
||||
/**
|
||||
|
@ -27,16 +37,20 @@ export class AssetCriticalityDataClient {
|
|||
esClient: this.options.esClient,
|
||||
logger: this.options.logger,
|
||||
options: {
|
||||
index: getAssetCriticalityIndex(this.options.namespace),
|
||||
index: this.getIndex(),
|
||||
mappings: mappingFromFieldMap(assetCriticalityFieldMap, 'strict'),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private getIndex() {
|
||||
return getAssetCriticalityIndex(this.options.namespace);
|
||||
}
|
||||
|
||||
public async doesIndexExist() {
|
||||
try {
|
||||
const result = await this.options.esClient.indices.exists({
|
||||
index: getAssetCriticalityIndex(this.options.namespace),
|
||||
index: this.getIndex(),
|
||||
});
|
||||
return result;
|
||||
} catch (e) {
|
||||
|
@ -51,4 +65,51 @@ export class AssetCriticalityDataClient {
|
|||
isAssetCriticalityResourcesInstalled,
|
||||
};
|
||||
}
|
||||
|
||||
public async get(idParts: AssetCriticalityIdParts): Promise<AssetCriticalityRecord | undefined> {
|
||||
const id = createId(idParts);
|
||||
|
||||
try {
|
||||
const body = await this.options.esClient.get<AssetCriticalityRecord>({
|
||||
id,
|
||||
index: this.getIndex(),
|
||||
});
|
||||
|
||||
return body._source;
|
||||
} catch (err) {
|
||||
if (err.statusCode === 404) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async upsert(record: AssetCriticalityUpsert): Promise<AssetCriticalityRecord> {
|
||||
const id = createId(record);
|
||||
const doc = {
|
||||
id_field: record.idField,
|
||||
id_value: record.idValue,
|
||||
criticality_level: record.criticalityLevel,
|
||||
'@timestamp': new Date().toISOString(),
|
||||
};
|
||||
|
||||
await this.options.esClient.update({
|
||||
id,
|
||||
index: this.getIndex(),
|
||||
body: {
|
||||
doc,
|
||||
doc_as_upsert: true,
|
||||
},
|
||||
});
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
public async delete(idParts: AssetCriticalityIdParts) {
|
||||
await this.options.esClient.delete({
|
||||
id: createId(idParts),
|
||||
index: this.getIndex(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { ASSET_CRITICALITY_URL, APP_ID } from '../../../../../common/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
import { AssetCriticalityRecordIdParts } from '../../../../../common/api/asset_criticality';
|
||||
import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation';
|
||||
import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources';
|
||||
export const assetCriticalityDeleteRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
logger: Logger
|
||||
) => {
|
||||
router.versioned
|
||||
.delete({
|
||||
access: 'internal',
|
||||
path: ASSET_CRITICALITY_URL,
|
||||
options: {
|
||||
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
query: buildRouteValidationWithZod(AssetCriticalityRecordIdParts),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
await checkAndInitAssetCriticalityResources(context, logger);
|
||||
|
||||
const securitySolution = await context.securitySolution;
|
||||
const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient();
|
||||
await assetCriticalityClient.delete({
|
||||
idField: request.query.id_field,
|
||||
idValue: request.query.id_value,
|
||||
});
|
||||
|
||||
return response.ok();
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
|
||||
return siemResponse.error({
|
||||
statusCode: error.statusCode,
|
||||
body: { message: error.message, full_error: JSON.stringify(e) },
|
||||
bypassErrorFormat: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { ASSET_CRITICALITY_URL, APP_ID } from '../../../../../common/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources';
|
||||
import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation';
|
||||
import { AssetCriticalityRecordIdParts } from '../../../../../common/api/asset_criticality';
|
||||
export const assetCriticalityGetRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => {
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'internal',
|
||||
path: ASSET_CRITICALITY_URL,
|
||||
options: {
|
||||
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
query: buildRouteValidationWithZod(AssetCriticalityRecordIdParts),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
await checkAndInitAssetCriticalityResources(context, logger);
|
||||
|
||||
const securitySolution = await context.securitySolution;
|
||||
const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient();
|
||||
const record = await assetCriticalityClient.get({
|
||||
idField: request.query.id_field,
|
||||
idValue: request.query.id_value,
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
return response.ok({ body: record });
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
|
||||
return siemResponse.error({
|
||||
statusCode: error.statusCode,
|
||||
body: { message: error.message, full_error: JSON.stringify(e) },
|
||||
bypassErrorFormat: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -6,3 +6,6 @@
|
|||
*/
|
||||
|
||||
export { assetCriticalityStatusRoute } from './status';
|
||||
export { assetCriticalityUpsertRoute } from './upsert';
|
||||
export { assetCriticalityGetRoute } from './get';
|
||||
export { assetCriticalityDeleteRoute } from './delete';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import type { Logger } from '@kbn/core/server';
|
||||
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { AssetCriticalityStatusResponse } from '../../../../../common/api/asset_criticality';
|
||||
import { ASSET_CRITICALITY_STATUS_URL, APP_ID } from '../../../../../common/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources';
|
||||
|
@ -32,10 +33,11 @@ export const assetCriticalityStatusRoute = (
|
|||
const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient();
|
||||
|
||||
const result = await assetCriticalityClient.getStatus();
|
||||
const body: AssetCriticalityStatusResponse = {
|
||||
asset_criticality_resources_installed: result.isAssetCriticalityResourcesInstalled,
|
||||
};
|
||||
return response.ok({
|
||||
body: {
|
||||
asset_criticality_resources_installed: result.isAssetCriticalityResourcesInstalled,
|
||||
},
|
||||
body,
|
||||
});
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { ASSET_CRITICALITY_URL, APP_ID } from '../../../../../common/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources';
|
||||
import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation';
|
||||
import { CreateAssetCriticalityRecord } from '../../../../../common/api/asset_criticality';
|
||||
export const assetCriticalityUpsertRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
logger: Logger
|
||||
) => {
|
||||
router.versioned
|
||||
.post({
|
||||
access: 'internal',
|
||||
path: ASSET_CRITICALITY_URL,
|
||||
options: {
|
||||
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
body: buildRouteValidationWithZod(CreateAssetCriticalityRecord),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
try {
|
||||
await checkAndInitAssetCriticalityResources(context, logger);
|
||||
|
||||
const securitySolution = await context.securitySolution;
|
||||
const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient();
|
||||
|
||||
const assetCriticalityRecord = {
|
||||
idField: request.body.id_field,
|
||||
idValue: request.body.id_value,
|
||||
criticalityLevel: request.body.criticality_level,
|
||||
};
|
||||
|
||||
const result = await assetCriticalityClient.upsert(assetCriticalityRecord);
|
||||
|
||||
return response.ok({
|
||||
body: result,
|
||||
});
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
|
||||
return siemResponse.error({
|
||||
statusCode: error.statusCode,
|
||||
body: { message: error.message, full_error: JSON.stringify(e) },
|
||||
bypassErrorFormat: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -66,8 +66,12 @@ import {
|
|||
import { registerTimelineRoutes } from '../lib/timeline/routes';
|
||||
import { riskScoreCalculationRoute } from '../lib/entity_analytics/risk_score/routes/calculation';
|
||||
import { riskScorePreviewRoute } from '../lib/entity_analytics/risk_score/routes/preview';
|
||||
import { assetCriticalityStatusRoute } from '../lib/entity_analytics/asset_criticality/routes';
|
||||
|
||||
import {
|
||||
assetCriticalityStatusRoute,
|
||||
assetCriticalityUpsertRoute,
|
||||
assetCriticalityGetRoute,
|
||||
assetCriticalityDeleteRoute,
|
||||
} from '../lib/entity_analytics/asset_criticality/routes';
|
||||
export const initRoutes = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
config: ConfigType,
|
||||
|
@ -161,5 +165,8 @@ export const initRoutes = (
|
|||
}
|
||||
if (config.experimentalFeatures.entityAnalyticsAssetCriticalityEnabled) {
|
||||
assetCriticalityStatusRoute(router, logger);
|
||||
assetCriticalityUpsertRoute(router, logger);
|
||||
assetCriticalityGetRoute(router, logger);
|
||||
assetCriticalityDeleteRoute(router, logger);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -10,6 +10,8 @@ import {
|
|||
cleanRiskEngine,
|
||||
cleanAssetCriticality,
|
||||
assetCriticalityRouteHelpersFactory,
|
||||
getAssetCriticalityDoc,
|
||||
getAssetCriticalityIndex,
|
||||
} from '../../utils';
|
||||
import { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
|
@ -33,13 +35,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
describe('initialisation of resources', () => {
|
||||
it('should has index installed on status api call', async () => {
|
||||
const assetCriticalityIndex = '.asset-criticality.asset-criticality-default';
|
||||
|
||||
let assetCriticalityIndexExist;
|
||||
|
||||
try {
|
||||
assetCriticalityIndexExist = await es.indices.exists({
|
||||
index: assetCriticalityIndex,
|
||||
index: getAssetCriticalityIndex(),
|
||||
});
|
||||
} catch (e) {
|
||||
assetCriticalityIndexExist = false;
|
||||
|
@ -54,7 +54,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
const assetCriticalityIndexResult = await es.indices.get({
|
||||
index: assetCriticalityIndex,
|
||||
index: getAssetCriticalityIndex(),
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -81,5 +81,125 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should correctly create asset criticality', async () => {
|
||||
const assetCriticality = {
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-01',
|
||||
criticality_level: 'important',
|
||||
};
|
||||
|
||||
const { body: result } = await assetCriticalityRoutes.upsert(assetCriticality);
|
||||
|
||||
expect(result.id_field).to.eql('host.name');
|
||||
expect(result.id_value).to.eql('host-01');
|
||||
expect(result.criticality_level).to.eql('important');
|
||||
expect(result['@timestamp']).to.be.a('string');
|
||||
|
||||
const doc = await getAssetCriticalityDoc({ idField: 'host.name', idValue: 'host-01', es });
|
||||
|
||||
expect(doc).to.eql(result);
|
||||
});
|
||||
|
||||
it('should return 400 if criticality is invalid', async () => {
|
||||
const assetCriticality = {
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-01',
|
||||
criticality_level: 'invalid',
|
||||
};
|
||||
|
||||
await assetCriticalityRoutes.upsert(assetCriticality, {
|
||||
expectStatusCode: 400,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 if id_field is invalid', async () => {
|
||||
const assetCriticality = {
|
||||
id_field: 'invalid',
|
||||
id_value: 'host-01',
|
||||
criticality_level: 'important',
|
||||
};
|
||||
|
||||
await assetCriticalityRoutes.upsert(assetCriticality, {
|
||||
expectStatusCode: 400,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('read', () => {
|
||||
it('should correctly get asset criticality', async () => {
|
||||
const assetCriticality = {
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-02',
|
||||
criticality_level: 'important',
|
||||
};
|
||||
|
||||
await assetCriticalityRoutes.upsert(assetCriticality);
|
||||
|
||||
const { body: result } = await assetCriticalityRoutes.get('host.name', 'host-02');
|
||||
|
||||
expect(result.id_field).to.eql('host.name');
|
||||
expect(result.id_value).to.eql('host-02');
|
||||
expect(result.criticality_level).to.eql('important');
|
||||
expect(result['@timestamp']).to.be.a('string');
|
||||
});
|
||||
|
||||
it('should return a 400 if id_field is invalid', async () => {
|
||||
await assetCriticalityRoutes.get('invalid', 'host-02', {
|
||||
expectStatusCode: 400,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should correctly update asset criticality', async () => {
|
||||
const assetCriticality = {
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-01',
|
||||
criticality_level: 'important',
|
||||
};
|
||||
|
||||
const { body: createdDoc } = await assetCriticalityRoutes.upsert(assetCriticality);
|
||||
const updatedAssetCriticality = {
|
||||
id_field: 'host.name',
|
||||
id_value: 'host-01',
|
||||
criticality_level: 'very_important',
|
||||
};
|
||||
|
||||
const { body: updatedDoc } = await assetCriticalityRoutes.upsert(updatedAssetCriticality);
|
||||
|
||||
expect(updatedDoc.id_field).to.eql('host.name');
|
||||
expect(updatedDoc.id_value).to.eql('host-01');
|
||||
expect(updatedDoc.criticality_level).to.eql('very_important');
|
||||
expect(updatedDoc['@timestamp']).to.be.a('string');
|
||||
expect(updatedDoc['@timestamp']).to.not.eql(createdDoc['@timestamp']);
|
||||
|
||||
const doc = await getAssetCriticalityDoc({ idField: 'host.name', idValue: 'host-01', es });
|
||||
|
||||
expect(doc).to.eql(updatedDoc);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should correctly delete asset criticality', async () => {
|
||||
const assetCriticality = {
|
||||
id_field: 'host.name',
|
||||
id_value: 'delete-me',
|
||||
criticality_level: 'important',
|
||||
};
|
||||
|
||||
await assetCriticalityRoutes.upsert(assetCriticality);
|
||||
|
||||
await assetCriticalityRoutes.delete('host.name', 'delete-me');
|
||||
const doc = await getAssetCriticalityDoc({
|
||||
idField: 'host.name',
|
||||
idValue: 'delete-me',
|
||||
es,
|
||||
});
|
||||
|
||||
expect(doc).to.eql(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -10,11 +10,18 @@ import {
|
|||
ELASTIC_HTTP_VERSION_HEADER,
|
||||
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
|
||||
} from '@kbn/core-http-common';
|
||||
import { ASSET_CRITICALITY_STATUS_URL } from '@kbn/security-solution-plugin/common/constants';
|
||||
import {
|
||||
ASSET_CRITICALITY_STATUS_URL,
|
||||
ASSET_CRITICALITY_URL,
|
||||
} from '@kbn/security-solution-plugin/common/constants';
|
||||
import type { Client } from '@elastic/elasticsearch';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import querystring from 'querystring';
|
||||
import { routeWithNamespace } from '../../detections_response/utils';
|
||||
|
||||
export const getAssetCriticalityIndex = (namespace?: string) =>
|
||||
`.asset-criticality.asset-criticality-${namespace ?? 'default'}`;
|
||||
|
||||
export const cleanAssetCriticality = async ({
|
||||
log,
|
||||
es,
|
||||
|
@ -27,7 +34,7 @@ export const cleanAssetCriticality = async ({
|
|||
try {
|
||||
await Promise.allSettled([
|
||||
es.indices.delete({
|
||||
index: [`.asset-criticality.asset-criticality-${namespace}`],
|
||||
index: [getAssetCriticalityIndex(namespace)],
|
||||
}),
|
||||
]);
|
||||
} catch (e) {
|
||||
|
@ -35,6 +42,24 @@ export const cleanAssetCriticality = async ({
|
|||
}
|
||||
};
|
||||
|
||||
export const getAssetCriticalityDoc = async (opts: {
|
||||
es: Client;
|
||||
idField: string;
|
||||
idValue: string;
|
||||
}) => {
|
||||
const { es, idField, idValue } = opts;
|
||||
try {
|
||||
const doc = await es.get({
|
||||
index: getAssetCriticalityIndex(),
|
||||
id: `${idField}:${idValue}`,
|
||||
});
|
||||
|
||||
return doc._source;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const assetCriticalityRouteHelpersFactory = (
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>,
|
||||
namespace?: string
|
||||
|
@ -47,4 +72,39 @@ export const assetCriticalityRouteHelpersFactory = (
|
|||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.send()
|
||||
.expect(200),
|
||||
upsert: async (
|
||||
body: Record<string, unknown>,
|
||||
{ expectStatusCode }: { expectStatusCode: number } = { expectStatusCode: 200 }
|
||||
) =>
|
||||
await supertest
|
||||
.post(routeWithNamespace(ASSET_CRITICALITY_URL, namespace))
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.send(body)
|
||||
.expect(expectStatusCode),
|
||||
delete: async (idField: string, idValue: string) => {
|
||||
const qs = querystring.stringify({ id_field: idField, id_value: idValue });
|
||||
const route = `${routeWithNamespace(ASSET_CRITICALITY_URL, namespace)}?${qs}`;
|
||||
return supertest
|
||||
.delete(route)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.expect(200);
|
||||
},
|
||||
get: async (
|
||||
idField: string,
|
||||
idValue: string,
|
||||
{ expectStatusCode }: { expectStatusCode: number } = { expectStatusCode: 200 }
|
||||
) => {
|
||||
const qs = querystring.stringify({ id_field: idField, id_value: idValue });
|
||||
const route = `${routeWithNamespace(ASSET_CRITICALITY_URL, namespace)}?${qs}`;
|
||||
return supertest
|
||||
.get(route)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||
.expect(expectStatusCode);
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue