Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cases] Case action: Integration tests #178277

Merged
merged 27 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f64b523
Refactor so registration
cnasikas Mar 8, 2024
7d07c9e
Use custom SO client to have access to hidden SOs
cnasikas Mar 8, 2024
1a1c9b7
Configure the case action as system action
cnasikas Mar 8, 2024
81fd651
Do not execute if there are no alerts
cnasikas Mar 8, 2024
095c2ec
Draft
cnasikas Mar 8, 2024
746ac55
Handle different type of errors returned by the SO client
cnasikas Mar 8, 2024
32b633c
Fix types
cnasikas Mar 8, 2024
acc29cf
Merge branch 'fix_so_errors' into ca_integration_tests
cnasikas Mar 8, 2024
7a0a4e9
Handle decorated errors
cnasikas Mar 8, 2024
6431727
Fix bug with schema
cnasikas Mar 8, 2024
68fc281
Add integration tests
cnasikas Mar 8, 2024
3d38566
Merge branch 'case_action' into ca_integration_tests
cnasikas Mar 11, 2024
1b6645e
Add more tests
cnasikas Mar 11, 2024
0904ec5
Support Boom errors
cnasikas Mar 11, 2024
da51642
Define cases required privileges
cnasikas Mar 11, 2024
24b6842
Disable security plugin in the saved object client
cnasikas Mar 11, 2024
22a6f3c
Add RBAC tests
cnasikas Mar 11, 2024
c017761
Add validation tests
cnasikas Mar 11, 2024
3454bbc
[CI] Auto-commit changed files from 'node scripts/check_mappings_upda…
kibanamachine Mar 11, 2024
fc61f18
Fix tests
cnasikas Mar 12, 2024
ef73a01
Merge branch 'ca_integration_tests' of github.com:cnasikas/kibana int…
cnasikas Mar 12, 2024
f6a7cf7
Test complex workflows
cnasikas Mar 13, 2024
4cc1427
Merge branch 'case_action' into ca_integration_tests
cnasikas Mar 14, 2024
8f80dc0
Add all alerts to the same attachment
cnasikas Mar 14, 2024
3553ce9
Add more tests
cnasikas Mar 14, 2024
ad2610e
Fix snapshots
cnasikas Mar 16, 2024
014fbf5
PR feedback
cnasikas Mar 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 250 additions & 55 deletions packages/kbn-check-mappings-update-cli/current_fields.json

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions packages/kbn-check-mappings-update-cli/current_mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,34 @@
}
}
},
"cases-oracle": {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR is going to be merged into a feature branch. There is no need for a thorough review at the moment.

"dynamic": false,
"properties": {
"cases": {
"properties": {
"id": {
"type": "keyword"
}
}
},
"counter": {
"type": "unsigned_long"
},
"createdAt": {
"type": "date"
},
"rules": {
"properties": {
"id": {
"type": "keyword"
}
}
},
"updatedAt": {
"type": "date"
}
}
},
"cases-telemetry": {
"dynamic": false,
"properties": {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ export const connectorTypes: string[] = [
'.bedrock',
'.d3security',
'.sentinelone',
'.cases',
];
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import Boom from '@hapi/boom';
import { actionsMock } from '@kbn/actions-plugin/server/mocks';
import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
Expand Down Expand Up @@ -45,14 +46,15 @@ describe('CasesConnector', () => {
const mockExecute = jest.fn();
const getCasesClient = jest.fn().mockResolvedValue({ foo: 'bar' });
const getSpaceId = jest.fn().mockReturnValue('default');
const getUnsecuredSavedObjectsClient = jest.fn();
// 1ms delay before retrying
const nextBackOff = jest.fn().mockReturnValue(1);

const backOffFactory = {
create: () => ({ nextBackOff }),
};

const casesParams = { getCasesClient, getSpaceId };
const casesParams = { getCasesClient, getSpaceId, getUnsecuredSavedObjectsClient };
const connectorParams = {
configurationUtilities: actionsConfigMock.create(),
config: {},
Expand Down Expand Up @@ -85,7 +87,7 @@ describe('CasesConnector', () => {

it('creates the CasesConnectorExecutor correctly', async () => {
await connector.run({
alerts: [],
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
Expand All @@ -105,7 +107,7 @@ describe('CasesConnector', () => {

it('executes the CasesConnectorExecutor correctly', async () => {
await connector.run({
alerts: [],
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
Expand All @@ -115,7 +117,7 @@ describe('CasesConnector', () => {
});

expect(mockExecute).toBeCalledWith({
alerts: [],
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
Expand All @@ -127,7 +129,7 @@ describe('CasesConnector', () => {

it('creates the cases client correctly', async () => {
await connector.run({
alerts: [],
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
Expand All @@ -144,7 +146,7 @@ describe('CasesConnector', () => {

await expect(() =>
connector.run({
alerts: [],
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
Expand All @@ -164,7 +166,7 @@ describe('CasesConnector', () => {

await expect(() =>
connector.run({
alerts: [],
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
Expand All @@ -184,7 +186,7 @@ describe('CasesConnector', () => {

await expect(() =>
connector.run({
alerts: [],
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
Expand All @@ -199,14 +201,36 @@ describe('CasesConnector', () => {
);
});

it('throws a CasesConnectorError when the executor throws a Boom error', async () => {
mockExecute.mockRejectedValue(
new Boom.Boom('Server error', { statusCode: 403, message: 'my error message' })
);

await expect(() =>
connector.run({
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
timeWindow,
reopenClosedCases,
maximumCasesToOpen,
})
).rejects.toThrowErrorMatchingInlineSnapshot(`"Forbidden: Server error"`);

expect(logger.error.mock.calls[0][0]).toBe(
'[CasesConnector][run] Execution of case connector failed. Message: Forbidden: Server error. Status code: 403'
);
});

it('retries correctly', async () => {
mockExecute
.mockRejectedValueOnce(new CasesConnectorError('Conflict error', 409))
.mockRejectedValueOnce(new CasesConnectorError('ES Unavailable', 503))
.mockResolvedValue({});

await connector.run({
alerts: [],
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
Expand All @@ -227,7 +251,7 @@ describe('CasesConnector', () => {

await expect(() =>
connector.run({
alerts: [],
alerts: [{ _id: 'alert-id-0', _index: 'alert-index-0' }],
groupingBy,
owner,
rule,
Expand All @@ -244,4 +268,21 @@ describe('CasesConnector', () => {
expect(nextBackOff).toBeCalledTimes(0);
expect(mockExecute).toBeCalledTimes(0);
});

it('does not execute with no alerts', async () => {
await connector.run({
alerts: [],
groupingBy,
owner,
rule,
timeWindow,
reopenClosedCases,
maximumCasesToOpen,
});

expect(getCasesClient).not.toBeCalled();
expect(CasesConnectorExecutorMock).not.toBeCalled();
expect(mockExecute).not.toBeCalled();
expect(nextBackOff).not.toBeCalled();
});
});
47 changes: 40 additions & 7 deletions x-pack/plugins/cases/server/connectors/cases/cases_connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
* 2.0.
*/

import Boom from '@hapi/boom';
import type { ServiceParams } from '@kbn/actions-plugin/server';
import { SubActionConnector } from '@kbn/actions-plugin/server';
import type { KibanaRequest } from '@kbn/core-http-server';
import type { SavedObjectsClientContract } from '@kbn/core/server';
import { SAVED_OBJECT_TYPES } from '../../../common';
import { CASES_CONNECTOR_SUB_ACTION } from './constants';
import type { CasesConnectorConfig, CasesConnectorRunParams, CasesConnectorSecrets } from './types';
import { CasesConnectorRunParamsSchema } from './schema';
Expand All @@ -22,32 +25,31 @@ import {
import { CasesConnectorExecutor } from './cases_connector_executor';
import { CaseConnectorRetryService } from './retry_service';
import { fullJitterBackoffFactory } from './full_jitter_backoff';
import { CASE_ORACLE_SAVED_OBJECT } from '../../../common/constants';

interface CasesConnectorParams {
connectorParams: ServiceParams<CasesConnectorConfig, CasesConnectorSecrets>;
casesParams: {
getCasesClient: (request: KibanaRequest) => Promise<CasesClient>;
getSpaceId: (request?: KibanaRequest) => string;
getUnsecuredSavedObjectsClient: (
request: KibanaRequest,
savedObjectTypes: string[]
) => Promise<SavedObjectsClientContract>;
};
}

export class CasesConnector extends SubActionConnector<
CasesConnectorConfig,
CasesConnectorSecrets
> {
private readonly casesOracleService: CasesOracleService;
private readonly casesService: CasesService;
private readonly retryService: CaseConnectorRetryService;
private readonly casesParams: CasesConnectorParams['casesParams'];

constructor({ connectorParams, casesParams }: CasesConnectorParams) {
super(connectorParams);

this.casesOracleService = new CasesOracleService({
logger: this.logger,
savedObjectsClient: this.savedObjectsClient,
});

this.casesService = new CasesService();

/**
Expand Down Expand Up @@ -84,6 +86,16 @@ export class CasesConnector extends SubActionConnector<
this.handleError(error);
}

if (params.alerts.length === 0) {
this.logDebugCurrentState(
'start',
'[CasesConnector][_run] No alerts. Skipping execution.',
params
);

return;
}

await this.retryService.retryWithBackoff(() => this._run(params));
}

Expand All @@ -95,11 +107,21 @@ export class CasesConnector extends SubActionConnector<
*/
const kibanaRequest = this.kibanaRequest as KibanaRequest;
const casesClient = await this.casesParams.getCasesClient(kibanaRequest);
const savedObjectsClient = await this.casesParams.getUnsecuredSavedObjectsClient(
kibanaRequest,
[...SAVED_OBJECT_TYPES, CASE_ORACLE_SAVED_OBJECT]
);

const spaceId = this.casesParams.getSpaceId(kibanaRequest);

const casesOracleService = new CasesOracleService({
logger: this.logger,
savedObjectsClient,
});

const connectorExecutor = new CasesConnectorExecutor({
logger: this.logger,
casesOracleService: this.casesOracleService,
casesOracleService,
casesService: this.casesService,
casesClient,
spaceId,
Expand Down Expand Up @@ -141,6 +163,17 @@ export class CasesConnector extends SubActionConnector<
throw caseConnectorError;
}

if (Boom.isBoom(error)) {
const caseConnectorError = new CasesConnectorError(
`${error.output.payload.error}: ${error.output.payload.message}`,
error.output.statusCode
);

this.logError(caseConnectorError);

throw caseConnectorError;
}

const caseConnectorError = new CasesConnectorError(error.message, 500);
this.logError(caseConnectorError);

Expand Down
Loading