Skip to content

Commit

Permalink
[Defend workflows] Add e2e tests to Automated response actions (elast…
Browse files Browse the repository at this point in the history
  • Loading branch information
tomsonpl authored Jun 27, 2023
1 parent b87c05f commit 514db6f
Show file tree
Hide file tree
Showing 17 changed files with 391 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,15 @@ export class EndpointRuleAlertGenerator extends BaseDataGenerator {
value: '',
},
],
response_actions: [
{
action_type_id: 'endpoint',
params: {
command: 'isolate',
comment: 'test',
},
},
],
rule_id: ELASTIC_SECURITY_RULE_ID,
rule_name_override: 'message',
severity: 'medium',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export class FleetActionGenerator extends BaseDataGenerator {
/** Generate a random endpoint Action (isolate or unisolate) */
generate(overrides: DeepPartial<EndpointAction> = {}): EndpointAction {
const timeStamp = overrides['@timestamp'] ? new Date(overrides['@timestamp']) : new Date();

return merge(
{
action_id: this.seededUUIDv4(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import type {
LogsEndpointAction,
LogsEndpointActionResponse,
} from '../types';
import { ENDPOINT_ACTIONS_INDEX, ENDPOINT_ACTION_RESPONSES_INDEX } from '../constants';
import { ENDPOINT_ACTION_RESPONSES_INDEX, ENDPOINT_ACTIONS_INDEX } from '../constants';
import { FleetActionGenerator } from '../data_generators/fleet_action_generator';
import { wrapErrorAndRejectPromise } from './utils';
import { EndpointActionGenerator } from '../data_generators/endpoint_action_generator';

const defaultFleetActionGenerator = new FleetActionGenerator();
const fleetActionGenerator = new FleetActionGenerator();
const endpointActionGenerator = new EndpointActionGenerator();

export interface IndexedEndpointAndFleetActionsForHostResponse {
actions: EndpointAction[];
Expand All @@ -34,25 +36,26 @@ export interface IndexedEndpointAndFleetActionsForHostResponse {

export interface IndexEndpointAndFleetActionsForHostOptions {
numResponseActions?: number;
alertIds?: string[];
}

/**
* Indexes a random number of Endpoint (via Fleet) Actions for a given host
* (NOTE: ensure that fleet is setup first before calling this loading function)
* (NOTE: ensure that fleet is set up first before calling this loading function)
*
* @param esClient
* @param endpointHost
* @param [fleetActionGenerator]
* @param options
*/
export const indexEndpointAndFleetActionsForHost = async (
esClient: Client,
endpointHost: HostMetadata,
fleetActionGenerator: FleetActionGenerator = defaultFleetActionGenerator,
options: IndexEndpointAndFleetActionsForHostOptions = {}
): Promise<IndexedEndpointAndFleetActionsForHostResponse> => {
const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } };
const agentId = endpointHost.elastic.agent.id;
const actionsCount = options.numResponseActions ?? 1;
const total = fleetActionGenerator.randomN(5) + actionsCount;
const total = actionsCount === 1 ? actionsCount : fleetActionGenerator.randomN(5) + actionsCount;
const response: IndexedEndpointAndFleetActionsForHostResponse = {
actions: [],
actionResponses: [],
Expand All @@ -65,58 +68,69 @@ export const indexEndpointAndFleetActionsForHost = async (
};

for (let i = 0; i < total; i++) {
// create an action
const action = fleetActionGenerator.generate({
data: { comment: 'data generator: this host is bad' },
// start with endpoint action
const logsEndpointAction: LogsEndpointAction = endpointActionGenerator.generate({
EndpointActions: {
data: { comment: 'data generator: this host is bad' },
},
});

action.agents = [agentId];
const fleetAction: EndpointAction = {
...logsEndpointAction.EndpointActions,
'@timestamp': logsEndpointAction['@timestamp'],
agents:
typeof logsEndpointAction.agent.id === 'string'
? [logsEndpointAction.agent.id]
: logsEndpointAction.agent.id,
user_id: logsEndpointAction.user.id,
};

// index fleet action
const indexFleetActions = esClient
.index(
.index<EndpointAction>(
{
index: AGENT_ACTIONS_INDEX,
body: action,
body: fleetAction,
refresh: 'wait_for',
},
ES_INDEX_OPTIONS
)
.catch(wrapErrorAndRejectPromise);

const endpointActionsBody: LogsEndpointAction & {
EndpointActions: LogsEndpointAction['EndpointActions'] & {
'@timestamp': undefined;
user_id: undefined;
};
} = {
const logsEndpointActionsBody: LogsEndpointAction = {
...logsEndpointAction,
EndpointActions: {
...action,
'@timestamp': undefined,
user_id: undefined,
},
agent: {
id: [agentId],
},
'@timestamp': action['@timestamp'],
user: {
id: action.user_id,
...logsEndpointAction.EndpointActions,
data: {
...logsEndpointAction.EndpointActions.data,
alert_id: options.alertIds,
},
},
// to test automated actions in cypress
user: options.alertIds ? { id: 'unknown' } : logsEndpointAction.user,
rule: options.alertIds
? {
id: 'generated_rule_id',
name: 'generated_rule_name',
}
: logsEndpointAction.rule,
};

await Promise.all([
indexFleetActions,
esClient
.index({
.index<LogsEndpointAction>({
index: ENDPOINT_ACTIONS_INDEX,
body: endpointActionsBody,
body: logsEndpointActionsBody,
refresh: 'wait_for',
})
.catch(wrapErrorAndRejectPromise),
]);

const randomFloat = fleetActionGenerator.randomFloat();
// Create an action response for the above
const actionResponse = fleetActionGenerator.generateResponse({
action_id: action.action_id,
const fleetActionResponse: EndpointActionResponse = fleetActionGenerator.generateResponse({
action_id: logsEndpointAction.EndpointActions.action_id,
agent_id: agentId,
action_response: {
endpoint: {
Expand All @@ -129,10 +143,10 @@ export const indexEndpointAndFleetActionsForHost = async (
});

const indexFleetResponses = esClient
.index(
.index<EndpointActionResponse>(
{
index: AGENT_ACTIONS_RESULTS_INDEX,
body: actionResponse,
body: fleetActionResponse,
refresh: 'wait_for',
},
ES_INDEX_OPTIONS
Expand All @@ -143,8 +157,8 @@ export const indexEndpointAndFleetActionsForHost = async (
if (randomFloat < 0.7) {
const endpointActionResponseBody = {
EndpointActions: {
...actionResponse,
data: actionResponse.action_data,
...fleetActionResponse,
data: fleetActionResponse.action_data,
'@timestamp': undefined,
action_data: undefined,
agent_id: undefined,
Expand All @@ -157,16 +171,16 @@ export const indexEndpointAndFleetActionsForHost = async (
error:
randomFloat < 0.1
? {
message: actionResponse.error,
message: fleetActionResponse.error,
}
: undefined,
'@timestamp': actionResponse['@timestamp'],
'@timestamp': fleetActionResponse['@timestamp'],
};

await Promise.all([
indexFleetResponses,
esClient
.index({
.index<LogsEndpointActionResponse>({
index: ENDPOINT_ACTION_RESPONSES_INDEX,
body: endpointActionResponseBody,
refresh: 'wait_for',
Expand All @@ -178,8 +192,8 @@ export const indexEndpointAndFleetActionsForHost = async (
await indexFleetResponses;
}

response.actions.push(action);
response.actionResponses.push(actionResponse);
response.actions.push(fleetAction);
response.actionResponses.push(fleetActionResponse);
}

// Add edge case fleet actions (maybe)
Expand All @@ -191,62 +205,62 @@ export const indexEndpointAndFleetActionsForHost = async (
};
// 70% of the time just add either an Isolate -OR- an UnIsolate action
if (randomFloat < 0.7) {
let action: EndpointAction;
let fleetAction: EndpointAction;

if (randomFloat < 0.3) {
// add a pending isolation
action = fleetActionGenerator.generateIsolateAction(actionStartedAt);
fleetAction = fleetActionGenerator.generateIsolateAction(actionStartedAt);
} else {
// add a pending UN-isolation
action = fleetActionGenerator.generateUnIsolateAction(actionStartedAt);
fleetAction = fleetActionGenerator.generateUnIsolateAction(actionStartedAt);
}

action.agents = [agentId];
fleetAction.agents = [agentId];

await esClient
.index(
.index<EndpointAction>(
{
index: AGENT_ACTIONS_INDEX,
body: action,
body: fleetAction,
refresh: 'wait_for',
},
ES_INDEX_OPTIONS
)
.catch(wrapErrorAndRejectPromise);

response.actions.push(action);
response.actions.push(fleetAction);
} else {
// Else (30% of the time) add a pending isolate AND pending un-isolate
const action1 = fleetActionGenerator.generateIsolateAction(actionStartedAt);
const action2 = fleetActionGenerator.generateUnIsolateAction(actionStartedAt);
const fleetAction1 = fleetActionGenerator.generateIsolateAction(actionStartedAt);
const fleetAction2 = fleetActionGenerator.generateUnIsolateAction(actionStartedAt);

action1.agents = [agentId];
action2.agents = [agentId];
fleetAction1.agents = [agentId];
fleetAction2.agents = [agentId];

await Promise.all([
esClient
.index(
.index<EndpointAction>(
{
index: AGENT_ACTIONS_INDEX,
body: action1,
body: fleetAction1,
refresh: 'wait_for',
},
ES_INDEX_OPTIONS
)
.catch(wrapErrorAndRejectPromise),
esClient
.index(
.index<EndpointAction>(
{
index: AGENT_ACTIONS_INDEX,
body: action2,
body: fleetAction2,
refresh: 'wait_for',
},
ES_INDEX_OPTIONS
)
.catch(wrapErrorAndRejectPromise),
]);

response.actions.push(action1, action2);
response.actions.push(fleetAction1, fleetAction2);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export async function indexEndpointHostDocs({
generator,
withResponseActions = true,
numResponseActions,
alertIds,
}: {
numDocs: number;
client: Client;
Expand All @@ -102,6 +103,7 @@ export async function indexEndpointHostDocs({
generator: EndpointDocGenerator;
withResponseActions?: boolean;
numResponseActions?: IndexEndpointAndFleetActionsForHostOptions['numResponseActions'];
alertIds?: string[];
}): Promise<IndexedHostsResponse> {
const timeBetweenDocs = 6 * 3600 * 1000; // 6 hours between metadata documents
const timestamp = new Date().getTime();
Expand Down Expand Up @@ -198,14 +200,10 @@ export async function indexEndpointHostDocs({

if (withResponseActions) {
// Create some fleet endpoint actions and .logs-endpoint actions for this Host
const actionsResponse = await indexEndpointAndFleetActionsForHost(
client,
hostMetadata,
undefined,
{
numResponseActions,
}
);
const actionsResponse = await indexEndpointAndFleetActionsForHost(client, hostMetadata, {
alertIds,
numResponseActions,
});
mergeAndAppendArrays(response, actionsResponse);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export async function indexHostsAndAlerts(
options: TreeOptions = {},
DocGenerator: typeof EndpointDocGenerator = EndpointDocGenerator,
withResponseActions = true,
numResponseActions?: number
numResponseActions?: number,
alertIds?: string[]
): Promise<IndexedHostsAndAlertsResponse> {
const random = seedrandom(seed);
const epmEndpointPackage = await getEndpointPackageInfo(kbnClient);
Expand Down Expand Up @@ -119,6 +120,7 @@ export async function indexHostsAndAlerts(
generator,
withResponseActions,
numResponseActions,
alertIds,
});

mergeAndAppendArrays(response, indexedHosts);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default defineCypressConfig({
// baseUrl: To override, set Env. variable `CYPRESS_BASE_URL`
baseUrl: 'http://localhost:5601',
supportFile: 'public/management/cypress/support/e2e.ts',
specPattern: 'public/management/cypress/e2e/mocked_data/*.cy.{js,jsx,ts,tsx}',
specPattern: 'public/management/cypress/e2e/mocked_data/',
experimentalRunAllSpecs: true,
setupNodeEvents: (on, config) => {
return dataLoaders(on, config);
Expand Down
Loading

0 comments on commit 514db6f

Please sign in to comment.