[8.11] [Security Solution] [Endpoint] Upload action endpoint tests and other missing workflow tests for response console (#168005) (#169314)

# Backport

This will backport the following commits from `main` to `8.11`:
- [[Security Solution] [Endpoint] Upload action endpoint tests and other
missing workflow tests for response console
(#168005)](https://github.com/elastic/kibana/pull/168005)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Ash","email":"1849116+ashokaditya@users.noreply.github.com"},"sourceCommit":{"committedDate":"2023-10-18T20:47:14Z","message":"[Security
Solution] [Endpoint] Upload action endpoint tests and other missing
workflow tests for response console (#168005)\n\n## Summary\r\n\r\nAdds
missing tests for upload response action workflows.\r\n\r\n Adds tests
for \r\n- [x] upload response action\r\n- [x] responder action from
Alerts -> Alert details\r\n- [x] responder action from Cases -> Alert
details\r\n- [x] responder action from Timeline view -> Alert
Details\r\n\r\n### Checklist\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Patryk Kopycinski
<contact@patrykkopycinski.com>\r\nCo-authored-by: Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"6fd7a867ea7ba56db8beb3210b7893288f191915","branchLabelMapping":{"^v8.12.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Fleet","Team:Defend
Workflows","OLM
Sprint","v8.11.0","v8.12.0"],"number":168005,"url":"https://github.com/elastic/kibana/pull/168005","mergeCommit":{"message":"[Security
Solution] [Endpoint] Upload action endpoint tests and other missing
workflow tests for response console (#168005)\n\n## Summary\r\n\r\nAdds
missing tests for upload response action workflows.\r\n\r\n Adds tests
for \r\n- [x] upload response action\r\n- [x] responder action from
Alerts -> Alert details\r\n- [x] responder action from Cases -> Alert
details\r\n- [x] responder action from Timeline view -> Alert
Details\r\n\r\n### Checklist\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Patryk Kopycinski
<contact@patrykkopycinski.com>\r\nCo-authored-by: Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"6fd7a867ea7ba56db8beb3210b7893288f191915"}},"sourceBranch":"main","suggestedTargetBranches":["8.11"],"targetPullRequestStates":[{"branch":"8.11","label":"v8.11.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.12.0","labelRegex":"^v8.12.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/168005","number":168005,"mergeCommit":{"message":"[Security
Solution] [Endpoint] Upload action endpoint tests and other missing
workflow tests for response console (#168005)\n\n## Summary\r\n\r\nAdds
missing tests for upload response action workflows.\r\n\r\n Adds tests
for \r\n- [x] upload response action\r\n- [x] responder action from
Alerts -> Alert details\r\n- [x] responder action from Cases -> Alert
details\r\n- [x] responder action from Timeline view -> Alert
Details\r\n\r\n### Checklist\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Patryk Kopycinski
<contact@patrykkopycinski.com>\r\nCo-authored-by: Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"6fd7a867ea7ba56db8beb3210b7893288f191915"}}]}]
BACKPORT-->

Co-authored-by: Ash <1849116+ashokaditya@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2023-10-27 06:10:18 -04:00 committed by GitHub
parent b3385d532d
commit e6d88ba201
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 372 additions and 25 deletions

View file

@ -10,12 +10,12 @@ import { Readable } from 'stream';
import type { estypes } from '@elastic/elasticsearch';
import type {
FleetFile,
FleetFromHostFileClientInterface,
FleetToHostFileClientInterface,
HapiReadableStream,
HostUploadedFileMetadata,
} from './types';
import type { FleetFile } from './types';
import type { HostUploadedFileMetadata } from './types';
export const createFleetFromHostFilesClientMock =
(): jest.Mocked<FleetFromHostFileClientInterface> => {

View file

@ -22,8 +22,6 @@ describe(
const [endpointAgentId, endpointHostname] = generateRandomStringName(2);
before(() => {
login(ROLE.endpoint_response_actions_access);
indexEndpointHosts({ numResponseActions: 2 }).then((indexEndpoints) => {
endpointData = indexEndpoints;
});
@ -59,6 +57,10 @@ describe(
}
});
beforeEach(() => {
login(ROLE.endpoint_response_actions_access);
});
it('enable filtering by type', () => {
cy.visit(`/app/security/administration/response_actions_history`);

View file

@ -0,0 +1,115 @@
/*
* 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 { closeAllToasts } from '../../tasks/toasts';
import {
getAlertsTableRows,
openAlertDetailsView,
openInvestigateInTimelineView,
openResponderFromEndpointAlertDetails,
} from '../../screens/alerts';
import { ensureOnResponder } from '../../screens/responder';
import { cleanupRule, loadRule } from '../../tasks/api_fixtures';
import type { PolicyData } from '../../../../../common/endpoint/types';
import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services';
import { waitForEndpointListPageToBeLoaded } from '../../tasks/response_console';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet';
import { toggleRuleOffAndOn, visitRuleAlerts } from '../../tasks/isolate';
import { login } from '../../tasks/login';
import { enableAllPolicyProtections } from '../../tasks/endpoint_policy';
import { createEndpointHost } from '../../tasks/create_endpoint_host';
import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data';
describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
let policy: PolicyData;
let createdHost: CreateAndEnrollEndpointHostResponse;
beforeEach(() => {
login();
});
before(() => {
getEndpointIntegrationVersion().then((version) =>
createAgentPolicyTask(version).then((data) => {
indexedPolicy = data;
policy = indexedPolicy.integrationPolicies[0];
return enableAllPolicyProtections(policy.id).then(() => {
// Create and enroll a new Endpoint host
return createEndpointHost(policy.policy_id).then((host) => {
createdHost = host as CreateAndEnrollEndpointHostResponse;
});
});
})
);
});
after(() => {
if (createdHost) {
cy.task('destroyEndpointHost', createdHost);
}
if (indexedPolicy) {
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
}
if (createdHost) {
deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] });
}
});
describe('From Alerts', () => {
let ruleId: string;
let ruleName: string;
before(() => {
loadRule(
{ query: `agent.name: ${createdHost.hostname} and agent.type: endpoint` },
false
).then((data) => {
ruleId = data.id;
ruleName = data.name;
});
});
after(() => {
if (ruleId) {
cleanupRule(ruleId);
}
});
it('should open responder from alert details flyout', () => {
waitForEndpointListPageToBeLoaded(createdHost.hostname);
toggleRuleOffAndOn(ruleName);
visitRuleAlerts(ruleName);
closeAllToasts();
getAlertsTableRows().should('have.length.greaterThan', 0);
openAlertDetailsView();
openResponderFromEndpointAlertDetails();
ensureOnResponder();
});
it('should open responder from timeline view alert details flyout', () => {
waitForEndpointListPageToBeLoaded(createdHost.hostname);
toggleRuleOffAndOn(ruleName);
visitRuleAlerts(ruleName);
closeAllToasts();
getAlertsTableRows().should('have.length.greaterThan', 0);
openInvestigateInTimelineView();
cy.getByTestSubj('timeline-flyout').within(() => {
openAlertDetailsView();
});
openResponderFromEndpointAlertDetails();
ensureOnResponder();
});
});
});

View file

@ -0,0 +1,122 @@
/*
* 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 { loadPage } from '../../tasks/common';
import { closeAllToasts } from '../../tasks/toasts';
import {
addAlertToCase,
getAlertsTableRows,
openAlertDetailsView,
openResponderFromEndpointAlertDetails,
} from '../../screens/alerts';
import { ensureOnResponder } from '../../screens/responder';
import { cleanupCase, cleanupRule, loadCase, loadRule } from '../../tasks/api_fixtures';
import type { PolicyData } from '../../../../../common/endpoint/types';
import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services';
import { waitForEndpointListPageToBeLoaded } from '../../tasks/response_console';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet';
import { openCaseAlertDetails, toggleRuleOffAndOn, visitRuleAlerts } from '../../tasks/isolate';
import { login } from '../../tasks/login';
import { enableAllPolicyProtections } from '../../tasks/endpoint_policy';
import { createEndpointHost } from '../../tasks/create_endpoint_host';
import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data';
import { APP_CASES_PATH } from '../../../../../common/constants';
describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
let policy: PolicyData;
let createdHost: CreateAndEnrollEndpointHostResponse;
before(() => {
getEndpointIntegrationVersion().then((version) =>
createAgentPolicyTask(version).then((data) => {
indexedPolicy = data;
policy = indexedPolicy.integrationPolicies[0];
return enableAllPolicyProtections(policy.id).then(() => {
// Create and enroll a new Endpoint host
return createEndpointHost(policy.policy_id).then((host) => {
createdHost = host as CreateAndEnrollEndpointHostResponse;
});
});
})
);
});
beforeEach(() => {
login();
});
after(() => {
if (createdHost) {
cy.task('destroyEndpointHost', createdHost);
}
if (indexedPolicy) {
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
}
if (createdHost) {
deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] });
}
});
describe('From Cases', () => {
let ruleId: string;
let ruleName: string;
let caseId: string;
const caseOwner = 'securitySolution';
beforeEach(() => {
loadRule(
{ query: `agent.name: ${createdHost.hostname} and agent.type: endpoint` },
false
).then((data) => {
ruleId = data.id;
ruleName = data.name;
});
loadCase(caseOwner).then((data) => {
caseId = data.id;
});
});
afterEach(() => {
if (ruleId) {
cleanupRule(ruleId);
}
if (caseId) {
cleanupCase(caseId);
}
});
it('should open responder', () => {
waitForEndpointListPageToBeLoaded(createdHost.hostname);
toggleRuleOffAndOn(ruleName);
visitRuleAlerts(ruleName);
closeAllToasts();
getAlertsTableRows().should('have.length.greaterThan', 0);
openAlertDetailsView();
addAlertToCase(caseId, caseOwner);
// visit case details page
cy.intercept('GET', `/api/cases/${caseId}/user_actions/_find*`).as('case');
loadPage(`${APP_CASES_PATH}/${caseId}`);
cy.wait('@case', { timeout: 30000 }).then(({ response: res }) => {
const caseAlertId = res?.body.userActions[1].id;
closeAllToasts();
openCaseAlertDetails(caseAlertId);
});
openResponderFromEndpointAlertDetails();
ensureOnResponder();
});
});
});

View file

@ -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 { ensureOnResponder } from '../../screens/responder';
import type { PolicyData } from '../../../../../common/endpoint/types';
import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services';
import {
openResponseConsoleFromEndpointList,
waitForEndpointListPageToBeLoaded,
} from '../../tasks/response_console';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet';
import { login } from '../../tasks/login';
import { enableAllPolicyProtections } from '../../tasks/endpoint_policy';
import { createEndpointHost } from '../../tasks/create_endpoint_host';
import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data';
describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
beforeEach(() => {
login();
});
describe('From endpoint list', () => {
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
let policy: PolicyData;
let createdHost: CreateAndEnrollEndpointHostResponse;
before(() => {
getEndpointIntegrationVersion().then((version) =>
createAgentPolicyTask(version).then((data) => {
indexedPolicy = data;
policy = indexedPolicy.integrationPolicies[0];
return enableAllPolicyProtections(policy.id).then(() => {
// Create and enroll a new Endpoint host
return createEndpointHost(policy.policy_id).then((host) => {
createdHost = host as CreateAndEnrollEndpointHostResponse;
});
});
})
);
});
after(() => {
if (createdHost) {
cy.task('destroyEndpointHost', createdHost);
}
if (indexedPolicy) {
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
}
if (createdHost) {
deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] });
}
});
it('should open responder', () => {
waitForEndpointListPageToBeLoaded(createdHost.hostname);
openResponseConsoleFromEndpointList();
ensureOnResponder();
});
});
});

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { openAlertDetailsView } from '../../screens/alerts';
import type { PolicyData } from '../../../../../common/endpoint/types';
import { APP_CASES_PATH, APP_ENDPOINTS_PATH } from '../../../../../common/constants';
import { closeAllToasts } from '../../tasks/toasts';
@ -14,7 +15,6 @@ import {
checkFlyoutEndpointIsolation,
filterOutIsolatedHosts,
isolateHostWithComment,
openAlertDetails,
openCaseAlertDetails,
releaseHostWithComment,
toggleRuleOffAndOn,
@ -139,7 +139,7 @@ describe.skip('Isolate command', { tags: ['@ess', '@serverless', '@brokenInServe
visitRuleAlerts(ruleName);
closeAllToasts();
openAlertDetails();
openAlertDetailsView();
isolateHostWithComment(isolateComment, createdHost.hostname);
@ -147,7 +147,7 @@ describe.skip('Isolate command', { tags: ['@ess', '@serverless', '@brokenInServe
cy.contains(`Isolation on host ${createdHost.hostname} successfully submitted`);
cy.getByTestSubj('euiFlyoutCloseButton').click();
openAlertDetails();
openAlertDetailsView();
checkFlyoutEndpointIsolation();
@ -156,7 +156,7 @@ describe.skip('Isolate command', { tags: ['@ess', '@serverless', '@brokenInServe
cy.contains(`Release on host ${createdHost.hostname} successfully submitted`);
cy.getByTestSubj('euiFlyoutCloseButton').click();
openAlertDetails();
openAlertDetailsView();
cy.getByTestSubj('event-field-agent.status').within(() => {
cy.get('[title="Isolated"]').should('not.exist');
});
@ -205,7 +205,7 @@ describe.skip('Isolate command', { tags: ['@ess', '@serverless', '@brokenInServe
visitRuleAlerts(ruleName);
closeAllToasts();
openAlertDetails();
openAlertDetailsView();
cy.getByTestSubj('add-to-existing-case-action').click();
cy.getByTestSubj(`cases-table-row-select-${caseId}`).click();

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { openAlertDetailsView } from '../../screens/alerts';
import { getEndpointListPath } from '../../../common/routing';
import {
checkEndpointIsIsolated,
@ -12,7 +13,6 @@ import {
filterOutIsolatedHosts,
interceptActionRequests,
isolateHostWithComment,
openAlertDetails,
openCaseAlertDetails,
releaseHostWithComment,
sendActionResponse,
@ -148,7 +148,7 @@ describe('Isolate command', { tags: ['@ess', '@serverless'] }, () => {
});
});
openAlertDetails();
openAlertDetailsView();
isolateHostWithComment(isolateComment, hostname);
@ -167,7 +167,7 @@ describe('Isolate command', { tags: ['@ess', '@serverless'] }, () => {
cy.getByTestSubj('euiFlyoutCloseButton').click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
openAlertDetails();
openAlertDetailsView();
checkFlyoutEndpointIsolation();
@ -185,7 +185,7 @@ describe('Isolate command', { tags: ['@ess', '@serverless'] }, () => {
cy.contains(`Release on host ${hostname} successfully submitted`);
cy.getByTestSubj('euiFlyoutCloseButton').click();
openAlertDetails();
openAlertDetailsView();
cy.getByTestSubj('event-field-agent.status').within(() => {
cy.get('[title="Isolated"]').should('not.exist');
});

View file

@ -16,7 +16,7 @@ import {
waitForEndpointListPageToBeLoaded,
} from '../../tasks/response_console';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { getEndpointIntegrationVersion, createAgentPolicyTask } from '../../tasks/fleet';
import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet';
import {
checkEndpointListForOnlyIsolatedHosts,
checkEndpointListForOnlyUnIsolatedHosts,
@ -188,7 +188,7 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles
});
});
describe('File operations: get-file and execute', () => {
describe('File operations: get-file, upload and execute', () => {
const homeFilePath = process.env.CI || true ? '/home/vagrant' : `/home/ubuntu`;
const fileContent = 'This is a test file for the get-file command.';
@ -271,6 +271,22 @@ describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerles
submitCommand();
waitForCommandToBeExecuted('execute');
});
it('"upload --file" - should upload a file', () => {
waitForEndpointListPageToBeLoaded(createdHost.hostname);
openResponseConsoleFromEndpointList();
inputConsoleCommand(`upload --file`);
cy.getByTestSubj('console-arg-file-picker').selectFile(
{
contents: Cypress.Buffer.from('upload file content here!'),
fileName: 'upload_file.txt',
lastModified: Date.now(),
},
{ force: true }
);
submitCommand();
waitForCommandToBeExecuted('upload');
});
});
describe('document signing', () => {

View file

@ -13,7 +13,7 @@ export const navigateToAlertsList = (urlQueryParams: string = '') => {
};
export const clickAlertListRefreshButton = (): Cypress.Chainable => {
cy.getByTestSubj('querySubmitButton').click();
cy.getByTestSubj('querySubmitButton').first().click();
return cy.getByTestSubj('querySubmitButton').should('be.enabled');
};
@ -41,3 +41,22 @@ export const getAlertsTableRows = (timeout?: number): Cypress.Chainable<JQuery<H
)
.then(() => $rows);
};
export const openAlertDetailsView = (rowIndex: number = 0): void => {
cy.getByTestSubj('expand-event').eq(rowIndex).click();
cy.getByTestSubj('take-action-dropdown-btn').click();
};
export const openInvestigateInTimelineView = (): void => {
cy.getByTestSubj('send-alert-to-timeline-button').first().click();
};
export const openResponderFromEndpointAlertDetails = (): void => {
cy.getByTestSubj('endpointResponseActions-action-item').click();
};
export const addAlertToCase = (caseId: string, caseOwner: string): void => {
cy.getByTestSubj('add-to-existing-case-action').click();
cy.getByTestSubj(`cases-table-row-select-${caseId}`).click();
cy.contains(`An alert was added to \"Test ${caseOwner} case`);
};

View file

@ -30,7 +30,7 @@ export const getConsoleHelpPanelResponseActionTestSubj = (): Record<
};
};
const ensureOnResponder = (): Cypress.Chainable<JQuery<HTMLDivElement>> => {
export const ensureOnResponder = (): Cypress.Chainable<JQuery<HTMLDivElement>> => {
return cy.getByTestSubj<HTMLDivElement>(TEST_SUBJ.responderPage).should('exist');
};

View file

@ -23,12 +23,13 @@
// ***********************************************************
import { subj as testSubjSelector } from '@kbn/test-subj-selector';
import 'cypress-react-selector';
// @ts-ignore
import registerCypressGrep from '@cypress/grep';
import { login, ROLE } from '../tasks/login';
import { loadPage } from '../tasks/common';
registerCypressGrep();
Cypress.Commands.addQuery<'getByTestSubj'>(
@ -100,3 +101,10 @@ Cypress.Commands.add(
);
Cypress.on('uncaught:exception', () => false);
// Login as a Platform Engineer to properly initialize Security Solution App
before(() => {
login(ROLE.soc_manager);
loadPage('/app/security/alerts');
cy.getByTestSubj('manage-alert-detection-rules').should('exist');
});

View file

@ -7,6 +7,7 @@
/* eslint-disable cypress/no-unnecessary-waiting */
import { openAlertDetailsView } from '../screens/alerts';
import type { ActionDetails } from '../../../../common/endpoint/types';
import { loadPage } from './common';
@ -46,11 +47,6 @@ export const releaseHostWithComment = (comment: string, hostname: string): void
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();
@ -84,7 +80,7 @@ export const checkFlyoutEndpointIsolation = (): void => {
} else {
cy.getByTestSubj('euiFlyoutCloseButton').click();
cy.wait(5000);
openAlertDetails();
openAlertDetailsView();
cy.getByTestSubj('event-field-agent.status').within(() => {
cy.contains('Isolated');
});