diff --git a/x-pack/test/security_solution_cypress/cypress/cypress.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress.config.ts index ef9a5b6130966..3b42b205e6030 100644 --- a/x-pack/test/security_solution_cypress/cypress/cypress.config.ts +++ b/x-pack/test/security_solution_cypress/cypress/cypress.config.ts @@ -7,6 +7,7 @@ import { defineCypressConfig } from '@kbn/cypress-config'; import { esArchiver } from './support/es_archiver'; +import { esClient } from './support/es_client'; export default defineCypressConfig({ chromeWebSecurity: false, @@ -31,6 +32,7 @@ export default defineCypressConfig({ experimentalCspAllowList: ['default-src', 'script-src', 'script-src-elem'], setupNodeEvents(on, config) { esArchiver(on, config); + esClient(on, config); on('before:browser:launch', (browser, launchOptions) => { if (browser.name === 'chrome' && browser.isHeadless) { launchOptions.args.push('--window-size=1920,1200'); diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_ci.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_ci.config.ts index 3fae578ab1145..d621b844786dd 100644 --- a/x-pack/test/security_solution_cypress/cypress/cypress_ci.config.ts +++ b/x-pack/test/security_solution_cypress/cypress/cypress_ci.config.ts @@ -7,6 +7,7 @@ import { defineCypressConfig } from '@kbn/cypress-config'; import { esArchiver } from './support/es_archiver'; +import { esClient } from './support/es_client'; // eslint-disable-next-line import/no-default-export export default defineCypressConfig({ @@ -40,6 +41,7 @@ export default defineCypressConfig({ specPattern: './cypress/e2e/**/*.cy.ts', setupNodeEvents(on, config) { esArchiver(on, config); + esClient(on, config); on('before:browser:launch', (browser, launchOptions) => { if (browser.name === 'chrome' && browser.isHeadless) { launchOptions.args.push('--window-size=1920,1200'); diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts index a5d59ca4dd4c6..2786ce7539092 100644 --- a/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts +++ b/x-pack/test/security_solution_cypress/cypress/cypress_ci_serverless.config.ts @@ -8,6 +8,7 @@ import { defineCypressConfig } from '@kbn/cypress-config'; import { esArchiver } from './support/es_archiver'; import { samlAuthentication } from './support/saml_auth'; +import { esClient } from './support/es_client'; // eslint-disable-next-line import/no-default-export export default defineCypressConfig({ @@ -54,6 +55,8 @@ export default defineCypressConfig({ return launchOptions; }); samlAuthentication(on, config); + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + esClient(on, config); // eslint-disable-next-line @typescript-eslint/no-var-requires require('@cypress/grep/src/plugin')(config); return config; diff --git a/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts b/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts index 5ca79319c8c32..dda12e04890c6 100644 --- a/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts +++ b/x-pack/test/security_solution_cypress/cypress/cypress_serverless.config.ts @@ -8,6 +8,7 @@ import { defineCypressConfig } from '@kbn/cypress-config'; import { esArchiver } from './support/es_archiver'; import { samlAuthentication } from './support/saml_auth'; +import { esClient } from './support/es_client'; // eslint-disable-next-line import/no-default-export export default defineCypressConfig({ @@ -33,6 +34,7 @@ export default defineCypressConfig({ experimentalMemoryManagement: true, setupNodeEvents(on, config) { esArchiver(on, config); + esClient(on, config); on('before:browser:launch', (browser, launchOptions) => { if (browser.name === 'chrome' && browser.isHeadless) { launchOptions.args.push('--window-size=1920,1200'); @@ -46,6 +48,7 @@ export default defineCypressConfig({ return launchOptions; }); 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); return config; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts index d97481d5489bf..5682a89fd6990 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts @@ -6,6 +6,7 @@ */ import { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getUsername } from '../../../../../../tasks/common'; import { expectedExportedExceptionList, getExceptionList, @@ -97,11 +98,12 @@ describe( exportExceptionList(getExceptionList1().list_id); cy.wait('@export').then(({ response }) => { - cy.wrap(response?.body).should( - 'eql', - expectedExportedExceptionList(exceptionListResponse) - ); - + getUsername('admin').then((username) => { + cy.wrap(response?.body).should( + 'eql', + expectedExportedExceptionList(exceptionListResponse, username as string) + ); + }); cy.get(TOASTER).should( 'have.text', `Exception list "${EXCEPTION_LIST_NAME}" exported successfully` diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts index c0895ec187365..05d2cddc8eaca 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts @@ -51,18 +51,9 @@ import { import { visit, visitWithTimeRange } from '../../../tasks/navigation'; import { CASES_URL, OVERVIEW_URL } from '../../../urls/navigation'; -import { ELASTICSEARCH_USERNAME, IS_SERVERLESS } from '../../../env_var_names_constants'; import { deleteCases } from '../../../tasks/api_calls/cases'; import { login } from '../../../tasks/login'; - -const isServerless = Cypress.env(IS_SERVERLESS); -const getUsername = () => { - if (isServerless) { - return cy.task('getFullname'); - } else { - return cy.wrap(Cypress.env(ELASTICSEARCH_USERNAME)); - } -}; +import { getFullname } from '../../../tasks/common'; // Tracked by https://github.com/elastic/security-team/issues/7696 describe('Cases', { tags: ['@ess', '@serverless'] }, () => { @@ -120,7 +111,7 @@ describe('Cases', { tags: ['@ess', '@serverless'] }, () => { `${this.mycase.description} ${this.mycase.timeline.title}` ); - getUsername().then((username) => { + getFullname('platform_engineer').then((username) => { cy.get(CASE_DETAILS_USERNAMES).eq(REPORTER).should('contain', username); cy.get(CASE_DETAILS_USERNAMES).eq(PARTICIPANTS).should('contain', username); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts index 7aff512667068..3c407a7506762 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts @@ -13,6 +13,7 @@ import { expectedExportedTimelineTemplate } from '../../../objects/timeline'; import { TIMELINE_TEMPLATES_URL } from '../../../urls/navigation'; import { createTimelineTemplate, deleteTimelines } from '../../../tasks/api_calls/timelines'; import { searchByTitle } from '../../../tasks/table_pagination'; +import { getFullname } from '../../../tasks/common'; describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { @@ -36,11 +37,12 @@ describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { cy.wait('@export').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); - - cy.wrap(response?.body).should( - 'eql', - expectedExportedTimelineTemplate(this.templateResponse) - ); + getFullname('admin').then((username) => { + cy.wrap(response?.body).should( + 'eql', + expectedExportedTimelineTemplate(this.templateResponse, username as string) + ); + }); }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts index e1e3a5cf2de32..826ca78228b61 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts @@ -21,6 +21,7 @@ import { TIMELINE_CHECKBOX } from '../../../screens/timelines'; import { createTimeline } from '../../../tasks/api_calls/timelines'; import { expectedExportedTimeline } from '../../../objects/timeline'; import { closeToast } from '../../../tasks/common/toast'; +import { getFullname } from '../../../tasks/common'; describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { @@ -51,7 +52,12 @@ describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { exportTimeline(this.timelineId1); cy.wait('@export').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); - cy.wrap(response?.body).should('eql', expectedExportedTimeline(this.timelineResponse1)); + getFullname('admin').then((username) => { + cy.wrap(response?.body).should( + 'eql', + expectedExportedTimeline(this.timelineResponse1, username as string) + ); + }); }); closeToast(); @@ -61,7 +67,13 @@ describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { exportSelectedTimelines(); cy.wait('@export').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); - cy.wrap(response?.body).should('eql', expectedExportedTimeline(this.timelineResponse1)); + + getFullname('admin').then((username) => { + cy.wrap(response?.body).should( + 'eql', + expectedExportedTimeline(this.timelineResponse1, username as string) + ); + }); }); closeToast(); @@ -81,8 +93,17 @@ describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { cy.wait('@export').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); const timelines = response?.body?.split('\n'); - assert.deepEqual(JSON.parse(timelines[0]), expectedExportedTimeline(this.timelineResponse2)); - assert.deepEqual(JSON.parse(timelines[1]), expectedExportedTimeline(this.timelineResponse1)); + + getFullname('admin').then((username) => { + assert.deepEqual( + JSON.parse(timelines[0]), + expectedExportedTimeline(this.timelineResponse2, username as string) + ); + assert.deepEqual( + JSON.parse(timelines[1]), + expectedExportedTimeline(this.timelineResponse1, username as string) + ); + }); }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/notes_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/notes_tab.cy.ts index 7bda7c4e4bfc3..7eead20dcb8c1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/notes_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/notes_tab.cy.ts @@ -23,6 +23,7 @@ import { createTimeline, deleteTimelines } from '../../../tasks/api_calls/timeli import { login } from '../../../tasks/login'; import { visitTimeline } from '../../../tasks/navigation'; import { addNotesToTimeline, goToNotesTab } from '../../../tasks/timeline'; +import { getFullname } from '../../../tasks/common'; const author = Cypress.env('ELASTICSEARCH_USERNAME'); const link = 'https://www.elastic.co/'; @@ -66,8 +67,10 @@ describe('Timeline notes tab', { tags: ['@ess', '@serverless'] }, () => { }); it('should render the right author', () => { - addNotesToTimeline(getTimelineNonValidQuery().notes); - cy.get(NOTES_AUTHOR).first().should('have.text', author); + getFullname('admin').then((username) => { + addNotesToTimeline(getTimelineNonValidQuery().notes); + cy.get(NOTES_AUTHOR).first().should('have.text', username); + }); }); // this test is failing on MKI only, the change was introduced by this EUI PR https://github.com/elastic/kibana/pull/195525 diff --git a/x-pack/test/security_solution_cypress/cypress/objects/exception.ts b/x-pack/test/security_solution_cypress/cypress/objects/exception.ts index 394130a257637..c4138052db304 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/exception.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/exception.ts @@ -6,7 +6,6 @@ */ import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { ELASTICSEARCH_USERNAME } from '../env_var_names_constants'; export interface Exception { field: string; @@ -59,18 +58,9 @@ export const getException = (): Exception => ({ }); export const expectedExportedExceptionList = ( - exceptionListResponse: Cypress.Response + exceptionListResponse: Cypress.Response, + username: string ): string => { const jsonRule = exceptionListResponse.body; - return `{"_version":"${jsonRule._version}","created_at":"${ - jsonRule.created_at - }","created_by":"${Cypress.env(ELASTICSEARCH_USERNAME)}","description":"${ - jsonRule.description - }","id":"${jsonRule.id}","immutable":false,"list_id":"${jsonRule.list_id}","name":"${ - jsonRule.name - }","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"${ - jsonRule.tie_breaker_id - }","type":"${jsonRule.type}","updated_at":"${jsonRule.updated_at}","updated_by":"${Cypress.env( - ELASTICSEARCH_USERNAME - )}","version":1}\n{"exported_exception_list_count":1,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n`; + return `{"_version":"${jsonRule._version}","created_at":"${jsonRule.created_at}","created_by":"${username}","description":"${jsonRule.description}","id":"${jsonRule.id}","immutable":false,"list_id":"${jsonRule.list_id}","name":"${jsonRule.name}","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"${jsonRule.tie_breaker_id}","type":"${jsonRule.type}","updated_at":"${jsonRule.updated_at}","updated_by":"${username}","version":1}\n{"exported_exception_list_count":1,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n`; }; diff --git a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts index 1dcaae65a3392..1aea82de6612d 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts @@ -69,7 +69,8 @@ export const getTimelineNonValidQuery = (): CompleteTimeline => ({ }); export const expectedExportedTimelineTemplate = ( - templateResponse: Cypress.Response + templateResponse: Cypress.Response, + username: string ) => { const timelineTemplateBody = templateResponse.body.data.persistTimeline.timeline; @@ -102,9 +103,9 @@ export const expectedExportedTimelineTemplate = ( templateTimelineVersion: 1, timelineType: 'template', created: timelineTemplateBody.created, - createdBy: Cypress.env('ELASTICSEARCH_USERNAME'), + createdBy: username, updated: timelineTemplateBody.updated, - updatedBy: Cypress.env('ELASTICSEARCH_USERNAME'), + updatedBy: username, sort: [], eventNotes: [], globalNotes: [], @@ -114,7 +115,8 @@ export const expectedExportedTimelineTemplate = ( }; export const expectedExportedTimeline = ( - timelineResponse: Cypress.Response + timelineResponse: Cypress.Response, + username: string ) => { const timelineBody = timelineResponse.body.data.persistTimeline.timeline; @@ -140,9 +142,9 @@ export const expectedExportedTimeline = ( description: timelineBody.description, title: timelineBody.title, created: timelineBody.created, - createdBy: Cypress.env('ELASTICSEARCH_USERNAME'), + createdBy: username, updated: timelineBody.updated, - updatedBy: Cypress.env('ELASTICSEARCH_USERNAME'), + updatedBy: username, timelineType: 'default', sort: [], eventNotes: [], diff --git a/x-pack/test/security_solution_cypress/cypress/support/es_client.ts b/x-pack/test/security_solution_cypress/cypress/support/es_client.ts new file mode 100644 index 0000000000000..4b5e114ed33dd --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/support/es_client.ts @@ -0,0 +1,156 @@ +/* + * 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 { systemIndicesSuperuser } from '@kbn/test'; +import { createEsClient } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; + +export const esClient = ( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +): Promise => { + const isServerless = config.env.IS_SERVERLESS; + const isCloudServerless = config.env.CLOUD_SERVERLESS; + + const user = { + username: config.env.ELASTICSEARCH_USERNAME, + password: config.env.ELASTICSEARCH_PASSWORD, + }; + + /* + system_indices_superuser is a user created for testing purposes (an operator one) that does not have restrictions, + that user is the one used on ESS and stateless environments to access internal indexes directly and does not exist on MKI environments. + */ + const authOverride = isServerless ? (isCloudServerless ? user : systemIndicesSuperuser) : user; + + const client = createEsClient({ + url: config.env.ELASTICSEARCH_URL, + username: authOverride?.username, + password: authOverride?.password, + }); + + on('task', { + putMapping: async (index: string) => { + await client.indices.putMapping({ + index, + body: { + dynamic: true, + }, + }); + return null; + }, + bulkInsert: async (body) => { + await client.bulk({ + refresh: true, + body, + }); + return null; + }, + refreshIndex: async (index: string) => { + try { + await client.indices.refresh({ index }); + return true; + } catch (error) { + return false; + } + }, + searchIndex: async (index) => { + try { + const response = await client.search({ + index, + body: { + query: { + match_all: {}, + }, + }, + }); + + return response.hits.hits.length; + } catch (error) { + return null; + } + }, + deleteDataStream: async (dataStreamName) => { + try { + await client.indices.deleteDataStream({ + name: dataStreamName, + }); + + return { success: true }; + } catch (error) { + return { success: false, error: error.message }; + } + }, + deleteIndex: async (index) => { + try { + await client.indices.delete({ + index, + }); + + return { success: true }; + } catch (error) { + return { success: false, error: error.message }; + } + }, + deleteDocuments: async (index: string) => { + await client.deleteByQuery({ + index, + body: { + query: { + match_all: {}, + }, + }, + conflicts: 'proceed', + scroll_size: 10000, + refresh: true, + }); + return null; + }, + createIndex: async ({ index: indexName, properties }) => { + const result = await client.indices.create({ + index: indexName, + body: { + mappings: { + properties, + }, + }, + }); + return result; + }, + createDocument: async ({ index: indexName, document }) => { + const result = await client.index({ + index: indexName, + body: document, + refresh: 'wait_for', + }); + + return result; + }, + deleteSecurityRulesFromKibana: async () => { + await client.deleteByQuery({ + index: '.kibana_*', + body: { + query: { + bool: { + filter: [ + { + match: { + type: 'security-rule', + }, + }, + ], + }, + }, + }, + conflicts: 'proceed', + refresh: true, + }); + return null; + }, + }); + + return Promise.resolve(); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts index 1b5c2adcae455..1f95d373b2c17 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts @@ -11,6 +11,9 @@ import { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { HostOptions, SamlSessionManager } from '@kbn/test'; import { REPO_ROOT } from '@kbn/repo-info'; import { resolve } from 'path'; +import axios from 'axios'; +import fs from 'fs'; +import yaml from 'js-yaml'; import { DEFAULT_SERVERLESS_ROLE } from '../env_var_names_constants'; export const samlAuthentication = async ( @@ -31,31 +34,81 @@ export const samlAuthentication = async ( password: config.env.ELASTICSEARCH_PASSWORD, }; + const rolesPath = + '../../../../packages/kbn-es/src/serverless_resources/project_roles/security/roles.yml'; + // If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles. const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined; const cloudUsersFilePath = resolve(REPO_ROOT, '.ftr', rolesFilename ?? 'role_users.json'); + const INTERNAL_REQUEST_HEADERS = { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + }; + + const getYamlData = (filePath: string): any => { + const fileContents = fs.readFileSync(filePath, 'utf8'); + return yaml.load(fileContents); + }; + + const getRoleConfiguration = (role: string, filePath: string): any => { + const data = getYamlData(filePath); + if (data[role]) { + return data[role]; + } else { + throw new Error(`Role '${role}' not found in the YAML file.`); + } + }; + + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath, + }); + on('task', { getSessionCookie: async (role: string | SecurityRoleName): Promise => { - const sessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - cloudUsersFilePath, - }); return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role); }, + getApiKeyForRole: async (role: string | SecurityRoleName): Promise => { + const adminCookieHeader = await sessionManager.getApiCredentialsForRole('admin'); + + let roleDescriptor = {}; + + const roleConfig = getRoleConfiguration(role, rolesPath); + + roleDescriptor = { [role]: roleConfig }; + + const response = await axios.post( + `${kbnHost}/internal/security/api_key`, + { + name: 'myTestApiKey', + metadata: {}, + role_descriptors: roleDescriptor, + }, + { + headers: { + ...INTERNAL_REQUEST_HEADERS, + ...adminCookieHeader, + }, + } + ); + + const apiKey = response.data.encoded; + return apiKey; + }, getFullname: async ( role: string | SecurityRoleName = DEFAULT_SERVERLESS_ROLE ): Promise => { - const sessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - cloudUsersFilePath, - }); const { full_name: fullName } = await sessionManager.getUserData(role); return fullName; }, + getUsername: async ( + role: string | SecurityRoleName = DEFAULT_SERVERLESS_ROLE + ): Promise => { + const { username } = await sessionManager.getUserData(role); + return username; + }, }); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts index 80d7c2c106815..480d97bf35979 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts @@ -14,7 +14,7 @@ import { deleteAllDocuments } from './elasticsearch'; import { getSpaceUrl } from '../space'; import { DEFAULT_ALERTS_INDEX_PATTERN } from './alerts'; -export const API_AUTH = Object.freeze({ +export const ESS_API_AUTH = Object.freeze({ user: Cypress.env(ELASTICSEARCH_USERNAME), pass: Cypress.env(ELASTICSEARCH_PASSWORD), }); @@ -28,17 +28,32 @@ export const API_HEADERS = Object.freeze({ export const INTERNAL_CLOUD_CONNECTORS = ['Elastic-Cloud-SMTP']; export const rootRequest = ({ - headers: optionHeaders, + headers: optionHeaders = {}, + role = 'admin', ...restOptions -}: Partial): Cypress.Chainable> => - cy.request({ - auth: API_AUTH, - headers: { - ...API_HEADERS, - ...(optionHeaders || {}), - }, - ...restOptions, - }); +}: Partial & { role?: string }): Cypress.Chainable> => { + if (Cypress.env('IS_SERVERLESS')) { + return cy.task('getApiKeyForRole', role).then((response) => { + return cy.request({ + headers: { + ...API_HEADERS, + ...optionHeaders, + Authorization: `ApiKey ${response}`, + }, + ...restOptions, + }); + }); + } else { + return cy.request({ + auth: ESS_API_AUTH, + headers: { + ...API_HEADERS, + ...optionHeaders, + }, + ...restOptions, + }); + } +}; // a helper function to wait for the root request to be successful // defaults to 5 second intervals for 3 attempts @@ -105,24 +120,7 @@ export const deleteConnectors = () => { }; export const deletePrebuiltRulesAssets = () => { - const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; - rootRequest({ - method: 'POST', - url: `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed&refresh`, - body: { - query: { - bool: { - filter: [ - { - match: { - type: 'security-rule', - }, - }, - ], - }, - }, - }, - }); + cy.task('deleteSecurityRulesFromKibana'); }; export const postDataView = (indexPattern: string, name?: string, id?: string) => { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/elasticsearch.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/elasticsearch.ts index 6dc3622f72a03..b169a37154a5b 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/elasticsearch.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/elasticsearch.ts @@ -4,70 +4,37 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { rootRequest } from './common'; export const deleteIndex = (index: string) => { - rootRequest({ - method: 'DELETE', - url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}`, - failOnStatusCode: false, - }); + cy.task('deleteIndex', index); }; export const deleteDataStream = (dataStreamName: string) => { - rootRequest({ - method: 'DELETE', - url: `${Cypress.env('ELASTICSEARCH_URL')}/_data_stream/${dataStreamName}`, - failOnStatusCode: false, - }); + cy.task('deleteDataStream', dataStreamName); }; export const deleteAllDocuments = (target: string) => { refreshIndex(target); - rootRequest({ - method: 'POST', - url: `${Cypress.env( - 'ELASTICSEARCH_URL' - )}/${target}/_delete_by_query?conflicts=proceed&scroll_size=10000&refresh`, - body: { - query: { - match_all: {}, - }, - }, - }); + cy.task('deleteDocuments', target); }; -export const createIndex = (indexName: string, properties: Record) => - rootRequest({ - method: 'PUT', - url: `${Cypress.env('ELASTICSEARCH_URL')}/${indexName}`, - body: { - mappings: { - properties, - }, - }, - }); +export const createIndex = (indexName: string, properties: Record) => { + cy.task('createIndex', { index: indexName, properties }); +}; -export const createDocument = (indexName: string, document: Record) => - rootRequest({ - method: 'POST', - url: `${Cypress.env('ELASTICSEARCH_URL')}/${indexName}/_doc?refresh=wait_for`, - body: document, - }); +export const createDocument = (indexName: string, document: Record) => { + cy.task('createDocument', { index: indexName, document }); +}; export const waitForNewDocumentToBeIndexed = (index: string, initialNumberOfDocuments: number) => { cy.waitUntil( () => - rootRequest<{ hits: { hits: unknown[] } }>({ - method: 'GET', - url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_search`, - failOnStatusCode: false, - }).then((response) => { - if (response.status !== 200) { - return false; + cy.task('searchIndex', index).then((currentNumberOfDocuments) => { + if (typeof currentNumberOfDocuments === 'number') { + return currentNumberOfDocuments > initialNumberOfDocuments; } else { - return response.body.hits.hits.length > initialNumberOfDocuments; + return false; } }), { interval: 500, timeout: 12000 } @@ -77,15 +44,8 @@ export const waitForNewDocumentToBeIndexed = (index: string, initialNumberOfDocu export const refreshIndex = (index: string) => { cy.waitUntil( () => - rootRequest({ - method: 'POST', - url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_refresh`, - failOnStatusCode: false, - }).then((response) => { - if (response.status !== 200) { - return false; - } - return true; + cy.task('refreshIndex', index).then((result) => { + return result === true; }), { interval: 500, timeout: 12000 } ); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts index 420794de7e338..08de9decbddf1 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts @@ -122,7 +122,6 @@ export const createNewRuleAsset = ({ headers: { 'Content-Type': 'application/json', }, - failOnStatusCode: false, body: rule, }) .then((response) => response.status === 200); @@ -142,7 +141,6 @@ export const bulkCreateRuleAssets = ({ 'Bulk Install prebuilt rules', rules?.map((rule) => rule['security-rule'].rule_id).join(', ') ); - const url = `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_bulk?refresh`; const bulkIndexRequestBody = rules.reduce((body, rule) => { const document = JSON.stringify(rule); @@ -165,29 +163,8 @@ export const bulkCreateRuleAssets = ({ return body.concat(indexRuleAsset, indexHistoricalRuleAsset); }, ''); - rootRequest({ - method: 'PUT', - url: `${Cypress.env('ELASTICSEARCH_URL')}/${index}/_mapping`, - body: { - dynamic: true, - }, - headers: { - 'Content-Type': 'application/json', - }, - }); - - cy.waitUntil( - () => { - return rootRequest({ - method: 'POST', - url, - headers: { 'Content-Type': 'application/json' }, - failOnStatusCode: false, - body: bulkIndexRequestBody, - }).then((response) => response.status === 200); - }, - { interval: 500, timeout: 12000 } - ); + cy.task('putMapping', index); + cy.task('bulkInsert', bulkIndexRequestBody); }; export const getRuleAssets = (index: string | undefined = '.kibana_security_solution') => { @@ -198,7 +175,6 @@ export const getRuleAssets = (index: string | undefined = '.kibana_security_solu headers: { 'Content-Type': 'application/json', }, - failOnStatusCode: false, body: { query: { term: { type: { value: 'security-rule' } }, diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/common.ts index ff0bbac6866cd..2e109805c4c0a 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/common.ts @@ -56,6 +56,22 @@ export const drop = (dropTarget: JQuery) => { .wait(300); }; +const getUserValue = (taskName: 'getFullname' | 'getUsername', role: string = 'admin') => { + if (Cypress.env('IS_SERVERLESS')) { + return cy.task(taskName, role); + } else { + return cy.wrap(Cypress.env('ELASTICSEARCH_USERNAME')); + } +}; + +export const getFullname = (role: string = 'admin') => { + return getUserValue('getFullname', role); +}; + +export const getUsername = (role: string = 'admin') => { + return getUserValue('getUsername', role); +}; + export const reload = () => { cy.reload(); cy.contains('a', 'Security'); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/privileges.ts b/x-pack/test/security_solution_cypress/cypress/tasks/privileges.ts index bd7c9d9178198..7f2d0dea8b545 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/privileges.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/privileges.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { API_AUTH } from './api_calls/common'; +import { ESS_API_AUTH } from './api_calls/common'; interface User { username: string; @@ -193,7 +193,7 @@ export const createUsersAndRoles = (users: User[], roles: Role[]) => { body: role.privileges, headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, method: 'PUT', - auth: API_AUTH, + auth: ESS_API_AUTH, url: `/api/security/role/${role.name}`, }) .its('status') @@ -213,7 +213,7 @@ export const createUsersAndRoles = (users: User[], roles: Role[]) => { }, headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, method: 'POST', - auth: API_AUTH, + auth: ESS_API_AUTH, url: `/internal/security/users/${user.username}`, }) .its('status') @@ -227,7 +227,7 @@ export const deleteUsersAndRoles = (users: User[], roles: Role[]) => { cy.request({ headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, method: 'DELETE', - auth: API_AUTH, + auth: ESS_API_AUTH, url: `/internal/security/users/${user.username}`, failOnStatusCode: false, }) @@ -240,7 +240,7 @@ export const deleteUsersAndRoles = (users: User[], roles: Role[]) => { cy.request({ headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, method: 'DELETE', - auth: API_AUTH, + auth: ESS_API_AUTH, url: `/api/security/role/${role.name}`, failOnStatusCode: false, })