mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[ska] create platform shared package for Cypress test helpers (#224361)
## Summary Part of https://github.com/elastic/kibana-team/issues/1503 This PR adds `kbn/cypress-test-helper` as platform shared package to replace invalid imports from private `security-solution` plugin in platform shared plugin `osquery`. The reason is that we are **currently blocked with x-pack relocation due to circular dependency**, e.g. in https://github.com/elastic/kibana/pull/223897 ``` info starting [tsc] > node_modules/typescript/bin/tsc -b tsconfig.refs.json --pretty -- | 2025-06-13 13:17:30 UTC | proc [tsc] error TS6202: Project references may not form a circular graph. Cycle detected: /opt/buildkite-agent/builds/bk-agent-prod-gcp-1749820368903967112/elastic/kibana-pull-request/kibana/tsconfig.refs.json | 2025-06-13 13:17:30 UTC | proc [tsc] /opt/buildkite-agent/builds/bk-agent-prod-gcp-1749820368903967112/elastic/kibana-pull-request/kibana/x-pack/platform/plugins/shared/osquery/cypress/tsconfig.type_check.json | 2025-06-13 13:17:30 UTC | proc [tsc] /opt/buildkite-agent/builds/bk-agent-prod-gcp-1749820368903967112/elastic/kibana-pull-request/kibana/x-pack/test_serverless/tsconfig.type_check.json | 2025-06-13 13:17:30 UTC | proc [tsc] /opt/buildkite-agent/builds/bk-agent-prod-gcp-1749820368903967112/elastic/kibana-pull-request/kibana/x-pack/solutions/security/test/tsconfig.type_check.json | 2025-06-13 13:17:30 UTC | proc [tsc] /opt/buildkite-agent/builds/bk-agent-prod-gcp-1749820368903967112/elastic/kibana-pull-request/kibana/x-pack/test/security_solution_endpoint/tsconfig.type_check.json ``` **Important:** This PR focuses only on replacing test helpers imports from `@kbn/security-solution-plugin` and `@kbn/test-suites-xpack` in `osquery` plugin, no code cleanup and updates in other plugins / test packages. We expect code owners to update other imports / refactor package to avoid code duplication --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
1c359dc1d8
commit
539007f65a
38 changed files with 1728 additions and 11 deletions
19
.eslintrc.js
19
.eslintrc.js
|
@ -2268,8 +2268,6 @@ module.exports = {
|
|||
'src/cli_setup/**', // is importing "@kbn/interactive-setup-plugin" (platform/private)
|
||||
'src/dev/build/tasks/install_chromium.ts', // is importing "@kbn/screenshotting-plugin" (platform/private)
|
||||
|
||||
// FIXME tomsonpl @kbn/osquery-plugin depends on @kbn/security-solution-plugin (security/private) (cypress code => cypress code)
|
||||
'x-pack/platform/plugins/shared/osquery/**',
|
||||
// FIXME PhilippeOberti @kbn/timelines-plugin depends on security-solution-plugin (security/private) (timelines is going to disappear)
|
||||
'x-pack/platform/plugins/shared/timelines/**',
|
||||
|
||||
|
@ -2300,6 +2298,23 @@ module.exports = {
|
|||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['x-pack/**/cypress/**/*.ts'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: '@kbn/cypress-test-helper',
|
||||
message:
|
||||
"Import from a sub-path (e.g. '@kbn/cypress-test-helper/src/utils'). Cypress uses Webpack, which requires direct file imports to avoid parse errors.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -441,6 +441,7 @@ src/platform/packages/shared/kbn-crypto-browser @elastic/kibana-core
|
|||
src/platform/packages/shared/kbn-css-utils @elastic/appex-sharedux
|
||||
src/platform/packages/shared/kbn-custom-icons @elastic/obs-ux-logs-team
|
||||
src/platform/packages/shared/kbn-cypress-config @elastic/kibana-operations
|
||||
src/platform/packages/shared/kbn-cypress-test-helper @elastic/security-solution
|
||||
src/platform/packages/shared/kbn-data-grid-in-table-search @elastic/kibana-data-discovery
|
||||
src/platform/packages/shared/kbn-data-service @elastic/kibana-visualizations @elastic/kibana-data-discovery
|
||||
src/platform/packages/shared/kbn-data-view-utils @elastic/kibana-data-discovery
|
||||
|
|
|
@ -1505,6 +1505,7 @@
|
|||
"@kbn/core-ui-settings-server-mocks": "link:src/core/packages/ui-settings/server-mocks",
|
||||
"@kbn/core-usage-data-server-mocks": "link:src/core/packages/usage-data/server-mocks",
|
||||
"@kbn/cypress-config": "link:src/platform/packages/shared/kbn-cypress-config",
|
||||
"@kbn/cypress-test-helper": "link:src/platform/packages/shared/kbn-cypress-test-helper",
|
||||
"@kbn/dependency-ownership": "link:packages/kbn-dependency-ownership",
|
||||
"@kbn/dependency-usage": "link:packages/kbn-dependency-usage",
|
||||
"@kbn/dev-cli-errors": "link:src/platform/packages/shared/kbn-dev-cli-errors",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/cypress-test-helper
|
||||
|
||||
Empty package generated by @kbn/generate
|
36
src/platform/packages/shared/kbn-cypress-test-helper/cypress.d.ts
vendored
Normal file
36
src/platform/packages/shared/kbn-cypress-test-helper/cypress.d.ts
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
// / <reference types="cypress" />
|
||||
|
||||
declare namespace Cypress {
|
||||
interface Chainable<Subject = any> {
|
||||
/**
|
||||
* Continuously call provided callback function until it either return `true`
|
||||
* or fail if `timeout` is reached.
|
||||
* @param fn
|
||||
* @param options
|
||||
* @param message
|
||||
*/
|
||||
waitUntil(
|
||||
fn: (subject?: any) => boolean | Promise<boolean> | Chainable<boolean>,
|
||||
options?: Partial<{
|
||||
interval: number;
|
||||
timeout: number;
|
||||
}>,
|
||||
message?: string
|
||||
): Chainable<Subject>;
|
||||
/**
|
||||
* Waits for no network activity for a given URL.
|
||||
* @param url Partial URL to match requests
|
||||
* @param timeout Optional timeout in ms (default: 500)
|
||||
*/
|
||||
waitForNetworkIdle(url: string, timeout?: number): Chainable<Subject>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export * from './src';
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../../../../..',
|
||||
roots: ['<rootDir>/src/platform/packages/shared/kbn-cypress-test-helper'],
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"type": "test-helper",
|
||||
"id": "@kbn/cypress-test-helper",
|
||||
"owner": "@elastic/security-solution",
|
||||
"group": "platform",
|
||||
"visibility": "shared",
|
||||
"devOnly": true
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/cypress-test-helper",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const getApiAuth = () => ({
|
||||
user: Cypress.env('KIBANA_USERNAME') ?? Cypress.env('ELASTICSEARCH_USERNAME'),
|
||||
pass: Cypress.env('KIBANA_PASSWORD') ?? Cypress.env('ELASTICSEARCH_PASSWORD'),
|
||||
});
|
||||
|
||||
export const COMMON_API_HEADERS = Object.freeze({
|
||||
'kbn-xsrf': 'cypress',
|
||||
'x-elastic-internal-origin': 'security-solution',
|
||||
'elastic-api-version': '2023-10-31',
|
||||
});
|
||||
|
||||
export const request = <T = unknown>({
|
||||
headers,
|
||||
...options
|
||||
}: Partial<Cypress.RequestOptions>): Cypress.Chainable<Cypress.Response<T>> =>
|
||||
cy.request<T>({
|
||||
auth: getApiAuth(),
|
||||
headers: { ...COMMON_API_HEADERS, ...headers },
|
||||
...options,
|
||||
});
|
|
@ -0,0 +1,274 @@
|
|||
{
|
||||
"reader": {
|
||||
"name": "reader",
|
||||
"elasticsearch": {
|
||||
"cluster": [],
|
||||
"indices": [
|
||||
{
|
||||
"names": [
|
||||
".siem-signals-*",
|
||||
".alerts-security*",
|
||||
".lists*",
|
||||
".items*",
|
||||
"metrics-endpoint.metadata_current_*",
|
||||
".fleet-agents*",
|
||||
".fleet-actions*"
|
||||
],
|
||||
"privileges": ["read"]
|
||||
},
|
||||
{
|
||||
"names": ["*"],
|
||||
"privileges": ["read", "maintenance", "view_index_metadata"]
|
||||
}
|
||||
],
|
||||
"run_as": []
|
||||
},
|
||||
"kibana": [
|
||||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siemV2": ["read", "read_alerts"],
|
||||
"securitySolutionAssistant": ["none"],
|
||||
"securitySolutionAttackDiscovery": ["none"],
|
||||
"securitySolutionCasesV2": ["read"],
|
||||
"securitySolutionTimeline": ["read"],
|
||||
"securitySolutionNotes": ["read"],
|
||||
"actions": ["read"],
|
||||
"builtInAlerts": ["read"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
"base": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"hunter": {
|
||||
"name": "hunter",
|
||||
"elasticsearch": {
|
||||
"cluster": [],
|
||||
"indices": [
|
||||
{
|
||||
"names": [
|
||||
"apm-*-transaction*",
|
||||
"traces-apm*",
|
||||
"auditbeat-*",
|
||||
"endgame-*",
|
||||
"filebeat-*",
|
||||
"logs-*",
|
||||
"packetbeat-*",
|
||||
"winlogbeat-*"
|
||||
],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": [".alerts-security*", ".siem-signals-*"],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": [".lists*", ".items*"],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"],
|
||||
"privileges": ["read"]
|
||||
}
|
||||
],
|
||||
"run_as": []
|
||||
},
|
||||
"kibana": [
|
||||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["read"],
|
||||
"securitySolutionNotes": ["read"],
|
||||
"actions": ["read"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
"base": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"hunter_no_actions": {
|
||||
"name": "hunter_no_actions",
|
||||
"elasticsearch": {
|
||||
"cluster": [],
|
||||
"indices": [
|
||||
{
|
||||
"names": [
|
||||
"apm-*-transaction*",
|
||||
"traces-apm*",
|
||||
"auditbeat-*",
|
||||
"endgame-*",
|
||||
"filebeat-*",
|
||||
"logs-*",
|
||||
"packetbeat-*",
|
||||
"winlogbeat-*"
|
||||
],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": [".alerts-security*", ".siem-signals-*"],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": [".lists*", ".items*"],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"],
|
||||
"privileges": ["read"]
|
||||
}
|
||||
],
|
||||
"run_as": []
|
||||
},
|
||||
"kibana": [
|
||||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["all"],
|
||||
"securitySolutionNotes": ["all"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
"base": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"no_risk_engine_privileges": {
|
||||
"name": "no_risk_engine_privileges",
|
||||
"elasticsearch": {
|
||||
"cluster": [],
|
||||
"indices": [],
|
||||
"run_as": []
|
||||
},
|
||||
"kibana": [
|
||||
{
|
||||
"feature": {
|
||||
"siemV2": ["read"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
"base": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"timeline_none": {
|
||||
"name": "timeline_none",
|
||||
"elasticsearch": {
|
||||
"cluster": [],
|
||||
"indices": [
|
||||
{
|
||||
"names": [
|
||||
"apm-*-transaction*",
|
||||
"traces-apm*",
|
||||
"auditbeat-*",
|
||||
"endgame-*",
|
||||
"filebeat-*",
|
||||
"logs-*",
|
||||
"packetbeat-*",
|
||||
"winlogbeat-*",
|
||||
".lists*",
|
||||
".items*",
|
||||
".asset-criticality.asset-criticality-*"
|
||||
],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": [
|
||||
".alerts-security*",
|
||||
".preview.alerts-security*",
|
||||
".internal.preview.alerts-security*",
|
||||
".adhoc.alerts-security*",
|
||||
".internal.adhoc.alerts-security*",
|
||||
".siem-signals-*"
|
||||
],
|
||||
"privileges": ["read", "write", "manage"]
|
||||
},
|
||||
{
|
||||
"names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"],
|
||||
"privileges": ["read"]
|
||||
}
|
||||
],
|
||||
"run_as": []
|
||||
},
|
||||
"kibana": [
|
||||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionNotes": ["all"],
|
||||
"actions": ["all"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
"base": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes_none": {
|
||||
"name": "notes_none",
|
||||
"elasticsearch": {
|
||||
"cluster": [],
|
||||
"indices": [
|
||||
{
|
||||
"names": [
|
||||
"apm-*-transaction*",
|
||||
"traces-apm*",
|
||||
"auditbeat-*",
|
||||
"endgame-*",
|
||||
"filebeat-*",
|
||||
"logs-*",
|
||||
"packetbeat-*",
|
||||
"winlogbeat-*",
|
||||
".lists*",
|
||||
".items*",
|
||||
".asset-criticality.asset-criticality-*"
|
||||
],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": [
|
||||
".alerts-security*",
|
||||
".preview.alerts-security*",
|
||||
".internal.preview.alerts-security*",
|
||||
".adhoc.alerts-security*",
|
||||
".internal.adhoc.alerts-security*",
|
||||
".siem-signals-*"
|
||||
],
|
||||
"privileges": ["read", "write", "manage"]
|
||||
},
|
||||
{
|
||||
"names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"],
|
||||
"privileges": ["read"]
|
||||
}
|
||||
],
|
||||
"run_as": []
|
||||
},
|
||||
"kibana": [
|
||||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["all"],
|
||||
"actions": ["all"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
"base": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import serverlessRoleDefinitions from '@kbn/es/src/serverless_resources/security_roles.json';
|
||||
import essRoleDefinitions from './ess_roles.json';
|
||||
|
||||
type ServerlessSecurityRoleName = keyof typeof serverlessRoleDefinitions;
|
||||
type EssSecurityRoleName = keyof typeof essRoleDefinitions;
|
||||
|
||||
export const KNOWN_SERVERLESS_ROLE_DEFINITIONS = serverlessRoleDefinitions;
|
||||
export const KNOWN_ESS_ROLE_DEFINITIONS = essRoleDefinitions;
|
||||
|
||||
export type SecurityRoleName = ServerlessSecurityRoleName | EssSecurityRoleName;
|
||||
|
||||
export enum ROLES {
|
||||
// Serverless roles
|
||||
t1_analyst = 't1_analyst',
|
||||
t2_analyst = 't2_analyst',
|
||||
t3_analyst = 't3_analyst',
|
||||
rule_author = 'rule_author',
|
||||
soc_manager = 'soc_manager',
|
||||
detections_admin = 'detections_admin',
|
||||
platform_engineer = 'platform_engineer',
|
||||
// ESS roles
|
||||
reader = 'reader',
|
||||
hunter = 'hunter',
|
||||
hunter_no_actions = 'hunter_no_actions',
|
||||
no_risk_engine_privileges = 'no_risk_engine_privileges',
|
||||
timeline_none = 'timeline_none',
|
||||
notes_none = 'notes_none',
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a map of the commonly used date ranges found under the Quick Menu popover of the
|
||||
* super date picker component.
|
||||
*/
|
||||
export const DATE_RANGE_OPTION_TO_TEST_SUBJ_MAP = Object.freeze({
|
||||
Today: 'superDatePickerCommonlyUsed_Today',
|
||||
'This week': 'superDatePickerCommonlyUsed_This_week',
|
||||
'Last 15 minutes': 'superDatePickerCommonlyUsed_Last_15 minutes',
|
||||
'Last 30 minutes': 'superDatePickerCommonlyUsed_Last_30 minutes',
|
||||
'Last 1 hour': 'superDatePickerCommonlyUsed_Last_1 hour',
|
||||
'Last 24 hours': 'superDatePickerCommonlyUsed_Last_24 hours',
|
||||
'Last 7 days': 'superDatePickerCommonlyUsed_Last_7 days',
|
||||
'Last 30 days': 'superDatePickerCommonlyUsed_Last_30 days',
|
||||
'Last 90 days': 'superDatePickerCommonlyUsed_Last_90 days',
|
||||
'Last 1 year': 'superDatePickerCommonlyUsed_Last_1 year',
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { login } from './login';
|
||||
export { samlAuthentication, resolveCloudUsersFilePath, ES_LOADED_USERS } from './saml_auth';
|
||||
export type {
|
||||
EndpointSecurityRoleNames,
|
||||
EndpointSecurityRoleDefinitions,
|
||||
KibanaKnownUserAccounts,
|
||||
SecurityTestUser,
|
||||
} from './roles_and_users';
|
||||
export {
|
||||
SECURITY_SERVERLESS_ROLE_NAMES,
|
||||
ENDPOINT_SECURITY_ROLE_NAMES,
|
||||
KIBANA_KNOWN_DEFAULT_ACCOUNTS,
|
||||
} from './roles_and_users';
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { LoginState } from '@kbn/security-plugin/common/login_state';
|
||||
import type { Role } from '@kbn/security-plugin/common';
|
||||
import { ENDPOINT_SECURITY_ROLE_NAMES, KIBANA_KNOWN_DEFAULT_ACCOUNTS } from './roles_and_users';
|
||||
import type { SecurityTestUser } from './roles_and_users';
|
||||
import { COMMON_API_HEADERS, request } from '../api';
|
||||
|
||||
export const ROLE = Object.freeze<Record<SecurityTestUser, SecurityTestUser>>({
|
||||
...ENDPOINT_SECURITY_ROLE_NAMES,
|
||||
...KIBANA_KNOWN_DEFAULT_ACCOUNTS,
|
||||
});
|
||||
|
||||
interface CyLoginTask {
|
||||
(user?: SecurityTestUser): ReturnType<typeof sendApiLoginRequest>;
|
||||
|
||||
/**
|
||||
* Login using any username/password
|
||||
* @param username
|
||||
* @param password
|
||||
*/
|
||||
with(username: string, password: string): ReturnType<typeof sendApiLoginRequest>;
|
||||
|
||||
/**
|
||||
* Creates the provided role in kibana/ES along with a respective user (same name as role)
|
||||
* and then login with this new user
|
||||
* @param role
|
||||
*/
|
||||
withCustomRole(role: Role): ReturnType<typeof sendApiLoginRequest>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to Kibana using API (not login page).
|
||||
* By default, user will be logged in using `KIBANA_USERNAME` and `KIBANA_PASSWORD` retrieved from
|
||||
* the cypress `env`
|
||||
*
|
||||
* @param user
|
||||
*/
|
||||
export const login: CyLoginTask = (
|
||||
user: SecurityTestUser = ROLE.endpoint_operations_analyst
|
||||
): ReturnType<typeof sendApiLoginRequest> => {
|
||||
let username = Cypress.env('KIBANA_USERNAME');
|
||||
let password = Cypress.env('KIBANA_PASSWORD');
|
||||
const isServerless = Cypress.env('IS_SERVERLESS');
|
||||
const isCloudServerless = Cypress.env('CLOUD_SERVERLESS');
|
||||
|
||||
if (isServerless && isCloudServerless) {
|
||||
// MKI QA Cloud Serverless
|
||||
return cy
|
||||
.task('getSessionCookie', user)
|
||||
.then((result) => {
|
||||
const {
|
||||
username: u,
|
||||
password: p,
|
||||
cookie,
|
||||
} = result as {
|
||||
username: string;
|
||||
password: string;
|
||||
cookie: string;
|
||||
};
|
||||
|
||||
username = u;
|
||||
password = p;
|
||||
// Set cookie asynchronously
|
||||
return cy.setCookie('sid', cookie, {
|
||||
// "hostOnly: true" sets the cookie without a domain.
|
||||
// This makes cookie available only for the current host (not subdomains).
|
||||
// It's needed to match the Serverless backend behavior where cookies are set without a domain.
|
||||
// More info: https://github.com/elastic/kibana/issues/221741
|
||||
hostOnly: true,
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// Visit URL after setting cookie
|
||||
return cy.visit('/');
|
||||
})
|
||||
.then(() => {
|
||||
cy.getCookies().then((cookies) => {
|
||||
// Ensure that there's only a single session cookie named 'sid'.
|
||||
const sessionCookies = cookies.filter((cookie) => cookie.name === 'sid');
|
||||
expect(sessionCookies).to.have.length(1);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// Return username and password
|
||||
return { username, password };
|
||||
});
|
||||
} else if (user) {
|
||||
return cy.task('loadUserAndRole', { name: user }).then((loadedUser) => {
|
||||
const { username: u, password: p } = loadedUser as {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
username = u;
|
||||
password = p;
|
||||
|
||||
return sendApiLoginRequest(username, password);
|
||||
});
|
||||
} else {
|
||||
return sendApiLoginRequest(username, password);
|
||||
}
|
||||
};
|
||||
|
||||
login.with = (username: string, password: string): ReturnType<typeof sendApiLoginRequest> => {
|
||||
return sendApiLoginRequest(username, password);
|
||||
};
|
||||
|
||||
login.withCustomRole = (role: Role): ReturnType<typeof sendApiLoginRequest> => {
|
||||
return cy.task('createUserAndRole', { role }).then((result) => {
|
||||
const { username, password } = result as {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
return sendApiLoginRequest(username, password);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send login via API
|
||||
* @param username
|
||||
* @param password
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const sendApiLoginRequest = (
|
||||
username: string,
|
||||
password: string
|
||||
): Cypress.Chainable<{ username: string; password: string }> => {
|
||||
const baseUrl = Cypress.config().baseUrl;
|
||||
const loginUrl = `${baseUrl}/internal/security/login`;
|
||||
const headers = { ...COMMON_API_HEADERS };
|
||||
|
||||
cy.log(`Authenticating [${username}] via ${loginUrl}`);
|
||||
|
||||
return request<LoginState>({ headers, url: `${baseUrl}/internal/security/login_state` })
|
||||
.then((loginState) => {
|
||||
const basicProvider = loginState.body.selector.providers.find(
|
||||
(provider) => provider.type === 'basic'
|
||||
);
|
||||
return request({
|
||||
url: loginUrl,
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: {
|
||||
providerType: basicProvider?.type,
|
||||
providerName: basicProvider?.name,
|
||||
currentURL: '/',
|
||||
params: { username, password },
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(() => ({ username, password }));
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { Role } from '@kbn/security-plugin/common';
|
||||
|
||||
export type EndpointSecurityRoleNames = keyof typeof ENDPOINT_SECURITY_ROLE_NAMES;
|
||||
|
||||
export type EndpointSecurityRoleDefinitions = Record<EndpointSecurityRoleNames, Role>;
|
||||
|
||||
/**
|
||||
* Security Solution set of roles that are loaded and used in serverless deployments.
|
||||
* The source of these role definitions is under `project-controller` at:
|
||||
*
|
||||
* @see https://github.com/elastic/project-controller/blob/main/internal/project/security/config/roles.yml
|
||||
*
|
||||
* The role definition spreadsheet can be found here:
|
||||
*
|
||||
* @see https://docs.google.com/spreadsheets/d/16aGow187AunLCBFZLlbVyS81iQNuMpNxd96LOerWj4c/edit#gid=1936689222
|
||||
*/
|
||||
export const SECURITY_SERVERLESS_ROLE_NAMES = Object.freeze({
|
||||
t1_analyst: 't1_analyst',
|
||||
t2_analyst: 't2_analyst',
|
||||
t3_analyst: 't3_analyst',
|
||||
threat_intelligence_analyst: 'threat_intelligence_analyst',
|
||||
rule_author: 'rule_author',
|
||||
soc_manager: 'soc_manager',
|
||||
detections_admin: 'detections_admin',
|
||||
platform_engineer: 'platform_engineer',
|
||||
endpoint_operations_analyst: 'endpoint_operations_analyst',
|
||||
endpoint_policy_manager: 'endpoint_policy_manager',
|
||||
});
|
||||
|
||||
export const ENDPOINT_SECURITY_ROLE_NAMES = Object.freeze({
|
||||
// --------------------------------------
|
||||
// Set of roles used in serverless
|
||||
...SECURITY_SERVERLESS_ROLE_NAMES,
|
||||
|
||||
// --------------------------------------
|
||||
// Other roles used for testing
|
||||
hunter: 'hunter',
|
||||
endpoint_response_actions_access: 'endpoint_response_actions_access',
|
||||
endpoint_response_actions_no_access: 'endpoint_response_actions_no_access',
|
||||
endpoint_security_policy_management_read: 'endpoint_security_policy_management_read',
|
||||
artifact_read_privileges: 'artifact_read_privileges',
|
||||
});
|
||||
|
||||
export type KibanaKnownUserAccounts = keyof typeof KIBANA_KNOWN_DEFAULT_ACCOUNTS;
|
||||
|
||||
export type SecurityTestUser = EndpointSecurityRoleNames | KibanaKnownUserAccounts;
|
||||
|
||||
/**
|
||||
* List of kibana system accounts
|
||||
*/
|
||||
export const KIBANA_KNOWN_DEFAULT_ACCOUNTS = {
|
||||
elastic: 'elastic',
|
||||
elastic_serverless: 'elastic_serverless',
|
||||
system_indices_superuser: 'system_indices_superuser',
|
||||
admin: 'admin',
|
||||
} as const;
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { resolve, join } from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import type { HostOptions } from '@kbn/test';
|
||||
import { SamlSessionManager } from '@kbn/test';
|
||||
import type { SecurityRoleName } from './common';
|
||||
|
||||
const ES_RESOURCES_DIR = resolve(
|
||||
REPO_ROOT,
|
||||
'x-pack/solutions/security/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/es_serverless_resources'
|
||||
);
|
||||
|
||||
export const ES_RESOURCES = Object.freeze({
|
||||
roles: join(ES_RESOURCES_DIR, 'roles.yml'),
|
||||
users: join(ES_RESOURCES_DIR, 'users'),
|
||||
users_roles: join(ES_RESOURCES_DIR, 'users_roles'),
|
||||
});
|
||||
|
||||
export const resolveCloudUsersFilePath = (filename: string) => resolve(REPO_ROOT, '.ftr', filename);
|
||||
|
||||
export const ES_LOADED_USERS = readFileSync(ES_RESOURCES.users)
|
||||
.toString()
|
||||
.split(/\n/)
|
||||
.filter((v) => !!v) // Ensure no empty strings
|
||||
.map((userAndPasswordString) => {
|
||||
return userAndPasswordString.split(':').at(0);
|
||||
});
|
||||
|
||||
export const samlAuthentication = async (
|
||||
on: Cypress.PluginEvents,
|
||||
config: Cypress.PluginConfigOptions
|
||||
): Promise<void> => {
|
||||
const log = new ToolingLog({ level: 'verbose', writeTo: process.stdout });
|
||||
|
||||
const kbnHost = config.env.KIBANA_URL || config.env.BASE_URL;
|
||||
|
||||
const kbnUrl = new URL(kbnHost);
|
||||
|
||||
const hostOptions: HostOptions = {
|
||||
protocol: kbnUrl.protocol as 'http' | 'https',
|
||||
hostname: kbnUrl.hostname,
|
||||
port: parseInt(kbnUrl.port, 10),
|
||||
username: config.env.ELASTICSEARCH_USERNAME,
|
||||
password: config.env.ELASTICSEARCH_PASSWORD,
|
||||
};
|
||||
|
||||
on('task', {
|
||||
getSessionCookie: async (
|
||||
role: string | SecurityRoleName
|
||||
): Promise<{ cookie: string; username: string; password: string }> => {
|
||||
// If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles.
|
||||
const rolesFilename = config.env.PROXY_ORG
|
||||
? `${config.env.PROXY_ORG}.json`
|
||||
: 'role_users.json';
|
||||
const sessionManager = new SamlSessionManager({
|
||||
hostOptions,
|
||||
log,
|
||||
isCloud: config.env.CLOUD_SERVERLESS,
|
||||
cloudUsersFilePath: resolveCloudUsersFilePath(rolesFilename),
|
||||
});
|
||||
return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role).then((cookie) => {
|
||||
return {
|
||||
cookie,
|
||||
username: hostOptions.username,
|
||||
password: hostOptions.password,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Endpoint base error class that supports an optional second argument for providing additional data
|
||||
* for the error.
|
||||
*/
|
||||
export class EndpointError<MetaType = unknown> extends Error {
|
||||
constructor(message: string, public readonly meta?: MetaType) {
|
||||
super(message);
|
||||
// For debugging - capture name of subclasses
|
||||
this.name = this.constructor.name;
|
||||
|
||||
if (meta instanceof Error) {
|
||||
this.stack += `\n----- original error -----\n${meta.stack}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a given Error is an instance of EndpointError
|
||||
* @param err
|
||||
*/
|
||||
export const isEndpointError = (err: Error): err is EndpointError => {
|
||||
return err instanceof EndpointError;
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
import { EndpointError } from './errors';
|
||||
|
||||
export class FormattedAxiosError extends EndpointError {
|
||||
public readonly request: {
|
||||
method: string;
|
||||
url: string;
|
||||
data: unknown;
|
||||
};
|
||||
public readonly response: {
|
||||
status: number;
|
||||
statusText: string;
|
||||
data: any;
|
||||
};
|
||||
|
||||
constructor(axiosError: AxiosError) {
|
||||
const method = axiosError.config?.method ?? '';
|
||||
const url = axiosError.config?.url ?? '';
|
||||
|
||||
super(
|
||||
`${axiosError.message}${
|
||||
axiosError?.response?.data ? `: ${JSON.stringify(axiosError?.response?.data)}` : ''
|
||||
}${url ? `\n(Request: ${method} ${url})` : ''}`,
|
||||
axiosError
|
||||
);
|
||||
|
||||
this.request = {
|
||||
method,
|
||||
url,
|
||||
data: axiosError.config?.data ?? '',
|
||||
};
|
||||
|
||||
this.response = {
|
||||
status: axiosError?.response?.status ?? 0,
|
||||
statusText: axiosError?.response?.statusText ?? '',
|
||||
data: axiosError?.response?.data,
|
||||
};
|
||||
|
||||
this.name = this.constructor.name;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
message: this.message,
|
||||
request: this.request,
|
||||
response: this.response,
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
return JSON.stringify(this.toJSON(), null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used with `promise.catch()`, it will format the Axios error to a new error and will re-throw
|
||||
* @param error
|
||||
*/
|
||||
export const catchAxiosErrorFormatAndThrow = (error: Error): never => {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new FormattedAxiosError(error);
|
||||
}
|
||||
|
||||
if (!(error instanceof EndpointError)) {
|
||||
throw new EndpointError(error.message, error);
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export * from './errors';
|
||||
export * from './format_axios_error';
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export * from './auth';
|
||||
export * from './error';
|
||||
export { enableFleetSpaceAwareness } from './services/fleet_services';
|
||||
export { isLocalhost } from './services/is_localhost';
|
||||
export { fetchKibanaStatus, isServerlessKibanaFlavor } from './services/kibana_status';
|
||||
export { getLocalhostRealIp } from './services/network_services';
|
||||
export { createSecuritySuperuser } from './services/security_user_services';
|
||||
export type { CreatedSecuritySuperuser } from './services/security_user_services';
|
||||
export { createRuntimeServices } from './services/stack_services';
|
||||
export { waitForAlertsToPopulate } from './services/alerting_services';
|
||||
export * from './api';
|
||||
export { createToolingLogger } from './logger';
|
||||
export * from './utils';
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { ToolingLogTextWriterConfig } from '@kbn/tooling-log';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import type { Flags } from '@kbn/dev-cli-runner';
|
||||
|
||||
interface CreateLoggerInterface {
|
||||
(level?: Partial<ToolingLogTextWriterConfig>['level']): ToolingLog;
|
||||
|
||||
/**
|
||||
* The default log level if one is not provided to the `createToolingLogger()` utility.
|
||||
* Can be used to globally set the log level to calls made to this utility with no `level` set
|
||||
* on input.
|
||||
*/
|
||||
defaultLogLevel: ToolingLogTextWriterConfig['level'];
|
||||
|
||||
/**
|
||||
* Set the default logging level based on the flag arguments provide to a CLI script that runs
|
||||
* via `@kbn/dev-cli-runner`
|
||||
* @param flags
|
||||
*/
|
||||
setDefaultLogLevelFromCliFlags: (flags: Flags) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of `ToolingLog` that outputs to `stdout`.
|
||||
* The default log `level` for all instances can be set by setting the function's `defaultLogLevel`
|
||||
* property. Default logging level can also be set from CLI scripts that use the `@kbn/dev-cli-runner`
|
||||
* by calling the `setDefaultLogLevelFromCliFlags(flags)` and passing in the `flags` property.
|
||||
*
|
||||
* @param level
|
||||
*
|
||||
* @example
|
||||
* // Set default log level - example: from cypress for CI jobs
|
||||
* createLogger.defaultLogLevel = 'verbose'
|
||||
*/
|
||||
export const createToolingLogger: CreateLoggerInterface = (level): ToolingLog => {
|
||||
return new ToolingLog({
|
||||
level: level || createToolingLogger.defaultLogLevel,
|
||||
writeTo: process.stdout,
|
||||
});
|
||||
};
|
||||
createToolingLogger.defaultLogLevel = 'info';
|
||||
createToolingLogger.setDefaultLogLevelFromCliFlags = (flags) => {
|
||||
createToolingLogger.defaultLogLevel = flags.verbose
|
||||
? 'verbose'
|
||||
: flags.debug
|
||||
? 'debug'
|
||||
: flags.silent
|
||||
? 'silent'
|
||||
: flags.quiet
|
||||
? 'error'
|
||||
: 'info';
|
||||
};
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import 'cypress-network-idle';
|
||||
|
||||
const GLOBAL_KQL_WRAPPER = '[data-test-subj="filters-global-container"]';
|
||||
const REFRESH_BUTTON = `${GLOBAL_KQL_WRAPPER} [data-test-subj="querySubmitButton"]`;
|
||||
const DATAGRID_CHANGES_IN_PROGRESS = '[data-test-subj="body-data-grid"] .euiProgress';
|
||||
const EVENT_CONTAINER_TABLE_LOADING = '[data-test-subj="internalAlertsPageLoading"]';
|
||||
const LOADING_INDICATOR = '[data-test-subj="globalLoadingIndicator"]';
|
||||
const EMPTY_ALERT_TABLE = '[data-test-subj="alertsTableEmptyState"]';
|
||||
const ALERTS_TABLE_COUNT = `[data-test-subj="toolbar-alerts-count"]`;
|
||||
const DETECTION_PAGE_FILTER_GROUP_WRAPPER = '.filter-group__wrapper';
|
||||
const DETECTION_PAGE_FILTERS_LOADING = '.securityPageWrapper .controlFrame--controlLoading';
|
||||
const DETECTION_PAGE_FILTER_GROUP_LOADING = '[data-test-subj="filter-group__loading"]';
|
||||
const OPTION_LISTS_LOADING = '.optionsList--filterBtnWrapper .euiLoadingSpinner';
|
||||
const ALERTS_URL = '/app/security/alerts';
|
||||
|
||||
const refreshPage = () => {
|
||||
cy.get(REFRESH_BUTTON).click({ force: true });
|
||||
cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating');
|
||||
};
|
||||
|
||||
const waitForPageFilters = () => {
|
||||
cy.log('Waiting for Page Filters');
|
||||
cy.url().then((urlString) => {
|
||||
const url = new URL(urlString);
|
||||
if (url.pathname.endsWith(ALERTS_URL)) {
|
||||
// since these are only valid on the alert page
|
||||
cy.get(DETECTION_PAGE_FILTER_GROUP_WRAPPER).should('exist');
|
||||
cy.get(DETECTION_PAGE_FILTER_GROUP_LOADING).should('not.exist');
|
||||
cy.get(DETECTION_PAGE_FILTERS_LOADING).should('not.exist');
|
||||
cy.get(OPTION_LISTS_LOADING).should('have.lengthOf', 0);
|
||||
} else {
|
||||
cy.log('Skipping Page Filters Wait');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const waitForAlerts = () => {
|
||||
waitForPageFilters();
|
||||
cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating');
|
||||
cy.get(DATAGRID_CHANGES_IN_PROGRESS).should('not.be.true');
|
||||
cy.get(EVENT_CONTAINER_TABLE_LOADING).should('not.exist');
|
||||
cy.get(LOADING_INDICATOR).should('not.exist');
|
||||
cy.waitForNetworkIdle('/internal/search/privateRuleRegistryAlertsSearchStrategy', 500);
|
||||
};
|
||||
|
||||
export const waitForAlertsToPopulate = (alertCountThreshold = 1) => {
|
||||
cy.waitUntil(
|
||||
() => {
|
||||
cy.log('Waiting for alerts to appear');
|
||||
refreshPage();
|
||||
cy.get([EMPTY_ALERT_TABLE, ALERTS_TABLE_COUNT].join(', '));
|
||||
return cy.root().then(($el) => {
|
||||
const emptyTableState = $el.find(EMPTY_ALERT_TABLE);
|
||||
if (emptyTableState.length > 0) {
|
||||
cy.log('Table is empty', emptyTableState.length);
|
||||
return false;
|
||||
}
|
||||
const countEl = $el.find(ALERTS_TABLE_COUNT);
|
||||
const alertCount = parseInt(countEl.text(), 10) || 0;
|
||||
return alertCount >= alertCountThreshold;
|
||||
});
|
||||
},
|
||||
{ interval: 500, timeout: 30000 }
|
||||
);
|
||||
waitForAlerts();
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { memoize } from 'lodash';
|
||||
import type { KbnClient } from '@kbn/test';
|
||||
import { catchAxiosErrorFormatAndThrow } from '../error';
|
||||
|
||||
/**
|
||||
* Calls the Fleet internal API to enable space awareness
|
||||
* @param kbnClient
|
||||
*/
|
||||
export const enableFleetSpaceAwareness = memoize(async (kbnClient: KbnClient): Promise<void> => {
|
||||
await kbnClient
|
||||
.request({
|
||||
path: '/internal/fleet/enable_space_awareness',
|
||||
headers: { 'Elastic-Api-Version': '1' },
|
||||
method: 'POST',
|
||||
})
|
||||
.catch(catchAxiosErrorFormatAndThrow);
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
const POSSIBLE_LOCALHOST_VALUES: readonly string[] = [
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
'0.0.0.0',
|
||||
'::1',
|
||||
'0000:0000:0000:0000:0000:0000:0000:0000',
|
||||
];
|
||||
|
||||
export const isLocalhost = (hostname: string): boolean => {
|
||||
return POSSIBLE_LOCALHOST_VALUES.includes(hostname.toLowerCase());
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { KbnClient } from '@kbn/test';
|
||||
import type { Client } from '@elastic/elasticsearch';
|
||||
import type { StatusResponse } from '@kbn/core-status-common';
|
||||
import { catchAxiosErrorFormatAndThrow } from '../error';
|
||||
|
||||
export const fetchKibanaStatus = async (kbnClient: KbnClient): Promise<StatusResponse> => {
|
||||
// We DO NOT use `kbnClient.status.get()` here because the `kbnClient` passed on input could be our enhanced
|
||||
// client (created by `x-pack/solutions/security/plugins/security_solution/scripts/endpoint/common/stack_services.ts:267`)
|
||||
// which could be using an API key (which the core KbnClient does not support)
|
||||
return kbnClient
|
||||
.request<StatusResponse>({
|
||||
method: 'GET',
|
||||
path: '/api/status',
|
||||
})
|
||||
.then(({ data }) => data)
|
||||
.catch(catchAxiosErrorFormatAndThrow);
|
||||
};
|
||||
/**
|
||||
* Checks to see if Kibana/ES is running in serverless mode
|
||||
* @param client
|
||||
*/
|
||||
export const isServerlessKibanaFlavor = async (client: KbnClient | Client): Promise<boolean> => {
|
||||
if (client instanceof KbnClient) {
|
||||
const kbnStatus = await fetchKibanaStatus(client);
|
||||
|
||||
// If we don't have status for plugins, then error
|
||||
// the Status API will always return something (its an open API), but if auth was successful,
|
||||
// it will also return more data.
|
||||
if (!kbnStatus?.status?.plugins) {
|
||||
throw new Error(
|
||||
`Unable to retrieve Kibana plugins status (likely an auth issue with the username being used for kibana)`
|
||||
);
|
||||
}
|
||||
|
||||
return kbnStatus.status.plugins?.serverless?.level === 'available';
|
||||
} else {
|
||||
return (await client.info()).version.build_flavor === 'serverless';
|
||||
}
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const getLocalhostRealIp = (): string => {
|
||||
// Use require dynamically so Cypress don't process it
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const os = require('os') as typeof import('os');
|
||||
const interfaces = os.networkInterfaces();
|
||||
|
||||
for (const netInterfaceList of Object.values(interfaces).reverse()) {
|
||||
if (netInterfaceList) {
|
||||
const netInterface = netInterfaceList.find(
|
||||
(networkInterface) =>
|
||||
networkInterface.family === 'IPv4' &&
|
||||
!networkInterface.internal &&
|
||||
networkInterface.address
|
||||
);
|
||||
if (netInterface) {
|
||||
return netInterface.address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '0.0.0.0';
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { Client } from '@elastic/elasticsearch';
|
||||
|
||||
export interface CreatedSecuritySuperuser {
|
||||
username: string;
|
||||
password: string;
|
||||
created: boolean;
|
||||
}
|
||||
|
||||
export const createSecuritySuperuser = async (
|
||||
esClient: Client,
|
||||
username?: string,
|
||||
password: string = 'changeme'
|
||||
): Promise<CreatedSecuritySuperuser> => {
|
||||
// Dynamically load userInfo from 'os' if no username is provided
|
||||
if (!username) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const os = require('os') as typeof import('os');
|
||||
username = os.userInfo().username;
|
||||
}
|
||||
|
||||
if (!username || !password) {
|
||||
throw new Error(`username and password require values.`);
|
||||
}
|
||||
|
||||
// Create a role which has full access to restricted indexes
|
||||
await esClient.transport.request({
|
||||
method: 'POST',
|
||||
path: '_security/role/superuser_restricted_indices',
|
||||
body: {
|
||||
cluster: ['all'],
|
||||
indices: [
|
||||
{
|
||||
names: ['*'],
|
||||
privileges: ['all'],
|
||||
allow_restricted_indices: true,
|
||||
},
|
||||
{
|
||||
names: ['*'],
|
||||
privileges: ['monitor', 'read', 'view_index_metadata', 'read_cross_cluster'],
|
||||
allow_restricted_indices: true,
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: '*',
|
||||
privileges: ['*'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
run_as: ['*'],
|
||||
},
|
||||
});
|
||||
|
||||
const addedUser = await esClient.transport.request<Promise<{ created: boolean }>>({
|
||||
method: 'POST',
|
||||
path: `_security/user/${username}`,
|
||||
body: {
|
||||
password,
|
||||
roles: ['superuser', 'kibana_system', 'superuser_restricted_indices'],
|
||||
full_name: username,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
created: addedUser.created,
|
||||
username,
|
||||
password,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,383 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { Client, HttpConnection } from '@elastic/elasticsearch';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import type { KbnClientOptions } from '@kbn/test';
|
||||
import { KbnClient } from '@kbn/test';
|
||||
import pRetry from 'p-retry';
|
||||
import type { ReqOptions } from '@kbn/test/src/kbn_client/kbn_client_requester';
|
||||
import { type AxiosResponse } from 'axios';
|
||||
import type { ClientOptions } from '@elastic/elasticsearch/lib/client';
|
||||
import fs from 'fs';
|
||||
import { CA_CERT_PATH } from '@kbn/dev-utils';
|
||||
import { omit } from 'lodash';
|
||||
import { addSpaceIdToPath, DEFAULT_SPACE_ID, getSpaceIdFromPath } from '@kbn/spaces-plugin/common';
|
||||
import { enableFleetSpaceAwareness } from './fleet_services';
|
||||
import { fetchKibanaStatus, isServerlessKibanaFlavor } from './kibana_status';
|
||||
import { createToolingLogger } from '../logger';
|
||||
import { isLocalhost } from './is_localhost';
|
||||
import { getLocalhostRealIp } from './network_services';
|
||||
import { createSecuritySuperuser } from './security_user_services';
|
||||
|
||||
const CA_CERTIFICATE: Buffer = fs.readFileSync(CA_CERT_PATH);
|
||||
|
||||
export interface RuntimeServices {
|
||||
kbnClient: KbnClient;
|
||||
esClient: Client;
|
||||
log: ToolingLog;
|
||||
user: Readonly<{
|
||||
username: string;
|
||||
password: string;
|
||||
}>;
|
||||
apiKey: string;
|
||||
localhostRealIp: string;
|
||||
kibana: {
|
||||
url: string;
|
||||
hostname: string;
|
||||
port: string;
|
||||
isLocalhost: boolean;
|
||||
};
|
||||
elastic: {
|
||||
url: string;
|
||||
hostname: string;
|
||||
port: string;
|
||||
isLocalhost: boolean;
|
||||
};
|
||||
fleetServer: {
|
||||
url: string;
|
||||
hostname: string;
|
||||
port: string;
|
||||
isLocalhost: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateRuntimeServicesOptions {
|
||||
kibanaUrl: string;
|
||||
elasticsearchUrl: string;
|
||||
fleetServerUrl?: string;
|
||||
username: string;
|
||||
password: string;
|
||||
/** The space id in kibana */
|
||||
spaceId?: string;
|
||||
/** If defined, both `username` and `password` will be ignored */
|
||||
apiKey?: string;
|
||||
/** If undefined, ES username defaults to `username` */
|
||||
esUsername?: string;
|
||||
/** If undefined, ES password defaults to `password` */
|
||||
esPassword?: string;
|
||||
log?: ToolingLog;
|
||||
asSuperuser?: boolean;
|
||||
/** If true, then a certificate will not be used when creating the Kbn/Es clients when url is `https` */
|
||||
useCertForSsl?: boolean;
|
||||
}
|
||||
|
||||
class KbnClientExtended extends KbnClient {
|
||||
private readonly apiKey: string | undefined;
|
||||
|
||||
constructor(protected readonly options: KbnClientOptions & { apiKey?: string }) {
|
||||
const { apiKey, url, ...opt } = options;
|
||||
|
||||
super({
|
||||
...opt,
|
||||
url: apiKey ? buildUrlWithCredentials(url, '', '') : url,
|
||||
});
|
||||
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
async request<T>(options: ReqOptions): Promise<AxiosResponse<T>> {
|
||||
const headers: ReqOptions['headers'] = {
|
||||
...(options.headers ?? {}),
|
||||
};
|
||||
|
||||
if (this.apiKey) {
|
||||
headers.Authorization = `ApiKey ${this.apiKey}`;
|
||||
this.options.log.verbose(`Adding API key header to request header 'Authorization'`);
|
||||
}
|
||||
|
||||
return super.request({
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const createRuntimeServices = async ({
|
||||
kibanaUrl: _kibanaUrl,
|
||||
elasticsearchUrl,
|
||||
fleetServerUrl = 'https://localhost:8220',
|
||||
username: _username,
|
||||
password: _password,
|
||||
spaceId,
|
||||
apiKey,
|
||||
esUsername: _esUsername,
|
||||
esPassword: _esPassword,
|
||||
log = createToolingLogger(),
|
||||
asSuperuser = false,
|
||||
useCertForSsl = false,
|
||||
}: CreateRuntimeServicesOptions): Promise<RuntimeServices> => {
|
||||
const kibanaUrl = spaceId ? buildUrlWithSpaceId(_kibanaUrl, spaceId) : _kibanaUrl;
|
||||
let username = _username;
|
||||
let password = _password;
|
||||
let esUsername = _esUsername;
|
||||
let esPassword = _esPassword;
|
||||
|
||||
if (asSuperuser) {
|
||||
const tmpKbnClient = createKbnClient({
|
||||
url: kibanaUrl,
|
||||
username,
|
||||
password,
|
||||
useCertForSsl,
|
||||
log,
|
||||
spaceId,
|
||||
});
|
||||
|
||||
await waitForKibana(tmpKbnClient);
|
||||
const isServerlessEs = await isServerlessKibanaFlavor(tmpKbnClient);
|
||||
|
||||
if (isServerlessEs) {
|
||||
log?.warning(
|
||||
'Creating Security Superuser is not supported in current environment.\nES is running in serverless mode. ' +
|
||||
'Will use username [system_indices_superuser] instead.'
|
||||
);
|
||||
|
||||
username = 'system_indices_superuser';
|
||||
password = 'changeme';
|
||||
|
||||
esUsername = 'system_indices_superuser';
|
||||
esPassword = 'changeme';
|
||||
} else {
|
||||
const superuserResponse = await createSecuritySuperuser(
|
||||
createEsClient({
|
||||
url: elasticsearchUrl,
|
||||
username: esUsername ?? username,
|
||||
password: esPassword ?? password,
|
||||
log,
|
||||
useCertForSsl,
|
||||
})
|
||||
);
|
||||
|
||||
({ username, password } = superuserResponse);
|
||||
|
||||
if (superuserResponse.created) {
|
||||
log.info(`Kibana user [${username}] was created with password [${password}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const kbnURL = new URL(kibanaUrl);
|
||||
const esURL = new URL(elasticsearchUrl);
|
||||
const fleetURL = new URL(fleetServerUrl);
|
||||
const kbnClient = createKbnClient({
|
||||
log,
|
||||
url: kibanaUrl,
|
||||
username,
|
||||
password,
|
||||
spaceId,
|
||||
apiKey,
|
||||
useCertForSsl,
|
||||
});
|
||||
|
||||
if (spaceId && spaceId !== DEFAULT_SPACE_ID) {
|
||||
log?.info(`Enabling Fleet space awareness`);
|
||||
await enableFleetSpaceAwareness(kbnClient);
|
||||
}
|
||||
|
||||
return {
|
||||
kbnClient,
|
||||
esClient: createEsClient({
|
||||
log,
|
||||
url: elasticsearchUrl,
|
||||
username: esUsername ?? username,
|
||||
password: esPassword ?? password,
|
||||
apiKey,
|
||||
useCertForSsl,
|
||||
}),
|
||||
log,
|
||||
localhostRealIp: getLocalhostRealIp(),
|
||||
apiKey: apiKey ?? '',
|
||||
user: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
kibana: {
|
||||
url: kibanaUrl,
|
||||
hostname: kbnURL.hostname,
|
||||
port: kbnURL.port,
|
||||
isLocalhost: isLocalhost(kbnURL.hostname),
|
||||
},
|
||||
fleetServer: {
|
||||
url: fleetServerUrl,
|
||||
hostname: fleetURL.hostname,
|
||||
port: fleetURL.port,
|
||||
isLocalhost: isLocalhost(fleetURL.hostname),
|
||||
},
|
||||
elastic: {
|
||||
url: elasticsearchUrl,
|
||||
hostname: esURL.hostname,
|
||||
port: esURL.port,
|
||||
isLocalhost: isLocalhost(esURL.hostname),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const buildUrlWithCredentials = (
|
||||
url: string,
|
||||
username: string,
|
||||
password: string
|
||||
): string => {
|
||||
const newUrl = new URL(url);
|
||||
|
||||
newUrl.username = username;
|
||||
newUrl.password = password;
|
||||
|
||||
return newUrl.href;
|
||||
};
|
||||
|
||||
export const createEsClient = ({
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
apiKey,
|
||||
log,
|
||||
useCertForSsl = false,
|
||||
}: {
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
/** If defined, both `username` and `password` will be ignored */
|
||||
apiKey?: string;
|
||||
log?: ToolingLog;
|
||||
useCertForSsl?: boolean;
|
||||
}): Client => {
|
||||
const isHttps = new URL(url).protocol.startsWith('https');
|
||||
const clientOptions: ClientOptions = {
|
||||
node: buildUrlWithCredentials(url, apiKey ? '' : username, apiKey ? '' : password),
|
||||
Connection: HttpConnection,
|
||||
requestTimeout: 30_000,
|
||||
};
|
||||
|
||||
if (isHttps && useCertForSsl) {
|
||||
clientOptions.tls = {
|
||||
ca: [CA_CERTIFICATE],
|
||||
};
|
||||
}
|
||||
|
||||
if (apiKey) {
|
||||
clientOptions.auth = { apiKey };
|
||||
}
|
||||
|
||||
if (log) {
|
||||
log.verbose(
|
||||
`Creating Elasticsearch client options: ${JSON.stringify({
|
||||
...omit(clientOptions, 'tls'),
|
||||
...(clientOptions.tls ? { tls: { ca: [typeof clientOptions.tls.ca] } } : {}),
|
||||
})}`
|
||||
);
|
||||
}
|
||||
|
||||
return new Client(clientOptions);
|
||||
};
|
||||
|
||||
export const createKbnClient = ({
|
||||
url: _url,
|
||||
username,
|
||||
password,
|
||||
spaceId,
|
||||
apiKey,
|
||||
log = createToolingLogger(),
|
||||
useCertForSsl = false,
|
||||
}: {
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
/** If defined, both `username` and `password` will be ignored */
|
||||
apiKey?: string;
|
||||
spaceId?: string;
|
||||
log?: ToolingLog;
|
||||
useCertForSsl?: boolean;
|
||||
}): KbnClient => {
|
||||
const url = spaceId ? buildUrlWithSpaceId(_url, spaceId) : _url;
|
||||
const isHttps = new URL(url).protocol.startsWith('https');
|
||||
const clientOptions: ConstructorParameters<typeof KbnClientExtended>[0] = {
|
||||
log,
|
||||
apiKey,
|
||||
url: buildUrlWithCredentials(url, username, password),
|
||||
};
|
||||
|
||||
if (isHttps && useCertForSsl) {
|
||||
clientOptions.certificateAuthorities = [CA_CERTIFICATE];
|
||||
}
|
||||
|
||||
if (log) {
|
||||
log.verbose(
|
||||
`Creating Kibana client with URL: ${clientOptions.url} ${
|
||||
apiKey ? ` + ApiKey: ${apiKey}` : ''
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
return new KbnClientExtended(clientOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a new URL based on the one provided on input for the given space id
|
||||
* @param url
|
||||
* @param spaceId
|
||||
*/
|
||||
export const buildUrlWithSpaceId = (url: string, spaceId: string): string => {
|
||||
const newUrl = new URL(url);
|
||||
let requestPath = newUrl.pathname;
|
||||
const currentUrlSpace = getSpaceIdFromPath(requestPath); // NOTE: we are not currently supporting a Kibana base path prefix
|
||||
|
||||
if (currentUrlSpace.pathHasExplicitSpaceIdentifier) {
|
||||
// Get the request path (if any) from the url
|
||||
requestPath = requestPath.substring(`/s/${currentUrlSpace.spaceId}`.length) || '/';
|
||||
}
|
||||
|
||||
newUrl.pathname = addSpaceIdToPath('/', spaceId, requestPath);
|
||||
|
||||
return newUrl.href;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the Stack (kibana/ES) version from the `/api/status` kibana api
|
||||
* @param kbnClient
|
||||
*/
|
||||
export const fetchStackVersion = async (kbnClient: KbnClient): Promise<string> => {
|
||||
const status = await fetchKibanaStatus(kbnClient);
|
||||
|
||||
if (!status?.version?.number) {
|
||||
throw new Error(
|
||||
`unable to get stack version from '/api/status' \n${JSON.stringify(status, null, 2)}`
|
||||
);
|
||||
}
|
||||
|
||||
return status.version.number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks to ensure Kibana is up and running
|
||||
* @param kbnClient
|
||||
*/
|
||||
export const waitForKibana = async (kbnClient: KbnClient): Promise<void> => {
|
||||
await pRetry(
|
||||
async () => {
|
||||
const response = await fetchKibanaStatus(kbnClient);
|
||||
|
||||
if (response.status.overall.level !== 'available') {
|
||||
throw new Error(
|
||||
`Kibana not available. [status.overall.level: ${response.status.overall.level}]`
|
||||
);
|
||||
}
|
||||
},
|
||||
{ maxTimeout: 10000 }
|
||||
);
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { schema, type TypeOf } from '@kbn/config-schema';
|
||||
|
||||
const TestFileFtrConfigSchema = schema.object(
|
||||
{
|
||||
license: schema.maybe(schema.string()),
|
||||
kbnServerArgs: schema.maybe(schema.arrayOf(schema.string())),
|
||||
productTypes: schema.maybe(
|
||||
// TODO:PT write validate function to ensure that only the correct combinations are used
|
||||
schema.arrayOf(
|
||||
schema.object({
|
||||
product_line: schema.oneOf([
|
||||
schema.literal('security'),
|
||||
schema.literal('endpoint'),
|
||||
schema.literal('cloud'),
|
||||
]),
|
||||
|
||||
product_tier: schema.oneOf([schema.literal('essentials'), schema.literal('complete')]),
|
||||
})
|
||||
)
|
||||
),
|
||||
},
|
||||
{ defaultValue: {}, unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
export type SecuritySolutionDescribeBlockFtrConfig = TypeOf<typeof TestFileFtrConfigSchema>;
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"extends": "../../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"cypress",
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"cypress.d.ts",
|
||||
"**/*.ts",
|
||||
"src/**/*",
|
||||
// have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636
|
||||
"src/**/*.json",
|
||||
"../../../../../typings/**/*",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/es",
|
||||
"@kbn/security-plugin",
|
||||
"@kbn/tooling-log",
|
||||
"@kbn/repo-info",
|
||||
"@kbn/test",
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/core-status-common",
|
||||
"@kbn/dev-utils",
|
||||
"@kbn/spaces-plugin",
|
||||
"@kbn/config-schema",
|
||||
]
|
||||
}
|
|
@ -728,6 +728,8 @@
|
|||
"@kbn/custom-integrations-plugin/*": ["src/platform/plugins/shared/custom_integrations/*"],
|
||||
"@kbn/cypress-config": ["src/platform/packages/shared/kbn-cypress-config"],
|
||||
"@kbn/cypress-config/*": ["src/platform/packages/shared/kbn-cypress-config/*"],
|
||||
"@kbn/cypress-test-helper": ["src/platform/packages/shared/kbn-cypress-test-helper"],
|
||||
"@kbn/cypress-test-helper/*": ["src/platform/packages/shared/kbn-cypress-test-helper/*"],
|
||||
"@kbn/dashboard-enhanced-plugin": ["x-pack/platform/plugins/shared/dashboard_enhanced"],
|
||||
"@kbn/dashboard-enhanced-plugin/*": ["x-pack/platform/plugins/shared/dashboard_enhanced/*"],
|
||||
"@kbn/dashboard-plugin": ["src/platform/plugins/shared/dashboard"],
|
||||
|
|
|
@ -10,7 +10,7 @@ import path from 'path';
|
|||
import { load as loadYaml } from 'js-yaml';
|
||||
import { readFileSync } from 'fs';
|
||||
import type { YamlRoleDefinitions } from '@kbn/test-suites-serverless/shared/lib';
|
||||
import { samlAuthentication } from '@kbn/security-solution-plugin/public/management/cypress/support/saml_authentication';
|
||||
import { samlAuthentication } from '@kbn/cypress-test-helper/src/auth/saml_auth';
|
||||
import { setupUserDataLoader } from './support/setup_data_loader_tasks';
|
||||
import { getFailedSpecVideos } from './support/filter_videos';
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { waitForAlertsToPopulate } from '@kbn/test-suites-xpack/security_solution_cypress/cypress/tasks/create_new_rule';
|
||||
import { waitForAlertsToPopulate } from '@kbn/cypress-test-helper/src/services/alerting_services';
|
||||
import { disableNewFeaturesTours } from '../../tasks/navigation';
|
||||
import { initializeDataViews } from '../../tasks/login';
|
||||
import { checkResults, clickRuleName, submitQuery } from '../../tasks/live_query';
|
||||
|
|
|
@ -31,8 +31,8 @@ import registerCypressGrep from '@cypress/grep';
|
|||
|
||||
registerCypressGrep();
|
||||
|
||||
import type { SecuritySolutionDescribeBlockFtrConfig } from '@kbn/security-solution-plugin/scripts/run_cypress/utils';
|
||||
import { login } from '@kbn/security-solution-plugin/public/management/cypress/tasks/login';
|
||||
import type { SecuritySolutionDescribeBlockFtrConfig } from '@kbn/cypress-test-helper/src/utils';
|
||||
import { login } from '@kbn/cypress-test-helper/src/auth/login';
|
||||
|
||||
import type { LoadedRoleAndUser } from '@kbn/test-suites-serverless/shared/lib';
|
||||
import type { ServerlessRoleName } from './roles';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createRuntimeServices } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services';
|
||||
import { createRuntimeServices } from '@kbn/cypress-test-helper/src/services/stack_services';
|
||||
import { SecurityRoleAndUserLoader } from '@kbn/test-suites-serverless/shared/lib';
|
||||
import type {
|
||||
LoadedRoleAndUser,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { waitForAlertsToPopulate } from '@kbn/test-suites-xpack/security_solution_cypress/cypress/tasks/create_new_rule';
|
||||
import { waitForAlertsToPopulate } from '@kbn/cypress-test-helper/src/services/alerting_services';
|
||||
import { disableNewFeaturesTours } from './navigation';
|
||||
import { getAdvancedButton } from '../screens/integrations';
|
||||
import {
|
||||
|
|
|
@ -32,10 +32,8 @@
|
|||
"path": "../tsconfig.json",
|
||||
"force": true
|
||||
},
|
||||
"@kbn/security-solution-plugin",
|
||||
"@kbn/fleet-plugin",
|
||||
"@kbn/cases-plugin",
|
||||
"@kbn/security-solution-plugin/public/management/cypress",
|
||||
"@kbn/test-suites-xpack/security_solution_cypress/cypress",
|
||||
"@kbn/cypress-test-helper",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5227,6 +5227,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/cypress-test-helper@link:src/platform/packages/shared/kbn-cypress-test-helper":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/dashboard-enhanced-plugin@link:x-pack/platform/plugins/shared/dashboard_enhanced":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue