[Security Solution][Entity Analytics] API Versioning for new EA Routes (#167365)

## Summary

* Adds API versioning to all routes involved in new Risk Engine, public
and private
* Adds missing PLI auth headers for some routes
* Updates API invocations to specify an appropriate version header
* Does NOT add header to legacy transform-based EA routes




### Checklist
- [x] Verify no API calls from the UI were missed

- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [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
This commit is contained in:
Ryland Herrick 2023-09-28 11:30:27 -05:00 committed by GitHub
parent 79c42be00c
commit 2edc13c2c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 163 additions and 139 deletions

View file

@ -34,6 +34,7 @@ export const fetchRiskScorePreview = async ({
params: RiskScorePreviewRequestSchema;
}): Promise<CalculateScoresResponse> => {
return KibanaServices.get().http.fetch<CalculateScoresResponse>(RISK_SCORE_PREVIEW_URL, {
version: '1',
method: 'POST',
body: JSON.stringify(params),
signal,
@ -49,6 +50,7 @@ export const fetchRiskEngineStatus = async ({
signal?: AbortSignal;
}): Promise<GetRiskEngineStatusResponse> => {
return KibanaServices.get().http.fetch<GetRiskEngineStatusResponse>(RISK_ENGINE_STATUS_URL, {
version: '1',
method: 'GET',
signal,
});
@ -59,6 +61,7 @@ export const fetchRiskEngineStatus = async ({
*/
export const initRiskEngine = async (): Promise<InitRiskEngineResponse> => {
return KibanaServices.get().http.fetch<InitRiskEngineResponse>(RISK_ENGINE_INIT_URL, {
version: '1',
method: 'POST',
});
};
@ -68,6 +71,7 @@ export const initRiskEngine = async (): Promise<InitRiskEngineResponse> => {
*/
export const enableRiskEngine = async (): Promise<EnableRiskEngineResponse> => {
return KibanaServices.get().http.fetch<EnableRiskEngineResponse>(RISK_ENGINE_ENABLE_URL, {
version: '1',
method: 'POST',
});
};
@ -77,6 +81,7 @@ export const enableRiskEngine = async (): Promise<EnableRiskEngineResponse> => {
*/
export const disableRiskEngine = async (): Promise<DisableRiskEngineResponse> => {
return KibanaServices.get().http.fetch<DisableRiskEngineResponse>(RISK_ENGINE_DISABLE_URL, {
version: '1',
method: 'POST',
});
};

View file

@ -16,15 +16,15 @@ export const riskEngineDisableRoute = (
router: SecuritySolutionPluginRouter,
getStartServices: StartServicesAccessor<StartPlugins>
) => {
router.post(
{
router.versioned
.post({
access: 'internal',
path: RISK_ENGINE_DISABLE_URL,
validate: {},
options: {
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
},
},
async (context, request, response) => {
})
.addVersion({ version: '1', validate: {} }, async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const [_, { taskManager }] = await getStartServices();
@ -52,6 +52,5 @@ export const riskEngineDisableRoute = (
body: { message: error.message, full_error: JSON.stringify(e) },
});
}
}
);
});
};

View file

@ -16,15 +16,15 @@ export const riskEngineEnableRoute = (
router: SecuritySolutionPluginRouter,
getStartServices: StartServicesAccessor<StartPlugins>
) => {
router.post(
{
router.versioned
.post({
access: 'internal',
path: RISK_ENGINE_ENABLE_URL,
validate: {},
options: {
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
},
},
async (context, request, response) => {
})
.addVersion({ version: '1', validate: {} }, async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const [_, { taskManager }] = await getStartServices();
const securitySolution = await context.securitySolution;
@ -51,6 +51,5 @@ export const riskEngineEnableRoute = (
body: { message: error.message, full_error: JSON.stringify(e) },
});
}
}
);
});
};

View file

@ -17,15 +17,15 @@ export const riskEngineInitRoute = (
router: SecuritySolutionPluginRouter,
getStartServices: StartServicesAccessor<StartPlugins>
) => {
router.post(
{
router.versioned
.post({
access: 'internal',
path: RISK_ENGINE_INIT_URL,
validate: {},
options: {
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
},
},
async (context, request, response) => {
})
.addVersion({ version: '1', validate: {} }, async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const securitySolution = await context.securitySolution;
const [_, { taskManager }] = await getStartServices();
@ -78,6 +78,5 @@ export const riskEngineInitRoute = (
body: { message: error.message, full_error: JSON.stringify(e) },
});
}
}
);
});
};

View file

@ -12,15 +12,15 @@ import { RISK_ENGINE_STATUS_URL, APP_ID } from '../../../../common/constants';
import type { SecuritySolutionPluginRouter } from '../../../types';
export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter) => {
router.get(
{
router.versioned
.get({
access: 'internal',
path: RISK_ENGINE_STATUS_URL,
validate: {},
options: {
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
},
},
async (context, request, response) => {
})
.addVersion({ version: '1', validate: {} }, async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const securitySolution = await context.securitySolution;
@ -46,6 +46,5 @@ export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter) => {
body: { message: error.message, full_error: JSON.stringify(e) },
});
}
}
);
});
};

View file

@ -9,6 +9,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 {
APP_ID,
DEFAULT_RISK_SCORE_PAGE_SIZE,
RISK_SCORE_CALCULATION_URL,
} from '../../../../common/constants';
@ -19,72 +20,77 @@ import { riskScoreServiceFactory } from '../risk_score_service';
import { getRiskInputsIndex } from '../get_risk_inputs_index';
export const riskScoreCalculationRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => {
router.post(
{
router.versioned
.post({
path: RISK_SCORE_CALCULATION_URL,
validate: { body: buildRouteValidation(riskScoreCalculationRequestSchema) },
access: 'public',
options: {
tags: ['access:securitySolution'],
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const securityContext = await context.securitySolution;
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asCurrentUser;
const soClient = coreContext.savedObjects.client;
const spaceId = securityContext.getSpaceId();
const riskEngineDataClient = securityContext.getRiskEngineDataClient();
})
.addVersion(
{
version: '2023-10-31',
validate: { request: { body: buildRouteValidation(riskScoreCalculationRequestSchema) } },
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const securityContext = await context.securitySolution;
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asCurrentUser;
const soClient = coreContext.savedObjects.client;
const spaceId = securityContext.getSpaceId();
const riskEngineDataClient = securityContext.getRiskEngineDataClient();
const riskScoreService = riskScoreServiceFactory({
esClient,
logger,
riskEngineDataClient,
spaceId,
});
const {
after_keys: userAfterKeys,
data_view_id: dataViewId,
debug,
page_size: userPageSize,
identifier_type: identifierType,
filter,
range,
weights,
} = request.body;
try {
const { index, runtimeMappings } = await getRiskInputsIndex({
dataViewId,
const riskScoreService = riskScoreServiceFactory({
esClient,
logger,
soClient,
riskEngineDataClient,
spaceId,
});
const afterKeys = userAfterKeys ?? {};
const pageSize = userPageSize ?? DEFAULT_RISK_SCORE_PAGE_SIZE;
const result = await riskScoreService.calculateAndPersistScores({
afterKeys,
const {
after_keys: userAfterKeys,
data_view_id: dataViewId,
debug,
pageSize,
identifierType,
index,
page_size: userPageSize,
identifier_type: identifierType,
filter,
range,
runtimeMappings,
weights,
});
} = request.body;
return response.ok({ body: result });
} catch (e) {
const error = transformError(e);
try {
const { index, runtimeMappings } = await getRiskInputsIndex({
dataViewId,
logger,
soClient,
});
return siemResponse.error({
statusCode: error.statusCode,
body: { message: error.message, full_error: JSON.stringify(e) },
});
const afterKeys = userAfterKeys ?? {};
const pageSize = userPageSize ?? DEFAULT_RISK_SCORE_PAGE_SIZE;
const result = await riskScoreService.calculateAndPersistScores({
afterKeys,
debug,
pageSize,
identifierType,
index,
filter,
range,
runtimeMappings,
weights,
});
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) },
});
}
}
}
);
);
};

View file

@ -9,7 +9,11 @@ import type { Logger } from '@kbn/core/server';
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
import { transformError } from '@kbn/securitysolution-es-utils';
import { DEFAULT_RISK_SCORE_PAGE_SIZE, RISK_SCORE_PREVIEW_URL } from '../../../../common/constants';
import {
APP_ID,
DEFAULT_RISK_SCORE_PAGE_SIZE,
RISK_SCORE_PREVIEW_URL,
} from '../../../../common/constants';
import { riskScorePreviewRequestSchema } from '../../../../common/risk_engine/risk_score_preview/request_schema';
import type { SecuritySolutionPluginRouter } from '../../../types';
import { buildRouteValidation } from '../../../utils/build_validation/route_validation';
@ -17,73 +21,78 @@ import { riskScoreServiceFactory } from '../risk_score_service';
import { getRiskInputsIndex } from '../get_risk_inputs_index';
export const riskScorePreviewRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => {
router.post(
{
router.versioned
.post({
access: 'internal',
path: RISK_SCORE_PREVIEW_URL,
validate: { body: buildRouteValidation(riskScorePreviewRequestSchema) },
options: {
tags: ['access:securitySolution'],
tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`],
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const securityContext = await context.securitySolution;
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asCurrentUser;
const soClient = coreContext.savedObjects.client;
const spaceId = securityContext.getSpaceId();
const riskEngineDataClient = securityContext.getRiskEngineDataClient();
})
.addVersion(
{
version: '1',
validate: { request: { body: buildRouteValidation(riskScorePreviewRequestSchema) } },
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const securityContext = await context.securitySolution;
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asCurrentUser;
const soClient = coreContext.savedObjects.client;
const spaceId = securityContext.getSpaceId();
const riskEngineDataClient = securityContext.getRiskEngineDataClient();
const riskScoreService = riskScoreServiceFactory({
esClient,
logger,
riskEngineDataClient,
spaceId,
});
const {
after_keys: userAfterKeys,
data_view_id: dataViewId,
debug,
page_size: userPageSize,
identifier_type: identifierType,
filter,
range: userRange,
weights,
} = request.body;
try {
const { index, runtimeMappings } = await getRiskInputsIndex({
dataViewId,
const riskScoreService = riskScoreServiceFactory({
esClient,
logger,
soClient,
riskEngineDataClient,
spaceId,
});
const afterKeys = userAfterKeys ?? {};
const range = userRange ?? { start: 'now-15d', end: 'now' };
const pageSize = userPageSize ?? DEFAULT_RISK_SCORE_PAGE_SIZE;
const result = await riskScoreService.calculateScores({
afterKeys,
const {
after_keys: userAfterKeys,
data_view_id: dataViewId,
debug,
page_size: userPageSize,
identifier_type: identifierType,
filter,
identifierType,
index,
pageSize,
range,
runtimeMappings,
range: userRange,
weights,
});
} = request.body;
return response.ok({ body: result });
} catch (e) {
const error = transformError(e);
try {
const { index, runtimeMappings } = await getRiskInputsIndex({
dataViewId,
logger,
soClient,
});
return siemResponse.error({
statusCode: error.statusCode,
body: { message: error.message, full_error: JSON.stringify(e) },
});
const afterKeys = userAfterKeys ?? {};
const range = userRange ?? { start: 'now-15d', end: 'now' };
const pageSize = userPageSize ?? DEFAULT_RISK_SCORE_PAGE_SIZE;
const result = await riskScoreService.calculateScores({
afterKeys,
debug,
filter,
identifierType,
index,
pageSize,
range,
runtimeMappings,
weights,
});
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) },
});
}
}
}
);
);
};

View file

@ -38,6 +38,7 @@ export default ({ getService }: FtrProviderContext): void => {
const { body: result } = await supertest
.post(RISK_SCORE_CALCULATION_URL)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '2023-10-31')
.send(body)
.expect(200);
return result;

View file

@ -36,6 +36,7 @@ export default ({ getService }: FtrProviderContext): void => {
const defaultBody = { data_view_id: '.alerts-security.alerts-default' };
const { body: result } = await supertest
.post(RISK_SCORE_PREVIEW_URL)
.set('elastic-api-version', '1')
.set('kbn-xsrf', 'true')
.send({ ...defaultBody, ...body })
.expect(200);

View file

@ -427,6 +427,7 @@ export const riskEngineRouteHelpersFactory = (
await supertest
.post(routeWithNamespace(RISK_ENGINE_INIT_URL, namespace))
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200),
@ -434,6 +435,7 @@ export const riskEngineRouteHelpersFactory = (
await supertest
.get(routeWithNamespace(RISK_ENGINE_STATUS_URL, namespace))
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200),
@ -441,6 +443,7 @@ export const riskEngineRouteHelpersFactory = (
await supertest
.post(routeWithNamespace(RISK_ENGINE_ENABLE_URL, namespace))
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200),
@ -448,6 +451,7 @@ export const riskEngineRouteHelpersFactory = (
await supertest
.post(routeWithNamespace(RISK_ENGINE_DISABLE_URL, namespace))
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200),
});
@ -460,12 +464,14 @@ export const installLegacyRiskScore = async ({
await supertest
.post('/internal/risk_score')
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send({ riskScoreEntity: 'host' })
.expect(200);
await supertest
.post('/internal/risk_score')
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send({ riskScoreEntity: 'user' })
.expect(200);