Skip to content

Commit

Permalink
[Security Solution][Endpoint] several refactors of CLI tooling and as…
Browse files Browse the repository at this point in the history
…sociated common services (#169987)

## Summary

PR makes a series of refactors to CLI scripts and common services used
in CLI scripts and CI, including:

- Standard interface for interacting with Host VMs that abstracts away
the need to know what VM manager was used to start that VM
- Reduce/eliminate the need to have conditional code when interacting
directly with a VM (ex. executing bash commands, stop/kill/delete VM,
etc)
- Removed use of `endpoint_agent_runner` (CLI script) private
implementation methods from Cypress and replace them with calls to
common services
- Removed duplicate code from `endpoint_agent_runner` CLI script and
replace it with calls to common services
- Enhanced the `run_sentinelone_host.js` script so that it also ensures
that the SentinenlOne fleet integration/policy (agentless policy) has at
least one VM host running
    - The VM ensures that the data from S1 is pulled into ES
- FYI: once changes for SentinelOne are merged and the Connector
available, script will also be updated to create an SentinelOne
connector instance under `"Stack Management > Connectors"`
- Added support for `WITH_FLEET_SERVER` to the Cypress config. When set
to `true`, fleet server will be automatically started and connected to
the stack
- Cypress parallel runner will now start fleet if this variable is true,
right after setting up the stack
  • Loading branch information
paul-tavares authored Nov 13, 2023
1 parent 49b4882 commit 8613b0f
Show file tree
Hide file tree
Showing 36 changed files with 975 additions and 1,254 deletions.
2 changes: 1 addition & 1 deletion src/dev/precommit_hook/casing_check_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const IGNORE_FILE_GLOBS = [
'packages/kbn-test/jest-preset.js',
'packages/kbn-test/*/jest-preset.js',
'test/package/Vagrantfile',
'x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/Vagrantfile',
'x-pack/plugins/security_solution/scripts/endpoint/common/vagrant/Vagrantfile',
'**/test/**/fixtures/**/*',

// Required to match the name in the docs.elastic.dev repo.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { mergeWith } from 'lodash';
import type { ToolingLogTextWriterConfig } from '@kbn/tooling-log';
import { ToolingLog } from '@kbn/tooling-log';
import type { Flags } from '@kbn/dev-cli-runner';

export const RETRYABLE_TRANSIENT_ERRORS: Readonly<Array<string | RegExp>> = [
'no_shard_available_action_exception',
Expand Down Expand Up @@ -117,12 +118,20 @@ interface CreateLoggerInterface {
* 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`.
* Log level can also be explicitly set on input.
* 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
*
Expand All @@ -137,3 +146,14 @@ export const createToolingLogger: CreateLoggerInterface = (level): ToolingLog =>
});
};
createToolingLogger.defaultLogLevel = 'info';
createToolingLogger.setDefaultLogLevelFromCliFlags = (flags) => {
createToolingLogger.defaultLogLevel = flags.verbose
? 'verbose'
: flags.debug
? 'debug'
: flags.silent
? 'silent'
: flags.quiet
? 'error'
: 'info';
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type {
CreateUserAndRoleCyTaskOptions,
UninstallAgentFromHostTaskOptions,
IsAgentAndEndpointUninstalledFromHostTaskOptions,
LogItTaskOptions,
} from './types';
import type {
DeleteIndexedFleetEndpointPoliciesResponse,
Expand Down Expand Up @@ -86,13 +87,15 @@ declare global {
* 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>;

task(
Expand Down Expand Up @@ -217,6 +220,12 @@ declare global {
arg: IsAgentAndEndpointUninstalledFromHostTaskOptions,
options?: Partial<Loggable & Timeoutable>
): Chainable<boolean>;

task(
name: 'logIt',
arg: LogItTaskOptions,
options?: Partial<Loggable & Timeoutable>
): Chainable<null>;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ export const getCypressBaseConfig = (
// to `debug` or `verbose` when wanting to debug tooling used by tests (ex. data indexer functions).
TOOLING_LOG_LEVEL: 'info',

// Variable works in conjunction with the Cypress parallel runner. When set to true, fleet server
// will be setup right after the Kibana stack, so that by the time cypress tests `.run()`/`.open()`,
// the env. will be all setup and we don't have to explicitly setup fleet from a test file
WITH_FLEET_SERVER: true,

// grep related configs
grepFilterSpecs: true,
grepOmitFiltered: true,
Expand All @@ -69,11 +74,12 @@ export const getCypressBaseConfig = (
experimentalRunAllSpecs: true,
experimentalMemoryManagement: true,
experimentalInteractiveRunEvents: true,
setupNodeEvents: (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => {
setupNodeEvents: async (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => {
// IMPORTANT: setting the log level should happen before any tooling is called
setupToolingLogLevel(config);

dataLoaders(on, config);

// Data loaders specific to "real" Endpoint testing
dataLoadersForRealEndpoints(on, config);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
createAgentPolicyTask,
enableAgentTamperProtectionFeatureFlagInPolicy,
unenrollAgent,
changeAgentPolicy,
reAssignFleetAgentToPolicy,
} from '../../../tasks/fleet';

import { login } from '../../../tasks/login';
Expand Down Expand Up @@ -79,10 +79,9 @@ describe(
it('should unenroll from fleet without issues', () => {
waitForEndpointListPageToBeLoaded(createdHost.hostname);
// Change agent policy and wait for action to be completed
changeAgentPolicy(
reAssignFleetAgentToPolicy(
createdHost.agentId,
policyWithAgentTamperProtectionEnabled.policy_id,
3
policyWithAgentTamperProtectionEnabled.policy_id
).then((hasChanged) => {
expect(hasChanged).to.eql(true);
unenrollAgent(createdHost.agentId).then((isUnenrolled) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
createAgentPolicyTask,
enableAgentTamperProtectionFeatureFlagInPolicy,
unenrollAgent,
changeAgentPolicy,
reAssignFleetAgentToPolicy,
} from '../../../tasks/fleet';

import { login } from '../../../tasks/login';
Expand Down Expand Up @@ -79,7 +79,7 @@ describe(
it('should unenroll from fleet without issues', () => {
waitForEndpointListPageToBeLoaded(createdHost.hostname);
// Change agent policy and wait for action to be completed
changeAgentPolicy(createdHost.agentId, policy.policy_id, 3).then((hasChanged) => {
reAssignFleetAgentToPolicy(createdHost.agentId, policy.policy_id).then((hasChanged) => {
expect(hasChanged).to.eql(true);
unenrollAgent(createdHost.agentId).then((isUnenrolled) => {
expect(isUnenrolled).to.eql(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
createAgentPolicyTask,
enableAgentTamperProtectionFeatureFlagInPolicy,
unenrollAgent,
changeAgentPolicy,
reAssignFleetAgentToPolicy,
} from '../../../tasks/fleet';

import { login } from '../../../tasks/login';
Expand Down Expand Up @@ -81,10 +81,9 @@ describe(
it('should unenroll from fleet without issues', () => {
waitForEndpointListPageToBeLoaded(createdHost.hostname);
// Change agent policy and wait for action to be completed
changeAgentPolicy(
reAssignFleetAgentToPolicy(
createdHost.agentId,
secondPolicyWithAgentTamperProtectionEnabled.policy_id,
3
secondPolicyWithAgentTamperProtectionEnabled.policy_id
).then((hasChanged) => {
expect(hasChanged).to.eql(true);
unenrollAgent(createdHost.agentId).then((isUnenrolled) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
createAgentPolicyTask,
enableAgentTamperProtectionFeatureFlagInPolicy,
getUninstallToken,
changeAgentPolicy,
reAssignFleetAgentToPolicy,
isAgentAndEndpointUninstalledFromHost,
uninstallAgentFromHost,
} from '../../../tasks/fleet';
Expand Down Expand Up @@ -82,10 +82,9 @@ describe(
waitForEndpointListPageToBeLoaded(createdHost.hostname);

// Change agent policy and wait for action to be completed
changeAgentPolicy(
reAssignFleetAgentToPolicy(
createdHost.agentId,
policyWithAgentTamperProtectionEnabled.policy_id,
3
policyWithAgentTamperProtectionEnabled.policy_id
).then((hasChanged) => {
expect(hasChanged).to.eql(true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getEndpointIntegrationVersion,
createAgentPolicyTask,
enableAgentTamperProtectionFeatureFlagInPolicy,
changeAgentPolicy,
reAssignFleetAgentToPolicy,
isAgentAndEndpointUninstalledFromHost,
uninstallAgentFromHost,
} from '../../../tasks/fleet';
Expand Down Expand Up @@ -81,7 +81,7 @@ describe.skip(
it('should uninstall from host without issues', () => {
waitForEndpointListPageToBeLoaded(createdHost.hostname);

changeAgentPolicy(createdHost.agentId, policy.policy_id, 3).then((hasChanged) => {
reAssignFleetAgentToPolicy(createdHost.agentId, policy.policy_id).then((hasChanged) => {
expect(hasChanged).to.eql(true);
uninstallAgentFromHost(createdHost.hostname).then((responseWithoutToken) => {
expect(responseWithoutToken).to.not.match(/(.*)Invalid uninstall token(.*)/);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
createAgentPolicyTask,
enableAgentTamperProtectionFeatureFlagInPolicy,
getUninstallToken,
changeAgentPolicy,
reAssignFleetAgentToPolicy,
isAgentAndEndpointUninstalledFromHost,
uninstallAgentFromHost,
} from '../../../tasks/fleet';
Expand Down Expand Up @@ -85,10 +85,9 @@ describe(
waitForEndpointListPageToBeLoaded(createdHost.hostname);

// Change agent policy and wait for action to be completed
changeAgentPolicy(
reAssignFleetAgentToPolicy(
createdHost.agentId,
secondPolicyWithAgentTamperProtectionEnabled.policy_id,
3
secondPolicyWithAgentTamperProtectionEnabled.policy_id
).then((hasChanged) => {
expect(hasChanged).to.eql(true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
*/

// / <reference types="cypress" />
import type { ExecaReturnValue } from 'execa';
import execa from 'execa';

import { VAGRANT_CWD } from '../../../../scripts/endpoint/common/endpoint_host_services';
import { getHostVmClient } from '../../../../scripts/endpoint/common/vm_services';

export const agentActions = (on: Cypress.PluginEvents): void => {
on('task', {
Expand All @@ -20,40 +18,19 @@ export const agentActions = (on: Cypress.PluginEvents): void => {
hostname: string;
uninstallToken?: string;
}): Promise<string> => {
let result;
const hostVmClient = getHostVmClient(hostname);

try {
if (process.env.CI) {
result = await execa(
'vagrant',
[
'ssh',
'--',
`sudo elastic-agent uninstall -f ${
uninstallToken ? `--uninstall-token ${uninstallToken}` : ''
}`,
],
{
env: {
VAGRANT_CWD,
},
}
);
} else {
result = await execa(`multipass`, [
'exec',
hostname,
'--',
'sh',
'-c',
return (
await hostVmClient.exec(
`sudo elastic-agent uninstall -f ${
uninstallToken ? `--uninstall-token ${uninstallToken}` : ''
}`,
]);
}
}`
)
).stdout;
} catch (err) {
return err.stderr;
}
return result.stdout;
},

isAgentAndEndpointUninstalledFromHost: async ({
Expand All @@ -62,25 +39,10 @@ export const agentActions = (on: Cypress.PluginEvents): void => {
hostname: string;
uninstallToken?: string;
}): Promise<boolean> => {
let execaReturnValue: ExecaReturnValue<string>;
if (process.env.CI) {
execaReturnValue = await execa('vagrant', ['ssh', '--', `ls /opt/Elastic`], {
env: {
VAGRANT_CWD,
},
});
} else {
execaReturnValue = await execa(`multipass`, [
'exec',
hostname,
'--',
'sh',
'-c',
`ls /opt/Elastic`,
]);
}
const hostVmClient = getHostVmClient(hostname);
const lsOutput = await hostVmClient.exec('ls /opt/Elastic');

if (execaReturnValue.stdout === '') {
if (lsOutput.stdout === '') {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 '../../../../scripts/endpoint/common/utils';
import type { RuntimeServices } from '../../../../scripts/endpoint/common/stack_services';
import { createRuntimeServices } from '../../../../scripts/endpoint/common/stack_services';

const RUNTIME_SERVICES_CACHE = new WeakMap<Cypress.PluginConfigOptions, RuntimeServices>();

export const setupStackServicesUsingCypressConfig = async (config: Cypress.PluginConfigOptions) => {
if (RUNTIME_SERVICES_CACHE.has(config)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return RUNTIME_SERVICES_CACHE.get(config)!;
}

const stackServices = await createRuntimeServices({
kibanaUrl: config.env.KIBANA_URL,
elasticsearchUrl: config.env.ELASTICSEARCH_URL,
fleetServerUrl: config.env.FLEET_SERVER_URL,
username: config.env.KIBANA_USERNAME,
password: config.env.KIBANA_PASSWORD,
esUsername: config.env.ELASTICSEARCH_USERNAME,
esPassword: config.env.ELASTICSEARCH_PASSWORD,
asSuperuser: true,
}).then(({ log, ...others }) => {
return {
...others,
log: prefixedOutputLogger('cy.dfw', log),
};
});

RUNTIME_SERVICES_CACHE.set(config, stackServices);

return stackServices;
};
Loading

0 comments on commit 8613b0f

Please sign in to comment.