[Security Solution] [Cypress] SAML for Serverless login and role testing (#172655)

Relates to:
* https://github.com/elastic/kibana/issues/166340
* https://github.com/elastic/kibana/pull/170852
* https://github.com/elastic/kibana/pull/170417
* https://github.com/elastic/kibana/pull/172678

## Summary

In this PR we are using the code implemented on
https://github.com/elastic/kibana/pull/170417 and
https://github.com/elastic/kibana/pull/172678 to allow SAML and role
testing inside Cypress.

* We are creating a Cypress task to use the above-developed code and be
able to retrieve a session cookie given a role.
* We updated the login task to know how we should perform the login
depending if we are in Serverless (MKI or serverless FTR) or ESS
* In the parallel serverless script:
* We are updating the `BASE_ENV_URL` variable to use the proper QA
environment (pending to be done in follow-up PRs, to extract this value
so it is not hardcoded cc @dkirchan )
* We are adding the `IS_SERVERLESS` environment variable needed for the
logic on the login task. This changed implied to update the
`es_archiver` file to continue work as expected.
* We have added the `TEST_CLOUD_HOST_NAME` environment variable needed
for the code we are reusing to retrieve the session cookie for MKI.
* We have updated the Security Solution quality gate script to set the
`role_users.json` file needed by the code we are reusing to get the
different session cookies on MKI
* We have adjusted the tests because the username now follows the
pattern `test <role>` (@dmlemeshko is it possible to have as username
just the role? Is this something that can impact other tests and teams?)
* We have [skipped](https://github.com/elastic/kibana/issues/173168) a
test that got unstable after the changes.

## How to test it in your machine

### Serverless FTR

1. Navigate to `x-pack/test/security_solution_cypress`
2. Execute `yarn cypress:open:qa:serverless`
3. Click on `E2E testing`
4. Click on any test to execute it


### Serverless MKI

Setup a valid Elastic Cloud API key for QA environment:

1. Navigate to QA environment.
2. Click on the `User menu button` located on the top right of the
header.
3. Click on `Organization`.
5. Click on the `API keys` tab.
6. Click on `Create API key` button.
7. Add a name, set an expiration date, assign an organization owner
role.
8. Click on `Create API key`
9. Save the value of the key

Store the saved key on `~/.elastic/cloud.json` using the following
format:

```json
{
  "api_key": {
    "qa": "<API_KEY>"
  }
}
```

Store the email and password of the account you used to login in the QA
Environment at the root directory of your Kibana project on
`.ftr/role_users.json`, using the following format:

```json
{
  "admin": {
    "email": "<email>",
    "password": "<password>"
  }
}
```

If you want to execute a test with a role different from the default
one, make sure you have created the user under your organization and is
added to the above json following the format:

```json
{
  "admin": {
    "email": "<email>",
    "password": "<password>"
  },
  "<roleName>": {
    "email": "<email>",
    "password": "<password>"
  }
}
```

1. Navigate to `x-pack/test/security_solution_cypress`
2. Execute `yarn cypress:open:qa:serverless`
3. Click on `E2E testing`
4. Click on any test to execute it

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Gloria Hornero 2023-12-13 17:12:57 +01:00 committed by GitHub
parent a26eec451f
commit c580213c00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 131 additions and 14 deletions

View file

@ -16,6 +16,9 @@ export JOB=kibana-security-solution-chrome
buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" "true"
mkdir .ftr
retry 5 5 vault kv get -format=json -field=data secret/kibana-issues/dev/security-quality-gate/role-users > .ftr/role_users.json
cd x-pack/test/security_solution_cypress
set +e

View file

@ -61,7 +61,7 @@ const DEFAULT_CONFIGURATION: Readonly<ProductType[]> = [
const DEFAULT_REGION = 'aws-eu-west-1';
const PROJECT_NAME_PREFIX = 'kibana-cypress-security-solution-ephemeral';
const BASE_ENV_URL = 'https://global.qa.cld.elstc.co';
const BASE_ENV_URL = 'https://console.qa.cld.elstc.co';
let log: ToolingLog;
const API_HEADERS = Object.freeze({
'kbn-xsrf': 'cypress-creds',
@ -571,6 +571,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)}
KIBANA_PASSWORD: credentials.password,
CLOUD_SERVERLESS: true,
IS_SERVERLESS: true,
};
if (process.env.DEBUG && !process.env.CI) {
@ -582,6 +583,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)}
----------------------------------------------
`);
}
process.env.TEST_CLOUD_HOST_NAME = new URL(BASE_ENV_URL).hostname;
if (isOpen) {
await cypress.open({

View file

@ -304,8 +304,51 @@ Store the saved key on `~/.elastic/cloud.json` using the following format:
}
```
#### Known limitations
- Currently RBAC cannot be tested.
Store the email and password of the account you used to login in the QA Environment at the root directory of your Kibana project on `.ftr/role_users.json`, using the following format:
```json
{
"admin": {
"email": "<email>",
"password": "<password>"
}
}
```
#### Testing with different roles
If you want to execute a test using Cypress on visual mode with MKI, you need to make sure you have the user created in your organization, and add it tot he `.ftr/role_users.json`:
```json
{
"admin": {
"email": "<email>",
"password": "<password>"
},
"<roleName>": {
"email": "<email>",
"password": "<password>"
}
}
```
As role names please use:
- admin
- detections_admin
- editor
- endpoint_operations_analyst
- endpoint_policy_manager
- none
- platform_engineer
- rule_author
- soc_manager
- t1_analyst
- t2_analyst
- t3_analyst
- threat_intelligence_analyst
- viewer
The above should be the same used on the automation.
#### PLIs

View file

@ -7,6 +7,7 @@
import { defineCypressConfig } from '@kbn/cypress-config';
import { esArchiver } from './support/es_archiver';
import { samlAuthentication } from './support/saml_auth';
// eslint-disable-next-line import/no-default-export
export default defineCypressConfig({
@ -39,6 +40,7 @@ export default defineCypressConfig({
specPattern: './cypress/e2e/**/*.cy.ts',
setupNodeEvents(on, config) {
esArchiver(on, config);
samlAuthentication(on, config);
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('@cypress/grep/src/plugin')(config);
return config;

View file

@ -7,6 +7,7 @@
import { defineCypressConfig } from '@kbn/cypress-config';
import { esArchiver } from './support/es_archiver';
import { samlAuthentication } from './support/saml_auth';
// eslint-disable-next-line import/no-default-export
export default defineCypressConfig({
@ -41,6 +42,7 @@ export default defineCypressConfig({
specPattern: './cypress/e2e/**/*.cy.ts',
setupNodeEvents(on, config) {
esArchiver(on, config);
samlAuthentication(on, config);
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('@cypress/grep/src/plugin')(config);

View file

@ -7,6 +7,7 @@
import { defineCypressConfig } from '@kbn/cypress-config';
import { esArchiver } from './support/es_archiver';
import { samlAuthentication } from './support/saml_auth';
// eslint-disable-next-line import/no-default-export
export default defineCypressConfig({
@ -31,6 +32,7 @@ export default defineCypressConfig({
experimentalMemoryManagement: true,
setupNodeEvents(on, config) {
esArchiver(on, config);
samlAuthentication(on, config);
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('@cypress/grep/src/plugin')(config);
return config;

View file

@ -106,10 +106,10 @@ describe('Cases', { tags: ['@ess', '@serverless'] }, () => {
);
cy.get(CASE_DETAILS_USERNAMES)
.eq(REPORTER)
.should('have.text', Cypress.env(ELASTICSEARCH_USERNAME));
.should('contain', Cypress.env(ELASTICSEARCH_USERNAME));
cy.get(CASE_DETAILS_USERNAMES)
.eq(PARTICIPANTS)
.should('have.text', Cypress.env(ELASTICSEARCH_USERNAME));
.should('contain', Cypress.env(ELASTICSEARCH_USERNAME));
cy.get(CASE_DETAILS_TAGS).should('have.text', expectedTags);
EXPECTED_METRICS.forEach((metric) => {

View file

@ -46,7 +46,8 @@ describe('Overview Page', { tags: ['@ess', '@serverless'] }, () => {
});
});
describe('Favorite Timelines', () => {
// https://github.com/elastic/kibana/issues/173168
describe('Favorite Timelines', { tags: ['@brokenInServerless'] }, () => {
it('should appear on overview page', () => {
createTimeline(getTimeline())
.then((response) => response.body.data.persistTimeline.timeline.savedObjectId)

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { IS_SERVERLESS, CLOUD_SERVERLESS } from '../env_var_names_constants';
import { getDataTestSubjectSelector } from '../helpers/common';
import { GLOBAL_FILTERS_CONTAINER } from './date_picker';
@ -204,9 +205,15 @@ export const ALERT_ASSIGNEES_SELECT_PANEL =
export const ALERT_ASSIGNEES_UPDATE_BUTTON =
'[data-test-subj="securitySolutionAssigneesApplyButton"]';
export const ALERT_USER_AVATAR = (assignee: string) =>
`[data-test-subj="securitySolutionUsersAvatar-${assignee}"][title='${assignee}']`;
export const ALERT_USER_AVATAR = (assignee: string) => {
let expectedAssignee = assignee;
if (Cypress.env(IS_SERVERLESS) && !Cypress.env(CLOUD_SERVERLESS)) {
expectedAssignee = `test ${expectedAssignee}`;
}
return `[data-test-subj^="securitySolutionUsersAvatar-"][title='${expectedAssignee}']`;
};
export const ALERT_AVATARS_PANEL = '[data-test-subj="securitySolutionUsersAvatarsPanel"]';
export const ALERT_ASIGNEES_COLUMN =

View file

@ -18,7 +18,7 @@ export const esArchiver = (
): EsArchiver => {
const log = new ToolingLog({ level: 'verbose', writeTo: process.stdout });
const isSnapshotServerless = config.env.IS_SERVERLESS;
const isServerless = config.env.IS_SERVERLESS;
const isCloudServerless = config.env.CLOUD_SERVERLESS;
const serverlessCloudUser = {
@ -27,7 +27,7 @@ export const esArchiver = (
};
let authOverride;
if (!isSnapshotServerless) {
if (isServerless) {
authOverride = isCloudServerless ? serverlessCloudUser : systemIndicesSuperuser;
}

View file

@ -0,0 +1,41 @@
/*
* 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 { ToolingLog } from '@kbn/tooling-log';
import { SecurityRoleName } from '@kbn/security-solution-plugin/common/test';
import { HostOptions, SamlSessionManager } from '@kbn/test';
export const samlAuthentication = async (
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions
): Promise<void> => {
const log = new ToolingLog({ level: 'verbose', writeTo: process.stdout });
const kbnHost = config.env.KIBANA_URL || config.env.BASE_URL;
const kbnUrl = new URL(kbnHost);
const hostOptions: HostOptions = {
protocol: kbnUrl.protocol as 'http' | 'https',
hostname: kbnUrl.hostname,
port: parseInt(kbnUrl.port, 10),
username: config.env.ELASTICSEARCH_USERNAME,
password: config.env.ELASTICSEARCH_PASSWORD,
};
on('task', {
getSessionCookie: async (role: string | SecurityRoleName): Promise<string> => {
const sessionManager = new SamlSessionManager({
hostOptions,
log,
isCloud: config.env.CLOUD_SERVERLESS,
});
return sessionManager.getSessionCookieForRole(role);
},
});
};

View file

@ -39,7 +39,7 @@ function createUser(username: string, password: string, roles: string[] = []): v
password,
roles,
full_name: username,
email: '',
email: `${username}@elastic.co`,
};
rootRequest({

View file

@ -41,8 +41,22 @@ export const getEnvAuth = (role: SecurityRoleName): User => {
};
export const login = (role?: SecurityRoleName): void => {
const user = role ? getEnvAuth(role) : defaultUser;
loginWithUser(user);
let testRole = '';
if (Cypress.env(IS_SERVERLESS)) {
if (!role) {
testRole = Cypress.env(CLOUD_SERVERLESS) ? 'admin' : 'system_indices_superuser';
} else {
testRole = role;
}
cy.task('getSessionCookie', testRole).then((cookie) => {
cy.setCookie('sid', cookie as string);
});
cy.visit('/');
} else {
const user = role ? getEnvAuth(role) : defaultUser;
loginWithUser(user);
}
};
export const loginWithUser = (user: User): void => {

View file

@ -40,6 +40,6 @@
"@kbn/management-settings-ids",
"@kbn/es-query",
"@kbn/ml-plugin",
"@kbn/license-management-plugin"
"@kbn/license-management-plugin",
]
}