diff --git a/.buildkite/pipelines/security_solution/api_integration.yml b/.buildkite/pipelines/security_solution/api_integration.yml index 474fbc31b0bc3..046d9fafe3873 100644 --- a/.buildkite/pipelines/security_solution/api_integration.yml +++ b/.buildkite/pipelines/security_solution/api_integration.yml @@ -1,19 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - queue: n2-16-spot - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: 'Upload runtime info' key: upload_runtime_info - depends_on: build_image agents: queue: n2-4-spot timeout_in_minutes: 300 @@ -24,7 +12,6 @@ steps: - group: 'Execute Tests' key: test_execution - depends_on: build_image steps: - label: Running exception_workflows:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_workflows:qa:serverless diff --git a/.buildkite/pipelines/security_solution/api_integration_serverless_release.yml b/.buildkite/pipelines/security_solution/api_integration_serverless_release.yml index 25c6f0ce06b8e..f1b9d002209d0 100644 --- a/.buildkite/pipelines/security_solution/api_integration_serverless_release.yml +++ b/.buildkite/pipelines/security_solution/api_integration_serverless_release.yml @@ -1,7 +1,6 @@ steps: - group: 'API Integration Serverless Release Tests' key: test_execution - depends_on: build_image steps: - label: Running integration tests for Serverless Exception Workflows command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_workflows:qa:serverless:release diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_defend_workflows.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_defend_workflows.yml index 11a99f29fbe14..974514f47c101 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_defend_workflows.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_defend_workflows.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -31,6 +15,5 @@ steps: limit: 1 - command: "echo 'Running the defend worklows tests in this step" - depends_on: build_image key: test_defend_workflows - label: 'Serverless MKI QA Defend Workflows - Security Solution Cypress Tests' + label: "Serverless MKI QA Defend Workflows - Security Solution Cypress Tests" diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_detection_engine.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_detection_engine.yml index ff4eb22fb2e2d..a57659281d731 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_detection_engine.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_detection_engine.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -33,7 +17,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine label: 'Serverless MKI QA Detection Engine - Security Solution Cypress Tests' key: test_detection_engine - depends_on: build_image env: BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" agents: @@ -53,7 +36,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine:exceptions label: 'Serverless MKI QA Detection Engine - Exceptions - Security Solution Cypress Tests' key: test_detection_engine_exceptions - depends_on: build_image env: BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" agents: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_entity_analytics.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_entity_analytics.yml index 518ec0268309b..97947ed98fd24 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_entity_analytics.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_entity_analytics.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -32,7 +16,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:entity_analytics label: 'Serverless MKI QA Entity Analytics - Security Solution Cypress Tests' - depends_on: build_image key: test_entity_analytics env: BK_TEST_SUITE_KEY: "serverless-cypress-entity-analytics" diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_explore.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_explore.yml index a8ded33bfdd22..95a87db37bb31 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_explore.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_explore.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -31,7 +15,6 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:explore - depends_on: build_image key: test_explore label: 'Serverless MKI QA Explore - Security Solution Cypress Tests' env: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_gen_ai.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_gen_ai.yml index 2b10150dd55ac..90018caab7024 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_gen_ai.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_gen_ai.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -31,8 +15,7 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:ai_assistant - label: 'Serverless MKI QA AI Assistant - Security Solution Cypress Tests' - depends_on: build_image + label: "Serverless MKI QA AI Assistant - Security Solution Cypress Tests" key: test_ai_assistant env: BK_TEST_SUITE_KEY: "serverless-cypress-gen-ai" @@ -47,5 +30,5 @@ steps: parallelism: 1 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_investigations.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_investigations.yml index b7ddac79641ce..39f4b66d5c607 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_investigations.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_investigations.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -31,7 +15,6 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:investigations - depends_on: build_image key: test_investigations label: 'Serverless MKI QA Investigations - Security Solution Cypress Tests' env: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_rule_management.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_rule_management.yml index 58f10084bdcda..933d7b2de0ac5 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_rule_management.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_rule_management.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -32,7 +16,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management label: 'Serverless MKI QA Rule Management - Security Solution Cypress Tests' - depends_on: build_image key: test_rule_management env: BK_TEST_SUITE_KEY: "serverless-cypress-rule-management" @@ -52,7 +35,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management:prebuilt_rules label: 'Serverless MKI QA Rule Management - Prebuilt Rules - Security Solution Cypress Tests' - depends_on: build_image key: test_rule_management_prebuilt_rules env: BK_TEST_SUITE_KEY: "serverless-cypress-rule-management" diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh index f87df9cb58bac..8b0ab8dcae33a 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh @@ -10,57 +10,87 @@ source .buildkite/scripts/common/util.sh buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" "true" -echo "--- Serverless Security Second Quality Gate" +source .buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh + cd x-pack/test/security_solution_api_integration set +e -QA_API_KEY=$(vault_get security-solution-quality-gate qa_api_key) -QA_CONSOLE_URL=$(vault_get security-solution-quality-gate qa_console_url) - # Generate a random 5-digit number random_number=$((10000 + $RANDOM % 90000)) -if [ -z "${KIBANA_MKI_USE_LATEST_COMMIT+x}" ] || [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "0" ]; then - ENVIRONMENT_DETAILS=$(curl --location "$QA_CONSOLE_URL/api/v1/serverless/projects/security" \ - --header "Authorization: ApiKey $QA_API_KEY" \ +PROXY_URL="https://cloud-handler-test-r344edqiza-uc.a.run.app" +# Check the healthcheck of the proxy service +response=$(curl -s -o /dev/null -w "%{http_code}" "$PROXY_URL/healthcheck") +echo "Proxy Healthcheck Response code: $response" + +if [ "$response" -eq 200 ]; then + # Proxy service is up and running. Use the proxy to handle the projects. + CREATE_URL="$PROXY_URL/projects" + RESET_CREDS_URL="$PROXY_URL/projects/{project_id}/_reset-internal-credentials" + DELETE_URL="$PROXY_URL/projects/{project_id}" + AUTH="Basic $(vault_get security-solution-quality-gate-proxy base_64_encoded_auth)" +else + # Proxy service is not available. Use default single org execution mode using cloud QA directly. + CREATE_URL="$QA_CONSOLE_URL/api/v1/serverless/projects/security" + RESET_CREDS_URL="$QA_CONSOLE_URL/api/v1/serverless/projects/security/{project_id}/_reset-internal-credentials" + DELETE_URL="$QA_CONSOLE_URL/api/v1/serverless/projects/security/{project_id}" + AUTH="ApiKey $CLOUD_QA_API_KEY" +fi + + +if [ -z "${KIBANA_MKI_IMAGE_COMMIT+x}" ]; then + # There is no provided commit to be used so running against whatever image + # is already qualified in Cloud QA. + ENVIRONMENT_DETAILS=$(curl --location "$CREATE_URL" \ + --header "Authorization: $AUTH" \ --header 'Content-Type: application/json' \ --data '{ "name": "ftr-integration-tests-'$random_number'", "region_id": "aws-eu-west-1"}' | jq '.') else - KBN_COMMIT_HASH=${BUILDKITE_COMMIT:0:12} - ENVIRONMENT_DETAILS=$(curl --location "$QA_CONSOLE_URL/api/v1/serverless/projects/security" \ - --header "Authorization: ApiKey $QA_API_KEY" \ + # A commit is provided so it will be used to run the tests against this qualified image. + KBN_COMMIT_HASH=${KIBANA_MKI_IMAGE_COMMIT:0:12} + ENVIRONMENT_DETAILS=$(curl --location "$CREATE_URL" \ + --header "Authorization: $AUTH" \ --header 'Content-Type: application/json' \ --data '{ "name": "ftr-integration-tests-'$random_number'", "region_id": "aws-eu-west-1", "overrides": { "kibana": { - "docker_image" : "docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-'$KBN_COMMIT_HASH'" + "docker_image" : "docker.elastic.co/kibana-ci/kibana-serverless:git-'$KBN_COMMIT_HASH'" } } }' | jq '.') fi +if [ "$response" -eq 200 ]; then + # Proxy is up and running so reading the ES and KB endpoints from the proxy response. + ES_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.elasticsearch_endpoint') + KB_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.kibana_endpoint') + ID=$(echo $ENVIRONMENT_DETAILS | jq -r '.project_id') +else + # Proxy is unavailable so reading the ES and KB endpoints from the cloud QA response. + ES_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.elasticsearch') + KB_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.kibana') + ID=$(echo $ENVIRONMENT_DETAILS | jq -r '.id') +fi NAME=$(echo $ENVIRONMENT_DETAILS | jq -r '.name') -ID=$(echo $ENVIRONMENT_DETAILS | jq -r '.id') -ES_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.elasticsearch') -KB_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.kibana') # Wait five seconds for the project to appear sleep 5 # Resetting the credentials of the elastic user in the project -CREDS_BODY=$(curl -s --location --request POST "$QA_CONSOLE_URL/api/v1/serverless/projects/security/$ID/_reset-internal-credentials" \ - --header "Authorization: ApiKey $QA_API_KEY" \ +RESET_CREDENTIALS_URL=$(echo "$RESET_CREDS_URL" | sed "s/{project_id}/$ID/g") +CREDS_BODY=$(curl -s --location --request POST "$RESET_CREDENTIALS_URL" \ + --header "Authorization: $AUTH" \ --header 'Content-Type: application/json' | jq '.') USERNAME=$(echo $CREDS_BODY | jq -r '.username') PASSWORD=$(echo $CREDS_BODY | jq -r '.password') -AUTH=$(echo "$USERNAME:$PASSWORD") +PROJECT_AUTH=$(echo "$USERNAME:$PASSWORD") # Checking if Elasticsearch has status green while : ; do - STATUS=$(curl -u $AUTH --location "$ES_URL:443/_cluster/health?wait_for_status=green&timeout=50s" | jq -r '.status') + STATUS=$(curl -u $PROJECT_AUTH --location "$ES_URL:443/_cluster/health?wait_for_status=green&timeout=50s" | jq -r '.status') if [ "$STATUS" != "green" ]; then echo "Sleeping for 40s to wait for ES status to be green..." sleep 40 @@ -72,7 +102,7 @@ done # Checking if Kibana is available while : ; do - STATUS=$(curl -u $AUTH --location "$KB_URL:443/api/status" | jq -r '.status.overall.level') + STATUS=$(curl -u $PROJECT_AUTH --location "$KB_URL:443/api/status" | jq -r '.status.overall.level') if [ "$STATUS" != "available" ]; then echo "Sleeping for 15s to wait for Kibana to be available..." sleep 15 @@ -90,11 +120,13 @@ FORMATTED_KB_URL="${KB_URL/https:\/\//}" # This is used in order to wait for the environment to be ready. sleep 150 +echo "--- Triggering API tests for $1" TEST_CLOUD=1 TEST_ES_URL="https://$USERNAME:$PASSWORD@$FORMATTED_ES_URL:443" TEST_KIBANA_URL="https://$USERNAME:$PASSWORD@$FORMATTED_KB_URL:443" yarn run $1 cmd_status=$? echo "Exit code with status: $cmd_status" -curl --location --request DELETE "$QA_CONSOLE_URL/api/v1/serverless/projects/security/$ID" \ - --header "Authorization: ApiKey $QA_API_KEY" +DELETE_PROJECT_URL=$(echo "$DELETE_URL" | sed "s/{project_id}/$ID/g") +curl --location --request DELETE "$DELETE_PROJECT_URL" \ + --header "Authorization: $AUTH" exit $cmd_status diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh new file mode 100644 index 0000000000000..85f361752f6f6 --- /dev/null +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +mkdir .ftr + +# The role-users file that is used as a fallback if the proxy service is unavailable. +vault_get security-quality-gate/role-users data -format=json > .ftr/role_users.json +# The role-users files relevant to the proxy service and its orgs. +vault_get security-quality-gate/role-users/sec-sol-auto-01 data -format=json > .ftr/sec-sol-auto-01.json +vault_get security-quality-gate/role-users/sec-sol-auto-02 data -format=json > .ftr/sec-sol-auto-02.json +vault_get security-quality-gate/role-users/sec-sol-auto-03 data -format=json > .ftr/sec-sol-auto-03.json + +# The vault entries relevant to QA Cloud +export CLOUD_QA_API_KEY=$(vault_get security-solution-quality-gate qa_api_key) +export QA_CONSOLE_URL=$(vault_get security-solution-quality-gate qa_console_url) +# The vault entries relevant to the Proxy service (Cloud Handler) +export PROXY_URL=$(vault_get security-solution-quality-gate-proxy proxy_url_test) +export PROXY_CLIENT_ID=$(vault_get security-solution-quality-gate-proxy client_id) +export PROXY_SECRET=$(vault_get security-solution-quality-gate-proxy secret) diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh index cf0dc230b0f40..6507f01750424 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh @@ -16,20 +16,12 @@ export JOB=kibana-security-solution-chrome buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" "true" -mkdir .ftr -vault_get security-quality-gate/role-users data -format=json > .ftr/role_users.json +source .buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh cd x-pack/test/security_solution_cypress set +e -if [ -z "${KIBANA_MKI_USE_LATEST_COMMIT+x}" ] || [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "0" ]; then - KIBANA_OVERRIDE_FLAG=0 -else - KIBANA_OVERRIDE_FLAG=1 -fi - -QA_API_KEY=$(vault_get security-solution-quality-gate qa_api_key) -QA_CONSOLE_URL=$(vault_get security-solution-quality-gate qa_console_url) -BK_ANALYTICS_API_KEY=$(vault_get security-solution-quality-gate $BK_TEST_SUITE_KEY) +export BK_ANALYTICS_API_KEY=$(vault_get security-solution-quality-gate $BK_TEST_SUITE_KEY) -QA_CONSOLE_URL=$QA_CONSOLE_URL KIBANA_MKI_USE_LATEST_COMMIT=$KIBANA_OVERRIDE_FLAG BK_ANALYTICS_API_KEY=$BK_ANALYTICS_API_KEY CLOUD_QA_API_KEY=$QA_API_KEY yarn $1; status=$?; yarn junit:merge || :; exit $status +echo "--- Triggering Kibana tests for $1" +BK_ANALYTICS_API_KEY=$BK_ANALYTICS_API_KEY yarn $1; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh index c1a22d221cafc..a39c51c07a47c 100644 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh @@ -2,11 +2,10 @@ echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co KIBANA_BASE_IMAGE="docker.elastic.co/kibana-ci/kibana-serverless" -KIBANA_CURRENT_COMMIT=${KIBANA_BASE_IMAGE}:sec-sol-qg-${BUILDKITE_COMMIT:0:12} KIBANA_LATEST=${KIBANA_BASE_IMAGE}:latest -if [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "1" ]; then - KBN_IMAGE=${KIBANA_CURRENT_COMMIT} +if [ -v KIBANA_MKI_IMAGE_COMMIT ]; then + KBN_IMAGE=${KIBANA_BASE_IMAGE}:git-${KIBANA_MKI_IMAGE_COMMIT:0:12} else KBN_IMAGE=${KIBANA_LATEST} fi diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index 7e7b862aa84b2..a8edca981c1d4 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -35,18 +35,23 @@ export interface SamlSessionManagerOptions { * Manages cookies associated with user roles */ export class SamlSessionManager { + private readonly DEFAULT_ROLES_FILE_NAME: string = 'role_users.json'; private readonly isCloud: boolean; private readonly kbnHost: string; private readonly kbnClient: KbnClient; private readonly log: ToolingLog; private readonly roleToUserMap: Map; private readonly sessionCache: Map; - private readonly userRoleFilePath = resolve(REPO_ROOT, '.ftr', 'role_users.json'); private readonly supportedRoles: string[]; + private readonly userRoleFilePath: string; - constructor(options: SamlSessionManagerOptions) { + constructor(options: SamlSessionManagerOptions, rolesFilename?: string) { this.isCloud = options.isCloud; this.log = options.log; + // if the rolesFilename is provided, respect it. Otherwise use DEFAULT_ROLES_FILE_NAME. + const rolesFile = rolesFilename ? rolesFilename : this.DEFAULT_ROLES_FILE_NAME; + this.log.info(`Using the file ${rolesFile} for the role users`); + this.userRoleFilePath = resolve(REPO_ROOT, '.ftr', rolesFile); const hostOptionsWithoutAuth = { protocol: options.hostOptions.protocol, hostname: options.hostOptions.hostname, diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 2c5ca1e049fae..8c3f13eefffce 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -28,42 +28,9 @@ import { exec } from 'child_process'; import { renderSummaryTable } from './print_run'; import { parseTestFileConfig, retrieveIntegrations } from './utils'; -interface ProductType { - product_line: string; - product_tier: string; -} - -interface OverrideEntry { - docker_image: string; -} - -interface ProductOverrides { - kibana?: OverrideEntry; - elasticsearch?: OverrideEntry; - fleet?: OverrideEntry; - cluster?: OverrideEntry; -} - -interface CreateProjectRequestBody { - name: string; - region_id: string; - product_types?: ProductType[]; - overrides?: ProductOverrides; -} - -interface Project { - name: string; - id: string; - region: string; - es_url: string; - kb_url: string; - product: string; -} - -interface Credentials { - username: string; - password: string; -} +import type { ProductType, Credentials, ProjectHandler } from './project_handler/project_handler'; +import { CloudHandler } from './project_handler/cloud_project_handler'; +import { ProxyHandler } from './project_handler/proxy_project_handler'; const DEFAULT_CONFIGURATION: Readonly = [ { product_line: 'security', product_tier: 'complete' }, @@ -71,7 +38,6 @@ const DEFAULT_CONFIGURATION: Readonly = [ { product_line: 'endpoint', product_tier: 'complete' }, ] as const; -const DEFAULT_REGION = 'aws-eu-west-1'; const PROJECT_NAME_PREFIX = 'kibana-cypress-security-solution-ephemeral'; const BASE_ENV_URL = `${process.env.QA_CONSOLE_URL}`; let log: ToolingLog; @@ -96,156 +62,27 @@ const getApiKeyFromElasticCloudJsonFile = (): string | undefined => { } }; -// Method to invoke the create project API for serverless. -async function createSecurityProject( - projectName: string, - apiKey: string, - productTypes: ProductType[], - commit: string -): Promise { - const body: CreateProjectRequestBody = { - name: projectName, - region_id: DEFAULT_REGION, - product_types: productTypes, - }; - - log.info(`Kibana override flag equals to ${process.env.KIBANA_MKI_USE_LATEST_COMMIT}!`); - if ( - (process.env.KIBANA_MKI_USE_LATEST_COMMIT && - process.env.KIBANA_MKI_USE_LATEST_COMMIT === '1') || - commit - ) { - const override = commit ? commit : process.env.BUILDKITE_COMMIT; - const kibanaOverrideImage = `${override?.substring(0, 12)}`; - log.info( - `Overriding Kibana image in the MKI with docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-${kibanaOverrideImage}` - ); - body.overrides = { - kibana: { - docker_image: `docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-${kibanaOverrideImage}`, - }, - }; - } - - try { - const response = await axios.post(`${BASE_ENV_URL}/api/v1/serverless/projects/security`, body, { - headers: { - Authorization: `ApiKey ${apiKey}`, - }, - }); - return { - name: response.data.name, - id: response.data.id, - region: response.data.region_id, - es_url: `${response.data.endpoints.elasticsearch}:443`, - kb_url: `${response.data.endpoints.kibana}:443`, - product: response.data.type, - }; - } catch (error) { - if (error instanceof AxiosError) { - const errorData = JSON.stringify(error.response?.data); - log.error(`${error.response?.status}:${errorData}`); - } else { - log.error(`${error.message}`); - } - } -} - -// Method to invoke the delete project API for serverless. -async function deleteSecurityProject( - projectId: string, - projectName: string, - apiKey: string -): Promise { - try { - await axios.delete(`${BASE_ENV_URL}/api/v1/serverless/projects/security/${projectId}`, { - headers: { - Authorization: `ApiKey ${apiKey}`, - }, - }); - log.info(`Project ${projectName} was successfully deleted!`); - } catch (error) { - if (error instanceof AxiosError) { - log.error(`${error.response?.status}:${error.response?.data}`); - } else { - log.error(`${error.message}`); - } - } -} +// Check if proxy service is up and running executing a healthcheck call. +function proxyHealthcheck(proxyUrl: string): Promise { + const fetchHealthcheck = async (attemptNum: number) => { + log.info(`Retry number ${attemptNum} to check if Elasticsearch is green.`); -// Method to reset the credentials for the created project. -async function resetCredentials( - projectId: string, - runnerId: string, - apiKey: string -): Promise { - log.info(`${runnerId} : Reseting credentials`); - - const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => { - const response = await axios.post( - `${BASE_ENV_URL}/api/v1/serverless/projects/security/${projectId}/_reset-internal-credentials`, - {}, - { - headers: { - Authorization: `ApiKey ${apiKey}`, - }, - } - ); - log.info('Credentials have ben reset'); - return { - password: response.data.password, - username: response.data.username, - }; + const response = await axios.get(`${proxyUrl}/healthcheck`); + log.info(`The proxy service is available.`); + return response.status === 200; }; - const retryOptions = { onFailedAttempt: (error: Error | AxiosError) => { - if (error instanceof AxiosError && error.code === 'ENOTFOUND') { - log.info('Project is not reachable. A retry will be triggered soon..'); - } else { - log.error(`${error.message}`); + if (error instanceof AxiosError) { + log.info(`The proxy service is not available. A retry will be triggered soon...`); } }, - retries: 100, + retries: 4, factor: 2, maxTimeout: 20000, }; - return pRetry(fetchResetCredentialsStatusAttempt, retryOptions); -} - -// Wait until Project is initialized -function waitForProjectInitialized(projectId: string, apiKey: string): Promise { - const fetchProjectStatusAttempt = async (attemptNum: number) => { - log.info(`Retry number ${attemptNum} to check if project is initialized.`); - const response = await axios.get( - `${BASE_ENV_URL}/api/v1/serverless/projects/security/${projectId}/status`, - { - headers: { - Authorization: `ApiKey ${apiKey}`, - }, - } - ); - if (response.data.phase !== 'initialized') { - log.info(response.data); - throw new Error('Project is not initialized. A retry will be triggered soon...'); - } else { - log.info('Project is initialized'); - } - }; - const retryOptions = { - onFailedAttempt: (error: Error | AxiosError) => { - if (error instanceof AxiosError && error.code === 'ENOTFOUND') { - log.info('Project is not reachable. A retry will be triggered soon...'); - } else { - log.error(`${error.message}`); - } - }, - retries: 100, - factor: 2, - maxTimeout: 20000, - }; - return pRetry(fetchProjectStatusAttempt, retryOptions); + return pRetry(fetchHealthcheck, retryOptions); } // Wait until elasticsearch status goes green @@ -406,10 +243,25 @@ export const cli = () => { return process.exit(1); } + const PROXY_URL = process.env.PROXY_URL ? process.env.PROXY_URL : undefined; + const PROXY_SECRET = process.env.PROXY_SECRET ? process.env.PROXY_SECRET : undefined; + const PROXY_CLIENT_ID = process.env.PROXY_CLIENT_ID ? process.env.PROXY_CLIENT_ID : undefined; + const API_KEY = process.env.CLOUD_QA_API_KEY ? process.env.CLOUD_QA_API_KEY : getApiKeyFromElasticCloudJsonFile(); + let cloudHandler: ProjectHandler; + if (PROXY_URL && PROXY_CLIENT_ID && PROXY_SECRET && (await proxyHealthcheck(PROXY_URL))) { + cloudHandler = new ProxyHandler(PROXY_URL, PROXY_CLIENT_ID, PROXY_SECRET); + } else if (API_KEY) { + cloudHandler = new CloudHandler(API_KEY, BASE_ENV_URL); + } else { + log.info('PROXY_URL or API KEY which are needed to create project could not be retrieved.'); + // eslint-disable-next-line no-process-exit + return process.exit(1); + } + const PARALLEL_COUNT = process.env.PARALLEL_COUNT ? Number(process.env.PARALLEL_COUNT) : 1; if (!process.env.CLOUD_ENV) { @@ -417,7 +269,6 @@ export const cli = () => { 'The cloud environment to be provided with the env var CLOUD_ENV. Currently working only for QA so the script can proceed.' ); // Abort when more environments will be integrated - // return process.exit(0); } @@ -469,15 +320,16 @@ ${JSON.stringify(argv, null, 2)} const isOpen = argv._.includes('open'); const cypressConfigFilePath = require.resolve(`../../${argv.configFile}`) as string; const cypressConfigFile = await import(cypressConfigFilePath); - // KIBANA_MKI_USE_LATEST_COMMIT === 1 means that we are overriding the image for the periodic pipeline execution. - // We don't override the image when executing the tests on the second quality gate. - if ( - !process.env.KIBANA_MKI_USE_LATEST_COMMIT || - process.env.KIBANA_MKI_USE_LATEST_COMMIT !== '1' - ) { - cypressConfigFile.env.grepTags = - '@serverlessQA --@skipInServerless --@skipInServerlessMKI '; + + // if KIBANA_MKI_QUALITY_GATE exists and has a value, it means that we are running the tests against the second + // quality gate. + if (process.env.KIBANA_MKI_QUALITY_GATE) { + log.info( + 'KIBANA_MKI_QUALITY_GATE is provided, so @serverlessQA --@skipInServerless --@skipInServerlessMKI tags will run.' + ); + cypressConfigFile.env.grepTags = '@serverlessQA --@skipInServerless --@skipInServerlessMKI'; } + const tier: string = argv.tier; const endpointAddon: boolean = argv.endpointAddon; const cloudAddon: boolean = argv.cloudAddon; @@ -559,17 +411,10 @@ ${JSON.stringify(cypressConfigFile, null, 2)} ? getProductTypes(tier, endpointAddon, cloudAddon) : (parseTestFileConfig(filePath).productTypes as ProductType[]); - if (!API_KEY) { - log.info('API KEY to create project could not be retrieved.'); - // eslint-disable-next-line no-process-exit - return process.exit(1); - } - log.info(`${id}: Creating project ${PROJECT_NAME}...`); // Creating project for the test to run - const project = await createSecurityProject( + const project = await cloudHandler.createSecurityProject( PROJECT_NAME, - API_KEY, productTypes, commit ); @@ -581,12 +426,15 @@ ${JSON.stringify(cypressConfigFile, null, 2)} } context.addCleanupTask(() => { - const command = `curl -X DELETE ${BASE_ENV_URL}/api/v1/serverless/projects/security/${project.id} -H "Authorization: ApiKey ${API_KEY}"`; - exec(command); + let command: string; + if (cloudHandler instanceof CloudHandler) { + command = `curl -X DELETE ${BASE_ENV_URL}/api/v1/serverless/projects/security/${project.id} -H "Authorization: ApiKey ${API_KEY}"`; + exec(command); + } }); // Reset credentials for elastic user - const credentials = await resetCredentials(project.id, id, API_KEY); + const credentials = await cloudHandler.resetCredentials(project.id, id); if (!credentials) { log.info('Credentials could not be reset.'); @@ -595,7 +443,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} } // Wait for project to be initialized - await waitForProjectInitialized(project.id, API_KEY); + await cloudHandler.waitForProjectInitialized(project.id); // Base64 encode the credentials in order to invoke ES and KB APIs const auth = btoa(`${credentials.username}:${credentials.password}`); @@ -620,6 +468,9 @@ ${JSON.stringify(cypressConfigFile, null, 2)} ELASTICSEARCH_USERNAME: credentials.username, ELASTICSEARCH_PASSWORD: credentials.password, + // Used in order to handle the correct role_users file loading. + PROXY_ORG: PROXY_URL ? project.proxy_org_name : undefined, + KIBANA_URL: project.kb_url, KIBANA_USERNAME: credentials.username, KIBANA_PASSWORD: credentials.password, @@ -674,7 +525,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} } // Delete serverless project log.info(`${id} : Deleting project ${PROJECT_NAME}...`); - await deleteSecurityProject(project.id, PROJECT_NAME, API_KEY); + await cloudHandler.deleteSecurityProject(project.id, PROJECT_NAME); } catch (error) { // False positive // eslint-disable-next-line require-atomic-updates diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts new file mode 100644 index 0000000000000..89166701ee206 --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts @@ -0,0 +1,170 @@ +/* + * 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 axios, { AxiosError } from 'axios'; +import pRetry from 'p-retry'; +import type { + ProductType, + Project, + CreateProjectRequestBody, + Credentials, +} from './project_handler'; +import { ProjectHandler } from './project_handler'; + +const DEFAULT_REGION = 'aws-eu-west-1'; + +export class CloudHandler extends ProjectHandler { + apiKey: string; + + constructor(apiKey: string, baseEnvUrl: string) { + super(baseEnvUrl); + this.apiKey = apiKey; + } + + // Method to invoke the create project API for serverless. + async createSecurityProject( + projectName: string, + productTypes: ProductType[], + commit: string + ): Promise { + const body: CreateProjectRequestBody = { + name: projectName, + region_id: DEFAULT_REGION, + product_types: productTypes, + }; + + if (process.env.KIBANA_MKI_IMAGE_COMMIT || commit) { + const override = commit ? commit : process.env.KIBANA_MKI_IMAGE_COMMIT; + const kibanaOverrideImage = `${override?.substring(0, 12)}`; + this.log.info(`Kibana Image Commit under test: ${process.env.KIBANA_MKI_IMAGE_COMMIT}!`); + this.log.info( + `Overriding Kibana image in the MKI with docker.elastic.co/kibana-ci/kibana-serverless:git-${kibanaOverrideImage}` + ); + body.overrides = { + kibana: { + docker_image: `docker.elastic.co/kibana-ci/kibana-serverless:git-${kibanaOverrideImage}`, + }, + }; + } + + try { + const response = await axios.post( + `${this.baseEnvUrl}/api/v1/serverless/projects/security`, + body, + { + headers: { + Authorization: `ApiKey ${this.apiKey}`, + }, + } + ); + return { + name: response.data.name, + id: response.data.id, + region: response.data.region_id, + es_url: `${response.data.endpoints.elasticsearch}:443`, + kb_url: `${response.data.endpoints.kibana}:443`, + product: response.data.type, + }; + } catch (error) { + if (error instanceof AxiosError) { + const errorData = JSON.stringify(error.response?.data); + this.log.error(`${error.response?.status}:${errorData}`); + } else { + this.log.error(`${error.message}`); + } + } + } + + // Method to invoke the delete project API for serverless. + async deleteSecurityProject(projectId: string, projectName: string): Promise { + try { + await axios.delete(`${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}`, { + headers: { + Authorization: `ApiKey ${this.apiKey}`, + }, + }); + this.log.info(`Project ${projectName} was successfully deleted!`); + } catch (error) { + if (error instanceof AxiosError) { + this.log.error(`${error.response?.status}:${error.response?.data}`); + } else { + this.log.error(`${error.message}`); + } + } + } + + // Method to reset the credentials for the created project. + resetCredentials(projectId: string, runnerId: string): Promise { + this.log.info(`${runnerId} : Reseting credentials`); + + const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => { + const response = await axios.post( + `${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}/_reset-internal-credentials`, + {}, + { + headers: { + Authorization: `ApiKey ${this.apiKey}`, + }, + } + ); + this.log.info('Credentials have ben reset'); + return { + password: response.data.password, + username: response.data.username, + }; + }; + + const retryOptions = { + onFailedAttempt: (error: Error | AxiosError) => { + if (error instanceof AxiosError && error.code === 'ENOTFOUND') { + this.log.info('Project is not reachable. A retry will be triggered soon..'); + } else { + this.log.error(`${error.message}`); + } + }, + retries: 100, + factor: 2, + maxTimeout: 20000, + }; + + return pRetry(fetchResetCredentialsStatusAttempt, retryOptions); + } + + // Wait until Project is initialized + waitForProjectInitialized(projectId: string): Promise { + const fetchProjectStatusAttempt = async (attemptNum: number) => { + this.log.info(`Retry number ${attemptNum} to check if project is initialized.`); + const response = await axios.get( + `${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}/status`, + { + headers: { + Authorization: `ApiKey ${this.apiKey}`, + }, + } + ); + if (response.data.phase !== 'initialized') { + this.log.info(response.data); + throw new Error('Project is not initialized. A retry will be triggered soon...'); + } else { + this.log.info('Project is initialized'); + } + }; + const retryOptions = { + onFailedAttempt: (error: Error | AxiosError) => { + if (error instanceof AxiosError && error.code === 'ENOTFOUND') { + this.log.info('Project is not reachable. A retry will be triggered soon...'); + } else { + this.log.error(`${error.message}`); + } + }, + retries: 100, + factor: 2, + maxTimeout: 20000, + }; + return pRetry(fetchProjectStatusAttempt, retryOptions); + } +} diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts new file mode 100644 index 0000000000000..199df9c4fb4c0 --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ToolingLog } from '@kbn/tooling-log'; + +export interface ProductType { + product_line: string; + product_tier: string; +} + +export interface OverrideEntry { + docker_image: string; +} + +export interface ProductOverrides { + kibana?: OverrideEntry; + elasticsearch?: OverrideEntry; + fleet?: OverrideEntry; + cluster?: OverrideEntry; +} + +export interface CreateProjectRequestBody { + name: string; + region_id: string; + product_types?: ProductType[]; + overrides?: ProductOverrides; +} + +export interface Project { + name: string; + id: string; + region: string; + es_url: string; + kb_url: string; + product: string; + proxy_id?: number; + proxy_org_id?: number; + proxy_org_name?: string; +} + +export interface Credentials { + username: string; + password: string; +} + +export class ProjectHandler { + private readonly DEFAULT_ERROR_MSG: string = + 'The method needs to be overriden when the class is inherited!'; + + baseEnvUrl: string; + log: ToolingLog; + + constructor(baseEnvUrl: string) { + this.baseEnvUrl = baseEnvUrl; + this.log = new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }); + } + + // Method to invoke the create project API for serverless. + async createSecurityProject( + projectName: string, + productTypes: ProductType[], + commit: string + ): Promise { + throw new Error(this.DEFAULT_ERROR_MSG); + } + + async deleteSecurityProject(projectId: string, projectName: string): Promise { + throw new Error(this.DEFAULT_ERROR_MSG); + } + + resetCredentials(projectId: string, runnerId: string): Promise { + throw new Error(this.DEFAULT_ERROR_MSG); + } + + waitForProjectInitialized(projectId: string): Promise { + throw new Error(this.DEFAULT_ERROR_MSG); + } +} diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts new file mode 100644 index 0000000000000..08e5b418b83ac --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts @@ -0,0 +1,166 @@ +/* + * 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 axios, { AxiosError } from 'axios'; +import pRetry from 'p-retry'; +import type { + ProductType, + Project, + CreateProjectRequestBody, + Credentials, +} from './project_handler'; +import { ProjectHandler } from './project_handler'; + +const DEFAULT_REGION = 'aws-eu-west-1'; + +export class ProxyHandler extends ProjectHandler { + proxyAuth: string; + + constructor(baseEnvUrl: string, proxyClientId: string, proxySecret: string) { + super(baseEnvUrl); + this.proxyAuth = btoa(`${proxyClientId}:${proxySecret}`); + } + + // Method to invoke the create project API for serverless. + async createSecurityProject( + projectName: string, + productTypes: ProductType[], + commit: string + ): Promise { + const body: CreateProjectRequestBody = { + name: projectName, + region_id: DEFAULT_REGION, + product_types: productTypes, + }; + + if (process.env.KIBANA_MKI_IMAGE_COMMIT || commit) { + const override = commit ? commit : process.env.KIBANA_MKI_IMAGE_COMMIT; + const kibanaOverrideImage = `${override?.substring(0, 12)}`; + this.log.info(`Kibana Image Commit under test: ${process.env.KIBANA_MKI_IMAGE_COMMIT}!`); + this.log.info( + `Overriding Kibana image in the MKI with docker.elastic.co/kibana-ci/kibana-serverless:git-${kibanaOverrideImage}` + ); + body.overrides = { + kibana: { + docker_image: `docker.elastic.co/kibana-ci/kibana-serverless:git-${kibanaOverrideImage}`, + }, + }; + } + + try { + const response = await axios.post(`${this.baseEnvUrl}/projects`, body, { + headers: { + Authorization: `Basic ${this.proxyAuth}`, + }, + }); + return { + name: response.data.name, + id: response.data.project_id, + region: response.data.region_id, + es_url: `${response.data.elasticsearch_endpoint}:443`, + kb_url: `${response.data.kibana_endpoint}:443`, + product: response.data.project_type, + proxy_id: response.data.id, + proxy_org_id: response.data.organization_id, + proxy_org_name: response.data.organization_name, + }; + } catch (error) { + if (error instanceof AxiosError) { + const errorData = JSON.stringify(error.response?.data); + this.log.error(`${error.response?.status}:${errorData}`); + } else { + this.log.error(`${error.message}`); + } + } + } + + // Method to invoke the delete project API for serverless. + async deleteSecurityProject(projectId: string, projectName: string): Promise { + try { + await axios.delete(`${this.baseEnvUrl}/projects/${projectId}`, { + headers: { + Authorization: `Basic ${this.proxyAuth}`, + }, + }); + this.log.info(`Project ${projectName} was successfully deleted!`); + } catch (error) { + if (error instanceof AxiosError) { + this.log.error(`${error.response?.status}:${error.response?.data}`); + } else { + this.log.error(`${error.message}`); + } + } + } + + // Method to reset the credentials for the created project. + resetCredentials(projectId: string, runnerId: string): Promise { + this.log.info(`${runnerId} : Reseting credentials`); + + const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => { + const response = await axios.post( + `${this.baseEnvUrl}/projects/${projectId}/_reset-internal-credentials`, + {}, + { + headers: { + Authorization: `Basic ${this.proxyAuth}`, + }, + } + ); + this.log.info('Credentials have ben reset'); + return { + password: response.data.password, + username: response.data.username, + }; + }; + + const retryOptions = { + onFailedAttempt: (error: Error | AxiosError) => { + if (error instanceof AxiosError && error.code === 'ENOTFOUND') { + this.log.info('Project is not reachable. A retry will be triggered soon..'); + } else { + this.log.error(`${error.message}`); + } + }, + retries: 100, + factor: 2, + maxTimeout: 20000, + }; + + return pRetry(fetchResetCredentialsStatusAttempt, retryOptions); + } + + // Wait until Project is initialized + waitForProjectInitialized(projectId: string): Promise { + const fetchProjectStatusAttempt = async (attemptNum: number) => { + this.log.info(`Retry number ${attemptNum} to check if project is initialized.`); + const response = await axios.get(`${this.baseEnvUrl}/projects/${projectId}/status`, { + headers: { + Authorization: `Basic ${this.proxyAuth}`, + }, + }); + if (response.data.phase !== 'initialized') { + this.log.info(response.data); + throw new Error('Project is not initialized. A retry will be triggered soon...'); + } else { + this.log.info('Project is initialized'); + } + }; + const retryOptions = { + onFailedAttempt: (error: Error | AxiosError) => { + if (error instanceof AxiosError && error.code === 'ENOTFOUND') { + this.log.info('Project is not reachable. A retry will be triggered soon...'); + } else { + this.log.error(`${error.message}`); + } + }, + retries: 100, + factor: 2, + maxTimeout: 20000, + }; + return pRetry(fetchProjectStatusAttempt, retryOptions); + } +} diff --git a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts index 0026f6c91ec27..4c26b46a0f62a 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts @@ -30,11 +30,16 @@ export const samlAuthentication = async ( on('task', { getSessionCookie: async (role: string | SecurityRoleName): Promise => { - const sessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }); + // If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles. + const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined; + const sessionManager = new SamlSessionManager( + { + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + }, + rolesFilename + ); return sessionManager.getSessionCookieForRole(role); }, });