diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d53b74d2430df..ee74bc7f4eb45 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1955,6 +1955,10 @@ x-pack/platform/plugins/shared/ml/server/models/data_recognizer/modules/security /x-pack/platform/test/accessibility/ftr_provider_context.d.ts @elastic/appex-qa /x-pack/platform/test/accessibility/page_objects.ts @elastic/appex-qa /x-pack/platform/test/accessibility/services.ts @elastic/appex-qa +/x-pack/test_serverless/functional/page_objects/add_cis_integration_form_page.ts @elastic/appex-qa # temporarily due to SKA tests relocation +/x-pack/test_serverless/functional/page_objects/constants/test_subject_ids.ts @elastic/appex-qa # temporarily due to SKA tests relocation +/x-pack/test_serverless/functional/page_objects/csp_dashboard_page.ts @elastic/appex-qa # temporarily due to SKA tests relocation +/x-pack/test_serverless/functional/page_objects/security_common.ts @elastic/appex-qa # temporarily due to SKA tests relocation # Core /src/platform/test/api_integration/fixtures/kbn_archiver/management/saved_objects/relationships.json @elastic/kibana-core @elastic/kibana-data-discovery diff --git a/x-pack/platform/test/functional/services/ml/api.ts b/x-pack/platform/test/functional/services/ml/api.ts index 38a2ed4f021ac..3b7f9a71d1b25 100644 --- a/x-pack/platform/test/functional/services/ml/api.ts +++ b/x-pack/platform/test/functional/services/ml/api.ts @@ -1672,7 +1672,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { getCompressedModelDefinition(modelType: ModelType) { return fs.readFileSync( require.resolve( - `@kbn/test-suites-xpack-platform/api_integration/services/ml/resources/trained_model_definitions/minimum_valid_config_${modelType}.json.gz.b64` + `../../../api_integration/services/ml/resources/trained_model_definitions/minimum_valid_config_${modelType}.json.gz.b64` ), 'utf-8' ); @@ -1681,7 +1681,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { getTrainedModelConfig(modelName: SupportedTrainedModelNamesType) { const configFileContent = fs.readFileSync( require.resolve( - `@kbn/test-suites-xpack-platform/api_integration/services/ml/resources/trained_model_definitions/${modelName}/config.json` + `../../../api_integration/services/ml/resources/trained_model_definitions/${modelName}/config.json` ), 'utf-8' ); @@ -1691,7 +1691,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { getTrainedModelVocabulary(modelName: SupportedTrainedModelNamesType) { const vocabularyFileContent = fs.readFileSync( require.resolve( - `@kbn/test-suites-xpack-platform/api_integration/services/ml/resources/trained_model_definitions/${modelName}/vocabulary.json` + `../../../api_integration/services/ml/resources/trained_model_definitions/${modelName}/vocabulary.json` ), 'utf-8' ); @@ -1700,7 +1700,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { getTrainedModelDefinitionPath(modelName: SupportedTrainedModelNamesType) { return require.resolve( - `@kbn/test-suites-xpack-platform/api_integration/services/ml/resources/trained_model_definitions/${modelName}/traced_pytorch_model.pt` + `../../../api_integration/services/ml/resources/trained_model_definitions/${modelName}/traced_pytorch_model.pt` ); }, diff --git a/x-pack/platform/test/serverless/config.base.ts b/x-pack/platform/test/serverless/config.base.ts index 79163d5d392e4..b279f5a38327c 100644 --- a/x-pack/platform/test/serverless/config.base.ts +++ b/x-pack/platform/test/serverless/config.base.ts @@ -20,6 +20,7 @@ import { CA_CERT_PATH, kibanaDevServiceAccount } from '@kbn/dev-utils'; import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils'; import path from 'path'; import { fleetPackageRegistryDockerImage, defineDockerServersConfig } from '@kbn/test'; +import { services as svlServices } from './services'; export default async () => { const packageRegistryConfig = path.join(__dirname, './common/package_registry_config.yml'); @@ -184,7 +185,7 @@ export default async () => { // Used by FTR to recognize serverless project and change its behavior accordingly serverless: true, - services: {}, // define later + services: svlServices, // overriding default timeouts from src/platform/packages/shared/kbn-test/src/functional_test_runner/lib/config/schema.ts // so we can easily adjust them for serverless where needed diff --git a/x-pack/platform/test/serverless/services/index.ts b/x-pack/platform/test/serverless/services/index.ts new file mode 100644 index 0000000000000..8e1ed515bd2b4 --- /dev/null +++ b/x-pack/platform/test/serverless/services/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { services as deploymentAgnosticServices } from '../../api_integration_deployment_agnostic/services'; + +export const services = { + ...deploymentAgnosticServices, + // Add any additional serverless-specific services here +}; diff --git a/x-pack/solutions/security/test/defend_workflows_cypress/serverless_config.base.ts b/x-pack/solutions/security/test/defend_workflows_cypress/serverless_config.base.ts index 07d514687e954..82f76fca10f66 100644 --- a/x-pack/solutions/security/test/defend_workflows_cypress/serverless_config.base.ts +++ b/x-pack/solutions/security/test/defend_workflows_cypress/serverless_config.base.ts @@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const svlSharedConfig = await readConfigFile( - require.resolve('@kbn/test-suites-serverless/shared/config.base') + require.resolve('@kbn/test-suites-xpack-platform/serverless/config.base') ); return { diff --git a/x-pack/solutions/security/test/osquery_cypress/serverless_config.base.ts b/x-pack/solutions/security/test/osquery_cypress/serverless_config.base.ts index 07d514687e954..82f76fca10f66 100644 --- a/x-pack/solutions/security/test/osquery_cypress/serverless_config.base.ts +++ b/x-pack/solutions/security/test/osquery_cypress/serverless_config.base.ts @@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const svlSharedConfig = await readConfigFile( - require.resolve('@kbn/test-suites-serverless/shared/config.base') + require.resolve('@kbn/test-suites-xpack-platform/serverless/config.base') ); return { diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v1.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v1.ts index bec22f86bd1f5..439501d8aa9fd 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v1.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import type { GetBenchmarkResponse } from '@kbn/cloud-security-posture-plugin/common/types/latest'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { createPackagePolicy } from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/helper'; +import { createPackagePolicy } from '../helper'; import { FtrProviderContext } from '../../../../ftr_provider_context'; import { RoleCredentials } from '../../../../../shared/services'; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v2.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v2.ts index c32ce2d21ec35..804e412f936ae 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v2.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v2.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import type { GetBenchmarkResponse } from '@kbn/cloud-security-posture-plugin/common/types/latest'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { createPackagePolicy } from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/helper'; +import { createPackagePolicy } from '../helper'; import { FtrProviderContext } from '../../../../ftr_provider_context'; import { RoleCredentials } from '../../../../../shared/services'; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/data.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/data.ts new file mode 100644 index 0000000000000..2c174328e3af7 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/data.ts @@ -0,0 +1,153 @@ +/* + * 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 interface MockTelemetryFindings { + rule: { + benchmark: { id: string; posture_type?: string | undefined; version: string; name: string }; + }; + resource: { type: string; sub_type: string; id: string }; + agent: { id: string }; + result: { evaluation: string }; + host: { name: string }; + cluster_id?: string; + cloud?: { account?: { id: string } }; + cloudbeat?: { kubernetes: { version: string } }; + data_stream?: { namespace: string }; +} + +export interface MockTelemetryData { + [key: string]: MockTelemetryFindings[]; +} + +export const data: MockTelemetryData = { + cspmFindings: [ + { + rule: { + benchmark: { + id: 'cis_aws', + posture_type: 'cspm', + version: 'v1.5.0', + name: 'CIS Amazon Web Services Foundations', + }, + }, + resource: { + type: 'identifyingType', + sub_type: 'aws-password-policy', + id: '15e450b7-8980-5bca-ade2-a0c795f9ea9d', + }, + agent: { id: '07bd3686-98ef-4b23-99cb-9ff544b25ae2' }, + result: { evaluation: 'failed' }, + cloud: { account: { id: 'my-aws-12345' } }, + host: { name: 'docker-fleet-agent' }, + data_stream: { namespace: 'default' }, + }, + { + rule: { + benchmark: { + id: 'cis_aws', + posture_type: 'cspm', + version: 'v1.5.0', + name: 'CIS Amazon Web Services Foundations', + }, + }, + resource: { + type: 'identifyingType', + sub_type: 'aws-password-policy', + id: '15e450b7-8980-5bca-ade2-a0c795f9ea99', + }, + agent: { id: '07bd3686-98ef-4b23-99cb-9ff544b25ae2' }, + result: { evaluation: 'passed' }, + cloud: { account: { id: 'my-aws-12345' } }, + host: { name: 'docker-fleet-agent' }, + data_stream: { namespace: 'default' }, + }, + ], + kspmFindings: [ + { + cluster_id: 'my-k8s-cluster-5555', + rule: { + benchmark: { + id: 'cis_k8s', + version: 'v1.0.0', + name: 'CIS Kubernetes V1.23', + posture_type: 'kspm', + }, + }, + resource: { + type: 'k8s_object', + sub_type: 'ServiceAccount', + id: '1111', + }, + agent: { id: '07bd3686-98ef-4b23-99cb-9ff544b25ae2' }, + result: { evaluation: 'passed' }, + host: { name: 'docker-fleet-agent' }, + cloudbeat: { kubernetes: { version: 'v1.23.0' } }, + data_stream: { namespace: 'default' }, + }, + { + cluster_id: 'my-k8s-cluster-5555', + rule: { + benchmark: { + id: 'cis_k8s', + version: 'v1.0.0', + name: 'CIS Kubernetes V1.23', + posture_type: 'kspm', + }, + }, + resource: { + type: 'process', + sub_type: 'process', + id: '1111', + }, + agent: { id: '07bd3686-98ef-4b23-99cb-9ff544b25ae3' }, + result: { evaluation: 'passed' }, + host: { name: 'control-plane' }, + cloudbeat: { kubernetes: { version: 'v1.23.0' } }, + data_stream: { namespace: 'default' }, + }, + ], + kspmFindingsNoPostureType: [ + { + cluster_id: 'my-k8s-cluster-5555', + rule: { + benchmark: { + id: 'cis_k8s', + version: 'v1.0.0', + name: 'CIS Kubernetes V1.23', + }, + }, + resource: { + type: 'k8s_object', + sub_type: 'ServiceAccount', + id: '1111', + }, + agent: { id: '07bd3686-98ef-4b23-99cb-9ff544b25ae2' }, + result: { evaluation: 'passed' }, + host: { name: 'docker-fleet-agent' }, + cloudbeat: { kubernetes: { version: 'v1.23.0' } }, + }, + { + cluster_id: 'my-k8s-cluster-5555', + rule: { + benchmark: { + id: 'cis_k8s', + version: 'v1.0.0', + name: 'CIS Kubernetes V1.23', + }, + }, + resource: { + type: 'process', + sub_type: 'process', + id: '1111', + }, + agent: { id: '07bd3686-98ef-4b23-99cb-9ff544b25ae3' }, + result: { evaluation: 'passed' }, + host: { name: 'control-plane' }, + cloudbeat: { kubernetes: { version: 'v1.23.0' } }, + }, + ], +}; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/find_csp_benchmark_rule.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/find_csp_benchmark_rule.ts index 61d9713ba684d..6e42724e9c53a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/find_csp_benchmark_rule.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/find_csp_benchmark_rule.ts @@ -11,7 +11,7 @@ import type { FindCspBenchmarkRuleResponse, } from '@kbn/cloud-security-posture-common/schema/rules/latest'; -import { createPackagePolicy } from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/helper'; +import { createPackagePolicy } from './helper'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { RoleCredentials } from '../../../../shared/services'; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/graph.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/graph.ts index 3f2578be42367..1ed7f9de7b64e 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/graph.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/graph.ts @@ -10,9 +10,9 @@ import { ELASTIC_HTTP_VERSION_HEADER, X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; -import { result } from '@kbn/test-suites-xpack-security/cloud_security_posture_api/utils'; import type { Agent } from 'supertest'; import type { GraphRequest } from '@kbn/cloud-security-posture-common/types/graph/v1'; +import { result } from './utils'; import type { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/helper.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/helper.ts new file mode 100644 index 0000000000000..01d948ecc7500 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/helper.ts @@ -0,0 +1,152 @@ +/* + * 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 { Agent as SuperTestAgent } from 'supertest'; + +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { CLOUD_SECURITY_PLUGIN_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; +import { RoleCredentials, SecurityService } from '@kbn/ftr-common-functional-services'; +import { SECURITY_FEATURE_ID } from '@kbn/security-solution-plugin/common/constants'; + +export async function createPackagePolicy( + supertest: SuperTestAgent, + agentPolicyId: string, + policyTemplate: string, + input: string, + deployment: string, + posture: string, + packageName: string = 'cloud_security_posture-1', + roleAuthc?: RoleCredentials, + internalRequestHeader?: { 'x-elastic-internal-origin': string; 'kbn-xsrf': string } +) { + const version = CLOUD_SECURITY_PLUGIN_VERSION; + const title = 'Security Posture Management'; + const streams = [ + { + enabled: true, + data_stream: { + type: 'logs', + dataset: 'cloud_security_posture.vulnerabilities', + }, + }, + ]; + + const inputTemplate = { + enabled: true, + type: input, + policy_template: policyTemplate, + }; + + const inputs = posture === 'vuln_mgmt' ? { ...inputTemplate, streams } : { ...inputTemplate }; + + const { body: postPackageResponse } = + roleAuthc && internalRequestHeader + ? await supertest + .post(`/api/fleet/package_policies`) + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(internalRequestHeader) + .set(roleAuthc.apiKeyHeader) + .send({ + force: true, + name: packageName, + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + inputs: [inputs], + package: { + name: 'cloud_security_posture', + title, + version, + }, + vars: { + deployment: { + value: deployment, + type: 'text', + }, + posture: { + value: posture, + type: 'text', + }, + }, + }) + .expect(200) + : await supertest + .post(`/api/fleet/package_policies`) + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set('kbn-xsrf', 'xxxx') + .send({ + force: true, + name: packageName, + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + inputs: [inputs], + package: { + name: 'cloud_security_posture', + title, + version, + }, + vars: { + deployment: { + value: deployment, + type: 'text', + }, + posture: { + value: posture, + type: 'text', + }, + }, + }) + .expect(200); + + return postPackageResponse.item; +} + +export const createUser = async (security: SecurityService, userName: string, roleName: string) => { + await security.user.create(userName, { + password: 'changeme', + roles: [roleName], + full_name: 'a reporting user', + }); +}; + +export const createCSPRole = async ( + security: SecurityService, + roleName: string, + indicesName?: string[] +) => { + await security.role.create(roleName, { + kibana: [ + { + feature: { [SECURITY_FEATURE_ID]: ['read'], fleetv2: ['all'], fleet: ['read'] }, + spaces: ['*'], + }, + ], + ...(indicesName && indicesName.length > 0 + ? { + elasticsearch: { + indices: [ + { + names: indicesName, + privileges: ['read'], + }, + ], + }, + } + : {}), + }); +}; + +export const deleteRole = async (security: SecurityService, roleName: string) => { + await security.role.delete(roleName); +}; + +export const deleteUser = async (security: SecurityService, userName: string) => { + await security.user.delete(userName); +}; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/mock_data.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/mock_data.ts new file mode 100644 index 0000000000000..beb425c2318ea --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/mock_data.ts @@ -0,0 +1,80 @@ +/* + * 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 Chance from 'chance'; + +const chance = new Chance(); + +export const findingsMockData = [ + { + resource: { id: chance.guid(), name: `kubelet`, sub_type: 'lower case sub type' }, + result: { evaluation: chance.integer() % 2 === 0 ? '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', + event: { + ingested: '2023-08-19T18:20:41Z', + created: '2023-08-19T18:17:15.609124281Z', + }, + data_stream: { + dataset: 'cloud_security_posture.findings', + }, + }, + { + resource: { id: chance.guid(), name: `Pod`, sub_type: 'Upper case sub type' }, + result: { evaluation: chance.integer() % 2 === 0 ? 'passed' : 'failed' }, + rule: { + name: 'lower case rule name', + section: 'Another upper case section', + benchmark: { + id: 'cis_aws', + posture_type: 'cspm', + name: 'CIS Kubernetes V1.23', + version: 'v1.0.0', + }, + type: 'process', + }, + cluster_id: 'Another Upper case cluster id', + event: { + ingested: '2023-08-19T18:20:41Z', + created: '2023-08-19T18:17:15.609124281Z', + }, + data_stream: { + dataset: 'cloud_security_posture.findings', + }, + }, +]; + +export const vulnerabilityMockData = [ + { + resource: { + name: 'NameNama', + id: '12345', + }, + vulnerability: { + severity: 'MEDIUM', + package: { + name: 'github.com/aws/aws-sdk-go', + version: 'v1.42.30', + }, + }, + cvss: { + redhat: { + V3Vector: 'CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N', + V3Score: 5.6, + }, + }, + }, +]; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/cloud_security_metering.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/cloud_security_metering.ts index eaffae523c263..ace0b266e606c 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/cloud_security_metering.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/cloud_security_metering.ts @@ -11,8 +11,8 @@ import { CDR_LATEST_NATIVE_MISCONFIGURATIONS_INDEX_ALIAS, } from '@kbn/cloud-security-posture-common'; import * as http from 'http'; -import { createPackagePolicy } from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/helper'; -import { EsIndexDataProvider } from '@kbn/test-suites-xpack-security/cloud_security_posture_api/utils'; +import { createPackagePolicy } from '../helper'; +import { EsIndexDataProvider } from '../utils'; import { RoleCredentials } from '../../../../../shared/services'; import { getMockFindings } from './mock_data'; import type { FtrProviderContext } from '../../../../ftr_provider_context'; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexed.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexed.ts index b24e25c76540f..9096ce7c9d377 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexed.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexed.ts @@ -11,12 +11,9 @@ import { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, CDR_LATEST_NATIVE_MISCONFIGURATIONS_INDEX_ALIAS, } from '@kbn/cloud-security-posture-common'; -import { createPackagePolicy } from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/helper'; -import { EsIndexDataProvider } from '@kbn/test-suites-xpack-security/cloud_security_posture_api/utils'; -import { - findingsMockData, - vulnerabilityMockData, -} from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/mock_data'; +import { createPackagePolicy } from '../helper'; +import { EsIndexDataProvider } from '../utils'; +import { findingsMockData, vulnerabilityMockData } from '../mock_data'; import { FtrProviderContext } from '../../../../ftr_provider_context'; import { RoleCredentials } from '../../../../../shared/services'; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts index c256a9ae3da7f..3a3ca88b72751 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts @@ -11,12 +11,9 @@ import { FINDINGS_INDEX_DEFAULT_NS, VULNERABILITIES_INDEX_DEFAULT_NS, } from '@kbn/cloud-security-posture-plugin/common/constants'; -import { createPackagePolicy } from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/helper'; -import { - findingsMockData, - vulnerabilityMockData, -} from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/mock_data'; -import { EsIndexDataProvider } from '@kbn/test-suites-xpack-security/cloud_security_posture_api/utils'; +import { createPackagePolicy } from '../helper'; +import { findingsMockData, vulnerabilityMockData } from '../mock_data'; +import { EsIndexDataProvider } from '../utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; import { RoleCredentials } from '../../../../../shared/services'; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_not_deployed_not_installed.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_not_deployed_not_installed.ts index f30e1cc07f8b2..4b90f3bd8c83e 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_not_deployed_not_installed.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_not_deployed_not_installed.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-common'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { createPackagePolicy } from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/helper'; +import { createPackagePolicy } from '../helper'; import { FtrProviderContext } from '../../../../ftr_provider_context'; import { RoleCredentials } from '../../../../../shared/services'; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/telemetry.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/telemetry.ts index 6ee6bf2e0ee9d..33ee0d0fec6fd 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/telemetry.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/telemetry.ts @@ -7,12 +7,9 @@ import expect from '@kbn/expect'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { data as telemetryMockData } from '@kbn/test-suites-xpack-security/cloud_security_posture_api/telemetry/data'; -import { createPackagePolicy } from '@kbn/test-suites-xpack-security/api_integration/apis/cloud_security_posture/helper'; -import { - waitForPluginInitialized, - EsIndexDataProvider, -} from '@kbn/test-suites-xpack-security/cloud_security_posture_api/utils'; +import { data as telemetryMockData } from './data'; +import { createPackagePolicy } from './helper'; +import { waitForPluginInitialized, EsIndexDataProvider } from './utils'; import { SupertestWithRoleScopeType } from '../../../services'; import type { FtrProviderContext } from '../../../ftr_provider_context'; import { RoleCredentials } from '../../../../shared/services'; diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/utils.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/utils.ts new file mode 100644 index 0000000000000..210a081b91473 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/utils.ts @@ -0,0 +1,93 @@ +/* + * 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 { RetryService } from '@kbn/ftr-common-functional-services'; +import type { Agent } from 'supertest'; +import type { ToolingLog } from '@kbn/tooling-log'; +import type { Client as EsClient } from '@elastic/elasticsearch'; +import type { CallbackHandler, Response } from 'superagent'; +import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; + +/** + * Checks if plugin initialization was done + * Required before indexing findings + */ +export const waitForPluginInitialized = ({ + retry, + logger, + supertest, +}: { + retry: RetryService; + logger: ToolingLog; + supertest: Pick; +}): Promise => + retry.try(async () => { + logger.debug('Check CSP plugin is initialized'); + const response = await supertest + .get('/internal/cloud_security_posture/status?check=init') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .expect(200); + expect(response.body).to.eql({ isPluginInitialized: true }); + logger.debug('CSP plugin is initialized'); + }); + +export function result(status: number, logger?: ToolingLog): CallbackHandler { + return (err: any, res: Response) => { + if ((res?.status || err.status) !== status) { + throw new Error( + `Expected ${status} ,got ${res?.status || err.status} resp: ${ + res?.body ? JSON.stringify(res.body) : err.text + }` + ); + } else if (err) { + logger?.warning(`Error result ${err.text}`); + } + }; +} + +export class EsIndexDataProvider { + private es: EsClient; + private readonly index: string; + + constructor(es: EsClient, index: string) { + this.es = es; + this.index = index; + } + + async addBulk(docs: Array>, overrideTimestamp = true) { + const operations = docs.flatMap((doc) => [ + { create: { _index: this.index } }, + { ...doc, ...(overrideTimestamp ? { '@timestamp': new Date().toISOString() } : {}) }, + ]); + + const resp = await this.es.bulk({ refresh: 'wait_for', index: this.index, operations }); + expect(resp.errors).eql(false, `Error in bulk indexing: ${JSON.stringify(resp)}`); + + return resp; + } + + async deleteAll() { + const indexExists = await this.es.indices.exists({ index: this.index }); + + if (indexExists) { + return this.es.deleteByQuery({ + index: this.index, + query: { match_all: {} }, + refresh: true, + }); + } + } + + async destroyIndex() { + const indexExists = await this.es.indices.exists({ index: this.index }); + + if (indexExists) { + return this.es.indices.delete({ index: this.index }); + } + } +} diff --git a/x-pack/test_serverless/functional/page_objects/add_cis_integration_form_page.ts b/x-pack/test_serverless/functional/page_objects/add_cis_integration_form_page.ts new file mode 100644 index 0000000000000..f5672cfcb84f9 --- /dev/null +++ b/x-pack/test_serverless/functional/page_objects/add_cis_integration_form_page.ts @@ -0,0 +1,700 @@ +/* + * 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 { v4 as uuidv4 } from 'uuid'; +import expect from '@kbn/expect'; +import { testSubjectIds } from './constants/test_subject_ids'; +import type { FtrProviderContext } from '../ftr_provider_context'; + +const TEST_IDS = { + POST_INSTALL_AZURE_ARM_TEMPLATE_MODAL: 'postInstallAzureArmTemplateModal', + EXTERNAL_LINK: 'externalLink', + POST_INSTALL_CLOUD_FORMATION_MODAL: 'postInstallCloudFormationModal', + LAUNCH_CLOUD_FORMATION_AGENTLESS_BUTTON: 'launchCloudFormationAgentlessButton', + POST_INSTALL_GOOGLE_CLOUD_SHELL_MODAL: 'postInstallGoogleCloudShellModal', + DATA_COLLECTION_SETUP_STEP: 'dataCollectionSetupStep', + INTEGRATION_NAME_LINK: 'integrationNameLink', + AGENT_ENROLLMENT_FLYOUT: 'agentEnrollmentFlyout', + AGENTLESS_INTEGRATION_NAME_LINK: 'agentlessIntegrationNameLink', + INTEGRATION_POLICY_TABLE: 'integrationPolicyTable', + EDIT_PACKAGE_POLICY_PAGE: 'editPackagePolicy_page', + CREATE_PACKAGE_POLICY_PAGE: 'createPackagePolicy_page', + CREATE_PACKAGE_POLICY_SAVE_BUTTON: 'createPackagePolicySaveButton', + CONFIRM_CLOUD_FORMATION_MODAL_CONFIRM_BUTTON: 'confirmCloudFormationModalConfirmButton', + SAVE_INTEGRATION: 'saveIntegration', + CONFIRM_MODAL_TITLE_TEXT: 'confirmModalTitleText', + CLOUD_SECURITY_POSTURE_PLI_AUTH_BLOCK: 'cloud-security-posture-integration-pli-auth-block', + ADD_AGENT_BUTTON: 'addAgentButton', + POLICY_UPDATE_SUCCESS_TOAST: 'policyUpdateSuccessToast', + AGENT_POLICY_NAME_LINK: 'agentPolicyNameLink', + AGENTLESS_STATUS_BADGE: 'agentlessStatusBadge', + CREATE_AGENT_POLICY_NAME_FIELD: 'createAgentPolicyNameField', + CREDENTIALS_JSON_SECRET_PANEL: 'credentials_json_secret_panel_test_id', + GCP_POLICY_OPTION_TEST_ID: 'cisGcpTestId', + AWS_POLICY_OPTION_TEST_ID: 'cisAwsTestId', + AZURE_POLICY_OPTION_TEST_ID: 'cisAzureTestId', +} as const; + +export function AddCisIntegrationFormPageProvider({ + getService, + getPageObjects, +}: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'header']); + const browser = getService('browser'); + const logger = getService('log'); + const retry = getService('retry'); + + const AWS_CREDENTIAL_SELECTOR = 'aws-credentials-type-selector'; + + const cisAzure = { + getPostInstallArmTemplateModal: async () => { + return await testSubjects.find(TEST_IDS.POST_INSTALL_AZURE_ARM_TEMPLATE_MODAL); + }, + }; + + const cisAws = { + getUrlValueInEditPage: async () => { + /* Newly added/edited integration always shows up on top by default as such we can just always click the most top if we want to check for the latest one */ + const fieldValue = await ( + await testSubjects.find(TEST_IDS.EXTERNAL_LINK) + ).getAttribute('href'); + return fieldValue; + }, + + getPostInstallCloudFormationModal: async () => { + return await testSubjects.find(TEST_IDS.POST_INSTALL_CLOUD_FORMATION_MODAL); + }, + showPostInstallCloudFormationModal: async () => { + return await testSubjects.exists(TEST_IDS.POST_INSTALL_CLOUD_FORMATION_MODAL); + }, + showLaunchCloudFormationAgentlessButton: async () => { + return await testSubjects.exists(TEST_IDS.LAUNCH_CLOUD_FORMATION_AGENTLESS_BUTTON); + }, + }; + + const cisGcp = { + isPostInstallGoogleCloudShellModal: async (isOrg: boolean, orgID?: string, prjID?: string) => { + const googleCloudShellModal = await testSubjects.find( + TEST_IDS.POST_INSTALL_GOOGLE_CLOUD_SHELL_MODAL + ); + const googleCloudShellModalVisibleText = await googleCloudShellModal.getVisibleText(); + const stringProjectId = prjID ? prjID : ''; + const stringOrganizationId = orgID ? `ORG_ID=${orgID}` : 'ORG_ID='; + const orgIdExist = googleCloudShellModalVisibleText.includes(stringOrganizationId); + const prjIdExist = googleCloudShellModalVisibleText.includes(stringProjectId); + + if (isOrg) { + return orgIdExist === true && prjIdExist === true; + } else { + return orgIdExist === false && prjIdExist === true; + } + }, + + checkGcpFieldExist: async (text: string) => { + const field = await testSubjects.findAll(text); + return field.length; + }, + + fillInTextField: async (selector: string, text: string) => { + const textField = await testSubjects.find(selector); + await textField.type(text); + }, + + chooseDropDown: async (selector: string, text: string) => { + const credentialTypeBox = await testSubjects.find(selector); + const chosenOption = await testSubjects.find(text); + await credentialTypeBox.click(); + await chosenOption.click(); + }, + + getFieldValueInEditPage: async (field: string) => { + /* Newly added/edited integration always shows up on top by default as such we can just always click the most top if we want to check for the latest one */ + const integrationList = await testSubjects.findAll(TEST_IDS.INTEGRATION_NAME_LINK); + await integrationList[0].click(); + const fieldValue = await (await testSubjects.find(field)).getAttribute('value'); + return fieldValue; + }, + + doesStringExistInCodeBlock: async (str: string) => { + const flyout = await testSubjects.find(TEST_IDS.AGENT_ENROLLMENT_FLYOUT); + const codeBlock = await flyout.findByXpath('//code'); + const commandsToBeCopied = await codeBlock.getVisibleText(); + return commandsToBeCopied.includes(str); + }, + + getFieldValueInAddAgentFlyout: async (field: string, value: string) => { + /* Newly added/edited integration always shows up on top by default as such we can just always click the most top if we want to check for the latest one */ + const integrationList = await testSubjects.findAll(TEST_IDS.AGENT_ENROLLMENT_FLYOUT); + await integrationList[0].click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + const fieldValue = (await (await testSubjects.find(field)).getAttribute(value)) ?? ''; + return fieldValue; + }, + showLaunchCloudShellAgentlessButton: async () => { + return await testSubjects.exists('launchGoogleCloudShellAgentlessButton'); + }, + }; + + const isRadioButtonChecked = async (selector: string) => { + const page = await testSubjects.find(TEST_IDS.DATA_COLLECTION_SETUP_STEP); + const findCheckedButton = await page.findAllByCssSelector(`input[id="${selector}"]:checked`); + if (findCheckedButton.length === 0) return false; + return true; + }; + + const getUrlOnPostInstallModal = async () => { + /* Newly added/edited integration always shows up on top by default as such we can just always click the most top if we want to check for the latest one */ + const fieldValue = await (await testSubjects.find(TEST_IDS.EXTERNAL_LINK)).getAttribute('href'); + return fieldValue; + }; + + const navigateToAddIntegrationCspmPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + + await PageObjects.common.navigateToUrl( + 'fleet', // Defined in Security Solution plugin + 'integrations/cloud_security_posture/add-integration/cspm', + options + ); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + const navigateToAddIntegrationWithVersionPage = async ( + packageVersion: string, + space?: string + ) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + + await PageObjects.common.navigateToUrl( + 'fleet', + `integrations/cloud_security_posture-${packageVersion}/add-integration`, + options + ); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + const navigateToAddIntegrationCspmWithVersionPage = async ( + packageVersion: string, + space?: string + ) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + + await PageObjects.common.navigateToUrl( + 'fleet', + `integrations/cloud_security_posture-${packageVersion}/add-integration/cspm`, + options + ); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + const navigateToAddIntegrationCnvmPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + + await PageObjects.common.navigateToUrl( + 'fleet', // Defined in Security Solution plugin + 'integrations/cloud_security_posture/add-integration/vuln_mgmt', + options + ); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + const navigateToEditIntegrationPage = async () => { + await testSubjects.click(TEST_IDS.INTEGRATION_NAME_LINK); + }; + + const navigateToEditAgentlessIntegrationPage = async () => { + await testSubjects.click(TEST_IDS.AGENTLESS_INTEGRATION_NAME_LINK); + }; + + const navigateToAddIntegrationKspmPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + + await PageObjects.common.navigateToUrl( + 'fleet', // Defined in Security Solution plugin + 'integrations/cloud_security_posture/add-integration/kspm', + options + ); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + const navigateToIntegrationCspList = async () => { + await PageObjects.common.navigateToActualUrl( + 'integrations', // Defined in Security Solution plugin + '/detail/cloud_security_posture/policies', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + } + ); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + const clickPolicyToBeEdited = async (name: string) => { + const table = await testSubjects.find(TEST_IDS.INTEGRATION_POLICY_TABLE); + const integrationToBeEdited = await table.findByXpath(`//text()="${name}"`); + await integrationToBeEdited.click(); + }; + + const clickFirstElementOnIntegrationTable = async () => { + const integrationList = await testSubjects.findAll(TEST_IDS.INTEGRATION_NAME_LINK); + await integrationList[0].click(); + }; + + const clickFirstElementOnIntegrationTableAddAgent = async () => { + const integrationList = await testSubjects.exists(TEST_IDS.ADD_AGENT_BUTTON); + if (integrationList) { + await testSubjects.click(TEST_IDS.ADD_AGENT_BUTTON); + } + }; + + const clickLaunchAndGetCurrentUrl = async (buttonId: string) => { + const button = await testSubjects.find(buttonId); + await button.click(); + // Wait a bit to allow the new tab to load the URL + await retry.tryForTime(3000, async () => { + await browser.switchTab(1); + }); + const currentUrl = await browser.getCurrentUrl(); + await browser.closeCurrentWindow(); + await browser.switchTab(0); + return currentUrl; + }; + + const getIntegrationFormEntirePage = () => testSubjects.find(TEST_IDS.DATA_COLLECTION_SETUP_STEP); + + const getIntegrationPolicyTable = () => testSubjects.find(TEST_IDS.INTEGRATION_POLICY_TABLE); + + const getIntegrationFormEditPage = () => testSubjects.find(TEST_IDS.EDIT_PACKAGE_POLICY_PAGE); + + const findOptionInPage = async (text: string) => { + await PageObjects.header.waitUntilLoadingHasFinished(); + const optionToBeClicked = await testSubjects.find(text); + return await optionToBeClicked; + }; + + const clickAccordianButton = async (text: string) => { + await PageObjects.header.waitUntilLoadingHasFinished(); + const advancedAccordian = await testSubjects.find(text); + await advancedAccordian.scrollIntoView(); + await advancedAccordian.click(); + }; + + const selectSetupTechnology = async (setupTechnology: 'agentless' | 'agent-based') => { + const radioGroup = await testSubjects.find(testSubjectIds.SETUP_TECHNOLOGY_SELECTOR); + const radio = await radioGroup.findByCssSelector(`input[value='${setupTechnology}']`); + await radio.click(); + }; + + const getSetupTechnologyRadio = async (setupTechnology: 'agentless' | 'agent-based') => { + const radioGroup = await testSubjects.find(testSubjectIds.SETUP_TECHNOLOGY_SELECTOR); + return await radioGroup.findByCssSelector(`input[value='${setupTechnology}']`); + }; + + const showSetupTechnologyComponent = async () => { + return await testSubjects.exists(testSubjectIds.SETUP_TECHNOLOGY_SELECTOR); + }; + + const selectAwsCredentials = async ( + credentialType: 'direct' | 'temporary' | 'cloud_connectors' + ) => { + let credentialTypeValue = 'direct_access_keys'; + + if (credentialType === 'temporary') { + credentialTypeValue = 'temporary_keys'; + } + if (credentialType === 'cloud_connectors') { + credentialTypeValue = 'cloud_connector'; + } + await testSubjects.click(AWS_CREDENTIAL_SELECTOR); + await selectValue(AWS_CREDENTIAL_SELECTOR, credentialTypeValue); + }; + + const clickOptionButton = async (text: string) => { + const optionToBeClicked = await findOptionInPage(text); + await optionToBeClicked.scrollIntoView(); + await optionToBeClicked.click(); + }; + + const isSaveButtonEnabled = async () => { + const saveButton = await testSubjects.find(TEST_IDS.CREATE_PACKAGE_POLICY_SAVE_BUTTON); + const isEnabled = await saveButton.getAttribute('disabled'); + return isEnabled === null; // If the button is enabled, it won't have a 'disabled' attribute + }; + + const clickAwsPolicyOption = async () => { + const awsPolicyOption = await findOptionInPage(TEST_IDS.AWS_POLICY_OPTION_TEST_ID); + await awsPolicyOption.click(); + }; + + const clickGcpPolicyOption = async () => { + const gcpPolicyOption = await findOptionInPage(TEST_IDS.GCP_POLICY_OPTION_TEST_ID); + await gcpPolicyOption.click(); + }; + + const clickAzurePolicyOption = async () => { + const azurePolicyOption = await findOptionInPage(TEST_IDS.AZURE_POLICY_OPTION_TEST_ID); + await azurePolicyOption.click(); + }; + + const clickSaveButton = async () => { + const optionToBeClicked = await findOptionInPage(TEST_IDS.CREATE_PACKAGE_POLICY_SAVE_BUTTON); + await optionToBeClicked.click(); + }; + + const waitUntilLaunchCloudFormationButtonAppears = async () => + await testSubjects.exists(TEST_IDS.CONFIRM_CLOUD_FORMATION_MODAL_CONFIRM_BUTTON); + + const clickSaveIntegrationButton = async () => { + const optionToBeClicked = await findOptionInPage(TEST_IDS.SAVE_INTEGRATION); + await optionToBeClicked.click(); + }; + + const getPostInstallModal = async () => { + return await testSubjects.exists(TEST_IDS.CONFIRM_MODAL_TITLE_TEXT); + }; + + const checkIntegrationPliAuthBlockExists = async () => { + return await testSubjects.exists(TEST_IDS.CLOUD_SECURITY_POSTURE_PLI_AUTH_BLOCK); + }; + + const fillInTextField = async (selector: string, text: string) => { + const textField = await testSubjects.find(selector); + await textField.clearValueWithKeyboard(); + await textField.type(text); + }; + + const fillInComboBox = async (selector: string, text: string) => { + const comboBox = await testSubjects.find(selector); + // Click to open the combobox + await comboBox.click(); + // Find the input within the combobox + const input = await comboBox.findByCssSelector('input'); + // Clear and type new value + await input.clearValueWithKeyboard(); + await input.type(text); + // Press Enter to create the custom option + await input.pressKeys(browser.keys.ENTER); + }; + + const chooseDropDown = async (selector: string, text: string) => { + const credentialTypeBox = await testSubjects.find(selector); + const chosenOption = await testSubjects.find(text); + await credentialTypeBox.click(); + await chosenOption.click(); + }; + + const doesStringExistInCodeBlock = async (str: string) => { + const flyout = await testSubjects.find(TEST_IDS.AGENT_ENROLLMENT_FLYOUT); + const codeBlock = await flyout.findByXpath('//code'); + const commandsToBeCopied = await codeBlock.getVisibleText(); + return commandsToBeCopied.includes(str); + }; + + const getFieldValueInAddAgentFlyout = async (field: string, value: string) => { + /* Newly added/edited integration always shows up on top by default as such we can just always click the most top if we want to check for the latest one */ + const integrationList = await testSubjects.findAll(TEST_IDS.AGENT_ENROLLMENT_FLYOUT); + await integrationList[0].click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + const fieldValue = await (await testSubjects.find(field)).getAttribute(value); + return fieldValue; + }; + + const selectValue = async (selector: string, value: string) => { + return testSubjects.selectValue(selector, value); + }; + + const getValueInEditPage = async (field: string) => { + /* Newly added/edited integration always shows up on top by default as such we can just always click the most top if we want to check for the latest one */ + const fieldValue = await (await testSubjects.find(field)).getAttribute('value'); + return fieldValue; + }; + + const isOptionChecked = async (testId: string, id: string) => { + const checkBox = await testSubjects.find(testId); + return await (await checkBox.findByCssSelector(`input[id='${id}']`)).getAttribute('checked'); + }; + + const getReplaceSecretButton = async (secretField: string) => { + return await testSubjects.find(`button-replace-${secretField}`); + }; + + const showCredentialJsonSecretPanel = async () => { + return await testSubjects.exists(TEST_IDS.CREDENTIALS_JSON_SECRET_PANEL); + }; + + const inputUniqueIntegrationName = async () => { + const flyout = await testSubjects.find(TEST_IDS.CREATE_PACKAGE_POLICY_PAGE); + const nameField = await flyout.findAllByCssSelector('input[id="name"]'); + await nameField[0].type(uuidv4()); + }; + + const inputIntegrationName = async (text: string) => { + const page = await testSubjects.find(TEST_IDS.CREATE_PACKAGE_POLICY_PAGE); + const nameField = await page.findAllByCssSelector('input[id="name"]'); + await nameField[0].clearValueWithKeyboard(); + await nameField[0].type(text); + }; + + const getSecretComponentReplaceButton = async (secretButtonSelector: string) => { + const secretComponentReplaceButton = await testSubjects.find(secretButtonSelector); + return secretComponentReplaceButton; + }; + + const getElementText = async (selector: string) => { + const element = await testSubjects.find(selector); + const text = await element.getVisibleText(); + return text; + }; + + const getFieldAttributeValue = async (field: string, attribute: string) => { + const fieldValue = await (await testSubjects.find(field)).getAttribute(attribute); + return fieldValue; + }; + + const getFieldValueInEditPage = async (field: string) => { + /* Newly added/edited integration always shows up on top by default as such we can just always click the most top if we want to check for the latest one */ + await navigateToEditIntegrationPage(); + const fieldValue = await getFieldAttributeValue(field, 'value'); + return fieldValue; + }; + + const fillOutAWSForm = async () => { + const directAccessKeyId = 'directAccessKeyIdTest'; + const directAccessSecretKey = 'directAccessSecretKeyTest'; + + await clickOptionButton(testSubjectIds.CIS_AWS_OPTION_TEST_ID); + + await selectSetupTechnology('agentless'); + await selectValue(testSubjectIds.AWS_CREDENTIAL_SELECTOR, 'direct_access_keys'); + await fillInTextField(testSubjectIds.DIRECT_ACCESS_KEY_ID_TEST_ID, directAccessKeyId); + await fillInTextField(testSubjectIds.DIRECT_ACCESS_SECRET_KEY_TEST_ID, directAccessSecretKey); + }; + + const fillOutGCPForm = async () => { + const projectId = 'PRJ_NAME_TEST'; + const credentialJson = 'CRED_JSON_TEST_NAME'; + + await clickOptionButton(testSubjectIds.CIS_GCP_OPTION_TEST_ID); + await clickOptionButton(testSubjectIds.GCP_SINGLE_ACCOUNT_TEST_ID); + await selectSetupTechnology('agentless'); + await fillInTextField(testSubjectIds.PRJ_ID_TEST_ID, projectId); + await fillInTextField(testSubjectIds.CREDENTIALS_JSON_TEST_ID, credentialJson); + }; + + const fillOutForm = async (cloudProvider: 'aws' | 'gcp') => { + switch (cloudProvider) { + case 'aws': + await fillOutAWSForm(); + break; + case 'gcp': + await fillOutGCPForm(); + break; + } + }; + + const createAgentlessIntegration = async ({ + cloudProvider, + }: { + cloudProvider: 'aws' | 'gcp'; + }) => { + // Navigate to the Integration CSPM to create an agentless integration + await navigateToAddIntegrationCspmPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await inputIntegrationName(`cloud_security_posture-${new Date().toISOString()}`); + + await fillOutForm(cloudProvider); + + // Click Save Button to create the Integration then navigate to Integration Policies Tab Page + await clickSaveButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + const editAgentlessIntegration = async (testSubjectId: string, value: string) => { + await navigateToIntegrationCspList(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await navigateToEditAgentlessIntegrationPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // Fill out form to edit an agentless integration + await fillInTextField(testSubjectId, value); + + // TechDebt: This is a workaround to ensure the form is saved + // const agentlessRadio = await getSetupTechnologyRadio('agentless'); + // const agentBasedRadio = await getSetupTechnologyRadio('agent-based'); + // expect(await agentlessRadio.isEnabled()).to.be(true); + // expect(agentBasedRadio.isEnabled()).to.be(true); + + // Clicking Save Button updates and navigates to Integration Policies Tab Page + await clickSaveIntegrationButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // Check if the Direct Access Key is updated package policy api with successful toast + expect(await testSubjects.exists(TEST_IDS.POLICY_UPDATE_SUCCESS_TOAST)).to.be(true); + + await navigateToEditAgentlessIntegrationPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + const showSuccessfulToast = async (testSubjectId: string) => { + return await testSubjects.exists(testSubjectId); + }; + + const getFirstCspmIntegrationPageIntegration = async () => { + const integration = await testSubjects.find(TEST_IDS.INTEGRATION_NAME_LINK); + return await integration.getVisibleText(); + }; + + const getFirstCspmIntegrationPageAgentlessIntegration = async () => { + const integration = await testSubjects.find(TEST_IDS.AGENTLESS_INTEGRATION_NAME_LINK); + return await integration.getVisibleText(); + }; + + const getFirstCspmIntegrationPageAgent = async () => { + const agent = await testSubjects.find(TEST_IDS.AGENT_POLICY_NAME_LINK); + // this is assuming that the agent was just created therefor should be the first element + return await agent.getVisibleText(); + }; + + const getFirstCspmIntegrationPageAgentlessStatus = async () => { + const agent = await testSubjects.find(TEST_IDS.AGENTLESS_STATUS_BADGE); + // this is assuming that the agent was just created therefor should be the first element + return await agent.getVisibleText(); + }; + + const getAgentBasedPolicyValue = async () => { + const agentName = await testSubjects.find(TEST_IDS.CREATE_AGENT_POLICY_NAME_FIELD); + return await agentName.getAttribute('value'); + }; + + const closeAllOpenTabs = async () => { + const handles = await browser.getAllWindowHandles(); + logger.debug(`Found ${handles.length} tabs to clean up`); + try { + // Keep the first tab and close all others in reverse order + for (let i = handles.length - 1; i > 0; i--) { + await browser.switchTab(i); + await browser.closeCurrentWindow(); + logger.debug(`Closed tab ${i}`); + } + + // Switch back to the first tab + await browser.switchTab(0); + logger.debug('Successfully closed all extra tabs and returned to main tab'); + } catch (err) { + logger.error(`Error while closing tabs: ${err}`); + // Attempt to return to first tab even if there was an error + try { + await browser.switchTab(0); + } catch (switchErr) { + logger.error(`Error switching back to first tab: ${switchErr}`); + } + } + }; + + return { + cisAzure, + cisAws, + cisGcp, + navigateToAddIntegrationWithVersionPage, + navigateToAddIntegrationCspmPage, + navigateToAddIntegrationCspmWithVersionPage, + navigateToAddIntegrationCnvmPage, + navigateToAddIntegrationKspmPage, + navigateToIntegrationCspList, + getUrlOnPostInstallModal, + isRadioButtonChecked, + clickPolicyToBeEdited, + clickFirstElementOnIntegrationTable, + clickFirstElementOnIntegrationTableAddAgent, + clickLaunchAndGetCurrentUrl, + getIntegrationFormEntirePage, + getIntegrationPolicyTable, + getIntegrationFormEditPage, + findOptionInPage, + clickOptionButton, + selectAwsCredentials, + selectSetupTechnology, + getSetupTechnologyRadio, + clickSaveButton, + clickSaveIntegrationButton, + clickAccordianButton, + getPostInstallModal, + fillInTextField, + chooseDropDown, + getFieldValueInEditPage, + doesStringExistInCodeBlock, + getFieldValueInAddAgentFlyout, + selectValue, + getValueInEditPage, + isOptionChecked, + checkIntegrationPliAuthBlockExists, + getReplaceSecretButton, + getSecretComponentReplaceButton, + inputUniqueIntegrationName, + getFieldAttributeValue, + getElementText, + createAgentlessIntegration, + editAgentlessIntegration, + testSubjectIds, + inputIntegrationName, + getFirstCspmIntegrationPageIntegration, + getFirstCspmIntegrationPageAgentlessIntegration, + getFirstCspmIntegrationPageAgent, + getFirstCspmIntegrationPageAgentlessStatus, + getAgentBasedPolicyValue, + showSuccessfulToast, + showSetupTechnologyComponent, + navigateToEditIntegrationPage, + navigateToEditAgentlessIntegrationPage, + closeAllOpenTabs, + waitUntilLaunchCloudFormationButtonAppears, + showCredentialJsonSecretPanel, + isSaveButtonEnabled, + clickAwsPolicyOption, + clickGcpPolicyOption, + clickAzurePolicyOption, + fillInComboBox, + }; +} diff --git a/x-pack/test_serverless/functional/page_objects/constants/test_subject_ids.ts b/x-pack/test_serverless/functional/page_objects/constants/test_subject_ids.ts new file mode 100644 index 0000000000000..c076dc4aa1ec7 --- /dev/null +++ b/x-pack/test_serverless/functional/page_objects/constants/test_subject_ids.ts @@ -0,0 +1,81 @@ +/* + * 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 const testSubjectIds = { + CIS_AWS_OPTION_TEST_ID: 'cisAwsTestId', + AWS_SINGLE_ACCOUNT_TEST_ID: 'awsSingleTestId', + AWS_MANUAL_TEST_ID: 'aws-manual-setup-option', + AWS_CREDENTIAL_SELECTOR: 'aws-credentials-type-selector', + ROLE_ARN_TEST_ID: 'awsRoleArnInput', + DIRECT_ACCESS_KEY_ID_TEST_ID: 'awsDirectAccessKeyId', + DIRECT_ACCESS_SECRET_KEY_TEST_ID: 'passwordInput-secret-access-key', + TEMP_ACCESS_KEY_ID_TEST_ID: 'awsTemporaryKeysAccessKeyId', + TEMP_ACCESS_KEY_SECRET_KEY_TEST_ID: 'passwordInput-secret-access-key', + TEMP_ACCESS_SESSION_TOKEN_TEST_ID: 'awsTemporaryKeysSessionToken', + SHARED_CREDENTIALS_FILE_TEST_ID: 'awsSharedCredentialFile', + SHARED_CREDETIALS_PROFILE_NAME_TEST_ID: 'awsCredentialProfileName', + CIS_GCP_OPTION_TEST_ID: 'cisGcpTestId', + GCP_ORGANIZATION_TEST_ID: 'gcpOrganizationAccountTestId', + GCP_SINGLE_ACCOUNT_TEST_ID: 'gcpSingleAccountTestId', + GCP_CLOUD_SHELL_TEST_ID: 'gcpGoogleCloudShellOptionTestId', + GCP_MANUAL_TEST_ID: 'gcpManualOptionTestId', + PRJ_ID_TEST_ID: 'project_id_test_id', + ORG_ID_TEST_ID: 'organization_id_test_id', + CREDENTIALS_TYPE_TEST_ID: 'credentials_type_test_id', + CREDENTIALS_FILE_TEST_ID: 'credentials_file_test_id', + CREDENTIALS_JSON_TEST_ID: 'textAreaInput-credentials-json', + CIS_AZURE_OPTION_TEST_ID: 'cisAzureTestId', + CIS_AZURE_SINGLE_SUB_TEST_ID: 'azureSingleAccountTestId', + AZURE_CREDENTIAL_SELECTOR: 'azure-credentials-type-selector', + CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS: { + TENANT_ID: 'cisAzureTenantId', + CLIENT_ID: 'cisAzureClientId', + CLIENT_SECRET: 'passwordInput-client-secret', + CLIENT_CERTIFICATE_PATH: 'cisAzureClientCertificatePath', + CLIENT_CERTIFICATE_PASSWORD: 'passwordInput-client-certificate-password', + CLIENT_USERNAME: 'cisAzureClientUsername', + CLIENT_PASSWORD: 'cisAzureClientPassword', + }, + CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS: { + ARM_TEMPLATE: 'cisAzureArmTemplate', + MANUAL: 'cisAzureManual', + }, + PREVIEW_SECTION_TEST_ID: 'previewSection', + PREVIEW_SECTION_HEADER_TEST_ID: 'previewSectionBannerText', + EVENTS_TABLE_ROW_CSS_SELECTOR: '[data-test-subj="events-viewer-panel"] .euiDataGridRow', + VISUALIZATIONS_SECTION_HEADER_TEST_ID: 'securitySolutionFlyoutVisualizationsHeader', + VISUALIZATIONS_SECTION_CONTENT_TEST_ID: 'securitySolutionFlyoutVisualizationsContent', + GRAPH_PREVIEW_CONTENT_TEST_ID: 'securitySolutionFlyoutGraphPreviewContent', + GRAPH_PREVIEW_LOADING_TEST_ID: 'securitySolutionFlyoutGraphPreviewLoading', + GRAPH_PREVIEW_TITLE_LINK_TEST_ID: 'securitySolutionFlyoutGraphPreviewTitleLink', + NODE_EXPAND_BUTTON_TEST_ID: 'cloudSecurityGraphNodeExpandButton', + GRAPH_INVESTIGATION_TEST_ID: 'cloudSecurityGraphGraphInvestigation', + GRAPH_NODE_EXPAND_POPOVER_TEST_ID: 'cloudSecurityGraphGraphInvestigationGraphNodeExpandPopover', + GRAPH_NODE_POPOVER_EXPLORE_RELATED_TEST_ID: + 'cloudSecurityGraphGraphInvestigationExploreRelatedEntities', + GRAPH_NODE_POPOVER_SHOW_ACTIONS_BY_TEST_ID: + 'cloudSecurityGraphGraphInvestigationShowActionsByEntity', + GRAPH_NODE_POPOVER_SHOW_ACTIONS_ON_TEST_ID: + 'cloudSecurityGraphGraphInvestigationShowActionsOnEntity', + GRAPH_NODE_POPOVER_SHOW_ENTITY_DETAILS_ITEM_ID: + 'cloudSecurityGraphGraphInvestigationShowEntityDetails', + GRAPH_LABEL_EXPAND_POPOVER_TEST_ID: 'cloudSecurityGraphGraphInvestigationGraphLabelExpandPopover', + GRAPH_LABEL_EXPAND_POPOVER_SHOW_EVENTS_WITH_THIS_ACTION_ITEM_ID: + 'cloudSecurityGraphGraphInvestigationShowEventsWithThisAction', + GRAPH_LABEL_EXPAND_POPOVER_SHOW_EVENT_DETAILS_ITEM_ID: + 'cloudSecurityGraphGraphInvestigationShowEventDetails', + GRAPH_ACTIONS_TOGGLE_SEARCH_ID: 'cloudSecurityGraphGraphInvestigationToggleSearch', + GRAPH_ACTIONS_INVESTIGATE_IN_TIMELINE_ID: + 'cloudSecurityGraphGraphInvestigationInvestigateInTimeline', + ALERT_TABLE_ROW_CSS_SELECTOR: '[data-test-subj="alertsTableIsLoaded"] .euiDataGridRow', + SETUP_TECHNOLOGY_SELECTOR: 'setup-technology-selector', + DIRECT_ACCESS_KEYS: 'direct_access_keys', + SETUP_TECHNOLOGY_SELECTOR_AGENTLESS_RADIO: 'setup-technology-agentless-radio', + SETUP_TECHNOLOGY_SELECTOR_AGENT_BASED_RADIO: 'setup-technology-agent-based-radio', + NAMESPACE_INPUT: 'namespaceInput', + ADVANCED_OPTION_ACCORDION: 'advancedOptionsAccordion', +}; diff --git a/x-pack/test_serverless/functional/page_objects/csp_dashboard_page.ts b/x-pack/test_serverless/functional/page_objects/csp_dashboard_page.ts new file mode 100644 index 0000000000000..a83e90f8038c3 --- /dev/null +++ b/x-pack/test_serverless/functional/page_objects/csp_dashboard_page.ts @@ -0,0 +1,223 @@ +/* + * 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 expect from '@kbn/expect'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; +import type { FtrProviderContext } from '../ftr_provider_context'; + +// Defined in CSP plugin +const LATEST_FINDINGS_INDEX = 'security_solution-cloud_security_posture.misconfiguration_latest'; + +export function CspDashboardPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'header']); + const retry = getService('retry'); + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + /** + * required before indexing findings + */ + const waitForPluginInitialized = (): Promise => + retry.try(async () => { + log.debug('Check CSP plugin is initialized'); + const response = await supertest + .get('/internal/cloud_security_posture/status?check=init') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200); + expect(response.body).to.eql({ isPluginInitialized: true }); + log.debug('CSP plugin is initialized'); + }); + + const index = { + remove: () => es.indices.delete({ index: LATEST_FINDINGS_INDEX, ignore_unavailable: true }), + add: async (findingsMock: T[]) => { + await Promise.all( + findingsMock.map((finding) => + es.index({ + index: LATEST_FINDINGS_INDEX, + document: finding, + }) + ) + ); + }, + }; + + const TAB_TYPES = { + CLOUD: 'Cloud', + KUBERNETES: 'Kubernetes', + } as const; + + const dashboard = { + getDashboardPageHeader: () => testSubjects.find('cloud-posture-dashboard-page-header'), + + getDashboardTabs: async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + const dashboardPageHeader = await dashboard.getDashboardPageHeader(); + return await dashboardPageHeader.findByClassName('euiTabs'); + }, + + getCloudTab: async () => { + const tabs = await dashboard.getDashboardTabs(); + return await tabs.findByXpath(`//span[text()="${TAB_TYPES.CLOUD}"]`); + }, + + getKubernetesTab: async () => { + const tabs = await dashboard.getDashboardTabs(); + return await tabs.findByXpath(`//span[text()="${TAB_TYPES.KUBERNETES}"]`); + }, + + clickTab: async (tab: (typeof TAB_TYPES)[keyof typeof TAB_TYPES]) => { + if (tab === TAB_TYPES.CLOUD) { + const cloudTab = await dashboard.getCloudTab(); + await cloudTab.click(); + } + if (tab === TAB_TYPES.KUBERNETES) { + const k8sTab = await dashboard.getKubernetesTab(); + await k8sTab.click(); + } + }, + + getAllComplianceScoresByCisSection: async (tab: (typeof TAB_TYPES)[keyof typeof TAB_TYPES]) => { + await dashboard.getDashoard(tab); + const pageContainer = await testSubjects.find('pageContainer'); + return await pageContainer.findAllByTestSubject('cloudSecurityFindingsComplianceScore'); + }, + + getDashoard: async (tab: (typeof TAB_TYPES)[keyof typeof TAB_TYPES]) => { + if (tab === TAB_TYPES.CLOUD) { + return await dashboard.getCloudDashboard(); + } + if (tab === TAB_TYPES.KUBERNETES) { + return await dashboard.getKubernetesDashboard(); + } + }, + + getFindingsLinks: async (tab: (typeof TAB_TYPES)[keyof typeof TAB_TYPES]) => { + await dashboard.getDashoard(tab); + const pageContainer = await testSubjects.find('pageContainer'); + return [ + await pageContainer.findByTestSubject('dashboard-summary-passed-findings'), + await pageContainer.findByTestSubject('dashboard-summary-failed-findings'), + ...(await pageContainer.findAllByTestSubject('grouped-findings-evaluation-link')), + ...(await pageContainer.findAllByTestSubject('view-all-failed-findings')), + ...(await pageContainer.findAllByTestSubject('benchmark-section-bench-name')), + ...(await pageContainer.findAllByTestSubject('benchmark-asset-type')), + ...(await pageContainer.findAllByTestSubject('compliance-score-section-passed')), + ...(await pageContainer.findAllByTestSubject('compliance-score-section-failed')), + ]; + }, + + getFindingsLinkAtIndex: async ( + tab: (typeof TAB_TYPES)[keyof typeof TAB_TYPES], + linkIndex = 0 + ) => { + const allLinks = await dashboard.getFindingsLinks(tab); + return allLinks[linkIndex]; + }, + + getFindingsLinksCount: async (tab: (typeof TAB_TYPES)[keyof typeof TAB_TYPES]) => { + const allLinks = await dashboard.getFindingsLinks(tab); + return allLinks.length; + }, + + getIntegrationDashboardContainer: () => testSubjects.find('dashboard-container'), + + // Cloud Dashboard + + getCloudDashboard: async () => { + await dashboard.clickTab(TAB_TYPES.CLOUD); + return await testSubjects.find('cloud-dashboard-container'); + }, + + getCloudSummarySection: async () => { + await dashboard.getCloudDashboard(); + return await testSubjects.find('dashboard-summary-section'); + }, + + getAllCloudComplianceScores: async () => { + await dashboard.getCloudDashboard(); + return await testSubjects.findAll('dashboard-summary-section-compliance-score'); + }, + + getCloudComplianceScore: async () => { + await dashboard.getCloudSummarySection(); + return await testSubjects.find('dashboard-summary-section-compliance-score'); + }, + + getCloudResourcesEvaluatedCard: async () => { + await dashboard.getCloudDashboard(); + return await testSubjects.find('dashboard-counter-card-resources-evaluated'); + }, + + getCloudResourcesEvaluated: async () => { + const resourcesEvaluatedCard = await dashboard.getCloudResourcesEvaluatedCard(); + return await resourcesEvaluatedCard.findByXpath('//div/p/span'); + }, + + // Kubernetes Dashboard + + getKubernetesDashboard: async () => { + await dashboard.clickTab(TAB_TYPES.KUBERNETES); + return await testSubjects.find('kubernetes-dashboard-container'); + }, + + getKubernetesSummarySection: async () => { + await dashboard.getKubernetesDashboard(); + return await testSubjects.find('dashboard-summary-section'); + }, + + getKubernetesComplianceScore: async () => { + await retry.waitFor( + 'Cloud posture dashboard summary section to be displayed', + async () => !!(await dashboard.getKubernetesSummarySection()) + ); + + return await testSubjects.find('dashboard-summary-section-compliance-score'); + }, + + getKubernetesResourcesEvaluatedCard: async () => { + await dashboard.getKubernetesDashboard(); + return await testSubjects.find('dashboard-counter-card-resources-evaluated'); + }, + + getKubernetesResourcesEvaluated: async () => { + const resourcesEvaluatedCard = await dashboard.getKubernetesResourcesEvaluatedCard(); + return await resourcesEvaluatedCard.findByXpath('//div/p/span'); + }, + }; + + const navigateToComplianceDashboardPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + + await PageObjects.common.navigateToUrl( + 'securitySolution', // Defined in Security Solution plugin + 'cloud_security_posture/dashboard', + options + ); + }; + + return { + waitForPluginInitialized, + navigateToComplianceDashboardPage, + dashboard, + index, + TAB_TYPES, + }; +} diff --git a/x-pack/test_serverless/functional/page_objects/index.ts b/x-pack/test_serverless/functional/page_objects/index.ts index 53a7469d92896..a3f39f22c6225 100644 --- a/x-pack/test_serverless/functional/page_objects/index.ts +++ b/x-pack/test_serverless/functional/page_objects/index.ts @@ -7,7 +7,6 @@ import { pageObjects as xpackFunctionalPageObjects } from '@kbn/test-suites-xpack/functional/page_objects'; -import { cloudSecurityPosturePageObjects } from '@kbn/test-suites-xpack-security/cloud_security_posture_functional/page_objects'; import { SvlCommonPageProvider } from './svl_common_page'; import { SvlCommonNavigationProvider } from './svl_common_navigation'; import { SvlObltOverviewPageProvider } from './svl_oblt_overview_page'; @@ -25,10 +24,12 @@ import { SvlApiKeysProvider } from './svl_api_keys'; import { SvlSearchCreateIndexPageProvider } from './svl_search_create_index_page'; import { SvlSearchInferenceManagementPageProvider } from './svl_search_inference_management_page'; import { SvlDataUsagePageProvider } from './svl_data_usage'; +import { CspDashboardPageProvider } from './csp_dashboard_page'; +import { AddCisIntegrationFormPageProvider } from './add_cis_integration_form_page'; +import { CspSecurityCommonProvider } from './security_common'; export const pageObjects = { ...xpackFunctionalPageObjects, - ...cloudSecurityPosturePageObjects, svlCommonPage: SvlCommonPageProvider, svlCommonNavigation: SvlCommonNavigationProvider, @@ -47,4 +48,8 @@ export const pageObjects = { svlSearchCreateIndexPage: SvlSearchCreateIndexPageProvider, svlSearchInferenceManagementPage: SvlSearchInferenceManagementPageProvider, svlDataUsagePage: SvlDataUsagePageProvider, + // Cloud Security Posture specific page objects + cloudPostureDashboard: CspDashboardPageProvider, + cisAddIntegration: AddCisIntegrationFormPageProvider, + cspSecurity: CspSecurityCommonProvider, }; diff --git a/x-pack/test_serverless/functional/page_objects/security_common.ts b/x-pack/test_serverless/functional/page_objects/security_common.ts new file mode 100644 index 0000000000000..28437fcdbe5de --- /dev/null +++ b/x-pack/test_serverless/functional/page_objects/security_common.ts @@ -0,0 +1,133 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export function CspSecurityCommonProvider({ getPageObjects, getService }: FtrProviderContext) { + const security = getService('security'); + const pageObjects = getPageObjects(['security']); + + const roles = [ + { + name: 'csp_viewer', + elasticsearch: { + indices: [ + { + names: ['logs-cloud_security_posture.findings-*'], + privileges: ['read'], + }, + { + names: ['logs-cloud_security_posture.findings_latest-*'], + privileges: ['read'], + }, + { + names: ['security_solution-cloud_security_posture.misconfiguration_latest'], + privileges: ['read'], + }, + + { + names: ['logs-cloud_security_posture.scores-*'], + privileges: ['read'], + }, + ], + }, + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], + }, + { + name: 'missing_access_findings_latest_role', + elasticsearch: { + indices: [ + { + names: ['logs-cloud_security_posture.findings-*'], + privileges: ['read'], + }, + { + names: ['logs-cloud_security_posture.scores-*'], + privileges: ['read'], + }, + ], + }, + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], + }, + ]; + + const users = [ + { + name: 'csp_read_user', + full_name: 'csp viewer', + password: 'test123', + roles: ['csp_viewer'], + }, + { + name: 'csp_missing_latest_findings_access_user', + full_name: 'missing latest findings index access', + password: 'csp123', + roles: ['missing_access_findings_latest_role'], + }, + ]; + + return { + async createRoles() { + for (const role of roles) { + await security.role.create(role.name, { + elasticsearch: role.elasticsearch, + kibana: role.kibana, + }); + } + }, + + async createUsers() { + for (const user of users) { + await security.user.create(user.name, { + password: user.password, + roles: user.roles, + full_name: user.full_name, + }); + } + }, + + async login(user: string) { + await pageObjects.security.login(user, this.getPasswordForUser(user), { + expectSpaceSelector: false, + }); + }, + + async logout() { + await pageObjects.security.forceLogout(); + }, + + async cleanRoles() { + for (const role of roles) { + await security.role.delete(role.name); + } + }, + + async cleanUsers() { + for (const user of users) { + await security.user.delete(user.name); + } + }, + + getPasswordForUser(user: string): string { + const userConfig = users.find((u) => u.name === user); + if (userConfig === undefined) { + throw new Error(`Can't log in user ${user} - not defined`); + } + return userConfig.password; + }, + }; +} diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index fc2ae6ff54ba1..60d45d4749409 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -89,11 +89,11 @@ "@kbn/data-usage-plugin", "@kbn/scout-info", "@kbn/test-suites-xpack-platform", - "@kbn/test-suites-xpack-security", "@kbn/streams-schema", "@kbn/infra-plugin", "@kbn/observability-plugin", "@kbn/response-ops-rule-params", "@kbn/telemetry-collection-manager-plugin", + "@kbn/security-solution-plugin", ] }