[EDR Workflows][MKI][Osquery] Osquery Cypress tests in MKI (#190675)

This PR introduces osquery Cypress tests to the MKI environment. These
tests will be executed within the **Kibana / Serverless / Security
Solution Quality Gate / Defend Workflows** scope. You can find an
example of the build here: [Buildkite
Example](https://buildkite.com/elastic/kibana-serverless-security-solution-quality-gate-defend-workflows/builds/1049).

The main challenge with running osquery tests in MKI is that we cannot
continuously log in users as we do in other environments. This is due to
an issue where users are logged out when switching apps. To address
this, I've added a simple check in the login method to prevent the login
function from being called unnecessarily in MKI. This is a temporary
solution until we can properly log in users via API calls using
authentication tokens.

All the `chalk` & `log.indent` changes were made to make logs more
beautiful 😸
![Screenshot 2024-08-09 at 13 00
50](https://github.com/user-attachments/assets/a113e2a2-7e9c-42f0-800d-4883de8a34d2)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Konrad Szwarc 2024-08-20 11:13:35 +02:00 committed by GitHub
parent 69a842eab7
commit 7027b0b4b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 317 additions and 139 deletions

View file

@ -2,7 +2,7 @@ steps:
- group: "Cypress MKI - Defend Workflows"
key: cypress_test_defend_workflows
steps:
- label: "Running cypress:dw:qa:serverless:run"
- label: "Cypress - DW - Running cypress:dw:qa:serverless:run"
command: .buildkite/scripts/pipelines/security_solution_quality_gate/edr_workflows/mki_security_solution_defend_workflows.sh cypress:dw:qa:serverless:run
key: test_defend_workflows
agents:
@ -14,7 +14,7 @@ steps:
localSsdInterface: nvme
machineType: n2-standard-4
timeout_in_minutes: 300
parallelism: 6
parallelism: 5
retry:
automatic:
- exit_status: "*"
@ -91,7 +91,7 @@ steps:
# - exit_status: "1"
# limit: 1
- label: "Running edr_workflows:policy_response:qa:serverless"
- label: "API - DW - Running edr_workflows:policy_response:qa:serverless"
command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh edr_workflows:policy_response:qa:serverless
key: edr_workflows:policy_response:qa:serverless
agents:
@ -108,7 +108,7 @@ steps:
- exit_status: "1"
limit: 1
- label: "Running edr_workflows:resolver:qa:serverless"
- label: "API - DW - Running edr_workflows:resolver:qa:serverless"
command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh edr_workflows:resolver:qa:serverless
key: edr_workflows:resolver:qa:serverless
agents:
@ -125,7 +125,7 @@ steps:
- exit_status: "1"
limit: 1
- label: "Running edr_workflows:response_actions:qa:serverless"
- label: "API - DW - Running edr_workflows:response_actions:qa:serverless"
command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh edr_workflows:response_actions:qa:serverless
key: edr_workflows:response_actions:qa:serverless
agents:
@ -141,3 +141,24 @@ steps:
automatic:
- exit_status: "1"
limit: 1
- group: "Osquery MKI - Defend Workflows"
key: cypress_test_osquery_defend_workflows
steps:
- label: "Osquery - Cypress - DW - Running cypress:qa:serverless:run"
command: .buildkite/scripts/pipelines/security_solution_quality_gate/edr_workflows/mki_security_solution_defend_workflows_osquery.sh cypress:qa:serverless:run
key: test_osquery_defend_workflows
agents:
image: family/kibana-ubuntu-2004
imageProject: elastic-images-prod
provider: gcp
enableNestedVirtualization: true
localSsds: 1
localSsdInterface: nvme
machineType: n2-standard-4
timeout_in_minutes: 300
parallelism: 3
retry:
automatic:
- exit_status: "*"
limit: 1

View file

@ -0,0 +1,27 @@
#!/bin/bash
set -euo pipefail
if [ -z "$1" ]
then
echo "No target script from the package.json file, is supplied"
exit 1
fi
source .buildkite/scripts/common/util.sh
.buildkite/scripts/bootstrap.sh
export JOB=kibana-defend-workflows-osquery-serverless-cypress
buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" "true"
source .buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh
cd x-pack/plugins/osquery
set +e
export BK_ANALYTICS_API_KEY=$(vault_get security-solution-quality-gate serverless-cypress-defend-workflows)
echo "--- Running the tests for target $1"
BK_ANALYTICS_API_KEY=$BK_ANALYTICS_API_KEY yarn $1; status=$?; yarn junit:merge || :; exit $status

View file

@ -7,60 +7,15 @@
import { defineCypressConfig } from '@kbn/cypress-config';
import path from 'path';
import { safeLoad as loadYaml } from 'js-yaml';
import { readFileSync } from 'fs';
import { getCypressBaseConfig } from './cypress_base.config';
import type { YamlRoleDefinitions } from '@kbn/test-suites-serverless/shared/lib';
import { setupUserDataLoader } from '@kbn/test-suites-serverless/functional/test_suites/security/cypress/support/setup_data_loader_tasks';
import { getFailedSpecVideos } from './support/filter_videos';
const ROLES_YAML_FILE_PATH = path.join(
`${__dirname}/support`,
'project_controller_osquery_roles.yml'
);
const roleDefinitions = loadYaml(readFileSync(ROLES_YAML_FILE_PATH, 'utf8')) as YamlRoleDefinitions;
export default defineCypressConfig({
reporter: '../../../node_modules/cypress-multi-reporters',
reporterOptions: {
configFile: './cypress/reporter_config.json',
},
defaultCommandTimeout: 60000,
execTimeout: 120000,
pageLoadTimeout: 12000,
retries: {
runMode: 1,
openMode: 0,
},
screenshotsFolder: '../../../target/kibana-osquery/cypress/screenshots',
trashAssetsBeforeRuns: false,
video: true,
videosFolder: '../../../target/kibana-osquery/cypress/videos',
videoCompression: 15,
viewportHeight: 900,
viewportWidth: 1440,
experimentalStudio: true,
env: {
grepFilterSpecs: true,
grepTags: '@ess',
grepOmitFiltered: true,
},
e2e: {
specPattern: './cypress/e2e/**/*.cy.ts',
baseUrl: 'http://localhost:5601',
experimentalRunAllSpecs: true,
experimentalMemoryManagement: true,
numTestsKeptInMemory: 3,
setupNodeEvents(on, config) {
setupUserDataLoader(on, config, { roleDefinitions, additionalRoleName: 'viewer' });
on('after:spec', getFailedSpecVideos);
return config;
export default defineCypressConfig(
getCypressBaseConfig({
env: {
grepTags: '@ess',
},
},
});
e2e: {
baseUrl: 'http://localhost:5601',
},
})
);

View file

@ -0,0 +1,70 @@
/*
* 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 { merge } from 'lodash';
import path from 'path';
import { safeLoad as loadYaml } from 'js-yaml';
import { readFileSync } from 'fs';
import type { YamlRoleDefinitions } from '@kbn/test-suites-serverless/shared/lib';
import { setupUserDataLoader } from '@kbn/test-suites-serverless/functional/test_suites/security/cypress/support/setup_data_loader_tasks';
import { samlAuthentication } from '@kbn/security-solution-plugin/public/management/cypress/support/saml_authentication';
import { getFailedSpecVideos } from './support/filter_videos';
const ROLES_YAML_FILE_PATH = path.join(
`${__dirname}/support`,
'project_controller_osquery_roles.yml'
);
const roleDefinitions = loadYaml(readFileSync(ROLES_YAML_FILE_PATH, 'utf8')) as YamlRoleDefinitions;
export const getCypressBaseConfig = (
overrides: Cypress.ConfigOptions = {}
): Cypress.ConfigOptions =>
merge(
{
reporter: '../../../node_modules/cypress-multi-reporters',
reporterOptions: {
configFile: './cypress/reporter_config.json',
},
defaultCommandTimeout: 60000,
execTimeout: 120000,
pageLoadTimeout: 12000,
screenshotsFolder: '../../../target/kibana-osquery/cypress/screenshots',
trashAssetsBeforeRuns: false,
video: true,
videosFolder: '../../../target/kibana-osquery/cypress/videos',
retries: {
runMode: 1,
openMode: 0,
},
videoCompression: 15,
viewportHeight: 900,
viewportWidth: 1440,
experimentalStudio: true,
env: {
grepFilterSpecs: true,
grepOmitFiltered: true,
},
e2e: {
specPattern: './cypress/e2e/**/*.cy.ts',
experimentalRunAllSpecs: true,
experimentalMemoryManagement: true,
numTestsKeptInMemory: 3,
setupNodeEvents(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
setupUserDataLoader(on, config, { roleDefinitions, additionalRoleName: 'viewer' });
samlAuthentication(on, config);
on('after:spec', getFailedSpecVideos);
return config;
},
},
},
overrides
);

View file

@ -30,11 +30,9 @@ describe('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, () =>
let packId: string;
let packName: string;
const packData = packFixture();
before(() => {
initializeDataViews();
});
beforeEach(() => {
initializeDataViews();
loadPack(packData).then((data) => {
packId = data.saved_object_id;
packName = data.name;
@ -89,13 +87,13 @@ describe('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, () =>
describe('Case', () => {
let caseId: string;
before(() => {
beforeEach(() => {
loadCase('securitySolution').then((data) => {
caseId = data.id;
});
});
after(() => {
afterEach(() => {
cleanupCase(caseId);
});

View file

@ -21,7 +21,7 @@ import { RESULTS_TABLE, RESULTS_TABLE_BUTTON } from '../../screens/live_query';
describe(
'Alert Event Details',
{
tags: ['@ess', '@serverless'],
tags: ['@ess', '@serverless', '@skipInServerlessMKI'],
},
() => {
let ruleId: string;

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { ServerlessRoleName } from '../../support/roles';
import { initializeDataViews } from '../../tasks/login';
import {
addLiveQueryToCase,
@ -13,7 +14,6 @@ import {
} from '../../tasks/live_query';
import { navigateTo } from '../../tasks/navigation';
import { loadLiveQuery, loadCase, cleanupCase } from '../../tasks/api_fixtures';
import { ServerlessRoleName } from '../../support/roles';
describe('Add to Cases', () => {
let liveQueryId: string;
@ -38,7 +38,7 @@ describe('Add to Cases', () => {
caseId = caseInfo.id;
caseTitle = caseInfo.title;
});
cy.login(ServerlessRoleName.SOC_MANAGER);
cy.login(ServerlessRoleName.SOC_MANAGER, false);
navigateTo('/app/osquery');
});
@ -70,7 +70,7 @@ describe('Add to Cases', () => {
caseId = caseInfo.id;
caseTitle = caseInfo.title;
});
cy.login(ServerlessRoleName.SOC_MANAGER);
cy.login(ServerlessRoleName.SOC_MANAGER, false);
navigateTo('/app/osquery');
});

View file

@ -17,15 +17,10 @@ import {
typeInECSFieldInput,
typeInOsqueryFieldInput,
} from '../../tasks/live_query';
import { ServerlessRoleName } from '../../support/roles';
describe('EcsMapping', { tags: ['@ess', '@serverless'] }, () => {
before(() => {
initializeDataViews();
});
beforeEach(() => {
cy.login(ServerlessRoleName.SOC_MANAGER);
initializeDataViews();
});
it('should properly show static values in form and results', () => {

View file

@ -10,7 +10,7 @@ import { checkOsqueryResponseActionsPermissions } from '../../tasks/response_act
describe(
'App Features for Security Complete PLI',
{
tags: ['@serverless'],
tags: ['@serverless', '@skipInServerlessMKI'],
env: {
ftrConfig: {
productTypes: [{ product_line: 'security', product_tier: 'complete' }],

View file

@ -12,7 +12,12 @@ describe(
{
tags: ['@serverless'],
env: {
ftrConfig: { productTypes: [{ product_line: 'security', product_tier: 'essentials' }] },
ftrConfig: {
productTypes: [
{ product_line: 'security', product_tier: 'essentials' },
{ product_line: 'endpoint', product_tier: 'essentials' },
],
},
},
},
() => checkOsqueryResponseActionsPermissions(false)

View file

@ -6,43 +6,19 @@
*/
import { defineCypressConfig } from '@kbn/cypress-config';
import { setupUserDataLoader } from '@kbn/test-suites-serverless/functional/test_suites/security/cypress/support/setup_data_loader_tasks';
import { getFailedSpecVideos } from './support/filter_videos';
import { getCypressBaseConfig } from './cypress_base.config';
// eslint-disable-next-line import/no-default-export
export default defineCypressConfig({
reporter: '../../../node_modules/cypress-multi-reporters',
reporterOptions: {
configFile: './cypress/reporter_config.json',
},
export default defineCypressConfig(
getCypressBaseConfig({
execTimeout: 60000,
pageLoadTimeout: 60000,
responseTimeout: 60000,
viewportHeight: 946,
viewportWidth: 1680,
defaultCommandTimeout: 60000,
execTimeout: 60000,
pageLoadTimeout: 60000,
responseTimeout: 60000,
screenshotsFolder: '../../../target/kibana-osquery/cypress/screenshots',
trashAssetsBeforeRuns: false,
video: true,
videosFolder: '../../../target/kibana-osquery/cypress/videos',
videoCompression: 15,
viewportHeight: 946,
viewportWidth: 1680,
env: {
grepFilterSpecs: true,
grepTags: '@serverless --@brokenInServerless',
grepOmitFiltered: true,
},
e2e: {
specPattern: './cypress/e2e/**/*.cy.ts',
experimentalRunAllSpecs: true,
experimentalMemoryManagement: true,
numTestsKeptInMemory: 3,
setupNodeEvents: (on, config) => {
setupUserDataLoader(on, config, { additionalRoleName: 'viewer' });
on('after:spec', getFailedSpecVideos);
return config;
env: {
grepTags: '@serverless --@brokenInServerless --@skipInServerless',
},
},
});
})
);

View file

@ -0,0 +1,27 @@
/*
* 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 { defineCypressConfig } from '@kbn/cypress-config';
import { getCypressBaseConfig } from './cypress_base.config';
// eslint-disable-next-line import/no-default-export
export default defineCypressConfig(
getCypressBaseConfig({
execTimeout: 60000,
pageLoadTimeout: 60000,
responseTimeout: 60000,
viewportHeight: 946,
viewportWidth: 1680,
env: {
grepTags: '@serverless --@skipInServerless --@brokenInServerless --@skipInServerlessMKI',
},
e2e: {
experimentalCspAllowList: ['default-src', 'script-src', 'script-src-elem'],
},
})
);

View file

@ -37,7 +37,7 @@ import { login } from '@kbn/security-solution-plugin/public/management/cypress/t
import type { ServerlessRoleName } from './roles';
import { waitUntil } from '../tasks/wait_until';
import { isServerless } from '../tasks/serverless';
import { isCloudServerless, isServerless } from '../tasks/serverless';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
@ -57,7 +57,7 @@ declare global {
clickOutside(): Chainable<JQuery<HTMLBodyElement>>;
login(role: ServerlessRoleName): void;
login(role: ServerlessRoleName, useCookiesForMKI?: boolean): void;
waitUntil(fn: () => Cypress.Chainable): Cypress.Chainable | undefined;
}
@ -78,8 +78,15 @@ Cypress.Commands.add(
() => cy.get('body').click(0, 0) // 0,0 here are the x and y coordinates
);
Cypress.Commands.add('login', (role) => {
if (isServerless) {
Cypress.Commands.add('login', (role, useCookiesForMKI = true) => {
// MKI does not support multiple logins throughout the test suite using cookies.
// Until a better alternative is found, we will prevent multiple logins in the MKI environment in test suites.
if (isCloudServerless && !useCookiesForMKI) {
return;
}
if (isServerless && !isCloudServerless) {
// Do not use login.with in MKI env, default to login which will route to proper login method
return login.with(role, 'changeme');
}

View file

@ -175,7 +175,7 @@ export const loadLiveQuery = (
}).then((response) => response.body.data);
export const loadRule = (includeResponseActions = false) => {
cy.login(ServerlessRoleName.SOC_MANAGER);
cy.login(ServerlessRoleName.SOC_MANAGER, false);
return request<RuleResponse>({
method: 'POST',

View file

@ -8,8 +8,8 @@
import { waitForAlertsToPopulate } from '@kbn/test-suites-xpack/security_solution_cypress/cypress/tasks/create_new_rule';
import { disableNewFeaturesTours } from './navigation';
import { getAdvancedButton } from '../screens/integrations';
import { ServerlessRoleName } from '../support/roles';
import { LIVE_QUERY_EDITOR, OSQUERY_FLYOUT_BODY_EDITOR } from '../screens/live_query';
import { ServerlessRoleName } from '../support/roles';
export const DEFAULT_QUERY = 'select * from processes;';
export const BIG_QUERY = 'select * from processes, users limit 110;';
@ -118,7 +118,7 @@ export const toggleRuleOffAndOn = (ruleName: string) => {
};
export const loadRuleAlerts = (ruleName: string) => {
cy.login(ServerlessRoleName.SOC_MANAGER);
cy.login(ServerlessRoleName.SOC_MANAGER, false);
cy.visit('/app/security/rules', {
onBeforeLoad: (win) => disableNewFeaturesTours(win),
});

View file

@ -6,3 +6,4 @@
*/
export const isServerless = Cypress.env().IS_SERVERLESS;
export const isCloudServerless = Cypress.env().CLOUD_SERVERLESS;

View file

@ -4,6 +4,7 @@
"**/*",
"./cypress.config.ts",
"./serverless_cypress.config.ts",
"./serverless_cypress_qa.config.ts",
"../../../test_serverless/shared/lib",
],
"exclude": [

View file

@ -13,6 +13,8 @@
"cypress:serverless": "NODE_OPTIONS=--openssl-legacy-provider node ../security_solution/scripts/start_cypress_parallel --config-file ../osquery/cypress/serverless_cypress.config.ts --ftr-config-file ../../../x-pack/test/osquery_cypress/serverless_cli_config",
"cypress:serverless:open": "yarn cypress:serverless open",
"cypress:serverless:run": "yarn cypress:serverless run",
"cypress:qa:serverless": "NODE_OPTIONS=--openssl-legacy-provider node ../security_solution/scripts/start_cypress_parallel_serverless --config-file ../osquery/cypress/serverless_cypress_qa.config.ts --onBeforeHook ../../test/osquery_cypress/runner_qa.ts",
"cypress:qa:serverless:run": "yarn cypress:qa:serverless run",
"nyc": "../../../node_modules/.bin/nyc report --reporter=text-summary",
"junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-osquery/cypress/results/mochawesome*.json > ../../../target/kibana-osquery/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-osquery/cypress/results/output.json --reportDir ../../../target/kibana-osquery/cypress/results && yarn junit:transform && mkdir -p ../../../target/junit && cp ../../../target/kibana-osquery/cypress/results/*.xml ../../../target/junit/",
"junit:transform": "node ../security_solution/scripts/junit_transformer --pathPattern '../../../target/kibana-osquery/cypress/results/*.xml' --rootDirectory ../../../ --reportName 'Osquery Cypress' --writeInPlace",

View file

@ -28,7 +28,7 @@ import { INITIAL_REST_VERSION } from '@kbn/data-views-plugin/server/constants';
import { catchAxiosErrorFormatAndThrow } from '../../common/endpoint/format_axios_error';
import { createToolingLogger } from '../../common/endpoint/data_loaders/utils';
import { renderSummaryTable } from './print_run';
import { parseTestFileConfig, retrieveIntegrations } from './utils';
import { getOnBeforeHook, parseTestFileConfig, retrieveIntegrations } from './utils';
import { prefixedOutputLogger } from '../endpoint/common/utils';
import type { ProductType, Credentials, ProjectHandler } from './project_handler/project_handler';
@ -327,6 +327,12 @@ export const cli = () => {
alias: 'c',
type: 'string',
default: '',
})
.option('onBeforeHook', {
// Execute a hook before running the tests with cypress.open/run
alias: 'b',
type: 'string',
default: '',
});
log.info(`
@ -527,6 +533,15 @@ ${JSON.stringify(cypressConfigFile, null, 2)}
}
process.env.TEST_CLOUD_HOST_NAME = new URL(BASE_ENV_URL).hostname;
// If provided, execute the onBeforeHook directly before running the tests once everything is set up
if (argv.onBeforeHook) {
const onBeforeFilePath = require.resolve(`../../${argv.onBeforeHook}`) as string;
const module: unknown = await import(onBeforeFilePath);
const onBeforeHook = getOnBeforeHook(module, onBeforeFilePath);
await onBeforeHook(cyCustomEnv);
}
if (isOpen) {
await cypress.open({
configFile: cypressConfigFilePath,

View file

@ -11,6 +11,7 @@ import * as parser from '@babel/parser';
import generate from '@babel/generator';
import type { ExpressionStatement, ObjectExpression, ObjectProperty } from '@babel/types';
import { schema, type TypeOf } from '@kbn/config-schema';
import chalk from 'chalk';
/**
* Retrieve test files using a glob pattern.
@ -135,3 +136,23 @@ const TestFileFtrConfigSchema = schema.object(
);
export type SecuritySolutionDescribeBlockFtrConfig = TypeOf<typeof TestFileFtrConfigSchema>;
export const getOnBeforeHook = (module: unknown, beforeSpecFilePath: string): Function => {
if (typeof module !== 'object' || module === null) {
throw new Error(
`${chalk.bold(
beforeSpecFilePath
)} expected to explicitly export function member named "onBeforeHook"`
);
}
if (!('onBeforeHook' in module) || typeof module.onBeforeHook !== 'function') {
throw new Error(
`${chalk.bold('onBeforeHook')} exported from ${chalk.bold(
beforeSpecFilePath
)} is not a function`
);
}
return module.onBeforeHook;
};

View file

@ -13,16 +13,17 @@ import {
waitForHostToEnroll,
} from '@kbn/security-solution-plugin/scripts/endpoint/common/fleet_services';
import chalk from 'chalk';
import { getLatestVersion } from './artifact_manager';
import { Manager } from './resource_manager';
import { generateRandomString } from './utils';
export class AgentManager extends Manager {
private log: ToolingLog;
private policyEnrollmentKey: string;
private fleetServerPort: string;
private readonly log: ToolingLog;
private readonly policyEnrollmentKey: string;
private readonly fleetServerPort: string;
private readonly kbnClient: KbnClient;
private agentContainerId?: string;
private kbnClient: KbnClient;
constructor(
policyEnrollmentKey: string,
@ -38,14 +39,15 @@ export class AgentManager extends Manager {
}
public async setup() {
this.log.info('Running agent preconfig');
this.log.info(chalk.bold('Setting up Agent'));
const artifact = `docker.elastic.co/beats/elastic-agent:${await getLatestVersion()}`;
this.log.info(artifact);
this.log.indent(4, () => this.log.info(`Image: ${artifact}`));
const containerName = generateRandomString(12);
const fleetServerUrl =
(await fetchFleetServerUrl(this.kbnClient)) ??
`https://host.docker.internal:${this.fleetServerPort}`;
this.log.indent(4, () => this.log.info(`Fleet Server: ${fleetServerUrl}`));
const dockerArgs = [
'run',
@ -72,24 +74,31 @@ export class AgentManager extends Manager {
const startedContainer = await execa('docker', dockerArgs);
this.log.info(`agent docker container started:\n${JSON.stringify(startedContainer, null, 2)}`);
this.log.debug(`Agent docker container started:\n${JSON.stringify(startedContainer, null, 2)}`);
this.agentContainerId = startedContainer.stdout;
this.log.indent(4, () =>
this.log.info(`Agent container started with id ${this.agentContainerId}`)
);
await waitForHostToEnroll(this.kbnClient, this.log, containerName, 240000);
}
public cleanup() {
super.cleanup();
this.log.info('Cleaning up the agent process');
this.log.info(chalk.bold('Cleaning up the agent process'));
if (this.agentContainerId) {
this.log.info('Closing fleet process');
this.log.indent(4, () =>
this.log.info(`Stopping and removing agent [${this.agentContainerId}] container`)
);
try {
execa.sync('docker', ['kill', this.agentContainerId]);
} catch (err) {
this.log.error('Error closing fleet agent process');
}
this.log.info('Fleet agent process closed');
this.log.indent(4, () =>
this.log.info(`Stopped and removed agent [${this.agentContainerId}] container`)
);
}
}
}

View file

@ -0,0 +1,28 @@
/*
* 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 { prefixedOutputLogger } from '@kbn/security-solution-plugin/scripts/endpoint/common/utils';
import { createToolingLogger } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/utils';
import { setupStackServicesUsingCypressConfig } from '@kbn/security-solution-plugin/public/management/cypress/support/common';
import { maybeCreateDockerNetwork, verifyDockerInstalled } from '@kbn/es';
import { AgentManager } from './agent';
import { createAgentPolicy } from './utils';
export async function onBeforeHook(config: Record<string, string | number | boolean | undefined>) {
const log = prefixedOutputLogger('cy.parallel(svl).onBeforeHook', createToolingLogger());
const stackServicesPromise = setupStackServicesUsingCypressConfig({ env: config });
const { kbnClient } = await stackServicesPromise;
await verifyDockerInstalled(log);
await maybeCreateDockerNetwork(log);
const policyEnrollmentKey = await createAgentPolicy(kbnClient, log, 'Default policy');
const policyEnrollmentKeyTwo = await createAgentPolicy(kbnClient, log, 'Osquery policy');
// For MKI, we need to fetch the fleet server URL. Therefore, we pass '0000' as the fleet server port, which will be ignored.
await new AgentManager(policyEnrollmentKey, '0000', log, kbnClient).setup();
await new AgentManager(policyEnrollmentKeyTwo, '0000', log, kbnClient).setup();
}

View file

@ -15,6 +15,7 @@ import {
CreateAgentPolicyResponse,
} from '@kbn/fleet-plugin/common/types';
import { ToolingLog } from '@kbn/tooling-log';
import chalk from 'chalk';
export const DEFAULT_HEADERS = Object.freeze({
'x-elastic-internal-product': 'security-solution',
@ -38,10 +39,10 @@ export const getInstalledIntegration = async (kbnClient: KbnClient, integrationN
export const createAgentPolicy = async (
kbnClient: KbnClient,
log: ToolingLog,
agentPolicyName = 'Osquery policy'
agentPolicyName = 'Osquery policy',
integrationName: string = 'osquery_manager'
) => {
log.info(`Creating "${agentPolicyName}" agent policy`);
log.info(chalk.bold(`Creating "${agentPolicyName}" agent policy`));
const {
data: {
item: { id: agentPolicyId },
@ -60,12 +61,26 @@ export const createAgentPolicy = async (
inactivity_timeout: 1209600,
},
});
log.indent(4, () => log.info(`Created "${agentPolicyName}" agent policy`));
log.info(`Adding integration to ${agentPolicyId}`);
log.info(
chalk.bold(
`Adding "${integrationName}" integration to agent policy "${agentPolicyName}" with id ${agentPolicyId}`
)
);
await addIntegrationToAgentPolicy(kbnClient, agentPolicyId, agentPolicyName);
await addIntegrationToAgentPolicy(kbnClient, agentPolicyId, agentPolicyName, integrationName);
log.indent(4, () =>
log.info(
`Added "${integrationName}" integration to agent policy "${agentPolicyName}" with id ${agentPolicyId}`
)
);
log.info('Getting agent enrollment key');
log.info(
chalk.bold(
`Getting agent enrollment key for agent policy "${agentPolicyName}" with id ${agentPolicyId}`
)
);
const { data: apiKeys } = await kbnClient.request<GetEnrollmentAPIKeysResponse>({
method: 'GET',
headers: {
@ -73,7 +88,11 @@ export const createAgentPolicy = async (
},
path: '/api/fleet/enrollment_api_keys',
});
log.indent(4, () =>
log.info(
`Got agent enrollment key for agent policy "${agentPolicyName}" with id ${agentPolicyId}`
)
);
return apiKeys.items[0].api_key;
};
@ -119,7 +138,7 @@ const isValidArtifactVersion = (version: string) => !!version.match(/^\d+\.\d+\.
/**
* Returns the Agent version that is available for install (will check `artifacts-api.elastic.co/v1/versions`)
* that is equal to or less than `maxVersion`.
* @param maxVersion
* @param kbnClient
*/
export const getLatestAvailableAgentVersion = async (kbnClient: KbnClient): Promise<string> => {

View file

@ -177,6 +177,7 @@
"@kbn/entities-schema",
"@kbn/actions-simulators-plugin",
"@kbn/cases-api-integration-test-plugin",
"@kbn/security-solution-plugin/public/management/cypress",
"@kbn/management-settings-ids",
"@kbn/mock-idp-utils"
]