Skip to content

Commit

Permalink
[Security Solution][Endpoint] Endpoint generator and data loader supp…
Browse files Browse the repository at this point in the history
…ort for Host Isolation (#100813)

Re-introduces the changes from #100727 which was backed out due to a bug. Changes included:

* Generate random isolation values for endpoint metadata
* Generator for Fleet Actions
* Added creation of actions to the index test data loader

Plus:

* Fix generator `randomBoolean()` to ensure it works with seeded random numbers
* Update resolver snapshots due to additional call to randomizer
  • Loading branch information
paul-tavares authored and kibanamachine committed May 28, 2021
1 parent fc3814d commit db8ff04
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import seedrandom from 'seedrandom';
import uuid from 'uuid';

const OS_FAMILY = ['windows', 'macos', 'linux'];
/** Array of 14 day offsets */
const DAY_OFFSETS = Array.from({ length: 14 }, (_, i) => 8.64e7 * (i + 1));

/**
* A generic base class to assist in creating domain specific data generators. It includes
* several general purpose random data generators for use within the class and exposes one
* public method named `generate()` which should be implemented by sub-classes.
*/
export class BaseDataGenerator<GeneratedDoc extends {} = {}> {
/** A javascript seeded random number (float between 0 and 1). Don't use `Math.random()` */
protected random: seedrandom.prng;

constructor(seed: string | seedrandom.prng = Math.random().toString()) {
Expand All @@ -33,6 +36,23 @@ export class BaseDataGenerator<GeneratedDoc extends {} = {}> {
throw new Error('method not implemented!');
}

/** Returns a future ISO date string */
protected randomFutureDate(from?: Date): string {
const now = from ? from.getTime() : Date.now();
return new Date(now + this.randomChoice(DAY_OFFSETS)).toISOString();
}

/** Returns a past ISO date string */
protected randomPastDate(from?: Date): string {
const now = from ? from.getTime() : Date.now();
return new Date(now - this.randomChoice(DAY_OFFSETS)).toISOString();
}

/** Generate either `true` or `false` */
protected randomBoolean(): boolean {
return this.random() < 0.5;
}

/** generate random OS family value */
protected randomOSFamily(): string {
return this.randomChoice(OS_FAMILY);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { DeepPartial } from 'utility-types';
import { merge } from 'lodash';
import { BaseDataGenerator } from './base_data_generator';
import { EndpointAction, EndpointActionResponse, ISOLATION_ACTIONS } from '../types';

const ISOLATION_COMMANDS: ISOLATION_ACTIONS[] = ['isolate', 'unisolate'];

export class FleetActionGenerator extends BaseDataGenerator {
/** Generate an Action */
generate(overrides: DeepPartial<EndpointAction> = {}): EndpointAction {
const timeStamp = new Date(this.randomPastDate());

return merge(
{
action_id: this.randomUUID(),
'@timestamp': timeStamp.toISOString(),
expiration: this.randomFutureDate(timeStamp),
type: 'INPUT_ACTION',
input_type: 'endpoint',
agents: [this.randomUUID()],
user_id: 'elastic',
data: {
command: this.randomIsolateCommand(),
comment: this.randomString(15),
},
},
overrides
);
}

/** Generates an action response */
generateResponse(overrides: DeepPartial<EndpointActionResponse> = {}): EndpointActionResponse {
const timeStamp = new Date();

return merge(
{
action_data: {
command: this.randomIsolateCommand(),
comment: '',
},
action_id: this.randomUUID(),
agent_id: this.randomUUID(),
started_at: this.randomPastDate(),
completed_at: timeStamp.toISOString(),
error: 'some error happen',
'@timestamp': timeStamp.toISOString(),
},
overrides
);
}

protected randomIsolateCommand() {
return this.randomChoice(ISOLATION_COMMANDS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ describe('data generator', () => {
expect(event2.event?.sequence).toBe((firstNonNullValue(event1.event?.sequence) ?? 0) + 1);
});

it('creates the same documents with same random seed', () => {
// Lets run this one multiple times just to ensure that the randomness
// is truly predicable based on the seed passed
it.each([1, 2, 3, 4, 5])('[%#] creates the same documents with same random seed', () => {
const generator1 = new EndpointDocGenerator('seed');
const generator2 = new EndpointDocGenerator('seed');
const timestamp = new Date().getTime();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,8 @@ export class EndpointDocGenerator extends BaseDataGenerator {

private createHostData(): HostInfo {
const hostName = this.randomHostname();
const isIsolated = this.randomBoolean();

return {
agent: {
version: this.randomVersion(),
Expand All @@ -465,10 +467,10 @@ export class EndpointDocGenerator extends BaseDataGenerator {
applied: this.randomChoice(APPLIED_POLICIES),
},
configuration: {
isolation: false,
isolation: isIsolated,
},
state: {
isolation: false,
isolation: isIsolated,
},
},
};
Expand Down
45 changes: 45 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/index_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import { policyFactory as policyConfigFactory } from './models/policy_config';
import { HostMetadata } from './types';
import { KbnClientWithApiKeySupport } from '../../scripts/endpoint/kbn_client_with_api_key_support';
import { FleetAgentGenerator } from './data_generators/fleet_agent_generator';
import { FleetActionGenerator } from './data_generators/fleet_action_generator';

const fleetAgentGenerator = new FleetAgentGenerator();
const fleetActionGenerator = new FleetActionGenerator();

export async function indexHostsAndAlerts(
client: Client,
Expand Down Expand Up @@ -175,6 +177,9 @@ async function indexHostDocs({
},
},
};

// Create some actions for this Host
await indexFleetActionsForHost(client, hostMetadata);
}

await client.index({
Expand Down Expand Up @@ -397,3 +402,43 @@ const indexFleetAgentForHost = async (

return agentDoc;
};

const indexFleetActionsForHost = async (
esClient: Client,
endpointHost: HostMetadata
): Promise<void> => {
const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } };
const agentId = endpointHost.elastic.agent.id;

for (let i = 0; i < 5; i++) {
// create an action
const isolateAction = fleetActionGenerator.generate({
data: { comment: 'data generator: this host is bad' },
});

isolateAction.agents = [agentId];

await esClient.index(
{
index: '.fleet-actions',
body: isolateAction,
},
ES_INDEX_OPTIONS
);

// Create an action response for the above
const unIsolateAction = fleetActionGenerator.generateResponse({
action_id: isolateAction.action_id,
agent_id: agentId,
action_data: isolateAction.data,
});

await esClient.index(
{
index: '.fleet-actions-results',
body: unIsolateAction,
},
ES_INDEX_OPTIONS
);
}
};
15 changes: 15 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/types/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ export interface EndpointAction {
};
}

export interface EndpointActionResponse {
'@timestamp': string;
/** The id of the action for which this response is associated with */
action_id: string;
/** The agent id that sent this action response */
agent_id: string;
started_at: string;
completed_at: string;
error: string;
action_data: {
command: ISOLATION_ACTIONS;
comment?: string;
};
}

export type HostIsolationRequestBody = TypeOf<typeof HostIsolationRequestSchema.body>;

export interface HostIsolationResponse {
Expand Down
Loading

0 comments on commit db8ff04

Please sign in to comment.