Skip to content

Commit

Permalink
[Security Solution] Introduce method to clean up/delete Risk Scoring …
Browse files Browse the repository at this point in the history
…resources (elastic#184509)

## Summary

Create a method that deletes all resources created by the risk engine.
The method returns a list of errors and an empty array for the success
scenario.

Signature:
```js
const errors = await riskEngineDataClient.tearDown({
  taskManager,
  riskScoreDataClient,
});
```

# How to test it?
1. Enable the risk engine
2. Call the method
3. It should return an empty array

-----
1. On a clean instance
2. Call the method
4. It should return a list of errors

### Checklist


- [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
  • Loading branch information
machadoum authored and pull[bot] committed Jun 10, 2024
1 parent 29230f1 commit 8d9b7bc
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,51 @@ describe('RiskEngineDataClient', () => {
});
});
});

describe('tearDownRiskEngine', () => {
const mockTaskManagerStart = taskManagerMock.createStart();

it('should delete the risk engine object and task if it exists', async () => {
mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration());
const riskScoreDataClient = riskScoreDataClientMock.create();
await riskEngineDataClient.tearDown({
taskManager: mockTaskManagerStart,
riskScoreDataClient,
});

expect(mockSavedObjectClient.delete).toHaveBeenCalledTimes(1);
expect(mockTaskManagerStart.remove).toHaveBeenCalledTimes(1);
expect(riskScoreDataClient.tearDown).toHaveBeenCalledTimes(1);
});

it('should return errors when exception is thrown ', async () => {
const error = new Error('testError');
mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration());
mockTaskManagerStart.remove.mockRejectedValueOnce(error);
mockSavedObjectClient.delete.mockRejectedValueOnce(error);

const errors = await riskEngineDataClient.tearDown({
taskManager: mockTaskManagerStart,
riskScoreDataClient: riskScoreDataClientMock.create(),
});

await expect(errors).toEqual([error, error]);
});

it('should return errors from riskScoreDataClient.tearDown ', async () => {
const error = new Error('testError');
mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration());
const riskScoreDataClient = riskScoreDataClientMock.create();
riskScoreDataClient.tearDown.mockResolvedValueOnce([error]);

const errors = await riskEngineDataClient.tearDown({
taskManager: mockTaskManagerStart,
riskScoreDataClient,
});

await expect(errors).toEqual([error]);
});
});
});
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getConfiguration,
initSavedObjects,
getEnabledRiskEngineAmount,
deleteSavedObjects,
} from './utils/saved_object_configuration';
import { bulkDeleteSavedObjects } from '../../risk_score/prebuilt_saved_objects/helpers/bulk_delete_saved_objects';
import type { RiskScoreDataClient } from '../risk_score/risk_score_data_client';
Expand All @@ -30,6 +31,11 @@ interface InitOpts {
riskScoreDataClient: RiskScoreDataClient;
}

interface TearDownParams {
taskManager: TaskManagerStartContract;
riskScoreDataClient: RiskScoreDataClient;
}

interface RiskEngineDataClientOpts {
logger: Logger;
kibanaVersion: string;
Expand Down Expand Up @@ -193,6 +199,29 @@ export class RiskEngineDataClient {
});
}

/**
* Delete all risk engine resources.
*
* It returns an array of errors that occurred during the deletion.
*
* WARNING: It will remove all data.
*/
public async tearDown({ taskManager, riskScoreDataClient }: TearDownParams) {
const errors: Error[] = [];
const addError = (e: Error) => errors.push(e);

await removeRiskScoringTask({
namespace: this.options.namespace,
taskManager,
logger: this.options.logger,
}).catch(addError);

await deleteSavedObjects({ savedObjectsClient: this.options.soClient }).catch(addError);
const riskScoreErrors = await riskScoreDataClient.tearDown();

return errors.concat(riskScoreErrors);
}

public async disableLegacyRiskEngine({ namespace }: { namespace: string }) {
const legacyRiskEngineStatus = await this.getLegacyStatus({ namespace });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ export const initSavedObjects = async ({
return result;
};

export const deleteSavedObjects = async ({
savedObjectsClient,
}: SavedObjectsClientArg): Promise<void> => {
const configuration = await getConfigurationSavedObject({ savedObjectsClient });
if (configuration) {
await savedObjectsClient.delete(riskEngineConfigurationTypeName, configuration.id);
}
};

export const getConfiguration = async ({
savedObjectsClient,
}: SavedObjectsClientArg): Promise<RiskEngineConfiguration | null> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const createRiskScoreDataClientMock = () =>
({
getWriter: jest.fn().mockResolvedValue({ bulk: jest.fn().mockResolvedValue({ errors: [] }) }),
init: jest.fn(),
tearDown: jest.fn(),
getRiskInputsIndex: jest.fn(),
upgradeIfNeeded: jest.fn(),
} as unknown as jest.Mocked<RiskScoreDataClient>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,4 +463,29 @@ describe('RiskScoreDataClient', () => {
);
});
});

describe('tearDown', () => {
it('deletes all resources', async () => {
const errors = await riskScoreDataClient.tearDown();

expect(esClient.transform.deleteTransform).toHaveBeenCalledTimes(1);
expect(esClient.indices.deleteDataStream).toHaveBeenCalledTimes(1);
expect(esClient.indices.deleteIndexTemplate).toHaveBeenCalledTimes(1);
expect(esClient.cluster.deleteComponentTemplate).toHaveBeenCalledTimes(1);
expect(errors).toEqual([]);
});

it('returns errors when promises are rejected', async () => {
const error = new Error('test error');

esClient.transform.deleteTransform.mockRejectedValueOnce(error);
esClient.indices.deleteDataStream.mockRejectedValueOnce(error);
esClient.indices.deleteIndexTemplate.mockRejectedValueOnce(error);
esClient.cluster.deleteComponentTemplate.mockRejectedValueOnce(error);

const errors = await riskScoreDataClient.tearDown();

expect(errors).toEqual([error, error, error, error]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,49 @@ export class RiskScoreDataClient {
throw error;
}
}

/**
* Deletes all resources created by init().
* It returns an array of errors that occurred during the deletion.
*
* WARNING: It will remove all data.
*/
public async tearDown() {
const namespace = this.options.namespace;
const esClient = this.options.esClient;
const indexPatterns = getIndexPatternDataStream(namespace);
const errors: Error[] = [];
const addError = (e: Error) => errors.push(e);

await esClient.transform
.deleteTransform({
transform_id: getLatestTransformId(namespace),
delete_dest_index: true,
force: true,
})
.catch(addError);

await esClient.indices
.deleteDataStream({
name: indexPatterns.alias,
})
.catch(addError);

await esClient.indices
.deleteIndexTemplate({
name: indexPatterns.template,
})
.catch(addError);

await esClient.cluster
.deleteComponentTemplate({
name: mappingComponentName,
})
.catch(addError);

return errors;
}

/**
* Ensures that configuration migrations for risk score indices are seamlessly handled across Kibana upgrades.
*
Expand Down

0 comments on commit 8d9b7bc

Please sign in to comment.