[Security Solution] Add e2e tests for Endpoint policy updates on Endpoints (#153938)

**>> Reopened to avoid unnecessary notifications for unrelated teams
<<**
Original PR with original comments:
https://github.com/elastic/kibana/pull/152097

## Summary

Added test cases:
- `endpoints.cy.ts`:
- Edit a Policy assigned to a real Endpoint and confirm that the
Endpoint returns a successful Policy response
- `artifacts.cy.ts`:
- Add a trusted application and confirm that the Endpoint returns a
successful Policy response
- Add an Event filter and confirm that the Endpoint returns a successful
Policy response
- Add a Blocklist entry and confirm that the Endpoint returns a
successful Policy response
- Add a Host Isolation exception and confirm that the Endpoint returns a
successful Policy response

To open Cypress for the new e2e test suite, first run this command:
`node scripts/build_kibana_platform_plugins`
Then use this command:
`yarn --cwd x-pack/plugins/security_solution
cypress:dw:endpoint:open-as-ci`

> **Warning**
> The `Endpoint reassignment` test group in `endpoints.cy.ts` will most
probably fail, due to this bug:
https://github.com/elastic/endpoint-dev/issues/12499 (as mentioned in
the PR for that test: https://github.com/elastic/kibana/pull/151887)
>
> So it's the best to skip that one, otherwise the endpoint will freeze
in *Out-of-date* state and you need to spin up the test suite again.

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Gergő Ábrahám 2023-03-31 15:03:58 +02:00 committed by GitHub
parent 684203944c
commit 7ea4722fb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 179 additions and 23 deletions

View file

@ -0,0 +1,92 @@
/*
* 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 { recurse } from 'cypress-recurse';
import { HOST_METADATA_LIST_ROUTE } from '../../../../../common/endpoint/constants';
import type { MetadataListResponse } from '../../../../../common/endpoint/types';
import { APP_ENDPOINTS_PATH } from '../../../../../common/constants';
import { getArtifactsListTestsData } from '../../fixtures/artifacts_page';
import { removeAllArtifacts } from '../../tasks/artifacts';
import { loadEndpointDataForEventFiltersIfNeeded } from '../../tasks/load_endpoint_data';
import { login } from '../../tasks/login';
import { performUserActions } from '../../tasks/perform_user_actions';
import { request } from '../../tasks/common';
import { yieldEndpointPolicyRevision } from '../../tasks/fleet';
const yieldAppliedEndpointRevision = (): Cypress.Chainable<number> =>
request<MetadataListResponse>({
method: 'GET',
url: HOST_METADATA_LIST_ROUTE,
}).then(({ body }) => {
expect(body.data.length).is.lte(1); // during update it can be temporary zero
return Number(body.data?.[0]?.metadata.Endpoint.policy.applied.endpoint_policy_version) ?? -1;
});
const parseRevNumber = (revString: string) => Number(revString.match(/\d+/)?.[0]);
describe('Artifact pages', () => {
before(() => {
login();
loadEndpointDataForEventFiltersIfNeeded();
removeAllArtifacts();
// wait for ManifestManager to pick up artifact changes that happened either here
// or in a previous test suite `after`
cy.wait(6000); // packagerTaskInterval + 1s
yieldEndpointPolicyRevision().then((actualEndpointPolicyRevision) => {
const hasReachedActualRevision = (revision: number) =>
revision === actualEndpointPolicyRevision;
// need to wait until revision is bumped to ensure test success
recurse(yieldAppliedEndpointRevision, hasReachedActualRevision, { delay: 1500 });
});
});
beforeEach(() => {
login();
});
after(() => {
removeAllArtifacts();
});
for (const testData of getArtifactsListTestsData()) {
describe(`${testData.title}`, () => {
it(`should update Endpoint Policy on Endpoint when adding ${testData.artifactName}`, () => {
cy.visit(APP_ENDPOINTS_PATH);
cy.getByTestSubj('policyListRevNo')
.first()
.invoke('text')
.then(parseRevNumber)
.then((initialRevisionNumber) => {
cy.visit(`/app/security/administration/${testData.urlPath}`);
cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).click();
performUserActions(testData.create.formActions);
cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click();
// Check new artifact is in the list
for (const checkResult of testData.create.checkResults) {
cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value);
}
cy.visit(APP_ENDPOINTS_PATH);
// depends on the 10s auto refresh
cy.getByTestSubj('policyListRevNo')
.first()
.should(($div) => {
const revisionNumber = parseRevNumber($div.text());
expect(revisionNumber).to.eq(initialRevisionNumber + 1);
});
});
});
});
}
});

View file

@ -6,6 +6,7 @@
*/
import type { Agent } from '@kbn/fleet-plugin/common';
import { APP_ENDPOINTS_PATH } from '../../../../../common/constants';
import { ENDPOINT_VM_NAME } from '../../tasks/common';
import {
getAgentByHostName,
@ -13,7 +14,6 @@ import {
reassignAgentPolicy,
} from '../../tasks/fleet';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { getEndpointListPath } from '../../../common/routing';
import { login } from '../../tasks/login';
import {
AGENT_HOSTNAME_CELL,
@ -34,7 +34,7 @@ describe('Endpoints page', () => {
});
it('Shows endpoint on the list', () => {
cy.visit(getEndpointListPath({ name: 'endpointList' }));
cy.visit(APP_ENDPOINTS_PATH);
cy.contains('Hosts running Elastic Defend').should('exist');
cy.getByTestSubj(AGENT_HOSTNAME_CELL).should('have.text', endpointHostname);
});
@ -48,9 +48,12 @@ describe('Endpoints page', () => {
initialAgentData = agentData;
});
getEndpointIntegrationVersion().then((version) => {
const policyName = `Reassign ${Math.random().toString(36).substring(2, 7)}`;
cy.task<IndexedFleetEndpointPolicyResponse>('indexFleetEndpointPolicy', {
policyName: `Reassign ${Math.random().toString(36).substr(2, 5)}`,
policyName,
endpointPackageVersion: version,
agentPolicyName: policyName,
}).then((data) => {
response = data;
});
@ -71,7 +74,7 @@ describe('Endpoints page', () => {
});
it('User can reassign a single endpoint to a different Agent Configuration', () => {
cy.visit(getEndpointListPath({ name: 'endpointList' }));
cy.visit(APP_ENDPOINTS_PATH);
const hostname = cy
.getByTestSubj(AGENT_HOSTNAME_CELL)
.filter(`:contains("${endpointHostname}")`);
@ -92,4 +95,38 @@ describe('Endpoints page', () => {
.should('have.text', response.agentPolicies[0].name);
});
});
it('should update endpoint policy on Endpoint', () => {
const parseRevNumber = (revString: string) => Number(revString.match(/\d+/)?.[0]);
cy.visit(APP_ENDPOINTS_PATH);
cy.getByTestSubj('policyListRevNo')
.first()
.invoke('text')
.then(parseRevNumber)
.then((initialRevisionNumber) => {
// Update policy
cy.getByTestSubj('policyNameCellLink').first().click();
cy.getByTestSubj('policyDetailsSaveButton').click();
cy.getByTestSubj('policyDetailsConfirmModal').should('exist');
cy.getByTestSubj('confirmModalConfirmButton').click();
cy.contains(/has been updated/);
cy.getByTestSubj('policyDetailsBackLink').click();
// Assert disappearing 'Out-of-date' indicator, Success Policy Status and increased revision number
cy.getByTestSubj('rowPolicyOutOfDate').should('exist');
cy.getByTestSubj('rowPolicyOutOfDate').should('not.exist'); // depends on the 10s auto-refresh
cy.getByTestSubj('policyStatusCellLink').first().should('contain', 'Success');
cy.getByTestSubj('policyListRevNo')
.first()
.invoke('text')
.then(parseRevNumber)
.should('equal', initialRevisionNumber + 1);
});
});
});

View file

@ -81,12 +81,12 @@ describe('Artifact tabs in Policy Details page', () => {
});
for (const testData of getArtifactsListTestsData()) {
beforeEach(() => {
login();
removeExceptionsList(testData.createRequestBody.list_id);
});
describe(`${testData.title} tab`, () => {
beforeEach(() => {
login();
removeExceptionsList(testData.createRequestBody.list_id);
});
it(`[NONE] User cannot see the tab for ${testData.title}`, () => {
loginWithPrivilegeNone(testData.privilegePrefix);
visitPolicyDetailsPage();

View file

@ -54,12 +54,19 @@ export const dataLoaders = (
indexFleetEndpointPolicy: async ({
policyName,
endpointPackageVersion,
agentPolicyName,
}: {
policyName: string;
endpointPackageVersion: string;
agentPolicyName?: string;
}) => {
const { kbnClient } = await stackServicesPromise;
return indexFleetEndpointPolicy(kbnClient, policyName, endpointPackageVersion);
return indexFleetEndpointPolicy(
kbnClient,
policyName,
endpointPackageVersion,
agentPolicyName
);
},
deleteIndexedFleetEndpointPolicies: async (indexData: IndexedFleetEndpointPolicyResponse) => {

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type { GetPackagePoliciesResponse } from '@kbn/fleet-plugin/common';
import { PACKAGE_POLICY_API_ROOT } from '@kbn/fleet-plugin/common';
import type {
ExceptionListItemSchema,
@ -79,14 +80,11 @@ export const createPerPolicyArtifact = (name: string, body: object, policyId?: '
});
};
export const yieldFirstPolicyID = () => {
return cy
.request({
method: 'GET',
url: `${PACKAGE_POLICY_API_ROOT}?page=1&perPage=1&kuery=ingest-package-policies.package.name: endpoint`,
})
.then(({ body }) => {
expect(body.items.length).to.be.least(1);
return body.items[0].id;
});
};
export const yieldFirstPolicyID = (): Cypress.Chainable<string> =>
request<GetPackagePoliciesResponse>({
method: 'GET',
url: `${PACKAGE_POLICY_API_ROOT}?page=1&perPage=1&kuery=ingest-package-policies.package.name: endpoint`,
}).then(({ body }) => {
expect(body.items.length).to.be.least(1);
return body.items[0].id;
});

View file

@ -5,8 +5,17 @@
* 2.0.
*/
import type { Agent, GetAgentsResponse, GetInfoResponse } from '@kbn/fleet-plugin/common';
import { agentRouteService, epmRouteService } from '@kbn/fleet-plugin/common';
import type {
Agent,
GetAgentsResponse,
GetInfoResponse,
GetPackagePoliciesResponse,
} from '@kbn/fleet-plugin/common';
import {
agentRouteService,
epmRouteService,
packagePolicyRouteService,
} from '@kbn/fleet-plugin/common';
import type { PutAgentReassignResponse } from '@kbn/fleet-plugin/common/types';
import { request } from './common';
@ -36,3 +45,14 @@ export const reassignAgentPolicy = (
policy_id: agentPolicyId,
},
});
export const yieldEndpointPolicyRevision = (): Cypress.Chainable<number> =>
request<GetPackagePoliciesResponse>({
method: 'GET',
url: packagePolicyRouteService.getListPath(),
qs: {
kuery: 'ingest-package-policies.package.name: endpoint',
},
}).then(({ body }) => {
return body.items?.[0]?.revision ?? -1;
});

View file

@ -25,6 +25,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
`--xpack.fleet.agents.elasticsearch.host=http://${hostIp}:${defendWorkflowsCypressConfig.get(
'servers.elasticsearch.port'
)}`,
// set the packagerTaskInterval to 5s in order to speed up test executions when checking fleet artifacts
'--xpack.securitySolution.packagerTaskInterval=5s',
],
},
testRunner: DefendWorkflowsCypressEndpointTestRunner,