[8.8] [Security Solutions][Endpoint][Response Actions] File operations cypress tests (#156604) (#158187)

# Backport

This will backport the following commits from `main` to `8.8`:
- [[Security Solutions][Endpoint][Response Actions] File operations
cypress tests (#156604)](https://github.com/elastic/kibana/pull/156604)

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

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

<!--BACKPORT
[{"author":{"name":"Ashokaditya","email":"1849116+ashokaditya@users.noreply.github.com"},"sourceCommit":{"committedDate":"2023-05-22T13:30:40Z","message":"[Security
Solutions][Endpoint][Response Actions] File operations cypress tests
(#156604)\n\n## Summary\r\n\r\nAdds e2e tests for `get-file` and
`execute` response actions.\r\n\r\n~- [ ] Test response actions fail
without signing~\r\n- [x] Ensure other endpoint tests are not using the
same endpoint for\r\ntests\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","sha":"9b50ae9546565793808c4ac710b790edacc584a2","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Defend
Workflows","OLM
Sprint","v8.8.0","v8.9.0"],"number":156604,"url":"https://github.com/elastic/kibana/pull/156604","mergeCommit":{"message":"[Security
Solutions][Endpoint][Response Actions] File operations cypress tests
(#156604)\n\n## Summary\r\n\r\nAdds e2e tests for `get-file` and
`execute` response actions.\r\n\r\n~- [ ] Test response actions fail
without signing~\r\n- [x] Ensure other endpoint tests are not using the
same endpoint for\r\ntests\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","sha":"9b50ae9546565793808c4ac710b790edacc584a2"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"8.8","label":"v8.8.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/156604","number":156604,"mergeCommit":{"message":"[Security
Solutions][Endpoint][Response Actions] File operations cypress tests
(#156604)\n\n## Summary\r\n\r\nAdds e2e tests for `get-file` and
`execute` response actions.\r\n\r\n~- [ ] Test response actions fail
without signing~\r\n- [x] Ensure other endpoint tests are not using the
same endpoint for\r\ntests\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","sha":"9b50ae9546565793808c4ac710b790edacc584a2"}}]}]
BACKPORT-->
This commit is contained in:
Ashokaditya 2023-05-30 12:11:39 +02:00 committed by GitHub
parent a1590a7eed
commit 0665fb4f62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 215 additions and 117 deletions

View file

@ -9,7 +9,8 @@ import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_dat
import { getAlertsTableRows, navigateToAlertsList } from '../../screens/alerts';
import { waitForEndpointAlerts } from '../../tasks/alerts';
import { request } from '../../tasks/common';
import { getEndpointIntegrationVersion } from '../../tasks/fleet';
import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet';
import { createEndpointHost } from '../../tasks/create_endpoint_host';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { enableAllPolicyProtections } from '../../tasks/endpoint_policy';
import type { PolicyData, ResponseActionApiResponse } from '../../../../../common/endpoint/types';
@ -25,29 +26,15 @@ describe('Endpoint generated alerts', () => {
before(() => {
getEndpointIntegrationVersion().then((version) => {
const policyName = `alerts test ${Math.random().toString(36).substring(2, 7)}`;
cy.task<IndexedFleetEndpointPolicyResponse>('indexFleetEndpointPolicy', {
policyName,
endpointPackageVersion: version,
agentPolicyName: policyName,
}).then((data) => {
createAgentPolicyTask(version, 'alerts test').then((data) => {
indexedPolicy = data;
policy = indexedPolicy.integrationPolicies[0];
return enableAllPolicyProtections(policy.id).then(() => {
// Create and enroll a new Endpoint host
return cy
.task(
'createEndpointHost',
{
agentPolicyId: policy.policy_id,
},
{ timeout: 180000 }
)
.then((host) => {
createdHost = host as CreateAndEnrollEndpointHostResponse;
});
return createEndpointHost(policy.policy_id).then((host) => {
createdHost = host as CreateAndEnrollEndpointHostResponse;
});
});
});
});

View file

@ -9,6 +9,7 @@ import type { Agent } from '@kbn/fleet-plugin/common';
import { APP_ENDPOINTS_PATH } from '../../../../../common/constants';
import { ENDPOINT_VM_NAME } from '../../tasks/common';
import {
createAgentPolicyTask,
getAgentByHostName,
getEndpointIntegrationVersion,
reassignAgentPolicy,
@ -48,13 +49,7 @@ describe('Endpoints page', () => {
initialAgentData = agentData;
});
getEndpointIntegrationVersion().then((version) => {
const policyName = `Reassign ${Math.random().toString(36).substring(2, 7)}`;
cy.task<IndexedFleetEndpointPolicyResponse>('indexFleetEndpointPolicy', {
policyName,
endpointPackageVersion: version,
agentPolicyName: policyName,
}).then((data) => {
createAgentPolicyTask(version).then((data) => {
response = data;
});
});

View file

@ -12,7 +12,6 @@ import {
checkEndpointListForOnlyIsolatedHosts,
checkEndpointListForOnlyUnIsolatedHosts,
checkFlyoutEndpointIsolation,
createAgentPolicyTask,
filterOutEndpoints,
filterOutIsolatedHosts,
isolateHostWithComment,
@ -28,6 +27,7 @@ import { ENDPOINT_VM_NAME } from '../../tasks/common';
import { login } from '../../tasks/login';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import {
createAgentPolicyTask,
getAgentByHostName,
getEndpointIntegrationVersion,
reassignAgentPolicy,

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import type { Agent } from '@kbn/fleet-plugin/common';
import type { PolicyData } from '../../../../../common/endpoint/types';
import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services';
import {
inputConsoleCommand,
openResponseConsoleFromEndpointList,
@ -15,18 +16,17 @@ import {
waitForEndpointListPageToBeLoaded,
} from '../../tasks/response_console';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import {
getAgentByHostName,
getEndpointIntegrationVersion,
reassignAgentPolicy,
} from '../../tasks/fleet';
import { getEndpointIntegrationVersion, createAgentPolicyTask } from '../../tasks/fleet';
import {
checkEndpointListForOnlyIsolatedHosts,
checkEndpointListForOnlyUnIsolatedHosts,
createAgentPolicyTask,
} from '../../tasks/isolate';
import { login } from '../../tasks/login';
import { ENDPOINT_VM_NAME } from '../../tasks/common';
import { enableAllPolicyProtections } from '../../tasks/endpoint_policy';
import { createEndpointHost } from '../../tasks/create_endpoint_host';
import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data';
describe('Response console', () => {
const endpointHostname = Cypress.env(ENDPOINT_VM_NAME);
@ -36,77 +36,100 @@ describe('Response console', () => {
});
describe('User journey for Isolate command: isolate and release an endpoint', () => {
let response: IndexedFleetEndpointPolicyResponse;
let initialAgentData: Agent;
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
let policy: PolicyData;
let createdHost: CreateAndEnrollEndpointHostResponse;
before(() => {
getAgentByHostName(endpointHostname).then((agentData) => {
initialAgentData = agentData;
});
getEndpointIntegrationVersion().then((version) =>
createAgentPolicyTask(version).then((data) => {
response = 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 (initialAgentData?.policy_id) {
reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id);
if (createdHost) {
cy.task('destroyEndpointHost', createdHost).then(() => {});
}
if (response) {
cy.task('deleteIndexedFleetEndpointPolicies', response);
if (indexedPolicy) {
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
}
if (createdHost) {
deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] });
}
});
it('should isolate host from response console', () => {
const command = 'isolate';
waitForEndpointListPageToBeLoaded(endpointHostname);
checkEndpointListForOnlyUnIsolatedHosts();
openResponseConsoleFromEndpointList();
performCommandInputChecks('isolate');
performCommandInputChecks(command);
submitCommand();
waitForCommandToBeExecuted();
waitForCommandToBeExecuted(command);
waitForEndpointListPageToBeLoaded(endpointHostname);
checkEndpointListForOnlyIsolatedHosts();
});
it('should release host from response console', () => {
const command = 'release';
waitForEndpointListPageToBeLoaded(endpointHostname);
checkEndpointListForOnlyIsolatedHosts();
openResponseConsoleFromEndpointList();
performCommandInputChecks('release');
performCommandInputChecks(command);
submitCommand();
waitForCommandToBeExecuted();
waitForCommandToBeExecuted(command);
waitForEndpointListPageToBeLoaded(endpointHostname);
checkEndpointListForOnlyUnIsolatedHosts();
});
});
describe('User journey for Processes commands: list, kill and suspend process.', () => {
let response: IndexedFleetEndpointPolicyResponse;
let initialAgentData: Agent;
describe('User journey for Processes operations: list, kill and suspend process', () => {
let cronPID: string;
let newCronPID: string;
before(() => {
getAgentByHostName(endpointHostname).then((agentData) => {
initialAgentData = agentData;
});
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
let policy: PolicyData;
let createdHost: CreateAndEnrollEndpointHostResponse;
before(() => {
getEndpointIntegrationVersion().then((version) =>
createAgentPolicyTask(version).then((data) => {
response = 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 (initialAgentData?.policy_id) {
reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id);
if (createdHost) {
cy.task('destroyEndpointHost', createdHost).then(() => {});
}
if (response) {
cy.task('deleteIndexedFleetEndpointPolicies', response);
if (indexedPolicy) {
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
}
if (createdHost) {
deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] });
}
});
@ -140,7 +163,7 @@ describe('Response console', () => {
openResponseConsoleFromEndpointList();
inputConsoleCommand(`kill-process --pid ${cronPID}`);
submitCommand();
waitForCommandToBeExecuted();
waitForCommandToBeExecuted('kill-process');
performCommandInputChecks('processes');
submitCommand();
@ -164,43 +187,47 @@ describe('Response console', () => {
openResponseConsoleFromEndpointList();
inputConsoleCommand(`suspend-process --pid ${newCronPID}`);
submitCommand();
waitForCommandToBeExecuted();
waitForCommandToBeExecuted('suspend-process');
});
});
describe('User journey for Get file command', () => {
let response: IndexedFleetEndpointPolicyResponse;
let initialAgentData: Agent;
describe('File operations: get-file and execute', () => {
const homeFilePath = `/home/ubuntu`;
const fileContent = 'This is a test file for the get-file command.';
const filePath = `/home/ubuntu/test_file.txt`;
before(() => {
getAgentByHostName(endpointHostname).then((agentData) => {
initialAgentData = agentData;
});
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
let policy: PolicyData;
let createdHost: CreateAndEnrollEndpointHostResponse;
before(() => {
getEndpointIntegrationVersion().then((version) =>
createAgentPolicyTask(version).then((data) => {
response = 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;
});
});
})
);
cy.task('installPackagesOnEndpoint', { hostname: endpointHostname, packages: ['unzip'] });
cy.task('createFileOnEndpoint', {
hostname: endpointHostname,
path: filePath,
content: fileContent,
});
});
after(() => {
if (initialAgentData?.policy_id) {
reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id);
if (createdHost) {
cy.task('destroyEndpointHost', createdHost).then(() => {});
}
if (response) {
cy.task('deleteIndexedFleetEndpointPolicies', response);
if (indexedPolicy) {
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
}
if (createdHost) {
deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] });
}
});
@ -234,30 +261,48 @@ describe('Response console', () => {
});
});
});
it('"execute --command" - should execute a command', async () => {
waitForEndpointListPageToBeLoaded(endpointHostname);
openResponseConsoleFromEndpointList();
inputConsoleCommand(`execute --command "ls -al ${homeFilePath}"`);
submitCommand();
waitForCommandToBeExecuted('execute');
});
});
describe('document signing', () => {
let response: IndexedFleetEndpointPolicyResponse;
let initialAgentData: Agent;
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
let policy: PolicyData;
let createdHost: CreateAndEnrollEndpointHostResponse;
before(() => {
getAgentByHostName(endpointHostname).then((agentData) => {
initialAgentData = agentData;
});
getEndpointIntegrationVersion().then((version) =>
createAgentPolicyTask(version).then((data) => {
response = 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 (initialAgentData?.policy_id) {
reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id);
if (createdHost) {
cy.task('destroyEndpointHost', createdHost).then(() => {});
}
if (response) {
cy.task('deleteIndexedFleetEndpointPolicies', response);
if (indexedPolicy) {
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
}
if (createdHost) {
deleteAllLoadedEndpointData({ endpointAgentIds: [createdHost.agentId] });
}
});

View file

@ -29,7 +29,7 @@ describe('Response console', () => {
login();
});
describe('Isolate command', () => {
describe('`isolate` command', () => {
let endpointData: ReturnTypeFromChainable<typeof indexEndpointHosts>;
let endpointHostname: string;
let isolateRequestResponse: ActionDetails;
@ -71,7 +71,7 @@ describe('Response console', () => {
});
});
describe('Release command', () => {
describe('`release` command', () => {
let endpointData: ReturnTypeFromChainable<typeof indexEndpointHosts>;
let endpointHostname: string;
let releaseRequestResponse: ActionDetails;
@ -110,7 +110,7 @@ describe('Response console', () => {
});
});
describe('Processes command', () => {
describe('`processes` command', () => {
let endpointData: ReturnTypeFromChainable<typeof indexEndpointHosts>;
let endpointHostname: string;
let processesRequestResponse: ActionDetails;
@ -150,7 +150,7 @@ describe('Response console', () => {
});
});
describe('Kill process command', () => {
describe('`kill-process` command', () => {
let endpointData: ReturnTypeFromChainable<typeof indexEndpointHosts>;
let endpointHostname: string;
let killProcessRequestResponse: ActionDetails;
@ -189,7 +189,7 @@ describe('Response console', () => {
});
});
describe('Suspend process command', () => {
describe('`suspend-process` command', () => {
let endpointData: ReturnTypeFromChainable<typeof indexEndpointHosts>;
let endpointHostname: string;
let suspendProcessRequestResponse: ActionDetails;
@ -228,7 +228,7 @@ describe('Response console', () => {
});
});
describe('Get file command', () => {
describe('`get-file` command', () => {
let endpointData: ReturnTypeFromChainable<typeof indexEndpointHosts>;
let endpointHostname: string;
let getFileRequestResponse: ActionDetails;
@ -276,4 +276,43 @@ describe('Response console', () => {
cy.readFile(`${downloadsFolder}/upload.zip`);
});
});
describe('`execute` command', () => {
let endpointData: ReturnTypeFromChainable<typeof indexEndpointHosts>;
let endpointHostname: string;
let executeRequestResponse: ActionDetails;
before(() => {
indexEndpointHosts({ withResponseActions: false, isolation: false }).then(
(indexEndpoints) => {
endpointData = indexEndpoints;
endpointHostname = endpointData.data.hosts[0].host.name;
}
);
});
after(() => {
if (endpointData) {
endpointData.cleanup();
// @ts-expect-error ignore setting to undefined
endpointData = undefined;
}
});
it('should execute a command from response console', () => {
waitForEndpointListPageToBeLoaded(endpointHostname);
openResponseConsoleFromEndpointList();
inputConsoleCommand(`execute --command "ls -al"`);
interceptActionRequests((responseBody) => {
executeRequestResponse = responseBody;
}, 'execute');
submitCommand();
cy.contains('Action pending.').should('exist');
cy.wait('@execute').then(() => {
sendActionResponse(executeRequestResponse);
});
cy.contains('Command execution was successful', { timeout: 120000 }).should('exist');
});
});
});

View file

@ -0,0 +1,22 @@
/*
* 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 { CreateAndEnrollEndpointHostResponse } from '../../../../scripts/endpoint/common/endpoint_host_services';
// only used in "real" endpoint tests not in mocked ones
export const createEndpointHost = (
agentPolicyId: string,
timeout?: number
): Cypress.Chainable<CreateAndEnrollEndpointHostResponse> => {
return cy.task(
'createEndpointHost',
{
agentPolicyId,
},
{ timeout: timeout ?? 180000 }
);
};

View file

@ -17,6 +17,7 @@ import {
packagePolicyRouteService,
} from '@kbn/fleet-plugin/common';
import type { PutAgentReassignResponse } from '@kbn/fleet-plugin/common/types';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { request } from './common';
export const getEndpointIntegrationVersion = (): Cypress.Chainable<string> =>
@ -56,3 +57,16 @@ export const yieldEndpointPolicyRevision = (): Cypress.Chainable<number> =>
}).then(({ body }) => {
return body.items?.[0]?.revision ?? -1;
});
export const createAgentPolicyTask = (
version: string,
policyPrefix?: string
): Cypress.Chainable<IndexedFleetEndpointPolicyResponse> => {
const policyName = `${policyPrefix || 'Reassign'} ${Math.random().toString(36).substring(2, 7)}`;
return cy.task<IndexedFleetEndpointPolicyResponse>('indexFleetEndpointPolicy', {
policyName,
endpointPackageVersion: version,
agentPolicyName: policyName,
});
};

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import type { IndexedFleetEndpointPolicyResponse } from '../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import type { ActionDetails } from '../../../../common/endpoint/types';
const API_ENDPOINT_ACTION_PATH = '/api/endpoint/action/*';
@ -111,18 +110,6 @@ export const filterOutEndpoints = (endpointHostname: string): void => {
});
};
export const createAgentPolicyTask = (
version: string
): Cypress.Chainable<IndexedFleetEndpointPolicyResponse> => {
const policyName = `Reassign ${Math.random().toString(36).substring(2, 7)}`;
return cy.task<IndexedFleetEndpointPolicyResponse>('indexFleetEndpointPolicy', {
policyName,
endpointPackageVersion: version,
agentPolicyName: policyName,
});
};
export const filterOutIsolatedHosts = (): void => {
cy.getByTestSubj('adminSearchBar').click().type('united.endpoint.Endpoint.state.isolation: true');
cy.getByTestSubj('querySubmitButton').click();

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type { ConsoleResponseActionCommands } from '../../../../common/endpoint/service/response_actions/constants';
import { closeAllToasts } from './close_all_toasts';
import { APP_ENDPOINTS_PATH } from '../../../../common/constants';
import Chainable = Cypress.Chainable;
@ -46,9 +47,17 @@ export const submitCommand = (): void => {
cy.getByTestSubj('endpointResponseActionsConsole-inputTextSubmitButton').click();
};
export const waitForCommandToBeExecuted = (): void => {
cy.contains('Action pending.').should('exist');
cy.contains('Action completed.', { timeout: 120000 }).should('exist');
export const waitForCommandToBeExecuted = (command: ConsoleResponseActionCommands): void => {
const actionResultMessage =
command === 'execute'
? 'Command execution was successful'
: command === 'get-file'
? 'File retrieved from the host.'
: 'Action completed.';
const actionPendingMessage =
command === 'get-file' ? 'Retrieving the file from host.' : 'Action pending.';
cy.contains(actionPendingMessage).should('exist');
cy.contains(actionResultMessage, { timeout: 120000 }).should('exist');
};
export const performCommandInputChecks = (command: string) => {

View file

@ -57,7 +57,7 @@ export const enrollEndpointHost = async (): Promise<string | undefined> => {
try {
const uniqueId = Math.random().toString().substring(2, 6);
const username = userInfo().username.toLowerCase().replace('.', '-'); // Multipass doesn't like periods in username
const username = userInfo().username.toLowerCase().replaceAll('.', '-'); // Multipass doesn't like periods in username
const policyId: string = policy || (await getOrCreateAgentPolicyId());
if (!policyId) {