diff --git a/.buildkite/pipelines/flaky_tests/groups.json b/.buildkite/pipelines/flaky_tests/groups.json index 9d47bdd850b949..77c3d23714d6f9 100644 --- a/.buildkite/pipelines/flaky_tests/groups.json +++ b/.buildkite/pipelines/flaky_tests/groups.json @@ -91,6 +91,14 @@ { "key": "cypress/inventory_cypress", "name": "Inventory - Cypress" + }, + { + "key": "cypress/cloud_security_posture", + "name": "Cloud Security Posture - Cypress" + }, + { + "key": "cypress/cloud_security_posture_serverless", + "name": "[Serverless] Cloud Security Posture - Cypress" } ] } \ No newline at end of file diff --git a/.buildkite/pipelines/pull_request/security_solution/cloud_security_posture.yml b/.buildkite/pipelines/pull_request/security_solution/cloud_security_posture.yml new file mode 100644 index 00000000000000..7f5131b77f2044 --- /dev/null +++ b/.buildkite/pipelines/pull_request/security_solution/cloud_security_posture.yml @@ -0,0 +1,30 @@ +steps: + - command: .buildkite/scripts/steps/functional/cloud_security_posture.sh + label: 'Cloud Security Posture Cypress Tests' + agents: + machineType: n2-standard-4 + preemptible: true + depends_on: + - build + - quick_checks + timeout_in_minutes: 60 + parallelism: 1 + retry: + automatic: + - exit_status: '-1' + limit: 1 + + - command: .buildkite/scripts/steps/functional/cloud_security_posture_serverless.sh + label: 'Cloud Security Posture Cypress Tests on Serverless' + agents: + machineType: n2-standard-4 + preemptible: true + depends_on: + - build + - quick_checks + timeout_in_minutes: 60 + parallelism: 1 + retry: + automatic: + - exit_status: '-1' + limit: 1 \ No newline at end of file diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.ts b/.buildkite/scripts/pipelines/pull_request/pipeline.ts index 8b9a4b62e60290..722c30cb425340 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.ts +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.ts @@ -308,6 +308,22 @@ const getPipeline = (filename: string, removeSteps = true) => { ); } + if ( + (await doAnyChangesMatch([ + /^x-pack\/packages\/kbn-cloud-security-posture/, + /^x-pack\/plugins\/cloud_security_posture/, + /^x-pack\/plugins\/security_solution/, + /^x-pack\/test\/security_solution_cypress/, + ])) || + GITHUB_PR_LABELS.includes('ci:all-cypress-suites') + ) { + pipeline.push( + getPipeline( + '.buildkite/pipelines/pull_request/security_solution/cloud_security_posture.yml' + ) + ); + } + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/post_build.yml')); // remove duplicated steps diff --git a/.buildkite/scripts/steps/functional/cloud_security_posture.sh b/.buildkite/scripts/steps/functional/cloud_security_posture.sh new file mode 100644 index 00000000000000..bea407e6a5b1c0 --- /dev/null +++ b/.buildkite/scripts/steps/functional/cloud_security_posture.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/steps/functional/common.sh + +export JOB=kibana-cloud-security-posture-cypress +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} + +echo "--- Cloud Security Posture Workflows Cypress tests" + +cd x-pack/test/security_solution_cypress + +set +e + +yarn cypress:cloud_security_posture:run:ess; status=$?; yarn junit:merge || :; exit $status \ No newline at end of file diff --git a/.buildkite/scripts/steps/functional/cloud_security_posture_serverless.sh b/.buildkite/scripts/steps/functional/cloud_security_posture_serverless.sh new file mode 100644 index 00000000000000..57c9be111a151b --- /dev/null +++ b/.buildkite/scripts/steps/functional/cloud_security_posture_serverless.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/steps/functional/common.sh + +export JOB=kibana-cloud-security-posture-serverless-cypress +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} + +echo "--- Cloud Security Posture Workflows Cypress tests on Serverless" + +cd x-pack/test/security_solution_cypress + +set +e + +yarn cypress:cloud_security_posture:run:serverless; status=$?; yarn junit:merge || :; exit $status diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a7a808f3d1d231..56fe95cd65b39d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1867,7 +1867,8 @@ x-pack/plugins/osquery @elastic/security-defend-workflows /x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.* @elastic/fleet @elastic/kibana-cloud-security-posture /x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/cloud_posture_third_party_support_callout.* @elastic/fleet @elastic/kibana-cloud-security-posture /x-pack/plugins/security_solution/public/cloud_security_posture @elastic/kibana-cloud-security-posture -/x-pack/test/security_solution_cypress/cypress/e2e/explore/hosts/vulnerabilities_contextual_flyout.cy.ts @elastic/kibana-cloud-security-posture +/x-pack/test/security_solution_cypress/cypress/e2e/cloud_security_posture/misconfiguration_contextual_flyout.cy.ts @elastic/kibana-cloud-security-posture +/x-pack/test/security_solution_cypress/cypress/e2e/cloud_security_posture/vulnerabilities_contextual_flyout.cy.ts @elastic/kibana-cloud-security-posture # Security Solution onboarding tour /x-pack/plugins/security_solution/public/common/components/guided_onboarding @elastic/security-threat-hunting-explore diff --git a/x-pack/plugins/cloud_security_posture/README.md b/x-pack/plugins/cloud_security_posture/README.md index bd0b7de6ac661a..f608a614fca1c1 100755 --- a/x-pack/plugins/cloud_security_posture/README.md +++ b/x-pack/plugins/cloud_security_posture/README.md @@ -20,6 +20,7 @@ For general guidelines, read [Kibana Testing Guide](https://www.elastic.co/guide 1. [End-to-End Tests](../../test/cloud_security_posture_functional/config.ts) 1. [Serverless API Integration tests](../../test_serverless/api_integration/test_suites/security/config.ts) 1. [Serverless End-to-End Tests](../../test_serverless/functional/test_suites/security/config.ts) +1. [Cypress End-to-End Tests](../../test/security_solution_cypress/cypress/e2e/cloud_security_posture) ### Tools @@ -73,6 +74,17 @@ yarn test:ftr --config x-pack/test_serverless/api_integration/test_suites/securi yarn test:ftr --config x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts ``` +Run [**End-to-End Cypress Tests**](https://github.com/elastic/kibana/tree/main/x-pack/test/security_solution_cypress/cypress): +> **Note** +> +> Run this from security_solution_cypress folder +```bash +yarn cypress:open:serverless +yarn cypress:open:ess +yarn cypress:cloud_security_posture:run:serverless +yarn cypress:cloud_security_posture:run:ess +``` + #### Run **FTR tests (integration or e2e) for development** Functional test runner (FTR) can be used separately with `ftr:runner` and `ftr:server`. This is convenient while developing tests. @@ -107,4 +119,29 @@ run serverless e2e tests: ```bash yarn test:ftr:server --config x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts yarn test:ftr:runner ---config x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts -``` \ No newline at end of file +``` + +#### Run **Cypress tests (e2e) for development** +When developing feature outside our plugin folder, instead of using FTRs for e2e test, we may use Cypress. Before running cypress, make sure you have installed it first. Like FTRs, we can run cypress in different environment, for example: + +run ess e2e tests: +```bash +yarn cypress:open:ess +``` + +run ess Cloud Security Posture e2e tests: +```bash +yarn cypress:cloud_security_posture:run:ess +``` + +run serverless e2e tests: +```bash +yarn cypress:open:serverless +``` + +run serverless Cloud Security Posture e2e tests: +```bash +yarn cypress:cloud_security_posture:run:serverless +``` + +Unlike FTR where we have to set server and runner separately, Cypress handles everything in 1 go, so just running the above the script is enough to get it running \ No newline at end of file diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx index 81547f7bbf5ac4..c03dc319952b5d 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx @@ -196,6 +196,7 @@ export const MisconfigurationFindingsDetailsTable = memo( columns={columns} pagination={pagination} onChange={onTableChange} + data-test-subj={'securitySolutionFlyoutMisconfigurationFindingsTable'} /> diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx index 0aae29395602ed..433e493493e37e 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx @@ -229,6 +229,7 @@ export const MisconfigurationsPreview = ({ css={css` font-weight: ${euiTheme.font.weight.semiBold}; `} + data-test-subj={'securitySolutionFlyoutInsightsMisconfigurationsTitleText'} > { + cy.get(CSP_INSIGHT_MISCONFIGURATION_TITLE).click(); +}; + +const timestamp = Date.now(); + +const date = new Date(timestamp); + +const iso8601String = date.toISOString(); + +const mockFindingHostName = (matches: boolean) => { + return { + '@timestamp': iso8601String, + host: { name: matches ? 'siem-kibana' : 'not-siem-kibana' }, + resource: { + id: '1234ABCD', + name: 'kubelet', + sub_type: 'lower case sub type', + }, + result: { evaluation: matches ? 'passed' : 'failed' }, + rule: { + name: 'Upper case rule name', + section: 'Upper case section', + benchmark: { + id: 'cis_k8s', + posture_type: 'kspm', + name: 'CIS Kubernetes V1.23', + version: 'v1.0.0', + }, + type: 'process', + }, + cluster_id: 'Upper case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, + }; +}; + +const mockFindingUserName = (matches: boolean) => { + return { + '@timestamp': iso8601String, + user: { name: matches ? 'test' : 'not-test' }, + resource: { + id: '1234ABCD', + name: 'kubelet', + sub_type: 'lower case sub type', + }, + result: { evaluation: matches ? 'passed' : 'failed' }, + rule: { + name: 'Upper case rule name', + section: 'Upper case section', + benchmark: { + id: 'cis_k8s', + posture_type: 'kspm', + name: 'CIS Kubernetes V1.23', + version: 'v1.0.0', + }, + type: 'process', + }, + cluster_id: 'Upper case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, + }; +}; + +const createMockFinding = (isNameMatches: boolean, findingType: 'host.name' | 'user.name') => { + return rootRequest({ + method: 'POST', + url: `${Cypress.env( + 'ELASTICSEARCH_URL' + )}/${CDR_LATEST_NATIVE_MISCONFIGURATIONS_INDEX_PATTERN}/_doc`, + body: + findingType === 'host.name' + ? mockFindingHostName(isNameMatches) + : mockFindingUserName(isNameMatches), + }); +}; + +const deleteDataStream = () => { + return rootRequest({ + method: 'DELETE', + url: `${Cypress.env( + 'ELASTICSEARCH_URL' + )}/_data_stream/${CDR_LATEST_NATIVE_MISCONFIGURATIONS_INDEX_PATTERN}`, + }); +}; + +describe('Alert Host details expandable flyout', { tags: ['@ess', '@serverless'] }, () => { + beforeEach(() => { + deleteAlertsAndRules(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + }); + + context('Host name - Has misconfiguration findings', () => { + beforeEach(() => { + createMockFinding(true, 'host.name'); + cy.reload(); + expandFirstAlertHostFlyout(); + }); + + afterEach(() => { + /* Deleting data stream even though we don't create it because data stream is automatically created when Cloud security API is used */ + deleteDataStream(); + }); + + it('should display Misconfiguration preview under Insights Entities when it has Misconfiguration Findings', () => { + cy.log('check if Misconfiguration preview title shown'); + cy.get(CSP_INSIGHT_MISCONFIGURATION_TITLE).should('be.visible'); + }); + + it('should display insight tabs and findings table upon clicking on misconfiguration accordion', () => { + clickMisconfigurationTitle(); + cy.get(CSP_INSIGHT_TAB_TITLE).should('be.visible'); + cy.get(CSP_INSIGHT_TABLE).should('be.visible'); + }); + }); + + context( + 'Host name - Has misconfiguration findings but host name is not the same as alert host name', + () => { + beforeEach(() => { + createMockFinding(false, 'host.name'); + cy.reload(); + expandFirstAlertHostFlyout(); + }); + + afterEach(() => { + deleteDataStream(); + }); + + it('should display Misconfiguration preview under Insights Entities when it has Misconfiguration Findings', () => { + expandFirstAlertHostFlyout(); + + cy.log('check if Misconfiguration preview title is not shown'); + cy.get(CSP_INSIGHT_MISCONFIGURATION_TITLE).should('not.exist'); + }); + } + ); + + context('User name - Has misconfiguration findings', () => { + beforeEach(() => { + createMockFinding(true, 'user.name'); + cy.reload(); + expandFirstAlertUserFlyout(); + }); + + afterEach(() => { + deleteDataStream(); + }); + + it('should display Misconfiguration preview under Insights Entities when it has Misconfiguration Findings', () => { + cy.log('check if Misconfiguration preview title shown'); + cy.get(CSP_INSIGHT_MISCONFIGURATION_TITLE).should('be.visible'); + }); + + it('should display insight tabs and findings table upon clicking on misconfiguration accordion', () => { + clickMisconfigurationTitle(); + cy.get(CSP_INSIGHT_TAB_TITLE).should('be.visible'); + cy.get(CSP_INSIGHT_TABLE).should('be.visible'); + }); + }); + + context( + 'User name - Has misconfiguration findings but host name is not the same as alert host name', + () => { + beforeEach(() => { + createMockFinding(false, 'user.name'); + cy.reload(); + expandFirstAlertHostFlyout(); + }); + + afterEach(() => { + deleteDataStream(); + }); + + it('should display Misconfiguration preview under Insights Entities when it has Misconfiguration Findings', () => { + expandFirstAlertUserFlyout(); + + cy.log('check if Misconfiguration preview title is not shown'); + cy.get(CSP_INSIGHT_MISCONFIGURATION_TITLE).should('not.exist'); + }); + } + ); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/cloud_security_posture/vulnerabilities_contextual_flyout.cy.ts similarity index 86% rename from x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/cloud_security_posture/vulnerabilities_contextual_flyout.cy.ts index fb83df1c791419..046864962318b4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/cloud_security_posture/vulnerabilities_contextual_flyout.cy.ts @@ -6,16 +6,16 @@ */ import { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN } from '@kbn/cloud-security-posture-common'; -import { createRule } from '../../../../tasks/api_calls/rules'; -import { getNewRule } from '../../../../objects/rule'; -import { getDataTestSubjectSelector } from '../../../../helpers/common'; +import { createRule } from '../../tasks/api_calls/rules'; +import { getNewRule } from '../../objects/rule'; +import { getDataTestSubjectSelector } from '../../helpers/common'; -import { rootRequest, deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; -import { expandFirstAlertHostFlyout } from '../../../../tasks/asset_criticality/common'; -import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -import { login } from '../../../../tasks/login'; -import { ALERTS_URL } from '../../../../urls/navigation'; -import { visit } from '../../../../tasks/navigation'; +import { rootRequest, deleteAlertsAndRules } from '../../tasks/api_calls/common'; +import { expandFirstAlertHostFlyout } from '../../tasks/asset_criticality/common'; +import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; +import { login } from '../../tasks/login'; +import { ALERTS_URL } from '../../urls/navigation'; +import { visit } from '../../tasks/navigation'; const CSP_INSIGHT_VULNERABILITIES_TITLE = getDataTestSubjectSelector( 'securitySolutionFlyoutInsightsVulnerabilitiesTitleLink' @@ -27,10 +27,8 @@ const CSP_INSIGHT_VULNERABILITIES_TABLE = getDataTestSubjectSelector( const timestamp = Date.now(); -// Create a Date object using the timestamp const date = new Date(timestamp); -// Convert the Date object to ISO 8601 format const iso8601String = date.toISOString(); const getMockVulnerability = (isNameMatchesAlert: boolean) => { @@ -138,8 +136,7 @@ const deleteDataStream = () => { }); }; -// skipping because failure on MKI environment (https://buildkite.com/elastic/kibana-serverless-security-solution-quality-gate-investigations/builds/1390#01927579-caed-41bc-9440-3cf29629a263) -describe.skip('Alert Host details expandable flyout', { tags: ['@ess', '@serverless'] }, () => { +describe('Alert Host details expandable flyout', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { deleteAlertsAndRules(); login(); @@ -167,6 +164,7 @@ describe.skip('Alert Host details expandable flyout', { tags: ['@ess', '@serverl }); afterEach(() => { + /* Deleting data stream even though we don't create it because data stream is automatically created when Cloud security API is used */ deleteDataStream(); }); diff --git a/x-pack/test/security_solution_cypress/package.json b/x-pack/test/security_solution_cypress/package.json index 5f7fc4fadf2bf8..c7bc7fe1d94a0c 100644 --- a/x-pack/test/security_solution_cypress/package.json +++ b/x-pack/test/security_solution_cypress/package.json @@ -19,6 +19,7 @@ "cypress:investigations:run:ess": "yarn cypress:ess --spec './cypress/e2e/investigations/**/*.cy.ts'", "cypress:explore:run:ess": "yarn cypress:ess --spec './cypress/e2e/explore/**/*.cy.ts'", "cypress:changed-specs-only:ess": "yarn cypress:ess --changed-specs-only --env burn=5", + "cypress:cloud_security_posture:run:ess": "yarn cypress:ess --spec './cypress/e2e/cloud_security_posture/**/*.cy.ts'", "cypress:burn:ess": "yarn cypress:ess --env burn=5", "junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results && yarn junit:transform && mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/", "junit:transform": "node ../../plugins/security_solution/scripts/junit_transformer --pathPattern '../../../target/kibana-security-solution/cypress/results/*.xml' --rootDirectory ../../../ --reportName 'Security Solution Cypress' --writeInPlace", @@ -35,6 +36,7 @@ "cypress:investigations:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/investigations/**/*.cy.ts'", "cypress:explore:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/explore/**/*.cy.ts'", "cypress:changed-specs-only:serverless": "yarn cypress:serverless --changed-specs-only --env burn=5", + "cypress:cloud_security_posture:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/cloud_security_posture/**/*.cy.ts'", "cypress:burn:serverless": "yarn cypress:serverless --env burn=2", "cypress:qa:serverless": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel_serverless --config-file ../../test/security_solution_cypress/cypress/cypress_ci_serverless_qa.config.ts", "cypress:open:qa:serverless": "yarn cypress:qa:serverless open", @@ -45,6 +47,7 @@ "cypress:run:qa:serverless:rule_management:prebuilt_rules": "yarn cypress:qa:serverless --spec './cypress/e2e/detection_response/rule_management/prebuilt_rules/**/*.cy.ts'", "cypress:run:qa:serverless:detection_engine": "yarn cypress:qa:serverless --spec './cypress/e2e/detection_response/detection_engine/!(exceptions)/**/*.cy.ts'", "cypress:run:qa:serverless:detection_engine:exceptions": "yarn cypress:qa:serverless --spec './cypress/e2e/detection_response/detection_engine/exceptions/**/*.cy.ts'", - "cypress:run:qa:serverless:ai_assistant": "yarn cypress:qa:serverless --spec './cypress/e2e/ai_assistant/**/*.cy.ts'" + "cypress:run:qa:serverless:ai_assistant": "yarn cypress:qa:serverless --spec './cypress/e2e/ai_assistant/**/*.cy.ts'", + "cypress:run:qa:serverless:cloud_security_posture": "yarn cypress:qa:serverless --spec './cypress/e2e/cloud_security_posture/**/*.cy.ts" } } \ No newline at end of file