diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/actions.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/actions.tsx
index 5b17b05a45f68..dccf5ae0b91a2 100644
--- a/x-pack/plugins/cases/public/components/user_actions/comment/actions.tsx
+++ b/x-pack/plugins/cases/public/components/user_actions/comment/actions.tsx
@@ -34,6 +34,7 @@ export const createActionAttachmentUserActionBuilder = ({
// TODO: Fix this manually. Issue #123375
// eslint-disable-next-line react/display-name
build: () => {
+ const actionIconName = comment.actions.type === 'isolate' ? 'lock' : 'lockOpen';
return [
{
username: (
@@ -52,7 +53,8 @@ export const createActionAttachmentUserActionBuilder = ({
),
'data-test-subj': 'endpoint-action',
timestamp: ,
- timelineAvatar: comment.actions.type === 'isolate' ? 'lock' : 'lockOpen',
+ timelineAvatar: actionIconName,
+ timelineAvatarAriaLabel: actionIconName,
actions: ,
children: comment.comment.trim().length > 0 && (
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts
index e5a44c46f5afc..bc429feb208d7 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_metadata_generator.ts
@@ -20,6 +20,7 @@ export interface GetCustomEndpointMetadataGeneratorOptions {
version: string;
/** OS type for the generated endpoint hosts */
os: 'macOS' | 'windows' | 'linux';
+ isolation: boolean;
}
/**
@@ -33,6 +34,7 @@ export class EndpointMetadataGenerator extends BaseDataGenerator {
static custom({
version,
os,
+ isolation,
}: Partial = {}): typeof EndpointMetadataGenerator {
return class extends EndpointMetadataGenerator {
generate(overrides: DeepPartial = {}): HostMetadataInterface {
@@ -54,6 +56,9 @@ export class EndpointMetadataGenerator extends BaseDataGenerator {
set(overrides, 'host.os', EndpointMetadataGenerator.windowsOSFields);
}
}
+ if (isolation !== undefined) {
+ set(overrides, 'Endpoint.state.isolation', isolation);
+ }
return super.generate(overrides);
}
@@ -104,10 +109,10 @@ export class EndpointMetadataGenerator extends BaseDataGenerator {
/** Generate an Endpoint host metadata document */
generate(overrides: DeepPartial = {}): HostMetadataInterface {
const ts = overrides['@timestamp'] ?? new Date().getTime();
- const hostName = this.randomHostname();
+ const hostName = overrides?.host?.hostname ?? this.randomHostname();
const agentVersion = overrides?.agent?.version ?? this.randomVersion();
const agentId = this.seededUUIDv4();
- const isIsolated = this.randomBoolean(0.3);
+ const isIsolated = overrides?.Endpoint?.state?.isolation ?? this.randomBoolean(0.3);
const capabilities: EndpointCapabilities[] = ['isolation'];
// v8.4 introduced additional endpoint capabilities
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_rule_alert_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_rule_alert_generator.ts
index 5578c179ba1f5..8396f86a45e97 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_rule_alert_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_rule_alert_generator.ts
@@ -37,6 +37,8 @@ export class EndpointRuleAlertGenerator extends BaseDataGenerator {
const endpointMetadataGenerator = new EndpointMetadataGenerator();
const endpointMetadata = endpointMetadataGenerator.generate({
agent: { version: kibanaPackageJson.version },
+ host: { hostname: overrides?.host?.hostname },
+ Endpoint: { state: { isolation: overrides?.Endpoint?.state?.isolation } },
});
const now = overrides['@timestamp'] ?? new Date().toISOString();
const endpointAgentId = overrides?.agent?.id ?? this.seededUUIDv4();
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts
index f9f96e650c056..684694bdb5c9a 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts
@@ -75,6 +75,7 @@ export interface IndexedHostsResponse
* @param policyResponseIndex
* @param enrollFleet
* @param generator
+ * @param disableEndpointActionsForHost
*/
export async function indexEndpointHostDocs({
numDocs,
@@ -86,6 +87,7 @@ export async function indexEndpointHostDocs({
policyResponseIndex,
enrollFleet,
generator,
+ withResponseActions = true,
}: {
numDocs: number;
client: Client;
@@ -96,6 +98,7 @@ export async function indexEndpointHostDocs({
policyResponseIndex: string;
enrollFleet: boolean;
generator: EndpointDocGenerator;
+ withResponseActions?: boolean;
}): Promise {
const timeBetweenDocs = 6 * 3600 * 1000; // 6 hours between metadata documents
const timestamp = new Date().getTime();
@@ -190,13 +193,15 @@ export async function indexEndpointHostDocs({
},
};
- // Create some fleet endpoint actions and .logs-endpoint actions for this Host
- const actionsResponse = await indexEndpointAndFleetActionsForHost(
- client,
- hostMetadata,
- undefined
- );
- mergeAndAppendArrays(response, actionsResponse);
+ if (withResponseActions) {
+ // Create some fleet endpoint actions and .logs-endpoint actions for this Host
+ const actionsResponse = await indexEndpointAndFleetActionsForHost(
+ client,
+ hostMetadata,
+ undefined
+ );
+ mergeAndAppendArrays(response, actionsResponse);
+ }
}
await client
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_rule_alerts.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_rule_alerts.ts
index 74e9d82a714e9..1c5883c052135 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_rule_alerts.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_rule_alerts.ts
@@ -22,6 +22,8 @@ import { EndpointRuleAlertGenerator } from '../data_generators/endpoint_rule_ale
export interface IndexEndpointRuleAlertsOptions {
esClient: Client;
endpointAgentId: string;
+ endpointHostname?: string;
+ endpointIsolated?: boolean;
count?: number;
log?: ToolingLog;
}
@@ -40,12 +42,16 @@ export interface DeletedIndexedEndpointRuleAlerts {
* written them to for a given endpoint
* @param esClient
* @param endpointAgentId
+ * @param endpointHostname
+ * @param endpointIsolated
* @param count
* @param log
*/
export const indexEndpointRuleAlerts = async ({
esClient,
endpointAgentId,
+ endpointHostname,
+ endpointIsolated,
count = 1,
log = new ToolingLog(),
}: IndexEndpointRuleAlertsOptions): Promise => {
@@ -57,7 +63,11 @@ export const indexEndpointRuleAlerts = async ({
const indexedAlerts: estypes.IndexResponse[] = [];
for (let n = 0; n < count; n++) {
- const alert = alertsGenerator.generate({ agent: { id: endpointAgentId } });
+ const alert = alertsGenerator.generate({
+ agent: { id: endpointAgentId },
+ host: { hostname: endpointHostname },
+ ...(endpointIsolated ? { Endpoint: { state: { isolation: endpointIsolated } } } : {}),
+ });
const indexedAlert = await esClient.index({
index: `${DEFAULT_ALERTS_INDEX}-default`,
refresh: 'wait_for',
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index d867c25ececf7..76ee903eb6889 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -410,7 +410,9 @@ export class EndpointDocGenerator extends BaseDataGenerator {
private createHostData(): CommonHostInfo {
const { agent, elastic, host, Endpoint } = this.metadataGenerator.generate({
- Endpoint: { policy: { applied: this.randomChoice(APPLIED_POLICIES) } },
+ Endpoint: {
+ policy: { applied: this.randomChoice(APPLIED_POLICIES) },
+ },
});
return { agent, elastic, host, Endpoint };
diff --git a/x-pack/plugins/security_solution/common/endpoint/index_data.ts b/x-pack/plugins/security_solution/common/endpoint/index_data.ts
index 77b3135c12353..db5039e5a72f0 100644
--- a/x-pack/plugins/security_solution/common/endpoint/index_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/index_data.ts
@@ -48,6 +48,7 @@ export type IndexedHostsAndAlertsResponse = IndexedHostsResponse;
* @param fleet
* @param options
* @param DocGenerator
+ * @param withResponseActions
*/
export async function indexHostsAndAlerts(
client: Client,
@@ -62,7 +63,8 @@ export async function indexHostsAndAlerts(
alertsPerHost: number,
fleet: boolean,
options: TreeOptions = {},
- DocGenerator: typeof EndpointDocGenerator = EndpointDocGenerator
+ DocGenerator: typeof EndpointDocGenerator = EndpointDocGenerator,
+ withResponseActions = true
): Promise {
const random = seedrandom(seed);
const epmEndpointPackage = await getEndpointPackageInfo(kbnClient);
@@ -114,6 +116,7 @@ export async function indexHostsAndAlerts(
policyResponseIndex,
enrollFleet: fleet,
generator,
+ withResponseActions,
});
mergeAndAppendArrays(response, indexedHosts);
diff --git a/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts b/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts
index a48498a7ee43b..c461f712b75b5 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/cypress.d.ts
@@ -11,8 +11,11 @@
import type { CasePostRequest } from '@kbn/cases-plugin/common/api';
import type { IndexedEndpointPolicyResponse } from '../../../common/endpoint/data_loaders/index_endpoint_policy_response';
-import type { HostPolicyResponse } from '../../../common/endpoint/types';
-import type { IndexEndpointHostsCyTaskOptions } from './types';
+import type {
+ HostPolicyResponse,
+ LogsEndpointActionResponse,
+} from '../../../common/endpoint/types';
+import type { IndexEndpointHostsCyTaskOptions, HostActionResponse } from './types';
import type {
DeleteIndexedFleetEndpointPoliciesResponse,
IndexedFleetEndpointPolicyResponse,
@@ -115,6 +118,12 @@ declare global {
arg: IndexedEndpointPolicyResponse,
options?: Partial
): Chainable;
+
+ task(
+ name: 'sendHostActionResponse',
+ arg: HostActionResponse,
+ options?: Partial
+ ): Chainable;
}
}
}
diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoints.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoints.cy.ts
index 35b931675a6c2..d5c946af96c14 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoints.cy.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/endpoints.cy.ts
@@ -5,18 +5,31 @@
* 2.0.
*/
+import type { ReturnTypeFromChainable } from '../../types';
+import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts';
import { login } from '../../tasks/login';
-import { runEndpointLoaderScript } from '../../tasks/run_endpoint_loader';
describe('Endpoints page', () => {
+ let endpointData: ReturnTypeFromChainable;
+
before(() => {
- runEndpointLoaderScript();
+ indexEndpointHosts().then((indexEndpoints) => {
+ endpointData = indexEndpoints;
+ });
});
beforeEach(() => {
login();
});
+ after(() => {
+ if (endpointData) {
+ endpointData.cleanup();
+ // @ts-expect-error ignore setting to undefined
+ endpointData = undefined;
+ }
+ });
+
it('Loads the endpoints page', () => {
cy.visit('/app/security/administration/endpoints');
cy.contains('Hosts running Elastic Defend').should('exist');
diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts
new file mode 100644
index 0000000000000..579c0cab8c540
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts
@@ -0,0 +1,328 @@
+/*
+ * 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 { getEndpointListPath } from '../../../common/routing';
+import {
+ interceptActionRequests,
+ isolateHostWithComment,
+ openAlertDetails,
+ openCaseAlertDetails,
+ releaseHostWithComment,
+ sendActionResponse,
+ waitForReleaseOption,
+} from '../../tasks/isolate';
+import type { ActionDetails } from '../../../../../common/endpoint/types';
+import { closeAllToasts } from '../../tasks/close_all_toasts';
+import type { ReturnTypeFromChainable } from '../../types';
+import { addAlertsToCase } from '../../tasks/add_alerts_to_case';
+import { APP_ALERTS_PATH, APP_CASES_PATH, APP_PATH } from '../../../../../common/constants';
+import { login } from '../../tasks/login';
+import { indexNewCase } from '../../tasks/index_new_case';
+import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts';
+import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts';
+
+describe('Isolate command', () => {
+ describe('from Manage', () => {
+ let endpointData: ReturnTypeFromChainable;
+ let isolatedEndpointData: ReturnTypeFromChainable;
+
+ before(() => {
+ indexEndpointHosts({
+ count: 2,
+ withResponseActions: false,
+ isolation: false,
+ }).then((indexEndpoints) => {
+ endpointData = indexEndpoints;
+ });
+
+ indexEndpointHosts({
+ count: 2,
+ withResponseActions: false,
+ isolation: true,
+ }).then((indexEndpoints) => {
+ isolatedEndpointData = indexEndpoints;
+ });
+ });
+
+ after(() => {
+ if (endpointData) {
+ endpointData.cleanup();
+ // @ts-expect-error ignore setting to undefined
+ endpointData = undefined;
+ }
+
+ if (isolatedEndpointData) {
+ isolatedEndpointData.cleanup();
+ // @ts-expect-error ignore setting to undefined
+ isolatedEndpointData = undefined;
+ }
+ });
+ beforeEach(() => {
+ login();
+ });
+ it('should allow filtering endpoint by Isolated status', () => {
+ cy.visit(APP_PATH + getEndpointListPath({ name: 'endpointList' }));
+ closeAllToasts();
+ cy.getByTestSubj('adminSearchBar')
+ .click()
+ .type('united.endpoint.Endpoint.state.isolation: true');
+ cy.getByTestSubj('querySubmitButton').click();
+ cy.contains('Showing 2 endpoints');
+ cy.getByTestSubj('endpointListTable').within(() => {
+ cy.get('tbody tr').each(($tr) => {
+ cy.wrap($tr).within(() => {
+ cy.get('td').eq(1).should('contain.text', 'Isolated');
+ });
+ });
+ });
+ });
+ });
+
+ describe('from Alerts', () => {
+ let endpointData: ReturnTypeFromChainable;
+ let alertData: ReturnTypeFromChainable;
+ let hostname: string;
+
+ before(() => {
+ indexEndpointHosts({ withResponseActions: false, isolation: false })
+ .then((indexEndpoints) => {
+ endpointData = indexEndpoints;
+ hostname = endpointData.data.hosts[0].host.name;
+ })
+ .then(() => {
+ return indexEndpointRuleAlerts({
+ endpointAgentId: endpointData.data.hosts[0].agent.id,
+ endpointHostname: endpointData.data.hosts[0].host.name,
+ endpointIsolated: false,
+ });
+ });
+ });
+
+ after(() => {
+ if (endpointData) {
+ endpointData.cleanup();
+ // @ts-expect-error ignore setting to undefined
+ endpointData = undefined;
+ }
+
+ if (alertData) {
+ alertData.cleanup();
+ // @ts-expect-error ignore setting to undefined
+ alertData = undefined;
+ }
+ });
+
+ beforeEach(() => {
+ login();
+ });
+
+ it('should isolate and release host', () => {
+ const isolateComment = `Isolating ${hostname}`;
+ const releaseComment = `Releasing ${hostname}`;
+ let isolateRequestResponse: ActionDetails;
+ let releaseRequestResponse: ActionDetails;
+
+ cy.visit(APP_ALERTS_PATH);
+ closeAllToasts();
+
+ cy.getByTestSubj('alertsTable').within(() => {
+ cy.getByTestSubj('expand-event')
+ .first()
+ .within(() => {
+ cy.get(`[data-is-loading="true"]`).should('exist');
+ });
+ cy.getByTestSubj('expand-event')
+ .first()
+ .within(() => {
+ cy.get(`[data-is-loading="true"]`).should('not.exist');
+ });
+ });
+
+ openAlertDetails();
+
+ isolateHostWithComment(isolateComment, hostname);
+
+ interceptActionRequests((responseBody) => {
+ isolateRequestResponse = responseBody;
+ }, 'isolate');
+
+ cy.getByTestSubj('hostIsolateConfirmButton').click();
+
+ cy.wait('@isolate').then(() => {
+ sendActionResponse(isolateRequestResponse);
+ });
+
+ cy.contains(`Isolation on host ${hostname} successfully submitted`);
+
+ cy.getByTestSubj('euiFlyoutCloseButton').click();
+ cy.wait(1000);
+ openAlertDetails();
+ cy.getByTestSubj('event-field-agent.status').then(($status) => {
+ if ($status.find('[title="Isolated"]').length > 0) {
+ cy.contains('Release host').click();
+ } else {
+ cy.getByTestSubj('euiFlyoutCloseButton').click();
+ openAlertDetails();
+ cy.getByTestSubj('event-field-agent.status').within(() => {
+ cy.contains('Isolated');
+ });
+ cy.contains('Release host').click();
+ }
+ });
+
+ releaseHostWithComment(releaseComment, hostname);
+
+ interceptActionRequests((responseBody) => {
+ releaseRequestResponse = responseBody;
+ }, 'release');
+
+ cy.contains('Confirm').click();
+
+ cy.wait('@release').then(() => {
+ sendActionResponse(releaseRequestResponse);
+ });
+
+ cy.contains(`Release on host ${hostname} successfully submitted`);
+ cy.getByTestSubj('euiFlyoutCloseButton').click();
+ openAlertDetails();
+ cy.getByTestSubj('event-field-agent.status').within(() => {
+ cy.get('[title="Isolated"]').should('not.exist');
+ });
+ });
+ });
+
+ describe('from Cases', () => {
+ let endpointData: ReturnTypeFromChainable;
+ let caseData: ReturnTypeFromChainable;
+ let alertData: ReturnTypeFromChainable;
+ let caseAlertActions: ReturnType;
+ let alertId: string;
+ let caseUrlPath: string;
+ let hostname: string;
+
+ before(() => {
+ indexNewCase().then((indexCase) => {
+ caseData = indexCase;
+ caseUrlPath = `${APP_CASES_PATH}/${indexCase.data.id}`;
+ });
+
+ indexEndpointHosts({ withResponseActions: false, isolation: false })
+ .then((indexEndpoints) => {
+ endpointData = indexEndpoints;
+ hostname = endpointData.data.hosts[0].host.name;
+ })
+ .then(() => {
+ return indexEndpointRuleAlerts({
+ endpointAgentId: endpointData.data.hosts[0].agent.id,
+ endpointHostname: endpointData.data.hosts[0].host.name,
+ endpointIsolated: false,
+ }).then((indexedAlert) => {
+ alertData = indexedAlert;
+ alertId = alertData.alerts[0]._id;
+ });
+ })
+ .then(() => {
+ caseAlertActions = addAlertsToCase({
+ caseId: caseData.data.id,
+ alertIds: [alertId],
+ });
+ });
+ });
+
+ after(() => {
+ if (caseData) {
+ caseData.cleanup();
+ // @ts-expect-error ignore setting to undefined
+ caseData = undefined;
+ }
+
+ if (endpointData) {
+ endpointData.cleanup();
+ // @ts-expect-error ignore setting to undefined
+ endpointData = undefined;
+ }
+
+ if (alertData) {
+ alertData.cleanup();
+ // @ts-expect-error ignore setting to undefined
+ alertData = undefined;
+ }
+ });
+
+ beforeEach(() => {
+ login();
+ });
+
+ it('should isolate and release host', () => {
+ let isolateRequestResponse: ActionDetails;
+ let releaseRequestResponse: ActionDetails;
+ const isolateComment = `Isolating ${hostname}`;
+ const releaseComment = `Releasing ${hostname}`;
+ const caseAlertId = caseAlertActions.comments[alertId];
+
+ cy.visit(caseUrlPath);
+ closeAllToasts();
+ openCaseAlertDetails(caseAlertId);
+
+ isolateHostWithComment(isolateComment, hostname);
+
+ interceptActionRequests((responseBody) => {
+ isolateRequestResponse = responseBody;
+ }, 'isolate');
+
+ cy.getByTestSubj('hostIsolateConfirmButton').click();
+
+ cy.wait('@isolate').then(() => {
+ sendActionResponse(isolateRequestResponse);
+ });
+
+ cy.contains(`Isolation on host ${hostname} successfully submitted`);
+
+ cy.getByTestSubj('euiFlyoutCloseButton').click();
+
+ cy.getByTestSubj('user-actions-list').within(() => {
+ cy.contains(isolateComment);
+ cy.get('[aria-label="lock"]').should('exist');
+ cy.get('[aria-label="lockOpen"]').should('not.exist');
+ });
+
+ waitForReleaseOption(caseAlertId);
+
+ releaseHostWithComment(releaseComment, hostname);
+
+ interceptActionRequests((responseBody) => {
+ releaseRequestResponse = responseBody;
+ }, 'release');
+
+ cy.contains('Confirm').click();
+
+ cy.wait('@release').then(() => {
+ sendActionResponse(releaseRequestResponse);
+ });
+
+ cy.contains(`Release on host ${hostname} successfully submitted`);
+ cy.getByTestSubj('euiFlyoutCloseButton').click();
+
+ cy.getByTestSubj('user-actions-list').within(() => {
+ cy.contains(releaseComment);
+ cy.contains(isolateComment);
+ cy.get('[aria-label="lock"]').should('exist');
+ cy.get('[aria-label="lockOpen"]').should('exist');
+ });
+
+ openCaseAlertDetails(caseAlertId);
+ cy.getByTestSubj('event-field-agent.status').then(($status) => {
+ if ($status.find('[title="Isolated"]').length > 0) {
+ cy.getByTestSubj('euiFlyoutCloseButton').click();
+ cy.getByTestSubj(`comment-action-show-alert-${caseAlertId}`).click();
+ cy.getByTestSubj('take-action-dropdown-btn').click();
+ }
+ cy.get('[title="Isolated"]').should('not.exist');
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts
index b31ee2ec25874..6dd4bedaa8937 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts
@@ -8,12 +8,17 @@
// /
import type { CasePostRequest } from '@kbn/cases-plugin/common/api';
+import { sendEndpointActionResponse } from '../../../../scripts/endpoint/agent_emulator/services/endpoint_response_actions';
import type { IndexedEndpointPolicyResponse } from '../../../../common/endpoint/data_loaders/index_endpoint_policy_response';
import {
deleteIndexedEndpointPolicyResponse,
indexEndpointPolicyResponse,
} from '../../../../common/endpoint/data_loaders/index_endpoint_policy_response';
-import type { HostPolicyResponse } from '../../../../common/endpoint/types';
+import type {
+ ActionDetails,
+ HostPolicyResponse,
+ LogsEndpointActionResponse,
+} from '../../../../common/endpoint/types';
import type { IndexEndpointHostsCyTaskOptions } from '../types';
import type {
IndexedEndpointRuleAlerts,
@@ -95,12 +100,14 @@ export const dataLoaders = (
indexEndpointHosts: async (options: IndexEndpointHostsCyTaskOptions = {}) => {
const { kbnClient, esClient } = await stackServicesPromise;
- const { count: numHosts, version, os } = options;
+ const { count: numHosts, version, os, isolation, withResponseActions } = options;
return cyLoadEndpointDataHandler(esClient, kbnClient, {
numHosts,
version,
os,
+ isolation,
+ withResponseActions,
});
},
@@ -140,5 +147,13 @@ export const dataLoaders = (
const { esClient } = await stackServicesPromise;
return deleteIndexedEndpointPolicyResponse(esClient, indexedData).then(() => null);
},
+
+ sendHostActionResponse: async (data: {
+ action: ActionDetails;
+ state: { state?: 'success' | 'failure' };
+ }): Promise => {
+ const { esClient } = await stackServicesPromise;
+ return sendEndpointActionResponse(esClient, data.action, { state: data.state.state });
+ },
});
};
diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/plugin_handlers/endpoint_data_loader.ts b/x-pack/plugins/security_solution/public/management/cypress/support/plugin_handlers/endpoint_data_loader.ts
index cfff2e9a2a4b0..9d0f5ac135d5d 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/support/plugin_handlers/endpoint_data_loader.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/support/plugin_handlers/endpoint_data_loader.ts
@@ -36,6 +36,9 @@ export interface CyLoadEndpointDataOptions
enableFleetIntegration: boolean;
generatorSeed: string;
waitUntilTransformed: boolean;
+ withResponseActions: boolean;
+ isolation: boolean;
+ bothIsolatedAndNormalEndpoints?: boolean;
}
/**
@@ -58,10 +61,12 @@ export const cyLoadEndpointDataHandler = async (
waitUntilTransformed = true,
version = kibanaPackageJson.version,
os,
+ withResponseActions,
+ isolation,
} = options;
const DocGenerator = EndpointDocGenerator.custom({
- CustomMetadataGenerator: EndpointMetadataGenerator.custom({ version, os }),
+ CustomMetadataGenerator: EndpointMetadataGenerator.custom({ version, os, isolation }),
});
if (waitUntilTransformed) {
@@ -85,7 +90,8 @@ export const cyLoadEndpointDataHandler = async (
alertsPerHost,
enableFleetIntegration,
undefined,
- DocGenerator
+ DocGenerator,
+ withResponseActions
);
if (waitUntilTransformed) {
diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/index_endpoint_rule_alerts.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/index_endpoint_rule_alerts.ts
index 498c105d0da49..21ebd75fa6329 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/tasks/index_endpoint_rule_alerts.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/index_endpoint_rule_alerts.ts
@@ -12,6 +12,8 @@ import type {
export const indexEndpointRuleAlerts = (options: {
endpointAgentId: string;
+ endpointHostname?: string;
+ endpointIsolated?: boolean;
count?: number;
}): Cypress.Chainable<
Pick & {
diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts
new file mode 100644
index 0000000000000..b00b567852026
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/isolate.ts
@@ -0,0 +1,69 @@
+/*
+ * 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 type { ActionDetails } from '../../../../common/endpoint/types';
+
+const API_ENDPOINT_ACTION_PATH = '/api/endpoint/action/*';
+export const interceptActionRequests = (
+ cb: (responseBody: ActionDetails) => void,
+ alias: string
+): void => {
+ cy.intercept('POST', API_ENDPOINT_ACTION_PATH, (req) => {
+ req.continue((res) => {
+ const {
+ body: { action, data },
+ } = res;
+
+ cb({ action, ...data });
+ });
+ }).as(alias);
+};
+
+export const sendActionResponse = (action: ActionDetails): void => {
+ cy.task('sendHostActionResponse', {
+ action,
+ state: { state: 'success' },
+ });
+};
+
+export const isolateHostWithComment = (comment: string, hostname: string): void => {
+ cy.getByTestSubj('isolate-host-action-item').click();
+ cy.contains(`Isolate host ${hostname} from network.`);
+ cy.getByTestSubj('endpointHostIsolationForm');
+ cy.getByTestSubj('host_isolation_comment').type(comment);
+};
+
+export const releaseHostWithComment = (comment: string, hostname: string): void => {
+ cy.contains(`${hostname} is currently isolated.`);
+ cy.getByTestSubj('endpointHostIsolationForm');
+ cy.getByTestSubj('host_isolation_comment').type(comment);
+};
+
+export const openAlertDetails = (): void => {
+ cy.getByTestSubj('expand-event').first().click();
+ cy.getByTestSubj('take-action-dropdown-btn').click();
+};
+
+export const openCaseAlertDetails = (alertId: string): void => {
+ cy.getByTestSubj(`comment-action-show-alert-${alertId}`).click();
+ cy.getByTestSubj('take-action-dropdown-btn').click();
+};
+export const waitForReleaseOption = (alertId: string): void => {
+ openCaseAlertDetails(alertId);
+ cy.getByTestSubj('event-field-agent.status').then(($status) => {
+ if ($status.find('[title="Isolated"]').length > 0) {
+ cy.contains('Release host').click();
+ } else {
+ cy.getByTestSubj('euiFlyoutCloseButton').click();
+ openCaseAlertDetails(alertId);
+ cy.getByTestSubj('event-field-agent.status').within(() => {
+ cy.contains('Isolated');
+ });
+ cy.contains('Release host').click();
+ }
+ });
+};
diff --git a/x-pack/plugins/security_solution/public/management/cypress/types.ts b/x-pack/plugins/security_solution/public/management/cypress/types.ts
index 0741f7fab1ad0..97d635a3b6840 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/types.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/types.ts
@@ -7,6 +7,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
+import type { ActionDetails } from '../../../common/endpoint/types';
import type { CyLoadEndpointDataOptions } from './support/plugin_handlers/endpoint_data_loader';
type PossibleChainable =
@@ -41,5 +42,15 @@ export type ReturnTypeFromChainable = C extends Cyp
: never;
export type IndexEndpointHostsCyTaskOptions = Partial<
- { count: number } & Pick
+ { count: number; withResponseActions: boolean } & Pick<
+ CyLoadEndpointDataOptions,
+ 'version' | 'os' | 'isolation'
+ >
>;
+
+export interface HostActionResponse {
+ data: {
+ action: ActionDetails;
+ state: { state?: 'success' | 'failure' };
+ };
+}
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
index b72d4fa30777d..40abffc508fab 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts
@@ -256,15 +256,15 @@ describe('endpoint list middleware', () => {
query: {
agent_ids: [
'0dc3661d-6e67-46b0-af39-6f12b025fcb0',
- '34634c58-24b4-4448-80f4-107fb9918494',
- '5a1298e3-e607-4bc0-8ef6-6d6a811312f2',
- '78c54b13-596d-4891-95f4-80092d04454b',
- '445f1fd2-5f81-4ddd-bdb6-f0d1bf2efe90',
- 'd77a3fc6-3096-4852-a6ee-f6b09278fbc6',
- '892fcccf-1bd8-45a2-a9cc-9a7860a3cb81',
- '693a3110-5ba0-4284-a264-5d78301db08c',
- '554db084-64fa-4e4a-ba47-2ba713f9932b',
- 'c217deb6-674d-4f97-bb1d-a3a04238e6d7',
+ 'fe16dda9-7f34-434c-9824-b4844880f410',
+ 'f412728b-929c-48d5-bdb6-5a1298e3e607',
+ 'd0405ddc-1e7c-48f0-93d7-d55f954bd745',
+ '46d78dd2-aedf-4d3f-b3a9-da445f1fd25f',
+ '5aafa558-26b8-4bb4-80e2-ac0644d77a3f',
+ 'edac2c58-1748-40c3-853c-8fab48c333d7',
+ '06b7223a-bb2a-428a-9021-f1c0d2267ada',
+ 'b8daa43b-7f73-4684-9221-dbc8b769405e',
+ 'fbc06310-7d41-46b8-a5ea-ceed8a993b1a',
],
},
});
diff --git a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts
index 2b3d73efac361..25c2e5f6327be 100644
--- a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts
+++ b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts
@@ -137,15 +137,21 @@ export const sendEndpointActionResponse = async (
message: 'Endpoint encountered an error and was unable to apply action to host',
};
- if (endpointResponse.EndpointActions.data.command === 'get-file') {
+ if (
+ endpointResponse.EndpointActions.data.command === 'get-file' &&
+ endpointResponse.EndpointActions.data.output
+ ) {
(
- endpointResponse.EndpointActions.data.output?.content as ResponseActionGetFileOutputContent
+ endpointResponse.EndpointActions.data.output.content as ResponseActionGetFileOutputContent
).code = endpointActionGenerator.randomGetFileFailureCode();
}
- if (endpointResponse.EndpointActions.data.command === 'execute') {
+ if (
+ endpointResponse.EndpointActions.data.command === 'execute' &&
+ endpointResponse.EndpointActions.data.output
+ ) {
(
- endpointResponse.EndpointActions.data.output?.content as ResponseActionExecuteOutputContent
+ endpointResponse.EndpointActions.data.output.content as ResponseActionExecuteOutputContent
).stderr = 'execute command timed out';
}
}
diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts
index 5d19932090168..735285753ea23 100644
--- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts
+++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts
@@ -33,27 +33,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
'Actions',
],
[
- 'Host-dpu1a2r2yi',
+ 'Host-9qenwrl9ko',
'x',
'x',
'Warning',
- 'macOS',
- '10.2.17.24, 10.56.215.200,10.254.196.130',
- 'x',
- 'x',
- '',
- ],
- [
- 'Host-rs9wp4o6l9',
- 'x',
- 'x',
- 'Success',
'Linux',
- '10.138.79.131, 10.170.160.154',
+ '10.56.228.101, 10.201.120.140,10.236.180.146',
'x',
'x',
'',
],
+ ['Host-qw2bti801m', 'x', 'x', 'Failure', 'macOS', '10.244.59.227', 'x', 'x', ''],
[
'Host-u5jy6j0pwb',
'x',