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

[7.x] [Security Solution][Endpoint] Endpoint generator and data loader support for Host Isolation (#100813) #100904

Merged
merged 1 commit into from
May 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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