[Security Solution][Endpoint] Move endpoint management serverless specific tests (#166669)

## Summary


- Moves the Serverless Endpoint Management tests from
`x-pack/test_serverless/functional/test_suites/security/cypress` to
`x-pack/plugins/security_solution/public/management/cypress` along with
its set of supporting `screens` and `task`'s
- This work is in preparation for enabling tests to be run in a
serverless environment

>   NOTE
> Tests for serverless are not currently running. The kibana core team
is working on providing serverless users that have serverless security
roles assigned to them for testing. Also, an effort in underway to also
use a tagging approach to define which existing tests should run in
serverless.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Paul Tavares 2023-09-20 12:28:10 -04:00 committed by GitHub
parent 3f3a27b20a
commit 23b2f3b080
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 450 additions and 389 deletions

3
.github/CODEOWNERS vendored
View file

@ -1320,9 +1320,6 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib
/x-pack/test/security_solution_endpoint/ @elastic/security-defend-workflows
/x-pack/test/security_solution_endpoint_api_int/ @elastic/security-defend-workflows
/x-pack/test_serverless/shared/lib/security/kibana_roles/ @elastic/security-defend-workflows
/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management @elastic/security-defend-workflows
/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management @elastic/security-defend-workflows
/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/endpoint_management @elastic/security-defend-workflows
/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management @elastic/security-defend-workflows
/x-pack/plugins/security_solution_serverless/server/endpoint @elastic/security-defend-workflows

View file

@ -30,8 +30,8 @@ export {};
import 'cypress-react-selector';
import registerCypressGrep from '@cypress/grep';
import type { ServerlessRoleName } from './roles';
import { login } from '../../../../test_serverless/functional/test_suites/security/cypress/tasks/login';
import type { ServerlessRoleName } from './roles';
registerCypressGrep();

View file

@ -29,6 +29,6 @@
},
"@kbn/security-solution-plugin",
"@kbn/fleet-plugin",
"@kbn/cases-plugin"
"@kbn/cases-plugin",
]
}

View file

@ -6,6 +6,12 @@
*/
import type { Agent } from '@kbn/fleet-plugin/common';
import {
AGENT_HOSTNAME_CELL,
AGENT_POLICY_CELL,
TABLE_ROW_ACTIONS,
TABLE_ROW_ACTIONS_MENU,
} from '../../screens';
import type { PolicyData } from '../../../../../common/endpoint/types';
import { APP_ENDPOINTS_PATH } from '../../../../../common/constants';
import {
@ -17,16 +23,10 @@ import {
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { login } from '../../tasks/login';
import { loadPage } from '../../tasks/common';
import {
AGENT_HOSTNAME_CELL,
TABLE_ROW_ACTIONS,
TABLE_ROW_ACTIONS_MENU,
AGENT_POLICY_CELL,
} from '../../screens/endpoints';
import {
FLEET_REASSIGN_POLICY_MODAL,
FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON,
} from '../../screens/fleet';
} from '../../screens/fleet/agent_details';
import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services';
import { createEndpointHost } from '../../tasks/create_endpoint_host';
import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data';

View file

@ -5,13 +5,13 @@
* 2.0.
*/
import { navigateToEndpointPolicyResponse } from '../../screens';
import type { CyIndexEndpointHosts } from '../../tasks/index_endpoint_hosts';
import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts';
import { navigateToEndpointPolicyResponse } from '../../screens/endpoints';
import type { HostMetadata } from '../../../../../common/endpoint/types';
import type { IndexedEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_endpoint_policy_response';
import { login } from '../../tasks/login';
import { navigateToFleetAgentDetails } from '../../screens/fleet';
import { navigateToFleetAgentDetails } from '../../screens/fleet/agent_details';
import { EndpointPolicyResponseGenerator } from '../../../../../common/endpoint/data_generators/endpoint_policy_response_generator';
import { descriptions } from '../../../components/policy_response/policy_response_friendly_names';

View file

@ -0,0 +1,6 @@
# Serverless only test
Directory contains tests that are only applicable to serverless.
Any other type of tests should be placed instead into the `./endpoint` directory or `./mocked_data` directories and tagged appropriately

View file

@ -5,17 +5,15 @@
* 2.0.
*/
import { login } from '../../tasks/login';
import type { CyIndexEndpointHosts } from '../../tasks/index_endpoint_hosts';
import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts';
import { loginServerless } from '../../tasks/login_serverless';
import {
getConsoleActionMenuItem,
getUnIsolateActionMenuItem,
openRowActionMenu,
visitEndpointList,
} from '../../screens/endpoint_management';
import {
CyIndexEndpointHosts,
indexEndpointHosts,
} from '../../tasks/endpoint_management/index_endpoint_hosts';
} from '../../screens';
describe(
'When on the Endpoint List in Security Essentials PLI',
@ -43,7 +41,7 @@ describe(
});
beforeEach(() => {
login();
loginServerless();
visitEndpointList();
openRowActionMenu();
});

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants';
import { login } from '../../../tasks/login';
import { getNoPrivilegesPage } from '../../../screens/endpoint_management/common';
import { getEndpointManagementPageList } from '../../../screens/endpoint_management';
import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management';
import { ensureResponseActionAuthzAccess } from '../../../tasks/response_actions';
import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless';
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../../../common/endpoint/service/response_actions/constants';
import { getNoPrivilegesPage } from '../../../screens/common';
import { getEndpointManagementPageList } from '../../../screens';
describe(
'App Features for Security Complete PLI',
@ -30,7 +30,7 @@ describe(
let password: string;
beforeEach(() => {
login('endpoint_operations_analyst').then((response) => {
loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST).then((response) => {
username = response.username;
password = response.password;
});

View file

@ -5,11 +5,14 @@
* 2.0.
*/
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants';
import { login } from '../../../tasks/login';
import { getAgentListTable, visitFleetAgentList } from '../../../screens';
import { getEndpointManagementPageList } from '../../../screens/endpoint_management';
import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management';
import { ensureResponseActionAuthzAccess } from '../../../tasks/response_actions';
import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless';
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../../../common/endpoint/service/response_actions/constants';
import {
getEndpointManagementPageList,
getFleetAgentListTable,
visitFleetAgentList,
} from '../../../screens';
describe(
'App Features for Security Complete PLI with Endpoint Complete Addon',
@ -29,7 +32,7 @@ describe(
let password: string;
beforeEach(() => {
login('endpoint_operations_analyst').then((response) => {
loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST).then((response) => {
username = response.username;
password = response.password;
});
@ -50,7 +53,7 @@ describe(
it(`should have access to Fleet`, () => {
visitFleetAgentList();
getAgentListTable().should('exist');
getFleetAgentListTable().should('exist');
});
}
);

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants';
import { login } from '../../../tasks/login';
import { getNoPrivilegesPage } from '../../../screens/endpoint_management/common';
import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management';
import { getEndpointManagementPageList } from '../../../screens/endpoint_management';
import { ensureResponseActionAuthzAccess } from '../../../tasks/response_actions';
import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless';
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../../../common/endpoint/service/response_actions/constants';
import { getNoPrivilegesPage } from '../../../screens/common';
import { getEndpointManagementPageList } from '../../../screens';
describe(
'App Features for Security Essential PLI',
@ -32,7 +32,7 @@ describe(
let password: string;
beforeEach(() => {
login('endpoint_operations_analyst').then((response) => {
loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST).then((response) => {
username = response.username;
password = response.password;
});

View file

@ -5,11 +5,14 @@
* 2.0.
*/
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants';
import { login } from '../../../tasks/login';
import { getAgentListTable, visitFleetAgentList } from '../../../screens';
import { getEndpointManagementPageMap } from '../../../screens/endpoint_management';
import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management';
import { ensureResponseActionAuthzAccess } from '../../../tasks/response_actions';
import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless';
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../../../common/endpoint/service/response_actions/constants';
import {
getEndpointManagementPageMap,
getFleetAgentListTable,
visitFleetAgentList,
} from '../../../screens';
describe(
'App Features for Security Essentials PLI with Endpoint Essentials Addon',
@ -37,7 +40,7 @@ describe(
let password: string;
beforeEach(() => {
login('endpoint_operations_analyst').then((response) => {
loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST).then((response) => {
username = response.username;
password = response.password;
});
@ -71,7 +74,7 @@ describe(
it(`should have access to Fleet`, () => {
visitFleetAgentList();
getAgentListTable().should('exist');
getFleetAgentListTable().should('exist');
});
}
);

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { IndexedFleetEndpointPolicyResponse } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { login } from '../../tasks/login';
import { visitPolicyDetails } from '../../screens/endpoint_management/policy_details';
import { loginServerless } from '../../tasks/login_serverless';
import { visitPolicyDetailsPage } from '../../screens/policy_details';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
describe(
'When displaying the Policy Details in Security Essentials PLI',
@ -34,8 +34,8 @@ describe(
});
beforeEach(() => {
login();
visitPolicyDetails(loadedPolicyData.integrationPolicies[0].id);
loginServerless();
visitPolicyDetailsPage(loadedPolicyData.integrationPolicies[0].id);
});
it('should display upselling section for protections', () => {

View file

@ -6,10 +6,12 @@
*/
import { pick } from 'lodash';
import { login } from '../../../tasks/login';
import { ServerlessRoleName } from '../../../../../../../shared/lib';
import type { CyIndexEndpointHosts } from '../../../tasks/index_endpoint_hosts';
import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts';
import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless';
import { ensurePolicyDetailsPageAuthzAccess } from '../../../screens/policy_details';
import type { EndpointArtifactPageId } from '../../../screens';
import {
EndpointArtifactPageId,
ensureArtifactPageAuthzAccess,
ensureEndpointListPageAuthzAccess,
ensurePolicyListPageAuthzAccess,
@ -21,21 +23,12 @@ import {
openRowActionMenu,
visitEndpointList,
visitPolicyList,
} from '../../../screens/endpoint_management';
import {
ensurePermissionDeniedScreen,
getAgentListTable,
ensureFleetPermissionDeniedScreen,
getFleetAgentListTable,
visitFleetAgentList,
} from '../../../screens';
import {
getConsoleHelpPanelResponseActionTestSubj,
openConsoleHelpPanel,
} from '../../../screens/endpoint_management/response_console';
import { ensurePolicyDetailsPageAuthzAccess } from '../../../screens/endpoint_management/policy_details';
import {
CyIndexEndpointHosts,
indexEndpointHosts,
} from '../../../tasks/endpoint_management/index_endpoint_hosts';
} from '../../../screens';
describe(
'User Roles for Security Complete PLI with Endpoint Complete addon',
@ -69,12 +62,12 @@ describe(
});
// roles `t1_analyst` and `t2_analyst` are very similar with exception of one page
(['t1_analyst', `t2_analyst`] as ServerlessRoleName[]).forEach((roleName) => {
(['t1_analyst', `t2_analyst`] as ServerlessUser[]).forEach((roleName) => {
describe(`for role: ${roleName}`, () => {
const deniedPages = allPages.filter((page) => page.id !== 'endpointList');
beforeEach(() => {
login(roleName);
loginServerless(roleName);
});
it('should have READ access to Endpoint list page', () => {
@ -98,7 +91,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
it('should NOT have access to execute response actions', () => {
@ -130,7 +123,7 @@ describe(
const deniedResponseActions = pick(consoleHelpPanelResponseActionsTestSubj, 'execute');
beforeEach(() => {
login('t3_analyst');
loginServerless(ServerlessUser.T3_ANALYST);
});
it('should have access to Endpoint list page', () => {
@ -154,7 +147,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
describe('Response Actions access', () => {
@ -182,7 +175,7 @@ describe(
const deniedPages = allPages.filter(({ id }) => id !== 'blocklist' && id !== 'endpointList');
beforeEach(() => {
login('threat_intelligence_analyst');
loginServerless(ServerlessUser.THREAT_INTELLIGENCE_ANALYST);
});
it('should have access to Endpoint list page', () => {
@ -205,7 +198,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
it('should have access to Response Actions Log', () => {
@ -227,7 +220,7 @@ describe(
];
beforeEach(() => {
login('rule_author');
loginServerless(ServerlessUser.RULE_AUTHOR);
});
for (const { id, title } of artifactPagesFullAccess) {
@ -254,7 +247,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
it('should have access to Response Actions Log', () => {
@ -278,7 +271,7 @@ describe(
const grantedAccessPages = [pageById.endpointList, pageById.policyList];
beforeEach(() => {
login('soc_manager');
loginServerless(ServerlessUser.SOC_MANAGER);
});
for (const { id, title } of artifactPagesFullAccess) {
@ -296,7 +289,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
describe('Response Actions access', () => {
@ -325,7 +318,7 @@ describe(
const grantedAccessPages = [pageById.endpointList, pageById.policyList];
beforeEach(() => {
login('endpoint_operations_analyst');
loginServerless(ServerlessUser.ENDPOINT_OPERATIONS_ANALYST);
});
for (const { id, title } of artifactPagesFullAccess) {
@ -352,59 +345,57 @@ describe(
it('should have access to Fleet', () => {
visitFleetAgentList();
getAgentListTable().should('exist');
getFleetAgentListTable().should('exist');
});
});
(['platform_engineer', 'endpoint_policy_manager'] as ServerlessRoleName[]).forEach(
(roleName) => {
describe(`for role: ${roleName}`, () => {
const artifactPagesFullAccess = [
pageById.trustedApps,
pageById.eventFilters,
pageById.blocklist,
pageById.hostIsolationExceptions,
];
const grantedAccessPages = [pageById.endpointList, pageById.policyList];
(['platform_engineer', 'endpoint_policy_manager'] as ServerlessUser[]).forEach((roleName) => {
describe(`for role: ${roleName}`, () => {
const artifactPagesFullAccess = [
pageById.trustedApps,
pageById.eventFilters,
pageById.blocklist,
pageById.hostIsolationExceptions,
];
const grantedAccessPages = [pageById.endpointList, pageById.policyList];
beforeEach(() => {
login(roleName);
});
for (const { id, title } of artifactPagesFullAccess) {
it(`should have CRUD access to: ${title}`, () => {
ensureArtifactPageAuthzAccess('all', id as EndpointArtifactPageId);
});
}
for (const { url, title } of grantedAccessPages) {
it(`should have access to: ${title}`, () => {
cy.visit(url);
getNoPrivilegesPage().should('not.exist');
});
}
it('should have access to Fleet', () => {
visitFleetAgentList();
getAgentListTable().should('exist');
});
it('should have access to Response Actions Log', () => {
cy.visit(pageById.responseActionLog);
if (roleName === 'endpoint_policy_manager') {
getNoPrivilegesPage().should('exist');
} else {
getNoPrivilegesPage().should('not.exist');
}
});
it('should NOT have access to execute response actions', () => {
visitEndpointList();
openRowActionMenu().findByTestSubj('console').should('not.exist');
});
beforeEach(() => {
loginServerless(roleName);
});
}
);
for (const { id, title } of artifactPagesFullAccess) {
it(`should have CRUD access to: ${title}`, () => {
ensureArtifactPageAuthzAccess('all', id as EndpointArtifactPageId);
});
}
for (const { url, title } of grantedAccessPages) {
it(`should have access to: ${title}`, () => {
cy.visit(url);
getNoPrivilegesPage().should('not.exist');
});
}
it('should have access to Fleet', () => {
visitFleetAgentList();
getFleetAgentListTable().should('exist');
});
it('should have access to Response Actions Log', () => {
cy.visit(pageById.responseActionLog);
if (roleName === 'endpoint_policy_manager') {
getNoPrivilegesPage().should('exist');
} else {
getNoPrivilegesPage().should('not.exist');
}
});
it('should NOT have access to execute response actions', () => {
visitEndpointList();
openRowActionMenu().findByTestSubj('console').should('not.exist');
});
});
});
}
);

View file

@ -5,28 +5,23 @@
* 2.0.
*/
import { login } from '../../../tasks/login';
import type { CyIndexEndpointHosts } from '../../../tasks/index_endpoint_hosts';
import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts';
import { loginServerless, ServerlessUser } from '../../../tasks/login_serverless';
import type { EndpointArtifactPageId } from '../../../screens';
import {
getNoPrivilegesPage,
getArtifactListEmptyStateAddButton,
getEndpointManagementPageMap,
getEndpointManagementPageList,
EndpointArtifactPageId,
ensureArtifactPageAuthzAccess,
ensureEndpointListPageAuthzAccess,
ensurePolicyListPageAuthzAccess,
} from '../../../screens/endpoint_management';
import {
ensurePermissionDeniedScreen,
getAgentListTable,
ensureFleetPermissionDeniedScreen,
getFleetAgentListTable,
visitFleetAgentList,
ensurePolicyDetailsPageAuthzAccess,
} from '../../../screens';
import { ServerlessRoleName } from '../../../../../../../shared/lib';
import { ensurePolicyDetailsPageAuthzAccess } from '../../../screens/endpoint_management/policy_details';
import {
CyIndexEndpointHosts,
indexEndpointHosts,
} from '../../../tasks/endpoint_management/index_endpoint_hosts';
describe(
'Roles for Security Essential PLI with Endpoint Essentials addon',
@ -59,12 +54,12 @@ describe(
});
// roles `t1_analyst` and `t2_analyst` are the same as far as endpoint access
(['t1_analyst', `t2_analyst`] as ServerlessRoleName[]).forEach((roleName) => {
(['t1_analyst', `t2_analyst`] as ServerlessUser[]).forEach((roleName) => {
describe(`for role: ${roleName}`, () => {
const deniedPages = allPages.filter((page) => page.id !== 'endpointList');
beforeEach(() => {
login(roleName);
loginServerless(roleName);
});
it('should have READ access to Endpoint list page', () => {
@ -80,7 +75,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
});
});
@ -93,7 +88,7 @@ describe(
];
beforeEach(() => {
login('t3_analyst');
loginServerless(ServerlessUser.T3_ANALYST);
});
it('should have access to Endpoint list page', () => {
@ -124,7 +119,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
});
@ -132,7 +127,7 @@ describe(
const deniedPages = allPages.filter(({ id }) => id !== 'blocklist' && id !== 'endpointList');
beforeEach(() => {
login('threat_intelligence_analyst');
loginServerless(ServerlessUser.THREAT_INTELLIGENCE_ANALYST);
});
it('should have access to Endpoint list page', () => {
@ -155,7 +150,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
});
@ -167,7 +162,7 @@ describe(
];
beforeEach(() => {
login('rule_author');
loginServerless(ServerlessUser.RULE_AUTHOR);
});
for (const { id, title } of artifactPagesFullAccess) {
@ -198,7 +193,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
});
@ -211,7 +206,7 @@ describe(
const grantedAccessPages = [pageById.endpointList, pageById.policyList];
beforeEach(() => {
login('soc_manager');
loginServerless(ServerlessUser.SOC_MANAGER);
});
for (const { id, title } of artifactPagesFullAccess) {
@ -236,7 +231,7 @@ describe(
it('should NOT have access to Fleet', () => {
visitFleetAgentList();
ensurePermissionDeniedScreen();
ensureFleetPermissionDeniedScreen();
});
});
@ -246,7 +241,7 @@ describe(
'platform_engineer',
`endpoint_operations_analyst`,
'endpoint_policy_manager',
] as ServerlessRoleName[]
] as ServerlessUser[]
).forEach((roleName) => {
describe(`for role: ${roleName}`, () => {
const artifactPagesFullAccess = [
@ -257,7 +252,7 @@ describe(
const grantedAccessPages = [pageById.endpointList, pageById.policyList];
beforeEach(() => {
login(roleName);
loginServerless(roleName);
});
for (const { id, title } of artifactPagesFullAccess) {
@ -282,7 +277,7 @@ describe(
it('should have access to Fleet', () => {
visitFleetAgentList();
getAgentListTable().should('exist');
getFleetAgentListTable().should('exist');
});
});
});

View file

@ -5,14 +5,11 @@
* 2.0.
*/
import { DeepReadonly } from 'utility-types';
import type { DeepReadonly } from 'utility-types';
import { subj as testSubjSelector } from '@kbn/test-subj-selector';
import {
EndpointArtifactPageId,
EndpointManagementPageMap,
getEndpointManagementPageMap,
} from './page_reference';
import { UserAuthzAccessLevel } from './types';
import type { EndpointArtifactPageId, EndpointManagementPageMap } from './page_reference';
import { getEndpointManagementPageMap } from './page_reference';
import type { UserAuthzAccessLevel } from './types';
const artifactPageTopTestSubjPrefix: Readonly<Record<EndpointArtifactPageId, string>> = {
trustedApps: 'trustedAppsListPage',

View file

@ -5,10 +5,14 @@
* 2.0.
*/
import { DeepReadonly } from 'utility-types';
import { EndpointManagementPageMap, getEndpointManagementPageMap } from './page_reference';
import { UserAuthzAccessLevel } from './types';
import type { DeepReadonly } from 'utility-types';
import type { EndpointManagementPageMap } from './page_reference';
import { getEndpointManagementPageMap } from './page_reference';
import type { UserAuthzAccessLevel } from './types';
import { getNoPrivilegesPage } from './common';
import { loadPage } from '../tasks/common';
import { APP_PATH } from '../../../../common';
import { getEndpointDetailsPath } from '../../common/routing';
interface ListRowOptions {
endpointId?: string;
@ -17,6 +21,11 @@ interface ListRowOptions {
rowIndex?: number;
}
export const TABLE_ROW_ACTIONS_MENU = 'tableRowActionsMenuPanel';
export const AGENT_HOSTNAME_CELL = 'hostnameCellLink';
export const AGENT_POLICY_CELL = 'policyNameCellLink';
export const TABLE_ROW_ACTIONS = 'endpointTableRowActions';
const pageById: DeepReadonly<EndpointManagementPageMap> = getEndpointManagementPageMap();
export const visitEndpointList = (): Cypress.Chainable => {
@ -81,3 +90,10 @@ export const getUnIsolateActionMenuItem = (): Cypress.Chainable => {
export const getConsoleActionMenuItem = (): Cypress.Chainable => {
return cy.getByTestSubj('tableRowActionsMenuPanel').findByTestSubj('console');
};
export const navigateToEndpointPolicyResponse = (endpointAgentId: string): void => {
loadPage(
APP_PATH +
getEndpointDetailsPath({ name: 'endpointPolicyResponse', selected_endpoint: endpointAgentId })
);
};

View file

@ -1,22 +0,0 @@
/*
* 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 { APP_PATH } from '../../../../common/constants';
import { getEndpointDetailsPath } from '../../common/routing';
import { loadPage } from '../tasks/common';
export const AGENT_HOSTNAME_CELL = 'hostnameCellLink';
export const AGENT_POLICY_CELL = 'policyNameCellLink';
export const TABLE_ROW_ACTIONS = 'endpointTableRowActions';
export const TABLE_ROW_ACTIONS_MENU = 'tableRowActionsMenuPanel';
export const navigateToEndpointPolicyResponse = (endpointAgentId: string): void => {
loadPage(
APP_PATH +
getEndpointDetailsPath({ name: 'endpointPolicyResponse', selected_endpoint: endpointAgentId })
);
};

View file

@ -6,7 +6,7 @@
*/
import { FLEET_BASE_PATH } from '@kbn/fleet-plugin/public/constants';
import { loadPage } from '../tasks/common';
import { loadPage } from '../../tasks/common';
export const FLEET_REASSIGN_POLICY_MODAL = 'agentReassignPolicyModal';
export const FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON = 'confirmModalConfirmButton';

View file

@ -8,9 +8,11 @@
import { FLEET_BASE_PATH } from '@kbn/fleet-plugin/public/constants';
export const visitFleetAgentList = (): Cypress.Chainable => {
// `failOnStatus` below is necesary because the page (when not accessible) will actually return
// a `4xx` error along with an HTML page to display.
return cy.visit(FLEET_BASE_PATH, { failOnStatusCode: false });
};
export const getAgentListTable = (): Cypress.Chainable => {
export const getFleetAgentListTable = (): Cypress.Chainable => {
return cy.getByTestSubj('fleetAgentListTable');
};

View file

@ -7,3 +7,4 @@
export * from './permission_denied';
export * from './agent_list';
export * from './agent_details';

View file

@ -9,6 +9,6 @@
* The screen normally returned by the API when a user does not have access to a Plugin.
* Note that the requested page will likely also receive an HTTP status code of `403`
*/
export const ensurePermissionDeniedScreen = (): Cypress.Chainable => {
export const ensureFleetPermissionDeniedScreen = (): Cypress.Chainable => {
return cy.contains('You do not have permission to access the requested page').should('exist');
};

View file

@ -8,6 +8,9 @@
export * from './common';
export * from './artifacts';
export * from './endpoint_list';
export * from './policy_details';
export * from './policy_list';
export * from './page_reference';
export * from './responder';
export * from './fleet';
export * from './types';

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { keyBy } from 'lodash';
import {
APP_BLOCKLIST_PATH,
APP_ENDPOINTS_PATH,
@ -13,8 +14,7 @@ import {
APP_POLICIES_PATH,
APP_RESPONSE_ACTIONS_HISTORY_PATH,
APP_TRUSTED_APPS_PATH,
} from '@kbn/security-solution-plugin/common/constants';
import { keyBy } from 'lodash';
} from '../../../../common/constants';
export interface EndpointManagementPageMap {
endpointList: EndpointManagementPage;

View file

@ -13,11 +13,18 @@ import type {
UpdatePackagePolicyResponse,
} from '@kbn/fleet-plugin/common';
import { packagePolicyRouteService } from '@kbn/fleet-plugin/common';
import type { UserAuthzAccessLevel } from './types';
import { APP_POLICIES_PATH } from '../../../../common/constants';
import type { PolicyConfig } from '../../../../common/endpoint/types';
import { request, loadPage } from '../tasks/common';
import { loadPage, request } from '../tasks/common';
import { expectAndCloseSuccessToast } from '../tasks/toasts';
import { getNoPrivilegesPage } from './common';
/**
* Loads the Policy details page - either for the `policyId` provided on input, or if undefined,
* then the first policy displayed on the Policy List will be opened
* @param policyId
*/
export const visitPolicyDetailsPage = (policyId?: string) => {
if (policyId) {
loadPage(`${APP_POLICIES_PATH}/${policyId}`);
@ -89,3 +96,23 @@ export class PackagePolicyBackupHelper {
});
}
}
export const ensurePolicyDetailsPageAuthzAccess = (
policyId: string,
accessLevel: UserAuthzAccessLevel,
visitPage: boolean = false
): Cypress.Chainable => {
if (visitPage) {
visitPolicyDetailsPage(policyId);
}
if (accessLevel === 'none') {
return getNoPrivilegesPage().should('exist');
}
if (accessLevel === 'read') {
return cy.getByTestSubj('policyDetailsSaveButton').should('not.exist');
}
return cy.getByTestSubj('policyDetailsSaveButton').should('exist');
};

View file

@ -5,11 +5,12 @@
* 2.0.
*/
import { DeepReadonly } from 'utility-types';
import { EndpointManagementPageMap, getEndpointManagementPageMap } from './page_reference';
import type { DeepReadonly } from 'utility-types';
import type { EndpointManagementPageMap } from './page_reference';
import { getEndpointManagementPageMap } from './page_reference';
import { getNoPrivilegesPage } from './common';
import { visitEndpointList } from './endpoint_list';
import { UserAuthzAccessLevel } from './types';
import type { UserAuthzAccessLevel } from './types';
const pageById: DeepReadonly<EndpointManagementPageMap> = getEndpointManagementPageMap();

View file

@ -6,6 +6,7 @@
*/
import { subj as testSubjSelector } from '@kbn/test-subj-selector';
import type { ConsoleResponseActionCommands } from '../../../../common/endpoint/service/response_actions/constants';
import { DATE_RANGE_OPTION_TO_TEST_SUBJ_MAP } from '../../../../common/test';
const TEST_SUBJ = Object.freeze({
@ -13,6 +14,22 @@ const TEST_SUBJ = Object.freeze({
actionLogFlyout: 'responderActionLogFlyout',
});
export const getConsoleHelpPanelResponseActionTestSubj = (): Record<
ConsoleResponseActionCommands,
string
> => {
return {
isolate: 'endpointResponseActionsConsole-commandList-Responseactions-isolate',
release: 'endpointResponseActionsConsole-commandList-Responseactions-release',
processes: 'endpointResponseActionsConsole-commandList-Responseactions-processes',
'kill-process': 'endpointResponseActionsConsole-commandList-Responseactions-kill-process',
'suspend-process': 'endpointResponseActionsConsole-commandList-Responseactions-suspend-process',
'get-file': 'endpointResponseActionsConsole-commandList-Responseactions-get-file',
execute: 'endpointResponseActionsConsole-commandList-Responseactions-execute',
upload: 'endpointResponseActionsConsole-commandList-Responseactions-upload',
};
};
const ensureOnResponder = (): Cypress.Chainable<JQuery<HTMLDivElement>> => {
return cy.getByTestSubj<HTMLDivElement>(TEST_SUBJ.responderPage).should('exist');
};
@ -59,3 +76,8 @@ export const setResponderActionLogDateRange = (
cy.getByTestSubj(DATE_RANGE_OPTION_TO_TEST_SUBJ_MAP[range]).click();
cy.getByTestSubj('superDatePickerQuickMenu').should('not.exist');
};
export const openConsoleHelpPanel = (): Cypress.Chainable => {
ensureOnResponder();
return cy.getByTestSubj('endpointResponseActionsConsole-header-helpButton').click();
};

View file

@ -0,0 +1,103 @@
/*
* 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 { LoginState } from '@kbn/security-plugin/common/login_state';
import { COMMON_API_HEADERS, request } from './common';
export enum ServerlessUser {
T1_ANALYST = 't1_analyst',
T2_ANALYST = 't2_analyst',
T3_ANALYST = 't3_analyst',
THREAT_INTELLIGENCE_ANALYST = 'threat_intelligence_analyst',
RULE_AUTHOR = 'rule_author',
SOC_MANAGER = 'soc_manager',
DETECTIONS_ADMIN = 'detections_admin',
PLATFORM_ENGINEER = 'platform_engineer',
ENDPOINT_OPERATIONS_ANALYST = 'endpoint_operations_analyst',
ENDPOINT_POLICY_MANAGER = 'endpoint_policy_manager',
}
/**
* Send login via API
* @param username
* @param password
*
* @private
*/
const sendApiLoginRequest = (
username: string,
password: string
): Cypress.Chainable<{ username: string; password: string }> => {
const baseUrl = Cypress.config().baseUrl;
const headers = { ...COMMON_API_HEADERS };
cy.log(`Authenticating [${username}] via ${baseUrl}`);
return request<LoginState>({ headers, url: `${baseUrl}/internal/security/login_state` })
.then((loginState) => {
const basicProvider = loginState.body.selector.providers.find(
(provider) => provider.type === 'basic'
);
return request({
url: `${baseUrl}/internal/security/login`,
method: 'POST',
headers,
body: {
providerType: basicProvider?.type,
providerName: basicProvider?.name,
currentURL: '/',
params: { username, password },
},
});
})
.then(() => ({ username, password }));
};
interface CyLoginTask {
(user?: ServerlessUser | 'elastic'): ReturnType<typeof sendApiLoginRequest>;
/**
* Login using any username/password
* @param username
* @param password
*/
with(username: string, password: string): ReturnType<typeof sendApiLoginRequest>;
}
/**
* Login to Kibana using API (not login page). By default, user will be logged in using
* the username and password defined via `KIBANA_USERNAME` and `KIBANA_PASSWORD` cypress env
* variables.
* @param user Defaults to `soc_manager`
*/
export const loginServerless: CyLoginTask = (
user: ServerlessUser | 'elastic' = ServerlessUser.SOC_MANAGER
): ReturnType<typeof sendApiLoginRequest> => {
const username = Cypress.env('KIBANA_USERNAME');
const password = Cypress.env('KIBANA_PASSWORD');
if (user && user !== 'elastic') {
throw new Error('Serverless usernames not yet implemented');
// return cy.task('loadUserAndRole', { name: user }).then((loadedUser) => {
// username = loadedUser.username;
// password = loadedUser.password;
//
// return sendApiLoginRequest(username, password);
// });
} else {
return sendApiLoginRequest(username, password);
}
};
loginServerless.with = (
username: string,
password: string
): ReturnType<typeof sendApiLoginRequest> => {
return sendApiLoginRequest(username, password);
};

View file

@ -5,10 +5,22 @@
* 2.0.
*/
import { request, loadPage } from './common';
import type { UserAuthzAccessLevel } from '../screens';
import { loadPage, request } from './common';
import { resolvePathVariables } from '../../../common/utils/resolve_path_variables';
import { ACTION_DETAILS_ROUTE } from '../../../../common/endpoint/constants';
import {
ACTION_DETAILS_ROUTE,
EXECUTE_ROUTE,
GET_FILE_ROUTE,
GET_PROCESSES_ROUTE,
ISOLATE_HOST_ROUTE_V2,
KILL_PROCESS_ROUTE,
SUSPEND_PROCESS_ROUTE,
UNISOLATE_HOST_ROUTE_V2,
UPLOAD_ROUTE,
} from '../../../../common/endpoint/constants';
import type { ActionDetails, ActionDetailsApiResponse } from '../../../../common/endpoint/types';
import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants';
import { ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS } from '../../../../common/endpoint/service/response_actions/constants';
export const validateAvailableCommands = () => {
@ -103,3 +115,95 @@ export const waitForActionToComplete = (
return action;
});
};
/**
* Ensure user has the given `accessLevel` to the type of response action
* @param accessLevel
* @param responseAction
* @param username
* @param password
*/
export const ensureResponseActionAuthzAccess = (
accessLevel: Exclude<UserAuthzAccessLevel, 'read'>,
responseAction: ResponseActionsApiCommandNames,
username: string,
password: string
): Cypress.Chainable => {
let url: string = '';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let apiPayload: any = {
endpoint_ids: ['some-id'],
};
switch (responseAction) {
case 'isolate':
url = ISOLATE_HOST_ROUTE_V2;
break;
case 'unisolate':
url = UNISOLATE_HOST_ROUTE_V2;
break;
case 'get-file':
url = GET_FILE_ROUTE;
Object.assign(apiPayload, { parameters: { path: 'one/two' } });
break;
case 'execute':
url = EXECUTE_ROUTE;
Object.assign(apiPayload, { parameters: { command: 'foo' } });
break;
case 'running-processes':
url = GET_PROCESSES_ROUTE;
break;
case 'kill-process':
url = KILL_PROCESS_ROUTE;
Object.assign(apiPayload, { parameters: { pid: 123 } });
break;
case 'suspend-process':
url = SUSPEND_PROCESS_ROUTE;
Object.assign(apiPayload, { parameters: { pid: 123 } });
break;
case 'upload':
url = UPLOAD_ROUTE;
{
const file = new File(['foo'], 'foo.txt');
const formData = new FormData();
formData.append('file', file, file.name);
for (const [key, value] of Object.entries(apiPayload as object)) {
formData.append(key, typeof value !== 'string' ? JSON.stringify(value) : value);
}
apiPayload = formData;
}
break;
default:
throw new Error(`Response action [${responseAction}] has no API payload defined`);
}
const requestOptions: Partial<Cypress.RequestOptions> = {
url,
method: 'post',
auth: {
user: username,
pass: password,
},
headers: {
'Content-Type': undefined,
},
failOnStatusCode: false,
body: apiPayload as Cypress.RequestBody,
};
if (accessLevel === 'none') {
return request(requestOptions).its('status').should('equal', 403);
}
return request(requestOptions).its('status').should('not.equal', 403);
};

View file

@ -6,12 +6,16 @@
*/
import { LEFT_NAVIGATION } from '../screens/landing_page';
import { login } from '../tasks/login';
import { navigatesToLandingPage } from '../tasks/navigation';
describe('Serverless', () => {
it('Should navigate to the landing page', () => {
login();
cy.visit('/', {
auth: {
username: 'elastic_serverless',
password: 'changeme',
},
});
navigatesToLandingPage();
cy.get(LEFT_NAVIGATION).should('exist');
});

View file

@ -1,34 +0,0 @@
/*
* 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 { APP_POLICIES_PATH } from '@kbn/security-solution-plugin/common/constants';
import { UserAuthzAccessLevel } from './types';
import { getNoPrivilegesPage } from './common';
export const visitPolicyDetails = (policyId: string): Cypress.Chainable => {
return cy.visit(`${APP_POLICIES_PATH}/${policyId}`);
};
export const ensurePolicyDetailsPageAuthzAccess = (
policyId: string,
accessLevel: UserAuthzAccessLevel,
visitPage: boolean = false
): Cypress.Chainable => {
if (visitPage) {
visitPolicyDetails(policyId);
}
if (accessLevel === 'none') {
return getNoPrivilegesPage().should('exist');
}
if (accessLevel === 'read') {
return cy.getByTestSubj('policyDetailsSaveButton').should('not.exist');
}
return cy.getByTestSubj('policyDetailsSaveButton').should('exist');
};

View file

@ -1,34 +0,0 @@
/*
* 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 { ConsoleResponseActionCommands } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants';
export const getConsoleHelpPanelResponseActionTestSubj = (): Record<
ConsoleResponseActionCommands,
string
> => {
return {
isolate: 'endpointResponseActionsConsole-commandList-Responseactions-isolate',
release: 'endpointResponseActionsConsole-commandList-Responseactions-release',
processes: 'endpointResponseActionsConsole-commandList-Responseactions-processes',
['kill-process']: 'endpointResponseActionsConsole-commandList-Responseactions-kill-process',
['suspend-process']:
'endpointResponseActionsConsole-commandList-Responseactions-suspend-process',
['get-file']: 'endpointResponseActionsConsole-commandList-Responseactions-get-file',
execute: 'endpointResponseActionsConsole-commandList-Responseactions-execute',
upload: 'endpointResponseActionsConsole-commandList-Responseactions-upload',
};
};
export const ensureResponseConsoleIsOpen = (): Cypress.Chainable => {
return cy.getByTestSubj('consolePageOverlay').should('exist');
};
export const openConsoleHelpPanel = (): Cypress.Chainable => {
ensureResponseConsoleIsOpen();
return cy.getByTestSubj('endpointResponseActionsConsole-header-helpButton').click();
};

View file

@ -5,5 +5,4 @@
* 2.0.
*/
export * from './fleet';
export * from './landing_page';

View file

@ -1,8 +0,0 @@
/*
* 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.
*/
export * from './response_actions';

View file

@ -1,111 +0,0 @@
/*
* 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 { ResponseActionsApiCommandNames } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants';
import { request } from '@kbn/security-solution-plugin/public/management/cypress/tasks/common';
import {
EXECUTE_ROUTE,
GET_FILE_ROUTE,
GET_PROCESSES_ROUTE,
ISOLATE_HOST_ROUTE_V2,
KILL_PROCESS_ROUTE,
SUSPEND_PROCESS_ROUTE,
UNISOLATE_HOST_ROUTE_V2,
UPLOAD_ROUTE,
} from '@kbn/security-solution-plugin/common/endpoint/constants';
import { UserAuthzAccessLevel } from '../../screens/endpoint_management';
/**
* Ensure user has the given `accessLevel` to the type of response action
* @param accessLevel
* @param responseAction
* @param username
* @param password
*/
export const ensureResponseActionAuthzAccess = (
accessLevel: Exclude<UserAuthzAccessLevel, 'read'>,
responseAction: ResponseActionsApiCommandNames,
username: string,
password: string
): Cypress.Chainable => {
let url: string = '';
let apiPayload: any = {
endpoint_ids: ['some-id'],
};
switch (responseAction) {
case 'isolate':
url = ISOLATE_HOST_ROUTE_V2;
break;
case 'unisolate':
url = UNISOLATE_HOST_ROUTE_V2;
break;
case 'get-file':
url = GET_FILE_ROUTE;
Object.assign(apiPayload, { parameters: { path: 'one/two' } });
break;
case 'execute':
url = EXECUTE_ROUTE;
Object.assign(apiPayload, { parameters: { command: 'foo' } });
break;
case 'running-processes':
url = GET_PROCESSES_ROUTE;
break;
case 'kill-process':
url = KILL_PROCESS_ROUTE;
Object.assign(apiPayload, { parameters: { pid: 123 } });
break;
case 'suspend-process':
url = SUSPEND_PROCESS_ROUTE;
Object.assign(apiPayload, { parameters: { pid: 123 } });
break;
case 'upload':
url = UPLOAD_ROUTE;
{
const file = new File(['foo'], 'foo.txt');
const formData = new FormData();
formData.append('file', file, file.name);
for (const [key, value] of Object.entries(apiPayload as object)) {
formData.append(key, typeof value !== 'string' ? JSON.stringify(value) : value);
}
apiPayload = formData;
}
break;
default:
throw new Error(`Response action [${responseAction}] has no API payload defined`);
}
const requestOptions: Partial<Cypress.RequestOptions> = {
url,
method: 'post',
auth: {
user: username,
pass: password,
},
headers: {
'Content-Type': undefined,
},
failOnStatusCode: false,
body: apiPayload as Cypress.RequestBody,
};
if (accessLevel === 'none') {
return request(requestOptions).its('status').should('equal', 403);
}
return request(requestOptions).its('status').should('not.equal', 403);
};

View file

@ -41,9 +41,7 @@
"@kbn/security-solution-plugin",
"@kbn/security-solution-plugin/public/management/cypress",
"@kbn/tooling-log",
"@kbn/fleet-plugin",
"@kbn/cases-plugin",
"@kbn/test-subj-selector",
"@kbn/core-http-common",
"@kbn/data-views-plugin",
"@kbn/core-saved-objects-server",