diff --git a/docs/developer/contributing/development-package-tests.asciidoc b/docs/developer/contributing/development-package-tests.asciidoc index 10c09d6cae8c0..7883ce2d83209 100644 --- a/docs/developer/contributing/development-package-tests.asciidoc +++ b/docs/developer/contributing/development-package-tests.asciidoc @@ -36,7 +36,7 @@ pip3 install --user ansible ``` # Build distributions -node scripts/build --all-platforms --debug --no-oss +node scripts/build --all-platforms --debug cd test/package diff --git a/src/dev/build/README.md b/src/dev/build/README.md index f6e11af67da33..c499ef4a61083 100644 --- a/src/dev/build/README.md +++ b/src/dev/build/README.md @@ -12,8 +12,8 @@ node scripts/build --help # build a release version node scripts/build --release -# reuse already downloaded node executables, turn on debug logging, and only build the default distributable -node scripts/build --skip-node-download --debug --no-oss +# reuse already downloaded node executables, turn on debug logging +node scripts/build --skip-node-download --debug ``` # Fixing out of memory issues diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts index e749af73241cf..555df8981d70f 100644 --- a/src/dev/build/args.test.ts +++ b/src/dev/build/args.test.ts @@ -26,8 +26,6 @@ it('build default and oss dist for current platform, without packages, by defaul expect(readCliArgs(['node', 'scripts/build'])).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, @@ -53,8 +51,6 @@ it('builds packages if --all-platforms is passed', () => { expect(readCliArgs(['node', 'scripts/build', '--all-platforms'])).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": true, @@ -80,8 +76,6 @@ it('limits packages if --rpm passed with --all-platforms', () => { expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--rpm'])).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, @@ -107,8 +101,6 @@ it('limits packages if --deb passed with --all-platforms', () => { expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--deb'])).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, @@ -135,8 +127,6 @@ it('limits packages if --docker passed with --all-platforms', () => { .toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, @@ -170,8 +160,6 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform ).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, @@ -198,8 +186,6 @@ it('limits packages if --all-platforms passed with --skip-docker-centos', () => .toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts index bbfbd3e6f8813..08a375e8011e6 100644 --- a/src/dev/build/args.ts +++ b/src/dev/build/args.ts @@ -15,8 +15,6 @@ export function readCliArgs(argv: string[]) { const unknownFlags: string[] = []; const flags = getopts(argv, { boolean: [ - 'oss', - 'no-oss', 'skip-archives', 'skip-initialize', 'skip-generic-folders', @@ -48,7 +46,6 @@ export function readCliArgs(argv: string[]) { rpm: null, deb: null, 'docker-images': null, - oss: null, 'version-qualifier': '', }, unknown: (flag) => { @@ -94,8 +91,6 @@ export function readCliArgs(argv: string[]) { const buildOptions: BuildOptions = { isRelease: Boolean(flags.release), versionQualifier: flags['version-qualifier'], - buildOssDist: flags.oss !== false, - buildDefaultDist: !flags.oss, initialize: !Boolean(flags['skip-initialize']), downloadFreshNode: !Boolean(flags['skip-node-download']), createGenericFolders: !Boolean(flags['skip-generic-folders']), diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts index f0403fac1e26b..159281ed71db0 100644 --- a/src/dev/build/build_distributables.ts +++ b/src/dev/build/build_distributables.ts @@ -13,8 +13,6 @@ import * as Tasks from './tasks'; export interface BuildOptions { isRelease: boolean; - buildOssDist: boolean; - buildDefaultDist: boolean; downloadFreshNode: boolean; initialize: boolean; createGenericFolders: boolean; @@ -37,8 +35,6 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions const run = createRunner({ config, log, - buildDefaultDist: options.buildDefaultDist, - buildOssDist: options.buildOssDist, }); /** diff --git a/src/dev/build/cli.ts b/src/dev/build/cli.ts index 3712e2230296a..c727c26d7dcd3 100644 --- a/src/dev/build/cli.ts +++ b/src/dev/build/cli.ts @@ -33,8 +33,6 @@ if (showHelp) { build the Kibana distributable options: - --oss {dim Only produce the OSS distributable of Kibana} - --no-oss {dim Only produce the default distributable of Kibana} --skip-archives {dim Don't produce tar/zip archives} --skip-os-packages {dim Don't produce rpm/deb/docker packages} --all-platforms {dim Produce archives for all platforms, not just this one} diff --git a/src/dev/build/lib/build.test.ts b/src/dev/build/lib/build.test.ts index fe328329d7df0..f92a91e2b7221 100644 --- a/src/dev/build/lib/build.test.ts +++ b/src/dev/build/lib/build.test.ts @@ -43,40 +43,24 @@ beforeEach(() => { jest.clearAllMocks(); }); -const ossBuild = new Build(config, true); -const defaultBuild = new Build(config, false); - -describe('#isOss()', () => { - it('returns true for oss', () => { - expect(ossBuild.isOss()).toBe(true); - }); - - it('returns false for default build', () => { - expect(defaultBuild.isOss()).toBe(false); - }); -}); +const defaultBuild = new Build(config); describe('#getName()', () => { it('returns kibana for default build', () => { expect(defaultBuild.getName()).toBe('kibana'); }); - - it('returns kibana-oss for oss', () => { - expect(ossBuild.getName()).toBe('kibana-oss'); - }); }); describe('#getLogTag()', () => { it('returns string with build name in it', () => { expect(defaultBuild.getLogTag()).toContain(defaultBuild.getName()); - expect(ossBuild.getLogTag()).toContain(ossBuild.getName()); }); }); describe('#resolvePath()', () => { it('uses passed config to resolve a path relative to the repo', () => { - expect(ossBuild.resolvePath('bar')).toMatchInlineSnapshot( - `/build/kibana-oss/bar` + expect(defaultBuild.resolvePath('bar')).toMatchInlineSnapshot( + `/build/kibana/bar` ); }); @@ -89,28 +73,27 @@ describe('#resolvePath()', () => { describe('#resolvePathForPlatform()', () => { it('uses config.resolveFromRepo(), config.getBuildVersion(), and platform.getBuildName() to create path', () => { - expect(ossBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot( - `/build/oss/kibana-8.0.0-linux-x86_64/foo/bar` + expect(defaultBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot( + `/build/default/kibana-8.0.0-linux-x86_64/foo/bar` ); }); }); describe('#getPlatformArchivePath()', () => { it('creates correct path for different platforms', () => { - expect(ossBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot( - `/target/kibana-oss-8.0.0-linux-x86_64.tar.gz` + expect(defaultBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot( + `/target/kibana-8.0.0-linux-x86_64.tar.gz` ); - expect(ossBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot( - `/target/kibana-oss-8.0.0-linux-aarch64.tar.gz` + expect(defaultBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot( + `/target/kibana-8.0.0-linux-aarch64.tar.gz` ); - expect(ossBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot( - `/target/kibana-oss-8.0.0-windows-x86_64.zip` + expect(defaultBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot( + `/target/kibana-8.0.0-windows-x86_64.zip` ); }); describe('#getRootDirectory()', () => { it('creates correct root directory name', () => { - expect(ossBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-oss-8.0.0"`); expect(defaultBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-8.0.0"`); }); }); diff --git a/src/dev/build/lib/build.ts b/src/dev/build/lib/build.ts index f4ccb30994eef..c777ad18dc51f 100644 --- a/src/dev/build/lib/build.ts +++ b/src/dev/build/lib/build.ts @@ -12,14 +12,10 @@ import { Config } from './config'; import { Platform } from './platform'; export class Build { - private name = this.oss ? 'kibana-oss' : 'kibana'; - private logTag = this.oss ? chalk`{magenta [kibana-oss]}` : chalk`{cyan [ kibana ]}`; + private name = 'kibana'; + private logTag = chalk`{cyan [ kibana ]}`; - constructor(private config: Config, private oss: boolean) {} - - isOss() { - return !!this.oss; - } + constructor(private config: Config) {} resolvePath(...args: string[]) { return this.config.resolveFromRepo('build', this.name, ...args); @@ -28,7 +24,7 @@ export class Build { resolvePathForPlatform(platform: Platform, ...args: string[]) { return this.config.resolveFromRepo( 'build', - this.oss ? 'oss' : 'default', + 'default', `kibana-${this.config.getBuildVersion()}-${platform.getBuildName()}`, ...args ); diff --git a/src/dev/build/lib/runner.test.ts b/src/dev/build/lib/runner.test.ts index 0e3c00d220ad9..2a08da2797a9d 100644 --- a/src/dev/build/lib/runner.test.ts +++ b/src/dev/build/lib/runner.test.ts @@ -45,7 +45,7 @@ beforeEach(() => { jest.clearAllMocks(); }); -const setup = async (opts: { buildDefaultDist: boolean; buildOssDist: boolean }) => { +const setup = async () => { const config = await Config.create({ isRelease: true, targetAllPlatforms: true, @@ -55,55 +55,14 @@ const setup = async (opts: { buildDefaultDist: boolean; buildOssDist: boolean }) const run = createRunner({ config, log, - ...opts, }); return { config, run }; }; -describe('buildOssDist = true, buildDefaultDist = true', () => { +describe('default dist', () => { it('runs global task once, passing config and log', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: true, - }); - - const mock = jest.fn(); - - await run({ - global: true, - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build), expect.any(Build)]); - }); - - it('calls local tasks twice, passing each build', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: true, - }); - - const mock = jest.fn(); - - await run({ - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(2); - expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); - }); -}); - -describe('just default dist', () => { - it('runs global task once, passing config and log', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: false, - }); + const { config, run } = await setup(); const mock = jest.fn(); @@ -118,52 +77,7 @@ describe('just default dist', () => { }); it('calls local tasks once, passing the default build', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: false, - }); - - const mock = jest.fn(); - - await run({ - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); - const [args] = mock.mock.calls; - const [, , build] = args; - if (build.isOss()) { - throw new Error('expected build to be the default dist, not the oss dist'); - } - }); -}); - -describe('just oss dist', () => { - it('runs global task once, passing config and log', async () => { - const { config, run } = await setup({ - buildDefaultDist: false, - buildOssDist: true, - }); - - const mock = jest.fn(); - - await run({ - global: true, - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build)]); - }); - - it('calls local tasks once, passing the oss build', async () => { - const { config, run } = await setup({ - buildDefaultDist: false, - buildOssDist: true, - }); + const { config, run } = await setup(); const mock = jest.fn(); @@ -174,20 +88,12 @@ describe('just oss dist', () => { expect(mock).toHaveBeenCalledTimes(1); expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); - const [args] = mock.mock.calls; - const [, , build] = args; - if (!build.isOss()) { - throw new Error('expected build to be the oss dist, not the default dist'); - } }); }); describe('task rejection', () => { it('rejects, logs error, and marks error logged', async () => { - const { run } = await setup({ - buildDefaultDist: true, - buildOssDist: false, - }); + const { run } = await setup(); const error = new Error('FOO'); expect(isErrorLogged(error)).toBe(false); @@ -213,10 +119,7 @@ describe('task rejection', () => { }); it('just rethrows errors that have already been logged', async () => { - const { run } = await setup({ - buildDefaultDist: true, - buildOssDist: false, - }); + const { run } = await setup(); const error = markErrorLogged(new Error('FOO')); const promise = run({ diff --git a/src/dev/build/lib/runner.ts b/src/dev/build/lib/runner.ts index 015de6fe7e9ef..1fccd884cc4f9 100644 --- a/src/dev/build/lib/runner.ts +++ b/src/dev/build/lib/runner.ts @@ -16,8 +16,6 @@ import { Config } from './config'; interface Options { config: Config; log: ToolingLog; - buildOssDist: boolean; - buildDefaultDist: boolean; } export interface GlobalTask { @@ -32,7 +30,7 @@ export interface Task { run(config: Config, log: ToolingLog, build: Build): Promise; } -export function createRunner({ config, log, buildOssDist, buildDefaultDist }: Options) { +export function createRunner({ config, log }: Options) { async function execTask(desc: string, task: Task | GlobalTask, lastArg: any) { log.info(desc); log.indent(4); @@ -63,12 +61,7 @@ export function createRunner({ config, log, buildOssDist, buildDefaultDist }: Op } const builds: Build[] = []; - if (buildDefaultDist) { - builds.push(new Build(config, false)); - } - if (buildOssDist) { - builds.push(new Build(config, true)); - } + builds.push(new Build(config)); /** * Run a task by calling its `run()` method with three arguments: diff --git a/src/dev/build/tasks/build_kibana_platform_plugins.ts b/src/dev/build/tasks/build_kibana_platform_plugins.ts index edff77d458f0f..48d249ca37409 100644 --- a/src/dev/build/tasks/build_kibana_platform_plugins.ts +++ b/src/dev/build/tasks/build_kibana_platform_plugins.ts @@ -27,7 +27,6 @@ export const BuildKibanaPlatformPlugins: Task = { repoRoot: REPO_ROOT, outputRoot: build.resolvePath(), cache: false, - oss: build.isOss(), examples: false, watch: false, dist: true, diff --git a/src/dev/build/tasks/build_packages_task.ts b/src/dev/build/tasks/build_packages_task.ts index e6305b3761a4f..808903661a595 100644 --- a/src/dev/build/tasks/build_packages_task.ts +++ b/src/dev/build/tasks/build_packages_task.ts @@ -63,7 +63,6 @@ export const BuildBazelPackages: Task = { await buildBazelProductionProjects({ kibanaRoot: config.resolveFromRepo(), buildRoot: build.resolvePath(), - onlyOSS: build.isOss(), }); }, }; @@ -75,7 +74,6 @@ export const BuildPackages: Task = { await buildNonBazelProductionProjects({ kibanaRoot: config.resolveFromRepo(), buildRoot: build.resolvePath(), - onlyOSS: build.isOss(), }); }, }; diff --git a/src/dev/build/tasks/create_archives_task.ts b/src/dev/build/tasks/create_archives_task.ts index e2d3ff7149c4c..37c4becae76a8 100644 --- a/src/dev/build/tasks/create_archives_task.ts +++ b/src/dev/build/tasks/create_archives_task.ts @@ -77,14 +77,14 @@ export const CreateArchives: Task = { const metrics: CiStatsMetric[] = []; for (const { format, path, fileCount } of archives) { metrics.push({ - group: `${build.isOss() ? 'oss ' : ''}distributable size`, + group: `distributable size`, id: format, value: (await asyncStat(path)).size, }); metrics.push({ group: 'distributable file count', - id: build.isOss() ? 'oss' : 'default', + id: 'default', value: fileCount, }); } diff --git a/src/dev/build/tasks/install_chromium.js b/src/dev/build/tasks/install_chromium.js index b21a8484fa710..37abcbad4466e 100644 --- a/src/dev/build/tasks/install_chromium.js +++ b/src/dev/build/tasks/install_chromium.js @@ -15,21 +15,17 @@ export const InstallChromium = { description: 'Installing Chromium', async run(config, log, build) { - if (build.isOss()) { - return; - } else { - for (const platform of config.getNodePlatforms()) { - log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); + for (const platform of config.getNodePlatforms()) { + log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); - const { binaryPath$ } = installBrowser( - // TODO: https://github.com/elastic/kibana/issues/72496 - log, - build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), - platform.getName(), - platform.getArchitecture() - ); - await binaryPath$.pipe(first()).toPromise(); - } + const { binaryPath$ } = installBrowser( + // TODO: https://github.com/elastic/kibana/issues/72496 + log, + build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), + platform.getName(), + platform.getArchitecture() + ); + await binaryPath$.pipe(first()).toPromise(); } }, }; diff --git a/src/dev/build/tasks/license_file_task.ts b/src/dev/build/tasks/license_file_task.ts index 7e5ed8da0a27a..ff33707476576 100644 --- a/src/dev/build/tasks/license_file_task.ts +++ b/src/dev/build/tasks/license_file_task.ts @@ -8,23 +8,13 @@ import { write, read, Task } from '../lib'; -const LICENSE_SEPARATOR = `\n------------------------------------------------------------------------\n\n`; - export const UpdateLicenseFile: Task = { description: 'Updating LICENSE.txt file', async run(config, log, build) { const elasticLicense = await read(config.resolveFromRepo('licenses/ELASTIC-LICENSE-2.0.txt')); - if (build.isOss()) { - const ssplLicense = await read(config.resolveFromRepo('licenses/SSPL-LICENSE.txt')); - log.info('Copying dual-license to LICENSE.txt'); - await write( - build.resolvePath('LICENSE.txt'), - ssplLicense + LICENSE_SEPARATOR + elasticLicense - ); - } else { - log.info('Copying Elastic license to LICENSE.txt'); - await write(build.resolvePath('LICENSE.txt'), elasticLicense); - } + + log.info('Copying Elastic license to LICENSE.txt'); + await write(build.resolvePath('LICENSE.txt'), elasticLicense); }, }; diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 2ae882000cae0..99d0e1998e78a 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -73,15 +73,12 @@ export const CreateDockerUBI: Task = { description: 'Creating Docker UBI image', async run(config, log, build) { - if (!build.isOss()) { - await runDockerGenerator(config, log, build, { - architecture: 'x64', - context: false, - ubi: true, - image: true, - dockerBuildDate, - }); - } + await runDockerGenerator(config, log, build, { + architecture: 'x64', + context: false, + ubi: true, + image: true, + }); }, }; @@ -95,19 +92,15 @@ export const CreateDockerContexts: Task = { dockerBuildDate, }); - if (!build.isOss()) { - await runDockerGenerator(config, log, build, { - ubi: true, - context: true, - image: false, - dockerBuildDate, - }); - await runDockerGenerator(config, log, build, { - ironbank: true, - context: true, - image: false, - dockerBuildDate, - }); - } + await runDockerGenerator(config, log, build, { + ubi: true, + context: true, + image: false, + }); + await runDockerGenerator(config, log, build, { + ironbank: true, + context: true, + image: false, + }); }, }; diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index c72112b7b6b03..97fd740409741 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -43,24 +43,18 @@ export async function runDockerGenerator( let imageFlavor = ''; if (flags.ubi) imageFlavor += `-${ubiVersionTag}`; if (flags.ironbank) imageFlavor += '-ironbank'; - if (build.isOss()) imageFlavor += '-oss'; // General docker var config - const license = build.isOss() ? 'ASL 2.0' : 'Elastic License'; + const license = 'Elastic License'; const imageTag = 'docker.elastic.co/kibana/kibana'; const version = config.getBuildVersion(); const artifactArchitecture = flags.architecture === 'aarch64' ? 'aarch64' : 'x86_64'; - const artifactFlavor = build.isOss() ? '-oss' : ''; - const artifactPrefix = `kibana${artifactFlavor}-${version}-linux`; + const artifactPrefix = `kibana-${version}-linux`; const artifactTarball = `${artifactPrefix}-${artifactArchitecture}.tar.gz`; const artifactsDir = config.resolveFromTarget('.'); const dockerBuildDate = flags.dockerBuildDate || new Date().toISOString(); // That would produce oss, default and default-ubi7 - const dockerBuildDir = config.resolveFromRepo( - 'build', - 'kibana-docker', - build.isOss() ? `oss` : `default${imageFlavor}` - ); + const dockerBuildDir = config.resolveFromRepo('build', 'kibana-docker', `default${imageFlavor}`); const imageArchitecture = flags.architecture === 'aarch64' ? '-aarch64' : ''; const dockerTargetFilename = config.resolveFromTarget( `kibana${imageFlavor}-${version}-docker-image${imageArchitecture}.tar.gz` diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts index 933b3e411b286..b732e4c80ea37 100644 --- a/src/dev/build/tasks/os_packages/run_fpm.ts +++ b/src/dev/build/tasks/os_packages/run_fpm.ts @@ -28,11 +28,7 @@ export async function runFpm( const fromBuild = (...paths: string[]) => build.resolvePathForPlatform(linux, ...paths); const pickLicense = () => { - if (build.isOss()) { - return type === 'rpm' ? 'ASL 2.0' : 'ASL-2.0'; - } else { - return type === 'rpm' ? 'Elastic License' : 'Elastic-License'; - } + return type === 'rpm' ? 'Elastic License' : 'Elastic-License'; }; const envFolder = type === 'rpm' ? 'sysconfig' : 'default'; @@ -57,7 +53,7 @@ export async function runFpm( // general info about the package '--name', - build.isOss() ? 'kibana-oss' : 'kibana', + 'kibana', '--description', 'Explore and visualize your Elasticsearch data', '--version', @@ -71,10 +67,6 @@ export async function runFpm( '--license', pickLicense(), - // prevent installing kibana if installing kibana-oss and vice versa - '--conflicts', - build.isOss() ? 'kibana' : 'kibana-oss', - // define install/uninstall scripts '--after-install', resolve(__dirname, 'package_scripts/post_install.sh'), diff --git a/src/plugins/apm_oss/server/index.ts b/src/plugins/apm_oss/server/index.ts index 1424cb1c7126f..7b16c42f4c9b9 100644 --- a/src/plugins/apm_oss/server/index.ts +++ b/src/plugins/apm_oss/server/index.ts @@ -7,9 +7,11 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { PluginInitializerContext } from '../../../core/server'; +import { ConfigDeprecationProvider, PluginInitializerContext } from '../../../core/server'; import { APMOSSPlugin } from './plugin'; +const deprecations: ConfigDeprecationProvider = ({ unused }) => [unused('fleetMode')]; + export const config = { schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -22,6 +24,7 @@ export const config = { indexPattern: schema.string({ defaultValue: 'apm-*' }), fleetMode: schema.boolean({ defaultValue: true }), }), + deprecations, }; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/test/scripts/jenkins_baseline.sh b/test/scripts/jenkins_baseline.sh index 40bfc6e83ad1b..5c0ca5a8e01ca 100755 --- a/test/scripts/jenkins_baseline.sh +++ b/test/scripts/jenkins_baseline.sh @@ -4,7 +4,7 @@ source src/dev/ci_setup/setup_env.sh source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" echo " -> building and extracting OSS Kibana distributable for use in functional tests" -node scripts/build --debug --oss +node scripts/build --debug echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index 198723908cf48..a9edd3ed2a701 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -33,7 +33,7 @@ node x-pack/scripts/functional_tests --assert-none-excluded \ # Do not build kibana for code coverage run if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> building and extracting default Kibana distributable for use in functional tests" - node scripts/build --debug --no-oss + node scripts/build --debug echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ diff --git a/test/scripts/jenkins_build_load_testing.sh b/test/scripts/jenkins_build_load_testing.sh index 5571eee4f28ed..d7c7bda83c9ef 100755 --- a/test/scripts/jenkins_build_load_testing.sh +++ b/test/scripts/jenkins_build_load_testing.sh @@ -60,7 +60,7 @@ export KBN_NP_PLUGINS_BUILT=true echo " -> Building and extracting default Kibana distributable for use in functional tests" cd "$KIBANA_DIR" -node scripts/build --debug --no-oss +node scripts/build --debug linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" mkdir -p "$installDir" diff --git a/test/scripts/jenkins_xpack_baseline.sh b/test/scripts/jenkins_xpack_baseline.sh index 8d5624949505a..c68c0f40902f2 100755 --- a/test/scripts/jenkins_xpack_baseline.sh +++ b/test/scripts/jenkins_xpack_baseline.sh @@ -5,7 +5,7 @@ source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" echo " -> building and extracting default Kibana distributable" cd "$KIBANA_DIR" -node scripts/build --debug --no-oss +node scripts/build --debug echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ diff --git a/test/scripts/jenkins_xpack_package_build.sh b/test/scripts/jenkins_xpack_package_build.sh index 698129a2d253b..86e846f720803 100755 --- a/test/scripts/jenkins_xpack_package_build.sh +++ b/test/scripts/jenkins_xpack_package_build.sh @@ -7,6 +7,6 @@ source src/dev/ci_setup/setup_env.sh export TMP=/tmp export TMPDIR=/tmp -node scripts/build --all-platforms --debug --no-oss +node scripts/build --all-platforms --debug gsutil -q -m cp 'target/*' "gs://ci-artifacts.kibana.dev/package-testing/$GIT_COMMIT/" diff --git a/x-pack/plugins/apm/server/index.test.ts b/x-pack/plugins/apm/server/index.test.ts index 226dfd6e95bd3..6052ec921f9f9 100644 --- a/x-pack/plugins/apm/server/index.test.ts +++ b/x-pack/plugins/apm/server/index.test.ts @@ -28,53 +28,15 @@ describe('mergeConfigs', () => { agent: { migrations: { enabled: true } }, } as APMXPackConfig; - expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({ - 'apm_oss.errorIndices': 'apm-*-error-*', - 'apm_oss.indexPattern': 'apm-*', - 'apm_oss.metricsIndices': 'apm-*-metric-*', - 'apm_oss.spanIndices': 'apm-*-span-*', - 'apm_oss.transactionIndices': 'apm-*-transaction-*', - 'xpack.apm.metricsInterval': 2000, - 'xpack.apm.ui.enabled': false, - 'xpack.apm.agent.migrations.enabled': true, - }); - }); - - it('adds fleet indices', () => { - const apmOssConfig = { - transactionIndices: 'apm-*-transaction-*', - spanIndices: 'apm-*-span-*', - errorIndices: 'apm-*-error-*', - metricsIndices: 'apm-*-metric-*', - fleetMode: true, - } as APMOSSConfig; - - const apmConfig = { ui: {}, agent: { migrations: {} } } as APMXPackConfig; - expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({ 'apm_oss.errorIndices': 'logs-apm*,apm-*-error-*', + 'apm_oss.indexPattern': 'apm-*', 'apm_oss.metricsIndices': 'metrics-apm*,apm-*-metric-*', 'apm_oss.spanIndices': 'traces-apm*,apm-*-span-*', 'apm_oss.transactionIndices': 'traces-apm*,apm-*-transaction-*', - }); - }); - - it('does not add fleet indices', () => { - const apmOssConfig = { - transactionIndices: 'apm-*-transaction-*', - spanIndices: 'apm-*-span-*', - errorIndices: 'apm-*-error-*', - metricsIndices: 'apm-*-metric-*', - fleetMode: false, - } as APMOSSConfig; - - const apmConfig = { ui: {}, agent: { migrations: {} } } as APMXPackConfig; - - expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({ - 'apm_oss.errorIndices': 'apm-*-error-*', - 'apm_oss.metricsIndices': 'apm-*-metric-*', - 'apm_oss.spanIndices': 'apm-*-span-*', - 'apm_oss.transactionIndices': 'apm-*-transaction-*', + 'xpack.apm.metricsInterval': 2000, + 'xpack.apm.ui.enabled': false, + 'xpack.apm.agent.migrations.enabled': true, }); }); }); diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 413efcdb78812..8ec92bfa7a1b5 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -102,23 +102,22 @@ export function mergeConfigs( 'xpack.apm.agent.migrations.enabled': apmConfig.agent.migrations.enabled, }; - if (apmOssConfig.fleetMode) { - mergedConfig[ - 'apm_oss.transactionIndices' - ] = `traces-apm*,${mergedConfig['apm_oss.transactionIndices']}`; + // Add data stream indices to list of configured values + mergedConfig[ + 'apm_oss.transactionIndices' + ] = `traces-apm*,${mergedConfig['apm_oss.transactionIndices']}`; - mergedConfig[ - 'apm_oss.spanIndices' - ] = `traces-apm*,${mergedConfig['apm_oss.spanIndices']}`; + mergedConfig[ + 'apm_oss.spanIndices' + ] = `traces-apm*,${mergedConfig['apm_oss.spanIndices']}`; - mergedConfig[ - 'apm_oss.errorIndices' - ] = `logs-apm*,${mergedConfig['apm_oss.errorIndices']}`; + mergedConfig[ + 'apm_oss.errorIndices' + ] = `logs-apm*,${mergedConfig['apm_oss.errorIndices']}`; - mergedConfig[ - 'apm_oss.metricsIndices' - ] = `metrics-apm*,${mergedConfig['apm_oss.metricsIndices']}`; - } + mergedConfig[ + 'apm_oss.metricsIndices' + ] = `metrics-apm*,${mergedConfig['apm_oss.metricsIndices']}`; return mergedConfig; } diff --git a/x-pack/plugins/cases/common/api/cases/comment.ts b/x-pack/plugins/cases/common/api/cases/comment.ts index 746c28f994239..378a89f8af8c3 100644 --- a/x-pack/plugins/cases/common/api/cases/comment.ts +++ b/x-pack/plugins/cases/common/api/cases/comment.ts @@ -39,6 +39,7 @@ export enum CommentType { user = 'user', alert = 'alert', generatedAlert = 'generated_alert', + actions = 'actions', } export const ContextTypeUserRt = rt.type({ @@ -63,11 +64,38 @@ export const AlertCommentRequestRt = rt.type({ owner: rt.string, }); +export const ActionsCommentRequestRt = rt.type({ + type: rt.literal(CommentType.actions), + comment: rt.string, + actions: rt.type({ + targets: rt.array( + rt.type({ + hostname: rt.string, + endpointId: rt.string, + }) + ), + type: rt.string, + }), + owner: rt.string, +}); + const AttributesTypeUserRt = rt.intersection([ContextTypeUserRt, CommentAttributesBasicRt]); const AttributesTypeAlertsRt = rt.intersection([AlertCommentRequestRt, CommentAttributesBasicRt]); -const CommentAttributesRt = rt.union([AttributesTypeUserRt, AttributesTypeAlertsRt]); +const AttributesTypeActionsRt = rt.intersection([ + ActionsCommentRequestRt, + CommentAttributesBasicRt, +]); +const CommentAttributesRt = rt.union([ + AttributesTypeUserRt, + AttributesTypeAlertsRt, + AttributesTypeActionsRt, +]); -export const CommentRequestRt = rt.union([ContextTypeUserRt, AlertCommentRequestRt]); +export const CommentRequestRt = rt.union([ + ContextTypeUserRt, + AlertCommentRequestRt, + ActionsCommentRequestRt, +]); export const CommentResponseRt = rt.intersection([ CommentAttributesRt, @@ -85,6 +113,14 @@ export const CommentResponseTypeAlertsRt = rt.intersection([ }), ]); +export const CommentResponseTypeActionsRt = rt.intersection([ + AttributesTypeActionsRt, + rt.type({ + id: rt.string, + version: rt.string, + }), +]); + export const AllCommentsResponseRT = rt.array(CommentResponseRt); export const CommentPatchRequestRt = rt.intersection([ @@ -125,15 +161,18 @@ export const FindQueryParamsRt = rt.partial({ }); export type FindQueryParams = rt.TypeOf; +export type AttributesTypeActions = rt.TypeOf; export type AttributesTypeAlerts = rt.TypeOf; export type AttributesTypeUser = rt.TypeOf; export type CommentAttributes = rt.TypeOf; export type CommentRequest = rt.TypeOf; export type CommentResponse = rt.TypeOf; export type CommentResponseAlertsType = rt.TypeOf; +export type CommentResponseActionsType = rt.TypeOf; export type AllCommentsResponse = rt.TypeOf; export type CommentsResponse = rt.TypeOf; export type CommentPatchRequest = rt.TypeOf; export type CommentPatchAttributes = rt.TypeOf; export type CommentRequestUserType = rt.TypeOf; export type CommentRequestAlertType = rt.TypeOf; +export type CommentRequestActionsType = rt.TypeOf; diff --git a/x-pack/plugins/cases/public/components/case_view/index.test.tsx b/x-pack/plugins/cases/public/components/case_view/index.test.tsx index 1fafbac50c2b9..e04bbbe54c837 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.test.tsx @@ -90,6 +90,10 @@ export const caseProps: CaseComponentProps = { }, getCaseDetailHrefWithCommentId: jest.fn(), onComponentInitialized: jest.fn(), + actionsNavigation: { + href: jest.fn(), + onClick: jest.fn(), + }, ruleDetailsNavigation: { href: jest.fn(), onClick: jest.fn(), @@ -408,6 +412,10 @@ describe('CaseView ', () => { }, getCaseDetailHrefWithCommentId: jest.fn(), onComponentInitialized: jest.fn(), + actionsNavigation: { + href: jest.fn(), + onClick: jest.fn(), + }, ruleDetailsNavigation: { href: jest.fn(), onClick: jest.fn(), @@ -448,6 +456,10 @@ describe('CaseView ', () => { }, getCaseDetailHrefWithCommentId: jest.fn(), onComponentInitialized: jest.fn(), + actionsNavigation: { + href: jest.fn(), + onClick: jest.fn(), + }, ruleDetailsNavigation: { href: jest.fn(), onClick: jest.fn(), @@ -485,6 +497,10 @@ describe('CaseView ', () => { }, getCaseDetailHrefWithCommentId: jest.fn(), onComponentInitialized: jest.fn(), + actionsNavigation: { + href: jest.fn(), + onClick: jest.fn(), + }, ruleDetailsNavigation: { href: jest.fn(), onClick: jest.fn(), @@ -522,6 +538,10 @@ describe('CaseView ', () => { }, getCaseDetailHrefWithCommentId: jest.fn(), onComponentInitialized: jest.fn(), + actionsNavigation: { + href: jest.fn(), + onClick: jest.fn(), + }, ruleDetailsNavigation: { href: jest.fn(), onClick: jest.fn(), diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx index 2fffd6464dbb1..ac7c9ebe08b5a 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.tsx @@ -50,6 +50,7 @@ export interface CaseViewComponentProps { configureCasesNavigation: CasesNavigation; getCaseDetailHrefWithCommentId: (commentId: string) => string; onComponentInitialized?: () => void; + actionsNavigation?: CasesNavigation; ruleDetailsNavigation?: CasesNavigation; showAlertDetails?: (alertId: string, index: string) => void; subCaseId?: string; @@ -99,6 +100,7 @@ export const CaseComponent = React.memo( getCaseDetailHrefWithCommentId, fetchCase, onComponentInitialized, + actionsNavigation, ruleDetailsNavigation, showAlertDetails, subCaseId, @@ -418,6 +420,7 @@ export const CaseComponent = React.memo( caseUserActions={caseUserActions} connectors={connectors} data={caseData} + actionsNavigation={actionsNavigation} fetchUserActions={fetchCaseUserActions.bind( null, caseId, @@ -505,6 +508,7 @@ export const CaseView = React.memo( getCaseDetailHrefWithCommentId, onCaseDataSuccess, onComponentInitialized, + actionsNavigation, ruleDetailsNavigation, showAlertDetails, subCaseId, @@ -543,6 +547,7 @@ export const CaseView = React.memo( getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId} fetchCase={fetchCase} onComponentInitialized={onComponentInitialized} + actionsNavigation={actionsNavigation} ruleDetailsNavigation={ruleDetailsNavigation} showAlertDetails={showAlertDetails} subCaseId={subCaseId} diff --git a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx index 5424ad8238a2a..5d234296dd503 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx @@ -7,12 +7,14 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiCommentProps } from '@elastic/eui'; import React from 'react'; - +import classNames from 'classnames'; import { CaseFullExternalService, ActionConnector, CaseStatuses, CommentType, + Comment, + CommentRequestActionsType, } from '../../../common'; import { CaseUserActions } from '../../containers/types'; import { CaseServices } from '../../containers/use_get_case_user_actions'; @@ -20,13 +22,16 @@ import { parseString } from '../../containers/utils'; import { Tags } from '../tag_list/tags'; import { UserActionUsernameWithAvatar } from './user_action_username_with_avatar'; import { UserActionTimestamp } from './user_action_timestamp'; +import { UserActionContentToolbar } from './user_action_content_toolbar'; import { UserActionCopyLink } from './user_action_copy_link'; +import { UserActionMarkdown } from './user_action_markdown'; import { UserActionMoveToReference } from './user_action_move_to_reference'; import { Status, statuses } from '../status'; import { UserActionShowAlert } from './user_action_show_alert'; import * as i18n from './translations'; import { AlertCommentEvent } from './user_action_alert_comment_event'; import { CasesNavigation } from '../links'; +import { HostIsolationCommentEvent } from './user_action_host_isolation_comment_event'; interface LabelTitle { action: CaseUserActions; @@ -34,6 +39,8 @@ interface LabelTitle { } export type RuleDetailsNavigation = CasesNavigation; +export type ActionsNavigation = CasesNavigation; + const getStatusTitle = (id: string, status: CaseStatuses) => ( string; + actionsNavigation?: ActionsNavigation; + manageMarkdownEditIds: string[]; + handleManageMarkdownEditId: (id: string) => void; + handleManageQuote: (id: string) => void; + handleSaveComment: ({ id, version }: { id: string; version: string }, content: string) => void; + action: CaseUserActions; +}): EuiCommentProps => ({ + username: ( + + ), + className: classNames({ + isEdit: manageMarkdownEditIds.includes(comment.id), + }), + event: ( + + ), + 'data-test-subj': 'endpoint-action', + timestamp: , + timelineIcon: comment.actions.type === 'isolate' ? 'lock' : 'lockOpen', + actions: ( + + ), + children: ( + + ), +}); + interface Signal { rule: { id: string; diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx index faa4f1d1a786f..610399c31928b 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx @@ -28,6 +28,7 @@ const defaultProps = { caseUserActions: [], connectors: [], getCaseDetailHrefWithCommentId: jest.fn(), + actionsNavigation: { href: jest.fn(), onClick: jest.fn() }, getRuleDetailsHref: jest.fn(), onRuleDetailsClick: jest.fn(), data: basicCase, diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index 5e90c2e6a951f..eb74eb2bd643a 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -26,6 +26,7 @@ import { useCurrentUser } from '../../common/lib/kibana'; import { AddComment, AddCommentRefObject } from '../add_comment'; import { ActionConnector, + ActionsCommentRequestRt, AlertCommentRequestRt, Case, CaseUserActions, @@ -45,6 +46,8 @@ import { getAlertAttachment, getGeneratedAlertsAttachment, RuleDetailsNavigation, + ActionsNavigation, + getActionAttachment, } from './helpers'; import { UserActionAvatar } from './user_action_avatar'; import { UserActionMarkdown } from './user_action_markdown'; @@ -61,6 +64,7 @@ export interface UserActionTreeProps { fetchUserActions: () => void; getCaseDetailHrefWithCommentId: (commentId: string) => string; getRuleDetailsHref?: RuleDetailsNavigation['href']; + actionsNavigation?: ActionsNavigation; isLoadingDescription: boolean; isLoadingUserActions: boolean; onRuleDetailsClick?: RuleDetailsNavigation['onClick']; @@ -125,6 +129,7 @@ export const UserActionTree = React.memo( fetchUserActions, getCaseDetailHrefWithCommentId, getRuleDetailsHref, + actionsNavigation, isLoadingDescription, isLoadingUserActions, onRuleDetailsClick, @@ -447,6 +452,30 @@ export const UserActionTree = React.memo( ] : []), ]; + } else if ( + comment != null && + isRight(ActionsCommentRequestRt.decode(comment)) && + comment.type === CommentType.actions + ) { + return [ + ...comments, + ...(comment.actions !== null + ? [ + getActionAttachment({ + comment, + userCanCrud, + isLoadingIds, + getCaseDetailHrefWithCommentId, + actionsNavigation, + manageMarkdownEditIds, + handleManageMarkdownEditId, + handleManageQuote, + handleSaveComment, + action, + }), + ] + : []), + ]; } } @@ -559,6 +588,7 @@ export const UserActionTree = React.memo( handleManageMarkdownEditId, handleSaveComment, getCaseDetailHrefWithCommentId, + actionsNavigation, userCanCrud, isLoadingIds, handleManageQuote, diff --git a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts index 27d1554ed255b..54738e29060f3 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts +++ b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts @@ -56,3 +56,17 @@ export const SHOW_ALERT_TOOLTIP = i18n.translate('xpack.cases.caseView.showAlert export const UNKNOWN_RULE = i18n.translate('xpack.cases.caseView.unknownRule.label', { defaultMessage: 'Unknown rule', }); + +export const ISOLATED_HOST = i18n.translate('xpack.cases.caseView.isolatedHost', { + defaultMessage: 'isolated host', +}); + +export const RELEASED_HOST = i18n.translate('xpack.cases.caseView.releasedHost', { + defaultMessage: 'released host', +}); + +export const OTHER_ENDPOINTS = (endpoints: number): string => + i18n.translate('xpack.cases.caseView.otherEndpoints', { + values: { endpoints }, + defaultMessage: ` and {endpoints} {endpoints, plural, =1 {other} other {others}}`, + }); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx new file mode 100644 index 0000000000000..d363e874a4e0d --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx @@ -0,0 +1,56 @@ +/* + * 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 React, { memo, useCallback } from 'react'; +import * as i18n from './translations'; +import { LinkAnchor } from '../links'; +import { ActionsNavigation } from './helpers'; + +interface EndpointInfo { + endpointId: string; + hostname: string; +} + +interface Props { + type: string; + endpoints: EndpointInfo[]; + href?: ActionsNavigation['href']; + onClick?: ActionsNavigation['onClick']; +} + +const HostIsolationCommentEventComponent: React.FC = ({ + type, + endpoints, + href, + onClick, +}) => { + const endpointDetailsHref = href ? href(endpoints[0].endpointId) : ''; + + const onLinkClick = useCallback( + (ev) => { + ev.preventDefault(); + if (onClick) onClick(endpoints[0].endpointId, ev); + }, + [onClick, endpoints] + ); + + return ( + <> + {type === 'isolate' ? `${i18n.ISOLATED_HOST} ` : `${i18n.RELEASED_HOST} `} + + {endpoints[0].hostname} + + {endpoints.length > 1 && i18n.OTHER_ENDPOINTS(endpoints.length - 1)} + + ); +}; + +export const HostIsolationCommentEvent = memo(HostIsolationCommentEventComponent); diff --git a/x-pack/plugins/cases/server/client/utils.ts b/x-pack/plugins/cases/server/client/utils.ts index 3240ec06803c7..0e7a21816de4c 100644 --- a/x-pack/plugins/cases/server/client/utils.ts +++ b/x-pack/plugins/cases/server/client/utils.ts @@ -17,6 +17,7 @@ import { nodeBuilder, KueryNode } from '../../../../../src/plugins/data/common'; import { esKuery } from '../../../../../src/plugins/data/server'; import { AlertCommentRequestRt, + ActionsCommentRequestRt, CASE_SAVED_OBJECT, CaseConnector, CaseStatuses, @@ -35,12 +36,15 @@ import { getIDsAndIndicesAsArrays, isCommentRequestTypeAlertOrGenAlert, isCommentRequestTypeUser, + isCommentRequestTypeActions, SavedObjectFindOptionsKueryNode, } from '../common'; export const decodeCommentRequest = (comment: CommentRequest) => { if (isCommentRequestTypeUser(comment)) { pipe(excess(ContextTypeUserRt).decode(comment), fold(throwErrors(badRequest), identity)); + } else if (isCommentRequestTypeActions(comment)) { + pipe(excess(ActionsCommentRequestRt).decode(comment), fold(throwErrors(badRequest), identity)); } else if (isCommentRequestTypeAlertOrGenAlert(comment)) { pipe(excess(AlertCommentRequestRt).decode(comment), fold(throwErrors(badRequest), identity)); const { ids, indices } = getIDsAndIndicesAsArrays(comment); diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 70ecc50df0f48..13d3f3768f391 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -317,6 +317,15 @@ export const isCommentRequestTypeUser = ( return context.type === CommentType.user; }; +/** + * A type narrowing function for actions comments. Exporting so integration tests can use it. + */ +export const isCommentRequestTypeActions = ( + context: CommentRequest +): context is CommentRequestUserType => { + return context.type === CommentType.actions; +}; + /** * A type narrowing function for alert comments. Exporting so integration tests can use it. */ diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts index cd2dec7260fa4..876ceb9bc2045 100644 --- a/x-pack/plugins/cases/server/saved_object_types/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts @@ -27,6 +27,18 @@ export const caseCommentSavedObjectType: SavedObjectsType = { type: { type: 'keyword', }, + actions: { + properties: { + targets: { + type: 'nested', + properties: { + hostname: { type: 'keyword' }, + endpointId: { type: 'keyword' }, + }, + }, + type: { type: 'keyword' }, + }, + }, alertId: { type: 'keyword', }, diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss index 19f70070ef5b2..6e31d3f26b4bc 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss @@ -9,6 +9,12 @@ .mapLayerControl__addLayerButton { flex-shrink: 0; + + // overrides disabled color that is transparent and doesn't work well on top of the map + &.euiButton-isDisabled { + // sass-lint:disable-block no-important + background-color: $euiColorLightShade !important; + } } .mapLayerControl__addLayerButton, diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts index 1f3d4307197f8..1c9adc8f2f9c3 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts @@ -102,7 +102,7 @@ export class BaseDataGenerator { } protected randomVersion(): string { - return [6, ...this.randomNGenerator(10, 2)].map((x) => x.toString()).join('.'); + return [7, ...this.randomNGenerator(20, 2)].map((x) => x.toString()).join('.'); } protected randomChoice(choices: T[]): T { diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index b08d5649540db..876cb3866c614 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -51,7 +51,7 @@ export const ANCESTRY_LIMIT: number = 2; const Windows: OSFields[] = [ { - name: 'windows 10.0', + name: 'Windows', full: 'Windows 10', version: '10.0', platform: 'Windows', @@ -61,7 +61,7 @@ const Windows: OSFields[] = [ }, }, { - name: 'windows 10.0', + name: 'Windows', full: 'Windows Server 2016', version: '10.0', platform: 'Windows', @@ -71,7 +71,7 @@ const Windows: OSFields[] = [ }, }, { - name: 'windows 6.2', + name: 'Windows', full: 'Windows Server 2012', version: '6.2', platform: 'Windows', @@ -81,7 +81,7 @@ const Windows: OSFields[] = [ }, }, { - name: 'windows 6.3', + name: 'Windows', full: 'Windows Server 2012R2', version: '6.3', platform: 'Windows', diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts index f58dd1f3370d4..fd4d89540f0ce 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts @@ -9,10 +9,8 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const HostIsolationRequestSchema = { body: schema.object({ - /** A list of Fleet Agent IDs whose hosts will be isolated */ - agent_ids: schema.maybe(schema.arrayOf(schema.string())), /** A list of endpoint IDs whose hosts will be isolated (Fleet Agent IDs will be retrieved for these) */ - endpoint_ids: schema.maybe(schema.arrayOf(schema.string())), + endpoint_ids: schema.arrayOf(schema.string(), { minSize: 1 }), /** If defined, any case associated with the given IDs will be updated */ alert_ids: schema.maybe(schema.arrayOf(schema.string())), /** Case IDs to be updated */ diff --git a/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts new file mode 100644 index 0000000000000..7d3810bed8f44 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts @@ -0,0 +1,66 @@ +/* + * 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 { isVersionSupported, isOsSupported, isIsolationSupported } from './utils'; + +describe('Host Isolation utils isVersionSupported', () => { + test.each` + a | b | expected + ${'8.14.0'} | ${'7.13.0'} | ${true} + ${'7.14.0'} | ${'7.13.0'} | ${true} + ${'7.14.1'} | ${'7.14.0'} | ${true} + ${'8.14.0'} | ${'9.14.0'} | ${false} + ${'7.13.0'} | ${'7.14.0'} | ${false} + ${'7.14.0'} | ${'7.14.1'} | ${false} + ${'7.14.0'} | ${'7.14.0'} | ${true} + ${'7.14.0-SNAPSHOT'} | ${'7.14.0'} | ${true} + ${'7.14.0-SNAPSHOT-beta'} | ${'7.14.0'} | ${true} + ${'7.14.0-alpha'} | ${'7.14.0'} | ${true} + `('should validate that version $a is compatible($expected) to $b', ({ a, b, expected }) => { + expect( + isVersionSupported({ + currentVersion: a, + minVersionRequired: b, + }) + ).toEqual(expected); + }); +}); + +describe('Host Isolation utils isOsSupported', () => { + test.each` + a | b | expected + ${'linux'} | ${['macos', 'linux']} | ${true} + ${'linux'} | ${['macos', 'windows']} | ${false} + `('should validate that os $a is compatible($expected) to $b', ({ a, b, expected }) => { + expect( + isOsSupported({ + currentOs: a, + supportedOss: b, + }) + ).toEqual(expected); + }); +}); + +describe('Host Isolation utils isIsolationSupported', () => { + test.each` + a | b | expected + ${'windows'} | ${'7.14.0'} | ${true} + ${'linux'} | ${'7.13.0'} | ${false} + ${'linux'} | ${'7.14.0'} | ${false} + ${'macos'} | ${'7.13.0'} | ${false} + `( + 'should validate that os $a and version $b supports hostIsolation($expected)', + ({ a, b, expected }) => { + expect( + isIsolationSupported({ + osName: a, + version: b, + }) + ).toEqual(expected); + } + ); +}); diff --git a/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts new file mode 100644 index 0000000000000..c5e57179bcb8d --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const isVersionSupported = ({ + currentVersion, + minVersionRequired, +}: { + currentVersion: string; + minVersionRequired: string; +}) => { + const parsedCurrentVersion = currentVersion.includes('-SNAPSHOT') + ? currentVersion.substring(0, currentVersion.indexOf('-')) + : currentVersion; + const tokenizedCurrent = parsedCurrentVersion + .split('.') + .map((token: string) => parseInt(token, 10)); + const tokenizedMin = minVersionRequired.split('.').map((token: string) => parseInt(token, 10)); + + const versionNotSupported = tokenizedCurrent.some((token: number, index: number) => { + return token < tokenizedMin[index]; + }); + + return !versionNotSupported; +}; + +export const isOsSupported = ({ + currentOs, + supportedOss, +}: { + currentOs: string; + supportedOss: string[]; +}) => { + return supportedOss.some((os) => currentOs === os); +}; + +export const isIsolationSupported = ({ osName, version }: { osName: string; version: string }) => { + const normalizedOs = osName.toLowerCase(); + return ( + isOsSupported({ currentOs: normalizedOs, supportedOss: ['macos', 'windows'] }) && + isVersionSupported({ currentVersion: version, minVersionRequired: '7.14.0' }) + ); +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 5474fcb47d87e..10a9e27fee1cf 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -35,6 +35,7 @@ import { useInsertTimeline } from '../use_insert_timeline'; import { SpyRoute } from '../../../common/utils/route/spy_routes'; import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; import { CaseDetailsRefreshContext } from '../../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context'; +import { getEndpointDetailsPath } from '../../../management/common/routing'; interface Props { caseId: string; @@ -162,6 +163,14 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = [dispatch] ); + const endpointDetailsHref = (endpointId: string) => + formatUrl( + getEndpointDetailsPath({ + name: 'endpointActivityLog', + selected_endpoint: endpointId, + }) + ); + const onComponentInitialized = useCallback(() => { dispatch( timelineActions.createTimeline({ @@ -220,6 +229,20 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = getCaseDetailHrefWithCommentId, onCaseDataSuccess, onComponentInitialized, + actionsNavigation: { + href: endpointDetailsHref, + onClick: (endpointId: string, e) => { + if (e) { + e.preventDefault(); + } + return navigateToApp(APP_ID, { + path: getEndpointDetailsPath({ + name: 'endpointActivityLog', + selected_endpoint: endpointId, + }), + }); + }, + }, ruleDetailsNavigation: { href: getDetectionsRuleDetailsHref, onClick: async (ruleId: string | null | undefined, e) => { diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts index 6198918496316..256359e40b6e2 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts @@ -14,7 +14,6 @@ import { ISOLATE_HOST_ROUTE, UNISOLATE_HOST_ROUTE } from '../../../../common/end export const hostIsolationRequestBodyMock = (): HostIsolationRequestBody => { return { - agent_ids: ['fd8a122b-4c54-4c05-b295-111'], endpoint_ids: ['88c04a90-b19c-11eb-b838-222'], alert_ids: ['88c04a90-b19c-11eb-b838-333'], case_ids: ['88c04a90-b19c-11eb-b838-444'], diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts index 93d0642c6b3b6..9cb25dd4bb5a5 100644 --- a/x-pack/plugins/security_solution/public/management/common/routing.ts +++ b/x-pack/plugins/security_solution/public/management/common/routing.ts @@ -66,7 +66,12 @@ export const getEndpointListPath = ( export const getEndpointDetailsPath = ( props: { - name: 'endpointDetails' | 'endpointPolicyResponse' | 'endpointIsolate' | 'endpointUnIsolate'; + name: + | 'endpointDetails' + | 'endpointPolicyResponse' + | 'endpointIsolate' + | 'endpointUnIsolate' + | 'endpointActivityLog'; } & EndpointIndexUIQueryParams & EndpointDetailsUrlProps, search?: string @@ -85,6 +90,9 @@ export const getEndpointDetailsPath = ( case 'endpointPolicyResponse': queryParams.show = 'policy_response'; break; + case 'endpointActivityLog': + queryParams.show = 'activity_log'; + break; } const urlQueryParams = querystringStringify( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx index 04fd8cd715c87..56ff523e2c887 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx @@ -46,6 +46,10 @@ describe('When using the Endpoint Details Actions Menu', () => { // Safe to mutate this mocked data // @ts-ignore endpointHost.metadata.Endpoint.state.isolation = isolation; + // @ts-ignore + endpointHost.metadata.host.os.name = 'Windows'; + // @ts-ignore + endpointHost.metadata.agent.version = '7.14.0'; httpMocks.responseProvider.metadataDetails.mockReturnValue(endpointHost); }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index 0422cbcaa4310..584e6df1ff781 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -17,6 +17,7 @@ import { useAppUrl } from '../../../../../common/lib/kibana/hooks'; import { ContextMenuItemNavByRouterProps } from '../components/context_menu_item_nav_by_rotuer'; import { isEndpointHostIsolated } from '../../../../../common/utils/validators'; import { useLicense } from '../../../../../common/hooks/use_license'; +import { isIsolationSupported } from '../../../../../../common/endpoint/service/host_isolation/utils'; /** * Returns a list (array) of actions for an individual endpoint @@ -37,6 +38,10 @@ export const useEndpointActionItems = ( const endpointPolicyId = endpointMetadata.Endpoint.policy.applied.id; const endpointHostName = endpointMetadata.host.hostname; const fleetAgentId = endpointMetadata.elastic.agent.id; + const isolationSupported = isIsolationSupported({ + osName: endpointMetadata.host.os.name, + version: endpointMetadata.agent.version, + }); const { show, selected_endpoint: _selectedEndpoint, @@ -73,7 +78,7 @@ export const useEndpointActionItems = ( /> ), }); - } else if (isPlatinumPlus) { + } else if (isPlatinumPlus && isolationSupported) { // For Platinum++ licenses, users also have ability to isolate isolationActions.push({ 'data-test-subj': 'isolateLink', diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 7bfd77e7dd975..ee5ef52d00f18 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -1189,6 +1189,17 @@ describe('when on the endpoint list page', () => { isolation: false, }, }, + host: { + ...hosts[0].metadata.host, + os: { + ...hosts[0].metadata.host.os, + name: 'Windows', + }, + }, + agent: { + ...hosts[0].metadata.agent, + version: '7.14.0', + }, }, query_strategy_version: queryStrategyVersion, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index 58b991dafed67..509c629dc287c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -32,6 +32,7 @@ import { } from '../../../../detections/components/host_isolation/translations'; import { ALERT_DETAILS } from './translations'; import { useIsolationPrivileges } from '../../../../common/hooks/endpoint/use_isolate_privileges'; +import { isIsolationSupported } from '../../../../../common/endpoint/service/host_isolation/utils'; import { endpointAlertCheck } from '../../../../common/utils/endpoint_alert_check'; import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context'; @@ -102,6 +103,22 @@ const EventDetailsPanelComponent: React.FC = ({ return findAgentId ? findAgentId[0] : ''; }, [detailsData]); + const hostOsFamily = useMemo(() => { + const findOsName = find({ category: 'host', field: 'host.os.name' }, detailsData)?.values; + return findOsName ? findOsName[0] : ''; + }, [detailsData]); + + const agentVersion = useMemo(() => { + const findAgentVersion = find({ category: 'agent', field: 'agent.version' }, detailsData) + ?.values; + return findAgentVersion ? findAgentVersion[0] : ''; + }, [detailsData]); + + const isolationSupported = isIsolationSupported({ + osName: hostOsFamily, + version: agentVersion, + }); + const backToAlertDetailsLink = useMemo(() => { return ( <> @@ -164,15 +181,18 @@ const EventDetailsPanelComponent: React.FC = ({ /> )} - {isIsolationAllowed && isEndpointAlert && isHostIsolationPanelOpen === false && ( - - - - - - - - )} + {isIsolationAllowed && + isEndpointAlert && + isolationSupported && + isHostIsolationPanelOpen === false && ( + + + + + + + + )} ) : ( <> diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts index 5ce27164dc878..1b1490d3af072 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts @@ -22,6 +22,7 @@ import { createMockPackageService, createRouteHandlerContext, } from '../../mocks'; +import { HostIsolationRequestSchema } from '../../../../common/endpoint/schema/actions'; import { registerHostIsolationRoutes } from './isolation'; import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { LicenseService } from '../../../../common/license'; @@ -55,282 +56,310 @@ const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold' } }); describe('Host Isolation', () => { - let endpointAppContextService: EndpointAppContextService; - let mockResponse: jest.Mocked; - let licenseService: LicenseService; - let licenseEmitter: Subject; + describe('schema', () => { + it('should require at least 1 Endpoint ID', () => { + expect(() => { + HostIsolationRequestSchema.body.validate({}); + }).toThrow(); + }); - let callRoute: ( - routePrefix: string, - opts: CallRouteInterface - ) => Promise>; - const superUser = { - username: 'superuser', - roles: ['superuser'], - }; + it('should accept an Endpoint ID as the only required field', () => { + expect(() => { + HostIsolationRequestSchema.body.validate({ + endpoint_ids: ['ABC-XYZ-000'], + }); + }).not.toThrow(); + }); - const docGen = new EndpointDocGenerator(); + it('should accept a comment', () => { + expect(() => { + HostIsolationRequestSchema.body.validate({ + endpoint_ids: ['ABC-XYZ-000'], + comment: 'a user comment', + }); + }).not.toThrow(); + }); - beforeEach(() => { - // instantiate... everything - const mockScopedClient = elasticsearchServiceMock.createScopedClusterClient(); - const mockClusterClient = elasticsearchServiceMock.createClusterClient(); - mockClusterClient.asScoped.mockReturnValue(mockScopedClient); - const routerMock = httpServiceMock.createRouter(); - mockResponse = httpServerMock.createResponseFactory(); - const startContract = createMockEndpointAppContextServiceStartContract(); - endpointAppContextService = new EndpointAppContextService(); - const mockSavedObjectClient = savedObjectsClientMock.create(); - const mockPackageService = createMockPackageService(); - mockPackageService.getInstalledEsAssetReferences.mockReturnValue( - Promise.resolve([ - { - id: 'logs-endpoint.events.security', - type: ElasticsearchAssetType.indexTemplate, - }, - { - id: `${metadataTransformPrefix}-0.16.0-dev.0`, - type: ElasticsearchAssetType.transform, - }, - ]) - ); - licenseEmitter = new Subject(); - licenseService = new LicenseService(); - licenseService.start(licenseEmitter); - endpointAppContextService.start({ - ...startContract, - licenseService, - packageService: mockPackageService, + it('should accept alert IDs', () => { + expect(() => { + HostIsolationRequestSchema.body.validate({ + endpoint_ids: ['ABC-XYZ-000'], + alert_ids: ['0000000-000-00'], + }); + }).not.toThrow(); }); - // add the host isolation route handlers to routerMock - registerHostIsolationRoutes(routerMock, { - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + it('should accept case IDs', () => { + expect(() => { + HostIsolationRequestSchema.body.validate({ + endpoint_ids: ['ABC-XYZ-000'], + case_ids: ['000000000-000-000'], + }); + }).not.toThrow(); }); + }); + + describe('handler', () => { + let endpointAppContextService: EndpointAppContextService; + let mockResponse: jest.Mocked; + let licenseService: LicenseService; + let licenseEmitter: Subject; - // define a convenience function to execute an API call for a given route, body, and mocked response from ES - // it returns the requestContext mock used in the call, to assert internal calls (e.g. the indexed document) - callRoute = async ( + let callRoute: ( routePrefix: string, - { body, idxResponse, searchResponse, mockUser, license }: CallRouteInterface - ): Promise> => { - const asUser = mockUser ? mockUser : superUser; - (startContract.security.authc.getCurrentUser as jest.Mock).mockImplementationOnce( - () => asUser - ); - const ctx = createRouteHandlerContext(mockScopedClient, mockSavedObjectClient); - const withIdxResp = idxResponse ? idxResponse : { statusCode: 201 }; - ctx.core.elasticsearch.client.asCurrentUser.index = jest - .fn() - .mockImplementationOnce(() => Promise.resolve(withIdxResp)); - ctx.core.elasticsearch.client.asCurrentUser.search = jest - .fn() - .mockImplementationOnce(() => - Promise.resolve({ body: createV2SearchResponse(searchResponse) }) - ); - const withLicense = license ? license : Platinum; - licenseEmitter.next(withLicense); - const mockRequest = httpServerMock.createKibanaRequest({ body }); - const [, routeHandler]: [ - RouteConfig, - RequestHandler - ] = routerMock.post.mock.calls.find(([{ path }]) => path.startsWith(routePrefix))!; - await routeHandler(ctx, mockRequest, mockResponse); - return (ctx as unknown) as jest.Mocked; + opts: CallRouteInterface + ) => Promise>; + const superUser = { + username: 'superuser', + roles: ['superuser'], }; - }); - - afterEach(() => { - endpointAppContextService.stop(); - licenseService.stop(); - licenseEmitter.complete(); - }); - it('errors if no endpoint or agent is provided', async () => { - await callRoute(ISOLATE_HOST_ROUTE, {}); - expect(mockResponse.badRequest).toBeCalled(); - }); - it('succeeds when an agent ID is provided', async () => { - await callRoute(ISOLATE_HOST_ROUTE, { body: { agent_ids: ['XYZ'] } }); - expect(mockResponse.ok).toBeCalled(); - }); - it('reports elasticsearch errors creating an action', async () => { - const ErrMessage = 'something went wrong?'; + const docGen = new EndpointDocGenerator(); - await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, - idxResponse: { - statusCode: 500, - body: { - result: ErrMessage, - }, - }, - }); - expect(mockResponse.ok).not.toBeCalled(); - const response = mockResponse.customError.mock.calls[0][0]; - expect(response.statusCode).toEqual(500); - expect((response.body as Error).message).toEqual(ErrMessage); - }); - it('accepts a comment field', async () => { - await callRoute(ISOLATE_HOST_ROUTE, { body: { agent_ids: ['XYZ'], comment: 'XYZ' } }); - expect(mockResponse.ok).toBeCalled(); - }); - it('sends the action to the requested agent', async () => { - const AgentID = '123-ABC'; - const ctx = await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: [AgentID] }, - }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; - expect(actionDoc.agents).toContain(AgentID); - }); - it('records the user who performed the action to the action record', async () => { - const testU = { username: 'testuser', roles: ['superuser'] }; - const ctx = await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, - mockUser: testU, - }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; - expect(actionDoc.user_id).toEqual(testU.username); - }); - it('records the comment in the action payload', async () => { - const CommentText = "I am isolating this because it's Friday"; - const ctx = await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'], comment: CommentText }, - }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; - expect(actionDoc.data.comment).toEqual(CommentText); - }); - it('creates an action and returns its ID', async () => { - const ctx = await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'], comment: 'XYZ' }, - }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; - const actionID = actionDoc.action_id; - expect(mockResponse.ok).toBeCalled(); - expect((mockResponse.ok.mock.calls[0][0]?.body as HostIsolationResponse).action).toEqual( - actionID - ); - }); - - it('succeeds when just an endpoint ID is provided', async () => { - await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] } }); - expect(mockResponse.ok).toBeCalled(); - }); - it('sends the action to the correct agent when endpoint ID is given', async () => { - const doc = docGen.generateHostMetadata(); - const AgentID = doc.elastic.agent.id; + beforeEach(() => { + // instantiate... everything + const mockScopedClient = elasticsearchServiceMock.createScopedClusterClient(); + const mockClusterClient = elasticsearchServiceMock.createClusterClient(); + mockClusterClient.asScoped.mockReturnValue(mockScopedClient); + const routerMock = httpServiceMock.createRouter(); + mockResponse = httpServerMock.createResponseFactory(); + const startContract = createMockEndpointAppContextServiceStartContract(); + endpointAppContextService = new EndpointAppContextService(); + const mockSavedObjectClient = savedObjectsClientMock.create(); + const mockPackageService = createMockPackageService(); + mockPackageService.getInstalledEsAssetReferences.mockReturnValue( + Promise.resolve([ + { + id: 'logs-endpoint.events.security', + type: ElasticsearchAssetType.indexTemplate, + }, + { + id: `${metadataTransformPrefix}-0.16.0-dev.0`, + type: ElasticsearchAssetType.transform, + }, + ]) + ); + licenseEmitter = new Subject(); + licenseService = new LicenseService(); + licenseService.start(licenseEmitter); + endpointAppContextService.start({ + ...startContract, + licenseService, + packageService: mockPackageService, + }); - const ctx = await callRoute(ISOLATE_HOST_ROUTE, { - body: { endpoint_ids: ['XYZ'] }, - searchResponse: doc, - }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; - expect(actionDoc.agents).toContain(AgentID); - }); - it('combines given agent IDs and endpoint IDs', async () => { - const doc = docGen.generateHostMetadata(); - const explicitAgentID = 'XYZ'; - const lookupAgentID = doc.elastic.agent.id; + // add the host isolation route handlers to routerMock + registerHostIsolationRoutes(routerMock, { + logFactory: loggingSystemMock.create(), + service: endpointAppContextService, + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); - const ctx = await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: [explicitAgentID], endpoint_ids: ['XYZ'] }, - searchResponse: doc, + // define a convenience function to execute an API call for a given route, body, and mocked response from ES + // it returns the requestContext mock used in the call, to assert internal calls (e.g. the indexed document) + callRoute = async ( + routePrefix: string, + { body, idxResponse, searchResponse, mockUser, license }: CallRouteInterface + ): Promise> => { + const asUser = mockUser ? mockUser : superUser; + (startContract.security.authc.getCurrentUser as jest.Mock).mockImplementationOnce( + () => asUser + ); + const ctx = createRouteHandlerContext(mockScopedClient, mockSavedObjectClient); + const withIdxResp = idxResponse ? idxResponse : { statusCode: 201 }; + ctx.core.elasticsearch.client.asCurrentUser.index = jest + .fn() + .mockImplementationOnce(() => Promise.resolve(withIdxResp)); + ctx.core.elasticsearch.client.asCurrentUser.search = jest + .fn() + .mockImplementation(() => + Promise.resolve({ body: createV2SearchResponse(searchResponse) }) + ); + const withLicense = license ? license : Platinum; + licenseEmitter.next(withLicense); + const mockRequest = httpServerMock.createKibanaRequest({ body }); + const [, routeHandler]: [ + RouteConfig, + RequestHandler + ] = routerMock.post.mock.calls.find(([{ path }]) => path.startsWith(routePrefix))!; + await routeHandler(ctx, mockRequest, mockResponse); + return (ctx as unknown) as jest.Mocked; + }; }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; - expect(actionDoc.agents).toContain(explicitAgentID); - expect(actionDoc.agents).toContain(lookupAgentID); - }); - it('sends the isolate command payload from the isolate route', async () => { - const ctx = await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, + afterEach(() => { + endpointAppContextService.stop(); + licenseService.stop(); + licenseEmitter.complete(); }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; - expect(actionDoc.data.command).toEqual('isolate'); - }); - it('sends the unisolate command payload from the unisolate route', async () => { - const ctx = await callRoute(UNISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, + it('succeeds when an endpoint ID is provided', async () => { + await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] } }); + expect(mockResponse.ok).toBeCalled(); }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; - expect(actionDoc.data.command).toEqual('unisolate'); - }); + it('reports elasticsearch errors creating an action', async () => { + const ErrMessage = 'something went wrong?'; - describe('License Level', () => { - it('allows platinum license levels to isolate hosts', async () => { await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, - license: Platinum, + body: { endpoint_ids: ['XYZ'] }, + idxResponse: { + statusCode: 500, + body: { + result: ErrMessage, + }, + }, }); + expect(mockResponse.ok).not.toBeCalled(); + const response = mockResponse.customError.mock.calls[0][0]; + expect(response.statusCode).toEqual(500); + expect((response.body as Error).message).toEqual(ErrMessage); + }); + it('accepts a comment field', async () => { + await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'], comment: 'XYZ' } }); expect(mockResponse.ok).toBeCalled(); }); - it('prohibits license levels less than platinum from isolating hosts', async () => { - licenseEmitter.next(Gold); - await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, - license: Gold, + it('sends the action to the requested agent', async () => { + const metadataResponse = docGen.generateHostMetadata(); + const AgentID = metadataResponse.elastic.agent.id; + const ctx = await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['ABC-XYZ-000'] }, + searchResponse: metadataResponse, }); - expect(mockResponse.forbidden).toBeCalled(); + const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser + .index as jest.Mock).mock.calls[0][0].body; + expect(actionDoc.agents).toContain(AgentID); }); - it('allows any license level to unisolate', async () => { - licenseEmitter.next(Gold); - await callRoute(UNISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, - license: Gold, + it('records the user who performed the action to the action record', async () => { + const testU = { username: 'testuser', roles: ['superuser'] }; + const ctx = await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + mockUser: testU, }); + const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser + .index as jest.Mock).mock.calls[0][0].body; + expect(actionDoc.user_id).toEqual(testU.username); + }); + it('records the comment in the action payload', async () => { + const CommentText = "I am isolating this because it's Friday"; + const ctx = await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'], comment: CommentText }, + }); + const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser + .index as jest.Mock).mock.calls[0][0].body; + expect(actionDoc.data.comment).toEqual(CommentText); + }); + it('creates an action and returns its ID', async () => { + const ctx = await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'], comment: 'XYZ' }, + }); + const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser + .index as jest.Mock).mock.calls[0][0].body; + const actionID = actionDoc.action_id; expect(mockResponse.ok).toBeCalled(); + expect((mockResponse.ok.mock.calls[0][0]?.body as HostIsolationResponse).action).toEqual( + actionID + ); }); - }); - describe('User Level', () => { - it('allows superuser to perform isolation', async () => { - const superU = { username: 'foo', roles: ['superuser'] }; - await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, - mockUser: superU, - }); + it('succeeds when just an endpoint ID is provided', async () => { + await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] } }); expect(mockResponse.ok).toBeCalled(); }); - it('allows superuser to perform unisolation', async () => { - const superU = { username: 'foo', roles: ['superuser'] }; - await callRoute(UNISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, - mockUser: superU, + it('sends the action to the correct agent when endpoint ID is given', async () => { + const doc = docGen.generateHostMetadata(); + const AgentID = doc.elastic.agent.id; + + const ctx = await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + searchResponse: doc, }); - expect(mockResponse.ok).toBeCalled(); + const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser + .index as jest.Mock).mock.calls[0][0].body; + expect(actionDoc.agents).toContain(AgentID); }); - it('prohibits non-admin user from performing isolation', async () => { - const superU = { username: 'foo', roles: ['user'] }; - await callRoute(ISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, - mockUser: superU, + it('sends the isolate command payload from the isolate route', async () => { + const ctx = await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, }); - expect(mockResponse.forbidden).toBeCalled(); + const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser + .index as jest.Mock).mock.calls[0][0].body; + expect(actionDoc.data.command).toEqual('isolate'); }); - it('prohibits non-admin user from performing unisolation', async () => { - const superU = { username: 'foo', roles: ['user'] }; - await callRoute(UNISOLATE_HOST_ROUTE, { - body: { agent_ids: ['XYZ'] }, - mockUser: superU, + it('sends the unisolate command payload from the unisolate route', async () => { + const ctx = await callRoute(UNISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + }); + const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser + .index as jest.Mock).mock.calls[0][0].body; + expect(actionDoc.data.command).toEqual('unisolate'); + }); + + describe('License Level', () => { + it('allows platinum license levels to isolate hosts', async () => { + await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + license: Platinum, + }); + expect(mockResponse.ok).toBeCalled(); + }); + it('prohibits license levels less than platinum from isolating hosts', async () => { + licenseEmitter.next(Gold); + await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + license: Gold, + }); + expect(mockResponse.forbidden).toBeCalled(); + }); + it('allows any license level to unisolate', async () => { + licenseEmitter.next(Gold); + await callRoute(UNISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + license: Gold, + }); + expect(mockResponse.ok).toBeCalled(); }); - expect(mockResponse.forbidden).toBeCalled(); }); - }); - describe('Cases', () => { - it.todo('logs a comment to the provided case'); - it.todo('logs a comment to any cases associated with the given alert'); + describe('User Level', () => { + it('allows superuser to perform isolation', async () => { + const superU = { username: 'foo', roles: ['superuser'] }; + await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + mockUser: superU, + }); + expect(mockResponse.ok).toBeCalled(); + }); + it('allows superuser to perform unisolation', async () => { + const superU = { username: 'foo', roles: ['superuser'] }; + await callRoute(UNISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + mockUser: superU, + }); + expect(mockResponse.ok).toBeCalled(); + }); + + it('prohibits non-admin user from performing isolation', async () => { + const superU = { username: 'foo', roles: ['user'] }; + await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + mockUser: superU, + }); + expect(mockResponse.forbidden).toBeCalled(); + }); + it('prohibits non-admin user from performing unisolation', async () => { + const superU = { username: 'foo', roles: ['user'] }; + await callRoute(UNISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + mockUser: superU, + }); + expect(mockResponse.forbidden).toBeCalled(); + }); + }); + + describe('Cases', () => { + it.todo('logs a comment to the provided case'); + it.todo('logs a comment to any cases associated with the given alert'); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts index 785434aa17ec6..45063ca92e2b0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts @@ -14,12 +14,12 @@ import { CasesByAlertId } from '../../../../../cases/common/api/cases/case'; import { HostIsolationRequestSchema } from '../../../../common/endpoint/schema/actions'; import { ISOLATE_HOST_ROUTE, UNISOLATE_HOST_ROUTE } from '../../../../common/endpoint/constants'; import { AGENT_ACTIONS_INDEX } from '../../../../../fleet/common'; -import { EndpointAction } from '../../../../common/endpoint/types'; +import { EndpointAction, HostMetadata } from '../../../../common/endpoint/types'; import { SecuritySolutionPluginRouter, SecuritySolutionRequestHandlerContext, } from '../../../types'; -import { getAgentIDsForEndpoints } from '../../services'; +import { getMetadataForEndpoints } from '../../services'; import { EndpointAppContext } from '../../types'; import { APP_ID } from '../../../../common/constants'; import { userCanIsolate } from '../../../../common/endpoint/actions'; @@ -61,19 +61,7 @@ export const isolationRequestHandler = function ( TypeOf, SecuritySolutionRequestHandlerContext > { - // eslint-disable-next-line complexity return async (context, req, res) => { - if ( - (!req.body.agent_ids || req.body.agent_ids.length === 0) && - (!req.body.endpoint_ids || req.body.endpoint_ids.length === 0) - ) { - return res.badRequest({ - body: { - message: 'At least one agent ID or endpoint ID is required', - }, - }); - } - // only allow admin users const user = endpointContext.service.security?.authc.getCurrentUser(req); if (!userCanIsolate(user?.roles)) { @@ -93,13 +81,9 @@ export const isolationRequestHandler = function ( }); } - // translate any endpoint_ids into agent_ids - let agentIDs = req.body.agent_ids?.slice() || []; - if (req.body.endpoint_ids && req.body.endpoint_ids.length > 0) { - const newIDs = await getAgentIDsForEndpoints(req.body.endpoint_ids, context, endpointContext); - agentIDs = agentIDs.concat(newIDs); - } - agentIDs = [...new Set(agentIDs)]; // dedupe + // fetch the Agent IDs to send the commands to + const endpointIDs = [...new Set(req.body.endpoint_ids)]; // dedupe + const endpointData = await getMetadataForEndpoints(endpointIDs, context, endpointContext); const casesClient = await endpointContext.service.getCasesClient(req); @@ -134,7 +118,7 @@ export const isolationRequestHandler = function ( expiration: moment().add(2, 'weeks').toISOString(), type: 'INPUT_ACTION', input_type: 'endpoint', - agents: agentIDs, + agents: endpointData.map((endpt: HostMetadata) => endpt.elastic.agent.id), user_id: user!.username, data: { command: isolate ? 'isolate' : 'unisolate', @@ -158,25 +142,24 @@ export const isolationRequestHandler = function ( }); } - const commentLines: string[] = []; - - commentLines.push(`${isolate ? 'I' : 'Uni'}solate action was sent to the following Agents:`); - // lines of markdown links, inside a code block - - commentLines.push(`${agentIDs.map((a) => `- [${a}](/app/fleet#/agents/${a})`).join('\n')}`); - if (req.body.comment) { - commentLines.push(`\n\nWith Comment:\n> ${req.body.comment}`); - } - // Update all cases with a comment if (caseIDs.length > 0) { + const targets = endpointData.map((endpt: HostMetadata) => ({ + hostname: endpt.host.hostname, + endpointId: endpt.agent.id, + })); + await Promise.all( caseIDs.map((caseId) => casesClient.attachments.add({ caseId, comment: { - comment: commentLines.join('\n'), - type: CommentType.user, + type: CommentType.actions, + comment: req.body.comment || '', + actions: { + targets, + type: isolate ? 'isolate' : 'unisolate', + }, owner: APP_ID, }, }) diff --git a/x-pack/plugins/security_solution/server/endpoint/services/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/index.ts index 9fabd043e2950..8bf64999c746a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/index.ts @@ -6,4 +6,4 @@ */ export * from './artifacts'; -export { getAgentIDsForEndpoints } from './lookup_agent'; +export { getMetadataForEndpoints } from './metadata'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/lookup_agent.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts similarity index 89% rename from x-pack/plugins/security_solution/server/endpoint/services/lookup_agent.ts rename to x-pack/plugins/security_solution/server/endpoint/services/metadata.ts index e82b548641290..0ca1983aa68d5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/lookup_agent.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts @@ -12,11 +12,11 @@ import { SecuritySolutionRequestHandlerContext } from '../../types'; import { getESQueryHostMetadataByIDs } from '../routes/metadata/query_builders'; import { EndpointAppContext } from '../types'; -export async function getAgentIDsForEndpoints( +export async function getMetadataForEndpoints( endpointIDs: string[], requestHandlerContext: SecuritySolutionRequestHandlerContext, endpointAppContext: EndpointAppContext -): Promise { +): Promise { const queryStrategy = await endpointAppContext.service ?.getMetadataService() ?.queryStrategy(requestHandlerContext.core.savedObjects.client); @@ -25,6 +25,5 @@ export async function getAgentIDsForEndpoints( const esClient = requestHandlerContext.core.elasticsearch.client.asCurrentUser; const { body } = await esClient.search(query as SearchRequest); const hosts = queryStrategy!.queryResponseToHostListResult(body as SearchResponse); - - return hosts.resultList.map((x: HostMetadata): string => x.elastic.agent.id); + return hosts.resultList; }