Skip to content

Commit

Permalink
[Security Solution] Interim Host Isolation Case Commenting (#100092)
Browse files Browse the repository at this point in the history
  • Loading branch information
pzl authored May 14, 2021
1 parent 25cad22 commit 97cc6dd
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 3 deletions.
13 changes: 13 additions & 0 deletions x-pack/plugins/cases/server/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
CasesClientGetUserActions,
CasesClientGetAlerts,
CasesClientPush,
CasesClientGetCasesByAlert,
} from './types';
import { create } from './cases/create';
import { update } from './cases/update';
Expand Down Expand Up @@ -247,4 +248,16 @@ export class CasesClientHandler implements CasesClient {
});
}
}

public async getCaseIdsByAlertId(args: CasesClientGetCasesByAlert) {
try {
return this._caseService.getCaseIdsByAlertId({
client: this._savedObjectsClient,
alertId: args.alertId,
});
} catch (error) {
this.logger.error(`Failed to get case using alert id: ${args.alertId}: ${error}`);
throw error;
}
}
}
1 change: 1 addition & 0 deletions x-pack/plugins/cases/server/client/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const createExternalCasesClientMock = (): CasesClientPluginContractMock =
getUserActions: jest.fn(),
update: jest.fn(),
updateAlertsStatus: jest.fn(),
getCaseIdsByAlertId: jest.fn(),
});

export const createCasesClientWithMockSavedObjectsClient = async ({
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/cases/server/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export interface ConfigureFields {
connectorType: string;
}

export interface CasesClientGetCasesByAlert {
alertId: string;
}

/**
* Defines the fields necessary to update an alert's status.
*/
Expand All @@ -106,6 +110,7 @@ export interface CasesClient {
push(args: CasesClientPush): Promise<CaseResponse>;
update(args: CasesPatchRequest): Promise<CasesResponse>;
updateAlertsStatus(args: CasesClientUpdateAlertsStatus): Promise<void>;
getCaseIdsByAlertId(args: CasesClientGetCasesByAlert): Promise<string[]>;
}

export interface MappingsClient {
Expand Down
16 changes: 15 additions & 1 deletion x-pack/plugins/cases/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
* 2.0.
*/

import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server';
import {
KibanaRequest,
PluginConfigDescriptor,
PluginInitializerContext,
RequestHandlerContext,
} from 'kibana/server';
import { CasesClient } from './client';
export { CasesClient } from './client';
import { ConfigType, ConfigSchema } from './config';
import { CasePlugin } from './plugin';

Expand All @@ -18,3 +25,10 @@ export const config: PluginConfigDescriptor<ConfigType> = {
};
export const plugin = (initializerContext: PluginInitializerContext) =>
new CasePlugin(initializerContext);

export interface PluginStartContract {
getCasesClientWithRequestAndContext(
context: RequestHandlerContext,
request: KibanaRequest
): CasesClient;
}
10 changes: 8 additions & 2 deletions x-pack/plugins/cases/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
* 2.0.
*/

import { IContextProvider, KibanaRequest, Logger, PluginInitializerContext } from 'kibana/server';
import {
IContextProvider,
KibanaRequest,
Logger,
PluginInitializerContext,
RequestHandlerContext,
} from 'kibana/server';
import { CoreSetup, CoreStart } from 'src/core/server';

import { SecurityPluginSetup } from '../../security/server';
Expand Down Expand Up @@ -128,7 +134,7 @@ export class CasePlugin {
this.log.debug(`Starting Case Workflow`);

const getCasesClientWithRequestAndContext = async (
context: CasesRequestHandlerContext,
context: RequestHandlerContext,
request: KibanaRequest
) => {
const user = await this.caseService!.getUser({ request });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
SavedObjectsClientContract,
} from 'src/core/server';
import { ExceptionListClient } from '../../../lists/server';
import {
CasesClient,
PluginStartContract as CasesPluginStartContract,
} from '../../../cases/server';
import { SecurityPluginStart } from '../../../security/server';
import {
AgentService,
Expand Down Expand Up @@ -41,6 +45,7 @@ import {
ExperimentalFeatures,
parseExperimentalConfigValue,
} from '../../common/experimental_features';
import { SecuritySolutionRequestHandlerContext } from '../types';

export interface MetadataService {
queryStrategy(
Expand Down Expand Up @@ -98,6 +103,7 @@ export type EndpointAppContextServiceStartContract = Partial<
savedObjectsStart: SavedObjectsServiceStart;
licenseService: LicenseService;
exceptionListsClient: ExceptionListClient | undefined;
cases: CasesPluginStartContract | undefined;
};

/**
Expand All @@ -114,6 +120,7 @@ export class EndpointAppContextService {
private config: ConfigType | undefined;
private license: LicenseService | undefined;
public security: SecurityPluginStart | undefined;
private cases: CasesPluginStartContract | undefined;

private experimentalFeatures: ExperimentalFeatures | undefined;

Expand All @@ -127,6 +134,7 @@ export class EndpointAppContextService {
this.config = dependencies.config;
this.license = dependencies.licenseService;
this.security = dependencies.security;
this.cases = dependencies.cases;

this.experimentalFeatures = parseExperimentalConfigValue(this.config.enableExperimental);

Expand Down Expand Up @@ -191,4 +199,14 @@ export class EndpointAppContextService {
}
return this.license;
}

public async getCasesClient(
req: KibanaRequest,
context: SecuritySolutionRequestHandlerContext
): Promise<CasesClient> {
if (!this.cases) {
throw new Error(`must call start on ${EndpointAppContextService.name} to call getter`);
}
return this.cases.getCasesClientWithRequestAndContext(context, req);
}
}
3 changes: 3 additions & 0 deletions x-pack/plugins/security_solution/server/endpoint/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked<
>(),
exceptionListsClient: listMock.getExceptionListClient(),
packagePolicyService: createPackagePolicyServiceMock(),
cases: {
getCasesClientWithRequestAndContext: jest.fn(),
},
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import moment from 'moment';
import { RequestHandler } from 'src/core/server';
import uuid from 'uuid';
import { TypeOf } from '@kbn/config-schema';
import { CommentType } from '../../../../../cases/common';
import { HostIsolationRequestSchema } from '../../../../common/endpoint/schema/actions';
import { ISOLATE_HOST_ROUTE, UNISOLATE_HOST_ROUTE } from '../../../../common/endpoint/constants';
import { AGENT_ACTIONS_INDEX } from '../../../../../fleet/common';
Expand Down Expand Up @@ -104,6 +105,20 @@ export const isolationRequestHandler = function (
}
agentIDs = [...new Set(agentIDs)]; // dedupe

// convert any alert IDs into cases
let caseIDs: string[] = req.body.case_ids?.slice() || [];
if (req.body.alert_ids && req.body.alert_ids.length > 0) {
const newIDs: string[][] = await Promise.all(
req.body.alert_ids.map(async (a: string) =>
(await endpointContext.service.getCasesClient(req, context)).getCaseIdsByAlertId({
alertId: a,
})
)
);
caseIDs = caseIDs.concat(...newIDs);
}
caseIDs = [...new Set(caseIDs)];

// create an Action ID and dispatch it to ES & Fleet Server
const esClient = context.core.elasticsearch.client.asCurrentUser;
const actionID = uuid.v4();
Expand Down Expand Up @@ -140,6 +155,29 @@ export const isolationRequestHandler = function (
},
});
}

const commentLines: string[] = [];

commentLines.push(`${isolate ? 'I' : 'Uni'}solate action was sent to the following Agents:`);
// lines of markdown links, inside a code block

commentLines.push(
`${agentIDs.map((a) => `- [${a}](/app/fleet#/fleet/agents/${a})`).join('\n')}`
);
if (req.body.comment) {
commentLines.push(`\n\nWith Comment:\n> ${req.body.comment}`);
}

caseIDs.forEach(async (caseId) => {
(await endpointContext.service.getCasesClient(req, context)).addComment({
caseId,
comment: {
comment: commentLines.join('\n'),
type: CommentType.user,
},
});
});

return res.ok({
body: {
action: actionID,
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/security_solution/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
PluginSetupContract as AlertingSetup,
PluginStartContract as AlertPluginStartContract,
} from '../../alerting/server';
import { PluginStartContract as CasesPluginStartContract } from '../../cases/server';
import { SecurityPluginSetup as SecuritySetup, SecurityPluginStart } from '../../security/server';
import { PluginSetupContract as FeaturesSetup } from '../../features/server';
import { MlPluginSetup as MlSetup } from '../../ml/server';
Expand Down Expand Up @@ -101,6 +102,7 @@ export interface StartPlugins {
taskManager?: TaskManagerStartContract;
telemetry?: TelemetryPluginStart;
security: SecurityPluginStart;
cases?: CasesPluginStartContract;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand Down Expand Up @@ -402,6 +404,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
security: plugins.security,
alerting: plugins.alerting,
config: this.config!,
cases: plugins.cases,
logger,
manifestManager,
registerIngestCallback,
Expand Down

0 comments on commit 97cc6dd

Please sign in to comment.