Skip to content

Commit

Permalink
[Entity Analytics] Implement Asset Criticality Create, Read & Delete …
Browse files Browse the repository at this point in the history
…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>
  • Loading branch information
hop-dev and kibanamachine authored Dec 1, 2023
1 parent 823552f commit 991b5f6
Show file tree
Hide file tree
Showing 18 changed files with 659 additions and 15 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 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(),
})
);
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 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(),
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ paths:
$ref: '#/components/schemas/AssetCriticalityStatusResponse'
'400':
description: Invalid request
responses:

components:
schemas:
AssetCriticalityStatusResponse:
type: object
properties:
asset_criticality_resources_installed:
type: boolean
type: boolean
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {}
/**
Expand All @@ -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) {
Expand All @@ -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(),
});
}
}
Original file line number Diff line number Diff line change
@@ -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,
});
}
}
);
};
Loading

0 comments on commit 991b5f6

Please sign in to comment.