From 4991ee1c2f5ea418407e687dbc2cd36af759923a Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 10 Jan 2025 12:38:17 -0600 Subject: [PATCH 1/5] [renovate] Fix match for all base branches (#205963) https://docs.renovatebot.com/string-pattern-matching/ doesn't appear to be working for this setting. The documentation [mentions regular expression support](https://docs.renovatebot.com/configuration-options/#matchbasebranches), this moves the expression over. --- renovate.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/renovate.json b/renovate.json index 395beffc8942b..5bba92dbadf43 100644 --- a/renovate.json +++ b/renovate.json @@ -67,7 +67,7 @@ "team:kibana-operations" ], "matchBaseBranches": [ - "*" + "/^[8-9].*/" ], "labels": [ "Team:Operations", @@ -94,7 +94,7 @@ "team:kibana-operations" ], "matchBaseBranches": [ - "*" + "/.*/" ], "labels": [ "Team:Operations", @@ -3213,7 +3213,7 @@ "team:kibana-operations" ], "matchBaseBranches": [ - "*" + "/.*/" ], "labels": [ "Team:Operations", From ff2a16008b2068bc1a8ef7d2e52984404f0e2ca0 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:49:39 +0000 Subject: [PATCH 2/5] Update APM (main) (#205153) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- package.json | 6 +++--- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 510a94f8a2dc1..91efc955da830 100644 --- a/package.json +++ b/package.json @@ -108,9 +108,9 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", - "@elastic/apm-rum": "^5.16.1", - "@elastic/apm-rum-core": "^5.21.1", - "@elastic/apm-rum-react": "^2.0.3", + "@elastic/apm-rum": "^5.16.3", + "@elastic/apm-rum-core": "^5.22.1", + "@elastic/apm-rum-react": "^2.0.5", "@elastic/charts": "68.0.4", "@elastic/datemath": "5.0.3", "@elastic/ebt": "^1.1.1", diff --git a/yarn.lock b/yarn.lock index 4934ac8c3de06..3351139897929 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2211,29 +2211,29 @@ dependencies: tslib "^2.0.0" -"@elastic/apm-rum-core@^5.21.1": - version "5.21.1" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum-core/-/apm-rum-core-5.21.1.tgz#ea6f2268629193962c194ae1d29971e2c5b2e531" - integrity sha512-/LyLhVdJ+zcsKogwq2AEYARc//RDXOoTHWPERLay4sCsvvxc4/GkkhhOC40CqI0oMu4kUAoJInQWZuCM7zCZRQ== +"@elastic/apm-rum-core@^5.22.1": + version "5.22.1" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum-core/-/apm-rum-core-5.22.1.tgz#51f652ace73d4f3ee2d93097a3a3a042764fb26e" + integrity sha512-G8NHxG3R+ok3XsRSUKwUcpUQR3/cKMN2UT2LXP57hrtTaI4eKEzk3zn9IXnxAwqwSl0TMtewaIoBTI9KVDCMyQ== dependencies: error-stack-parser "^1.3.5" opentracing "^0.14.3" promise-polyfill "^8.1.3" -"@elastic/apm-rum-react@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-2.0.3.tgz#6193464943ea80a74682373de2242490623111f6" - integrity sha512-oDdNz1GEZvy7R1CPOJWkXFRRHY5yNO3vRhUttDXqvXvRBEd1m7HosskHs/R/LjtoYghpAVrG+yzJGvDI9TSuxQ== +"@elastic/apm-rum-react@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-2.0.5.tgz#0992d3b869f83bb97a89b22bf03b0342e3be1d54" + integrity sha512-yRb4qQSxAO2aQPxqBTob1t1QcoVJ35IP4ixMZkxNKFAL0bvqYRssU+92bOSkj6eyYBOets8NJKzy1Cg3/wOXGw== dependencies: - "@elastic/apm-rum" "^5.16.1" + "@elastic/apm-rum" "^5.16.3" hoist-non-react-statics "^3.3.0" -"@elastic/apm-rum@^5.16.1": - version "5.16.1" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum/-/apm-rum-5.16.1.tgz#59767f587a31293c17497ff86e05754627182be4" - integrity sha512-hxmhjLqF4EqEl2wVwxKU5p88jiwHPF//zqk97bm09vj5CQQ6HBtWkwIxQ5dvEEOnRwJinFEf9V2JibpamgzUWA== +"@elastic/apm-rum@^5.16.3": + version "5.16.3" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum/-/apm-rum-5.16.3.tgz#11c31765477ab4174a1fb0949ca4e315f8d070db" + integrity sha512-gi4aFrAL46vKXOQM2S/1ZVfpklxJwlzWH2G69qFBw3drpa/lhmpjg6lW8N44v0whz1um1FiSVdl0I+QSgZNYfw== dependencies: - "@elastic/apm-rum-core" "^5.21.1" + "@elastic/apm-rum-core" "^5.22.1" "@elastic/app-search-javascript@^8.1.2": version "8.1.2" From 4ca57799b751e48d7657604ab04b50887e5b624c Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:54:47 +0000 Subject: [PATCH 3/5] [SecuritySolution] Apply different color tokens for themes (#206254) ## Summary Previous changes applied same tokens for Borealis and Amsterdam: https://github.com/elastic/kibana/pull/204631#issuecomment-2573415425 PR above causes color changes to the current theme, after discussing with UX, we decide to maintain different color tokens until Borealis is launched. This PR should revert the color changed on Amsterdam by the previous PR and only apply the new color for Borealis. | Current and Amsterdam | Borealis | |-------------------------|----------| |Source: Hard coded: `#d36186`|Source: `euiColorVis4` - `#EE72A6` | |Dest: Hard coded: `#9170b8` |Dest: `euiColorVis2` - `#61A2FF`| ### Host IPs: | Current and Amsterdam | Borealis | |-------------------------|----------| |host_IPs|host_bor_light| |host_IPs_dark|host_bor_dark| ### Network IPs: | Current and Amsterdam | Borealis | |-------------------------|----------| |network_IPs|network_bor_light| |host_IPs_dark|network_bor_dark| ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../common/utils/unique_ips_palette.ts | 13 +++++++++++++ .../__snapshots__/kpi_unique_ips_area.test.ts.snap | 4 ++-- .../__snapshots__/kpi_unique_ips_bar.test.ts.snap | 4 ++-- .../lens_attributes/hosts/kpi_unique_ips_area.ts | 9 +++------ .../lens_attributes/hosts/kpi_unique_ips_bar.ts | 7 +++---- .../kpi_unique_private_ips_area.test.ts.snap | 4 ++-- .../kpi_unique_private_ips_bar.test.ts.snap | 4 ++-- .../network/kpi_unique_private_ips_area.ts | 5 +++-- .../network/kpi_unique_private_ips_bar.ts | 5 +++-- .../hosts/components/kpi_hosts/unique_ips/index.tsx | 10 +++++++--- .../kpi_network/unique_private_ips/index.tsx | 10 +++++++--- 11 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/utils/unique_ips_palette.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/utils/unique_ips_palette.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/utils/unique_ips_palette.ts new file mode 100644 index 0000000000000..a2cbe731fa6c4 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/utils/unique_ips_palette.ts @@ -0,0 +1,13 @@ +/* + * 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 type { EuiThemeComputed } from '@elastic/eui'; + +export const getDestinationIpColor = (euiTheme: EuiThemeComputed) => + euiTheme.flags.hasVisColorAdjustment ? '#9170b8' : euiTheme.colors.vis.euiColorVis2; + +export const getSourceIpColor = (euiTheme: EuiThemeComputed) => + euiTheme.flags.hasVisColorAdjustment ? '#d36186' : euiTheme.colors.vis.euiColorVis4; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap index a829f18fd216c..73b57d04c823e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap @@ -199,7 +199,7 @@ Object { "xAccessor": "a0cb6400-f708-46c3-ad96-24788f12dae4", "yConfig": Array [ Object { - "color": "#CA8EAE", + "color": "#d36186", "forAccessor": "d9a6eb6b-8b78-439e-98e7-a718f8ffbebe", }, ], @@ -214,7 +214,7 @@ Object { "xAccessor": "95e74e6-99dd-4b11-8faf-439b4d959df9", "yConfig": Array [ Object { - "color": "#D36086", + "color": "#9170b8", "forAccessor": "e7052671-fb9e-481f-8df3-7724c98cfc6f", }, ], diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap index a6e3277032138..83135f5a4e4dd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap @@ -212,7 +212,7 @@ Object { "xAccessor": "f8bfa719-5c1c-4bf2-896e-c318d77fc08e", "yConfig": Array [ Object { - "color": "#CA8EAE", + "color": "#d36186", "forAccessor": "32f66676-f4e1-48fd-b7f8-d4de38318601", }, ], @@ -227,7 +227,7 @@ Object { "xAccessor": "c72aad6a-fc9c-43dc-9194-e13ca3ee8aff", "yConfig": Array [ Object { - "color": "#D36086", + "color": "#9170b8", "forAccessor": "b7e59b08-96e6-40d1-84fd-e97b977d1c47", }, ], diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.ts index 133f7a51a5e1b..5120365243a4f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_area.ts @@ -7,6 +7,7 @@ import { DESTINATION_CHART_LABEL, SOURCE_CHART_LABEL } from '../../translations'; import type { GetLensAttributes, LensAttributes } from '../../types'; +import { getDestinationIpColor, getSourceIpColor } from '../common/utils/unique_ips_palette'; const columnSourceTimestamp = 'a0cb6400-f708-46c3-ad96-24788f12dae4'; const columnSourceUniqueIp = 'd9a6eb6b-8b78-439e-98e7-a718f8ffbebe'; @@ -89,9 +90,7 @@ export const getKpiUniqueIpsAreaLensAttributes: GetLensAttributes = ({ euiTheme layerType: 'data', seriesType: 'area', xAccessor: columnSourceTimestamp, - yConfig: [ - { color: euiTheme.colors.vis.euiColorVis4, forAccessor: columnSourceUniqueIp }, - ], + yConfig: [{ color: getSourceIpColor(euiTheme), forAccessor: columnSourceUniqueIp }], }, { accessors: [columnDestinationIp], @@ -99,9 +98,7 @@ export const getKpiUniqueIpsAreaLensAttributes: GetLensAttributes = ({ euiTheme layerType: 'data', seriesType: 'area', xAccessor: columnDestinationTimestamp, - yConfig: [ - { color: euiTheme.colors.vis.euiColorVis2, forAccessor: columnDestinationIp }, - ], + yConfig: [{ color: getDestinationIpColor(euiTheme), forAccessor: columnDestinationIp }], }, ], legend: { isVisible: false, position: 'right', showSingleSeries: false }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.ts index 0303c9abe9e12..5cec96c4eb953 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/kpi_unique_ips_bar.ts @@ -7,6 +7,7 @@ import type { GetLensAttributes, LensAttributes } from '../../types'; import { SOURCE_CHART_LABEL, DESTINATION_CHART_LABEL, UNIQUE_COUNT } from '../../translations'; +import { getDestinationIpColor, getSourceIpColor } from '../common/utils/unique_ips_palette'; const columnSourceIp = '32f66676-f4e1-48fd-b7f8-d4de38318601'; const columnSourceFilter = 'f8bfa719-5c1c-4bf2-896e-c318d77fc08e'; @@ -96,7 +97,7 @@ export const getKpiUniqueIpsBarLensAttributes: GetLensAttributes = ({ euiTheme } layerType: 'data', seriesType: 'bar_horizontal_stacked', xAccessor: columnSourceFilter, - yConfig: [{ color: euiTheme.colors.vis.euiColorVis4, forAccessor: columnSourceIp }], + yConfig: [{ color: getSourceIpColor(euiTheme), forAccessor: columnSourceIp }], }, { accessors: [columnDestinationIp], @@ -104,9 +105,7 @@ export const getKpiUniqueIpsBarLensAttributes: GetLensAttributes = ({ euiTheme } layerType: 'data', seriesType: 'bar_horizontal_stacked', xAccessor: columnDestinationFilter, - yConfig: [ - { color: euiTheme.colors.vis.euiColorVis2, forAccessor: columnDestinationIp }, - ], + yConfig: [{ color: getDestinationIpColor(euiTheme), forAccessor: columnDestinationIp }], }, ], legend: { isVisible: false, position: 'right', showSingleSeries: false }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap index 3e46284421b4a..23dd658448ccc 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap @@ -223,7 +223,7 @@ Object { "xAccessor": "662cd5e5-82bf-4325-a703-273f84b97e09", "yConfig": Array [ Object { - "color": "#CA8EAE", + "color": "#d36186", "forAccessor": "5f317308-cfbb-4ee5-bfb9-07653184fabf", }, ], @@ -238,7 +238,7 @@ Object { "xAccessor": "36444b8c-7e10-4069-8298-6c1b46912be2", "yConfig": Array [ Object { - "color": "#D36086", + "color": "#9170b8", "forAccessor": "ac1eb80c-ddde-46c4-a90c-400261926762", }, ], diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap index 443ddb2cfdfca..24b50ee3d6f61 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap @@ -238,7 +238,7 @@ Object { "xAccessor": "d9c438c5-f776-4436-9d20-d62dc8c03be8", "yConfig": Array [ Object { - "color": "#CA8EAE", + "color": "#d36186", "forAccessor": "5acd4c9d-dc3b-4b21-9632-e4407944c36d", }, ], @@ -253,7 +253,7 @@ Object { "xAccessor": "4607c585-3af3-43b9-804f-e49b27796d79", "yConfig": Array [ Object { - "color": "#D36086", + "color": "#9170b8", "forAccessor": "d27e0966-daf9-41f4-9033-230cf1e76dc9", }, ], diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.ts index a0a74af33cd68..2f0c97cb6ddac 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area.ts @@ -6,6 +6,7 @@ */ import { DESTINATION_CHART_LABEL, SOURCE_CHART_LABEL } from '../../translations'; import type { LensAttributes, GetLensAttributes } from '../../types'; +import { getDestinationIpColor, getSourceIpColor } from '../common/utils/unique_ips_palette'; const columnTimestamp = '662cd5e5-82bf-4325-a703-273f84b97e09'; const columnSourceIp = '5f317308-cfbb-4ee5-bfb9-07653184fabf'; @@ -66,7 +67,7 @@ export const getKpiUniquePrivateIpsAreaLensAttributes: GetLensAttributes = ({ eu yConfig: [ { forAccessor: columnSourceIp, - color: euiTheme.colors.vis.euiColorVis4, + color: getSourceIpColor(euiTheme), }, ], }, @@ -79,7 +80,7 @@ export const getKpiUniquePrivateIpsAreaLensAttributes: GetLensAttributes = ({ eu yConfig: [ { forAccessor: columnDestinationIp, - color: euiTheme.colors.vis.euiColorVis2, + color: getDestinationIpColor(euiTheme), }, ], }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.ts index 7f4b444451a9f..eca03cc2e4f26 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar.ts @@ -7,6 +7,7 @@ import { v4 as uuidv4 } from 'uuid'; import { SOURCE_CHART_LABEL, DESTINATION_CHART_LABEL, UNIQUE_COUNT } from '../../translations'; import type { LensAttributes, GetLensAttributes } from '../../types'; +import { getDestinationIpColor, getSourceIpColor } from '../common/utils/unique_ips_palette'; const columnSourceIp = uuidv4(); const columnSourceIpFilter = uuidv4(); @@ -69,7 +70,7 @@ export const getKpiUniquePrivateIpsBarLensAttributes: GetLensAttributes = ({ eui yConfig: [ { forAccessor: columnSourceIp, - color: euiTheme.colors.vis.euiColorVis4, + color: getSourceIpColor(euiTheme), }, ], xAccessor: columnSourceIpFilter, @@ -82,7 +83,7 @@ export const getKpiUniquePrivateIpsBarLensAttributes: GetLensAttributes = ({ eui yConfig: [ { forAccessor: columnDestinationIp, - color: euiTheme.colors.vis.euiColorVis2, + color: getDestinationIpColor(euiTheme), }, ], xAccessor: columnDestinationIpFilter, diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/components/kpi_hosts/unique_ips/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/components/kpi_hosts/unique_ips/index.tsx index 4dc8e9fd62b66..27f75c3864c43 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/components/kpi_hosts/unique_ips/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/components/kpi_hosts/unique_ips/index.tsx @@ -16,6 +16,10 @@ import { kpiUniqueIpsSourceMetricLensAttributes } from '../../../../../common/co import { KpiBaseComponent } from '../../../../components/kpi'; import type { HostsKpiProps } from '../types'; import * as i18n from './translations'; +import { + getDestinationIpColor, + getSourceIpColor, +} from '../../../../../common/components/visualization_actions/lens_attributes/common/utils/unique_ips_palette'; export const ID = 'hostsKpiUniqueIpsQuery'; @@ -30,7 +34,7 @@ export const useGetUniqueIpsStatItems: () => Readonly = () => { key: 'uniqueSourceIps', name: i18n.SOURCE_CHART_LABEL, description: i18n.SOURCE_UNIT_LABEL, - color: euiTheme.colors.vis.euiColorVis4, + color: getSourceIpColor(euiTheme), icon: 'visMapCoordinate', lensAttributes: kpiUniqueIpsSourceMetricLensAttributes, }, @@ -38,7 +42,7 @@ export const useGetUniqueIpsStatItems: () => Readonly = () => { key: 'uniqueDestinationIps', name: i18n.DESTINATION_CHART_LABEL, description: i18n.DESTINATION_UNIT_LABEL, - color: euiTheme.colors.vis.euiColorVis2, + color: getDestinationIpColor(euiTheme), icon: 'visMapCoordinate', lensAttributes: kpiUniqueIpsDestinationMetricLensAttributes, }, @@ -50,7 +54,7 @@ export const useGetUniqueIpsStatItems: () => Readonly = () => { getBarChartLensAttributes: getKpiUniqueIpsBarLensAttributes, }, ], - [euiTheme.colors.vis.euiColorVis2, euiTheme.colors.vis.euiColorVis4] + [euiTheme] ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/kpi_network/unique_private_ips/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/kpi_network/unique_private_ips/index.tsx index b53804076b67c..37b8f9f349967 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/kpi_network/unique_private_ips/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/network/components/kpi_network/unique_private_ips/index.tsx @@ -16,6 +16,10 @@ import { kpiUniquePrivateIpsDestinationMetricLensAttributes } from '../../../../ import { getKpiUniquePrivateIpsAreaLensAttributes } from '../../../../../common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_area'; import { getKpiUniquePrivateIpsBarLensAttributes } from '../../../../../common/components/visualization_actions/lens_attributes/network/kpi_unique_private_ips_bar'; import { KpiBaseComponent } from '../../../../components/kpi'; +import { + getDestinationIpColor, + getSourceIpColor, +} from '../../../../../common/components/visualization_actions/lens_attributes/common/utils/unique_ips_palette'; export const ID = 'networkKpiUniquePrivateIpsQuery'; @@ -30,7 +34,7 @@ export const useGetUniquePrivateIpsStatItems: () => Readonly = () = key: 'uniqueSourcePrivateIps', name: i18n.SOURCE_CHART_LABEL, description: i18n.SOURCE_UNIT_LABEL, - color: euiTheme.colors.vis.euiColorVis4, + color: getSourceIpColor(euiTheme), icon: 'visMapCoordinate', lensAttributes: kpiUniquePrivateIpsSourceMetricLensAttributes, }, @@ -38,7 +42,7 @@ export const useGetUniquePrivateIpsStatItems: () => Readonly = () = key: 'uniqueDestinationPrivateIps', name: i18n.DESTINATION_CHART_LABEL, description: i18n.DESTINATION_UNIT_LABEL, - color: euiTheme.colors.vis.euiColorVis2, + color: getDestinationIpColor(euiTheme), icon: 'visMapCoordinate', lensAttributes: kpiUniquePrivateIpsDestinationMetricLensAttributes, }, @@ -50,7 +54,7 @@ export const useGetUniquePrivateIpsStatItems: () => Readonly = () = getBarChartLensAttributes: getKpiUniquePrivateIpsBarLensAttributes, }, ], - [euiTheme.colors.vis.euiColorVis2, euiTheme.colors.vis.euiColorVis4] + [euiTheme] ); }; From 221f1b100f79addee5acd6a89950dfa7561ec1e0 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Sat, 11 Jan 2025 06:07:06 +1100 Subject: [PATCH 4/5] skip failing test suite (#193876) --- .../circuit_breaker/index_threshold_max_alerts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts index 8bab44baee538..d18a70fd4699c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts @@ -24,7 +24,8 @@ export default function maxAlertsRuleTests({ getService }: FtrProviderContext) { const es = getService('es'); const esTestIndexTool = new ESTestIndexTool(es, retry); - describe('index threshold rule that hits max alerts circuit breaker', () => { + // Failing: See https://github.com/elastic/kibana/issues/193876 + describe.skip('index threshold rule that hits max alerts circuit breaker', () => { const objectRemover = new ObjectRemover(supertest); beforeEach(async () => { From d8918077d24bedcb630bfcbb4b0acb467f8aaeff Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:20:46 -0500 Subject: [PATCH 5/5] [Security Solution][Endpoint] Agent status api support for Microsoft Defender for Endpoint hosts (#205817) ## Summary ### Stack Connectors changes - Added new method to the Microsoft Defender for Endpoint connector to retrieve list of Machines ### Security Solution - Added support for retrieving the status of Microsoft Defender agents --- .../microsoft_defender_endpoint/constants.ts | 1 + .../microsoft_defender_endpoint/schema.ts | 99 +++++++++++ .../microsoft_defender_endpoint/types.ts | 13 ++ .../microsoft_defender_endpoint.test.ts | 27 +++ .../microsoft_defender_endpoint.ts | 32 ++++ .../microsoft_defender_endpoint/mocks.ts | 8 + .../routes/agent/agent_status_handler.test.ts | 11 +- .../routes/agent/agent_status_handler.ts | 5 +- .../microsoft/defender/endpoint/mocks.ts | 20 +++ .../actions/pending_actions_summary.ts | 4 +- .../agent/clients/get_agent_status_client.ts | 3 + .../microsoft_defender_endpoint/index.ts | 8 + ...ender_endpoint_agent_status_client.test.ts | 155 ++++++++++++++++++ ...t_defender_endpoint_agent_status_client.ts | 117 +++++++++++++ 14 files changed, 498 insertions(+), 5 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/index.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/microsoft_defender_endpoint_agent_status_client.test.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/microsoft_defender_endpoint_agent_status_client.ts diff --git a/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/constants.ts b/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/constants.ts index bc78aa44ff91c..07cb3192e04e4 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/constants.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/constants.ts @@ -11,6 +11,7 @@ export const MICROSOFT_DEFENDER_ENDPOINT_CONNECTOR_ID = '.microsoft_defender_end export enum MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION { TEST_CONNECTOR = 'testConnector', GET_AGENT_DETAILS = 'getAgentDetails', + GET_AGENT_LIST = 'getAgentList', ISOLATE_HOST = 'isolateHost', RELEASE_HOST = 'releaseHost', GET_ACTIONS = 'getActions', diff --git a/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/schema.ts b/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/schema.ts index 179b9cc26d550..a6063340211fc 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/schema.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/schema.ts @@ -37,6 +37,105 @@ export const AgentDetailsParamsSchema = schema.object({ id: schema.string({ minLength: 1 }), }); +const MachineHealthStatusSchema = schema.oneOf([ + schema.literal('Active'), + schema.literal('Inactive'), + schema.literal('ImpairedCommunication'), + schema.literal('NoSensorData'), + schema.literal('NoSensorDataImpairedCommunication'), + schema.literal('Unknown'), +]); + +export const AgentListParamsSchema = schema.object({ + computerDnsName: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + id: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + version: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + deviceValue: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + aaDeviceId: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + machineTags: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + lastSeen: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + exposureLevel: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + onboardingStatus: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + lastIpAddress: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + healthStatus: schema.maybe( + schema.oneOf([ + MachineHealthStatusSchema, + schema.arrayOf(MachineHealthStatusSchema, { minSize: 1 }), + ]) + ), + osPlatform: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + riskScore: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + rbacGroupId: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + + page: schema.maybe(schema.number({ min: 1, defaultValue: 1 })), + pageSize: schema.maybe(schema.number({ min: 1, max: 1000, defaultValue: 20 })), +}); + export const IsolateHostParamsSchema = schema.object({ id: schema.string({ minLength: 1 }), comment: schema.string({ minLength: 1 }), diff --git a/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/types.ts b/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/types.ts index e4bfbd6b51cec..9e25f9a784596 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/types.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/common/microsoft_defender_endpoint/types.ts @@ -17,6 +17,7 @@ import { TestConnectorParamsSchema, AgentDetailsParamsSchema, GetActionsParamsSchema, + AgentListParamsSchema, } from './schema'; export type MicrosoftDefenderEndpointConfig = TypeOf; @@ -35,6 +36,18 @@ export interface MicrosoftDefenderEndpointTestConnector { export type MicrosoftDefenderEndpointAgentDetailsParams = TypeOf; +export type MicrosoftDefenderEndpointAgentListParams = TypeOf; + +export interface MicrosoftDefenderEndpointAgentListResponse { + '@odata.context': string; + '@odata.count'?: number; + /** If value is `-1`, then API did not provide a total count */ + total: number; + page: number; + pageSize: number; + value: MicrosoftDefenderEndpointMachine[]; +} + export type MicrosoftDefenderEndpointGetActionsParams = TypeOf; export interface MicrosoftDefenderEndpointGetActionsResponse { diff --git a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/microsoft_defender_endpoint.test.ts b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/microsoft_defender_endpoint.test.ts index fa5ae386da9eb..38be24e6aa224 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/microsoft_defender_endpoint.test.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/microsoft_defender_endpoint.test.ts @@ -205,4 +205,31 @@ describe('Microsoft Defender for Endpoint Connector', () => { } ); }); + + describe('#getAgentList()', () => { + it('should return expected response', async () => { + await expect( + connectorMock.instanceMock.getAgentList({ id: '1-2-3' }, connectorMock.usageCollector) + ).resolves.toEqual({ + '@odata.context': 'https://api-us3.securitycenter.microsoft.com/api/$metadata#Machines', + '@odata.count': 1, + page: 1, + pageSize: 20, + total: 1, + value: [expect.any(Object)], + }); + }); + + it('should call Microsoft API with expected query params', async () => { + await connectorMock.instanceMock.getAgentList({ id: '1-2-3' }, connectorMock.usageCollector); + + expect(connectorMock.instanceMock.request).toHaveBeenCalledWith( + expect.objectContaining({ + url: 'https://api.mock__microsoft.com/api/machines', + params: { $count: true, $filter: 'id eq 1-2-3', $top: 20 }, + }), + connectorMock.usageCollector + ); + }); + }); }); diff --git a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/microsoft_defender_endpoint.ts b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/microsoft_defender_endpoint.ts index 137d1f1dcc053..203b5be802a50 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/microsoft_defender_endpoint.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/microsoft_defender_endpoint.ts @@ -18,6 +18,7 @@ import { MicrosoftDefenderEndpointDoNotValidateResponseSchema, GetActionsParamsSchema, AgentDetailsParamsSchema, + AgentListParamsSchema, } from '../../../common/microsoft_defender_endpoint/schema'; import { MicrosoftDefenderEndpointAgentDetailsParams, @@ -32,6 +33,8 @@ import { MicrosoftDefenderEndpointTestConnector, MicrosoftDefenderEndpointGetActionsParams, MicrosoftDefenderEndpointGetActionsResponse, + MicrosoftDefenderEndpointAgentListParams, + MicrosoftDefenderEndpointAgentListResponse, } from '../../../common/microsoft_defender_endpoint/types'; export class MicrosoftDefenderEndpointConnector extends SubActionConnector< @@ -70,6 +73,11 @@ export class MicrosoftDefenderEndpointConnector extends SubActionConnector< method: 'getAgentDetails', schema: AgentDetailsParamsSchema, }); + this.registerSubAction({ + name: MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION.GET_AGENT_LIST, + method: 'getAgentList', + schema: AgentListParamsSchema, + }); this.registerSubAction({ name: MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION.ISOLATE_HOST, @@ -243,6 +251,30 @@ export class MicrosoftDefenderEndpointConnector extends SubActionConnector< ); } + public async getAgentList( + { page = 1, pageSize = 20, ...filter }: MicrosoftDefenderEndpointAgentListParams, + connectorUsageCollector: ConnectorUsageCollector + ): Promise { + // API Reference: https://learn.microsoft.com/en-us/defender-endpoint/api/get-machines + // OData usage reference: https://learn.microsoft.com/en-us/defender-endpoint/api/exposed-apis-odata-samples + + const response = await this.fetchFromMicrosoft( + { + url: `${this.urls.machines}`, + method: 'GET', + params: this.buildODataUrlParams({ filter, page, pageSize }), + }, + connectorUsageCollector + ); + + return { + ...response, + page, + pageSize, + total: response['@odata.count'] ?? -1, + }; + } + public async isolateHost( { id, comment }: MicrosoftDefenderEndpointIsolateHostParams, connectorUsageCollector: ConnectorUsageCollector diff --git a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/mocks.ts b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/mocks.ts index 9c28c227c2efd..c19ef36cacf25 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/mocks.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/microsoft_defender_endpoint/mocks.ts @@ -78,6 +78,14 @@ const createMicrosoftDefenderConnectorMock = (): CreateMicrosoftDefenderConnecto '@odata.count': 1, value: [createMicrosoftMachineAction()], }), + + // Machine List + [`${apiUrl}/api/machines`]: () => + createAxiosResponseMock({ + '@odata.context': 'https://api-us3.securitycenter.microsoft.com/api/$metadata#Machines', + '@odata.count': 1, + value: [createMicrosoftMachineMock()], + }), }; instanceMock.request.mockImplementation( diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts index 6ea890cdf716e..ee6bbe33589a9 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.test.ts @@ -71,9 +71,10 @@ describe('Agent Status API route handler', () => { }); it.each` - agentType | featureFlag - ${'sentinel_one'} | ${'responseActionsSentinelOneV1Enabled'} - ${'crowdstrike'} | ${'responseActionsCrowdstrikeManualHostIsolationEnabled'} + agentType | featureFlag + ${'sentinel_one'} | ${'responseActionsSentinelOneV1Enabled'} + ${'crowdstrike'} | ${'responseActionsCrowdstrikeManualHostIsolationEnabled'} + ${'microsoft_defender_endpoint'} | ${'responseActionsMSDefenderEndpointEnabled'} `( 'should error if the $agentType feature flag ($featureFlag) is turned off', async ({ @@ -102,6 +103,10 @@ describe('Agent Status API route handler', () => { it.each(RESPONSE_ACTION_AGENT_TYPE)('should accept agent type of %s', async (agentType) => { httpRequestMock.query.agentType = agentType; + apiTestSetup.endpointAppContextMock.experimentalFeatures = { + ...apiTestSetup.endpointAppContextMock.experimentalFeatures, + responseActionsMSDefenderEndpointEnabled: true, + }; await apiTestSetup .getRegisteredVersionedRoute('get', AGENT_STATUS_ROUTE, '1') .routeHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts index 7ca24156b45fd..f62d43e88c51d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/agent/agent_status_handler.ts @@ -74,7 +74,10 @@ export const getAgentStatusRouteHandler = ( (agentType === 'sentinel_one' && !endpointContext.experimentalFeatures.responseActionsSentinelOneV1Enabled) || (agentType === 'crowdstrike' && - !endpointContext.experimentalFeatures.responseActionsCrowdstrikeManualHostIsolationEnabled) + !endpointContext.experimentalFeatures + .responseActionsCrowdstrikeManualHostIsolationEnabled) || + (agentType === 'microsoft_defender_endpoint' && + !endpointContext.experimentalFeatures.responseActionsMSDefenderEndpointEnabled) ) { return errorHandler( logger, diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/mocks.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/mocks.ts index e5f607e386efd..5c08982b9ade1 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/mocks.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/mocks.ts @@ -12,6 +12,7 @@ import { MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION, } from '@kbn/stack-connectors-plugin/common/microsoft_defender_endpoint/constants'; import type { + MicrosoftDefenderEndpointAgentListResponse, MicrosoftDefenderEndpointGetActionsResponse, MicrosoftDefenderEndpointMachine, MicrosoftDefenderEndpointMachineAction, @@ -59,6 +60,11 @@ const createMsConnectorActionsClientMock = (): ActionsClientMock => { data: createMicrosoftMachineMock(), }); + case MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION.GET_AGENT_LIST: + return responseActionsClientMock.createConnectorActionExecuteResponse({ + data: createMicrosoftGetMachineListApiResponseMock(), + }); + case MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION.ISOLATE_HOST: return responseActionsClientMock.createConnectorActionExecuteResponse({ data: createMicrosoftMachineActionMock({ type: 'Isolate' }), @@ -159,9 +165,23 @@ const createMicrosoftGetActionsApiResponseMock = }; }; +const createMicrosoftGetMachineListApiResponseMock = + (): MicrosoftDefenderEndpointAgentListResponse => { + return { + '@odata.context': 'some-context', + '@odata.count': 1, + total: 1, + page: 1, + pageSize: 0, + value: [createMicrosoftMachineMock()], + }; + }; + export const microsoftDefenderMock = { createConstructorOptions: createMsDefenderClientConstructorOptionsMock, + createMsConnectorActionsClient: createMsConnectorActionsClientMock, createMachineAction: createMicrosoftMachineActionMock, createMachine: createMicrosoftMachineMock, createGetActionsApiResponse: createMicrosoftGetActionsApiResponseMock, + createMicrosoftGetMachineListApiResponse: createMicrosoftGetMachineListApiResponseMock, }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/pending_actions_summary.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/pending_actions_summary.ts index ba7c063fa2937..e9a54e8b214fa 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/pending_actions_summary.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/pending_actions_summary.ts @@ -76,8 +76,10 @@ export const getPendingActionsSummary = async ( setActionAsPending(unExpiredAction.command); } else if ( unExpiredAction.wasSuccessful && - (unExpiredAction.command === 'isolate' || unExpiredAction.command === 'unisolate') + (unExpiredAction.command === 'isolate' || unExpiredAction.command === 'unisolate') && + unExpiredAction.agentType === 'endpoint' ) { + // For Elastic Defend (endpoint): // For Isolate and Un-Isolate, we want to ensure that the isolation status being reported in the // endpoint metadata was received after the action was completed. This is to ensure that the // isolation status being reported in the UI remains as accurate as possible. diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/get_agent_status_client.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/get_agent_status_client.ts index 6adbbb05d3408..065bbdc6f312f 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/get_agent_status_client.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/get_agent_status_client.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { MicrosoftDefenderEndpointAgentStatusClient } from './microsoft_defender_endpoint'; import { CrowdstrikeAgentStatusClient } from './crowdstrike/crowdstrike_agent_status_client'; import { SentinelOneAgentStatusClient } from './sentinel_one/sentinel_one_agent_status_client'; import type { AgentStatusClientInterface } from './lib/types'; @@ -30,6 +31,8 @@ export const getAgentStatusClient = ( return new SentinelOneAgentStatusClient(constructorOptions); case 'crowdstrike': return new CrowdstrikeAgentStatusClient(constructorOptions); + case 'microsoft_defender_endpoint': + return new MicrosoftDefenderEndpointAgentStatusClient(constructorOptions); default: throw new UnsupportedAgentTypeError( `Agent type [${agentType}] does not support agent status` diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/index.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/index.ts new file mode 100644 index 0000000000000..3a225a53b0b7f --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/index.ts @@ -0,0 +1,8 @@ +/* + * 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 * from './microsoft_defender_endpoint_agent_status_client'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/microsoft_defender_endpoint_agent_status_client.test.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/microsoft_defender_endpoint_agent_status_client.test.ts new file mode 100644 index 0000000000000..a2c3006249e6c --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/microsoft_defender_endpoint_agent_status_client.test.ts @@ -0,0 +1,155 @@ +/* + * 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 { getPendingActionsSummary as _getPendingActionsSummary } from '../../..'; +import { createMockEndpointAppContextService } from '../../../../mocks'; +import { MicrosoftDefenderEndpointAgentStatusClient } from './microsoft_defender_endpoint_agent_status_client'; +import { microsoftDefenderMock } from '../../../actions/clients/microsoft/defender/endpoint/mocks'; +import type { AgentStatusClientOptions } from '../lib/base_agent_status_client'; +import { HostStatus } from '../../../../../../common/endpoint/types'; +import { responseActionsClientMock } from '../../../actions/clients/mocks'; + +jest.mock('../../../actions/pending_actions_summary', () => { + const realModule = jest.requireActual('../../../actions/pending_actions_summary'); + return { + ...realModule, + getPendingActionsSummary: jest.fn(realModule.getPendingActionsSummary), + }; +}); + +const getPendingActionsSummaryMock = _getPendingActionsSummary as jest.Mock; + +describe('Microsoft Defender Agent Status client', () => { + let clientConstructorOptions: AgentStatusClientOptions; + let msAgentStatusClientMock: MicrosoftDefenderEndpointAgentStatusClient; + + beforeEach(() => { + const endpointAppContextServiceMock = createMockEndpointAppContextService(); + const soClient = endpointAppContextServiceMock.savedObjects.createInternalScopedSoClient({ + readonly: false, + }); + + getPendingActionsSummaryMock.mockResolvedValue([ + { + agent_id: '1-2-3', + pending_actions: { isolate: 1 }, + }, + ]); + + clientConstructorOptions = { + endpointService: endpointAppContextServiceMock, + connectorActionsClient: microsoftDefenderMock.createMsConnectorActionsClient(), + esClient: endpointAppContextServiceMock.getInternalEsClient(), + soClient, + }; + + msAgentStatusClientMock = new MicrosoftDefenderEndpointAgentStatusClient( + clientConstructorOptions + ); + }); + + afterEach(() => { + getPendingActionsSummaryMock.mockReset(); + }); + + it('should error if instantiated with no Connector Actions Client', () => { + clientConstructorOptions.connectorActionsClient = undefined; + + expect(() => new MicrosoftDefenderEndpointAgentStatusClient(clientConstructorOptions)).toThrow( + 'connectorActionsClient is required to create an instance of MicrosoftDefenderEndpointAgentStatusClient' + ); + }); + + it('should call connector to get list of machines from MS using the IDs passed in', async () => { + await msAgentStatusClientMock.getAgentStatuses(['1-2-3', 'foo']); + + expect(clientConstructorOptions.connectorActionsClient?.execute).toHaveBeenCalledWith( + expect.objectContaining({ + params: { + subAction: 'getAgentList', + subActionParams: { id: ['1-2-3', 'foo'] }, + }, + }) + ); + }); + + it('should retrieve a list of pending response actions with the IDs that were passed in', async () => { + await msAgentStatusClientMock.getAgentStatuses(['1-2-3', 'foo']); + + expect(getPendingActionsSummaryMock).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + expect.anything(), + ['1-2-3', 'foo'] + ); + }); + + it('should return the expected agent status records', async () => { + await expect(msAgentStatusClientMock.getAgentStatuses(['1-2-3', 'foo'])).resolves.toEqual({ + '1-2-3': { + agentId: '1-2-3', + agentType: 'microsoft_defender_endpoint', + found: true, + isolated: false, + lastSeen: '2018-08-02T14:55:03.7791856Z', + pendingActions: { + isolate: 1, + }, + status: 'healthy', + }, + foo: { + agentId: 'foo', + agentType: 'microsoft_defender_endpoint', + found: false, + isolated: false, + lastSeen: '', + pendingActions: {}, + status: 'unenrolled', + }, + }); + }); + + it.each` + msHealthStatus | expectedAgentStatus + ${'Active'} | ${HostStatus.HEALTHY} + ${'Inactive'} | ${HostStatus.INACTIVE} + ${'ImpairedCommunication'} | ${HostStatus.UNHEALTHY} + ${'NoSensorData'} | ${HostStatus.UNHEALTHY} + ${'NoSensorDataImpairedCommunication'} | ${HostStatus.UNHEALTHY} + ${'Unknown'} | ${HostStatus.UNENROLLED} + `( + 'should correctly map MS machine healthStatus of $msHealthStatus to agent status $expectedAgentStatus', + async ({ msHealthStatus, expectedAgentStatus }) => { + const priorExecuteMock = ( + clientConstructorOptions.connectorActionsClient?.execute as jest.Mock + ).getMockImplementation(); + (clientConstructorOptions.connectorActionsClient?.execute as jest.Mock).mockImplementation( + async (options) => { + if (options.params.subAction === 'getAgentList') { + const machineListResponse = + microsoftDefenderMock.createMicrosoftGetMachineListApiResponse(); + machineListResponse.value[0].healthStatus = msHealthStatus; + + return responseActionsClientMock.createConnectorActionExecuteResponse({ + data: machineListResponse, + }); + } + + if (priorExecuteMock) { + return priorExecuteMock(options); + } + } + ); + + await expect(msAgentStatusClientMock.getAgentStatuses(['1-2-3'])).resolves.toEqual({ + '1-2-3': expect.objectContaining({ + status: expectedAgentStatus, + }), + }); + } + ); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/microsoft_defender_endpoint_agent_status_client.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/microsoft_defender_endpoint_agent_status_client.ts new file mode 100644 index 0000000000000..6f4820290aa85 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/agent/clients/microsoft_defender_endpoint/microsoft_defender_endpoint_agent_status_client.ts @@ -0,0 +1,117 @@ +/* + * 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 { + MICROSOFT_DEFENDER_ENDPOINT_CONNECTOR_ID, + MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION, +} from '@kbn/stack-connectors-plugin/common/microsoft_defender_endpoint/constants'; +import { keyBy } from 'lodash'; +import type { + MicrosoftDefenderEndpointAgentListResponse, + MicrosoftDefenderEndpointMachine, +} from '@kbn/stack-connectors-plugin/common/microsoft_defender_endpoint/types'; +import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { AgentStatusClientError } from '../errors'; +import { getPendingActionsSummary, NormalizedExternalConnectorClient } from '../../..'; +import type { AgentStatusClientOptions } from '../lib/base_agent_status_client'; +import { AgentStatusClient } from '../lib/base_agent_status_client'; +import type { AgentStatusRecords } from '../../../../../../common/endpoint/types'; +import { HostStatus } from '../../../../../../common/endpoint/types'; + +export class MicrosoftDefenderEndpointAgentStatusClient extends AgentStatusClient { + protected readonly agentType: ResponseActionAgentType = 'microsoft_defender_endpoint'; + + protected readonly connectorActions: NormalizedExternalConnectorClient; + + constructor(options: AgentStatusClientOptions) { + super(options); + + if (!options.connectorActionsClient) { + throw new AgentStatusClientError( + 'connectorActionsClient is required to create an instance of MicrosoftDefenderEndpointAgentStatusClient' + ); + } + + this.connectorActions = new NormalizedExternalConnectorClient( + options.connectorActionsClient, + this.log + ); + this.connectorActions.setup(MICROSOFT_DEFENDER_ENDPOINT_CONNECTOR_ID); + } + + protected getAgentStatusFromMachineHealthStatus( + healthStatus: MicrosoftDefenderEndpointMachine['healthStatus'] | undefined + ): HostStatus { + // Definition of sensor health status can be found here: + // https://learn.microsoft.com/en-us/defender-endpoint/check-sensor-status + + switch (healthStatus) { + case 'Active': + return HostStatus.HEALTHY; + + case 'Inactive': + return HostStatus.INACTIVE; + + case 'ImpairedCommunication': + case 'NoSensorData': + case 'NoSensorDataImpairedCommunication': + return HostStatus.UNHEALTHY; + + default: + return HostStatus.UNENROLLED; + } + } + + public async getAgentStatuses(agentIds: string[]): Promise { + const esClient = this.options.esClient; + const metadataService = this.options.endpointService.getEndpointMetadataService(); + + try { + const [{ data: msMachineListResponse }, allPendingActions] = await Promise.all([ + this.connectorActions.execute({ + params: { + subAction: MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION.GET_AGENT_LIST, + subActionParams: { id: agentIds }, + }, + }), + + // Fetch pending actions summary + getPendingActionsSummary(esClient, metadataService, this.log, agentIds), + ]); + + const machinesById = keyBy(msMachineListResponse?.value ?? [], 'id'); + const pendingActionsByAgentId = keyBy(allPendingActions, 'agent_id'); + + return agentIds.reduce((acc, agentId) => { + const thisMachine = machinesById[agentId]; + const thisAgentPendingActions = pendingActionsByAgentId[agentId]; + + acc[agentId] = { + agentId, + agentType: this.agentType, + found: !!thisMachine, + // Unfortunately, it does not look like MS Defender has a way to determine + // if a host is isolated or not via API, so we just set this to false + isolated: false, + lastSeen: thisMachine?.lastSeen ?? '', + status: this.getAgentStatusFromMachineHealthStatus(thisMachine?.healthStatus), + pendingActions: thisAgentPendingActions?.pending_actions ?? {}, + }; + + return acc; + }, {}); + } catch (err) { + const error = new AgentStatusClientError( + `Failed to fetch Microsoft Defender for Endpoint agent status for agentIds: [${agentIds}], failed with: ${err.message}`, + 500, + err + ); + this.log.error(error); + throw error; + } + } +}