diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json index 211777a5274a5..aa84c709c655b 100644 --- a/packages/kbn-check-mappings-update-cli/current_fields.json +++ b/packages/kbn-check-mappings-update-cli/current_fields.json @@ -460,6 +460,36 @@ "token", "valid_until" ], + "fleet-agent-policies": [ + "advanced_settings", + "agent_features", + "agent_features.enabled", + "agent_features.name", + "data_output_id", + "description", + "download_source_id", + "fleet_server_host_id", + "global_data_tags", + "inactivity_timeout", + "is_default", + "is_default_fleet_server", + "is_managed", + "is_preconfigured", + "is_protected", + "keep_monitoring_alive", + "monitoring_enabled", + "monitoring_output_id", + "name", + "namespace", + "overrides", + "revision", + "schema_version", + "status", + "supports_agentless", + "unenroll_timeout", + "updated_at", + "updated_by" + ], "fleet-fleet-server-host": [ "host_urls", "is_default", @@ -469,6 +499,32 @@ "proxy_id" ], "fleet-message-signing-keys": [], + "fleet-package-policies": [ + "created_at", + "created_by", + "description", + "elasticsearch", + "enabled", + "inputs", + "is_managed", + "name", + "namespace", + "output_id", + "overrides", + "package", + "package.name", + "package.requires_root", + "package.title", + "package.version", + "policy_id", + "policy_ids", + "revision", + "secret_references", + "secret_references.id", + "updated_at", + "updated_by", + "vars" + ], "fleet-preconfiguration-deletion-record": [ "id" ], @@ -651,7 +707,9 @@ "has_seen_add_data_notice", "output_secret_storage_requirements_met", "prerelease_integrations_enabled", - "secret_storage_requirements_met" + "secret_storage_requirements_met", + "use_space_awareness_migration_started_at", + "use_space_awareness_migration_status" ], "inventory-view": [], "kql-telemetry": [], diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index e6e1fef63ee85..97e34c9b29341 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -1553,6 +1553,99 @@ } } }, + "fleet-agent-policies": { + "properties": { + "advanced_settings": { + "index": false, + "type": "flattened" + }, + "agent_features": { + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "type": "keyword" + } + } + }, + "data_output_id": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "download_source_id": { + "type": "keyword" + }, + "fleet_server_host_id": { + "type": "keyword" + }, + "global_data_tags": { + "index": false, + "type": "flattened" + }, + "inactivity_timeout": { + "type": "integer" + }, + "is_default": { + "type": "boolean" + }, + "is_default_fleet_server": { + "type": "boolean" + }, + "is_managed": { + "type": "boolean" + }, + "is_preconfigured": { + "type": "keyword" + }, + "is_protected": { + "type": "boolean" + }, + "keep_monitoring_alive": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "monitoring_output_id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "overrides": { + "index": false, + "type": "flattened" + }, + "revision": { + "type": "integer" + }, + "schema_version": { + "type": "version" + }, + "status": { + "type": "keyword" + }, + "supports_agentless": { + "type": "boolean" + }, + "unenroll_timeout": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, "fleet-fleet-server-host": { "properties": { "host_urls": { @@ -1581,6 +1674,87 @@ "dynamic": false, "properties": {} }, + "fleet-package-policies": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "elasticsearch": { + "dynamic": false, + "properties": {} + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "dynamic": false, + "properties": {} + }, + "is_managed": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "overrides": { + "index": false, + "type": "flattened" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "requires_root": { + "type": "boolean" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "policy_id": { + "type": "keyword" + }, + "policy_ids": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "secret_references": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + } + }, "fleet-preconfiguration-deletion-record": { "properties": { "id": { @@ -2168,6 +2342,14 @@ }, "secret_storage_requirements_met": { "type": "boolean" + }, + "use_space_awareness_migration_started_at": { + "index": false, + "type": "date" + }, + "use_space_awareness_migration_status": { + "index": false, + "type": "keyword" } } }, diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index f15fb0035d670..3f498eb5f4606 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -118,10 +118,13 @@ const STANDARD_LIST_TYPES = [ 'infrastructure-monitoring-log-view', 'apm-indices', // Fleet saved object types + 'ingest_manager_settings', 'ingest-outputs', 'ingest-download-sources', 'ingest-agent-policies', + 'fleet-agent-policies', 'ingest-package-policies', + 'fleet-package-policies', 'epm-packages', 'epm-packages-assets', 'fleet-preconfiguration-deletion-record', diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 6d978b2d33ca4..b965b48762879 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -102,8 +102,10 @@ describe('checking migration metadata changes on all registered SO types', () => "file": "6b65ae5899b60ebe08656fd163ea532e557d3c98", "file-upload-usage-collection-telemetry": "06e0a8c04f991e744e09d03ab2bd7f86b2088200", "fileShare": "5be52de1747d249a221b5241af2838264e19aaa1", + "fleet-agent-policies": "f57d3b70e4175a19a18f18ee72a379ceec82e1fc", "fleet-fleet-server-host": "69be15f6b6f2a2875ad3c7050ddea7a87f505417", "fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f", + "fleet-package-policies": "2f4d524adb49a5281d3af0b66bb3003ba0ff2e44", "fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e", "fleet-proxy": "6cb688f0d2dd856400c1dbc998b28704ff70363d", "fleet-setup-lock": "0dc784792c79b5af5a6e6b5dcac06b0dbaa90bde", @@ -120,7 +122,7 @@ describe('checking migration metadata changes on all registered SO types', () => "ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d", "ingest-outputs": "daafff49255ab700e07491376fe89f04fc998b91", "ingest-package-policies": "53a94064674835fdb35e5186233bcd7052eabd22", - "ingest_manager_settings": "91445219e7115ff0c45d1dabd5d614a80b421797", + "ingest_manager_settings": "e794576a05d19dd5306a1e23cbb82c09bffabd65", "inventory-view": "b8683c8e352a286b4aca1ab21003115a4800af83", "kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad", "legacy-url-alias": "9b8cca3fbb2da46fd12823d3cd38fdf1c9f24bc8", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts index fff7d32d78af5..e7c1034ba4a1e 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts @@ -67,6 +67,8 @@ const previouslyRegisteredTypes = [ 'file-upload-usage-collection-telemetry', 'fleet-agent-actions', 'fleet-agent-events', + 'fleet-agent-policies', + 'fleet-package-policies', 'fleet-agents', 'fleet-enrollment-api-keys', 'fleet-fleet-server-host', diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index b6e32f86ac514..b89577ed7c365 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -5,7 +5,9 @@ * 2.0. */ -export const AGENT_POLICY_SAVED_OBJECT_TYPE = 'ingest-agent-policies'; +export const LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE = 'ingest-agent-policies'; +export const AGENT_POLICY_SAVED_OBJECT_TYPE = 'fleet-agent-policies'; + export const AGENT_POLICY_INDEX = '.fleet-policies'; export const agentPolicyStatuses = { Active: 'active', diff --git a/x-pack/plugins/fleet/common/constants/package_policy.ts b/x-pack/plugins/fleet/common/constants/package_policy.ts index 00b41a8a29de2..9ff84c65ad22b 100644 --- a/x-pack/plugins/fleet/common/constants/package_policy.ts +++ b/x-pack/plugins/fleet/common/constants/package_policy.ts @@ -5,7 +5,9 @@ * 2.0. */ -export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies'; +export const LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies'; + +export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'fleet-package-policies'; export const PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES = ['auto_configure', 'create_doc']; diff --git a/x-pack/plugins/fleet/common/index.ts b/x-pack/plugins/fleet/common/index.ts index 9b50a140a7e93..5b88793b3e6f2 100644 --- a/x-pack/plugins/fleet/common/index.ts +++ b/x-pack/plugins/fleet/common/index.ts @@ -24,8 +24,9 @@ export { FLEET_ENDPOINT_PACKAGE, // Saved object type AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE as PACKAGE_POLICY_SAVED_OBJECT_TYPE, OUTPUT_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/fleet/common/types/models/settings.ts b/x-pack/plugins/fleet/common/types/models/settings.ts index bb44724d5c54e..9a5166e41df96 100644 --- a/x-pack/plugins/fleet/common/types/models/settings.ts +++ b/x-pack/plugins/fleet/common/types/models/settings.ts @@ -13,7 +13,10 @@ export interface BaseSettings { export interface Settings extends BaseSettings { id: string; + version?: string; preconfigured_fields?: Array<'fleet_server_hosts'>; secret_storage_requirements_met?: boolean; output_secret_storage_requirements_met?: boolean; + use_space_awareness_migration_status?: 'pending' | 'success' | 'error'; + use_space_awareness_migration_started_at?: string | null; } diff --git a/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts b/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts index df308458e3049..61180ba5094d1 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts @@ -17,5 +17,6 @@ export interface GetFleetStatusResponse { >; missing_optional_features: Array<'encrypted_saved_object_encryption_key_required'>; package_verification_key_id?: string; + is_space_awareness_enabled?: boolean; is_secrets_storage_enabled?: boolean; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.test.tsx index 22f81b125015e..d426cd754b619 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.test.tsx @@ -15,7 +15,7 @@ import { createFleetTestRendererMock } from '../../../mock'; import { AGENTS_PREFIX, FLEET_ENROLLMENT_API_PREFIX, - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, AGENTS_INDEX, ENROLLMENT_API_KEYS_INDEX, INGEST_SAVED_OBJECT_INDEX, @@ -238,169 +238,171 @@ describe('getFieldSpecs', () => { }); it('returns fieldSpecs for Fleet agent policies', () => { - expect(getFieldSpecs(INGEST_SAVED_OBJECT_INDEX, AGENT_POLICY_SAVED_OBJECT_TYPE)).toEqual([ - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.agent_features.name', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['boolean'], - name: 'ingest-agent-policies.agent_features.enabled', - searchable: true, - type: 'boolean', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.data_output_id', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['text'], - name: 'ingest-agent-policies.description', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.download_source_id', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.fleet_server_host_id', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['integer'], - name: 'ingest-agent-policies.inactivity_timeout', - searchable: true, - type: 'number', - }, - { - aggregatable: true, - esTypes: ['boolean'], - name: 'ingest-agent-policies.is_default', - searchable: true, - type: 'boolean', - }, - { - aggregatable: true, - esTypes: ['boolean'], - name: 'ingest-agent-policies.is_default_fleet_server', - searchable: true, - type: 'boolean', - }, - { - aggregatable: true, - esTypes: ['boolean'], - name: 'ingest-agent-policies.is_managed', - searchable: true, - type: 'boolean', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.is_preconfigured', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['boolean'], - name: 'ingest-agent-policies.is_protected', - searchable: true, - type: 'boolean', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.monitoring_enabled', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['false'], - name: 'ingest-agent-policies.monitoring_enabled.index', - searchable: true, - type: 'false', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.monitoring_output_id', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.name', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.namespace', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['integer'], - name: 'ingest-agent-policies.revision', - searchable: true, - type: 'number', - }, - { - aggregatable: true, - esTypes: ['version'], - name: 'ingest-agent-policies.schema_version', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.status', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - esTypes: ['integer'], - name: 'ingest-agent-policies.unenroll_timeout', - searchable: true, - type: 'number', - }, - { - aggregatable: true, - esTypes: ['date'], - name: 'ingest-agent-policies.updated_at', - searchable: true, - type: 'date', - }, - { - aggregatable: true, - esTypes: ['keyword'], - name: 'ingest-agent-policies.updated_by', - searchable: true, - type: 'string', - }, - ]); + expect(getFieldSpecs(INGEST_SAVED_OBJECT_INDEX, LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE)).toEqual( + [ + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.agent_features.name', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['boolean'], + name: 'ingest-agent-policies.agent_features.enabled', + searchable: true, + type: 'boolean', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.data_output_id', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['text'], + name: 'ingest-agent-policies.description', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.download_source_id', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.fleet_server_host_id', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['integer'], + name: 'ingest-agent-policies.inactivity_timeout', + searchable: true, + type: 'number', + }, + { + aggregatable: true, + esTypes: ['boolean'], + name: 'ingest-agent-policies.is_default', + searchable: true, + type: 'boolean', + }, + { + aggregatable: true, + esTypes: ['boolean'], + name: 'ingest-agent-policies.is_default_fleet_server', + searchable: true, + type: 'boolean', + }, + { + aggregatable: true, + esTypes: ['boolean'], + name: 'ingest-agent-policies.is_managed', + searchable: true, + type: 'boolean', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.is_preconfigured', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['boolean'], + name: 'ingest-agent-policies.is_protected', + searchable: true, + type: 'boolean', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.monitoring_enabled', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['false'], + name: 'ingest-agent-policies.monitoring_enabled.index', + searchable: true, + type: 'false', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.monitoring_output_id', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.name', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.namespace', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['integer'], + name: 'ingest-agent-policies.revision', + searchable: true, + type: 'number', + }, + { + aggregatable: true, + esTypes: ['version'], + name: 'ingest-agent-policies.schema_version', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.status', + searchable: true, + type: 'string', + }, + { + aggregatable: true, + esTypes: ['integer'], + name: 'ingest-agent-policies.unenroll_timeout', + searchable: true, + type: 'number', + }, + { + aggregatable: true, + esTypes: ['date'], + name: 'ingest-agent-policies.updated_at', + searchable: true, + type: 'date', + }, + { + aggregatable: true, + esTypes: ['keyword'], + name: 'ingest-agent-policies.updated_by', + searchable: true, + type: 'string', + }, + ] + ); }); it('returns empty array if indexPattern is not one of the previous', async () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx index 3e47a3a7955b3..1a7074e2f885a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx @@ -23,6 +23,7 @@ import { AGENTS_INDEX, ENROLLMENT_API_KEYS_INDEX, AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, INGEST_SAVED_OBJECT_INDEX, } from '../constants'; @@ -51,6 +52,8 @@ const getMappings = (indexPattern: string, fieldPrefix: string) => { switch (fieldPrefix) { case AGENT_POLICY_SAVED_OBJECT_TYPE: return AGENT_POLICY_MAPPINGS; + case LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE: + return AGENT_POLICY_MAPPINGS; default: return {}; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx index c17e3345bfd1d..f7886d2d5e0ce 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx @@ -30,7 +30,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, dataTypes, DEFAULT_MAX_AGENT_POLICIES_WITH_INACTIVITY_TIMEOUT, } from '../../../../../../../common/constants'; @@ -95,7 +95,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = const { data: agentPoliciesData } = useGetAgentPolicies({ page: 1, perPage: 0, - kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.inactivity_timeout:*`, + kuery: `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.inactivity_timeout:*`, }); const totalAgentPoliciesWithInactivityTimeout = agentPoliciesData?.total ?? 0; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx index 40f057b56551c..6ab01d06f52dd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx @@ -25,7 +25,11 @@ import { useHistory } from 'react-router-dom'; import type { AgentPolicy } from '../../../types'; import { getRootIntegrations } from '../../../../../../common/services'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE, INGEST_SAVED_OBJECT_INDEX } from '../../../constants'; +import { + AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, + INGEST_SAVED_OBJECT_INDEX, +} from '../../../constants'; import { useAuthz, usePagination, @@ -35,6 +39,7 @@ import { useUrlParams, useBreadcrumbs, useGetAgentPoliciesQuery, + useFleetStatus, } from '../../../hooks'; import { SearchBar } from '../../../components'; import { AgentPolicySummaryLine } from '../../../../../components'; @@ -43,6 +48,7 @@ import { LinkedAgentCount, AgentPolicyActionMenu } from '../components'; import { CreateAgentPolicyFlyout } from './components'; export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { + const { isSpaceAwarenessEnabled } = useFleetStatus(); useBreadcrumbs('policies_list'); const { getPath } = useLink(); const hasFleetAllAgentPoliciesPrivileges = useAuthz().fleet.allAgentPolicies; @@ -321,7 +327,11 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { { setPagination({ ...pagination, @@ -342,7 +352,6 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { {createAgentPolicyButton} - loading={isLoading} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.tsx index 95041d7c089d1..e67d0e83c28e0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.tsx @@ -25,7 +25,7 @@ import { sendGetActionStatus, } from '../../../../hooks'; import { AgentStatusKueryHelper, ExperimentalFeaturesService } from '../../../../services'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../../constants'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../../constants'; import { getKuery } from '../utils/get_kuery'; @@ -166,7 +166,7 @@ export function useFetchAgentsData() { kuery: AgentStatusKueryHelper.buildKueryForInactiveAgents(), }), sendGetAgentPolicies({ - kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.is_managed:true`, + kuery: `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.is_managed:true`, perPage: SO_SEARCH_LIMIT, full: false, }), diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/preconfiguration_debugger.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/preconfiguration_debugger.tsx index 7642589bc1d6c..f140e4068d040 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/preconfiguration_debugger.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/preconfiguration_debugger.tsx @@ -29,13 +29,13 @@ import { useLink, useStartServices, } from '../../../hooks'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../constants'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../constants'; import { queryClient } from '..'; import { CodeBlock } from './code_block'; const fetchPreconfiguredPolicies = async () => { - const kuery = `${AGENT_POLICY_SAVED_OBJECT_TYPE}.is_preconfigured:true`; + const kuery = `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.is_preconfigured:true`; const response = await sendGetAgentPolicies({ kuery, perPage: SO_SEARCH_LIMIT, full: true }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/saved_object_debugger.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/saved_object_debugger.tsx index cf41612a0d5fd..4c4cdddd1f106 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/saved_object_debugger.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/saved_object_debugger.tsx @@ -26,8 +26,8 @@ import { debugRoutesService } from '../../../../../../common/services'; import { OUTPUT_SAVED_OBJECT_TYPE, - AGENT_POLICY_SAVED_OBJECT_TYPE, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, @@ -60,13 +60,13 @@ const fetchSavedObjects = async (type?: string, name?: string) => { export const SavedObjectDebugger: React.FunctionComponent = () => { const types = [ { - value: `${AGENT_POLICY_SAVED_OBJECT_TYPE}`, + value: `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}`, text: i18n.translate('xpack.fleet.debug.savedObjectDebugger.agentPolicyLabel', { defaultMessage: 'Agent policy', }), }, { - value: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}`, + value: `${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}`, text: i18n.translate('xpack.fleet.debug.savedObjectDebugger.packagePolicyLabel', { defaultMessage: 'Integration policy', }), diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/services/get_count.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/services/get_count.tsx index ce9428771c0e4..23acf9e8c43cd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/services/get_count.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/services/get_count.tsx @@ -7,12 +7,12 @@ import { sendGetAgentPolicies, sendGetAgents } from '../../../../../hooks'; import type { DownloadSource } from '../../../../../types'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../../../constants'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../../../constants'; export async function getCountsForDownloadSource(downloadSource: DownloadSource) { - let kuery = `${AGENT_POLICY_SAVED_OBJECT_TYPE}.download_source_id:"${downloadSource.id}"`; + let kuery = `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.download_source_id:"${downloadSource.id}"`; if (downloadSource.is_default) { - kuery += ` or (not ${AGENT_POLICY_SAVED_OBJECT_TYPE}.download_source_id:*)`; + kuery += ` or (not ${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.download_source_id:*)`; } const agentPolicies = await sendGetAgentPolicies({ kuery, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/services/agent_and_policies_count.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/services/agent_and_policies_count.tsx index 8720ede4f04b8..9ea3db4e14851 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/services/agent_and_policies_count.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/services/agent_and_policies_count.tsx @@ -8,17 +8,17 @@ import { sendGetAgentPolicies, sendGetPackagePolicies, sendGetAgents } from '../../../hooks'; import type { Output } from '../../../types'; import { - AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT, } from '../../../constants'; export async function getAgentAndPolicyCountForOutput(output: Output) { - let agentPolicyKuery = `${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:"${output.id}" or ${AGENT_POLICY_SAVED_OBJECT_TYPE}.monitoring_output_id:"${output.id}"`; + let agentPolicyKuery = `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:"${output.id}" or ${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.monitoring_output_id:"${output.id}"`; const packagePolicyKuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.output_id:"${output.id}"`; if (output.is_default) { - agentPolicyKuery += ` or (not ${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:*)`; + agentPolicyKuery += ` or (not ${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:*)`; } const agentPolicies = await sendGetAgentPolicies({ diff --git a/x-pack/plugins/fleet/public/constants/index.ts b/x-pack/plugins/fleet/public/constants/index.ts index 1c0a04b9cb8a7..4fbe799aa7337 100644 --- a/x-pack/plugins/fleet/public/constants/index.ts +++ b/x-pack/plugins/fleet/public/constants/index.ts @@ -14,10 +14,11 @@ export { AGENT_API_ROUTES, SO_SEARCH_LIMIT, AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, AGENTS_PREFIX, UNPRIVILEGED_AGENT_KUERY, PRIVILEGED_AGENT_KUERY, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE as PACKAGE_POLICY_SAVED_OBJECT_TYPE, FLEET_SERVER_PACKAGE, // Fleet Server index AGENTS_INDEX, diff --git a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx index b05b3a1abc049..019940dc92238 100644 --- a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx @@ -21,6 +21,7 @@ export interface FleetStatusProviderProps { missingRequirements?: GetFleetStatusResponse['missing_requirements']; missingOptionalFeatures?: GetFleetStatusResponse['missing_optional_features']; isSecretsStorageEnabled?: GetFleetStatusResponse['is_secrets_storage_enabled']; + isSpaceAwarenessEnabled?: GetFleetStatusResponse['is_space_awareness_enabled']; spaceId?: string; } @@ -64,6 +65,7 @@ export const FleetStatusProvider: React.FC<{ missingRequirements: data?.missing_requirements, missingOptionalFeatures: data?.missing_optional_features, isSecretsStorageEnabled: data?.is_secrets_storage_enabled, + isSpaceAwarenessEnabled: data?.is_space_awareness_enabled, spaceId, }; diff --git a/x-pack/plugins/fleet/server/collectors/agent_policies.ts b/x-pack/plugins/fleet/server/collectors/agent_policies.ts index 190c43f341ff8..3412e5f7f3c6e 100644 --- a/x-pack/plugins/fleet/server/collectors/agent_policies.ts +++ b/x-pack/plugins/fleet/server/collectors/agent_policies.ts @@ -8,12 +8,9 @@ import type { SavedObjectsClientContract } from '@kbn/core/server'; import _ from 'lodash'; -import { - AGENT_POLICY_SAVED_OBJECT_TYPE, - OUTPUT_SAVED_OBJECT_TYPE, - SO_SEARCH_LIMIT, -} from '../../common'; +import { OUTPUT_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../common'; import type { OutputSOAttributes, AgentPolicy } from '../types'; +import { getAgentPolicySavedObjectType } from '../services/agent_policy'; export interface AgentPoliciesUsage { count: number; @@ -35,9 +32,10 @@ export const getAgentPoliciesUsage = async ( const outputsById = _.keyBy(outputs, 'id'); + const agentPolicySavedObjectType = await getAgentPolicySavedObjectType(); const { saved_objects: agentPolicies, total: totalAgentPolicies } = await soClient.find({ - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: agentPolicySavedObjectType, page: 1, perPage: SO_SEARCH_LIMIT, }); diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index 8ea5297ecd59b..7751a8f23a59b 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -45,8 +45,9 @@ export { // Saved object types SO_SEARCH_LIMIT, AGENTS_PREFIX, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, AGENT_POLICY_SAVED_OBJECT_TYPE, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE as PACKAGE_POLICY_SAVED_OBJECT_TYPE, OUTPUT_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/fleet/server/integration_tests/enable_space_awareness.test.ts b/x-pack/plugins/fleet/server/integration_tests/enable_space_awareness.test.ts new file mode 100644 index 0000000000000..0af9026bf8fa2 --- /dev/null +++ b/x-pack/plugins/fleet/server/integration_tests/enable_space_awareness.test.ts @@ -0,0 +1,228 @@ +/* + * 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 Path from 'path'; + +import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; +import { type MockedLogger, loggerMock } from '@kbn/logging-mocks'; + +import { + type TestElasticsearchUtils, + type TestKibanaUtils, + createRootWithCorePlugins, + createTestServers, +} from '@kbn/core-test-helpers-kbn-server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; + +import { + AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, + PACKAGE_POLICY_SAVED_OBJECT_TYPE, +} from '../../common/constants'; + +import { appContextService } from '../services/app_context'; +import { enableSpaceAwarenessMigration } from '../services/spaces/enable_space_awareness'; + +import { + FLEET_AGENT_POLICIES_SCHEMA_VERSION, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, +} from '../constants'; + +import { useDockerRegistry, waitForFleetSetup } from './helpers'; + +const logFilePath = Path.join(__dirname, 'logs.log'); + +const fakeRequest = { + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, +} as unknown as KibanaRequest; + +describe('enableSpaceAwareness', () => { + let esServer: TestElasticsearchUtils; + let kbnServer: TestKibanaUtils; + + const registryUrl = useDockerRegistry(); + + const startServers = async () => { + const { startES } = createTestServers({ + adjustTimeout: (t) => jest.setTimeout(t), + settings: { + es: { + license: 'trial', + }, + kbn: {}, + }, + }); + + esServer = await startES(); + const startKibana = async () => { + const root = createRootWithCorePlugins( + { + xpack: { + fleet: { + registryUrl, + packages: [ + { + name: 'nginx', + version: 'latest', + }, + ], + }, + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + loggers: [ + { + name: 'root', + appenders: ['file'], + }, + { + name: 'plugins.fleet', + level: 'all', + }, + ], + }, + }, + { oss: false } + ); + + await root.preboot(); + const coreSetup = await root.setup(); + const coreStart = await root.start(); + + return { + root, + coreSetup, + coreStart, + stop: async () => await root.shutdown(), + }; + }; + kbnServer = await startKibana(); + + await waitForFleetSetup(kbnServer.root); + }; + + const stopServers = async () => { + if (kbnServer) { + await kbnServer.stop(); + } + + if (esServer) { + await esServer.stop(); + } + + await new Promise((res) => setTimeout(res, 10000)); + }; + + // Share the same servers for all the test to make test a lot faster (but test are not isolated anymore) + beforeAll(async () => { + await startServers(); + }); + + afterAll(async () => { + await stopServers(); + }); + + let soClient: SavedObjectsClientContract; + + let logger: MockedLogger; + + beforeAll(async () => { + soClient = kbnServer.coreStart.savedObjects.getScopedClient(fakeRequest, { + excludedExtensions: [SECURITY_EXTENSION_ID], + }); + logger = loggerMock.create(); + appContextService.getLogger = () => logger; + + const RANGES = Array.from({ length: 5000 }, (value, index) => index); + + await soClient.bulkCreate( + RANGES.map((i) => ({ + id: `agent-policy-${i}`, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, + attributes: { + name: `agent-policy-${i}`, + schema_version: FLEET_AGENT_POLICIES_SCHEMA_VERSION, + revision: 1, + updated_at: new Date().toISOString(), + }, + })), + { + refresh: 'wait_for', + } + ); + + await soClient.bulkCreate( + RANGES.map((i) => ({ + id: `package-policy-${i}`, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, + attributes: { + name: `package-policy-${i}`, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }, + })), + { + refresh: 'wait_for', + } + ); + }); + it('should support concurrent calls', async () => { + const res = await Promise.allSettled([ + enableSpaceAwarenessMigration(), + enableSpaceAwarenessMigration(), + enableSpaceAwarenessMigration(), + enableSpaceAwarenessMigration(), + enableSpaceAwarenessMigration(), + ]); + + const logs = loggerMock.collect(logger); + expect(res.filter((p) => p.status === 'fulfilled')).toHaveLength(1); + // It should start and complete the migration only once + expect( + logs.info.filter((m) => m[0] === 'Starting Fleet space awareness migration') + ).toHaveLength(1); + expect( + logs.info.filter((m) => m[0] === 'Fleet space awareness migration is complete') + ).toHaveLength(1); + // + expect( + logs.info.filter((m) => m[0] === 'Fleet space awareness migration is pending') + ).toHaveLength(4); + + // Check saved object are migrated + const resAgentPolicies = await soClient.find({ + type: AGENT_POLICY_SAVED_OBJECT_TYPE, + perPage: 0, + }); + expect(resAgentPolicies.total).toBe(5000); + + const resPackagePolicies = await soClient.find({ + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + perPage: 0, + }); + expect(resPackagePolicies.total).toBe(5000); + }); +}); diff --git a/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts b/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts index dd4e925a7f17a..57e42a842a02c 100644 --- a/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts @@ -23,7 +23,10 @@ import { createRootWithCorePlugins, } from '@kbn/core-test-helpers-kbn-server'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE, FLEET_AGENT_POLICIES_SCHEMA_VERSION } from '../constants'; +import { + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, + FLEET_AGENT_POLICIES_SCHEMA_VERSION, +} from '../constants'; import { upgradeAgentPolicySchemaVersion } from '../services/setup/upgrade_agent_policy_schema_version'; import { AGENT_POLICY_INDEX } from '../../common'; import { agentPolicyService } from '../services'; @@ -141,7 +144,7 @@ describe('upgrade agent policy schema version', () => { await soClient.bulkCreate([ // up-to-date schema_version { - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, id: uuidv4(), attributes: { schema_version: FLEET_AGENT_POLICIES_SCHEMA_VERSION, @@ -150,7 +153,7 @@ describe('upgrade agent policy schema version', () => { }, // out-of-date schema_version { - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, id: uuidv4(), attributes: { schema_version: '0.0.1', @@ -159,7 +162,7 @@ describe('upgrade agent policy schema version', () => { }, // missing schema_version { - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, id: uuidv4(), attributes: { revision: 1, @@ -170,7 +173,7 @@ describe('upgrade agent policy schema version', () => { await upgradeAgentPolicySchemaVersion(soClient); const policies = await agentPolicyService.list(soClient, { - kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.schema_version:${FLEET_AGENT_POLICIES_SCHEMA_VERSION}`, + kuery: `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.schema_version:${FLEET_AGENT_POLICIES_SCHEMA_VERSION}`, }); // all 3 should be up-to-date after upgrade expect(policies.total).toBe(3); diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 9dfb920251e76..200bd6270f6a6 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -167,20 +167,20 @@ export const createPackagePolicyServiceMock = (): jest.Mocked { - return { + return Promise.resolve({ async *[Symbol.asyncIterator]() { yield Promise.resolve([PackagePolicyMocks.generatePackagePolicy({ id: '111' })]); yield Promise.resolve([PackagePolicyMocks.generatePackagePolicy({ id: '222' })]); }, - }; + }); }), fetchAllItemIds: jest.fn((..._) => { - return { + return Promise.resolve({ async *[Symbol.asyncIterator]() { yield Promise.resolve(['111']); yield Promise.resolve(['222']); }, - }; + }); }), removeOutputFromAll: jest.fn(), }; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index ae65f94238bfa..98ab5630cad80 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -78,7 +78,7 @@ import { } from './services/security'; import { - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, @@ -183,7 +183,7 @@ export type FleetSetupContract = void; const allSavedObjectTypes = [ OUTPUT_SAVED_OBJECT_TYPE, - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 8ff3f82b7e6c6..350eb24847d85 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -47,8 +47,8 @@ import { getAgentStatusForAgentPolicy } from '../../services/agents'; import { isAgentInNamespace } from '../../services/spaces/agent_namespaces'; import { getCurrentNamespace } from '../../services/spaces/get_current_namespace'; -function verifyNamespace(agent: Agent, namespace?: string) { - if (!isAgentInNamespace(agent, namespace)) { +async function verifyNamespace(agent: Agent, namespace?: string) { + if (!(await isAgentInNamespace(agent, namespace))) { throw new FleetNotFoundError(`${agent.id} not found in namespace`); } } @@ -62,7 +62,7 @@ export const getAgentHandler: FleetRequestHandler< const esClientCurrentUser = coreContext.elasticsearch.client.asCurrentUser; let agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); - verifyNamespace(agent, getCurrentNamespace(coreContext.savedObjects.client)); + await verifyNamespace(agent, getCurrentNamespace(coreContext.savedObjects.client)); if (request.query.withMetrics) { agent = (await fetchAndAssignAgentMetrics(esClientCurrentUser, [agent]))[0]; @@ -92,7 +92,7 @@ export const deleteAgentHandler: FleetRequestHandler< try { const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); - verifyNamespace(agent, getCurrentNamespace(coreContext.savedObjects.client)); + await verifyNamespace(agent, getCurrentNamespace(coreContext.savedObjects.client)); await AgentService.deleteAgent(esClient, request.params.agentId); @@ -132,7 +132,7 @@ export const updateAgentHandler: FleetRequestHandler< try { const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); - verifyNamespace(agent, getCurrentNamespace(soClient)); + await verifyNamespace(agent, getCurrentNamespace(soClient)); await AgentService.updateAgent(esClient, request.params.agentId, partialAgent); const body = { diff --git a/x-pack/plugins/fleet/server/routes/app/index.ts b/x-pack/plugins/fleet/server/routes/app/index.ts index db1b86c12243b..981cbdad8f5a7 100644 --- a/x-pack/plugins/fleet/server/routes/app/index.ts +++ b/x-pack/plugins/fleet/server/routes/app/index.ts @@ -8,16 +8,17 @@ import type { RequestHandler, RouteValidationResultFactory } from '@kbn/core/server'; import type { TypeOf } from '@kbn/config-schema'; +import { parseExperimentalConfigValue } from '../../../common/experimental_features'; import type { FleetAuthzRouter } from '../../services/security'; - import { APP_API_ROUTES } from '../../constants'; import { API_VERSIONS } from '../../../common/constants'; - import { appContextService } from '../../services'; import type { CheckPermissionsResponse, GenerateServiceTokenResponse } from '../../../common/types'; import { defaultFleetErrorHandler, GenerateServiceTokenError } from '../../errors'; import type { FleetRequestHandler, GenerateServiceTokenRequestSchema } from '../../types'; import { CheckPermissionsRequestSchema } from '../../types'; +import { enableSpaceAwarenessMigration } from '../../services/spaces/enable_space_awareness'; +import { type FleetConfigType } from '../../config'; export const getCheckPermissionsHandler: FleetRequestHandler< unknown, @@ -98,6 +99,23 @@ export const getCheckPermissionsHandler: FleetRequestHandler< } }; +export const postEnableSpaceAwarenessHandler: FleetRequestHandler = async ( + context, + request, + response +) => { + try { + await enableSpaceAwarenessMigration(); + + return response.ok({ + body: {}, + }); + } catch (e) { + const error = new GenerateServiceTokenError(e); + return defaultFleetErrorHandler({ error, response }); + } +}; + export const generateServiceTokenHandler: RequestHandler< null, null, @@ -142,7 +160,26 @@ const serviceTokenBodyValidation = (data: any, validationResult: RouteValidation return ok({ remote }); }; -export const registerRoutes = (router: FleetAuthzRouter) => { +export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType) => { + const experimentalFeatures = parseExperimentalConfigValue(config.enableExperimental); + + if (experimentalFeatures.useSpaceAwareness) { + router.versioned + .post({ + path: '/internal/fleet/enable_space_awareness', + access: 'internal', + fleetAuthz: { + fleet: { all: true }, + }, + }) + .addVersion( + { + version: API_VERSIONS.internal.v1, + validate: {}, + }, + postEnableSpaceAwarenessHandler + ); + } router.versioned .get({ path: APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN, diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts index 4edea93176de4..a38f5bdadc617 100644 --- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts +++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts @@ -23,19 +23,19 @@ import type { import * as APIKeyService from '../../services/api_keys'; import { agentPolicyService } from '../../services/agent_policy'; import { defaultFleetErrorHandler, AgentPolicyNotFoundError } from '../../errors'; -import { appContextService } from '../../services'; import { getCurrentNamespace } from '../../services/spaces/get_current_namespace'; +import { isSpaceAwarenessEnabled } from '../../services/spaces/helpers'; export const getEnrollmentApiKeysHandler: RequestHandler< undefined, TypeOf > = async (context, request, response) => { - const { useSpaceAwareness } = appContextService.getExperimentalFeatures(); // Use kibana_system and depend on authz checks on HTTP layer to prevent abuse const esClient = (await context.core).elasticsearch.client.asInternalUser; const soClient = (await context.core).savedObjects.client; try { + const useSpaceAwareness = await isSpaceAwarenessEnabled(); const { items, total, page, perPage } = await APIKeyService.listEnrollmentApiKeys(esClient, { page: request.query.page, perPage: request.query.perPage, @@ -91,7 +91,7 @@ export const deleteEnrollmentApiKeyHandler: RequestHandler< TypeOf > = async (context, request, response) => { try { - const { useSpaceAwareness } = appContextService.getExperimentalFeatures(); + const useSpaceAwareness = await isSpaceAwarenessEnabled(); const coreContext = await context.core; const esClient = coreContext.elasticsearch.client.asInternalUser; const currentNamespace = getCurrentNamespace(coreContext.savedObjects.client); @@ -124,7 +124,7 @@ export const getOneEnrollmentApiKeyHandler: RequestHandler< const coreContext = await context.core; const esClient = coreContext.elasticsearch.client.asInternalUser; const currentNamespace = getCurrentNamespace(coreContext.savedObjects.client); - const { useSpaceAwareness } = appContextService.getExperimentalFeatures(); + const useSpaceAwareness = await isSpaceAwarenessEnabled(); const apiKey = await APIKeyService.getEnrollmentAPIKey( esClient, diff --git a/x-pack/plugins/fleet/server/routes/index.ts b/x-pack/plugins/fleet/server/routes/index.ts index 9257d672848a7..41ce57e85de2b 100644 --- a/x-pack/plugins/fleet/server/routes/index.ts +++ b/x-pack/plugins/fleet/server/routes/index.ts @@ -31,7 +31,7 @@ import { registerRoutes as registerDebugRoutes } from './debug'; export function registerRoutes(fleetAuthzRouter: FleetAuthzRouter, config: FleetConfigType) { // Always register app routes for permissions checking - registerAppRoutes(fleetAuthzRouter); + registerAppRoutes(fleetAuthzRouter, config); // The upload package route is only authorized for the superuser registerEPMRoutes(fleetAuthzRouter, config); diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts index bb9bf0b507ca9..d5a49bdb28e0f 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts @@ -179,6 +179,7 @@ describe('FleetStatusHandler', () => { const expectedBody = { isReady: true, is_secrets_storage_enabled: false, + is_space_awareness_enabled: false, missing_optional_features: [], missing_requirements: [], }; @@ -200,6 +201,7 @@ describe('FleetStatusHandler', () => { const expectedBody = { isReady: false, is_secrets_storage_enabled: false, + is_space_awareness_enabled: false, missing_optional_features: [], missing_requirements: ['api_keys', 'fleet_server'], }; @@ -228,6 +230,7 @@ describe('FleetStatusHandler', () => { const expectedBody = { isReady: true, is_secrets_storage_enabled: false, + is_space_awareness_enabled: false, missing_optional_features: [], missing_requirements: [], }; diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index 019fb2af5276b..05ee55320d445 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -13,6 +13,7 @@ import { defaultFleetErrorHandler } from '../../errors'; import type { FleetRequestHandler } from '../../types'; import { getGpgKeyIdOrUndefined } from '../../services/epm/packages/package_verification'; import { isSecretStorageEnabled } from '../../services/secrets'; +import { isSpaceAwarenessEnabled } from '../../services/spaces/helpers'; export const getFleetStatusHandler: FleetRequestHandler = async (context, request, response) => { const coreContext = await context.core; @@ -24,7 +25,14 @@ export const getFleetStatusHandler: FleetRequestHandler = async (context, reques const isApiKeysEnabled = await appContextService .getSecurity() .authc.apiKeys.areAPIKeysEnabled(); - const isFleetServerMissing = !(await hasFleetServers(esClient, soClient)); + + const [hasFleetServersRes, useSecretsStorage, isSpaceAwarenessEnabledRes] = await Promise.all([ + hasFleetServers(esClient, soClient), + isSecretStorageEnabled(esClient, soClient), + isSpaceAwarenessEnabled(), + ]); + + const isFleetServerMissing = !hasFleetServersRes; const isFleetServerStandalone = appContextService.getConfig()?.internal?.fleetServerStandalone ?? false; @@ -43,13 +51,12 @@ export const getFleetStatusHandler: FleetRequestHandler = async (context, reques missingOptionalFeatures.push('encrypted_saved_object_encryption_key_required'); } - const useSecretsStorage = await isSecretStorageEnabled(esClient, soClient); - const body: GetFleetStatusResponse = { isReady: missingRequirements.length === 0, missing_requirements: missingRequirements, missing_optional_features: missingOptionalFeatures, is_secrets_storage_enabled: useSecretsStorage, + is_space_awareness_enabled: isSpaceAwarenessEnabledRes, }; const packageVerificationKeyId = await getGpgKeyIdOrUndefined(); diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts index 5e61d5b9b01a2..24d85b8d14250 100644 --- a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts @@ -15,7 +15,7 @@ import type { } from '../../types/rest_spec/uninstall_token'; import { defaultFleetErrorHandler } from '../../errors'; import type { GetUninstallTokenResponse } from '../../../common/types/rest_spec/uninstall_token'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../constants'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../constants'; export const getUninstallTokensMetadataHandler: FleetRequestHandler< unknown, @@ -40,7 +40,7 @@ export const getUninstallTokensMetadataHandler: FleetRequestHandler< const { items: managedPolicies } = await agentPolicyService.list(soClient, { fields: ['id'], perPage: SO_SEARCH_LIMIT, - kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.is_managed:true`, + kuery: `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.is_managed:true`, }); const managedPolicyIds = managedPolicies.map((policy) => policy.id); diff --git a/x-pack/plugins/fleet/server/routes/utils/filter_utils_real_queries.test.ts b/x-pack/plugins/fleet/server/routes/utils/filter_utils_real_queries.test.ts index a8765dc87327b..0c81462dd197b 100644 --- a/x-pack/plugins/fleet/server/routes/utils/filter_utils_real_queries.test.ts +++ b/x-pack/plugins/fleet/server/routes/utils/filter_utils_real_queries.test.ts @@ -8,7 +8,7 @@ import * as esKuery from '@kbn/es-query'; import { - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, AGENTS_PREFIX, AGENT_POLICY_MAPPINGS, @@ -30,11 +30,11 @@ describe('ValidateFilterKueryNode validates real kueries through KueryNode', () describe('Agent policies', () => { it('Search by data_output_id', async () => { const astFilter = esKuery.fromKueryExpression( - `${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id: test_id` + `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id: test_id` ); const validationObject = validateFilterKueryNode({ astFilter, - types: [AGENT_POLICY_SAVED_OBJECT_TYPE], + types: [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], indexMapping: AGENT_POLICY_MAPPINGS, storeValue: true, }); @@ -51,11 +51,11 @@ describe('ValidateFilterKueryNode validates real kueries through KueryNode', () it('Search by inactivity timeout', async () => { const astFilter = esKuery.fromKueryExpression( - `${AGENT_POLICY_SAVED_OBJECT_TYPE}.inactivity_timeout:*` + `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.inactivity_timeout:*` ); const validationObject = validateFilterKueryNode({ astFilter, - types: [AGENT_POLICY_SAVED_OBJECT_TYPE], + types: [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], indexMapping: AGENT_POLICY_MAPPINGS, storeValue: true, }); @@ -73,9 +73,9 @@ describe('ValidateFilterKueryNode validates real kueries through KueryNode', () it('Complex query', async () => { const validationObject = validateFilterKueryNode({ astFilter: esKuery.fromKueryExpression( - `${AGENT_POLICY_SAVED_OBJECT_TYPE}.download_source_id:some_id or (not ${AGENT_POLICY_SAVED_OBJECT_TYPE}.download_source_id:*)` + `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.download_source_id:some_id or (not ${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.download_source_id:*)` ), - types: [AGENT_POLICY_SAVED_OBJECT_TYPE], + types: [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], indexMapping: AGENT_POLICY_MAPPINGS, storeValue: true, }); @@ -100,11 +100,11 @@ describe('ValidateFilterKueryNode validates real kueries through KueryNode', () it('Test another complex query', async () => { const astFilter = esKuery.fromKueryExpression( - `${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id: test_id or ${AGENT_POLICY_SAVED_OBJECT_TYPE}.monitoring_output_id: test_id or (not ${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:*)` + `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id: test_id or ${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.monitoring_output_id: test_id or (not ${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:*)` ); const validationObject = validateFilterKueryNode({ astFilter, - types: [AGENT_POLICY_SAVED_OBJECT_TYPE], + types: [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], indexMapping: AGENT_POLICY_MAPPINGS, storeValue: true, }); @@ -136,11 +136,11 @@ describe('ValidateFilterKueryNode validates real kueries through KueryNode', () it('Returns error if the attribute does not exist', async () => { const astFilter = esKuery.fromKueryExpression( - `${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies:test_id_1 or ${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies:test_id_2` + `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies:test_id_1 or ${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies:test_id_2` ); const validationObject = validateFilterKueryNode({ astFilter, - types: [AGENT_POLICY_SAVED_OBJECT_TYPE], + types: [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], indexMapping: AGENT_POLICY_MAPPINGS, storeValue: true, }); @@ -523,8 +523,8 @@ describe('validateKuery validates real kueries', () => { describe('Agent policies', () => { it('Search by data_output_id', async () => { const validationObj = validateKuery( - `${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id: test_id`, - [AGENT_POLICY_SAVED_OBJECT_TYPE], + `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id: test_id`, + [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], AGENT_POLICY_MAPPINGS, true ); @@ -533,8 +533,8 @@ describe('validateKuery validates real kueries', () => { it('Search by data_output_id without SO wrapping', async () => { const validationObj = validateKuery( - `${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id: test_id`, - [AGENT_POLICY_SAVED_OBJECT_TYPE], + `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id: test_id`, + [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], AGENT_POLICY_MAPPINGS, true ); @@ -543,8 +543,8 @@ describe('validateKuery validates real kueries', () => { it('Search by name', async () => { const validationObj = validateKuery( - `${AGENT_POLICY_SAVED_OBJECT_TYPE}.name: test_id`, - [AGENT_POLICY_SAVED_OBJECT_TYPE], + `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.name: test_id`, + [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], AGENT_POLICY_MAPPINGS, true ); @@ -553,8 +553,8 @@ describe('validateKuery validates real kueries', () => { it('Kuery with non existent parameter wrapped by SO', async () => { const validationObj = validateKuery( - `${AGENT_POLICY_SAVED_OBJECT_TYPE}.non_existent_parameter: 'test_id'`, - [AGENT_POLICY_SAVED_OBJECT_TYPE], + `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.non_existent_parameter: 'test_id'`, + [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], AGENT_POLICY_MAPPINGS, true ); @@ -567,7 +567,7 @@ describe('validateKuery validates real kueries', () => { it('Invalid search by non existent parameter', async () => { const validationObj = validateKuery( `non_existent_parameter: 'test_id'`, - [AGENT_POLICY_SAVED_OBJECT_TYPE], + [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], AGENT_POLICY_MAPPINGS, true ); diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index f6fcae4de6505..d955b10031536 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -9,10 +9,15 @@ import type { SavedObjectsServiceSetup, SavedObjectsType } from '@kbn/core/serve import type { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, + PACKAGE_POLICY_SAVED_OBJECT_TYPE, +} from '../../common/constants'; + import { OUTPUT_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, AGENT_POLICY_SAVED_OBJECT_TYPE, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, ASSETS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, @@ -134,10 +139,7 @@ export const getSavedObjectTypes = ( }, mappings: { dynamic: false, - properties: { - // allowed_namespace_prefixes: { enabled: false }, - // managed_by: { type: 'keyword', index: false }, - }, + properties: {}, }, }, // Deprecated @@ -156,6 +158,8 @@ export const getSavedObjectTypes = ( prerelease_integrations_enabled: { type: 'boolean' }, secret_storage_requirements_met: { type: 'boolean' }, output_secret_storage_requirements_met: { type: 'boolean' }, + use_space_awareness_migration_status: { type: 'keyword', index: false }, + use_space_awareness_migration_started_at: { type: 'date', index: false }, }, }, migrations: { @@ -165,13 +169,24 @@ export const getSavedObjectTypes = ( }, modelVersions: { 1: settingsV1, + 2: { + changes: [ + { + type: 'mappings_addition', + addedMappings: { + use_space_awareness_migration_status: { type: 'keyword', index: false }, + use_space_awareness_migration_started_at: { type: 'date', index: false }, + }, + }, + ], + }, }, }, - [AGENT_POLICY_SAVED_OBJECT_TYPE]: { - name: AGENT_POLICY_SAVED_OBJECT_TYPE, + [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]: { + name: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, - namespaceType: useSpaceAwareness ? 'single' : 'agnostic', + namespaceType: 'agnostic', management: { importableAndExportable: false, }, @@ -250,6 +265,50 @@ export const getSavedObjectTypes = ( }, }, }, + [AGENT_POLICY_SAVED_OBJECT_TYPE]: { + name: AGENT_POLICY_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, + hidden: false, + namespaceType: 'multiple', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + name: { type: 'keyword' }, + schema_version: { type: 'version' }, + description: { type: 'text' }, + namespace: { type: 'keyword' }, + is_managed: { type: 'boolean' }, + is_default: { type: 'boolean' }, + is_default_fleet_server: { type: 'boolean' }, + status: { type: 'keyword' }, + unenroll_timeout: { type: 'integer' }, + inactivity_timeout: { type: 'integer' }, + updated_at: { type: 'date' }, + updated_by: { type: 'keyword' }, + revision: { type: 'integer' }, + monitoring_enabled: { type: 'keyword', index: false }, + is_preconfigured: { type: 'keyword' }, + data_output_id: { type: 'keyword' }, + monitoring_output_id: { type: 'keyword' }, + download_source_id: { type: 'keyword' }, + fleet_server_host_id: { type: 'keyword' }, + agent_features: { + properties: { + name: { type: 'keyword' }, + enabled: { type: 'boolean' }, + }, + }, + is_protected: { type: 'boolean' }, + overrides: { type: 'flattened', index: false }, + keep_monitoring_alive: { type: 'boolean' }, + advanced_settings: { type: 'flattened', index: false }, + supports_agentless: { type: 'boolean' }, + global_data_tags: { type: 'flattened', index: false }, + }, + }, + }, [OUTPUT_SAVED_OBJECT_TYPE]: { name: OUTPUT_SAVED_OBJECT_TYPE, indexPattern: INGEST_SAVED_OBJECT_INDEX, @@ -464,11 +523,11 @@ export const getSavedObjectTypes = ( '8.0.0': migrateOutputToV800, }, }, - [PACKAGE_POLICY_SAVED_OBJECT_TYPE]: { - name: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + [LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE]: { + name: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, - namespaceType: useSpaceAwareness ? 'single' : 'agnostic', + namespaceType: 'agnostic', management: { importableAndExportable: false, }, @@ -668,6 +727,51 @@ export const getSavedObjectTypes = ( '8.8.0': migratePackagePolicyToV880, }, }, + [PACKAGE_POLICY_SAVED_OBJECT_TYPE]: { + name: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + indexPattern: INGEST_SAVED_OBJECT_INDEX, + hidden: false, + namespaceType: 'multiple', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + name: { type: 'keyword' }, + description: { type: 'text' }, + namespace: { type: 'keyword' }, + enabled: { type: 'boolean' }, + is_managed: { type: 'boolean' }, + policy_id: { type: 'keyword' }, + policy_ids: { type: 'keyword' }, + output_id: { type: 'keyword' }, + package: { + properties: { + name: { type: 'keyword' }, + title: { type: 'keyword' }, + version: { type: 'keyword' }, + requires_root: { type: 'boolean' }, + }, + }, + elasticsearch: { + dynamic: false, + properties: {}, + }, + vars: { type: 'flattened' }, + inputs: { + dynamic: false, + properties: {}, + }, + secret_references: { properties: { id: { type: 'keyword' } } }, + overrides: { type: 'flattened', index: false }, + revision: { type: 'integer' }, + updated_at: { type: 'date' }, + updated_by: { type: 'keyword' }, + created_at: { type: 'date' }, + created_by: { type: 'keyword' }, + }, + }, + }, [PACKAGES_SAVED_OBJECT_TYPE]: { name: PACKAGES_SAVED_OBJECT_TYPE, indexPattern: INGEST_SAVED_OBJECT_INDEX, diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index 628be3ec2b2d5..0ff710b11fac8 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -11,6 +11,8 @@ import { loggerMock } from '@kbn/logging-mocks'; import type { Logger } from '@kbn/core/server'; import type { SavedObjectError } from '@kbn/core-saved-objects-common'; +import { LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../common/constants'; + import { PackagePolicyRestrictionRelatedError, FleetUnauthorizedError, @@ -23,7 +25,10 @@ import type { NewAgentPolicy, PreconfiguredAgentPolicy, } from '../types'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; +import { + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, + AGENT_POLICY_SAVED_OBJECT_TYPE, +} from '../constants'; import { AGENT_POLICY_INDEX, SO_SEARCH_LIMIT } from '../../common'; @@ -31,7 +36,7 @@ import { agentPolicyService } from './agent_policy'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; import { getAgentsByKuery } from './agents'; -import { packagePolicyService } from './package_policy'; +import { getPackagePolicySavedObjectType, packagePolicyService } from './package_policy'; import { appContextService } from './app_context'; import { outputService } from './output'; import { downloadSourceService } from './download_source'; @@ -40,11 +45,14 @@ import * as outputsHelpers from './agent_policies/outputs_helpers'; import { auditLoggingService } from './audit_logging'; import { licenseService } from './license'; import type { UninstallTokenServiceInterface } from './security/uninstall_token_service'; +import { isSpaceAwarenessEnabled } from './spaces/helpers'; + +jest.mock('./spaces/helpers'); function getSavedObjectMock(agentPolicyAttributes: any) { const mock = savedObjectsClientMock.create(); const mockPolicy = { - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, references: [], attributes: agentPolicyAttributes as AgentPolicy, }; @@ -61,7 +69,7 @@ function getSavedObjectMock(agentPolicyAttributes: any) { }); mock.find.mockImplementation(async (options) => { switch (options.type) { - case AGENT_POLICY_SAVED_OBJECT_TYPE: + case LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE: return { saved_objects: [ { @@ -74,7 +82,7 @@ function getSavedObjectMock(agentPolicyAttributes: any) { page: 1, per_page: 1, }; - case PACKAGE_POLICY_SAVED_OBJECT_TYPE: + case LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE: return { saved_objects: [], total: 0, @@ -150,6 +158,10 @@ describe('Agent policy', () => { mockedLogger = loggerMock.create(); mockedAppContextService.getLogger.mockReturnValue(mockedLogger); mockedAppContextService.getExperimentalFeatures.mockReturnValue({ agentless: false } as any); + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(false); + jest + .mocked(getPackagePolicySavedObjectType) + .mockResolvedValue(LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE); }); afterEach(() => { @@ -204,7 +216,7 @@ describe('Agent policy', () => { soClient.create.mockResolvedValueOnce({ id: 'test-agent-policy', - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, attributes: {}, references: [], }); @@ -221,6 +233,48 @@ describe('Agent policy', () => { { id: 'test-agent-policy' } ); + expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenCalledWith({ + action: 'create', + id: 'test-agent-policy', + savedObjectType: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, + }); + }); + + it('should write to the correct saved object-type if user opt-in for space awerness', async () => { + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(true); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const soClient = savedObjectsClientMock.create(); + + soClient.find.mockResolvedValueOnce({ + total: 0, + saved_objects: [], + per_page: 0, + page: 1, + }); + + soClient.create.mockResolvedValueOnce({ + id: 'test-agent-policy', + type: AGENT_POLICY_SAVED_OBJECT_TYPE, + attributes: {}, + references: [], + }); + + mockOutputsHelpers.validateOutputForPolicy.mockResolvedValueOnce(undefined); + + await agentPolicyService.create( + soClient, + esClient, + { + name: 'test', + namespace: 'default', + }, + { id: 'test-agent-policy' } + ); + expect(soClient.create).toBeCalledWith( + AGENT_POLICY_SAVED_OBJECT_TYPE, + expect.anything(), + expect.anything() + ); expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenCalledWith({ action: 'create', id: 'test-agent-policy', @@ -443,7 +497,7 @@ describe('Agent policy', () => { id: 'test-agent-policy', attributes: {}, references: [], - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); await agentPolicyService.get(soClient, 'test-agent-policy', false); @@ -451,7 +505,7 @@ describe('Agent policy', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toBeCalledWith({ action: 'get', id: 'test-agent-policy', - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -466,13 +520,13 @@ describe('Agent policy', () => { id: 'test-agent-policy-1', attributes: {}, references: [], - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }, { id: 'test-agent-policy-2', attributes: {}, references: [], - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }, ], }); @@ -482,13 +536,13 @@ describe('Agent policy', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(1, { action: 'get', id: 'test-agent-policy-1', - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(2, { action: 'get', id: 'test-agent-policy-2', - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -504,14 +558,14 @@ describe('Agent policy', () => { id: 'test-agent-policy-1', attributes: {}, references: [], - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, score: 0, }, { id: 'test-agent-policy-2', attributes: {}, references: [], - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, score: 0, }, ], @@ -528,13 +582,13 @@ describe('Agent policy', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(1, { action: 'find', id: 'test-agent-policy-1', - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(2, { action: 'find', id: 'test-agent-policy-2', - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -608,7 +662,7 @@ describe('Agent policy', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenCalledWith({ action: 'delete', id: 'mocked', - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); }); @@ -708,7 +762,7 @@ describe('Agent policy', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenCalledWith({ action: 'delete', id: 'mocked', - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); }); @@ -1055,7 +1109,7 @@ describe('Agent policy', () => { attributes: {}, references: [], id: 'test-agent-policy', - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); await agentPolicyService.update(soClient, esClient, 'test-agent-policy', { @@ -1067,7 +1121,7 @@ describe('Agent policy', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenCalledWith({ action: 'update', id: 'test-agent-policy', - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }); }); @@ -1370,7 +1424,7 @@ describe('Agent policy', () => { attributes: {}, references: [], id: 'test-agent-policy', - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, }, ], }); @@ -1407,7 +1461,7 @@ describe('Agent policy', () => { soClient.create.mockResolvedValueOnce({ id: 'my-unique-id', - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, attributes: {}, references: [], }); @@ -1419,7 +1473,7 @@ describe('Agent policy', () => { ); expect(soClient.create).toHaveBeenCalledWith( - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, expect.anything(), expect.objectContaining({ id: 'my-unique-id' }) ); @@ -1429,7 +1483,7 @@ describe('Agent policy', () => { describe('getInactivityTimeouts', () => { const createPolicySO = (id: string, inactivityTimeout: number) => ({ id, - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, attributes: { inactivity_timeout: inactivityTimeout }, references: [], score: 1, @@ -1493,7 +1547,7 @@ describe('Agent policy', () => { return { score: 1, id: 'so-123', - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, version: 'abc', updated_at: soAttributes.updated_at, attributes: soAttributes, @@ -1522,13 +1576,13 @@ describe('Agent policy', () => { }); it('should return an iterator', async () => { - expect(agentPolicyService.fetchAllAgentPolicyIds(soClientMock)).toEqual({ + expect(await agentPolicyService.fetchAllAgentPolicyIds(soClientMock)).toEqual({ [Symbol.asyncIterator]: expect.any(Function), }); }); it('should provide item ids on every iteration', async () => { - for await (const ids of agentPolicyService.fetchAllAgentPolicyIds(soClientMock)) { + for await (const ids of await agentPolicyService.fetchAllAgentPolicyIds(soClientMock)) { expect(ids).toEqual(['so-123', 'so-123']); } @@ -1536,13 +1590,13 @@ describe('Agent policy', () => { }); it('should use default options', async () => { - for await (const ids of agentPolicyService.fetchAllAgentPolicyIds(soClientMock)) { + for await (const ids of await agentPolicyService.fetchAllAgentPolicyIds(soClientMock)) { expect(ids); } expect(soClientMock.find).toHaveBeenCalledWith( expect.objectContaining({ - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, perPage: 1000, sortField: 'created_at', sortOrder: 'asc', @@ -1553,7 +1607,7 @@ describe('Agent policy', () => { }); it('should use custom options when defined', async () => { - for await (const ids of agentPolicyService.fetchAllAgentPolicyIds(soClientMock, { + for await (const ids of await agentPolicyService.fetchAllAgentPolicyIds(soClientMock, { perPage: 13, kuery: 'one=two', })) { @@ -1562,7 +1616,7 @@ describe('Agent policy', () => { expect(soClientMock.find).toHaveBeenCalledWith( expect.objectContaining({ - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, perPage: 13, sortField: 'created_at', sortOrder: 'asc', @@ -1586,13 +1640,13 @@ describe('Agent policy', () => { }); it('should return an iterator', async () => { - expect(agentPolicyService.fetchAllAgentPolicies(soClientMock)).toEqual({ + expect(await agentPolicyService.fetchAllAgentPolicies(soClientMock)).toEqual({ [Symbol.asyncIterator]: expect.any(Function), }); }); it('should provide items on every iteration', async () => { - for await (const items of agentPolicyService.fetchAllAgentPolicies(soClientMock)) { + for await (const items of await agentPolicyService.fetchAllAgentPolicies(soClientMock)) { expect(items.map((item) => item.id)).toEqual(soList.map((_so) => 'so-123')); } @@ -1600,7 +1654,25 @@ describe('Agent policy', () => { }); it('should use default options', async () => { - for await (const ids of agentPolicyService.fetchAllAgentPolicies(soClientMock)) { + for await (const ids of await agentPolicyService.fetchAllAgentPolicies(soClientMock)) { + expect(ids); + } + + expect(soClientMock.find).toHaveBeenCalledWith( + expect.objectContaining({ + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, + perPage: 1000, + sortField: 'created_at', + sortOrder: 'asc', + fields: [], + filter: undefined, + }) + ); + }); + + it('should use new saved object if user opt-in for space awareness', async () => { + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(true); + for await (const ids of await agentPolicyService.fetchAllAgentPolicies(soClientMock)) { expect(ids); } @@ -1617,7 +1689,7 @@ describe('Agent policy', () => { }); it('should use custom options when defined', async () => { - for await (const ids of agentPolicyService.fetchAllAgentPolicies(soClientMock, { + for await (const ids of await agentPolicyService.fetchAllAgentPolicies(soClientMock, { kuery: 'one=two', perPage: 12, sortOrder: 'desc', @@ -1628,7 +1700,7 @@ describe('Agent policy', () => { expect(soClientMock.find).toHaveBeenCalledWith( expect.objectContaining({ - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, perPage: 12, sortField: 'updated_by', sortOrder: 'desc', @@ -1642,7 +1714,7 @@ describe('Agent policy', () => { describe('turnOffAgentTamperProtections', () => { const createPolicySO = (id: string, isProtected: boolean, error?: SavedObjectError) => ({ id, - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, attributes: { is_protected: isProtected, }, @@ -1664,9 +1736,11 @@ describe('Agent policy', () => { }); const getMockAgentPolicyFetchAllAgentPolicies = (items: AgentPolicy[]) => - jest.fn(async function* () { - yield items; - }); + jest.fn().mockResolvedValue( + jest.fn(async function* () { + yield items; + })() + ); it('should return if all policies are compliant', async () => { const mockSoClient = savedObjectsClientMock.create(); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index a7176083a718f..aceb494687f52 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -41,8 +41,7 @@ import { import type { HTTPAuthorizationHeader } from '../../common/http_authorization_header'; import { - PACKAGE_POLICY_SAVED_OBJECT_TYPE, - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, AGENTS_PREFIX, FLEET_AGENT_POLICIES_SCHEMA_VERSION, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, @@ -67,6 +66,7 @@ import { agentPolicyStatuses, FLEET_ELASTIC_AGENT_PACKAGE, UUID_V5_NAMESPACE, + AGENT_POLICY_SAVED_OBJECT_TYPE, } from '../../common/constants'; import type { DeleteAgentPolicyResponse, @@ -100,22 +100,47 @@ import { import { bulkInstallPackages } from './epm/packages'; import { getAgentsByKuery } from './agents'; -import { packagePolicyService } from './package_policy'; +import { getPackagePolicySavedObjectType, packagePolicyService } from './package_policy'; import { incrementPackagePolicyCopyName } from './package_policies'; import { outputService } from './output'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; -import { escapeSearchQueryPhrase, normalizeKuery } from './saved_object'; +import { escapeSearchQueryPhrase, normalizeKuery as _normalizeKuery } from './saved_object'; import { getFullAgentPolicy, validateOutputForPolicy } from './agent_policies'; import { auditLoggingService } from './audit_logging'; import { licenseService } from './license'; import { createSoFindIterable } from './utils/create_so_find_iterable'; import { isAgentlessEnabled } from './utils/agentless'; import { validatePolicyNamespaceForSpace } from './spaces/policy_namespaces'; - -const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; +import { isSpaceAwarenessEnabled } from './spaces/helpers'; const KEY_EDITABLE_FOR_MANAGED_POLICIES = ['namespace']; +function normalizeKuery(savedObjectType: string, kuery: string) { + if (savedObjectType === LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE) { + return _normalizeKuery( + savedObjectType, + kuery.replace( + new RegExp(`${AGENT_POLICY_SAVED_OBJECT_TYPE}\\.`, 'g'), + `${savedObjectType}.attributes.` + ) + ); + } else { + return _normalizeKuery( + savedObjectType, + kuery.replace( + new RegExp(`${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}\\.`, 'g'), + `${savedObjectType}.attributes.` + ) + ); + } +} + +export async function getAgentPolicySavedObjectType() { + return (await isSpaceAwarenessEnabled()) + ? AGENT_POLICY_SAVED_OBJECT_TYPE + : LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE; +} + class AgentPolicyService { private triggerAgentPolicyUpdatedEvent = async ( esClient: ElasticsearchClient, @@ -144,10 +169,11 @@ class AgentPolicyService { returnUpdatedPolicy: true, } ): Promise { + const savedObjectType = await getAgentPolicySavedObjectType(); auditLoggingService.writeCustomSoAuditLog({ action: 'update', id, - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); const logger = appContextService.getLogger(); logger.debug(`Starting update of agent policy ${id}`); @@ -179,8 +205,7 @@ class AgentPolicyService { getAllowedOutputTypeForPolicy(existingAgentPolicy) ); } - - await soClient.update(SAVED_OBJECT_TYPE, id, { + await soClient.update(savedObjectType, id, { ...agentPolicy, ...(options.bumpRevision ? { revision: existingAgentPolicy.revision + 1 } : {}), ...(options.removeProtection @@ -323,6 +348,7 @@ class AgentPolicyService { skipDeploy?: boolean; } = {} ): Promise { + const savedObjectType = await getAgentPolicySavedObjectType(); // Ensure an ID is provided, so we can include it in the audit logs below if (!options.id) { options.id = SavedObjectsUtils.generateId(); @@ -331,7 +357,7 @@ class AgentPolicyService { auditLoggingService.writeCustomSoAuditLog({ action: 'create', id: options.id, - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); await this.runExternalCallbacks('agentPolicyCreate', agentPolicy); this.checkTamperProtectionLicense(agentPolicy); @@ -355,7 +381,7 @@ class AgentPolicyService { await validateOutputForPolicy(soClient, agentPolicy); const newSo = await soClient.create( - SAVED_OBJECT_TYPE, + savedObjectType, { ...agentPolicy, status: 'active', @@ -385,8 +411,10 @@ class AgentPolicyService { soClient: SavedObjectsClientContract, givenPolicy: { id?: string; name: string } ) { + const savedObjectType = await getAgentPolicySavedObjectType(); + const results = await soClient.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, searchFields: ['name'], search: escapeSearchQueryPhrase(givenPolicy.name), }); @@ -409,7 +437,9 @@ class AgentPolicyService { id: string, withPackagePolicies: boolean = true ): Promise { - const agentPolicySO = await soClient.get(SAVED_OBJECT_TYPE, id); + const savedObjectType = await getAgentPolicySavedObjectType(); + + const agentPolicySO = await soClient.get(savedObjectType, id); if (!agentPolicySO) { return null; } @@ -428,7 +458,7 @@ class AgentPolicyService { auditLoggingService.writeCustomSoAuditLog({ action: 'get', id, - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); return agentPolicy; @@ -439,15 +469,17 @@ class AgentPolicyService { ids: Array, options: { fields?: string[]; withPackagePolicies?: boolean; ignoreMissing?: boolean } = {} ): Promise { + const savedObjectType = await getAgentPolicySavedObjectType(); + const objects = ids.map((id) => { if (typeof id === 'string') { - return { ...options, id, type: SAVED_OBJECT_TYPE }; + return { ...options, id, type: savedObjectType }; } return { ...options, id: id.id, namespaces: id.spaceId ? [id.spaceId] : undefined, - type: SAVED_OBJECT_TYPE, + type: savedObjectType, }; }); const bulkGetResponse = await soClient.bulkGet(objects); @@ -488,7 +520,7 @@ class AgentPolicyService { auditLoggingService.writeCustomSoAuditLog({ action: 'get', id: agentPolicy.id, - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); } @@ -509,6 +541,8 @@ class AgentPolicyService { page: number; perPage: number; }> { + const savedObjectType = await getAgentPolicySavedObjectType(); + const { page = 1, perPage = 20, @@ -520,14 +554,14 @@ class AgentPolicyService { } = options; const baseFindParams = { - type: SAVED_OBJECT_TYPE, + type: savedObjectType, sortField, sortOrder, page, perPage, ...(fields ? { fields } : {}), }; - const filter = kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined; + const filter = kuery ? normalizeKuery(savedObjectType, kuery) : undefined; let agentPoliciesSO; try { agentPoliciesSO = await soClient.find({ @@ -580,7 +614,7 @@ class AgentPolicyService { auditLoggingService.writeCustomSoAuditLog({ action: 'find', id: agentPolicy.id, - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); } @@ -807,9 +841,10 @@ class AgentPolicyService { esClient: ElasticsearchClient, outputId: string ) { + const savedObjectType = await getAgentPolicySavedObjectType(); const agentPolicies = ( await soClient.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, fields: ['revision', 'data_output_id', 'monitoring_output_id'], searchFields: ['data_output_id', 'monitoring_output_id'], search: escapeSearchQueryPhrase(outputId), @@ -865,9 +900,10 @@ class AgentPolicyService { esClient: ElasticsearchClient, fleetServerHostId: string ) { + const savedObjectType = await getAgentPolicySavedObjectType(); const agentPolicies = ( await soClient.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, fields: ['revision', 'fleet_server_host_id'], searchFields: ['fleet_server_host_id'], search: escapeSearchQueryPhrase(fleetServerHostId), @@ -958,10 +994,11 @@ class AgentPolicyService { const internalSoClientWithoutSpaceExtension = appContextService.getInternalUserSOClientWithoutSpaceExtension(); + const savedObjectType = await getAgentPolicySavedObjectType(); // All agent policies directly using output const agentPoliciesUsingOutput = await internalSoClientWithoutSpaceExtension.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, fields: ['revision', 'data_output_id', 'monitoring_output_id', 'namespaces'], searchFields: ['data_output_id', 'monitoring_output_id'], search: escapeSearchQueryPhrase(outputId), @@ -972,7 +1009,7 @@ class AgentPolicyService { // All package policies directly using output const packagePoliciesUsingOutput = await internalSoClientWithoutSpaceExtension.find({ - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: await getPackagePolicySavedObjectType(), fields: ['output_id', 'namespaces', 'policy_ids'], searchFields: ['output_id'], search: escapeSearchQueryPhrase(outputId), @@ -995,7 +1032,7 @@ class AgentPolicyService { const agentPoliciesOfPackagePoliciesUsingOutput = await internalSoClientWithoutSpaceExtension.bulkGet( [...agentPolicyIdsOfPackagePoliciesUsingOutput].map((id) => ({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, id, fields: ['revision', 'data_output_id', 'monitoring_output_id', 'namespaces'], ...(useSpaceAwareness ? { namespaces: ['*'] } : {}), @@ -1019,10 +1056,10 @@ class AgentPolicyService { ): Promise> { const internalSoClientWithoutSpaceExtension = appContextService.getInternalUserSOClientWithoutSpaceExtension(); - + const savedObjectType = await getAgentPolicySavedObjectType(); const currentPolicies = await internalSoClientWithoutSpaceExtension.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, fields: ['name', 'revision', 'namespaces'], perPage: SO_SEARCH_LIMIT, namespaces: ['*'], @@ -1044,11 +1081,11 @@ class AgentPolicyService { ): Promise { const logger = appContextService.getLogger(); logger.debug(`Deleting agent policy ${id}`); - + const savedObjectType = await getAgentPolicySavedObjectType(); auditLoggingService.writeCustomSoAuditLog({ action: 'delete', id, - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); const agentPolicy = await this.get(soClient, id, false); @@ -1131,7 +1168,7 @@ class AgentPolicyService { }); } - await soClient.delete(SAVED_OBJECT_TYPE, id); + await soClient.delete(savedObjectType, id); await this.triggerAgentPolicyUpdatedEvent(esClient, 'deleted', id, { spaceId: soClient.getCurrentNamespace(), }); @@ -1400,9 +1437,10 @@ class AgentPolicyService { esClient: ElasticsearchClient, downloadSourceId: string ) { + const savedObjectType = await getAgentPolicySavedObjectType(); const agentPolicies = ( await soClient.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, fields: ['revision', 'download_source_id'], searchFields: ['download_source_id'], search: escapeSearchQueryPhrase(downloadSourceId), @@ -1437,9 +1475,10 @@ class AgentPolicyService { ): Promise> { const internalSoClientWithoutSpaceExtension = appContextService.getInternalUserSOClientWithoutSpaceExtension(); + const savedObjectType = await getAgentPolicySavedObjectType(); const currentPolicies = await internalSoClientWithoutSpaceExtension.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, fields: ['revision', 'download_source_id', 'namespaces'], searchFields: ['download_source_id'], search: escapeSearchQueryPhrase(downloadSourceId), @@ -1462,9 +1501,10 @@ class AgentPolicyService { ): Promise> { const internalSoClientWithoutSpaceExtension = appContextService.getInternalUserSOClientWithoutSpaceExtension(); + const savedObjectType = await getAgentPolicySavedObjectType(); const currentPolicies = await internalSoClientWithoutSpaceExtension.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, fields: ['revision', 'fleet_server_host_id', 'namespaces'], searchFields: ['fleet_server_host_id'], search: escapeSearchQueryPhrase(fleetServerHostId), @@ -1482,11 +1522,12 @@ class AgentPolicyService { public async getInactivityTimeouts( soClient: SavedObjectsClientContract ): Promise> { + const savedObjectType = await getAgentPolicySavedObjectType(); const findRes = await soClient.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, page: 1, perPage: SO_SEARCH_LIMIT, - filter: `${SAVED_OBJECT_TYPE}.attributes.inactivity_timeout > 0`, + filter: `${savedObjectType}.attributes.inactivity_timeout > 0`, fields: [`inactivity_timeout`], }); @@ -1502,8 +1543,9 @@ class AgentPolicyService { updatedPolicies: Array> | null; failedPolicies: Array<{ id: string; error: Error | SavedObjectError }>; }> { - const agentPolicyFetcher = this.fetchAllAgentPolicies(soClient, { - kuery: 'ingest-agent-policies.is_protected: true', + const savedObjectType = await getAgentPolicySavedObjectType(); + const agentPolicyFetcher = await this.fetchAllAgentPolicies(soClient, { + kuery: `${savedObjectType}.is_protected: true`, }); const updatedAgentPolicies: Array> = []; @@ -1515,7 +1557,7 @@ class AgentPolicyService { const { id, revision } = agentPolicy; return { id, - type: SAVED_OBJECT_TYPE, + type: savedObjectType, attributes: { is_protected: false, revision: revision + 1, @@ -1562,36 +1604,38 @@ class AgentPolicyService { } public async getAllManagedAgentPolicies(soClient: SavedObjectsClientContract) { + const savedObjectType = await getAgentPolicySavedObjectType(); const { saved_objects: agentPolicies } = await soClient.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, page: 1, perPage: SO_SEARCH_LIMIT, - filter: normalizeKuery(SAVED_OBJECT_TYPE, 'ingest-agent-policies.is_managed: true'), + filter: normalizeKuery(savedObjectType, 'ingest-agent-policies.is_managed: true'), }); return agentPolicies; } - public fetchAllAgentPolicyIds( + public async fetchAllAgentPolicyIds( soClient: SavedObjectsClientContract, { perPage = 1000, kuery = undefined }: FetchAllAgentPolicyIdsOptions = {} - ): AsyncIterable { + ): Promise> { + const savedObjectType = await getAgentPolicySavedObjectType(); return createSoFindIterable<{}>({ soClient, findRequest: { - type: SAVED_OBJECT_TYPE, + type: savedObjectType, perPage, sortField: 'created_at', sortOrder: 'asc', fields: ['id'], - filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + filter: kuery ? normalizeKuery(savedObjectType, kuery) : undefined, }, resultsMapper: (data) => { return data.saved_objects.map((agentPolicySO) => { auditLoggingService.writeCustomSoAuditLog({ action: 'find', id: agentPolicySO.id, - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); return agentPolicySO.id; }); @@ -1599,7 +1643,7 @@ class AgentPolicyService { }); } - public fetchAllAgentPolicies( + public async fetchAllAgentPolicies( soClient: SavedObjectsClientContract, { perPage = 1000, @@ -1608,23 +1652,24 @@ class AgentPolicyService { sortField = 'created_at', fields = [], }: FetchAllAgentPoliciesOptions = {} - ): AsyncIterable { + ): Promise> { + const savedObjectType = await getAgentPolicySavedObjectType(); return createSoFindIterable({ soClient, findRequest: { - type: SAVED_OBJECT_TYPE, + type: savedObjectType, sortField, sortOrder, perPage, fields, - filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + filter: kuery ? normalizeKuery(savedObjectType, kuery) : undefined, }, resultsMapper(data) { return data.saved_objects.map((agentPolicySO) => { auditLoggingService.writeCustomSoAuditLog({ action: 'find', id: agentPolicySO.id, - savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); return mapAgentPolicySavedObjectToAgentPolicy(agentPolicySO); }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy_watch.test.ts b/x-pack/plugins/fleet/server/services/agent_policy_watch.test.ts index 0c848703fa7a3..e2aab90b2b34a 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy_watch.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy_watch.test.ts @@ -21,7 +21,7 @@ import type { SavedObjectError } from '@kbn/core-saved-objects-common'; import type { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; import type { AgentPolicy } from '../../common'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../common'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../common'; import { LicenseService } from '../../common/services'; @@ -52,7 +52,7 @@ describe('Agent Policy-Changing license watcher', () => { const createPolicySO = (id: string, isProtected: boolean, error?: SavedObjectError) => ({ id, - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, attributes: { is_protected: isProtected, }, @@ -101,12 +101,14 @@ describe('Agent Policy-Changing license watcher', () => { it('should bulk update policies that are not compliant', async () => { const getMockAgentPolicyFetchAllAgentPolicies = (items: AgentPolicy[]) => - jest.fn(async function* (soClient: SavedObjectsClientContract) { - const chunkSize = 1000; // Emulate paginated response - for (let i = 0; i < items.length; i += chunkSize) { - yield items.slice(i, i + chunkSize); - } - }); + jest.fn().mockResolvedValue( + jest.fn(async function* () { + const chunkSize = 1000; // Emulate paginated response + for (let i = 0; i < items.length; i += chunkSize) { + yield items.slice(i, i + chunkSize); + } + })() + ); const policiesToUpdate = Array.from({ length: 2001 }, (_, i) => createAgentPolicyMock({ id: `policy${i}`, is_protected: true }) @@ -159,9 +161,11 @@ describe('Agent Policy-Changing license watcher', () => { it('should return failed policies if bulk update fails', async () => { const getMockAgentPolicyFetchAllAgentPolicies = (items: AgentPolicy[]) => - jest.fn(async function* (soClient: SavedObjectsClientContract) { - yield items; - }); + jest.fn().mockResolvedValue( + jest.fn(async function* () { + yield items; + })() + ); agentPolicySvcMock.fetchAllAgentPolicies = getMockAgentPolicyFetchAllAgentPolicies([ createAgentPolicyMock({ is_protected: true }), diff --git a/x-pack/plugins/fleet/server/services/agent_policy_watch.ts b/x-pack/plugins/fleet/server/services/agent_policy_watch.ts index 37df623f4f8bb..bb8304bd41fa9 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy_watch.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy_watch.ts @@ -23,13 +23,12 @@ import type { AgentPolicySOAttributes } from '../types'; import type { LicenseService } from '../../common/services/license'; import type { AgentPolicy } from '../../common'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../common'; import { isAgentPolicyValidForLicense, unsetAgentPolicyAccordingToLicenseLevel, } from '../../common/services/agent_policy_config'; -import { agentPolicyService } from './agent_policy'; +import { agentPolicyService, getAgentPolicySavedObjectType } from './agent_policy'; export class PolicyWatcher { private logger: Logger; @@ -72,7 +71,7 @@ export class PolicyWatcher { public async watch(license: ILicense) { const log = this.logger.get('endpoint', 'agentPolicyLicenseWatch'); - const agentPolicyFetcher = agentPolicyService.fetchAllAgentPolicies( + const agentPolicyFetcher = await agentPolicyService.fetchAllAgentPolicies( this.makeInternalSOClient(this.soStart), { fields: ['is_protected', 'id', 'revision'] } // Don't forget to extend this to include all fields that are used in the `isAgentPolicyValidForLicense` function ); @@ -92,6 +91,7 @@ export class PolicyWatcher { if (policiesToUpdate.length === 0) { break; } + const savedObjectType = await getAgentPolicySavedObjectType(); const { saved_objects: bulkUpdateSavedObjects } = await this.makeInternalSOClient( this.soStart @@ -99,7 +99,7 @@ export class PolicyWatcher { policiesToUpdate.map((policy) => { const { id, revision, ...policyContent } = policy; return { - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: savedObjectType, id, attributes: { ...policyContent, diff --git a/x-pack/plugins/fleet/server/services/agents/action_status.ts b/x-pack/plugins/fleet/server/services/agents/action_status.ts index 5c93c5eaa3c2f..d078620c30321 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_status.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_status.ts @@ -239,7 +239,7 @@ async function getActions( ignore_unavailable: true, from: 0, size: getPerPage(options), - query: addNamespaceFilteringToQuery(query, namespace), + query: await addNamespaceFilteringToQuery(query, namespace), body: { sort: [{ '@timestamp': 'desc' }], }, @@ -390,7 +390,7 @@ async function getPolicyChangeActions( index: AGENT_POLICY_INDEX, ignore_unavailable: true, size: getPerPage(options), - query: addNamespaceFilteringToQuery(query, namespace), + query: await addNamespaceFilteringToQuery(query, namespace), sort: [ { '@timestamp': { diff --git a/x-pack/plugins/fleet/server/services/agents/crud.test.ts b/x-pack/plugins/fleet/server/services/agents/crud.test.ts index d51b0d52d4d7c..ac5f1a282d8da 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.test.ts @@ -38,6 +38,7 @@ jest.mock('./versions', () => { getLatestAvailableAgentVersion: jest.fn().mockResolvedValue('8.8.0'), }; }); +jest.mock('../spaces/helpers'); const mockedAuditLoggingService = auditLoggingService as jest.Mocked; diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 64c20cbbc4d6b..7fdf76c76992b 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -29,6 +29,7 @@ import { import { auditLoggingService } from '../audit_logging'; import { isAgentInNamespace } from '../spaces/agent_namespaces'; import { getCurrentNamespace } from '../spaces/get_current_namespace'; +import { isSpaceAwarenessEnabled } from '../spaces/helpers'; import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers'; import { buildAgentStatusRuntimeField } from './build_status_runtime_field'; @@ -228,7 +229,7 @@ export async function getAgentsByKuery( } = options; const filters = []; - const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + const useSpaceAwareness = await isSpaceAwarenessEnabled(); if (useSpaceAwareness && spaceId) { if (spaceId === DEFAULT_SPACE_ID) { filters.push(`namespaces:"${DEFAULT_SPACE_ID}" or not namespaces:*`); @@ -406,7 +407,7 @@ export async function getAgentById( throw new AgentNotFoundError(`Agent ${agentId} not found`); } - if (!isAgentInNamespace(agentHit, getCurrentNamespace(soClient))) { + if ((await isAgentInNamespace(agentHit, getCurrentNamespace(soClient))) !== true) { throw new AgentNotFoundError(`${agentHit.id} not found in namespace`); } diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index 1940a816dd2d7..99d2d25b139c8 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -16,13 +16,11 @@ import type { } from '@elastic/elasticsearch/lib/api/types'; import { agentStatusesToSummary } from '../../../common/services'; - import { AGENTS_INDEX } from '../../constants'; import type { AgentStatus } from '../../types'; import { FleetError, FleetUnauthorizedError } from '../../errors'; - import { appContextService } from '../app_context'; - +import { isSpaceAwarenessEnabled } from '../spaces/helpers'; import { retryTransientEsErrors } from '../epm/elasticsearch/retry'; import { getAgentById, removeSOAttributes } from './crud'; @@ -54,7 +52,7 @@ export async function getAgentStatusForAgentPolicy( const clauses: QueryDslQueryContainer[] = []; - const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + const useSpaceAwareness = await isSpaceAwarenessEnabled(); if (useSpaceAwareness && spaceId) { if (spaceId === DEFAULT_SPACE_ID) { clauses.push( diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts index 35163288e97dc..efeb5649cd576 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts @@ -8,8 +8,7 @@ import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { ElasticsearchClientMock } from '@kbn/core/server/mocks'; import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { appContextService } from '../app_context'; - +import { isSpaceAwarenessEnabled } from '../spaces/helpers'; import type { Agent } from '../../types'; import { createClientMock } from './action.mock'; @@ -17,6 +16,7 @@ import { MAX_RETRY_COUNT } from './retry_helper'; import { updateAgentTags } from './update_agent_tags'; import { UpdateAgentTagsActionRunner, updateTagsBatch } from './update_agent_tags_action_runner'; +jest.mock('../spaces/helpers'); jest.mock('../app_context', () => { const { loggerMock } = jest.requireActual('@kbn/logging-mocks'); return { @@ -418,11 +418,9 @@ describe('update_agent_tags', () => { ); }); - describe('with the useSpaceAwareness feature flag enabled', () => { + describe('with isSpaceAwarenessEnabled return true', () => { beforeEach(() => { - jest.mocked(appContextService.getExperimentalFeatures).mockReturnValue({ - useSpaceAwareness: true, - } as any); + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(true); }); it('should not update tags for agents in another space', async () => { diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts index f3443458249b7..7d37581cef997 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts @@ -38,7 +38,7 @@ export async function updateAgentTags( outgoingErrors[maybeAgent.id] = new AgentReassignmentError( `Cannot find agent ${maybeAgent.id}` ); - } else if (!isAgentInNamespace(maybeAgent, currentNameSpace)) { + } else if ((await isAgentInNamespace(maybeAgent, currentNameSpace)) !== true) { outgoingErrors[maybeAgent.id] = new AgentReassignmentError( `Agent ${maybeAgent.id} is not in the current space` ); @@ -49,7 +49,7 @@ export async function updateAgentTags( } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; - const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); + const namespaceFilter = await agentsKueryNamespaceFilter(currentNameSpace); const filters = namespaceFilter ? [namespaceFilter] : []; if (options.kuery !== '') { filters.push(options.kuery); diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.test.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.test.ts index 6d37dd44a7f5e..7ad62121950d3 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.test.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.test.ts @@ -26,6 +26,7 @@ import { jest.mock('../audit_logging'); jest.mock('../agent_policy'); jest.mock('../app_context'); +jest.mock('../spaces/helpers'); jest.mock('uuid', () => { return { diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts index e89917143732f..d6b94875b6ed1 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts @@ -23,6 +23,7 @@ import { escapeSearchQueryPhrase } from '../saved_object'; import { auditLoggingService } from '../audit_logging'; import { _joinFilters } from '../agents'; import { appContextService } from '../app_context'; +import { isSpaceAwarenessEnabled } from '../spaces/helpers'; import { invalidateAPIKeys } from './security'; @@ -54,7 +55,7 @@ export async function listEnrollmentApiKeys( filters.push(kuery); } - const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + const useSpaceAwareness = await isSpaceAwarenessEnabled(); if (useSpaceAwareness && spaceId) { if (spaceId === DEFAULT_SPACE_ID) { // TODO use constant diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 970291bf7d552..f2965025d45d1 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -35,7 +35,10 @@ import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/ import { SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { FleetConfigType } from '../../common/types'; -import type { ExperimentalFeatures } from '../../common/experimental_features'; +import { + allowedExperimentalValues, + type ExperimentalFeatures, +} from '../../common/experimental_features'; import type { ExternalCallback, ExternalCallbacksStorage, @@ -61,7 +64,7 @@ class AppContextService { private encryptedSavedObjectsStart: EncryptedSavedObjectsPluginStart | undefined; private data: DataPluginStart | undefined; private esClient: ElasticsearchClient | undefined; - private experimentalFeatures?: ExperimentalFeatures; + private experimentalFeatures: ExperimentalFeatures = allowedExperimentalValues; private securityCoreStart: SecurityServiceStart | undefined; private securitySetup: SecurityPluginSetup | undefined; private securityStart: SecurityPluginStart | undefined; @@ -168,9 +171,6 @@ class AppContextService { } public getExperimentalFeatures() { - if (!this.experimentalFeatures) { - throw new Error('experimentalFeatures not set.'); - } return this.experimentalFeatures; } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index 97b0eeb823e02..c8c7a8d9e3d03 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -20,7 +20,7 @@ import { getNormalizedDataStreams } from '../../../../common/services'; import { MAX_TIME_COMPLETE_INSTALL, ASSETS_SAVED_OBJECT_TYPE, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT, } from '../../../../common/constants'; import { PACKAGES_SAVED_OBJECT_TYPE, FLEET_INSTALL_FORMAT_VERSION } from '../../../constants'; @@ -361,7 +361,7 @@ export async function _installPackage({ const policyIdsToUpgrade = await packagePolicyService.listIds(savedObjectsClient, { page: 1, perPage: SO_SEARCH_LIMIT, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${pkgName}`, + kuery: `${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${pkgName}`, }); logger.debug( `Package install - Package is flagged with keep_policies_up_to_date, upgrading its associated package policies ${policyIdsToUpgrade}` diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index ce0bbeb4f6d2c..b3f0d28438d54 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -22,7 +22,6 @@ import { buildNode as buildWildcardNode } from '@kbn/es-query/src/kuery/node_typ import { ASSETS_SAVED_OBJECT_TYPE, installationStatuses, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT, } from '../../../../common/constants'; import { isPackageLimited } from '../../../../common/services'; @@ -55,7 +54,7 @@ import * as Registry from '../registry'; import type { PackageAsset } from '../archive/storage'; import { getEsPackage } from '../archive/storage'; import { normalizeKuery } from '../../saved_object'; - +import { getPackagePolicySavedObjectType } from '../../package_policy'; import { auditLoggingService } from '../../audit_logging'; import { getFilteredSearchPackages } from '../filtered_packages'; @@ -479,9 +478,11 @@ export const getPackageUsageStats = async ({ savedObjectsClient: SavedObjectsClientContract; pkgName: string; }): Promise => { + const packagePolicySavedObjectType = await getPackagePolicySavedObjectType(); + const filter = normalizeKuery( - PACKAGE_POLICY_SAVED_OBJECT_TYPE, - `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${pkgName}` + packagePolicySavedObjectType, + `${packagePolicySavedObjectType}.package.name: ${pkgName}` ); const agentPolicyCount = new Set(); let page = 1; @@ -491,7 +492,7 @@ export const getPackageUsageStats = async ({ // using saved Objects client directly, instead of the `list()` method of `package_policy` service // in order to not cause a circular dependency (package policy service imports from this module) const packagePolicies = await savedObjectsClient.find({ - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: packagePolicySavedObjectType, perPage: 1000, page: page++, filter, @@ -501,7 +502,7 @@ export const getPackageUsageStats = async ({ auditLoggingService.writeCustomSoAuditLog({ action: 'find', id: packagePolicy.id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: packagePolicySavedObjectType, }); } diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.ts index 7a5c4a48695d6..8248797e7963a 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/index.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.ts @@ -12,7 +12,10 @@ import semverCoerce from 'semver/functions/coerce'; import { uniqBy } from 'lodash'; import type { AgentPolicy } from '../../../common/types'; -import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, FLEET_SERVER_PACKAGE } from '../../../common/constants'; +import { + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, + FLEET_SERVER_PACKAGE, +} from '../../../common/constants'; import { SO_SEARCH_LIMIT } from '../../constants'; import { getAgentsByKuery, getAgentStatusById } from '../agents'; import { packagePolicyService } from '../package_policy'; @@ -27,7 +30,7 @@ export const getFleetServerPolicies = async ( soClient: SavedObjectsClientContract ): Promise => { const fleetServerPackagePolicies = await packagePolicyService.list(soClient, { - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${FLEET_SERVER_PACKAGE}`, + kuery: `${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${FLEET_SERVER_PACKAGE}`, spaceId: '*', }); diff --git a/x-pack/plugins/fleet/server/services/output.ts b/x-pack/plugins/fleet/server/services/output.ts index 2748ad78e765b..57a641ed44d6f 100644 --- a/x-pack/plugins/fleet/server/services/output.ts +++ b/x-pack/plugins/fleet/server/services/output.ts @@ -37,7 +37,7 @@ import type { PolicySecretReference, } from '../types'; import { - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, DEFAULT_OUTPUT, DEFAULT_OUTPUT_ID, @@ -133,13 +133,13 @@ async function getAgentPoliciesPerOutput(outputId?: string, isDefault?: boolean) const packagePoliciesKuery: string = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.output_id:"${outputId}"`; if (outputId) { if (isDefault) { - agentPoliciesKuery = `${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:"${outputId}" or not ${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:*`; + agentPoliciesKuery = `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:"${outputId}" or not ${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:*`; } else { - agentPoliciesKuery = `${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:"${outputId}"`; + agentPoliciesKuery = `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:"${outputId}"`; } } else { if (isDefault) { - agentPoliciesKuery = `not ${AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:*`; + agentPoliciesKuery = `not ${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.data_output_id:*`; } else { return; } diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index 9505edd3556cb..08a2ef507fe20 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -19,6 +19,10 @@ import type { } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, + PACKAGE_POLICY_SAVED_OBJECT_TYPE, +} from '../../common/constants'; import { PackagePolicyMocks } from '../mocks/package_policy.mocks'; import type { @@ -53,8 +57,6 @@ import { PackagePolicyValidationError, } from '../errors'; -import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; - import { mapPackagePolicySavedObjectToPackagePolicy } from './package_policies'; import { @@ -71,6 +73,9 @@ import { getPackageInfo } from './epm/packages'; import { sendTelemetryEvents } from './upgrade_sender'; import { auditLoggingService } from './audit_logging'; import { agentPolicyService } from './agent_policy'; +import { isSpaceAwarenessEnabled } from './spaces/helpers'; + +jest.mock('./spaces/helpers'); const mockedSendTelemetryEvents = sendTelemetryEvents as jest.MockedFunction< typeof sendTelemetryEvents @@ -221,6 +226,7 @@ const mockAgentPolicyGet = () => { describe('Package policy service', () => { beforeEach(() => { appContextService.start(createAppContextStartContractMock()); + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(false); }); afterEach(() => { @@ -240,7 +246,7 @@ describe('Package policy service', () => { id: 'test-package-policy', attributes: {}, references: [], - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); mockAgentPolicyGet(); @@ -268,7 +274,7 @@ describe('Package policy service', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toBeCalledWith({ action: 'create', id: 'test-package-policy', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -281,7 +287,7 @@ describe('Package policy service', () => { id: 'test-package-policy', attributes: {}, references: [], - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); mockAgentPolicyGet(); @@ -334,13 +340,13 @@ describe('Package policy service', () => { id: 'test-package-policy-1', attributes: {}, references: [], - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }, { id: 'test-package-policy-2', attributes: {}, references: [], - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }, ], }); @@ -371,13 +377,13 @@ describe('Package policy service', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(1, { action: 'create', id: 'test-package-policy-1', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(2, { action: 'create', id: 'test-package-policy-2', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -389,7 +395,7 @@ describe('Package policy service', () => { id: 'test-package-policy', attributes: {}, references: [], - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); await packagePolicyService.get(soClient, 'test-package-policy'); @@ -397,7 +403,7 @@ describe('Package policy service', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toBeCalledWith({ action: 'get', id: 'test-package-policy', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -411,13 +417,13 @@ describe('Package policy service', () => { id: 'test-package-policy-1', attributes: {}, references: [], - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }, { id: 'test-package-policy-2', attributes: {}, references: [], - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }, ], }); @@ -430,13 +436,13 @@ describe('Package policy service', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(1, { action: 'get', id: 'test-package-policy-1', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(2, { action: 'get', id: 'test-package-policy-2', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -453,14 +459,14 @@ describe('Package policy service', () => { id: 'test-package-policy-1', attributes: {}, references: [], - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, score: 0, }, { id: 'test-package-policy-2', attributes: {}, references: [], - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, score: 0, }, ], @@ -475,13 +481,13 @@ describe('Package policy service', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(1, { action: 'find', id: 'test-package-policy-1', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenNthCalledWith(2, { action: 'find', id: 'test-package-policy-2', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -1686,14 +1692,14 @@ describe('Package policy service', () => { soClient.get.mockResolvedValue({ id: 'test-package-policy', - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, references: [], attributes, }); soClient.update.mockResolvedValue({ id: 'test-package-policy', - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, references: [], attributes, }); @@ -1706,7 +1712,7 @@ describe('Package policy service', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenCalledWith({ action: 'update', id: 'test-package-policy', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -2474,13 +2480,13 @@ describe('Package policy service', () => { const mockPackagePolicies = [ { id: 'test-package-policy-1', - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, attributes: {}, references: [], }, { id: 'test-package-policy-2', - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, attributes: {}, references: [], }, @@ -2527,7 +2533,7 @@ describe('Package policy service', () => { const mockPackagePolicy = { id: 'test-package-policy', - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, attributes: {}, references: [], }; @@ -2545,7 +2551,7 @@ describe('Package policy service', () => { expect(mockedAuditLoggingService.writeCustomSoAuditLog).toHaveBeenCalledWith({ action: 'delete', id: 'test-package-policy', - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, }); }); }); @@ -4972,13 +4978,13 @@ describe('Package policy service', () => { }); it('should return an iterator', async () => { - expect(packagePolicyService.fetchAllItemIds(soClientMock)).toEqual({ + expect(await packagePolicyService.fetchAllItemIds(soClientMock)).toEqual({ [Symbol.asyncIterator]: expect.any(Function), }); }); it('should provide item ids on every iteration', async () => { - for await (const ids of packagePolicyService.fetchAllItemIds(soClientMock)) { + for await (const ids of await packagePolicyService.fetchAllItemIds(soClientMock)) { expect(ids).toEqual(['so-123', 'so-123']); } @@ -4986,13 +4992,13 @@ describe('Package policy service', () => { }); it('should use default options', async () => { - for await (const ids of packagePolicyService.fetchAllItemIds(soClientMock)) { + for await (const ids of await packagePolicyService.fetchAllItemIds(soClientMock)) { expect(ids); } expect(soClientMock.find).toHaveBeenCalledWith( expect.objectContaining({ - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, perPage: 1000, sortField: 'created_at', sortOrder: 'asc', @@ -5003,7 +5009,7 @@ describe('Package policy service', () => { }); it('should use custom options when defined', async () => { - for await (const ids of packagePolicyService.fetchAllItemIds(soClientMock, { + for await (const ids of await packagePolicyService.fetchAllItemIds(soClientMock, { perPage: 13, kuery: 'one=two', })) { @@ -5012,7 +5018,7 @@ describe('Package policy service', () => { expect(soClientMock.find).toHaveBeenCalledWith( expect.objectContaining({ - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, perPage: 13, sortField: 'created_at', sortOrder: 'asc', @@ -5040,13 +5046,13 @@ describe('Package policy service', () => { }); it('should return an iterator', async () => { - expect(packagePolicyService.fetchAllItems(soClientMock)).toEqual({ + expect(await packagePolicyService.fetchAllItems(soClientMock)).toEqual({ [Symbol.asyncIterator]: expect.any(Function), }); }); it('should provide items on every iteration', async () => { - for await (const items of packagePolicyService.fetchAllItems(soClientMock)) { + for await (const items of await packagePolicyService.fetchAllItems(soClientMock)) { expect(items).toEqual( PackagePolicyMocks.generatePackagePolicySavedObjectFindResponse().saved_objects.map( (soItem) => { @@ -5060,7 +5066,25 @@ describe('Package policy service', () => { }); it('should use default options', async () => { - for await (const ids of packagePolicyService.fetchAllItemIds(soClientMock)) { + for await (const ids of await packagePolicyService.fetchAllItemIds(soClientMock)) { + expect(ids); + } + + expect(soClientMock.find).toHaveBeenCalledWith( + expect.objectContaining({ + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, + perPage: 1000, + sortField: 'created_at', + sortOrder: 'asc', + fields: [], + filter: undefined, + }) + ); + }); + + it('should use space aware saved object type if user opt-in for space awareness', async () => { + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(true); + for await (const ids of await packagePolicyService.fetchAllItemIds(soClientMock)) { expect(ids); } @@ -5077,7 +5101,7 @@ describe('Package policy service', () => { }); it('should use custom options when defined', async () => { - for await (const ids of packagePolicyService.fetchAllItems(soClientMock, { + for await (const ids of await packagePolicyService.fetchAllItems(soClientMock, { kuery: 'one=two', perPage: 12, sortOrder: 'desc', @@ -5088,7 +5112,7 @@ describe('Package policy service', () => { expect(soClientMock.find).toHaveBeenCalledWith( expect.objectContaining({ - type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, perPage: 12, sortField: 'updated_by', sortOrder: 'desc', diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index cafb7e85d9d32..b189d5dbcf278 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -48,6 +48,8 @@ import { SO_SEARCH_LIMIT, PACKAGES_SAVED_OBJECT_TYPE, DATASET_VAR_NAME, + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, + PACKAGE_POLICY_SAVED_OBJECT_TYPE, } from '../../common/constants'; import type { PostDeletePackagePoliciesResponse, @@ -69,7 +71,6 @@ import type { AssetsMap, AgentPolicy, } from '../../common/types'; -import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; import { FleetError, fleetErrorToResponseOptions, @@ -107,7 +108,7 @@ import { agentPolicyService } from './agent_policy'; import { getPackageInfo, getInstallation, ensureInstalledPackage } from './epm/packages'; import { getAssetsDataFromAssetsMap } from './epm/packages/assets'; import { compileTemplate } from './epm/agent/agent'; -import { escapeSearchQueryPhrase, normalizeKuery } from './saved_object'; +import { escapeSearchQueryPhrase, normalizeKuery as _normalizeKuery } from './saved_object'; import { appContextService } from '.'; import { removeOldAssets } from './epm/packages/cleanup'; import type { PackageUpdateEvent, UpdateEventType } from './upgrade_sender'; @@ -135,13 +136,12 @@ import { getPackageAssetsMap } from './epm/packages/get'; import { validateAgentPolicyOutputForIntegration } from './agent_policies/outputs_helpers'; import type { PackagePolicyClientFetchAllItemIdsOptions } from './package_policy_service'; import { validatePolicyNamespaceForSpace } from './spaces/policy_namespaces'; +import { isSpaceAwarenessEnabled, isSpaceAwarenessMigrationPending } from './spaces/helpers'; export type InputsOverride = Partial & { vars?: Array; }; -const SAVED_OBJECT_TYPE = PACKAGE_POLICY_SAVED_OBJECT_TYPE; - async function getPkgInfoAssetsMap({ savedObjectsClient, packageInfos, @@ -174,6 +174,32 @@ async function getPkgInfoAssetsMap({ return packageInfosandAssetsMap; } +export async function getPackagePolicySavedObjectType() { + return (await isSpaceAwarenessEnabled()) + ? PACKAGE_POLICY_SAVED_OBJECT_TYPE + : LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE; +} + +function normalizeKuery(savedObjectType: string, kuery: string) { + if (savedObjectType === LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE) { + return _normalizeKuery( + savedObjectType, + kuery.replace( + new RegExp(`${PACKAGE_POLICY_SAVED_OBJECT_TYPE}\\.`, 'g'), + `${savedObjectType}.attributes.` + ) + ); + } else { + return _normalizeKuery( + savedObjectType, + kuery.replace( + new RegExp(`${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}\\.`, 'g'), + `${savedObjectType}.attributes.` + ) + ); + } +} + class PackagePolicyClientImpl implements PackagePolicyClient { public async create( soClient: SavedObjectsClientContract, @@ -202,10 +228,12 @@ class PackagePolicyClientImpl implements PackagePolicyClient { authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request); } + const savedObjectType = await getPackagePolicySavedObjectType(); + auditLoggingService.writeCustomSoAuditLog({ action: 'create', id: packagePolicyId, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); const logger = appContextService.getLogger(); @@ -348,7 +376,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { const isoDate = new Date().toISOString(); const newSo = await soClient.create( - SAVED_OBJECT_TYPE, + savedObjectType, { ...enrichedPackagePolicy, ...(enrichedPackagePolicy.package @@ -407,6 +435,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { created: PackagePolicy[]; failed: Array<{ packagePolicy: NewPackagePolicy; error?: Error | SavedObjectError }>; }> { + const savedObjectType = await getPackagePolicySavedObjectType(); for (const packagePolicy of packagePolicies) { if (!packagePolicy.id) { packagePolicy.id = SavedObjectsUtils.generateId(); @@ -414,7 +443,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { auditLoggingService.writeCustomSoAuditLog({ action: 'create', id: packagePolicy.id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); this.keepPolicyIdInSync(packagePolicy); @@ -497,7 +526,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } policiesToCreate.push({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, id: packagePolicyId, attributes: { ...pkgPolicyWithoutId, @@ -615,7 +644,8 @@ class PackagePolicyClientImpl implements PackagePolicyClient { soClient: SavedObjectsClientContract, id: string ): Promise { - const packagePolicySO = await soClient.get(SAVED_OBJECT_TYPE, id); + const savedObjectType = await getPackagePolicySavedObjectType(); + const packagePolicySO = await soClient.get(savedObjectType, id); if (!packagePolicySO) { return null; } @@ -651,7 +681,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { auditLoggingService.writeCustomSoAuditLog({ action: 'get', id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); return response; @@ -661,11 +691,10 @@ class PackagePolicyClientImpl implements PackagePolicyClient { soClient: SavedObjectsClientContract, agentPolicyId: string ): Promise { + const savedObjectType = await getPackagePolicySavedObjectType(); const packagePolicySO = await soClient.find({ - type: SAVED_OBJECT_TYPE, - filter: `${SAVED_OBJECT_TYPE}.attributes.policy_ids:${escapeSearchQueryPhrase( - agentPolicyId - )}`, + type: savedObjectType, + filter: `${savedObjectType}.attributes.policy_ids:${escapeSearchQueryPhrase(agentPolicyId)}`, perPage: SO_SEARCH_LIMIT, }); if (!packagePolicySO) { @@ -682,7 +711,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { auditLoggingService.writeCustomSoAuditLog({ action: 'find', id: packagePolicy.id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); } @@ -694,10 +723,11 @@ class PackagePolicyClientImpl implements PackagePolicyClient { ids: string[], options: { ignoreMissing?: boolean } = {} ): Promise { + const savedObjectType = await getPackagePolicySavedObjectType(); const packagePolicySO = await soClient.bulkGet( ids.map((id) => ({ id, - type: SAVED_OBJECT_TYPE, + type: savedObjectType, })) ); if (!packagePolicySO) { @@ -728,7 +758,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { auditLoggingService.writeCustomSoAuditLog({ action: 'get', id: packagePolicy.id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); } @@ -739,6 +769,8 @@ class PackagePolicyClientImpl implements PackagePolicyClient { soClient: SavedObjectsClientContract, options: ListWithKuery & { spaceId?: string } ): Promise> { + const savedObjectType = await getPackagePolicySavedObjectType(); + const { page = 1, perPage = 20, @@ -749,13 +781,13 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } = options; const packagePolicies = await soClient.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, sortField, sortOrder, page, perPage, fields, - filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + filter: kuery ? normalizeKuery(savedObjectType, kuery) : undefined, namespaces: options.spaceId ? [options.spaceId] : undefined, }); @@ -763,7 +795,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { auditLoggingService.writeCustomSoAuditLog({ action: 'find', id: packagePolicy.id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); } @@ -785,22 +817,22 @@ class PackagePolicyClientImpl implements PackagePolicyClient { options: ListWithKuery ): Promise> { const { page = 1, perPage = 20, sortField = 'updated_at', sortOrder = 'desc', kuery } = options; - + const savedObjectType = await getPackagePolicySavedObjectType(); const packagePolicies = await soClient.find<{}>({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, sortField, sortOrder, page, perPage, fields: [], - filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + filter: kuery ? normalizeKuery(savedObjectType, kuery) : undefined, }); for (const packagePolicy of packagePolicies.saved_objects) { auditLoggingService.writeCustomSoAuditLog({ action: 'find', id: packagePolicy.id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); } @@ -819,10 +851,11 @@ class PackagePolicyClientImpl implements PackagePolicyClient { packagePolicyUpdate: UpdatePackagePolicy, options?: { user?: AuthenticatedUser; force?: boolean; skipUniqueNameVerification?: boolean } ): Promise { + const savedObjectType = await getPackagePolicySavedObjectType(); auditLoggingService.writeCustomSoAuditLog({ action: 'update', id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); const logger = appContextService.getLogger(); @@ -939,7 +972,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { logger.debug(`Updating SO with revision ${oldPackagePolicy.revision + 1}`); await soClient.update( - SAVED_OBJECT_TYPE, + savedObjectType, id, { ...restOfPackagePolicy, @@ -1027,11 +1060,12 @@ class PackagePolicyClientImpl implements PackagePolicyClient { error: Error | SavedObjectError; }>; }> { + const savedObjectType = await getPackagePolicySavedObjectType(); for (const packagePolicy of packagePolicyUpdates) { auditLoggingService.writeCustomSoAuditLog({ action: 'update', id: packagePolicy.id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); } const oldPackagePolicies = await this.getByIDs( @@ -1127,7 +1161,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy }); policiesToUpdate.push({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, id, attributes: { ...restOfPackagePolicy, @@ -1230,11 +1264,12 @@ class PackagePolicyClientImpl implements PackagePolicyClient { context?: RequestHandlerContext, request?: KibanaRequest ): Promise { + const savedObjectType = await getPackagePolicySavedObjectType(); for (const id of ids) { auditLoggingService.writeCustomSoAuditLog({ action: 'delete', id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); } @@ -1291,9 +1326,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { const packagePolicy = packagePolicies.find((p) => p.id === id); if (!packagePolicy) { - throw new PackagePolicyNotFoundError( - `Saved object [ingest-package-policies/${id}] not found` - ); + throw new PackagePolicyNotFoundError(`Saved object [${savedObjectType}/${id}] not found`); } if (packagePolicy.is_managed && !options?.force) { @@ -1319,7 +1352,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { const secretsToDelete: string[] = []; if (idsToDelete.length > 0) { const { statuses } = await soClient.bulkDelete( - idsToDelete.map((id) => ({ id, type: SAVED_OBJECT_TYPE })) + idsToDelete.map((id) => ({ id, type: savedObjectType })) ); statuses.forEach(({ id, success, error }) => { @@ -2002,9 +2035,10 @@ class PackagePolicyClientImpl implements PackagePolicyClient { esClient: ElasticsearchClient, outputId: string ) { + const savedObjectType = await getPackagePolicySavedObjectType(); const packagePolicies = ( await soClient.find({ - type: SAVED_OBJECT_TYPE, + type: savedObjectType, fields: ['name', 'enabled', 'policy_ids', 'inputs', 'output_id'], searchFields: ['output_id'], search: escapeSearchQueryPhrase(outputId), @@ -2067,21 +2101,22 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } } - fetchAllItemIds( + async fetchAllItemIds( soClient: SavedObjectsClientContract, { perPage = 1000, kuery }: PackagePolicyClientFetchAllItemIdsOptions = {} - ): AsyncIterable { + ): Promise> { // TODO:PT Question for fleet team: do I need to `auditLoggingService.writeCustomSoAuditLog()` here? Its only IDs + const savedObjectType = await getPackagePolicySavedObjectType(); return createSoFindIterable<{}>({ soClient, findRequest: { - type: SAVED_OBJECT_TYPE, + type: savedObjectType, perPage, sortField: 'created_at', sortOrder: 'asc', fields: [], - filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + filter: kuery ? normalizeKuery(savedObjectType, kuery) : undefined, }, resultsMapper: (data) => { return data.saved_objects.map((packagePolicySO) => packagePolicySO.id); @@ -2089,7 +2124,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { }); } - fetchAllItems( + async fetchAllItems( soClient: SavedObjectsClientContract, { perPage = 1000, @@ -2097,22 +2132,24 @@ class PackagePolicyClientImpl implements PackagePolicyClient { sortOrder = 'asc', sortField = 'created_at', }: PackagePolicyClientFetchAllItemsOptions = {} - ): AsyncIterable { + ): Promise> { + const savedObjectType = await getPackagePolicySavedObjectType(); + return createSoFindIterable({ soClient, findRequest: { - type: SAVED_OBJECT_TYPE, + type: savedObjectType, sortField, sortOrder, perPage, - filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + filter: kuery ? normalizeKuery(savedObjectType, kuery) : undefined, }, resultsMapper(data) { return data.saved_objects.map((packagePolicySO) => { auditLoggingService.writeCustomSoAuditLog({ action: 'find', id: packagePolicySO.id, - savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + savedObjectType, }); return mapPackagePolicySavedObjectToPackagePolicy(packagePolicySO); @@ -2133,13 +2170,22 @@ export class PackagePolicyServiceImpl if (doesNotHaveRequiredFleetAuthz(authz, fleetRequiredAuthz)) { throw new FleetUnauthorizedError('Not authorized to this action on integration policies'); } + + if ((await isSpaceAwarenessMigrationPending()) === true) { + throw new FleetError('Migration to space awareness is pending'); + } }; return new PackagePolicyClientWithAuthz(preflightCheck); } public get asInternalUser() { - return new PackagePolicyClientWithAuthz(); + const preflightCheck = async () => { + if ((await isSpaceAwarenessMigrationPending()) === true) { + throw new FleetError('Migration to space awareness is pending'); + } + }; + return new PackagePolicyClientWithAuthz(preflightCheck); } } @@ -2158,6 +2204,51 @@ class PackagePolicyClientWithAuthz extends PackagePolicyClientImpl { } }; + async bulkCreate( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + packagePolicies: NewPackagePolicyWithId[], + options?: + | { + user?: AuthenticatedUser | undefined; + bumpRevision?: boolean | undefined; + force?: true | undefined; + } + | undefined + ): Promise<{ + created: PackagePolicy[]; + failed: Array<{ packagePolicy: NewPackagePolicy; error?: Error | SavedObjectError }>; + }> { + await this.#runPreflight({ + fleetAuthz: { + integrations: { writeIntegrationPolicies: true }, + }, + }); + return super.bulkCreate(soClient, esClient, packagePolicies, options); + } + + async update( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + id: string, + packagePolicyUpdate: UpdatePackagePolicy, + options?: + | { + user?: AuthenticatedUser | undefined; + force?: boolean | undefined; + skipUniqueNameVerification?: boolean | undefined; + } + | undefined + ): Promise { + await this.#runPreflight({ + fleetAuthz: { + integrations: { writeIntegrationPolicies: true }, + }, + }); + + return super.update(soClient, esClient, id, packagePolicyUpdate, options); + } + async create( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, @@ -2939,9 +3030,10 @@ async function requireUniqueName( packagePolicy: UpdatePackagePolicy | NewPackagePolicy, id?: string ) { + const savedObjectType = await getPackagePolicySavedObjectType(); const existingPoliciesWithName = await packagePolicyService.list(soClient, { perPage: SO_SEARCH_LIMIT, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name:"${packagePolicy.name}"`, + kuery: `${savedObjectType}.name:"${packagePolicy.name}"`, }); const policiesToCheck = id diff --git a/x-pack/plugins/fleet/server/services/package_policy_service.ts b/x-pack/plugins/fleet/server/services/package_policy_service.ts index fed46872ab6cb..8b47f42380935 100644 --- a/x-pack/plugins/fleet/server/services/package_policy_service.ts +++ b/x-pack/plugins/fleet/server/services/package_policy_service.ts @@ -239,7 +239,7 @@ export interface PackagePolicyClient { fetchAllItemIds( soClient: SavedObjectsClientContract, options?: PackagePolicyClientFetchAllItemIdsOptions - ): AsyncIterable; + ): Promise>; /** * Returns an `AsyncIterable` for retrieving all integration policies @@ -249,7 +249,7 @@ export interface PackagePolicyClient { fetchAllItems( soClient: SavedObjectsClientContract, options?: PackagePolicyClientFetchAllItemsOptions - ): AsyncIterable; + ): Promise>; } export type PackagePolicyClientFetchAllItemIdsOptions = Pick; diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index 21caf5088f457..802edd93e0543 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -20,7 +20,7 @@ import type { } from '../../common/types'; import type { AgentPolicy, NewPackagePolicy, Output, DownloadSource } from '../types'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../constants'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '../constants'; import { appContextService } from './app_context'; @@ -67,7 +67,7 @@ const mockDefaultDownloadService: DownloadSource = { function getPutPreconfiguredPackagesMock() { const soClient = savedObjectsClientMock.create(); soClient.find.mockImplementation(async ({ type, search }) => { - if (type === AGENT_POLICY_SAVED_OBJECT_TYPE) { + if (type === LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE) { const id = search!.replace(/"/g, ''); const attributes = mockConfiguredPolicies.get(id); if (attributes) { diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/outputs.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration/outputs.test.ts index 3088814c8f8a3..e21ae1e06dc78 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration/outputs.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration/outputs.test.ts @@ -7,11 +7,9 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { appContextService } from '..'; - +import { appContextService } from '../app_context'; import type { PreconfiguredOutput } from '../../../common/types'; import type { Output } from '../../types'; - import * as agentPolicy from '../agent_policy'; import { outputService } from '../output'; @@ -26,15 +24,17 @@ jest.mock('../agent_policy_update'); jest.mock('../output'); jest.mock('../epm/packages/bundled_packages'); jest.mock('../epm/archive'); +jest.mock('../settings'); const mockedOutputService = outputService as jest.Mocked; jest.mock('../app_context', () => ({ appContextService: { - getInternalUserSOClientWithoutSpaceExtension: jest.fn(), - getExperimentalFeatures: () => ({ - useSpaceAwareness: true, + getExperimentalFeatures: jest.fn().mockReturnValue({ + useSpaceAwareness: false, }), + getInternalUserSOClient: jest.fn(), + getInternalUserSOClientWithoutSpaceExtension: jest.fn(), getLogger: () => new Proxy( {}, diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/reset_agent_policies.ts b/x-pack/plugins/fleet/server/services/preconfiguration/reset_agent_policies.ts index 79ea65c04980e..7e65dd665d0bd 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration/reset_agent_policies.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration/reset_agent_policies.ts @@ -12,12 +12,12 @@ import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { appContextService } from '../app_context'; import { setupFleet } from '../setup'; import { - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT, PACKAGE_POLICY_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, } from '../../constants'; -import { agentPolicyService } from '../agent_policy'; +import { agentPolicyService, getAgentPolicySavedObjectType } from '../agent_policy'; import { packagePolicyService } from '../package_policy'; import { getAgentsByKuery, forceUnenrollAgent } from '../agents'; import { listEnrollmentApiKeys, deleteEnrollmentApiKey } from '../api_keys'; @@ -61,7 +61,8 @@ async function _deleteGhostPackagePolicies( return; } - const objects = policyIds.map((id) => ({ id, type: AGENT_POLICY_SAVED_OBJECT_TYPE })); + const savedObjectType = await getAgentPolicySavedObjectType(); + const objects = policyIds.map((id) => ({ id, type: savedObjectType })); const agentPolicyExistsMap = (await soClient.bulkGet(objects)).saved_objects.reduce((acc, so) => { if (so.error && so.error.statusCode === 404) { acc.set(so.id, false); @@ -146,7 +147,7 @@ async function _deleteExistingData( existingPolicies = ( await agentPolicyService.list(soClient, { perPage: SO_SEARCH_LIMIT, - kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.is_preconfigured:true`, + kuery: `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.is_preconfigured:true`, }) ).items; } diff --git a/x-pack/plugins/fleet/server/services/security/fleet_router.ts b/x-pack/plugins/fleet/server/services/security/fleet_router.ts index f8e374c458344..11a4b084d4807 100644 --- a/x-pack/plugins/fleet/server/services/security/fleet_router.ts +++ b/x-pack/plugins/fleet/server/services/security/fleet_router.ts @@ -5,21 +5,19 @@ * 2.0. */ -import type { - IKibanaResponse, - IRouter, - KibanaRequest, - KibanaResponseFactory, - Logger, - RequestHandler, - RouteMethod, +import { + type IKibanaResponse, + type IRouter, + type KibanaRequest, + type KibanaResponseFactory, + type Logger, + type RequestHandler, + type RouteMethod, } from '@kbn/core/server'; import type { VersionedRouteConfig } from '@kbn/core-http-server'; import { PUBLIC_API_ACCESS } from '../../../common/constants'; - import type { FleetRequestHandlerContext } from '../..'; - import { getRequestStore } from '../request_store'; import type { FleetVersionedRouteConfig } from './types'; @@ -82,6 +80,7 @@ export function makeRouterWithFleetAuthz { const so = getDefaultSO(canEncrypt); const so2 = getDefaultSO2(canEncrypt); - agentPolicyService.fetchAllAgentPolicyIds = jest.fn(async function* () { - yield items || [so.attributes.policy_id, so2.attributes.policy_id]; - }); + agentPolicyService.fetchAllAgentPolicyIds = jest.fn().mockResolvedValue( + jest.fn(async function* () { + yield items || [so.attributes.policy_id, so2.attributes.policy_id]; + })() + ); } function setupMocks(canEncrypt: boolean = true) { diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts index 4cb5aa1221db7..0abad8961e47e 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts @@ -42,13 +42,9 @@ import type { UninstallTokenMetadata, } from '../../../../common/types/models/uninstall_token'; -import { - UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, - SO_SEARCH_LIMIT, - AGENT_POLICY_SAVED_OBJECT_TYPE, -} from '../../../constants'; +import { UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../constants'; import { appContextService } from '../../app_context'; -import { agentPolicyService } from '../../agent_policy'; +import { agentPolicyService, getAgentPolicySavedObjectType } from '../../agent_policy'; interface UninstallTokenSOAttributes { policy_id: string; @@ -231,10 +227,12 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { } private async searchPoliciesByName(policyNameSearchString: string): Promise { - const policyNameFilter = `${AGENT_POLICY_SAVED_OBJECT_TYPE}.attributes.name:${policyNameSearchString}`; + const agentPolicySavedObjectType = await getAgentPolicySavedObjectType(); + + const policyNameFilter = `${agentPolicySavedObjectType}.attributes.name:${policyNameSearchString}`; const agentPoliciesSOs = await this.soClient.find({ - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: agentPolicySavedObjectType, filter: policyNameFilter, }); @@ -576,7 +574,7 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { } private async getAllPolicyIds(): Promise { - const agentPolicyIdsFetcher = agentPolicyService.fetchAllAgentPolicyIds(this.soClient); + const agentPolicyIdsFetcher = await agentPolicyService.fetchAllAgentPolicyIds(this.soClient); const policyIds: string[] = []; for await (const agentPolicyId of agentPolicyIdsFetcher) { policyIds.push(...agentPolicyId); diff --git a/x-pack/plugins/fleet/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts index 6e248d7817a5f..68829b734eeaf 100644 --- a/x-pack/plugins/fleet/server/services/settings.ts +++ b/x-pack/plugins/fleet/server/services/settings.ts @@ -6,7 +6,7 @@ */ import Boom from '@hapi/boom'; -import type { SavedObjectsClientContract } from '@kbn/core/server'; +import type { SavedObjectsClientContract, SavedObjectsUpdateOptions } from '@kbn/core/server'; import { normalizeHostsForAgents } from '../../common/services'; import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_ID } from '../../common/constants'; @@ -35,6 +35,7 @@ export async function getSettings(soClient: SavedObjectsClientContract): Promise return { id: settingsSo.id, + version: settingsSo.version, ...settingsSo.attributes, fleet_server_hosts: fleetServerHosts.items.flatMap((item) => item.host_urls), preconfigured_fields: getConfigFleetServerHosts() ? ['fleet_server_hosts'] : [], @@ -70,12 +71,14 @@ export async function settingsSetup(soClient: SavedObjectsClientContract) { export async function saveSettings( soClient: SavedObjectsClientContract, - newData: Partial> + newData: Partial>, + options?: SavedObjectsUpdateOptions & { createWithOverwrite?: boolean } ): Promise & Pick> { const data = { ...newData }; if (data.fleet_server_hosts) { data.fleet_server_hosts = data.fleet_server_hosts.map(normalizeHostsForAgents); } + const { createWithOverwrite, ...updateOptions } = options ?? {}; try { const settings = await getSettings(soClient); @@ -89,7 +92,8 @@ export async function saveSettings( const res = await soClient.update( GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, settings.id, - data + data, + updateOptions ); return { @@ -114,7 +118,8 @@ export async function saveSettings( }, { id: GLOBAL_SETTINGS_ID, - overwrite: true, + // Do not overwrite if version is passed + overwrite: typeof createWithOverwrite === 'undefined' ? true : createWithOverwrite, } ); diff --git a/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.test.ts b/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.test.ts index 3cf070ab8fea3..7ced6a3fcddb9 100644 --- a/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.test.ts +++ b/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.test.ts @@ -5,125 +5,114 @@ * 2.0. */ -import { appContextService } from '../app_context'; - import type { Agent } from '../../types'; import { agentsKueryNamespaceFilter, isAgentInNamespace } from './agent_namespaces'; +import { isSpaceAwarenessEnabled } from './helpers'; -jest.mock('../app_context'); - -const mockedAppContextService = appContextService as jest.Mocked; +jest.mock('./helpers'); describe('isAgentInNamespace', () => { - describe('with the useSpaceAwareness feature flag disabled', () => { + describe('with isSpaceAwarenessEnabled is false', () => { beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: false, - } as any); + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(false); }); - it('returns true even if the agent is in a different space', () => { + it('returns true even if the agent is in a different space', async () => { const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; - expect(isAgentInNamespace(agent, 'space2')).toEqual(true); + expect(await isAgentInNamespace(agent, 'space2')).toEqual(true); }); }); - describe('with the useSpaceAwareness feature flag enabled', () => { + describe('with the isSpaceAwarenessEnabled return true', () => { beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: true, - } as any); + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(true); }); describe('when the namespace is defined', () => { - it('returns true in a custom space if the agent namespaces include the namespace', () => { + it('returns true in a custom space if the agent namespaces include the namespace', async () => { const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; - expect(isAgentInNamespace(agent, 'space1')).toEqual(true); + expect(await isAgentInNamespace(agent, 'space1')).toEqual(true); }); - it('returns false in a custom space if the agent namespaces do not include the namespace', () => { + it('returns false in a custom space if the agent namespaces do not include the namespace', async () => { const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; - expect(isAgentInNamespace(agent, 'space2')).toEqual(false); + expect(await isAgentInNamespace(agent, 'space2')).toEqual(false); }); - it('returns true in the default space if the agent has zero length namespaces', () => { + it('returns true in the default space if the agent has zero length namespaces', async () => { const agent = { id: '123', namespaces: [] as string[] } as Agent; - expect(isAgentInNamespace(agent, 'default')).toEqual(true); + expect(await isAgentInNamespace(agent, 'default')).toEqual(true); }); - it('returns false in a custom space if the agent has zero length namespaces', () => { + it('returns false in a custom space if the agent has zero length namespaces', async () => { const agent = { id: '123', namespaces: [] as string[] } as Agent; - expect(isAgentInNamespace(agent, 'space1')).toEqual(false); + expect(await isAgentInNamespace(agent, 'space1')).toEqual(false); }); - it('returns true in the default space if the agent does not have namespaces', () => { + it('returns true in the default space if the agent does not have namespaces', async () => { const agent = { id: '123' } as Agent; - expect(isAgentInNamespace(agent, 'default')).toEqual(true); + expect(await isAgentInNamespace(agent, 'default')).toEqual(true); }); - it('returns false in a custom space if the agent does not have namespaces', () => { + it('returns false in a custom space if the agent does not have namespaces', async () => { const agent = { id: '123' } as Agent; - expect(isAgentInNamespace(agent, 'space1')).toEqual(false); + expect(await isAgentInNamespace(agent, 'space1')).toEqual(false); }); }); describe('when the namespace is undefined', () => { - it('returns true if the agent does not have namespaces', () => { + it('returns true if the agent does not have namespaces', async () => { const agent = { id: '123' } as Agent; - expect(isAgentInNamespace(agent)).toEqual(true); + expect(await isAgentInNamespace(agent)).toEqual(true); }); - it('returns true if the agent has zero length namespaces', () => { + it('returns true if the agent has zero length namespaces', async () => { const agent = { id: '123', namespaces: [] as string[] } as Agent; - expect(isAgentInNamespace(agent)).toEqual(true); + expect(await isAgentInNamespace(agent)).toEqual(true); }); - it('returns true if the agent namespaces include the default one', () => { + it('returns true if the agent namespaces include the default one', async () => { const agent = { id: '123', namespaces: ['default'] } as Agent; - expect(isAgentInNamespace(agent)).toEqual(true); + expect(await isAgentInNamespace(agent)).toEqual(true); }); - it('returns false if the agent namespaces include the default one', () => { + it('returns false if the agent namespaces include the default one', async () => { const agent = { id: '123', namespaces: ['space1'] } as Agent; - expect(isAgentInNamespace(agent)).toEqual(false); + expect(await isAgentInNamespace(agent)).toEqual(false); }); }); }); }); describe('agentsKueryNamespaceFilter', () => { - describe('with the useSpaceAwareness feature flag disabled', () => { + describe('with isSpaceAwarenessEnabled returning false', () => { beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: false, - } as any); + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(false); }); - it('returns undefined if the useSpaceAwareness feature flag disabled', () => { - expect(agentsKueryNamespaceFilter('space1')).toBeUndefined(); + it('returns undefined', async () => { + expect(await agentsKueryNamespaceFilter('space1')).toBeUndefined(); }); }); - describe('with the useSpaceAwareness feature flag enabled', () => { + describe('with isSpaceAwarenessEnabled returning true', () => { beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: true, - } as any); + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(true); }); - it('returns undefined if the namespace is undefined', () => { - expect(agentsKueryNamespaceFilter()).toBeUndefined(); + it('returns undefined if the namespace is undefined', async () => { + expect(await agentsKueryNamespaceFilter()).toBeUndefined(); }); - it('returns a kuery for the default space', () => { - expect(agentsKueryNamespaceFilter('default')).toEqual( + it('returns a kuery for the default space', async () => { + expect(await agentsKueryNamespaceFilter('default')).toEqual( 'namespaces:(default) or not namespaces:*' ); }); - it('returns a kuery for custom spaces', () => { - expect(agentsKueryNamespaceFilter('space1')).toEqual('namespaces:(space1)'); + it('returns a kuery for custom spaces', async () => { + expect(await agentsKueryNamespaceFilter('space1')).toEqual('namespaces:(space1)'); }); }); }); diff --git a/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.ts b/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.ts index 1a1834635662b..49253ad9767ea 100644 --- a/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.ts +++ b/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.ts @@ -7,12 +7,12 @@ import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; -import { appContextService } from '../app_context'; - import type { Agent } from '../../types'; -export function isAgentInNamespace(agent: Agent, namespace?: string) { - const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; +import { isSpaceAwarenessEnabled } from './helpers'; + +export async function isAgentInNamespace(agent: Agent, namespace?: string) { + const useSpaceAwareness = await isSpaceAwarenessEnabled(); if (!useSpaceAwareness) { return true; } @@ -31,8 +31,8 @@ export function isAgentInNamespace(agent: Agent, namespace?: string) { ); } -export function agentsKueryNamespaceFilter(namespace?: string) { - const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; +export async function agentsKueryNamespaceFilter(namespace?: string) { + const useSpaceAwareness = await isSpaceAwarenessEnabled(); if (!useSpaceAwareness || !namespace) { return; } diff --git a/x-pack/plugins/fleet/server/services/spaces/enable_space_awareness.test.ts b/x-pack/plugins/fleet/server/services/spaces/enable_space_awareness.test.ts new file mode 100644 index 0000000000000..f8f4e95c78b45 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/spaces/enable_space_awareness.test.ts @@ -0,0 +1,203 @@ +/* + * 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 MockedLogger, loggerMock } from '@kbn/logging-mocks'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; + +import type { Settings } from '../../types'; +import { appContextService } from '../app_context'; +import { getSettingsOrUndefined, saveSettings } from '../settings'; + +import { enableSpaceAwarenessMigration } from './enable_space_awareness'; + +jest.mock('../app_context'); +jest.mock('../settings'); + +function mockGetSettingsOrUndefined(settings?: Partial) { + if (settings) { + jest.mocked(getSettingsOrUndefined).mockResolvedValue(settings as any); + } else { + jest.mocked(getSettingsOrUndefined).mockResolvedValue(undefined); + } +} + +describe('enableSpaceAwarenessMigration', () => { + let mockedLogger: MockedLogger; + let soClient: ReturnType; + beforeEach(() => { + mockedLogger = loggerMock.create(); + soClient = savedObjectsClientMock.create(); + jest.mocked(appContextService.getExperimentalFeatures).mockReset(); + jest.mocked(appContextService.getLogger).mockReturnValue(mockedLogger); + jest + .mocked(appContextService.getInternalUserSOClientWithoutSpaceExtension) + .mockReturnValue(soClient); + jest.mocked(getSettingsOrUndefined).mockReset(); + jest.mocked(saveSettings).mockReset(); + + jest.mocked(saveSettings).mockResolvedValue({} as any); + }); + it('should do nothing if migration is already done', async () => { + mockGetSettingsOrUndefined({ + use_space_awareness_migration_status: 'success', + }); + await enableSpaceAwarenessMigration(); + const logs = loggerMock.collect(mockedLogger); + expect(logs).toMatchInlineSnapshot(` + Object { + "debug": Array [], + "error": Array [], + "fatal": Array [], + "info": Array [], + "log": Array [], + "trace": Array [], + "warn": Array [], + } + `); + }); + + it('should do migration if migration is not pending', async () => { + mockGetSettingsOrUndefined({}); + + soClient.createPointInTimeFinder.mockReturnValueOnce({ + find: jest.fn().mockImplementation(async function* () { + yield { + saved_objects: [ + { id: 'agent-policy-1', attributes: {} }, + { id: 'agent-policy-2', attributes: {} }, + ], + }; + }), + close: jest.fn(), + }); + + soClient.createPointInTimeFinder.mockReturnValueOnce({ + find: jest.fn().mockImplementation(async function* () { + yield { + saved_objects: [ + { id: 'package-policy-1', attributes: {} }, + { id: 'package-policy-2', attributes: {} }, + ], + }; + }), + close: jest.fn(), + }); + + soClient.bulkCreate.mockImplementation((objects) => { + return { + saved_objects: objects.map(() => ({})), + } as any; + }); + + await enableSpaceAwarenessMigration(); + + const logs = loggerMock.collect(mockedLogger); + expect(logs).toMatchInlineSnapshot(` + Object { + "debug": Array [], + "error": Array [], + "fatal": Array [], + "info": Array [ + Array [ + "Starting Fleet space awareness migration", + ], + Array [ + "Fleet space awareness migration is complete", + ], + ], + "log": Array [], + "trace": Array [], + "warn": Array [], + } + `); + + expect(soClient.bulkCreate).toBeCalledWith( + [ + expect.objectContaining({ + id: 'agent-policy-1', + type: 'fleet-agent-policies', + }), + expect.objectContaining({ + id: 'agent-policy-2', + type: 'fleet-agent-policies', + }), + ], + { overwrite: true, refresh: 'wait_for' } + ); + expect(soClient.bulkCreate).toBeCalledWith( + [ + expect.objectContaining({ + id: 'package-policy-1', + type: 'fleet-package-policies', + }), + expect.objectContaining({ + id: 'package-policy-2', + type: 'fleet-package-policies', + }), + ], + { overwrite: true, refresh: 'wait_for' } + ); + + expect(saveSettings).toBeCalledWith( + expect.anything(), + expect.objectContaining({ + use_space_awareness_migration_status: 'success', + }) + ); + }); + + it('should set the status to error if an error happen', async () => { + mockGetSettingsOrUndefined({}); + + soClient.createPointInTimeFinder.mockImplementation(() => { + return { + async *find() { + throw new Error('unexpected error test'); + }, + close: jest.fn(), + } as any; + }); + + let error: Error | undefined; + await enableSpaceAwarenessMigration().catch((err) => { + error = err; + }); + + expect(error).toBeDefined(); + + const logs = loggerMock.collect(mockedLogger); + expect(logs).toMatchInlineSnapshot(` + Object { + "debug": Array [], + "error": Array [ + Array [ + "Fleet space awareness migration failed", + Object { + "error": [Error: unexpected error test], + }, + ], + ], + "fatal": Array [], + "info": Array [ + Array [ + "Starting Fleet space awareness migration", + ], + ], + "log": Array [], + "trace": Array [], + "warn": Array [], + } + `); + + expect(saveSettings).toBeCalledWith( + expect.anything(), + expect.objectContaining({ + use_space_awareness_migration_status: 'error', + }) + ); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/spaces/enable_space_awareness.ts b/x-pack/plugins/fleet/server/services/spaces/enable_space_awareness.ts new file mode 100644 index 0000000000000..ba8b08dbc6914 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/spaces/enable_space_awareness.ts @@ -0,0 +1,128 @@ +/* + * 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 { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsErrorHelpers, type Logger } from '@kbn/core/server'; + +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../common'; +import { + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, + PACKAGE_POLICY_SAVED_OBJECT_TYPE, +} from '../../../common/constants'; +import { appContextService } from '..'; +import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../constants'; +import { getSettingsOrUndefined, saveSettings } from '../settings'; +import { FleetError } from '../../errors'; + +import { PENDING_MIGRATION_TIMEOUT } from './helpers'; + +export async function enableSpaceAwarenessMigration() { + const soClient = appContextService.getInternalUserSOClientWithoutSpaceExtension(); + const logger = appContextService.getLogger(); + + const existingSettings = await getSettingsOrUndefined(soClient); + if (existingSettings?.use_space_awareness_migration_status === 'success') { + return; + } + + if ( + existingSettings?.use_space_awareness_migration_started_at && + new Date(existingSettings?.use_space_awareness_migration_started_at).getTime() > + Date.now() - PENDING_MIGRATION_TIMEOUT + ) { + logger.info('Fleet space awareness migration is pending'); + throw new FleetError('Migration is pending.'); + } + + await saveSettings( + soClient, + { + use_space_awareness_migration_status: 'pending', + use_space_awareness_migration_started_at: new Date().toISOString(), + }, + { + createWithOverwrite: false, + version: existingSettings?.version, + } + ).catch((err) => { + if (SavedObjectsErrorHelpers.isConflictError(err)) { + logger.info('Fleet space awareness migration is pending'); + throw new FleetError('Migration is pending. (conflict acquiring the lock)'); + } + + throw err; + }); + + await runMigration(soClient, logger) + .then(async () => { + logger.info('Fleet space awareness migration is complete'); + // Update Settings SO + await saveSettings(soClient, { + use_space_awareness_migration_status: 'success', + use_space_awareness_migration_started_at: null, + }); + }) + .catch(async (error) => { + logger.error('Fleet space awareness migration failed', { error }); + await saveSettings(soClient, { + use_space_awareness_migration_status: 'error', + use_space_awareness_migration_started_at: null, + }); + throw error; + }); +} + +async function runMigration(soClient: SavedObjectsClientContract, logger: Logger) { + logger.info('Starting Fleet space awareness migration'); + // Agent Policy + await batchMigration( + soClient, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, + AGENT_POLICY_SAVED_OBJECT_TYPE + ); + // Package policu + await batchMigration( + soClient, + LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, + PACKAGE_POLICY_SAVED_OBJECT_TYPE + ); +} + +const BATCH_SIZE = 1000; + +async function batchMigration( + soClient: SavedObjectsClientContract, + sourceSoType: string, + targetSoType: string +) { + const finder = soClient.createPointInTimeFinder({ + type: sourceSoType, + perPage: BATCH_SIZE, + }); + try { + for await (const result of finder.find()) { + const createRes = await soClient.bulkCreate( + result.saved_objects.map((so) => ({ + type: targetSoType, + id: so.id, + attributes: so.attributes, + })), + { + overwrite: true, + refresh: 'wait_for', + } + ); + for (const res of createRes.saved_objects) { + if (res.error) { + throw res.error; + } + } + } + } finally { + await finder.close(); + } +} diff --git a/x-pack/plugins/fleet/server/services/spaces/helper.test.ts b/x-pack/plugins/fleet/server/services/spaces/helper.test.ts new file mode 100644 index 0000000000000..76d9c8707fe0b --- /dev/null +++ b/x-pack/plugins/fleet/server/services/spaces/helper.test.ts @@ -0,0 +1,131 @@ +/* + * 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 { Settings } from '../../types'; +import { appContextService } from '../app_context'; +import { getSettingsOrUndefined } from '../settings'; + +import { isSpaceAwarenessEnabled, isSpaceAwarenessMigrationPending } from './helpers'; + +jest.mock('../app_context'); +jest.mock('../settings'); + +function mockFeatureFlag(val: boolean) { + jest.mocked(appContextService.getExperimentalFeatures).mockReturnValue({ + useSpaceAwareness: val, + } as any); +} + +function mockGetSettings(settings?: Partial) { + if (settings) { + jest.mocked(getSettingsOrUndefined).mockResolvedValue(settings as any); + } else { + jest.mocked(getSettingsOrUndefined).mockResolvedValue(undefined); + } +} + +describe('isSpaceAwarenessEnabled', () => { + beforeEach(() => { + jest.mocked(appContextService.getExperimentalFeatures).mockReset(); + jest.mocked(getSettingsOrUndefined).mockReset(); + }); + it('should return false if feature flag is disabled', async () => { + mockFeatureFlag(false); + await expect(isSpaceAwarenessEnabled()).resolves.toBe(false); + }); + + it('should return false if feature flag is enabled but user did not optin', async () => { + mockFeatureFlag(true); + mockGetSettings({ + use_space_awareness_migration_status: undefined, + }); + const res = await isSpaceAwarenessEnabled(); + + expect(res).toBe(false); + }); + + it('should return false if feature flag is enabled and settings do not exists', async () => { + mockFeatureFlag(true); + mockGetSettings(); + const res = await isSpaceAwarenessEnabled(); + + expect(res).toBe(false); + }); + + it('should return true if feature flag is enabled and user optin', async () => { + mockFeatureFlag(true); + mockGetSettings({ + use_space_awareness_migration_status: 'success', + }); + const res = await isSpaceAwarenessEnabled(); + + expect(res).toBe(true); + }); +}); + +describe('isSpaceAwarenessMigrationPending', () => { + beforeEach(() => { + jest.mocked(appContextService.getExperimentalFeatures).mockReset(); + jest.mocked(getSettingsOrUndefined).mockReset(); + }); + it('should return false if feature flag is disabled', async () => { + mockFeatureFlag(false); + const res = await isSpaceAwarenessMigrationPending(); + + expect(res).toBe(false); + }); + + it('should return false if feature flag is enabled but user did not optin', async () => { + mockFeatureFlag(true); + mockGetSettings({ + use_space_awareness_migration_status: undefined, + }); + const res = await isSpaceAwarenessMigrationPending(); + + expect(res).toBe(false); + }); + + it('should return false if feature flag is enabled and settings do not exists', async () => { + mockFeatureFlag(true); + mockGetSettings(); + const res = await isSpaceAwarenessMigrationPending(); + + expect(res).toBe(false); + }); + + it('should return false if feature flag is enabled and migration is complete', async () => { + mockFeatureFlag(true); + mockGetSettings({ + use_space_awareness_migration_status: 'success', + }); + const res = await isSpaceAwarenessMigrationPending(); + + expect(res).toBe(false); + }); + + it('should return true if feature flag is enabled and migration is in progress', async () => { + mockFeatureFlag(true); + mockGetSettings({ + use_space_awareness_migration_status: 'pending', + use_space_awareness_migration_started_at: new Date().toISOString(), + }); + const res = await isSpaceAwarenessMigrationPending(); + + expect(res).toBe(true); + }); + + it('should return false if feature flag is enabled and an old migration is in progress', async () => { + mockFeatureFlag(true); + mockGetSettings({ + use_space_awareness_migration_status: 'pending', + use_space_awareness_migration_started_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(), + }); + const res = await isSpaceAwarenessMigrationPending(); + + expect(res).toBe(false); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/spaces/helpers.ts b/x-pack/plugins/fleet/server/services/spaces/helpers.ts new file mode 100644 index 0000000000000..b9034acb178d2 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/spaces/helpers.ts @@ -0,0 +1,44 @@ +/* + * 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 { appContextService } from '../app_context'; +import { getSettingsOrUndefined } from '../settings'; + +export const PENDING_MIGRATION_TIMEOUT = 60 * 60 * 1000; +/** + * Return true if user optin for the space awareness feature. + */ +export async function isSpaceAwarenessEnabled(): Promise { + if (!appContextService.getExperimentalFeatures().useSpaceAwareness) { + return false; + } + + const settings = await getSettingsOrUndefined(appContextService.getInternalUserSOClient()); + + return settings?.use_space_awareness_migration_status === 'success' ?? false; +} + +/** + * Return true if space awareness migration is currently running + */ +export async function isSpaceAwarenessMigrationPending(): Promise { + if (!appContextService.getExperimentalFeatures().useSpaceAwareness) { + return false; + } + + const settings = await getSettingsOrUndefined(appContextService.getInternalUserSOClient()); + + if ( + settings?.use_space_awareness_migration_status === 'pending' && + settings?.use_space_awareness_migration_started_at && + new Date(settings?.use_space_awareness_migration_started_at).getTime() > + Date.now() - PENDING_MIGRATION_TIMEOUT + ) { + return true; + } + return false; +} diff --git a/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.test.ts b/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.test.ts index 43713597e364a..e9bdc4cb38e43 100644 --- a/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.test.ts +++ b/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.test.ts @@ -5,13 +5,10 @@ * 2.0. */ -import { appContextService } from '..'; - +import { isSpaceAwarenessEnabled } from './helpers'; import { addNamespaceFilteringToQuery } from './query_namespaces_filtering'; -const mockedAppContextService = appContextService as jest.Mocked; - -jest.mock('../app_context'); +jest.mock('./helpers'); describe('addNamespaceFilteringToQuery', () => { const baseActionQuery = { @@ -67,31 +64,29 @@ describe('addNamespaceFilteringToQuery', () => { }, }; - describe('with the useSpaceAwareness feature flag disabled', () => { + describe('with isSpaceAwarenessEnabled returning false', () => { beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: false, - } as any); + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(false); }); - it('should return the same query', () => { - expect(addNamespaceFilteringToQuery(baseActionQuery, 'mySpace')).toEqual(baseActionQuery); + it('should return the same query', async () => { + expect(await addNamespaceFilteringToQuery(baseActionQuery, 'mySpace')).toEqual( + baseActionQuery + ); }); }); - describe('with the useSpaceAwareness feature flag enabled', () => { + describe('with isSpaceAwarenessEnabled returning true', () => { beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: true, - } as any); + jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(true); }); - it('should return the same query if the current namespace is undefined', () => { - expect(addNamespaceFilteringToQuery(baseActionQuery)).toEqual(baseActionQuery); + it('should return the same query if the current namespace is undefined', async () => { + expect(await addNamespaceFilteringToQuery(baseActionQuery)).toEqual(baseActionQuery); }); - it('should add a filter to the base action query in a custom space', () => { - expect(addNamespaceFilteringToQuery(baseActionQuery, 'mySpace')).toEqual({ + it('should add a filter to the base action query in a custom space', async () => { + expect(await addNamespaceFilteringToQuery(baseActionQuery, 'mySpace')).toEqual({ bool: { must_not: [ { @@ -111,8 +106,8 @@ describe('addNamespaceFilteringToQuery', () => { }); }); - it('should add a filter to the base action query in a custom space if there is already filtering', () => { - expect(addNamespaceFilteringToQuery(baseActionQueryWithFilter, 'mySpace')).toEqual({ + it('should add a filter to the base action query in a custom space if there is already filtering', async () => { + expect(await addNamespaceFilteringToQuery(baseActionQueryWithFilter, 'mySpace')).toEqual({ bool: { must_not: [ { @@ -140,8 +135,8 @@ describe('addNamespaceFilteringToQuery', () => { }); }); - it('should add a filter to the base policy query in a custom space', () => { - expect(addNamespaceFilteringToQuery(basePolicyQuery, 'mySpace')).toEqual({ + it('should add a filter to the base policy query in a custom space', async () => { + expect(await addNamespaceFilteringToQuery(basePolicyQuery, 'mySpace')).toEqual({ bool: { filter: [ { @@ -166,8 +161,8 @@ describe('addNamespaceFilteringToQuery', () => { }); }); - it('should add a filter to the base action query in the default space', () => { - expect(addNamespaceFilteringToQuery(baseActionQuery, 'default')).toEqual({ + it('should add a filter to the base action query in the default space', async () => { + expect(await addNamespaceFilteringToQuery(baseActionQuery, 'default')).toEqual({ bool: { must_not: [ { @@ -204,8 +199,8 @@ describe('addNamespaceFilteringToQuery', () => { }); }); - it('should add a filter to the base action query in the default space if there is already filtering', () => { - expect(addNamespaceFilteringToQuery(baseActionQueryWithFilter, 'default')).toEqual({ + it('should add a filter to the base action query in the default space if there is already filtering', async () => { + expect(await addNamespaceFilteringToQuery(baseActionQueryWithFilter, 'default')).toEqual({ bool: { must_not: [ { @@ -250,8 +245,8 @@ describe('addNamespaceFilteringToQuery', () => { }); }); - it('should add a filter to the base policy query in the default space', () => { - expect(addNamespaceFilteringToQuery(basePolicyQuery, 'default')).toEqual({ + it('should add a filter to the base policy query in the default space', async () => { + expect(await addNamespaceFilteringToQuery(basePolicyQuery, 'default')).toEqual({ bool: { filter: [ { diff --git a/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.ts b/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.ts index a2c233cbcc21a..55c450c116f65 100644 --- a/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.ts +++ b/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.ts @@ -7,10 +7,10 @@ import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; -import { appContextService } from '..'; +import { isSpaceAwarenessEnabled } from './helpers'; -export function addNamespaceFilteringToQuery(query: any, namespace?: string) { - const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; +export async function addNamespaceFilteringToQuery(query: any, namespace?: string) { + const useSpaceAwareness = await isSpaceAwarenessEnabled(); if (!useSpaceAwareness || !namespace) { return query; } diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent_policy.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent_policy.ts index aa38b54582fe5..0add093c8ce4e 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent_policy.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent_policy.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { NewAgentPolicySchema } from '../models'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE, AGENT_POLICY_MAPPINGS } from '../../constants'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, AGENT_POLICY_MAPPINGS } from '../../constants'; import { validateKuery } from '../../routes/utils/filter_utils'; @@ -27,7 +27,7 @@ export const GetAgentPoliciesRequestSchema = { validate: (value: string) => { const validationObj = validateKuery( value, - [AGENT_POLICY_SAVED_OBJECT_TYPE], + [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE], AGENT_POLICY_MAPPINGS, true ); diff --git a/x-pack/plugins/osquery/server/lib/fleet_integration.ts b/x-pack/plugins/osquery/server/lib/fleet_integration.ts index 684334c1488b4..e94fb23e043e6 100644 --- a/x-pack/plugins/osquery/server/lib/fleet_integration.ts +++ b/x-pack/plugins/osquery/server/lib/fleet_integration.ts @@ -8,7 +8,7 @@ import type { SavedObjectReference, SavedObjectsClient } from '@kbn/core/server'; import { filter, map } from 'lodash'; import type { PostPackagePolicyPostDeleteCallback } from '@kbn/fleet-plugin/server'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import { packSavedObjectType } from '../../common/types'; import { OSQUERY_INTEGRATION_NAME } from '../../common'; @@ -25,7 +25,7 @@ export const getPackagePolicyDeleteCallback = const foundPacks = await packsClient.find({ type: packSavedObjectType, hasReference: { - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, id: deletedOsqueryManagerPolicy.policy_id, }, perPage: 1000, diff --git a/x-pack/plugins/osquery/server/lib/telemetry/helpers.ts b/x-pack/plugins/osquery/server/lib/telemetry/helpers.ts index b8e8d99ed99ca..b3aebadd83396 100644 --- a/x-pack/plugins/osquery/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/osquery/server/lib/telemetry/helpers.ts @@ -7,7 +7,7 @@ import { filter, find, isEmpty, pick, isString } from 'lodash'; import type { PackagePolicy } from '@kbn/fleet-plugin/common'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import type { PackSavedObject, SavedQuerySavedObject } from '../../common/types'; /** @@ -33,7 +33,8 @@ export const templatePacks = (packsData: PackSavedObject[]) => { name: item.name, enabled: item.enabled, queries: item.queries, - policies: (filter(item.references, ['type', AGENT_POLICY_SAVED_OBJECT_TYPE]), 'id')?.length, + policies: (filter(item.references, ['type', LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]), 'id') + ?.length, prebuilt: !!filter(item.references, ['type', 'osquery-pack-asset']) && item.version !== undefined, }, diff --git a/x-pack/plugins/osquery/server/lib/update_global_packs.ts b/x-pack/plugins/osquery/server/lib/update_global_packs.ts index 2b825fd883c9b..fb14092441c49 100644 --- a/x-pack/plugins/osquery/server/lib/update_global_packs.ts +++ b/x-pack/plugins/osquery/server/lib/update_global_packs.ts @@ -9,7 +9,7 @@ import type { SavedObjectsClient } from '@kbn/core/server'; import { set } from '@kbn/safer-lodash-set'; import { has, map, mapKeys } from 'lodash'; import type { NewPackagePolicy } from '@kbn/fleet-plugin/common'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import produce from 'immer'; import { convertShardsToObject } from '../routes/utils'; import { packSavedObjectType } from '../../common/types'; @@ -58,7 +58,7 @@ export const updateGlobalPacksCreateCallback = async ( ...packagePolicy.policy_ids.map((policyId) => ({ id: policyId, name: agentPolicies[policyId]?.name, - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, })), ], } diff --git a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts index 31f9395d2174e..91baee991c4e0 100644 --- a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts @@ -11,7 +11,7 @@ import { has, unset, some, mapKeys } from 'lodash'; import { produce } from 'immer'; import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, } from '@kbn/fleet-plugin/common'; import type { IRouter } from '@kbn/core/server'; @@ -112,7 +112,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte const references = policiesList.map((id) => ({ id, name: agentPoliciesIdMap[id]?.name, - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, })); const packSO = await savedObjectsClient.create( diff --git a/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts index 808bb1bdbb864..450f3ff805acb 100644 --- a/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts @@ -7,7 +7,7 @@ import { filter, map, omit } from 'lodash'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import type { IRouter } from '@kbn/core/server'; import type { FindPacksRequestQuerySchema } from '../../../common/api'; import { buildRouteValidation } from '../../utils/build_validation/route_validation'; @@ -51,7 +51,7 @@ export const findPackRoute = (router: IRouter) => { const packSavedObjects: PackResponseData[] = map(soClientResponse.saved_objects, (pack) => { const policyIds = map( - filter(pack.references, ['type', AGENT_POLICY_SAVED_OBJECT_TYPE]), + filter(pack.references, ['type', LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]), 'id' ); diff --git a/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts index d42d3f95bebac..724deedf19845 100644 --- a/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts @@ -6,7 +6,7 @@ */ import { filter, map } from 'lodash'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import type { IRouter } from '@kbn/core/server'; import type { ReadPacksRequestParamsSchema } from '../../../common/api'; import { buildRouteValidation } from '../../utils/build_validation/route_validation'; @@ -46,7 +46,10 @@ export const readPackRoute = (router: IRouter) => { const { attributes, references, id, ...rest } = await savedObjectsClient.get(packSavedObjectType, request.params.id); - const policyIds = map(filter(references, ['type', AGENT_POLICY_SAVED_OBJECT_TYPE]), 'id'); + const policyIds = map( + filter(references, ['type', LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]), + 'id' + ); const osqueryPackAssetReference = !!filter(references, ['type', 'osquery-pack-asset']); const data: ReadPackResponseData = { diff --git a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts index 0109270f539d9..451a7daf4e1d6 100644 --- a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts @@ -11,7 +11,7 @@ import { unset, has, difference, filter, find, map, mapKeys, uniq, some, isEmpty import { produce } from 'immer'; import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, } from '@kbn/fleet-plugin/common'; import type { IRouter } from '@kbn/core/server'; @@ -135,7 +135,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte const nonAgentPolicyReferences = filter( currentPackSO.references, - (reference) => reference.type !== AGENT_POLICY_SAVED_OBJECT_TYPE + (reference) => reference.type !== LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE ); const getUpdatedReferences = () => { if (!policy_ids && isEmpty(shards)) { @@ -147,7 +147,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte ...policiesList.map((id) => ({ id, name: agentPoliciesIdMap[id]?.name, - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, })), ]; }; @@ -173,7 +173,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte ); const currentAgentPolicyIds = map( - filter(currentPackSO.references, ['type', AGENT_POLICY_SAVED_OBJECT_TYPE]), + filter(currentPackSO.references, ['type', LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]), 'id' ); const updatedPackSO = await savedObjectsClient.get( diff --git a/x-pack/plugins/osquery/server/routes/status/create_status_route.ts b/x-pack/plugins/osquery/server/routes/status/create_status_route.ts index 06ea214c9cf6b..8b6f75100a371 100644 --- a/x-pack/plugins/osquery/server/routes/status/create_status_route.ts +++ b/x-pack/plugins/osquery/server/routes/status/create_status_route.ts @@ -12,7 +12,7 @@ import { filter, reduce, mapKeys, each, unset, uniq, map, has, flatMap } from 'l import type { PackagePolicyInputStream } from '@kbn/fleet-plugin/common'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, } from '@kbn/fleet-plugin/common'; import type { IRouter } from '@kbn/core/server'; import { API_VERSIONS } from '../../../common/constants'; @@ -146,7 +146,7 @@ export const createStatusRoute = (router: IRouter, osqueryContext: OsqueryAppCon references: packObject.policy_ids.map((policyId: string) => ({ id: policyId, name: agentPolicies[policyId].name, - type: AGENT_POLICY_SAVED_OBJECT_TYPE, + type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, })), refresh: 'wait_for', } diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts index c657cc6e53119..5ad28bc37566c 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts @@ -29,7 +29,7 @@ import type { import { AGENT_API_ROUTES, AGENT_POLICY_API_ROUTES, - AGENT_POLICY_SAVED_OBJECT_TYPE, + LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, agentPolicyRouteService, agentRouteService, AGENTS_INDEX, @@ -850,7 +850,7 @@ export const getOrCreateDefaultAgentPolicy = async ({ policyName = DEFAULT_AGENT_POLICY_NAME, }: GetOrCreateDefaultAgentPolicyOptions): Promise => { const existingPolicy = await fetchAgentPolicyList(kbnClient, { - kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.name: "${policyName}"`, + kuery: `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.name: "${policyName}"`, }); if (existingPolicy.items[0]) { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index bb2ea455675c0..22e113f05de13 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -84,9 +84,11 @@ describe('ManifestManager', () => { const ARTIFACT_NAME_BLOCKLISTS_LINUX = 'endpoint-blocklist-linux-v1'; const getMockPolicyFetchAllItemIds = (items: string[]) => - jest.fn(async function* () { - yield items; - }); + jest.fn(async () => + jest.fn(async function* () { + yield items; + })() + ); let ARTIFACTS: InternalArtifactCompleteSchema[] = []; let ARTIFACTS_BY_ID: { [K: string]: InternalArtifactCompleteSchema } = {}; @@ -1265,9 +1267,11 @@ describe('ManifestManager', () => { describe('tryDispatch', () => { const getMockPolicyFetchAllItems = (items: PackagePolicy[]) => - jest.fn(async function* () { - yield items; - }); + jest.fn(async () => + jest.fn(async function* () { + yield items; + })() + ); test(`Should not dispatch if no policies`, async () => { const context = buildManifestManagerContextMock({}); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index f10dbb1ab3a50..63a2b56cb8525 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -686,7 +686,7 @@ export class ManifestManager { }, }); - for await (const policies of this.fetchAllPolicies()) { + for await (const policies of await this.fetchAllPolicies()) { for (const packagePolicy of policies) { const { id, name } = packagePolicy; @@ -768,7 +768,7 @@ export class ManifestManager { } } - private fetchAllPolicies(): AsyncIterable { + private fetchAllPolicies(): Promise> { return this.packagePolicyService.fetchAllItems(this.savedObjectsClient, { kuery: 'ingest-package-policies.package.name:endpoint', }); @@ -776,7 +776,7 @@ export class ManifestManager { private async listEndpointPolicyIds(): Promise { const allPolicyIds: string[] = []; - const idFetcher = this.packagePolicyService.fetchAllItemIds(this.savedObjectsClient, { + const idFetcher = await this.packagePolicyService.fetchAllItemIds(this.savedObjectsClient, { kuery: 'ingest-package-policies.package.name:endpoint', }); diff --git a/x-pack/plugins/security_solution/server/integration_tests/lib/telemetry_helpers.ts b/x-pack/plugins/security_solution/server/integration_tests/lib/telemetry_helpers.ts index 2daf8c732002b..2e8820dedd52e 100644 --- a/x-pack/plugins/security_solution/server/integration_tests/lib/telemetry_helpers.ts +++ b/x-pack/plugins/security_solution/server/integration_tests/lib/telemetry_helpers.ts @@ -23,7 +23,7 @@ import { deleteExceptionList, deleteExceptionListItem, } from '@kbn/lists-plugin/server/services/exception_lists'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common/constants'; +import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common/constants'; import { packagePolicyService } from '@kbn/fleet-plugin/server/services'; @@ -282,7 +282,7 @@ export async function createAgentPolicy( ], }; - await soClient.create(AGENT_POLICY_SAVED_OBJECT_TYPE, {}, { id }).catch(() => {}); + await soClient.create(LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, {}, { id }).catch(() => {}); await packagePolicyService .create(soClient, esClient, packagePolicy, { id, diff --git a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts index b860a774ba122..b830373e7dbbd 100644 --- a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts @@ -49,13 +49,13 @@ export default function (providerContext: FtrProviderContext) { const enableOutputSecrets = async () => { try { - await kibanaServer.savedObjects.update({ + await kibanaServer.savedObjects.create({ type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, id: 'fleet-default-settings', attributes: { output_secret_storage_requirements_met: true, }, - overwrite: false, + overwrite: true, }); } catch (e) { throw e; @@ -64,13 +64,13 @@ export default function (providerContext: FtrProviderContext) { const disableOutputSecrets = async () => { try { - await kibanaServer.savedObjects.update({ + await kibanaServer.savedObjects.create({ type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, id: 'fleet-default-settings', attributes: { output_secret_storage_requirements_met: false, }, - overwrite: false, + overwrite: true, }); } catch (e) { throw e; diff --git a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts index d8e641b7af0a5..86e32ca567413 100644 --- a/x-pack/test/fleet_api_integration/apis/policy_secrets.ts +++ b/x-pack/test/fleet_api_integration/apis/policy_secrets.ts @@ -169,13 +169,13 @@ export default function (providerContext: FtrProviderContext) { // Reset the global settings object to disable secrets between tests. // Each test can re-run setup as part of its setup if it needs to enable secrets - await kibanaServer.savedObjects.update({ + await kibanaServer.savedObjects.create({ type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, id: 'fleet-default-settings', attributes: { secret_storage_requirements_met: false, }, - overwrite: false, + overwrite: true, }); }; diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts index efd73ddb54b0f..4f458cd7190cc 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts @@ -56,6 +56,8 @@ export default function (providerContext: FtrProviderContext) { let testSpaceAgent2: string; before(async () => { + await apiClient.postEnableSpaceAwareness(); + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ apiClient.createAgentPolicy(), apiClient.createAgentPolicy(TEST_SPACE_1), diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts index 7ab4e86448bde..74bd3aa0871f3 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts @@ -44,6 +44,8 @@ export default function (providerContext: FtrProviderContext) { let spaceTest1Policy1: CreateAgentPolicyResponse; let spaceTest1Policy2: CreateAgentPolicyResponse; before(async () => { + await apiClient.postEnableSpaceAwareness(); + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ apiClient.createAgentPolicy(), apiClient.createAgentPolicy(TEST_SPACE_1), diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts index 047d32a854511..b4f7241dec0fb 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts @@ -50,6 +50,8 @@ export default function (providerContext: FtrProviderContext) { let testSpaceAgent2: string; before(async () => { + await apiClient.postEnableSpaceAwareness(); + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ apiClient.createAgentPolicy(), apiClient.createAgentPolicy(TEST_SPACE_1), diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts index 11fd693d9340b..b3879dd780e25 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts @@ -15,6 +15,7 @@ import { GetAgentsResponse, GetOneAgentPolicyResponse, GetOneAgentResponse, + GetPackagePoliciesResponse, } from '@kbn/fleet-plugin/common'; import { GetEnrollmentAPIKeysResponse, @@ -48,6 +49,7 @@ export class SpaceTestApiClient { return res; } + // Agent policies async createAgentPolicy( spaceId?: string, @@ -79,6 +81,14 @@ export class SpaceTestApiClient { return res; } + + async getPackagePolicies(spaceId?: string): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/package_policies`) + .expect(200); + + return res; + } async createFleetServerPolicy(spaceId?: string): Promise { const { body: res } = await this.supertest .post(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies`) @@ -322,4 +332,13 @@ export class SpaceTestApiClient { return res; } + // Enable space awareness + async postEnableSpaceAwareness(spaceId?: string): Promise { + const { body: res } = await this.supertest + .post(`${this.getBaseUrl(spaceId)}/internal/fleet/enable_space_awareness`) + .set('kbn-xsrf', 'xxxx') + .set('elastic-api-version', '1'); + + return res; + } } diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_api_keys.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_api_keys.ts index 13238acb3917c..c14e7336fedda 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_api_keys.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_api_keys.ts @@ -49,6 +49,8 @@ export default function (providerContext: FtrProviderContext) { let spaceTest1EnrollmentKey1: EnrollmentAPIKey; // Create agent policies it should create a enrollment key for every keys before(async () => { + await apiClient.postEnableSpaceAwareness(); + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ apiClient.createAgentPolicy(), apiClient.createAgentPolicy(TEST_SPACE_1), diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts index af648ec765971..b05e090efccf1 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts @@ -42,6 +42,7 @@ export default function (providerContext: FtrProviderContext) { setupTestSpaces(providerContext); before(async () => { + await apiClient.postEnableSpaceAwareness(); await apiClient.setup(); }); @@ -78,6 +79,7 @@ export default function (providerContext: FtrProviderContext) { setupTestSpaces(providerContext); before(async () => { + await apiClient.postEnableSpaceAwareness(); await apiClient.setup(); const testSpaceFleetServerPolicy = await apiClient.createFleetServerPolicy(TEST_SPACE_1); await createFleetAgent(esClient, testSpaceFleetServerPolicy.item.id, TEST_SPACE_1); @@ -116,6 +118,7 @@ export default function (providerContext: FtrProviderContext) { setupTestSpaces(providerContext); before(async () => { + await apiClient.postEnableSpaceAwareness(); await apiClient.setup(); const defaultFleetServerPolicy = await apiClient.createFleetServerPolicy(); await createFleetAgent(esClient, defaultFleetServerPolicy.item.id); diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/index.js b/x-pack/test/fleet_api_integration/apis/space_awareness/index.js index c684504372736..8025d5c810824 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/index.js +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/index.js @@ -15,5 +15,6 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./package_install')); loadTestFile(require.resolve('./space_settings')); loadTestFile(require.resolve('./actions')); + loadTestFile(require.resolve('./space_awareness_migration')); }); } diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/space_awareness_migration.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/space_awareness_migration.ts new file mode 100644 index 0000000000000..6d780a600496f --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/space_awareness_migration.ts @@ -0,0 +1,140 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { SpaceTestApiClient } from './api_helper'; +import { cleanFleetIndices } from './helpers'; +import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const esClient = getService('es'); + const kibanaServer = getService('kibanaServer'); + + describe('space awareness migration', async function () { + skipIfNoDockerRegistry(providerContext); + const apiClient = new SpaceTestApiClient(supertest); + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + setupTestSpaces(providerContext); + + // Create agent policies it should create a enrollment key for every keys + before(async () => { + const [defaultSpacePolicy1, spaceTest1Policy1] = await Promise.all([ + apiClient.createAgentPolicy(), + apiClient.createAgentPolicy(TEST_SPACE_1), + apiClient.createAgentPolicy(TEST_SPACE_1), + ]); + + await apiClient.installPackage({ + pkgName: 'nginx', + pkgVersion: '1.20.0', + force: true, // To avoid package verification + }); + + await apiClient.createPackagePolicy(undefined, { + policy_ids: [defaultSpacePolicy1.item.id], + name: `test-nginx-1-${Date.now()}`, + description: 'test', + package: { + name: 'nginx', + version: '1.20.0', + }, + inputs: {}, + }); + + await apiClient.createPackagePolicy(TEST_SPACE_1, { + policy_ids: [spaceTest1Policy1.item.id], + name: `test-nginx-2-${Date.now()}`, + description: 'test', + package: { + name: 'nginx', + version: '1.20.0', + }, + inputs: {}, + }); + }); + + describe('without opt-in', () => { + it('agent policies should not be space aware', async () => { + const policiesDefaultSpaceIds = (await apiClient.getAgentPolicies()).items + .map(({ id }) => id) + .sort(); + + const policiesTestSpaceIds = (await apiClient.getAgentPolicies(TEST_SPACE_1)).items + .map(({ id }) => id) + .sort(); + + expect(policiesDefaultSpaceIds.length).to.eql(3); + expect(policiesDefaultSpaceIds).to.eql(policiesTestSpaceIds); + }); + + it('package policies should not be space aware', async () => { + const policiesDefaultSpaceIds = (await apiClient.getPackagePolicies()).items + .map(({ id }) => id) + .sort(); + + const policiesTestSpaceIds = (await apiClient.getPackagePolicies(TEST_SPACE_1)).items + .map(({ id }) => id) + .sort(); + + expect(policiesDefaultSpaceIds.length).to.eql(2); + expect(policiesDefaultSpaceIds).to.eql(policiesTestSpaceIds); + }); + }); + + describe('with space awareness opt-in', () => { + before(async () => { + await apiClient.postEnableSpaceAwareness(); + }); + + it('agent policies should be migrated to the default space', async () => { + const policiesDefaultSpaceIds = (await apiClient.getAgentPolicies()).items + .map(({ id }) => id) + .sort(); + + const policiesTestSpaceIds = (await apiClient.getAgentPolicies(TEST_SPACE_1)).items + .map(({ id }) => id) + .sort(); + + expect(policiesDefaultSpaceIds.length).to.eql(3); + expect(policiesTestSpaceIds.length).to.eql(0); + }); + + it('package policies should be migrated to the default space', async () => { + const policiesDefaultSpaceIds = (await apiClient.getPackagePolicies()).items + .map(({ id }) => id) + .sort(); + + const policiesTestSpaceIds = (await apiClient.getPackagePolicies(TEST_SPACE_1)).items + .map(({ id }) => id) + .sort(); + + expect(policiesDefaultSpaceIds.length).to.eql(2); + expect(policiesTestSpaceIds.length).to.eql(0); + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/uninstall_tokens.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/uninstall_tokens.ts index 12bbc8bec3b37..b79afac99292b 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/uninstall_tokens.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/uninstall_tokens.ts @@ -48,6 +48,7 @@ export default function (providerContext: FtrProviderContext) { let spaceTest1Token: UninstallTokenMetadata; // Create agent policies it should create am uninstall token for every keys before(async () => { + await apiClient.postEnableSpaceAwareness(); const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ apiClient.createAgentPolicy(), apiClient.createAgentPolicy(TEST_SPACE_1), diff --git a/x-pack/test/fleet_api_integration/helpers.ts b/x-pack/test/fleet_api_integration/helpers.ts index 6144e17327b2a..6ae7845522733 100644 --- a/x-pack/test/fleet_api_integration/helpers.ts +++ b/x-pack/test/fleet_api_integration/helpers.ts @@ -143,14 +143,30 @@ export function setPrereleaseSetting(supertest: SuperTestAgent) { } export async function enableSecrets(providerContext: FtrProviderContext) { - await providerContext.getService('kibanaServer').savedObjects.update({ - type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, - id: 'fleet-default-settings', - attributes: { - secret_storage_requirements_met: true, - }, - overwrite: false, - }); + const settingsSO = await providerContext + .getService('kibanaServer') + .savedObjects.get({ type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, id: 'fleet-default-settings' }) + .catch((err) => {}); + + if (settingsSO) { + await providerContext.getService('kibanaServer').savedObjects.update({ + type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + id: 'fleet-default-settings', + attributes: { + secret_storage_requirements_met: true, + }, + overwrite: false, + }); + } else { + await providerContext.getService('kibanaServer').savedObjects.create({ + type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + id: 'fleet-default-settings', + attributes: { + secret_storage_requirements_met: true, + }, + overwrite: true, + }); + } } export const generateNAgentPolicies = async (