[Security Solution] Introduce method to clean up/delete Risk Scoring resources (#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
This commit is contained in:
Pablo Machado 2024-06-04 11:56:45 +02:00 committed by GitHub
parent a0096cef0c
commit aa95692deb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 152 additions and 0 deletions

View file

@ -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]);
});
});
}); });
} }
}); });

View file

@ -17,6 +17,7 @@ import {
getConfiguration, getConfiguration,
initSavedObjects, initSavedObjects,
getEnabledRiskEngineAmount, getEnabledRiskEngineAmount,
deleteSavedObjects,
} from './utils/saved_object_configuration'; } from './utils/saved_object_configuration';
import { bulkDeleteSavedObjects } from '../../risk_score/prebuilt_saved_objects/helpers/bulk_delete_saved_objects'; import { bulkDeleteSavedObjects } from '../../risk_score/prebuilt_saved_objects/helpers/bulk_delete_saved_objects';
import type { RiskScoreDataClient } from '../risk_score/risk_score_data_client'; import type { RiskScoreDataClient } from '../risk_score/risk_score_data_client';
@ -30,6 +31,11 @@ interface InitOpts {
riskScoreDataClient: RiskScoreDataClient; riskScoreDataClient: RiskScoreDataClient;
} }
interface TearDownParams {
taskManager: TaskManagerStartContract;
riskScoreDataClient: RiskScoreDataClient;
}
interface RiskEngineDataClientOpts { interface RiskEngineDataClientOpts {
logger: Logger; logger: Logger;
kibanaVersion: string; kibanaVersion: string;
@ -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 }) { public async disableLegacyRiskEngine({ namespace }: { namespace: string }) {
const legacyRiskEngineStatus = await this.getLegacyStatus({ namespace }); const legacyRiskEngineStatus = await this.getLegacyStatus({ namespace });

View file

@ -95,6 +95,15 @@ export const initSavedObjects = async ({
return result; 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 ({ export const getConfiguration = async ({
savedObjectsClient, savedObjectsClient,
}: SavedObjectsClientArg): Promise<RiskEngineConfiguration | null> => { }: SavedObjectsClientArg): Promise<RiskEngineConfiguration | null> => {

View file

@ -11,6 +11,7 @@ const createRiskScoreDataClientMock = () =>
({ ({
getWriter: jest.fn().mockResolvedValue({ bulk: jest.fn().mockResolvedValue({ errors: [] }) }), getWriter: jest.fn().mockResolvedValue({ bulk: jest.fn().mockResolvedValue({ errors: [] }) }),
init: jest.fn(), init: jest.fn(),
tearDown: jest.fn(),
getRiskInputsIndex: jest.fn(), getRiskInputsIndex: jest.fn(),
upgradeIfNeeded: jest.fn(), upgradeIfNeeded: jest.fn(),
} as unknown as jest.Mocked<RiskScoreDataClient>); } as unknown as jest.Mocked<RiskScoreDataClient>);

View file

@ -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]);
});
});
}); });

View file

@ -179,6 +179,49 @@ export class RiskScoreDataClient {
throw error; 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. * Ensures that configuration migrations for risk score indices are seamlessly handled across Kibana upgrades.
* *