Skip to content

Commit

Permalink
[Entity Analytics] Create public versions of asset criticality get,up…
Browse files Browse the repository at this point in the history
…sert,delete and csv upload APIs (elastic#186169)

## Summary

Adds 4 new public APIs for managing asset criticality. These are public
versions of our existing internal asset criticality APIs:

- Get record `GET /api/asset_criticality?id_field=x?id_value=y`
- Upsert record`POST /api/asset_criticality`
- Delete record `DELETE /api/asset_criticality?id_field=x?id_value=y`
- Bulk CSV Upload `POST /api/asset_criticality/upload_csv`

We will delete the internal versions in the future but for now we keep
both.

I have switched the tests and UI to use the public APIs.

I have also moved our API versions to constants.

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
hop-dev and kibanamachine authored Jun 18, 2024
1 parent 3a2e162 commit 4a95ffb
Show file tree
Hide file tree
Showing 15 changed files with 585 additions and 304 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ paths:
x-labels: [ess, serverless]
x-internal: true
operationId: AssetCriticalityCreateRecord
summary: Deprecated Internal Create Criticality Record
requestBody:
required: true
content:
application/json:
schema:
$ref: './common.schema.yaml#/components/schemas/CreateAssetCriticalityRecord'
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: './common.schema.yaml#/components/schemas/AssetCriticalityRecord'
'400':
description: Invalid request
/api/asset_criticality:
post:
x-labels: [ess, serverless]
operationId: AssetCriticalityCreateRecord
summary: Create Criticality Record
requestBody:
required: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ paths:
x-labels: [ess, serverless]
x-internal: true
operationId: AssetCriticalityDeleteRecord
summary: Deprecated Internal Delete Criticality Record
parameters:
- $ref: './common.schema.yaml#/components/parameters/id_value'
- $ref: './common.schema.yaml#/components/parameters/id_field'
responses:
'200':
description: Successful response
'400':
description: Invalid request
/api/asset_criticality:
delete:
x-labels: [ess, serverless]
operationId: AssetCriticalityDeleteRecord
summary: Delete Criticality Record
parameters:
- $ref: './common.schema.yaml#/components/parameters/id_value'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@ paths:
x-labels: [ess, serverless]
x-internal: true
operationId: AssetCriticalityGetRecord
summary: Deprecated Internal Get Criticality Record
parameters:
- $ref: './common.schema.yaml#/components/parameters/id_value'
- $ref: './common.schema.yaml#/components/parameters/id_field'
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: './common.schema.yaml#/components/schemas/AssetCriticalityRecord'
'400':
description: Invalid request
'404':
description: Criticality record not found
/api/asset_criticality:
get:
x-labels: [ess, serverless]
operationId: AssetCriticalityGetRecord
summary: Get Criticality Record
parameters:
- $ref: './common.schema.yaml#/components/parameters/id_value'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,32 @@ servers:
default: '5601'
paths:
/internal/asset_criticality/upload_csv:
post:
x-labels: [ess, serverless]
x-internal: true
summary: Deprecated internal API which Uploads a CSV file containing asset criticality data
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description: The CSV file to upload.
required:
- file
responses:
'200':
description: CSV upload successful
content:
application/json:
schema:
$ref: '#/components/schemas/AssetCriticalityCsvUploadResponse'
'413':
description: File too large
/api/asset_criticality/upload_csv:
post:
x-labels: [ess, serverless]
x-internal: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@
* 2.0.
*/

export const ASSET_CRITICALITY_URL = `/internal/asset_criticality` as const;
export const ASSET_CRITICALITY_PRIVILEGES_URL = `${ASSET_CRITICALITY_URL}/privileges` as const;
export const ASSET_CRITICALITY_STATUS_URL = `${ASSET_CRITICALITY_URL}/status` as const;
export const ASSET_CRITICALITY_CSV_UPLOAD_URL = `${ASSET_CRITICALITY_URL}/upload_csv` as const;
export const ASSET_CRITICALITY_INTERNAL_URL = `/internal/asset_criticality` as const;
export const ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL =
`${ASSET_CRITICALITY_INTERNAL_URL}/privileges` as const;
export const ASSET_CRITICALITY_INTERNAL_STATUS_URL =
`${ASSET_CRITICALITY_INTERNAL_URL}/status` as const;
export const ASSET_CRITICALITY_INTERNAL_CSV_UPLOAD_URL =
`${ASSET_CRITICALITY_INTERNAL_URL}/upload_csv` as const;

export const ASSET_CRITICALITY_PUBLIC_URL = `/api/asset_criticality` as const;
export const ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL =
`${ASSET_CRITICALITY_PUBLIC_URL}/upload_csv` as const;

export const ASSET_CRITICALITY_INDEX_PATTERN = '.asset-criticality.asset-criticality-*';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@
export * from './asset_criticality/constants';
export * from './risk_engine/constants';
export * from './risk_score/constants';

export const API_VERSIONS = {
public: {
v1: '2023-10-31',
},
internal: {
v1: '1',
},
};
40 changes: 22 additions & 18 deletions x-pack/plugins/security_solution/public/entity_analytics/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ import {
RISK_ENGINE_DISABLE_URL,
RISK_ENGINE_INIT_URL,
RISK_ENGINE_PRIVILEGES_URL,
ASSET_CRITICALITY_PRIVILEGES_URL,
ASSET_CRITICALITY_URL,
ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL,
ASSET_CRITICALITY_PUBLIC_URL,
RISK_SCORE_INDEX_STATUS_API_URL,
RISK_ENGINE_SETTINGS_URL,
ASSET_CRITICALITY_CSV_UPLOAD_URL,
ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL,
RISK_SCORE_ENTITY_CALCULATION_URL,
API_VERSIONS,
} from '../../../common/constants';
import type { RiskEngineSettingsResponse } from '../../../common/api/entity_analytics/risk_engine';
import type { SnakeToCamelCase } from '../common/utils';
Expand Down Expand Up @@ -127,7 +128,7 @@ export const useEntityAnalyticsRoutes = () => {
* Get asset criticality privileges
*/
const fetchAssetCriticalityPrivileges = () =>
http.fetch<EntityAnalyticsPrivileges>(ASSET_CRITICALITY_PRIVILEGES_URL, {
http.fetch<EntityAnalyticsPrivileges>(ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, {
version: '1',
method: 'GET',
});
Expand All @@ -140,8 +141,8 @@ export const useEntityAnalyticsRoutes = () => {
refresh?: 'wait_for';
}
): Promise<AssetCriticalityRecord> =>
http.fetch<AssetCriticalityRecord>(ASSET_CRITICALITY_URL, {
version: '1',
http.fetch<AssetCriticalityRecord>(ASSET_CRITICALITY_PUBLIC_URL, {
version: API_VERSIONS.public.v1,
method: 'POST',
body: JSON.stringify({
id_value: params.idValue,
Expand All @@ -154,8 +155,8 @@ export const useEntityAnalyticsRoutes = () => {
const deleteAssetCriticality = async (
params: Pick<AssetCriticality, 'idField' | 'idValue'> & { refresh?: 'wait_for' }
): Promise<{ deleted: true }> => {
await http.fetch(ASSET_CRITICALITY_URL, {
version: '1',
await http.fetch(ASSET_CRITICALITY_PUBLIC_URL, {
version: API_VERSIONS.public.v1,
method: 'DELETE',
query: { id_value: params.idValue, id_field: params.idField, refresh: params.refresh },
});
Expand All @@ -170,8 +171,8 @@ export const useEntityAnalyticsRoutes = () => {
const fetchAssetCriticality = async (
params: Pick<AssetCriticality, 'idField' | 'idValue'>
): Promise<AssetCriticalityRecord> => {
return http.fetch<AssetCriticalityRecord>(ASSET_CRITICALITY_URL, {
version: '1',
return http.fetch<AssetCriticalityRecord>(ASSET_CRITICALITY_PUBLIC_URL, {
version: API_VERSIONS.public.v1,
method: 'GET',
query: { id_value: params.idValue, id_field: params.idField },
});
Expand All @@ -185,14 +186,17 @@ export const useEntityAnalyticsRoutes = () => {
const body = new FormData();
body.append('file', file);

return http.fetch<AssetCriticalityCsvUploadResponse>(ASSET_CRITICALITY_CSV_UPLOAD_URL, {
version: '1',
method: 'POST',
headers: {
'Content-Type': undefined, // Lets the browser set the appropriate content type
},
body,
});
return http.fetch<AssetCriticalityCsvUploadResponse>(
ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL,
{
version: API_VERSIONS.public.v1,
method: 'POST',
headers: {
'Content-Type': undefined, // Lets the browser set the appropriate content type
},
body,
}
);
};

const getRiskScoreIndexStatus = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
* 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 type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import type { SecuritySolutionRequestHandlerContext } from '../../../../types';
import {
ASSET_CRITICALITY_URL,
ASSET_CRITICALITY_PUBLIC_URL,
ASSET_CRITICALITY_INTERNAL_URL,
APP_ID,
ENABLE_ASSET_CRITICALITY_SETTING,
API_VERSIONS,
} from '../../../../../common/constants';
import { DeleteAssetCriticalityRecord } from '../../../../../common/api/entity_analytics/asset_criticality';
import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation';
Expand All @@ -19,64 +22,101 @@ import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setti
import type { EntityAnalyticsRoutesDeps } from '../../types';
import { AssetCriticalityAuditActions } from '../audit';
import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit';
export const assetCriticalityDeleteRoute = (

type DeleteHandler = (
context: SecuritySolutionRequestHandlerContext,
request: {
query: DeleteAssetCriticalityRecord;
},
response: KibanaResponseFactory
) => Promise<IKibanaResponse>;

const handler: (logger: Logger) => DeleteHandler =
(logger) => async (context, request, response) => {
const securitySolution = await context.securitySolution;

securitySolution.getAuditLogger()?.log({
message: 'User attempted to un-assign asset criticality from an entity',
event: {
action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UNASSIGN,
category: AUDIT_CATEGORY.DATABASE,
type: AUDIT_TYPE.DELETION,
outcome: AUDIT_OUTCOME.UNKNOWN,
},
});

const siemResponse = buildSiemResponse(response);
try {
await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING);
await checkAndInitAssetCriticalityResources(context, logger);

const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient();
await assetCriticalityClient.delete(
{
idField: request.query.id_field,
idValue: request.query.id_value,
},
request.query.refresh
);

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,
});
}
};

export const assetCriticalityInternalDeleteRoute = (
router: EntityAnalyticsRoutesDeps['router'],
logger: Logger
) => {
router.versioned
.delete({
access: 'internal',
path: ASSET_CRITICALITY_URL,
path: ASSET_CRITICALITY_INTERNAL_URL,
options: {
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
},
})
.addVersion(
{
version: '1',
version: API_VERSIONS.internal.v1,
validate: {
request: {
query: buildRouteValidationWithZod(DeleteAssetCriticalityRecord),
},
},
},
async (context, request, response) => {
const securitySolution = await context.securitySolution;
handler(logger)
);
};

securitySolution.getAuditLogger()?.log({
message: 'User attempted to un-assign asset criticality from an entity',
event: {
action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UNASSIGN,
category: AUDIT_CATEGORY.DATABASE,
type: AUDIT_TYPE.DELETION,
outcome: AUDIT_OUTCOME.UNKNOWN,
export const assetCriticalityPublicDeleteRoute = (
router: EntityAnalyticsRoutesDeps['router'],
logger: Logger
) => {
router.versioned
.delete({
access: 'public',
path: ASSET_CRITICALITY_PUBLIC_URL,
options: {
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {
query: buildRouteValidationWithZod(DeleteAssetCriticalityRecord),
},
});

const siemResponse = buildSiemResponse(response);
try {
await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING);
await checkAndInitAssetCriticalityResources(context, logger);

const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient();
await assetCriticalityClient.delete(
{
idField: request.query.id_field,
idValue: request.query.id_value,
},
request.query.refresh
);

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,
});
}
}
},
},
handler(logger)
);
};
Loading

0 comments on commit 4a95ffb

Please sign in to comment.