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 51cd74de62f67..712d9439c52ad 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 @@ -145,6 +145,30 @@ declare global { arg: { endpointAgentIds: string[] }, options?: Partial ): Chainable; + + task( + name: 'createFileOnEndpoint', + arg: { hostname: string; path: string; content: string }, + options?: Partial + ): Chainable; + + task( + name: 'uploadFileToEndpoint', + arg: { hostname: string; srcPath: string; destPath: string }, + options?: Partial + ): Chainable; + + task( + name: 'installPackagesOnEndpoint', + arg: { hostname: string; packages: string[] }, + options?: Partial + ): Chainable; + + task( + name: 'readZippedFileContentOnEndpoint', + arg: { hostname: string; path: string; password?: string }, + options?: Partial + ): Chainable; } } } diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts index a2dc9cde510d0..b305a367b34e7 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts @@ -168,6 +168,74 @@ describe('Response console', () => { }); }); + describe('User journey for Get file command', () => { + let response: IndexedFleetEndpointPolicyResponse; + let initialAgentData: Agent; + + 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; + }); + + getEndpointIntegrationVersion().then((version) => + createAgentPolicyTask(version).then((data) => { + response = data; + }) + ); + + 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 (response) { + cy.task('deleteIndexedFleetEndpointPolicies', response); + } + }); + + it('"get-file --path" - should retrieve a file', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`get-file --path ${filePath}`); + submitCommand(); + cy.getByTestSubj('getFileSuccess', { timeout: 60000 }).within(() => { + cy.contains('File retrieved from the host.'); + cy.contains('(ZIP file passcode: elastic)'); + cy.contains( + 'Files are periodically deleted to clear storage space. Download and save file locally if needed.' + ); + cy.contains('Click here to download').click(); + const downloadsFolder = Cypress.config('downloadsFolder'); + cy.readFile(`${downloadsFolder}/upload.zip`); + + cy.task('uploadFileToEndpoint', { + hostname: endpointHostname, + srcPath: `${downloadsFolder}/upload.zip`, + destPath: '/home/ubuntu/upload.zip', + }); + + cy.task('readZippedFileContentOnEndpoint', { + hostname: endpointHostname, + path: '/home/ubuntu/upload.zip', + password: 'elastic', + }).then((unzippedFileContent) => { + expect(unzippedFileContent).to.equal(fileContent); + }); + }); + }); + }); + describe('document signing', () => { let response: IndexedFleetEndpointPolicyResponse; let initialAgentData: Agent; diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/response_console.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/response_console.cy.ts index 2877a91f6b63c..5d31b378c9617 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/response_console.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/response_console.cy.ts @@ -227,4 +227,53 @@ describe('Response console', () => { cy.contains('Action completed.', { timeout: 120000 }).should('exist'); }); }); + + describe('Get file command', () => { + let endpointData: ReturnTypeFromChainable; + let endpointHostname: string; + let getFileRequestResponse: 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 get file from response console', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + openResponseConsoleFromEndpointList(); + inputConsoleCommand(`get-file --path /test/path/test.txt`); + + interceptActionRequests((responseBody) => { + getFileRequestResponse = responseBody; + }, 'get-file'); + submitCommand(); + cy.contains('Retrieving the file from host.').should('exist'); + cy.wait('@get-file').then(() => { + sendActionResponse(getFileRequestResponse); + }); + cy.getByTestSubj('getFileSuccess').within(() => { + cy.contains('File retrieved from the host.'); + cy.contains('(ZIP file passcode: elastic)'); + cy.contains( + 'Files are periodically deleted to clear storage space. Download and save file locally if needed.' + ); + cy.contains('Click here to download').click(); + }); + + const downloadsFolder = Cypress.config('downloadsFolder'); + cy.readFile(`${downloadsFolder}/upload.zip`); + }); + }); }); 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 1af2aee01b4e2..a219bbf37d274 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,6 +8,7 @@ // / import type { CasePostRequest } from '@kbn/cases-plugin/common/api'; +import execa from 'execa'; import { sendEndpointActionResponse, sendFleetActionResponse, @@ -224,6 +225,70 @@ export const dataLoadersForRealEndpoints = ( return destroyEndpointHost(kbnClient, createdHost).then(() => null); }, + createFileOnEndpoint: async ({ + hostname, + path, + content, + }: { + hostname: string; + path: string; + content: string; + }): Promise => { + await execa(`multipass`, ['exec', hostname, '--', 'sh', '-c', `echo ${content} > ${path}`]); + return null; + }, + + uploadFileToEndpoint: async ({ + hostname, + srcPath, + destPath = '.', + }: { + hostname: string; + srcPath: string; + destPath: string; + }): Promise => { + await execa(`multipass`, ['transfer', srcPath, `${hostname}:${destPath}`]); + return null; + }, + + installPackagesOnEndpoint: async ({ + hostname, + packages, + }: { + hostname: string; + packages: string[]; + }): Promise => { + await execa(`multipass`, [ + 'exec', + hostname, + '--', + 'sh', + '-c', + `sudo apt install -y ${packages.join(' ')}`, + ]); + return null; + }, + + readZippedFileContentOnEndpoint: async ({ + hostname, + path, + password, + }: { + hostname: string; + path: string; + password?: string; + }): Promise => { + const result = await execa(`multipass`, [ + 'exec', + hostname, + '--', + 'sh', + '-c', + `unzip -p ${password ? `-P ${password} ` : ''}${path}`, + ]); + return result.stdout; + }, + stopEndpointHost: async () => { const hosts = await getEndpointHosts(); const hostName = hosts[0].name;