From 4e415822555b9efa15649a8c20cd739cd5abde1e Mon Sep 17 00:00:00 2001 From: Cristina Amico Date: Thu, 18 Jan 2024 22:58:15 +0100 Subject: [PATCH] [Fleet] Cascade agent policy's namespace to package policies (#174776) ## Summary Cascade agent policy's namespace to package policies. This change introduces a new behavior for package policies namespace, mirroring the way outputs work: - Allow leaving namespace blank when creating/updating a package policy. Agent policies namespace keeps being mandatory. - If a package policy has a namespace defined, that's used to compile the inputs in the full agent policy. - If a package policy has blank namespace, use the agent policy one. - Namespace "default" is no longer used as default for package policy namespace across the code - All the checks for the namespace are now moved to full agent policy handler. ## UI The only change in the UI is that the namespace in the policy creation can now be left blank: ![Screenshot 2024-01-16 at 16 55 07](https://github.com/elastic/kibana/assets/16084106/ed1be524-c4e6-49ac-8b53-39b07240d204) I think that the text is still relevant, as nothing has changed from that point of view. ## Testing ### Creation - Inherit namespace - Create agent policy with namespace `agentpolicyspace` - Create package policy with blank namespace - Generate full agent policy; package policy should have namespace `agentpolicyspace` ### Creation - Package policy custom namespace - Create agent policy with namespace `agentpolicyspace` - Create package policy with namespace `packagepolicyspace` - Generate full agent policy; package policy should have namespace `packagepolicyspace` ### Update - Change default namespace - Create agent policy with namespace `agentpolicyspace` - Create package policy with blank namespace - Generate full agent policy; Package policy should have namespace `agentpolicyspace` - Change agent policy namespace to `newspace` - Package policy should have namespace `newspace` as well ### Update - Don't override custom namespace - Create agent policy with namespace `agentpolicyspace` - Create package policy with namespace `packagepolicyspace` - Change agent policy namespace to `newspace` ### Check full agent policy - namespace should be correct; check inputs for the package policies and output permissions as well ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/fleet/common/openapi/bundled.json | 4 +- .../plugins/fleet/common/openapi/bundled.yaml | 6 +- .../schemas/package_policy_request.yaml | 4 +- .../schemas/update_package_policy.yaml | 1 - .../common/services/is_valid_namespace.ts | 7 +- .../services/validate_package_policy.ts | 9 +- .../common/types/models/package_policy.ts | 2 +- .../steps/step_define_package_policy.tsx | 1 + .../components/page_steps/add_integration.tsx | 2 +- .../hooks/send_generate_package_policy.tsx | 2 +- .../single_page_layout/hooks/form.test.tsx | 26 +- .../single_page_layout/hooks/form.tsx | 5 +- .../single_page_layout/index.test.tsx | 2 +- .../package_policies_table.tsx | 17 +- .../server/routes/package_policy/handlers.ts | 2 +- .../agent_policies/full_agent_policy.test.ts | 223 +++++++++++++++++- .../agent_policies/full_agent_policy.ts | 4 +- .../package_policies_to_agent_inputs.test.ts | 78 ++++++ .../package_policies_to_agent_inputs.ts | 18 +- ...kage_policies_to_agent_permissions.test.ts | 19 +- .../package_policies_to_agent_permissions.ts | 8 +- .../server/services/package_policy.test.ts | 4 +- .../fleet/server/services/package_policy.ts | 2 +- .../fleet/server/types/models/agent_policy.ts | 15 +- .../server/types/models/package_policy.ts | 10 +- .../server/types/models/preconfiguration.ts | 8 +- .../fleet/server/types/so_attributes.ts | 2 +- .../apis/package_policy/create.ts | 8 +- 28 files changed, 416 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index ecd544cc10646..0731047098a75 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -7671,8 +7671,8 @@ }, "namespace": { "type": "string", - "description": "namespace by default \"default\"", - "example": "default" + "description": "The package policy namespace. Leave blank to inherit the agent policy's namespace.", + "example": "customnamespace" }, "policy_id": { "type": "string", diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 2d583a09626ac..a97c369fcb1bf 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -4932,8 +4932,10 @@ components: example: my description namespace: type: string - description: namespace by default "default" - example: default + description: >- + The package policy namespace. Leave blank to inherit the agent + policy's namespace. + example: customnamespace policy_id: type: string description: Agent policy ID where that package policy will be added diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/package_policy_request.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/package_policy_request.yaml index 4f4f5489d82a2..dd96b2961e20f 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/package_policy_request.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/package_policy_request.yaml @@ -14,8 +14,8 @@ properties: example: 'my description' namespace: type: string - description: namespace by default "default" - example: 'default' + description: The package policy namespace. Leave blank to inherit the agent policy's namespace. + example: 'customnamespace' policy_id: type: string description: Agent policy ID where that package policy will be added diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/update_package_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/update_package_policy.yaml index 71e898017d06a..795d92ff1d4a7 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/update_package_policy.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/update_package_policy.yaml @@ -59,6 +59,5 @@ properties: type: boolean required: - name - - namespace - policy_id - enabled diff --git a/x-pack/plugins/fleet/common/services/is_valid_namespace.ts b/x-pack/plugins/fleet/common/services/is_valid_namespace.ts index 359f8069c2046..49332c4a88156 100644 --- a/x-pack/plugins/fleet/common/services/is_valid_namespace.ts +++ b/x-pack/plugins/fleet/common/services/is_valid_namespace.ts @@ -10,8 +10,11 @@ import { i18n } from '@kbn/i18n'; // Namespace string eventually becomes part of an index name. This method partially implements index name rules from // https://github.com/elastic/elasticsearch/blob/master/docs/reference/indices/create-index.asciidoc // and implements a limit based on https://github.com/elastic/kibana/issues/75846 -export function isValidNamespace(namespace: string): { valid: boolean; error?: string } { - if (!namespace.trim()) { +export function isValidNamespace( + namespace: string, + allowBlankNamespace?: boolean +): { valid: boolean; error?: string } { + if (!namespace.trim() && !allowBlankNamespace) { return { valid: false, error: i18n.translate('xpack.fleet.namespaceValidation.requiredErrorMessage', { diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.ts index 15aa58d547a84..8cbaf56b546db 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.ts @@ -63,8 +63,6 @@ export const validatePackagePolicy = ( inputs: {}, vars: {}, }; - const namespaceValidation = isValidNamespace(packagePolicy.namespace); - if (!packagePolicy.name.trim()) { validationResults.name = [ i18n.translate('xpack.fleet.packagePolicyValidation.nameRequiredErrorMessage', { @@ -73,8 +71,11 @@ export const validatePackagePolicy = ( ]; } - if (!namespaceValidation.valid && namespaceValidation.error) { - validationResults.namespace = [namespaceValidation.error]; + if (packagePolicy?.namespace) { + const namespaceValidation = isValidNamespace(packagePolicy?.namespace, true); + if (!namespaceValidation.valid && namespaceValidation.error) { + validationResults.namespace = [namespaceValidation.error]; + } } // Validate package-level vars diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts index 63c55952f6257..5b7450305c3ac 100644 --- a/x-pack/plugins/fleet/common/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts @@ -74,7 +74,7 @@ export interface NewPackagePolicy { id?: string | number; name: string; description?: string; - namespace: string; + namespace?: string; enabled: boolean; is_managed?: boolean; policy_id: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx index 3e5a385db87fb..289508b516fcc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx @@ -284,6 +284,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ > = (props const [packagePolicy, setPackagePolicy] = useState({ name: '', description: '', - namespace: 'default', + namespace: '', policy_id: '', enabled: true, inputs: [], diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/hooks/send_generate_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/hooks/send_generate_package_policy.tsx index f4cf4e3a59103..f332426ce72fb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/hooks/send_generate_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/hooks/send_generate_package_policy.tsx @@ -29,7 +29,7 @@ export const sendGeneratePackagePolicy = async ( const defaultPolicy: NewPackagePolicy = { name: incrementedName, description: '', - namespace: 'default', + namespace: '', policy_id: agentPolicyId, enabled: true, inputs: [], diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.test.tsx index 3128bec1a8bbf..aef418603ff64 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.test.tsx @@ -10,7 +10,7 @@ import type { RenderHookResult } from '@testing-library/react-hooks'; import type { TestRenderer } from '../../../../../../../mock'; import { createFleetTestRendererMock } from '../../../../../../../mock'; -import type { AgentPolicy, PackageInfo } from '../../../../../types'; +import type { PackageInfo } from '../../../../../types'; import { sendGetPackagePolicies } from '../../../../../hooks'; @@ -78,7 +78,7 @@ describe('useOnSubmit', () => { packageInfo, withSysMonitoring: false, selectedPolicyTab: SelectedPolicyTab.NEW, - newAgentPolicy: { name: 'test', namespace: 'default' }, + newAgentPolicy: { name: 'test', namespace: '' }, queryParamsPolicyId: undefined, }) )); @@ -94,21 +94,23 @@ describe('useOnSubmit', () => { }); }); - it('should set package policy id and namespace when agent policy changes', () => { + it('should set new values when package policy changes', () => { act(() => { - renderResult.result.current.updateAgentPolicy({ - id: 'some-id', - namespace: 'default', - } as AgentPolicy); + renderResult.result.current.updatePackagePolicy({ + id: 'new-id', + namespace: 'newspace', + name: 'apache-2', + }); }); expect(renderResult.result.current.packagePolicy).toEqual({ - policy_id: 'some-id', - namespace: 'default', + id: 'new-id', + policy_id: '', + namespace: 'newspace', description: '', enabled: true, inputs: [], - name: 'apache-1', + name: 'apache-2', package: { name: 'apache', title: 'Apache', @@ -142,7 +144,7 @@ describe('useOnSubmit', () => { enabled: true, inputs: [], name: 'apache-1', - namespace: 'default', + namespace: '', policy_id: '', package: { name: 'apache', @@ -187,7 +189,7 @@ describe('useOnSubmit', () => { enabled: true, inputs: [], name: 'apache-11', - namespace: 'default', + namespace: '', policy_id: '', package: { name: 'apache', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx index d81e754a69977..178c834e9256f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -86,7 +86,7 @@ async function savePackagePolicy(pkgPolicy: CreatePackagePolicyRequest['body']) const DEFAULT_PACKAGE_POLICY = { name: '', description: '', - namespace: 'default', + namespace: '', policy_id: '', enabled: true, inputs: [], @@ -221,7 +221,7 @@ export function useOnSubmit({ packageToPackagePolicy( packageInfo, agentPolicy?.id || '', - agentPolicy?.namespace || DEFAULT_PACKAGE_POLICY.namespace, + '', DEFAULT_PACKAGE_POLICY.name || incrementedName, DEFAULT_PACKAGE_POLICY.description, integrationToEnable @@ -236,7 +236,6 @@ export function useOnSubmit({ if (agentPolicy && packagePolicy.policy_id !== agentPolicy.id) { updatePackagePolicy({ policy_id: agentPolicy.id, - namespace: agentPolicy.namespace, }); } }, [packagePolicy, agentPolicy, updatePackagePolicy]); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx index 27c3c8c0cc37d..2fceec2e08333 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx @@ -269,7 +269,7 @@ describe('when on the package policy create page', () => { }, ], name: 'nginx-1', - namespace: 'default', + namespace: '', package: { name: 'nginx', title: 'Nginx', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx index 934fee64b4043..e5b4f468520d9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx @@ -19,6 +19,7 @@ import { EuiIcon, EuiToolTip, EuiLink, + EuiIconTip, } from '@elastic/eui'; import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../common'; @@ -79,7 +80,6 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ if (packagePolicy.namespace) { namespacesValues.add(packagePolicy.namespace); } - const hasUpgrade = isPackagePolicyUpgradable(packagePolicy); return { @@ -95,7 +95,6 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ const namespaceFilterOptions = [...namespacesValues] .sort(stringSortAscending) .map(toFilterOption); - return [mappedPackagePolicies, namespaceFilterOptions]; }, [originalPackagePolicies, isPackagePolicyUpgradable]); @@ -225,7 +224,19 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ } ), render: (namespace: InMemoryPackagePolicy['namespace']) => { - return namespace ? {namespace} : ''; + return namespace ? ( + {namespace} + ) : ( + <> + {agentPolicy.namespace} + + + ); }, }, { diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index a16fd37c9ac15..e402cfc2d2464 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -371,7 +371,7 @@ export const updatePackagePolicyHandler: FleetRequestHandler< ...body, name: body.name ?? packagePolicy.name, description: body.description ?? packagePolicy.description, - namespace: body.namespace ?? packagePolicy.namespace, + namespace: body.namespace ?? packagePolicy?.namespace, policy_id: body.policy_id ?? packagePolicy.policy_id, enabled: 'enabled' in body ? body.enabled ?? packagePolicy.enabled : packagePolicy.enabled, package: pkg ?? packagePolicy.package, diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index 3250c55d5f6e7..53d181a0ce8c6 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -7,12 +7,15 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import type { AgentPolicy, Output, DownloadSource } from '../../types'; +import omit from 'lodash/omit'; + +import type { AgentPolicy, Output, DownloadSource, PackageInfo } from '../../types'; import { createAppContextStartContractMock } from '../../mocks'; import { agentPolicyService } from '../agent_policy'; import { agentPolicyUpdateEventHandler } from '../agent_policy_update'; import { appContextService } from '../app_context'; +import { getPackageInfo } from '../epm/packages'; import { generateFleetConfig, @@ -21,12 +24,15 @@ import { } from './full_agent_policy'; import { getMonitoringPermissions } from './monitoring_permissions'; +jest.mock('../epm/packages'); + const mockedGetElasticAgentMonitoringPermissions = getMonitoringPermissions as jest.Mock< ReturnType >; const mockedAgentPolicyService = agentPolicyService as jest.Mocked; const soClientMock = savedObjectsClientMock.create(); +const mockedGetPackageInfo = getPackageInfo as jest.Mock>; function mockAgentPolicy(data: Partial) { mockedAgentPolicyService.get.mockResolvedValue({ @@ -467,6 +473,221 @@ describe('getFullAgentPolicy', () => { signature: 'thisisasignature', }); }); + + it('should compile full policy with correct namespaces', async () => { + mockedGetPackageInfo.mockResolvedValue({ + data_streams: [ + { + type: 'logs', + dataset: 'elastic_agent.metricbeat', + }, + { + type: 'metrics', + dataset: 'elastic_agent.metricbeat', + }, + { + type: 'logs', + dataset: 'elastic_agent.filebeat', + }, + { + type: 'metrics', + dataset: 'elastic_agent.filebeat', + }, + ], + } as PackageInfo); + mockAgentPolicy({ + id: 'agent-policy', + status: 'active', + package_policies: [ + { + id: 'package-policy-uuid-test-123', + name: 'test-policy-1', + namespace: 'policyspace', + enabled: true, + package: { name: 'test_package', version: '0.0.0', title: 'Test Package' }, + inputs: [ + { + type: 'test-logs', + enabled: true, + streams: [ + { + id: 'test-logs', + enabled: true, + data_stream: { type: 'logs', dataset: 'some-logs' }, + }, + ], + }, + { + type: 'test-metrics', + enabled: false, + streams: [ + { + id: 'test-logs', + enabled: false, + data_stream: { type: 'metrics', dataset: 'some-metrics' }, + }, + ], + }, + ], + created_at: '', + updated_at: '', + created_by: '', + updated_by: '', + revision: 1, + policy_id: '', + }, + { + id: 'package-policy-uuid-test-123', + name: 'test-policy-2', + namespace: '', + enabled: true, + package: { name: 'test_package', version: '0.0.0', title: 'Test Package' }, + inputs: [ + { + type: 'test-logs', + enabled: true, + streams: [ + { + id: 'test-logs', + enabled: true, + data_stream: { type: 'logs', dataset: 'some-logs' }, + }, + ], + }, + { + type: 'test-metrics', + enabled: false, + streams: [ + { + id: 'test-logs', + enabled: false, + data_stream: { type: 'metrics', dataset: 'some-metrics' }, + }, + ], + }, + ], + created_at: '', + updated_at: '', + created_by: '', + updated_by: '', + revision: 1, + policy_id: '', + }, + ], + is_managed: false, + namespace: 'defaultspace', + revision: 1, + name: 'Policy', + updated_at: '2020-01-01', + updated_by: 'qwerty', + is_protected: false, + }); + + const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(omit(agentPolicy, 'signed', 'secret_references', 'agent.protection')).toEqual({ + agent: { + download: { + sourceURI: 'http://default-registry.co', + }, + features: {}, + monitoring: { + enabled: false, + logs: false, + metrics: false, + }, + }, + fleet: { + hosts: ['http://fleetserver:8220'], + }, + id: 'agent-policy', + inputs: [ + { + data_stream: { + namespace: 'policyspace', + }, + id: 'test-logs-package-policy-uuid-test-123', + meta: { + package: { + name: 'test_package', + version: '0.0.0', + }, + }, + name: 'test-policy-1', + package_policy_id: 'package-policy-uuid-test-123', + revision: 1, + streams: [ + { + data_stream: { + dataset: 'some-logs', + type: 'logs', + }, + id: 'test-logs', + }, + ], + type: 'test-logs', + use_output: 'default', + }, + { + data_stream: { + namespace: 'defaultspace', + }, + id: 'test-logs-package-policy-uuid-test-123', + meta: { + package: { + name: 'test_package', + version: '0.0.0', + }, + }, + name: 'test-policy-2', + package_policy_id: 'package-policy-uuid-test-123', + revision: 1, + streams: [ + { + data_stream: { + dataset: 'some-logs', + type: 'logs', + }, + id: 'test-logs', + }, + ], + type: 'test-logs', + use_output: 'default', + }, + ], + output_permissions: { + default: { + _elastic_agent_checks: { + cluster: ['monitor'], + }, + _elastic_agent_monitoring: { + indices: [ + { + names: [], + privileges: [], + }, + ], + }, + 'package-policy-uuid-test-123': { + indices: [ + { + names: ['logs-some-logs-defaultspace'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }, + }, + }, + outputs: { + default: { + hosts: ['http://127.0.0.1:9201'], + preset: 'balanced', + type: 'elasticsearch', + }, + }, + revision: 1, + }); + }); }); describe('transformOutputToFullPolicyOutput', () => { diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index 154614d2aa297..c1dde1443e5a4 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -112,7 +112,8 @@ export async function getFullAgentPolicy( const inputs = await storedPackagePoliciesToAgentInputs( agentPolicy.package_policies as PackagePolicy[], packageInfoCache, - getOutputIdForAgentPolicy(dataOutput) + getOutputIdForAgentPolicy(dataOutput), + agentPolicy.namespace ); const features = (agentPolicy.agent_features || []).reduce((acc, { name, ...featureConfig }) => { acc[name] = featureConfig; @@ -189,6 +190,7 @@ export async function getFullAgentPolicy( const dataPermissions = (await storedPackagePoliciesToAgentPermissions( packageInfoCache, + agentPolicy.namespace, agentPolicy.package_policies )) || {}; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.test.ts index 67d08cd5e6b2a..79b2543299d23 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.test.ts @@ -470,4 +470,82 @@ describe('Fleet - storedPackagePoliciesToAgentInputs', () => { }, ]); }); + it('returns agent inputs using package policy namespace if defined', async () => { + expect( + await storedPackagePoliciesToAgentInputs( + [ + { + ...mockPackagePolicy, + inputs: [ + { + ...mockInput, + streams: [{ ...mockInput.streams[0] }, { ...mockInput.streams[1], enabled: false }], + }, + ], + namespace: 'packagepolicyspace', + }, + ], + packageInfoCache, + 'default', + 'agentpolicyspace' + ) + ).toEqual([ + { + id: 'test-logs-some-uuid', + name: 'mock_package-policy', + package_policy_id: 'some-uuid', + revision: 1, + type: 'test-logs', + data_stream: { namespace: 'packagepolicyspace' }, + use_output: 'default', + streams: [ + { + id: 'test-logs-foo', + data_stream: { dataset: 'foo', type: 'logs' }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, + ], + }, + ]); + }); + it('returns agent inputs using agent policy namespace if package policy namespace is blank', async () => { + expect( + await storedPackagePoliciesToAgentInputs( + [ + { + ...mockPackagePolicy, + inputs: [ + { + ...mockInput, + streams: [{ ...mockInput.streams[0] }, { ...mockInput.streams[1], enabled: false }], + }, + ], + namespace: '', + }, + ], + packageInfoCache, + 'default', + 'agentpolicyspace' + ) + ).toEqual([ + { + id: 'test-logs-some-uuid', + name: 'mock_package-policy', + package_policy_id: 'some-uuid', + revision: 1, + type: 'test-logs', + data_stream: { namespace: 'agentpolicyspace' }, + use_output: 'default', + streams: [ + { + id: 'test-logs-foo', + data_stream: { dataset: 'foo', type: 'logs' }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, + ], + }, + ]); + }); }); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.ts index e1de799873f64..938677c7c8bb9 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.ts @@ -23,7 +23,8 @@ const isPolicyEnabled = (packagePolicy: PackagePolicy) => { export const storedPackagePolicyToAgentInputs = ( packagePolicy: PackagePolicy, packageInfo?: PackageInfo, - outputId: string = DEFAULT_OUTPUT.name + outputId: string = DEFAULT_OUTPUT.name, + agentPolicyNamespace?: string ): FullAgentPolicyInput[] => { const fullInputs: FullAgentPolicyInput[] = []; @@ -52,7 +53,7 @@ export const storedPackagePolicyToAgentInputs = ( name: packagePolicy.name, type: input.type, data_stream: { - namespace: packagePolicy.namespace || 'default', + namespace: packagePolicy?.namespace || agentPolicyNamespace || 'default', // custom namespace has precedence on agent policy's one }, use_output: outputId, package_policy_id: packagePolicy.id, @@ -97,14 +98,14 @@ export const storedPackagePolicyToAgentInputs = ( fullInputs.push(fullInput); }); - return fullInputs; }; export const storedPackagePoliciesToAgentInputs = async ( packagePolicies: PackagePolicy[], packageInfoCache: Map, - outputId: string = DEFAULT_OUTPUT.name + outputId: string = DEFAULT_OUTPUT.name, + agentPolicyNamespace?: string ): Promise => { const fullInputs: FullAgentPolicyInput[] = []; @@ -117,7 +118,14 @@ export const storedPackagePoliciesToAgentInputs = async ( ? packageInfoCache.get(pkgToPkgKey(packagePolicy.package)) : undefined; - fullInputs.push(...storedPackagePolicyToAgentInputs(packagePolicy, packageInfo, outputId)); + fullInputs.push( + ...storedPackagePolicyToAgentInputs( + packagePolicy, + packageInfo, + outputId, + agentPolicyNamespace + ) + ); } return fullInputs; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts index e4f2b30bc4a9e..f68343e98ffb1 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.test.ts @@ -101,7 +101,7 @@ packageInfoCache.set('osquery_manager-0.3.0', { { dataset: 'osquery_manager.result', package: 'osquery_manager', - ingest_pipeline: 'default', + ingest_pipeline: 'test', path: 'result', streams: [], title: 'Osquery Manager queries', @@ -283,19 +283,19 @@ packageInfoCache.set('apm-8.9.0-preview', { describe('storedPackagePoliciesToAgentPermissions()', () => { it('Returns `undefined` if there are no package policies', async () => { - const permissions = await storedPackagePoliciesToAgentPermissions(packageInfoCache, []); + const permissions = await storedPackagePoliciesToAgentPermissions(packageInfoCache, 'test', []); expect(permissions).toBeUndefined(); }); it('Throw an error if package policies is not an array', () => { - expect(() => storedPackagePoliciesToAgentPermissions(packageInfoCache, undefined)).toThrow( - /storedPackagePoliciesToAgentPermissions should be called with a PackagePolicy/ - ); + expect(() => + storedPackagePoliciesToAgentPermissions(packageInfoCache, 'test', undefined) + ).toThrow(/storedPackagePoliciesToAgentPermissions should be called with a PackagePolicy/); }); it('Returns the default permissions if a package policy does not have a package', () => { expect(() => - storedPackagePoliciesToAgentPermissions(packageInfoCache, [ + storedPackagePoliciesToAgentPermissions(packageInfoCache, 'test', [ { name: 'foo', package: undefined } as PackagePolicy, ]) ).toThrow(/No package for package policy foo/); @@ -344,6 +344,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { const permissions = await storedPackagePoliciesToAgentPermissions( packageInfoCache, + 'test', packagePolicies ); expect(permissions).toMatchObject({ @@ -391,6 +392,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { const permissions = await storedPackagePoliciesToAgentPermissions( packageInfoCache, + 'test', packagePolicies ); expect(permissions).toMatchObject({ @@ -443,6 +445,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { const permissions = await storedPackagePoliciesToAgentPermissions( packageInfoCache, + 'test', packagePolicies ); expect(permissions).toMatchObject({ @@ -491,6 +494,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { const permissions = await storedPackagePoliciesToAgentPermissions( packageInfoCache, + 'test', packagePolicies ); expect(permissions).toMatchObject({ @@ -531,6 +535,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { const permissions = await storedPackagePoliciesToAgentPermissions( packageInfoCache, + 'test', packagePolicies ); @@ -571,6 +576,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { const permissions = await storedPackagePoliciesToAgentPermissions( packageInfoCache, + 'test', packagePolicies ); @@ -612,6 +618,7 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { const permissions = await storedPackagePoliciesToAgentPermissions( packageInfoCache, + 'test', packagePolicies ); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts index ec36c7575937e..a5fc2ba25de47 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_permissions.ts @@ -43,6 +43,7 @@ export const UNIVERSAL_PROFILING_PERMISSIONS = [ export function storedPackagePoliciesToAgentPermissions( packageInfoCache: Map, + agentPolicyNamespace: string, packagePolicies?: PackagePolicy[] ): FullAgentPolicyOutputPermissions | undefined { // I'm not sure what permissions to return for this case, so let's return the defaults @@ -155,13 +156,12 @@ export function storedPackagePoliciesToAgentPermissions( cluster, }; } - + // namespace is either the package policy's or the agent policy one + const namespace = packagePolicy?.namespace || agentPolicyNamespace; return [ packagePolicy.id, { - indices: dataStreamsForPermissions.map((ds) => - getDataStreamPrivileges(ds, packagePolicy.namespace) - ), + indices: dataStreamsForPermissions.map((ds) => getDataStreamPrivileges(ds, namespace)), ...clusterRoleDescriptor, }, ]; 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 ada094ec9a7d0..24483be93a9f5 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -4694,7 +4694,7 @@ describe('Package policy service', () => { ); expect(result).toEqual({ name: 'apache-1', - namespace: 'default', + namespace: '', description: '', package: { name: 'apache', title: 'Apache', version: '1.0.0' }, enabled: true, @@ -4770,7 +4770,7 @@ describe('Package policy service', () => { ); expect(result).toEqual({ name: 'aws-1', - namespace: 'default', + namespace: '', description: '', package: { name: 'aws', title: 'AWS', version: '1.0.0' }, enabled: true, diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 3f43aaa2d71f2..98e3f9247bf36 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -1661,7 +1661,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { newPackagePolicy = { ...newPP, name: newPolicy.name, - namespace: newPolicy.namespace ?? 'default', + namespace: newPolicy?.namespace ?? '', description: newPolicy.description ?? '', enabled: newPolicy.enabled ?? true, package: { diff --git a/x-pack/plugins/fleet/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts index 20e36e2637cf7..8fbad6d90fdaa 100644 --- a/x-pack/plugins/fleet/server/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/agent_policy.ts @@ -8,8 +8,19 @@ import { schema } from '@kbn/config-schema'; import { agentPolicyStatuses, dataTypes } from '../../../common/constants'; +import { isValidNamespace } from '../../../common/services'; -import { PackagePolicySchema, NamespaceSchema } from './package_policy'; +import { PackagePolicySchema } from './package_policy'; + +export const AgentPolicyNamespaceSchema = schema.string({ + minLength: 1, + validate: (value) => { + const namespaceValidation = isValidNamespace(value || ''); + if (!namespaceValidation.valid && namespaceValidation.error) { + return namespaceValidation.error; + } + }, +}); function validateNonEmptyString(val: string) { if (val.trim() === '') { @@ -28,7 +39,7 @@ function isInteger(n: number) { export const AgentPolicyBaseSchema = { id: schema.maybe(schema.string()), name: schema.string({ minLength: 1, validate: validateNonEmptyString }), - namespace: NamespaceSchema, + namespace: AgentPolicyNamespaceSchema, description: schema.maybe(schema.string()), is_managed: schema.maybe(schema.boolean()), has_fleet_server: schema.maybe(schema.boolean()), diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts index 6227f84555689..7153982498c29 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -9,10 +9,9 @@ import { schema } from '@kbn/config-schema'; import { isValidNamespace } from '../../../common/services'; -export const NamespaceSchema = schema.string({ - minLength: 1, +export const PackagePolicyNamespaceSchema = schema.string({ validate: (value) => { - const namespaceValidation = isValidNamespace(value || ''); + const namespaceValidation = isValidNamespace(value || '', true); if (!namespaceValidation.valid && namespaceValidation.error) { return namespaceValidation.error; } @@ -96,7 +95,7 @@ const ExperimentalDataStreamFeatures = schema.arrayOf( const PackagePolicyBaseSchema = { name: schema.string(), description: schema.maybe(schema.string()), - namespace: NamespaceSchema, + namespace: schema.maybe(PackagePolicyNamespaceSchema), policy_id: schema.string(), enabled: schema.boolean(), is_managed: schema.maybe(schema.boolean()), @@ -122,7 +121,6 @@ export const NewPackagePolicySchema = schema.object({ const CreatePackagePolicyProps = { ...PackagePolicyBaseSchema, - namespace: schema.maybe(NamespaceSchema), policy_id: schema.maybe(schema.string()), enabled: schema.maybe(schema.boolean()), package: schema.maybe( @@ -167,7 +165,7 @@ export const SimplifiedCreatePackagePolicyRequestBodySchema = schema.object({ name: schema.string(), description: schema.maybe(schema.string()), policy_id: schema.string(), - namespace: schema.string({ defaultValue: 'default' }), + namespace: schema.maybe(schema.string()), package: schema.object({ name: schema.string(), version: schema.string(), diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index 2928db35587d1..7572da134bd99 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -18,8 +18,8 @@ import { RemoteElasticSearchSchema, } from './output'; -import { AgentPolicyBaseSchema } from './agent_policy'; -import { NamespaceSchema } from './package_policy'; +import { AgentPolicyBaseSchema, AgentPolicyNamespaceSchema } from './agent_policy'; +import { PackagePolicyNamespaceSchema } from './package_policy'; const varsSchema = schema.maybe( schema.arrayOf( @@ -130,7 +130,7 @@ export const PreconfiguredFleetProxiesSchema = schema.arrayOf( export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( schema.object({ ...AgentPolicyBaseSchema, - namespace: schema.maybe(NamespaceSchema), + namespace: schema.maybe(AgentPolicyNamespaceSchema), id: schema.maybe(schema.oneOf([schema.string(), schema.number()])), is_default: schema.maybe(schema.boolean()), is_default_fleet_server: schema.maybe(schema.boolean()), @@ -154,7 +154,7 @@ export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( }), }), description: schema.maybe(schema.string()), - namespace: schema.maybe(NamespaceSchema), + namespace: schema.maybe(PackagePolicyNamespaceSchema), inputs: schema.maybe( schema.arrayOf( schema.object({ diff --git a/x-pack/plugins/fleet/server/types/so_attributes.ts b/x-pack/plugins/fleet/server/types/so_attributes.ts index ea637d401bb12..1d6601e08e38f 100644 --- a/x-pack/plugins/fleet/server/types/so_attributes.ts +++ b/x-pack/plugins/fleet/server/types/so_attributes.ts @@ -109,7 +109,7 @@ export interface FleetServerHostSOAttributes { export interface PackagePolicySOAttributes { name: string; - namespace: string; + namespace?: string; enabled: boolean; revision: number; created_at: string; diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts index 058202f813cba..9223c7a2118d3 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts @@ -150,12 +150,12 @@ export default function (providerContext: FtrProviderContext) { expect(body.tags.find((tag: any) => tag.name === 'For File Tests').relationCount).to.be(9); }); - it('should return a 400 with an empty namespace', async function () { + it('should allow to pass an empty namespace', async function () { await supertest .post(`/api/fleet/package_policies`) .set('kbn-xsrf', 'xxxx') .send({ - name: 'filetest-1', + name: 'filetest-5', description: '', namespace: '', policy_id: agentPolicyId, @@ -167,7 +167,7 @@ export default function (providerContext: FtrProviderContext) { version: '0.1.0', }, }) - .expect(400); + .expect(200); }); it('should return a 400 with an invalid namespace', async function () { @@ -175,7 +175,7 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/package_policies`) .set('kbn-xsrf', 'xxxx') .send({ - name: 'filetest-1', + name: 'filetest-2', description: '', namespace: 'InvalidNamespace', policy_id: agentPolicyId,