diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 50564d2fc404c..6a7d0efcc5082 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -61345,6 +61345,7 @@ components: - rule_default - endpoint - endpoint_trusted_apps + - endpoint_trusted_devices - endpoint_events - endpoint_host_isolation_exceptions - endpoint_blocklists @@ -63277,7 +63278,6 @@ components: - `query` (object, optional): Object containing a query filter which gets applied to an action and determines whether the action should run. - `kql` (string, required): A KQL string. - `filters` (array of objects, required): Array of filter objects, as defined in the `kbn-es-query` package. - type: object Security_Detections_API_RuleActionFrequency: description: The action frequency defines when the action runs (for example, only on rule execution or at specific time intervals). diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 394608ce3b985..e1e044256be12 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -73641,6 +73641,7 @@ components: - rule_default - endpoint - endpoint_trusted_apps + - endpoint_trusted_devices - endpoint_events - endpoint_host_isolation_exceptions - endpoint_blocklists @@ -75694,7 +75695,6 @@ components: - `query` (object, optional): Object containing a query filter which gets applied to an action and determines whether the action should run. - `kql` (string, required): A KQL string. - `filters` (array of objects, required): Array of filter objects, as defined in the `kbn-es-query` package. - type: object Security_Detections_API_RuleActionFrequency: description: The action frequency defines when the action runs (for example, only on rule execution or at specific time intervals). diff --git a/src/platform/packages/shared/deeplinks/security/deep_links.ts b/src/platform/packages/shared/deeplinks/security/deep_links.ts index 835845545b6a2..d8630e34e1677 100644 --- a/src/platform/packages/shared/deeplinks/security/deep_links.ts +++ b/src/platform/packages/shared/deeplinks/security/deep_links.ts @@ -73,6 +73,7 @@ export enum SecurityPageName { timelines = 'timelines', timelinesTemplates = 'timelines-templates', trustedApps = 'trusted_apps', + trustedDevices = 'trusted_devices', users = 'users', usersAll = 'users-all', usersAnomalies = 'users-anomalies', diff --git a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts index de52211457c88..d6e120a71f5be 100644 --- a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts +++ b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts @@ -446,6 +446,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D avcResults: `https://www.elastic.co/blog/elastic-security-av-comparatives-business-test`, bidirectionalIntegrations: `${ELASTIC_DOCS}solutions/security/endpoint-response-actions/third-party-response-actions`, trustedApps: `${ELASTIC_DOCS}solutions/security/manage-elastic-defend/trusted-applications`, + trustedDevices: `${ELASTIC_DOCS}solutions/security/manage-elastic-defend/trusted-applications`, // TODO: Update this link when trusted devices is available elasticAiFeatures: `${ELASTIC_DOCS}solutions/security/ai`, eventFilters: `${ELASTIC_DOCS}solutions/security/manage-elastic-defend/event-filters`, blocklist: `${ELASTIC_DOCS}solutions/security/manage-elastic-defend/blocklist`, diff --git a/src/platform/packages/shared/kbn-doc-links/src/types.ts b/src/platform/packages/shared/kbn-doc-links/src/types.ts index b31669f7c7582..3168f3f1f35e3 100644 --- a/src/platform/packages/shared/kbn-doc-links/src/types.ts +++ b/src/platform/packages/shared/kbn-doc-links/src/types.ts @@ -308,6 +308,7 @@ export interface DocLinks { readonly bidirectionalIntegrations: string; readonly thirdPartyLlmProviders: string; readonly trustedApps: string; + readonly trustedDevices: string; readonly elasticAiFeatures: string; readonly eventFilters: string; readonly eventMerging: string; diff --git a/x-pack/platform/plugins/shared/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap b/x-pack/platform/plugins/shared/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap index 66600807f06ca..09d9707e6ea1e 100644 --- a/x-pack/platform/plugins/shared/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap +++ b/x-pack/platform/plugins/shared/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap @@ -5754,6 +5754,7 @@ Object { "rule_default", "endpoint", "endpoint_trusted_apps", + "endpoint_trusted_devices", "endpoint_events", "endpoint_host_isolation_exceptions", "endpoint_blocklists", @@ -6425,6 +6426,7 @@ Object { "rule_default", "endpoint", "endpoint_trusted_apps", + "endpoint_trusted_devices", "endpoint_events", "endpoint_host_isolation_exceptions", "endpoint_blocklists", @@ -7162,6 +7164,7 @@ Object { "rule_default", "endpoint", "endpoint_trusted_apps", + "endpoint_trusted_devices", "endpoint_events", "endpoint_host_isolation_exceptions", "endpoint_blocklists", @@ -7814,6 +7817,7 @@ Object { "rule_default", "endpoint", "endpoint_trusted_apps", + "endpoint_trusted_devices", "endpoint_events", "endpoint_host_isolation_exceptions", "endpoint_blocklists", @@ -8522,6 +8526,7 @@ Object { "rule_default", "endpoint", "endpoint_trusted_apps", + "endpoint_trusted_devices", "endpoint_events", "endpoint_host_isolation_exceptions", "endpoint_blocklists", @@ -9231,6 +9236,7 @@ Object { "rule_default", "endpoint", "endpoint_trusted_apps", + "endpoint_trusted_devices", "endpoint_events", "endpoint_host_isolation_exceptions", "endpoint_blocklists", @@ -9940,6 +9946,7 @@ Object { "rule_default", "endpoint", "endpoint_trusted_apps", + "endpoint_trusted_devices", "endpoint_events", "endpoint_host_isolation_exceptions", "endpoint_blocklists", diff --git a/x-pack/solutions/security/packages/features/src/security/v3_features/kibana_sub_features.ts b/x-pack/solutions/security/packages/features/src/security/v3_features/kibana_sub_features.ts index 5689edcc2fe9f..4cb39c324bb9c 100644 --- a/x-pack/solutions/security/packages/features/src/security/v3_features/kibana_sub_features.ts +++ b/x-pack/solutions/security/packages/features/src/security/v3_features/kibana_sub_features.ts @@ -873,7 +873,7 @@ export const getSecurityV3SubFeaturesMap = ({ SecuritySubFeatureId.trustedApplications, enableSpaceAwarenessIfNeeded(trustedApplicationsSubFeature()), ], - ...((experimentalFeatures.trustedDevicesEnabled + ...((experimentalFeatures.trustedDevices ? [ [ SecuritySubFeatureId.trustedDevices, diff --git a/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_list/index.ts b/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_list/index.ts index 50273a9c55a99..add02c6ee1217 100644 --- a/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_list/index.ts +++ b/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/common/exception_list/index.ts @@ -11,6 +11,7 @@ export const exceptionListType = t.keyof({ detection: null, rule_default: null, endpoint: null, + endpoint_trusted_devices: null, endpoint_trusted_apps: null, endpoint_events: null, endpoint_host_isolation_exceptions: null, @@ -24,6 +25,7 @@ export enum ExceptionListTypeEnum { RULE_DEFAULT = 'rule_default', // rule default, cannot be shared ENDPOINT = 'endpoint', ENDPOINT_TRUSTED_APPS = 'endpoint', + ENDPOINT_TRUSTED_DEVICES = 'endpoint_trusted_devices', ENDPOINT_EVENTS = 'endpoint_events', ENDPOINT_HOST_ISOLATION_EXCEPTIONS = 'endpoint_host_isolation_exceptions', ENDPOINT_BLOCKLISTS = 'endpoint_blocklists', diff --git a/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/common/lists/index.test.ts b/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/common/lists/index.test.ts index f6b4e66290bcd..299e165608575 100644 --- a/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/common/lists/index.test.ts +++ b/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/common/lists/index.test.ts @@ -85,7 +85,7 @@ describe('Lists', () => { const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "rule_default" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions" | "endpoint_blocklists", namespace_type: "agnostic" | "single" |}>"', + 'Invalid value "1" supplied to "Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "rule_default" | "endpoint" | "endpoint_trusted_devices" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions" | "endpoint_blocklists", namespace_type: "agnostic" | "single" |}>"', ]); expect(message.schema).toEqual({}); }); @@ -116,8 +116,8 @@ describe('Lists', () => { const message = pipe(decoded, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "1" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "rule_default" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions" | "endpoint_blocklists", namespace_type: "agnostic" | "single" |}> | undefined)"', - 'Invalid value "[1]" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "rule_default" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions" | "endpoint_blocklists", namespace_type: "agnostic" | "single" |}> | undefined)"', + 'Invalid value "1" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "rule_default" | "endpoint" | "endpoint_trusted_devices" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions" | "endpoint_blocklists", namespace_type: "agnostic" | "single" |}> | undefined)"', + 'Invalid value "[1]" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "rule_default" | "endpoint" | "endpoint_trusted_devices" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions" | "endpoint_blocklists", namespace_type: "agnostic" | "single" |}> | undefined)"', ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/request/internal/create_exception_list_schema/index.ts b/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/request/internal/create_exception_list_schema/index.ts index ff8f9670a4e03..018e2804bfe5f 100644 --- a/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/request/internal/create_exception_list_schema/index.ts +++ b/x-pack/solutions/security/packages/kbn-securitysolution-io-ts-list-types/src/request/internal/create_exception_list_schema/index.ts @@ -21,6 +21,7 @@ export const internalCreateExceptionListSchema = t.intersection([ endpoint_events: null, endpoint_host_isolation_exceptions: null, endpoint_blocklists: null, + endpoint_trusted_devices: null, }), }) ), diff --git a/x-pack/solutions/security/packages/kbn-securitysolution-list-constants/index.ts b/x-pack/solutions/security/packages/kbn-securitysolution-list-constants/index.ts index 32c5acbcdb886..12d5299ada969 100644 --- a/x-pack/solutions/security/packages/kbn-securitysolution-list-constants/index.ts +++ b/x-pack/solutions/security/packages/kbn-securitysolution-list-constants/index.ts @@ -80,6 +80,11 @@ export const ENDPOINT_ARTIFACT_LISTS = deepFreeze({ name: 'Endpoint Security Trusted Apps List', description: 'Endpoint Security Trusted Apps List', }, + trustedDevices: { + id: 'endpoint_trusted_devices', + name: 'Endpoint Security Trusted Devices List', + description: 'Endpoint Security Trusted Devices List', + }, eventFilters: { id: 'endpoint_event_filters', name: 'Endpoint Security Event Filters List', diff --git a/x-pack/solutions/security/packages/navigation/src/navigation_tree/assets_navigation_tree.ts b/x-pack/solutions/security/packages/navigation/src/navigation_tree/assets_navigation_tree.ts index 8d13869bd2fec..90e283fedbeb6 100644 --- a/x-pack/solutions/security/packages/navigation/src/navigation_tree/assets_navigation_tree.ts +++ b/x-pack/solutions/security/packages/navigation/src/navigation_tree/assets_navigation_tree.ts @@ -60,6 +60,10 @@ export const createAssetsNavigationTree = (core: CoreStart): NodeDefinition => ( id: SecurityPageName.trustedApps, link: securityLink(SecurityPageName.trustedApps), }, + { + id: SecurityPageName.trustedDevices, + link: securityLink(SecurityPageName.trustedDevices), + }, { id: SecurityPageName.eventFilters, link: securityLink(SecurityPageName.eventFilters), diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/alerts/schema.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/alerts/schema.ts index d33a6dd2a98f9..a7567318bf4c1 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/alerts/schema.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/alerts/schema.ts @@ -271,6 +271,7 @@ type DetectionAlertSchema = { | 'rule_default' | 'endpoint' | 'endpoint_trusted_apps' + | 'endpoint_trusted_devices' | 'endpoint_events' | 'endpoint_host_isolation_exceptions' | 'endpoint_blocklists'; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts index 51f63b8326fa1..d1e85c5b8e807 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts @@ -576,7 +576,6 @@ export const RuleActionFrequency = z.object({ - `query` (object, optional): Object containing a query filter which gets applied to an action and determines whether the action should run. - `kql` (string, required): A KQL string. - `filters` (array of objects, required): Array of filter objects, as defined in the `kbn-es-query` package. - */ export type RuleActionAlertsFilter = z.infer; @@ -669,6 +668,7 @@ export const ExceptionListType = z.enum([ 'rule_default', 'endpoint', 'endpoint_trusted_apps', + 'endpoint_trusted_devices', 'endpoint_events', 'endpoint_host_isolation_exceptions', 'endpoint_blocklists', diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml index 1080c39e0c248..3dc6e495373b3 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml @@ -475,23 +475,23 @@ components: RelatedIntegration: type: object description: | - Related integration is a potential dependency of a rule. It's assumed that if the user installs - one of the related integrations of a rule, the rule might start to work properly because it will - have source events (generated by this integration) potentially matching the rule's query. + Related integration is a potential dependency of a rule. It's assumed that if the user installs + one of the related integrations of a rule, the rule might start to work properly because it will + have source events (generated by this integration) potentially matching the rule's query. - NOTE: Proper work is not guaranteed, because a related integration, if installed, can be - configured differently or generate data that is not necessarily relevant for this rule. + NOTE: Proper work is not guaranteed, because a related integration, if installed, can be + configured differently or generate data that is not necessarily relevant for this rule. - Related integration is a combination of a Fleet package and (optionally) one of the - package's "integrations" that this package contains. It is represented by 3 properties: + Related integration is a combination of a Fleet package and (optionally) one of the + package's "integrations" that this package contains. It is represented by 3 properties: - - `package`: name of the package (required, unique id) - - `version`: version of the package (required, semver-compatible) - - `integration`: name of the integration of this package (optional, id within the package) + - `package`: name of the package (required, unique id) + - `version`: version of the package (required, semver-compatible) + - `integration`: name of the integration of this package (optional, id within the package) - There are Fleet packages like `windows` that contain only one integration; in this case, - `integration` should be unspecified. There are also packages like `aws` and `azure` that contain - several integrations; in this case, `integration` should be specified. + There are Fleet packages like `windows` that contain only one integration; in this case, + `integration` should be unspecified. There are also packages like `aws` and `azure` that contain + several integrations; in this case, `integration` should be specified. properties: package: $ref: '../../../model/primitives.schema.yaml#/components/schemas/NonEmptyString' @@ -578,7 +578,7 @@ components: - `query` (object, optional): Object containing a query filter which gets applied to an action and determines whether the action should run. - `kql` (string, required): A KQL string. - `filters` (array of objects, required): Array of filter objects, as defined in the `kbn-es-query` package. - + RuleActionParams: type: object description: | @@ -669,6 +669,7 @@ components: - rule_default - endpoint - endpoint_trusted_apps + - endpoint_trusted_devices - endpoint_events - endpoint_host_isolation_exceptions - endpoint_blocklists diff --git a/x-pack/solutions/security/plugins/security_solution/common/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/constants.ts index 2609c8ece74c9..b13ddfed88b74 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/constants.ts @@ -118,6 +118,7 @@ export const THREAT_INTELLIGENCE_PATH = '/threat_intelligence' as const; export const ENDPOINTS_PATH = `${MANAGEMENT_PATH}/endpoints` as const; export const POLICIES_PATH = `${MANAGEMENT_PATH}/policy` as const; export const TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/trusted_apps` as const; +export const TRUSTED_DEVICES_PATH = `${MANAGEMENT_PATH}/trusted_devices` as const; export const EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/event_filters` as const; export const HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/host_isolation_exceptions` as const; diff --git a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts index ad84556328899..fdb7ed270c202 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts @@ -292,6 +292,7 @@ export const allowedExperimentalValues = Object.freeze({ * Allows users to manage trusted USB and external devices */ trustedDevices: false, + /** * Enables the ability to import and migration dashboards through automatic migration service */ diff --git a/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml index 5188f6ae86b08..75f32f90a262d 100644 --- a/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml @@ -6273,6 +6273,7 @@ components: - rule_default - endpoint - endpoint_trusted_apps + - endpoint_trusted_devices - endpoint_events - endpoint_host_isolation_exceptions - endpoint_blocklists @@ -8527,7 +8528,6 @@ components: gets applied to an action and determines whether the action should run. - `kql` (string, required): A KQL string. - `filters` (array of objects, required): Array of filter objects, as defined in the `kbn-es-query` package. - type: object RuleActionFrequency: description: >- diff --git a/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml index d12a7e45a2747..3f07d678756dc 100644 --- a/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml @@ -5603,6 +5603,7 @@ components: - rule_default - endpoint - endpoint_trusted_apps + - endpoint_trusted_devices - endpoint_events - endpoint_host_isolation_exceptions - endpoint_blocklists @@ -7736,7 +7737,6 @@ components: gets applied to an action and determines whether the action should run. - `kql` (string, required): A KQL string. - `filters` (array of objects, required): Array of filter objects, as defined in the `kbn-es-query` package. - type: object RuleActionFrequency: description: >- diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/app/translations.ts index f33e6a4394fbd..260a9d7957a2e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/app/translations.ts @@ -168,6 +168,12 @@ export const TRUSTED_APPLICATIONS = i18n.translate( defaultMessage: 'Trusted applications', } ); +export const TRUSTED_DEVICES = i18n.translate( + 'xpack.securitySolution.search.administration.trustedDevices', + { + defaultMessage: 'Trusted devices', + } +); export const EVENT_FILTERS = i18n.translate( 'xpack.securitySolution.search.administration.eventFilters', { diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/common/breadcrumbs.ts b/x-pack/solutions/security/plugins/security_solution/public/management/common/breadcrumbs.ts index 82b321abdcd6e..ea7b68125c2b7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/common/breadcrumbs.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/common/breadcrumbs.ts @@ -7,7 +7,13 @@ import type { ChromeBreadcrumb } from '@kbn/core/public'; import { AdministrationSubTab } from '../types'; -import { ENDPOINTS_TAB, EVENT_FILTERS_TAB, POLICIES_TAB, TRUSTED_APPS_TAB } from './translations'; +import { + ENDPOINTS_TAB, + EVENT_FILTERS_TAB, + POLICIES_TAB, + TRUSTED_APPS_TAB, + TRUSTED_DEVICES_TAB, +} from './translations'; import type { AdministrationRouteSpyState } from '../../common/utils/route/types'; import { HOST_ISOLATION_EXCEPTIONS, @@ -21,6 +27,7 @@ const TabNameMappedToI18nKey: Record = { [AdministrationSubTab.endpoints]: ENDPOINTS_TAB, [AdministrationSubTab.policies]: POLICIES_TAB, [AdministrationSubTab.trustedApps]: TRUSTED_APPS_TAB, + [AdministrationSubTab.trustedDevices]: TRUSTED_DEVICES_TAB, [AdministrationSubTab.eventFilters]: EVENT_FILTERS_TAB, [AdministrationSubTab.hostIsolationExceptions]: HOST_ISOLATION_EXCEPTIONS, [AdministrationSubTab.blocklist]: BLOCKLIST, diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/common/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/management/common/constants.ts index b319adbd0faeb..6dfeace27c2f3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/common/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/common/constants.ts @@ -14,6 +14,7 @@ export const MANAGEMENT_ROUTING_ENDPOINTS_PATH = `${MANAGEMENT_PATH}/:tabName(${ export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})`; export const MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/settings`; export const MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/trustedApps`; +export const MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_DEVICES_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/trustedDevices`; export const MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/eventFilters`; export const MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/hostIsolationExceptions`; export const MANAGEMENT_ROUTING_POLICY_DETAILS_BLOCKLISTS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/blocklists`; @@ -22,6 +23,7 @@ export const MANAGEMENT_ROUTING_NOTES_PATH = `${MANAGEMENT_PATH}/:tabName(${Admi /** @deprecated use the paths defined above instead */ export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH_OLD = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`; export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`; +export const MANAGEMENT_ROUTING_TRUSTED_DEVICES_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.trustedDevices})`; export const MANAGEMENT_ROUTING_EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.eventFilters})`; export const MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.hostIsolationExceptions})`; export const MANAGEMENT_ROUTING_BLOCKLIST_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.blocklist})`; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/common/routing.ts b/x-pack/solutions/security/plugins/security_solution/public/management/common/routing.ts index dc662a68dcd29..92e9b51b84537 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/common/routing.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/common/routing.ts @@ -31,7 +31,9 @@ import { MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_PROTECTION_UPDATES_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, + MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_DEVICES_PATH, MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, + MANAGEMENT_ROUTING_TRUSTED_DEVICES_PATH, } from './constants'; import { isDefaultOrMissing, getArtifactListPageUrlPath } from './url_routing'; @@ -137,6 +139,18 @@ export const getPolicyTrustedAppsPath = (policyId: string, search?: string) => { })}${appendSearch(search)}`; }; +export const getPolicyTrustedDevicesPath = ( + policyId: string, + location?: Partial +) => { + return `${generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_DEVICES_PATH, { + tabName: AdministrationSubTab.policies, + policyId, + })}${appendSearch( + querystring.stringify(normalizePolicyDetailsArtifactsListPageLocation(location)) + )}`; +}; + export const getPolicyEventFiltersPath = ( policyId: string, location?: Partial @@ -212,6 +226,16 @@ export const getTrustedAppsListPath = (location?: Partial +): string => { + const path = generatePath(MANAGEMENT_ROUTING_TRUSTED_DEVICES_PATH, { + tabName: AdministrationSubTab.trustedDevices, + }); + + return getArtifactListPageUrlPath(path, location); +}; + export const extractPolicyDetailsArtifactsListPageLocation = ( query: querystring.ParsedUrlQuery ): PolicyDetailsArtifactsPageLocation => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/common/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/common/translations.ts index b4977fb403afb..8cfd1da06a3f8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/common/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/common/translations.ts @@ -20,6 +20,10 @@ export const TRUSTED_APPS_TAB = i18n.translate('xpack.securitySolution.trustedAp defaultMessage: 'Trusted applications', }); +export const TRUSTED_DEVICES_TAB = i18n.translate('xpack.securitySolution.trustedDevicesTab', { + defaultMessage: 'Trusted devices', +}); + export const EVENT_FILTERS_TAB = i18n.translate('xpack.securitySolution.eventFiltersTab', { defaultMessage: 'Event filters', }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/cypress/tasks/artifacts.ts b/x-pack/solutions/security/plugins/security_solution/public/management/cypress/tasks/artifacts.ts index 394e43e38ebc1..4a9930222853d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/cypress/tasks/artifacts.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/cypress/tasks/artifacts.ts @@ -57,6 +57,7 @@ const removeExceptionsListPromise = (listId: string) => { const ENDPOINT_ARTIFACT_LIST_TYPES = { [ENDPOINT_ARTIFACT_LISTS.trustedApps.id]: ExceptionListTypeEnum.ENDPOINT, + [ENDPOINT_ARTIFACT_LISTS.trustedDevices.id]: ExceptionListTypeEnum.ENDPOINT_TRUSTED_DEVICES, [ENDPOINT_ARTIFACT_LISTS.eventFilters.id]: ExceptionListTypeEnum.ENDPOINT_EVENTS, [ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id]: ExceptionListTypeEnum.ENDPOINT_HOST_ISOLATION_EXCEPTIONS, diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/links.test.ts b/x-pack/solutions/security/plugins/security_solution/public/management/links.test.ts index a21e14db8700d..143523a2e4393 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/links.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/links.test.ts @@ -93,7 +93,8 @@ describe('links', () => { SecurityPageName.hostIsolationExceptions, SecurityPageName.policies, SecurityPageName.responseActionsHistory, - SecurityPageName.trustedApps + SecurityPageName.trustedApps, + SecurityPageName.trustedDevices ) ); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/links.ts b/x-pack/solutions/security/plugins/security_solution/public/management/links.ts index c9f541317d73f..992d0fbcd5dee 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/links.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/links.ts @@ -26,6 +26,7 @@ import { SecurityPageName, SECURITY_FEATURE_ID, TRUSTED_APPS_PATH, + TRUSTED_DEVICES_PATH, } from '../../common/constants'; import { BLOCKLIST, @@ -36,6 +37,7 @@ import { POLICIES, RESPONSE_ACTIONS_HISTORY, TRUSTED_APPLICATIONS, + TRUSTED_DEVICES, ENTITY_ANALYTICS_RISK_SCORE, ENTITY_STORE, } from '../app/translations'; @@ -72,6 +74,7 @@ const categories = [ SecurityPageName.endpoints, SecurityPageName.policies, SecurityPageName.trustedApps, + SecurityPageName.trustedDevices, SecurityPageName.eventFilters, SecurityPageName.hostIsolationExceptions, SecurityPageName.blocklist, @@ -139,6 +142,21 @@ export const links: LinkItem = { skipUrlState: true, hideTimeline: true, }, + { + id: SecurityPageName.trustedDevices, + title: TRUSTED_DEVICES, + description: i18n.translate('xpack.securitySolution.appLinks.trustedDevicesDescription', { + defaultMessage: + 'Add a trusted device to improve performance or alleviate compatibility issues.', + }), + landingIcon: IconDashboards, + path: TRUSTED_DEVICES_PATH, + skipUrlState: true, + hideTimeline: true, + experimentalKey: 'trustedDevices', + capabilities: [`${SECURITY_FEATURE_ID}.readTrustedDevices`], + licenseType: 'enterprise', + }, { id: SecurityPageName.eventFilters, title: EVENT_FILTERS, @@ -230,6 +248,7 @@ export const getManagementFilteredLinks = async ( canReadHostIsolationExceptions, canReadEndpointList, canReadTrustedApplications, + canReadTrustedDevices, canReadEventFilters, canReadBlocklist, canReadPolicyManagement, @@ -267,6 +286,10 @@ export const getManagementFilteredLinks = async ( linksToExclude.push(SecurityPageName.trustedApps); } + if (!canReadTrustedDevices) { + linksToExclude.push(SecurityPageName.trustedDevices); + } + if (!canReadEventFilters) { linksToExclude.push(SecurityPageName.eventFilters); } diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/index.tsx index 5ebaf0f970582..14a29fd89dd4f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/index.tsx @@ -20,6 +20,7 @@ import { MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH, MANAGEMENT_ROUTING_POLICIES_PATH, MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, + MANAGEMENT_ROUTING_TRUSTED_DEVICES_PATH, MANAGEMENT_ROUTING_BLOCKLIST_PATH, MANAGEMENT_ROUTING_RESPONSE_ACTIONS_HISTORY_PATH, MANAGEMENT_ROUTING_NOTES_PATH, @@ -38,6 +39,7 @@ import { BlocklistContainer } from './blocklist'; import { ResponseActionsContainer } from './response_actions'; import { PrivilegedRoute } from '../components/privileged_route'; import { SecurityRoutePageWrapper } from '../../common/components/security_route_page_wrapper'; +import { TrustedDevicesContainer } from './trusted_devices'; const EndpointTelemetry = () => ( @@ -60,6 +62,13 @@ const TrustedAppTelemetry = () => ( ); +const TrustedDevicesTelemetry = () => ( + + + + +); + const EventFilterTelemetry = () => ( @@ -92,11 +101,14 @@ export const ManagementContainer = memo(() => { 'securitySolutionNotesDisabled' ); + const trustedDevicesEnabled = useIsExperimentalFeatureEnabled('trustedDevices'); + const { loading, canReadPolicyManagement, canReadBlocklist, canReadTrustedApplications, + canReadTrustedDevices, canReadEventFilters, canReadActionsLogManagement, canReadEndpointList, @@ -141,6 +153,13 @@ export const ManagementContainer = memo(() => { component={TrustedAppTelemetry} hasPrivilege={canReadTrustedApplications} /> + {trustedDevicesEnabled && ( + + )} { const isProtectionUpdatesFeatureEnabled = useIsExperimentalFeatureEnabled( 'protectionUpdatesEnabled' ); + const isTrustedDevicesFeatureEnabled = useIsExperimentalFeatureEnabled('trustedDevices'); const isEnterprise = useLicense().isEnterprise(); const isProtectionUpdatesEnabled = isEnterprise && isProtectionUpdatesFeatureEnabled; @@ -38,6 +40,9 @@ export const PolicyContainer = memo(() => { path={[ MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, + ...(isTrustedDevicesFeatureEnabled + ? [MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_DEVICES_PATH] + : []), MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_BLOCKLISTS_PATH, diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_common_selectors.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_common_selectors.ts index eb081d10d6375..b051b2e4bcf29 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_common_selectors.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_common_selectors.ts @@ -14,6 +14,7 @@ import { MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_BLOCKLISTS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_PROTECTION_UPDATES_PATH, + MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_DEVICES_PATH, } from '../../../../../common/constants'; import type { PolicyDetailsSelector, PolicyDetailsState } from '../../../types'; @@ -53,6 +54,19 @@ export const isOnPolicyTrustedAppsView: PolicyDetailsSelector = createS } ); +/** Returns a boolean of whether the user is on the policy trusted devices page or not */ +export const isOnPolicyTrustedDevicesView: PolicyDetailsSelector = createSelector( + getUrlLocationPathname, + (pathname) => { + return ( + matchPath(pathname ?? '', { + path: MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_DEVICES_PATH, + exact: true, + }) !== null + ); + } +); + /** Returns a boolean of whether the user is on the policy event filters page or not */ export const isOnPolicyEventFiltersView: PolicyDetailsSelector = createSelector( getUrlLocationPathname, diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts index 2cc4fed6517bb..c9af3bb84a9f1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts @@ -25,6 +25,7 @@ import { MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_BLOCKLISTS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_PROTECTION_UPDATES_PATH, + MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_DEVICES_PATH, } from '../../../../../common/constants'; import type { ManagementRoutePolicyDetailsParams } from '../../../../../types'; import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy'; @@ -33,6 +34,7 @@ import { isOnPolicyEventFiltersView, isOnHostIsolationExceptionsView, isOnPolicyFormView, + isOnPolicyTrustedDevicesView, isOnBlocklistsView, isOnProtectionUpdatesView, } from './policy_common_selectors'; @@ -96,6 +98,7 @@ export const needsToRefresh = (state: Immutable): boolean => export const isOnPolicyDetailsPage = (state: Immutable) => isOnPolicyFormView(state) || isOnPolicyTrustedAppsView(state) || + isOnPolicyTrustedDevicesView(state) || isOnPolicyEventFiltersView(state) || isOnHostIsolationExceptionsView(state) || isOnBlocklistsView(state) || @@ -115,6 +118,7 @@ export const policyIdFromParams: (state: Immutable) => strin path: [ MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, + MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_DEVICES_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_BLOCKLISTS_PATH, diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx index 6e72dc260d64a..4822f25ef182a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/endpoint_package_custom_extension.tsx @@ -23,13 +23,19 @@ import { getEventFiltersListPath, getHostIsolationExceptionsListPath, getTrustedAppsListPath, + getTrustedDevicesListPath, } from '../../../../../common/routing'; import { BLOCKLISTS_LABELS, EVENT_FILTERS_LABELS, HOST_ISOLATION_EXCEPTIONS_LABELS, TRUSTED_APPS_LABELS, + TRUSTED_DEVICES_LABELS, } from './translations'; +import { useLicense } from '../../../../../../common/hooks/use_license'; + +import { TrustedDevicesApiClient } from '../../../../trusted_devices/service/api_client'; +import { useIsExperimentalFeatureEnabled } from '../../../../../../common/hooks/use_experimental_features'; const TrustedAppsArtifactCard = memo((props) => { const http = useHttp(); @@ -50,6 +56,25 @@ const TrustedAppsArtifactCard = memo((prop }); TrustedAppsArtifactCard.displayName = 'TrustedAppsArtifactCard'; +const TrustedDevicesArtifactCard = memo((props) => { + const http = useHttp(); + const trustedDevicesApiClientInstance = useMemo( + () => TrustedDevicesApiClient.getInstance(http), + [http] + ); + + return ( + + ); +}); +TrustedDevicesArtifactCard.displayName = 'TrustedDevicesArtifactCard'; + const EventFiltersArtifactCard = memo((props) => { const http = useHttp(); const eventFiltersApiClientInstance = useMemo( @@ -67,6 +92,7 @@ const EventFiltersArtifactCard = memo((pro /> ); }); + EventFiltersArtifactCard.displayName = 'EventFiltersArtifactCard'; const HostIsolationExceptionsArtifactCard = memo((props) => { @@ -115,9 +141,13 @@ export const EndpointPackageCustomExtension = memo { if (loading) { @@ -137,6 +167,13 @@ export const EndpointPackageCustomExtension = memo )} + {trustedDevicesVisible && ( + <> + + + + )} + {canReadEventFilters && ( <> @@ -155,13 +192,14 @@ export const EndpointPackageCustomExtension = memo ); }, [ - canReadBlocklist, - canReadEventFilters, - canReadTrustedApplications, - canReadHostIsolationExceptions, loading, - props, userCanAccessContent, + canReadTrustedApplications, + props, + trustedDevicesVisible, + canReadEventFilters, + canReadHostIsolationExceptions, + canReadBlocklist, ]); if (loading) { diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/translations.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/translations.tsx index 31d9f6256608b..b0b4d6b742902 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/translations.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/translations.tsx @@ -26,6 +26,23 @@ export const TRUSTED_APPS_LABELS = { ), }; +export const TRUSTED_DEVICES_LABELS = { + artifactsSummaryApiError: (error: string) => + i18n.translate( + 'xpack.securitySolution.endpoint.fleetCustomExtension.trustedDevicesSummary.error', + { + defaultMessage: 'There was an error trying to fetch trusted devices stats: "{error}"', + values: { error }, + } + ), + cardTitle: ( + + ), +}; + export const EVENT_FILTERS_LABELS = { artifactsSummaryApiError: (error: string) => i18n.translate( diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension/components/endpoint_policy_artifact_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension/components/endpoint_policy_artifact_cards.tsx index bbb4258ccc2da..229a7ba302690 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension/components/endpoint_policy_artifact_cards.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension/components/endpoint_policy_artifact_cards.tsx @@ -14,9 +14,11 @@ import { EVENT_FILTERS_LABELS, HOST_ISOLATION_EXCEPTIONS_LABELS, TRUSTED_APPS_LABELS, + TRUSTED_DEVICES_LABELS, } from '../translations'; import { useCanAccessSomeArtifacts } from '../../hooks/use_can_access_some_artifacts'; import { BlocklistsApiClient } from '../../../../../blocklist/services'; +import { TrustedDevicesApiClient } from '../../../../../trusted_devices/service/api_client'; import { HostIsolationExceptionsApiClient } from '../../../../../host_isolation_exceptions/host_isolation_exceptions_api_client'; import { EventFiltersApiClient } from '../../../../../event_filters/service/api_client'; import { TrustedAppsApiClient } from '../../../../../trusted_apps/service'; @@ -28,7 +30,9 @@ import { getPolicyEventFiltersPath, getPolicyHostIsolationExceptionsPath, getPolicyTrustedAppsPath, + getPolicyTrustedDevicesPath, getTrustedAppsListPath, + getTrustedDevicesListPath, } from '../../../../../../common/routing'; import { SEARCHABLE_FIELDS as TRUSTED_APPS_SEARCHABLE_FIELDS } from '../../../../../trusted_apps/constants'; import type { FleetIntegrationArtifactCardProps } from './fleet_integration_artifacts_card'; @@ -36,7 +40,10 @@ import { FleetIntegrationArtifactsCard } from './fleet_integration_artifacts_car import { SEARCHABLE_FIELDS as EVENT_FILTERS_SEARCHABLE_FIELDS } from '../../../../../event_filters/constants'; import { SEARCHABLE_FIELDS as HOST_ISOLATION_EXCEPTIONS_SEARCHABLE_FIELDS } from '../../../../../host_isolation_exceptions/constants'; import { SEARCHABLE_FIELDS as BLOCKLIST_SEARCHABLE_FIELDS } from '../../../../../blocklist/constants'; +import { SEARCHABLE_FIELDS as TRUSTED_DEVICES_SEARCHABLE_FIELDS } from '../../../../../trusted_devices/constants'; +import { useIsExperimentalFeatureEnabled } from '../../../../../../../common/hooks/use_experimental_features'; import { useHttp } from '../../../../../../../common/lib/kibana'; +import { useLicense } from '../../../../../../../common/hooks/use_license'; interface PolicyArtifactCardProps { policyId: string; @@ -72,6 +79,36 @@ const TrustedAppsPolicyCard = memo(({ policyId }) => { }); TrustedAppsPolicyCard.displayName = 'TrustedAppsPolicyCard'; +const TrustedDevicesPolicyCard = memo(({ policyId }) => { + const http = useHttp(); + const trustedDevicesApiClientInstance = useMemo( + () => TrustedDevicesApiClient.getInstance(http), + [http] + ); + const { canReadPolicyManagement } = useUserPrivileges().endpointPrivileges; + + const getArtifactPathHandler: FleetIntegrationArtifactCardProps['getArtifactsPath'] = + useCallback(() => { + if (canReadPolicyManagement) { + return getPolicyTrustedDevicesPath(policyId); + } + + return getTrustedDevicesListPath({ includedPolicies: `${policyId},global` }); + }, [canReadPolicyManagement, policyId]); + + return ( + + ); +}); +TrustedDevicesPolicyCard.displayName = 'TrustedDevicesPolicyCard'; + const EventFiltersPolicyCard = memo(({ policyId }) => { const http = useHttp(); const eventFiltersApiClientInstance = useMemo( @@ -174,8 +211,13 @@ export const EndpointPolicyArtifactCards = memo; @@ -205,6 +247,13 @@ export const EndpointPolicyArtifactCards = memo )} + {trustedDevicesVisible && ( + <> + + + + )} + {canReadEventFilters && ( <> diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension/translations.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension/translations.tsx index c99ea5bf3b303..2067997c514cd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension/translations.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension/translations.tsx @@ -95,3 +95,26 @@ export const TRUSTED_APPS_LABELS = { /> ), }; + +export const TRUSTED_DEVICES_LABELS = { + artifactsSummaryApiError: (error: string) => + i18n.translate( + 'xpack.securitySolution.endpoint.fleetIntegrationCard.trustedDevicesSummary.error', + { + defaultMessage: 'There was an error trying to fetch trusted devices stats: "{error}"', + values: { error }, + } + ), + cardTitle: ( + + ), + linkLabel: ( + + ), +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts index 19c3d9d6f3ea6..16ce12c9f9e30 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts @@ -9,6 +9,7 @@ import { useCallback } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { + ENDPOINT_ARTIFACT_LISTS, ENDPOINT_BLOCKLISTS_LIST_ID, ENDPOINT_EVENT_FILTERS_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID, @@ -24,6 +25,7 @@ import { getPolicyDetailsArtifactsListPath, getPolicyEventFiltersPath, getPolicyHostIsolationExceptionsPath, + getPolicyTrustedDevicesPath, } from '../../../common/routing'; import { getCurrentArtifactsLocation, policyIdFromParams } from '../store/policy_details/selectors'; import { POLICIES_PATH } from '../../../../../common/constants'; @@ -56,6 +58,11 @@ export function usePolicyDetailsArtifactsNavigateCallback(listId: string) { ...location, ...args, }); + } else if (listId === ENDPOINT_ARTIFACT_LISTS.trustedDevices.id) { + return getPolicyTrustedDevicesPath(policyId, { + ...location, + ...args, + }); } else if (listId === ENDPOINT_EVENT_FILTERS_LIST_ID) { return getPolicyEventFiltersPath(policyId, { ...location, diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx index c350f91c914d6..6863d7f077b8a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx @@ -30,6 +30,8 @@ import { getBlocklistsListPath, getPolicyBlocklistsPath, getPolicyProtectionUpdatesPath, + getTrustedDevicesListPath, + getPolicyTrustedDevicesPath, } from '../../../../common/routing'; import { useHttp, useToasts } from '../../../../../common/lib/kibana'; import { ManagementPageLoader } from '../../../../components/management_page_loader'; @@ -42,6 +44,7 @@ import { policyDetails, policyIdFromParams, isOnProtectionUpdatesView, + isOnPolicyTrustedDevicesView, } from '../../store/policy_details/selectors'; import { PolicyArtifactsLayout } from '../artifacts/layout/policy_artifacts_layout'; import { usePolicyDetailsSelector } from '../policy_hooks'; @@ -57,8 +60,11 @@ import { SEARCHABLE_FIELDS as TRUSTED_APPS_SEARCHABLE_FIELDS } from '../../../tr import { SEARCHABLE_FIELDS as EVENT_FILTERS_SEARCHABLE_FIELDS } from '../../../event_filters/constants'; import { SEARCHABLE_FIELDS as HOST_ISOLATION_EXCEPTIONS_SEARCHABLE_FIELDS } from '../../../host_isolation_exceptions/constants'; import { SEARCHABLE_FIELDS as BLOCKLISTS_SEARCHABLE_FIELDS } from '../../../blocklist/constants'; +import { SEARCHABLE_FIELDS as TRUSTED_DEVICES_SEARCHABLE_FIELDS } from '../../../trusted_devices/constants'; import type { PolicyDetailsRouteState } from '../../../../../../common/endpoint/types'; import { useHostIsolationExceptionsAccess } from '../../../../hooks/artifacts/use_host_isolation_exceptions_access'; +import { TrustedDevicesApiClient } from '../../../trusted_devices/service/api_client'; +import { POLICY_ARTIFACT_TRUSTED_DEVICES_LABELS } from './trusted_devices_translations'; enum PolicyTabKeys { SETTINGS = 'settings', @@ -67,6 +73,7 @@ enum PolicyTabKeys { HOST_ISOLATION_EXCEPTIONS = 'hostIsolationExceptions', BLOCKLISTS = 'blocklists', PROTECTION_UPDATES = 'protectionUpdates', + TRUSTED_DEVICES = 'trustedDevices', } interface PolicyTab { @@ -82,6 +89,7 @@ export const PolicyTabs = React.memo(() => { const isInSettingsTab = usePolicyDetailsSelector(isOnPolicyFormView); const isInTrustedAppsTab = usePolicyDetailsSelector(isOnPolicyTrustedAppsView); + const isInTrustedDevicesTab = usePolicyDetailsSelector(isOnPolicyTrustedDevicesView); const isInEventFiltersTab = usePolicyDetailsSelector(isOnPolicyEventFiltersView); const isInHostIsolationExceptionsTab = usePolicyDetailsSelector(isOnHostIsolationExceptionsView); const isInBlocklistsTab = usePolicyDetailsSelector(isOnBlocklistsView); @@ -124,6 +132,8 @@ export const PolicyTabs = React.memo(() => { canWriteHostIsolationExceptions, canReadBlocklist, canWriteBlocklist, + canReadTrustedDevices, + canWriteTrustedDevices, loading: isPrivilegesLoading, } = useUserPrivileges().endpointPrivileges; const { state: routeState = {} } = useLocation(); @@ -131,8 +141,12 @@ export const PolicyTabs = React.memo(() => { const isProtectionUpdatesFeatureEnabled = useIsExperimentalFeatureEnabled( 'protectionUpdatesEnabled' ); + const isTrustedDevicesFeatureEnabled = useIsExperimentalFeatureEnabled('trustedDevices'); + const isEnterprise = useLicense().isEnterprise(); const isProtectionUpdatesEnabled = isEnterprise && isProtectionUpdatesFeatureEnabled; + const isTrustedDevicesEnabled = + isEnterprise && isTrustedDevicesFeatureEnabled && canReadTrustedDevices; const getHostIsolationExceptionsApiClientInstance = useCallback( () => HostIsolationExceptionsApiClient.getInstance(http), @@ -161,7 +175,8 @@ export const PolicyTabs = React.memo(() => { (isInTrustedAppsTab && !canReadTrustedApplications) || (isInEventFiltersTab && !canReadEventFilters) || redirectHostIsolationException || - (isInBlocklistsTab && !canReadBlocklist) + (isInBlocklistsTab && !canReadBlocklist) || + (isInTrustedDevicesTab && !canReadTrustedDevices) ) { history.replace(getPolicyDetailPath(policyId)); toasts.addDanger( @@ -176,6 +191,7 @@ export const PolicyTabs = React.memo(() => { canReadEventFilters, canReadHostIsolationExceptions, canReadTrustedApplications, + canReadTrustedDevices, hasAccessToHostIsolationExceptions, history, isHostIsolationExceptionsAccessLoading, @@ -184,6 +200,7 @@ export const PolicyTabs = React.memo(() => { isInHostIsolationExceptionsTab, isInProtectionUpdatesTab, isInTrustedAppsTab, + isInTrustedDevicesTab, isPrivilegesLoading, policyId, toasts, @@ -194,6 +211,11 @@ export const PolicyTabs = React.memo(() => { [http] ); + const getTrustedDevicesApiClientInstance = useCallback( + () => TrustedDevicesApiClient.getInstance(http), + [http] + ); + const getEventFiltersApiClientInstance = useCallback( () => EventFiltersApiClient.getInstance(http), [http] @@ -216,6 +238,17 @@ export const PolicyTabs = React.memo(() => { ), }; + const trustedDevicesLabels = { + ...POLICY_ARTIFACT_TRUSTED_DEVICES_LABELS, + layoutAboutMessage: (count: number, link: React.ReactElement): React.ReactNode => ( + + ), + }; + const eventFiltersLabels = { ...POLICY_ARTIFACT_EVENT_FILTERS_LABELS, layoutAboutMessage: (count: number, link: React.ReactElement): React.ReactNode => ( @@ -293,6 +326,32 @@ export const PolicyTabs = React.memo(() => { 'data-test-subj': 'policyTrustedAppsTab', } : undefined, + [PolicyTabKeys.TRUSTED_DEVICES]: isTrustedDevicesEnabled + ? { + id: PolicyTabKeys.TRUSTED_DEVICES, + name: i18n.translate( + 'xpack.securitySolution.endpoint.policy.details.tabs.trustedDevices', + { + defaultMessage: 'Trusted devices', + } + ), + content: ( + <> + + + + ), + 'data-test-subj': 'policyTrustedDevicesTab', + } + : undefined, [PolicyTabKeys.EVENT_FILTERS]: canReadEventFilters ? { id: PolicyTabKeys.EVENT_FILTERS, @@ -398,6 +457,9 @@ export const PolicyTabs = React.memo(() => { canReadTrustedApplications, getTrustedAppsApiClientInstance, canWriteTrustedApplications, + isTrustedDevicesEnabled, + getTrustedDevicesApiClientInstance, + canWriteTrustedDevices, canReadEventFilters, getEventFiltersApiClientInstance, canWriteEventFilters, @@ -424,6 +486,8 @@ export const PolicyTabs = React.memo(() => { selectedTab = tabs[PolicyTabKeys.SETTINGS]; } else if (isInTrustedAppsTab) { selectedTab = tabs[PolicyTabKeys.TRUSTED_APPS]; + } else if (isInTrustedDevicesTab) { + selectedTab = tabs[PolicyTabKeys.TRUSTED_DEVICES]; } else if (isInEventFiltersTab) { selectedTab = tabs[PolicyTabKeys.EVENT_FILTERS]; } else if (isInHostIsolationExceptionsTab) { @@ -439,6 +503,7 @@ export const PolicyTabs = React.memo(() => { tabs, isInSettingsTab, isInTrustedAppsTab, + isInTrustedDevicesTab, isInEventFiltersTab, isInHostIsolationExceptionsTab, isInBlocklistsTab, @@ -462,6 +527,9 @@ export const PolicyTabs = React.memo(() => { case PolicyTabKeys.TRUSTED_APPS: path = getPolicyTrustedAppsPath(policyId); break; + case PolicyTabKeys.TRUSTED_DEVICES: + path = getPolicyTrustedDevicesPath(policyId); + break; case PolicyTabKeys.EVENT_FILTERS: path = getPolicyEventFiltersPath(policyId); break; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_devices_translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_devices_translations.ts new file mode 100644 index 0000000000000..9bf82bcfa0457 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_devices_translations.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; + +export const POLICY_ARTIFACT_TRUSTED_DEVICES_LABELS = Object.freeze({ + deleteModalTitle: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.list.removeDialog.title', + { + defaultMessage: 'Remove trusted device from policy', + } + ), + deleteModalImpactInfo: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.list.removeDialog.messageCallout', + { + defaultMessage: + 'This trusted device will be removed only from this policy and can still be found and managed from the artifact page.', + } + ), + deleteModalErrorMessage: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.list.removeDialog.errorToastTitle', + { + defaultMessage: 'Error while attempting to remove trusted device', + } + ), + flyoutWarningCalloutMessage: (maxNumber: number) => + i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.flyout.searchWarning.text', + { + defaultMessage: + 'Only the first {maxNumber} trusted devices are displayed. Please use the search bar to refine the results.', + values: { maxNumber }, + } + ), + flyoutNoArtifactsToBeAssignedMessage: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.flyout.noAssignable', + { + defaultMessage: 'There are no trusted devices that can be assigned to this policy.', + } + ), + flyoutTitle: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.flyout.title', + { + defaultMessage: 'Assign trusted devices', + } + ), + flyoutSubtitle: (policyName: string): string => + i18n.translate('xpack.securitySolution.endpoint.policy.trustedDevices.layout.flyout.subtitle', { + defaultMessage: 'Select trusted devices to add to {policyName}', + values: { policyName }, + }), + flyoutSearchPlaceholder: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.search.label', + { + defaultMessage: 'Search trusted devices', + } + ), + flyoutErrorMessage: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.flyout.toastError.text', + { + defaultMessage: `An error occurred updating trusted devices`, + } + ), + flyoutSuccessMessageText: (updatedExceptions: ExceptionListItemSchema[]): string => + updatedExceptions.length > 1 + ? i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.flyout.toastSuccess.textMultiples', + { + defaultMessage: '{count} trusted devices have been added to your list.', + values: { count: updatedExceptions.length }, + } + ) + : i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.flyout.toastSuccess.textSingle', + { + defaultMessage: '"{name}" has been added to your trusted device list.', + values: { name: updatedExceptions[0].name }, + } + ), + emptyUnassignedTitle: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.empty.unassigned.title', + { defaultMessage: 'No assigned trusted devices' } + ), + emptyUnassignedMessage: (policyName: string): string => + i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.empty.unassigned.content', + { + defaultMessage: + 'There are currently no trusted devices assigned to {policyName}. Assign trusted devices now or add and manage them on the trusted devices page.', + values: { policyName }, + } + ), + emptyUnassignedPrimaryActionButtonTitle: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.empty.unassigned.primaryAction', + { + defaultMessage: 'Assign trusted devices', + } + ), + emptyUnassignedSecondaryActionButtonTitle: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.empty.unassigned.secondaryAction', + { + defaultMessage: 'Manage trusted devices', + } + ), + emptyUnassignedNoPrivilegesMessage: (policyName: string): string => + i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.empty.unassigned.noPrivileges.content', + { + defaultMessage: 'There are currently no trusted devices assigned to {policyName}.', + values: { policyName }, + } + ), + emptyUnexistingTitle: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.empty.unexisting.title', + { defaultMessage: 'No trusted devices exist' } + ), + emptyUnexistingMessage: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.empty.unexisting.content', + { defaultMessage: 'There are currently no trusted devices applied to your endpoints.' } + ), + emptyUnexistingPrimaryActionButtonTitle: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.empty.unexisting.action', + { defaultMessage: 'Add trusted devices' } + ), + listTotalItemCountMessage: (totalItemsCount: number): string => + i18n.translate('xpack.securitySolution.endpoint.policy.trustedDevices.list.totalItemCount', { + defaultMessage: + 'Showing {totalItemsCount, plural, one {# trusted device} other {# trusted devices}}', + values: { totalItemsCount }, + }), + listRemoveActionNotAllowedMessage: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.list.removeActionNotAllowed', + { + defaultMessage: 'Globally applied trusted device cannot be removed from policy.', + } + ), + listSearchPlaceholderMessage: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.list.search.placeholder', + { + defaultMessage: `Search on the fields below: name, description, value`, + } + ), + layoutTitle: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.title', + { + defaultMessage: 'Assigned trusted devices', + } + ), + layoutAssignButtonTitle: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.assignToPolicy', + { + defaultMessage: 'Assign trusted devices to policy', + } + ), + layoutViewAllLinkMessage: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedDevices.layout.about.viewAllLinkLabel', + { + defaultMessage: 'view all trusted devices', + } + ), +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/constants.ts index d8e54cb2836ba..8ac5b3015482c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/constants.ts @@ -10,11 +10,7 @@ import type { ExceptionListType, } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import { - ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION, - ENDPOINT_TRUSTED_APPS_LIST_ID, - ENDPOINT_TRUSTED_APPS_LIST_NAME, -} from '@kbn/securitysolution-list-constants'; +import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants'; export const TRUSTED_APPS_LIST_TYPE: ExceptionListType = ExceptionListTypeEnum.ENDPOINT_TRUSTED_APPS; @@ -28,9 +24,9 @@ export const SEARCHABLE_FIELDS: Readonly = [ ]; export const TRUSTED_APPS_EXCEPTION_LIST_DEFINITION: CreateExceptionListSchema = { - name: ENDPOINT_TRUSTED_APPS_LIST_NAME, + name: ENDPOINT_ARTIFACT_LISTS.trustedApps.name, namespace_type: 'agnostic', - description: ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION, - list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + description: ENDPOINT_ARTIFACT_LISTS.trustedApps.description, + list_id: ENDPOINT_ARTIFACT_LISTS.trustedApps.id, type: ExceptionListTypeEnum.ENDPOINT_TRUSTED_APPS, }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/constants.ts new file mode 100644 index 0000000000000..46018ab0ffe08 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/constants.ts @@ -0,0 +1,26 @@ +/* + * 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 { CreateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +export const SEARCHABLE_FIELDS: Readonly = [ + `name`, + `description`, + 'item_id', + `entries.value`, + `entries.entries.value`, +]; + +export const TRUSTED_DEVICES_EXCEPTION_LIST_DEFINITION: CreateExceptionListSchema = { + description: ENDPOINT_ARTIFACT_LISTS.trustedDevices.description, + list_id: ENDPOINT_ARTIFACT_LISTS.trustedDevices.id, + name: ENDPOINT_ARTIFACT_LISTS.trustedDevices.name, + namespace_type: 'agnostic', + type: ExceptionListTypeEnum.ENDPOINT_TRUSTED_DEVICES, +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/index.tsx new file mode 100644 index 0000000000000..478e72cf13e06 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/index.tsx @@ -0,0 +1,23 @@ +/* + * 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 } from 'react'; +import { Routes, Route } from '@kbn/shared-ux-router'; +import { MANAGEMENT_ROUTING_TRUSTED_DEVICES_PATH } from '../../common/constants'; +import { NotFoundPage } from '../../../app/404'; +import { TrustedDevicesList } from './view/trusted_devices_list'; + +export const TrustedDevicesContainer = memo(() => { + return ( + + + + + ); +}); + +TrustedDevicesContainer.displayName = 'TrustedDevicesContainer'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/service/api_client.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/service/api_client.ts new file mode 100644 index 0000000000000..ff9707b162f31 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/service/api_client.ts @@ -0,0 +1,64 @@ +/* + * 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 { + CreateExceptionListItemSchema, + ExceptionListItemSchema, + UpdateExceptionListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; +import type { HttpStart } from '@kbn/core/public'; +import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants'; +import type { ConditionEntry } from '../../../../../common/endpoint/types'; +import { + conditionEntriesToEntries, + entriesToConditionEntries, +} from '../../../../common/utils/exception_list_items'; +import { ExceptionsListApiClient } from '../../../services/exceptions_list/exceptions_list_api_client'; +import { TRUSTED_DEVICES_EXCEPTION_LIST_DEFINITION } from '../constants'; + +function readTransform(item: ExceptionListItemSchema): ExceptionListItemSchema { + return { + ...item, + entries: entriesToConditionEntries(item.entries) as ExceptionListItemSchema['entries'], + }; +} + +function writeTransform( + item: T +): T { + return { + ...item, + entries: conditionEntriesToEntries(item.entries as ConditionEntry[], true), + } as T; +} + +/** + * Trusted Devices exceptions Api client class using ExceptionsListApiClient as base class + * It follows the Singleton pattern. + * Please, use the getInstance method instead of creating a new instance when using this implementation. + */ +export class TrustedDevicesApiClient extends ExceptionsListApiClient { + constructor(http: HttpStart) { + super( + http, + ENDPOINT_ARTIFACT_LISTS.trustedDevices.id, + TRUSTED_DEVICES_EXCEPTION_LIST_DEFINITION, + readTransform, + writeTransform + ); + } + + public static getInstance(http: HttpStart): ExceptionsListApiClient { + return super.getInstance( + http, + ENDPOINT_ARTIFACT_LISTS.trustedDevices.id, + TRUSTED_DEVICES_EXCEPTION_LIST_DEFINITION, + readTransform, + writeTransform + ); + } +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/components/artifacts_docs_link.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/components/artifacts_docs_link.tsx new file mode 100644 index 0000000000000..faaef6bf053d3 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/components/artifacts_docs_link.tsx @@ -0,0 +1,32 @@ +/* + * 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 } from 'react'; +import { EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '../../../../../common/lib/kibana'; + +/** + * Link to Trusted Devices documentation + * + * NOTE: This is currently a placeholder for UI development. + * Actual documentation links will be implemented in future phase. + */ +export const TrustedDevicesArtifactsDocsLink = memo(() => { + const { docLinks } = useKibana().services; + + return ( + + + + ); +}); + +TrustedDevicesArtifactsDocsLink.displayName = 'TrustedDevicesArtifactsDocsLink'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/components/form.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/components/form.tsx new file mode 100644 index 0000000000000..c91dec469a66e --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/components/form.tsx @@ -0,0 +1,18 @@ +/* + * 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 } from 'react'; + +import type { ArtifactFormComponentProps } from '../../../../components/artifact_list_page'; + +export const TrustedDevicesForm = memo( + ({ item, onChange, mode = 'create', disabled = false, error }) => { + return
{'Form placeholder'}
; + } +); + +TrustedDevicesForm.displayName = 'TrustedDevicesForm'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/translations.ts new file mode 100644 index 0000000000000..34eeb40ed3a73 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/translations.ts @@ -0,0 +1,88 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const DETAILS_HEADER = i18n.translate( + 'xpack.securitySolution.trustedDevices.form.detailsHeader', + { + defaultMessage: 'Details', + } +); + +export const DETAILS_HEADER_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.trustedDevices.form.detailsHeaderDescription', + { + defaultMessage: + 'Add a trusted device to improve performance or alleviate compatibility issues.', + } +); + +export const NAME_LABEL = i18n.translate('xpack.securitySolution.trustedDevices.form.nameLabel', { + defaultMessage: 'Name', +}); + +export const DESCRIPTION_LABEL = i18n.translate( + 'xpack.securitySolution.trustedDevices.form.descriptionLabel', + { + defaultMessage: 'Description', + } +); + +export const CONDITIONS_HEADER = i18n.translate( + 'xpack.securitySolution.trustedDevices.form.conditionsHeader', + { + defaultMessage: 'Conditions', + } +); + +export const CONDITIONS_HEADER_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.trustedDevices.form.conditionsHeaderDescription', + { + defaultMessage: + 'Select operating system and add conditions. Availability of conditions may depend on your chosen operating system.', + } +); + +export const SELECT_OS_LABEL = i18n.translate( + 'xpack.securitySolution.trustedDevices.form.selectOsLabel', + { + defaultMessage: 'Select operating system', + } +); + +export const POLICY_SELECT_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.trustedDevices.form.policySelectDescription', + { + defaultMessage: 'Optionally select policies to assign this trusted device to.', + } +); + +export const INPUT_ERRORS = { + name: (itemName: string) => + i18n.translate('xpack.securitySolution.trustedDevices.form.errors.nameRequired', { + defaultMessage: '{itemName} name is required', + values: { itemName }, + }), + entries: i18n.translate('xpack.securitySolution.trustedDevices.form.errors.entriesRequired', { + defaultMessage: 'At least one condition is required', + }), + entriesDuplicateFields: (duplicateFields: string[]) => + i18n.translate('xpack.securitySolution.trustedDevices.form.errors.entriesDuplicateFields', { + defaultMessage: 'Duplicate field(s): {duplicateFields}', + values: { duplicateFields: duplicateFields.join(', ') }, + }), + invalidHash: i18n.translate('xpack.securitySolution.trustedDevices.form.errors.invalidHash', { + defaultMessage: 'Invalid hash value', + }), + invalidPath: i18n.translate('xpack.securitySolution.trustedDevices.form.errors.invalidPath', { + defaultMessage: 'Invalid path', + }), + invalidField: i18n.translate('xpack.securitySolution.trustedDevices.form.errors.invalidField', { + defaultMessage: 'Field entry must have a value', + }), +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/trusted_devices_list.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/trusted_devices_list.tsx new file mode 100644 index 0000000000000..521a61f48fff7 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/trusted_devices_list.tsx @@ -0,0 +1,162 @@ +/* + * 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 } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { DocLinks } from '@kbn/doc-links'; +import { EuiLink } from '@elastic/eui'; +import type { ArtifactListPageProps } from '../../../components/artifact_list_page'; +import { ArtifactListPage } from '../../../components/artifact_list_page'; +import { TrustedDevicesApiClient } from '../service/api_client'; +import { TrustedDevicesForm } from './components/form'; +import { useHttp } from '../../../../common/lib/kibana'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { SEARCHABLE_FIELDS } from '../constants'; +import { TrustedDevicesArtifactsDocsLink } from './components/artifacts_docs_link'; + +type TrustedDevicesListProps = Omit< + ArtifactListPageProps, + 'apiClient' | 'ArtifactFormComponent' | 'labels' | 'data-test-subj' +>; + +const TRUSTED_DEVICES_PAGE_LABELS: ArtifactListPageProps['labels'] = { + pageTitle: i18n.translate('xpack.securitySolution.trustedDevices.list.pageTitle', { + defaultMessage: 'Trusted devices', + }), + pageAboutInfo: i18n.translate('xpack.securitySolution.trustedDevices.list.pageAboutInfo', { + defaultMessage: + 'Add a trusted device to improve performance or alleviate compatibility issues with the Elastic Security app.', + }), + pageAddButtonTitle: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.pageAddButtonTitle', + { + defaultMessage: 'Add trusted device', + } + ), + getShowingCountLabel: (total) => + i18n.translate('xpack.securitySolution.trustedDevices.list.showingTotal', { + defaultMessage: + 'Showing {total} {total, plural, one {trusted device} other {trusted devices}}', + values: { total }, + }), + cardActionEditLabel: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.cardActionEditLabel', + { + defaultMessage: 'Edit trusted device', + } + ), + cardActionDeleteLabel: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.cardActionDeleteLabel', + { + defaultMessage: 'Remove from trusted devices list', + } + ), + flyoutCreateTitle: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.flyoutCreateTitle', + { + defaultMessage: 'Add trusted device', + } + ), + flyoutEditTitle: i18n.translate('xpack.securitySolution.trustedDevices.list.flyoutEditTitle', { + defaultMessage: 'Edit trusted device', + }), + flyoutCreateSubmitButtonLabel: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.flyoutCreateSubmitButtonLabel', + { + defaultMessage: 'Add trusted device', + } + ), + flyoutCreateSubmitSuccess: ({ name }) => + i18n.translate('xpack.securitySolution.trustedDevices.list.flyoutCreateSubmitSuccess', { + defaultMessage: '"{name}" has been added to your trusted devices list.', + values: { name }, + }), + flyoutEditSubmitSuccess: ({ name }) => + i18n.translate('xpack.securitySolution.trustedDevices.list.flyoutEditSubmitSuccess', { + defaultMessage: '"{name}" has been updated.', + values: { name }, + }), + flyoutDowngradedLicenseDocsInfo: ( + securitySolutionDocsLinks: DocLinks['securitySolution'] + ): React.ReactNode => { + return ( + <> + + + + + + ); + }, + deleteActionSuccess: (itemName) => + i18n.translate('xpack.securitySolution.trustedDevices.list.deleteActionSuccess', { + defaultMessage: '"{itemName}" has been removed from the trusted devices list', + values: { itemName }, + }), + emptyStateTitleNoEntries: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.emptyStateTitleNoEntries', + { + defaultMessage: 'There are no trusted devices to display.', + } + ), + emptyStateTitle: i18n.translate('xpack.securitySolution.trustedDevices.list.emptyStateTitle', { + defaultMessage: 'Add your first trusted device', + }), + emptyStateInfo: i18n.translate('xpack.securitySolution.trustedDevices.list.emptyStateInfo', { + defaultMessage: + 'There are currently no trusted devices on your Endpoints. Add trusted devices to improve performance or alleviate compatibility issues with the Elastic Security app.', + }), + emptyStatePrimaryButtonLabel: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.emptyStatePrimaryButtonLabel', + { + defaultMessage: 'Add trusted device', + } + ), + searchPlaceholderInfo: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.searchPlaceholderInfo', + { + defaultMessage: 'Search on the fields below: name, description, value', + } + ), +}; + +export const TrustedDevicesList = memo((props) => { + const http = useHttp(); + const isTrustedDevicesEnabled = useIsExperimentalFeatureEnabled('trustedDevices'); + + const trustedDevicesListApiClient = TrustedDevicesApiClient.getInstance(http); + + const { canWriteTrustedDevices } = useUserPrivileges().endpointPrivileges; + + if (!isTrustedDevicesEnabled) { + return null; + } + + return ( + } + allowCardDeleteAction={canWriteTrustedDevices} + allowCardEditAction={canWriteTrustedDevices} + allowCardCreateAction={canWriteTrustedDevices} + /> + ); +}); + +TrustedDevicesList.displayName = 'TrustedDevicesList'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/types.ts b/x-pack/solutions/security/plugins/security_solution/public/management/types.ts index ef0e3e56c7285..f18910e06dd76 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/types.ts @@ -28,6 +28,7 @@ export enum AdministrationSubTab { endpoints = 'endpoints', policies = 'policy', trustedApps = 'trusted_apps', + trustedDevices = 'trusted_devices', eventFilters = 'event_filters', hostIsolationExceptions = 'host_isolation_exceptions', blocklist = 'blocklist', diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/migrations/space_awareness_migration.test.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/migrations/space_awareness_migration.test.ts index b3831f00b046d..24a9f10fdb394 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/migrations/space_awareness_migration.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/migrations/space_awareness_migration.test.ts @@ -147,18 +147,20 @@ describe('Space awareness migration', () => { executeFunctionOnStream: expect.any(Function), listId: [ 'endpoint_trusted_apps', + 'endpoint_trusted_devices', 'endpoint_event_filters', 'endpoint_host_isolation_exceptions', 'endpoint_blocklists', 'endpoint_list', ], - namespaceType: ['agnostic', 'agnostic', 'agnostic', 'agnostic', 'agnostic'], + namespaceType: ['agnostic', 'agnostic', 'agnostic', 'agnostic', 'agnostic', 'agnostic'], filter: [ `NOT exception-list-agnostic.attributes.tags:"${buildSpaceOwnerIdTag('*')}"`, `NOT exception-list-agnostic.attributes.tags:"${buildSpaceOwnerIdTag('*')}"`, `NOT exception-list-agnostic.attributes.tags:"${buildSpaceOwnerIdTag('*')}"`, `NOT exception-list-agnostic.attributes.tags:"${buildSpaceOwnerIdTag('*')}"`, `NOT exception-list-agnostic.attributes.tags:"${buildSpaceOwnerIdTag('*')}"`, + `NOT exception-list-agnostic.attributes.tags:"${buildSpaceOwnerIdTag('*')}"`, ], }) ); diff --git a/x-pack/solutions/security/test/security_solution_cypress/cypress/screens/serverless_security_header.ts b/x-pack/solutions/security/test/security_solution_cypress/cypress/screens/serverless_security_header.ts index c3b40beff475a..3bed5383123c6 100644 --- a/x-pack/solutions/security/test/security_solution_cypress/cypress/screens/serverless_security_header.ts +++ b/x-pack/solutions/security/test/security_solution_cypress/cypress/screens/serverless_security_header.ts @@ -45,6 +45,8 @@ export const POLICIES = '[data-test-subj~="panelNavItem-id-policy"]'; export const TRUSTED_APPS = '[data-test-subj~="panelNavItem-id-trusted_apps"]'; +export const TRUSTED_DEVICES = '[data-test-subj~="panelNavItem-id-trusted_devices"]'; + export const EVENT_FILTERS = '[data-test-subj~="panelNavItem-id-event_filters"]'; export const BLOCKLIST = '[data-test-subj~="panelNavItem-id-blocklist"]'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/spaces/trial_license_complete_tier/artifacts.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/spaces/trial_license_complete_tier/artifacts.ts index a0029b151c0cb..963facba81493 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/spaces/trial_license_complete_tier/artifacts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/spaces/trial_license_complete_tier/artifacts.ts @@ -140,7 +140,9 @@ export default function ({ getService }: FtrProviderContext) { await Promise.allSettled(afterEachDataCleanup.splice(0).map((data) => data.cleanup())); }); - const artifactLists = Object.keys(ENDPOINT_ARTIFACT_LISTS); + const artifactLists = Object.keys(ENDPOINT_ARTIFACT_LISTS).filter( + (k) => k !== 'trustedDevices' + ); // Todo: Enable once trustedDevices are implemented. for (const artifactList of artifactLists) { const listInfo =