From ed71baf7782e5445c96db57e906a51d5cee26e24 Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Wed, 11 Jan 2023 18:45:56 +0000 Subject: [PATCH] [Fleet] Do not allow namespace or dataset to be edited for input only package policies (#148737) ## Summary Part of #145529. Input packages will create component and index templates on package policy creation. These changes make it so that to change the namespace or dataset of an input only package the user must create a new package policy, this is because by changing these the user is sending the data to a new destination which semantically is a different policy. Screenshot 2023-01-04 at 21 05 15 Co-authored-by: Kyle Pollich --- .../fleet/common/services/policy_template.ts | 2 +- .../steps/components/dataset_combo.tsx | 5 +- .../package_policy_input_config.tsx | 4 + .../components/package_policy_input_panel.tsx | 5 +- .../package_policy_input_stream.tsx | 4 + .../package_policy_input_var_field.tsx | 26 +- .../steps/step_configure_package.tsx | 3 + .../steps/step_define_package_policy.test.tsx | 5 +- .../steps/step_define_package_policy.tsx | 49 ++-- .../single_page_layout/index.tsx | 1 - .../edit_package_policy_page/index.tsx | 3 +- .../server/services/package_policy.test.ts | 128 +++++++++ .../fleet/server/services/package_policy.ts | 52 +++- .../1.0.0/agent/input/input.yml.hbs | 18 ++ .../input_package/1.0.0/changelog.yml | 6 + .../input_package/1.0.0/docs/README.md | 1 + .../input_package/1.0.0/fields/input.yml | 4 + .../input_package/1.0.0/manifest.yml | 37 +++ .../apis/package_policy/update.ts | 260 +++++++++++------- 19 files changed, 482 insertions(+), 131 deletions(-) create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/agent/input/input.yml.hbs create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/changelog.yml create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/docs/README.md create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/fields/input.yml create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/manifest.yml diff --git a/x-pack/plugins/fleet/common/services/policy_template.ts b/x-pack/plugins/fleet/common/services/policy_template.ts index 1d523af8b05ec..912ec96f6091f 100644 --- a/x-pack/plugins/fleet/common/services/policy_template.ts +++ b/x-pack/plugins/fleet/common/services/policy_template.ts @@ -20,7 +20,7 @@ const DATA_STREAM_DATASET_VAR: RegistryVarsEntry = { type: 'text', title: 'Dataset name', description: - "Set the name for your dataset. Changing the dataset will send the data to a different index. You can't use `-` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html).\n", + "Set the name for your dataset. Once selected a dataset cannot be changed without creating a new integration policy. You can't use `-` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html) are permitted.\n", multi: false, required: true, show_user: true, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx index 4588ebc4912b3..15e1a18f26dcd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx @@ -13,7 +13,8 @@ export const DatasetComboBox: React.FC<{ value: any; onChange: (newValue: any) => void; datasets: string[]; -}> = ({ value, onChange, datasets }) => { + isDisabled?: boolean; +}> = ({ value, onChange, datasets, isDisabled }) => { const datasetOptions = datasets.map((dataset: string) => ({ label: dataset })) ?? []; const defaultOption = 'generic'; const [selectedOptions, setSelectedOptions] = useState>([ @@ -42,7 +43,6 @@ export const DatasetComboBox: React.FC<{ setSelectedOptions([newOption]); onChange(searchValue); }; - return ( ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx index dff5d719dc36d..fd4a3a752eacd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx @@ -29,6 +29,7 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ updatePackagePolicyInput: (updatedInput: Partial) => void; inputVarsValidationResults: PackagePolicyConfigValidationResults; forceShowErrors?: boolean; + isEditPage?: boolean; }> = memo( ({ hasInputStreams, @@ -37,6 +38,7 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ updatePackagePolicyInput, inputVarsValidationResults, forceShowErrors, + isEditPage = false, }) => { // Showing advanced options toggle state const [isShowingAdvanced, setIsShowingAdvanced] = useState(false); @@ -121,6 +123,7 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ }} errors={inputVarsValidationResults.vars?.[varName]} forceShowErrors={forceShowErrors} + isEditPage={isEditPage} /> ); @@ -178,6 +181,7 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ }} errors={inputVarsValidationResults.vars?.[varName]} forceShowErrors={forceShowErrors} + isEditPage={isEditPage} /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx index ccb6a6f47e1c8..68f26a3cf6121 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx @@ -80,6 +80,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ updatePackagePolicyInput: (updatedInput: Partial) => void; inputValidationResults: PackagePolicyInputValidationResults; forceShowErrors?: boolean; + isEditPage?: boolean; }> = memo( ({ packageInput, @@ -91,6 +92,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ updatePackagePolicyInput, inputValidationResults, forceShowErrors, + isEditPage = false, }) => { const defaultDataStreamId = useDataStreamId(); // Showing streams toggle state @@ -213,7 +215,6 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ {/* Header rule break */} {isShowingStreams ? : null} - {/* Input level policy */} {isShowingStreams && packageInput.vars && packageInput.vars.length ? ( @@ -224,6 +225,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ updatePackagePolicyInput={updatePackagePolicyInput} inputVarsValidationResults={{ vars: inputValidationResults?.vars }} forceShowErrors={forceShowErrors} + isEditPage={isEditPage} /> {hasInputStreams ? : } @@ -273,6 +275,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ inputValidationResults?.streams![packagePolicyInputStream!.data_stream!.dataset] } forceShowErrors={forceShowErrors} + isEditPage={isEditPage} /> {index !== inputStreams.length - 1 ? ( <> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx index 2191b151414ba..814d1b47783f3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx @@ -58,6 +58,7 @@ interface Props { updatePackagePolicyInputStream: (updatedStream: Partial) => void; inputStreamValidationResults: PackagePolicyConfigValidationResults; forceShowErrors?: boolean; + isEditPage?: boolean; } export const PackagePolicyInputStreamConfig = memo( @@ -70,6 +71,7 @@ export const PackagePolicyInputStreamConfig = memo( updatePackagePolicyInputStream, inputStreamValidationResults, forceShowErrors, + isEditPage, }) => { const config = useConfig(); const isExperimentalDataStreamSettingsEnabled = @@ -226,6 +228,7 @@ export const PackagePolicyInputStreamConfig = memo( forceShowErrors={forceShowErrors} packageType={packageInfo.type} datasets={datasets} + isEditPage={isEditPage} /> ); @@ -287,6 +290,7 @@ export const PackagePolicyInputStreamConfig = memo( forceShowErrors={forceShowErrors} packageType={packageInfo.type} datasets={datasets} + isEditPage={isEditPage} /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx index e314fb2c79ca6..48b67d3078336 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx @@ -40,6 +40,7 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{ frozen?: boolean; packageType?: string; datasets?: string[]; + isEditPage?: boolean; }> = memo( ({ varDef, @@ -50,6 +51,7 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{ frozen, packageType, datasets = [], + isEditPage = false, }) => { const [isDirty, setIsDirty] = useState(false); const { multi, required, type, title, name, description } = varDef; @@ -68,9 +70,15 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{ /> ); } - if (name === 'data_stream.dataset' && packageType === 'input') { - return ; + return ( + + ); } switch (type) { case 'textarea': @@ -152,7 +160,19 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{ /> ); } - }, [isInvalid, multi, onChange, type, value, fieldLabel, frozen, datasets, name, packageType]); + }, [ + multi, + name, + packageType, + type, + value, + onChange, + frozen, + datasets, + isEditPage, + isInvalid, + fieldLabel, + ]); // Boolean cannot be optional by default set to false const isOptional = useMemo(() => type !== 'bool' && !required, [required, type]); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx index 01968fd601996..edcea931b5535 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx @@ -37,6 +37,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ validationResults: PackagePolicyValidationResults; submitAttempted: boolean; noTopRule?: boolean; + isEditPage?: boolean; }> = ({ packageInfo, showOnlyIntegration, @@ -45,6 +46,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ validationResults, submitAttempted, noTopRule = false, + isEditPage = false, }) => { const hasIntegrations = useMemo(() => doesPackageHaveIntegrations(packageInfo), [packageInfo]); const packagePolicyTemplates = useMemo( @@ -109,6 +111,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ ] } forceShowErrors={submitAttempted} + isEditPage={isEditPage} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx index be9e7425ca617..e088e00d2f95a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.test.tsx @@ -92,7 +92,7 @@ describe('StepDefinePackagePolicy', () => { let testRenderer: TestRenderer; let renderResult: ReturnType; - const render = ({ isUpdate } = { isUpdate: false }) => + const render = () => (renderResult = testRenderer.render( { updatePackagePolicy={mockUpdatePackagePolicy} validationResults={validationResults} submitAttempted={false} - isUpdate={isUpdate} /> )); @@ -165,7 +164,7 @@ describe('StepDefinePackagePolicy', () => { describe('update', () => { describe('when package vars are introduced in a new package version', () => { it('should display new package vars', () => { - render({ isUpdate: true }); + render(); waitFor(async () => { expect(renderResult.getByDisplayValue('showUserVarVal')).toBeInTheDocument(); 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 186dea6c3c3da..07cebf31baebc 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 @@ -50,23 +50,21 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ agentPolicy?: AgentPolicy; packageInfo: PackageInfo; packagePolicy: NewPackagePolicy; - integrationToEnable?: string; updatePackagePolicy: (fields: Partial) => void; validationResults: PackagePolicyValidationResults; submitAttempted: boolean; - isUpdate?: boolean; + isEditPage?: boolean; noAdvancedToggle?: boolean; }> = memo( ({ agentPolicy, packageInfo, packagePolicy, - integrationToEnable, - isUpdate, updatePackagePolicy, validationResults, submitAttempted, noAdvancedToggle = false, + isEditPage = false, }) => { const { docLinks } = useStartServices(); @@ -251,7 +249,6 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ )} {/* Advanced options content */} - {/* Todo: Populate list of existing namespaces */} {isShowingAdvanced ? ( @@ -266,27 +263,35 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ /> } helpText={ - - {i18n.translate( - 'xpack.fleet.createPackagePolicy.stepConfigure.packagePolicyNamespaceHelpLearnMoreLabel', - { defaultMessage: 'Learn more' } - )} - - ), - }} - /> + isEditPage && packageInfo.type === 'input' ? ( + + ) : ( + + {i18n.translate( + 'xpack.fleet.createPackagePolicy.stepConfigure.packagePolicyNamespaceHelpLearnMoreLabel', + { defaultMessage: 'Learn more' } + )} + + ), + }} + /> + ) } > {/* Only show the out-of-box configuration step if a UI extension is NOT registered */} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index 07e0636f04640..afab7a48ebb76 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -286,7 +286,7 @@ export const EditPackagePolicyForm = memo<{ updatePackagePolicy={updatePackagePolicy} validationResults={validationResults!} submitAttempted={formState === 'INVALID'} - isUpdate={true} + isEditPage={true} /> )} @@ -298,6 +298,7 @@ export const EditPackagePolicyForm = memo<{ updatePackagePolicy={updatePackagePolicy} validationResults={validationResults!} submitAttempted={formState === 'INVALID'} + isEditPage={true} /> )} 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 faf7e41f15378..ae50ec3f785f3 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -55,6 +55,7 @@ import { packagePolicyService, _applyIndexPrivileges, _compilePackagePolicyInputs, + _validateRestrictedFieldsNotModifiedOrThrow, } from './package_policy'; import { appContextService } from './app_context'; @@ -4578,3 +4579,130 @@ describe('_applyIndexPrivileges()', () => { expect(streamOut).toEqual(expectedStream); }); }); + +describe('_validateRestrictedFieldsNotModifiedOrThrow()', () => { + const pkgInfo = { + name: 'custom_logs', + title: 'Custom Logs', + version: '1.0.0', + type: 'input', + } as any as PackageInfo; + + const createInputPkgPolicy = (opts: { namespace: string; dataset: string }) => { + const { namespace, dataset } = opts; + return { + id: 'id-1234', + version: 'WzI1MywxXQ==', + name: 'custom_logs-1', + namespace, + description: '', + enabled: true, + policy_id: '1234', + revision: 1, + created_at: '2023-01-04T14:51:53.061Z', + created_by: 'elastic', + updated_at: '2023-01-04T14:51:53.061Z', + updated_by: 'elastic', + vars: {}, + inputs: [ + { + type: 'logfile', + policy_template: 'logs', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + type: 'logs', + dataset: 'custom_logs.logs', + }, + vars: { + 'data_stream.dataset': { + type: 'text', + value: dataset, + }, + }, + id: 'logfile-custom_logs.logs-1', + }, + ], + }, + ], + package: { + name: 'custom_logs', + title: 'Custom Logs', + version: '1.0.0', + }, + }; + }; + it('should not throw if restricted fields are not modified', () => { + const oldPackagePolicy = createInputPkgPolicy({ + namespace: 'default', + dataset: 'custom_logs.logs', + }); + expect(() => + _validateRestrictedFieldsNotModifiedOrThrow({ + oldPackagePolicy, + packagePolicyUpdate: oldPackagePolicy, + pkgInfo, + }) + ).not.toThrow(); + }); + + it('should throw if namespace is modified', () => { + const oldPackagePolicy = createInputPkgPolicy({ + namespace: 'default', + dataset: 'custom_logs.logs', + }); + const newPackagePolicy = createInputPkgPolicy({ + namespace: 'new-namespace', + dataset: 'custom_logs.logs', + }); + expect(() => + _validateRestrictedFieldsNotModifiedOrThrow({ + oldPackagePolicy, + packagePolicyUpdate: newPackagePolicy, + pkgInfo, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Package policy namespace cannot be modified for input only packages, please create a new package policy."` + ); + }); + + it('should throw if dataset is modified', () => { + const oldPackagePolicy = createInputPkgPolicy({ + namespace: 'default', + dataset: 'custom_logs.logs', + }); + const newPackagePolicy = createInputPkgPolicy({ + namespace: 'default', + dataset: 'new-dataset', + }); + expect(() => + _validateRestrictedFieldsNotModifiedOrThrow({ + oldPackagePolicy, + packagePolicyUpdate: newPackagePolicy, + pkgInfo, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Package policy dataset cannot be modified for input only packages, please create a new package policy."` + ); + }); + + it('should not throw if dataset is modified but package is integration package', () => { + const oldPackagePolicy = createInputPkgPolicy({ + namespace: 'default', + dataset: 'custom_logs.logs', + }); + const newPackagePolicy = createInputPkgPolicy({ + namespace: 'default', + dataset: 'new-dataset', + }); + expect(() => + _validateRestrictedFieldsNotModifiedOrThrow({ + oldPackagePolicy, + packagePolicyUpdate: newPackagePolicy, + pkgInfo: { ...pkgInfo, type: 'integration' }, + }) + ).not.toThrow(); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 0c824211ab2d5..73fda5ac50aff 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -519,7 +519,11 @@ class PackagePolicyClientImpl implements PackagePolicyClient { pkgVersion: packagePolicy.package.version, prerelease: true, }); - + _validateRestrictedFieldsNotModifiedOrThrow({ + pkgInfo, + oldPackagePolicy, + packagePolicyUpdate, + }); validatePackagePolicyOrThrow(packagePolicy, pkgInfo); inputs = await _compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs); @@ -1950,6 +1954,52 @@ export function preconfigurePackageInputs( return resultingPackagePolicy; } +// input only packages cannot have their namespace or dataset modified +export function _validateRestrictedFieldsNotModifiedOrThrow(opts: { + pkgInfo: PackageInfo; + oldPackagePolicy: PackagePolicy; + packagePolicyUpdate: UpdatePackagePolicy; +}) { + const { pkgInfo, oldPackagePolicy, packagePolicyUpdate } = opts; + + if (pkgInfo.type !== 'input') return; + + const { namespace, inputs } = packagePolicyUpdate; + if (namespace && namespace !== oldPackagePolicy.namespace) { + throw new PackagePolicyValidationError( + i18n.translate('xpack.fleet.updatePackagePolicy.namespaceCannotBeModified', { + defaultMessage: + 'Package policy namespace cannot be modified for input only packages, please create a new package policy.', + }) + ); + } + + if (inputs) { + for (const input of inputs) { + const oldInput = oldPackagePolicy.inputs.find((i) => i.id === input.id); + if (oldInput) { + for (const stream of input.streams || []) { + const oldStream = oldInput.streams.find( + (s) => s.data_stream.dataset === stream.data_stream.dataset + ); + if ( + oldStream && + oldStream?.vars?.['data_stream.dataset'] && + oldStream?.vars['data_stream.dataset'] !== stream?.vars?.['data_stream.dataset'] + ) { + throw new PackagePolicyValidationError( + i18n.translate('xpack.fleet.updatePackagePolicy.datasetCannotBeModified', { + defaultMessage: + 'Package policy dataset cannot be modified for input only packages, please create a new package policy.', + }) + ); + } + } + } + } + } +} + async function validateIsNotHostedPolicy( soClient: SavedObjectsClientContract, id: string, diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/agent/input/input.yml.hbs b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/agent/input/input.yml.hbs new file mode 100644 index 0000000000000..1ba86fa98a2f8 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/agent/input/input.yml.hbs @@ -0,0 +1,18 @@ +paths: +{{#each paths}} + - {{this}} +{{/each}} + +{{#if tags}} +tags: +{{#each tags as |tag i|}} + - {{tag}} +{{/each}} +{{/if}} + +{{#if pipeline}} +pipeline: {{pipeline}} +{{/if}} + +data_stream: + dataset: {{data_stream.dataset}} \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/changelog.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/changelog.yml new file mode 100644 index 0000000000000..b7bc82c1696ba --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "1.0.0" + changes: + - description: basic input package + type: enhancement + link: https://github.com/elastic/package-spec/pull/325 diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/docs/README.md new file mode 100644 index 0000000000000..9f29c89e0f5ef --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/docs/README.md @@ -0,0 +1 @@ +# Custom Logs \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/fields/input.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/fields/input.yml new file mode 100644 index 0000000000000..f5851c64b6b3a --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/fields/input.yml @@ -0,0 +1,4 @@ +- name: input.name + type: constant_keyword + description: Sample field to be added. + value: logs \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/manifest.yml new file mode 100644 index 0000000000000..7ca322f5f1b37 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/input_package/1.0.0/manifest.yml @@ -0,0 +1,37 @@ +format_version: 1.0.0 +name: input_package +title: Test Custom Logs Input Package +description: >- + Read lines from active log files with Elastic Agent. +type: input +version: 1.0.0 +license: basic +categories: + - custom +policy_templates: + - name: logs + type: logs + title: Custom log file + description: Collect your custom log files. + input: logfile + template_path: input.yml.hbs + vars: + - name: paths + type: text + title: Paths + multi: true + required: true + show_user: true + - name: tags + type: text + title: Tags + multi: true + required: true + show_user: false + - name: ignore_older + type: text + title: Ignore events older than + required: false + default: 72h +owner: + github: elastic/integrations \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts index 3a931ca09ca3d..08a0681f7cde6 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts @@ -6,10 +6,10 @@ */ import expect from '@kbn/expect'; import { policyFactory } from '@kbn/security-solution-plugin/common/endpoint/models/policy_config'; +import type { NewPackagePolicy } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { testUsers } from '../test_users'; - export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); @@ -36,6 +36,10 @@ export default function (providerContext: FtrProviderContext) { let packagePolicyId2: string; let packagePolicyId3: string; let endpointPackagePolicyId: string; + let inputOnlyPackagePolicyId: string; + + let inputOnlyBasePackagePolicy: NewPackagePolicy; + before(async () => { await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); @@ -45,25 +49,21 @@ export default function (providerContext: FtrProviderContext) { if (!server.enabled) { return; } - const { body: agentPolicyResponse } = await supertest - .post(`/api/fleet/agent_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'Test policy', - namespace: 'default', - }); + const [{ body: agentPolicyResponse }, { body: managedAgentPolicyResponse }] = + await Promise.all([ + supertest.post(`/api/fleet/agent_policies`).set('kbn-xsrf', 'xxxx').send({ + name: 'Test policy', + namespace: 'default', + }), + supertest.post(`/api/fleet/agent_policies`).set('kbn-xsrf', 'xxxx').send({ + name: 'Test hosted agent policy', + namespace: 'default', + is_managed: true, + }), + ]); agentPolicyId = agentPolicyResponse.item.id; - const { body: managedAgentPolicyResponse } = await supertest - .post(`/api/fleet/agent_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'Test hosted agent policy', - namespace: 'default', - is_managed: true, - }); - // if one already exists, re-use that const managedExists = managedAgentPolicyResponse.statusCode === 409; if (managedExists) { @@ -76,90 +76,128 @@ export default function (providerContext: FtrProviderContext) { managedAgentPolicyId = managedAgentPolicyResponse.item.id; } - const { body: packagePolicyResponse } = await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'filetest-1', - description: '', - namespace: 'default', - policy_id: agentPolicyId, - enabled: true, - inputs: [], - package: { - name: 'filetest', - title: 'For File Tests', - version: '0.1.0', - }, - }); - packagePolicyId = packagePolicyResponse.item.id; - - const { body: packagePolicyResponse2 } = await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'filetest-2', - description: '', - namespace: 'default', - policy_id: agentPolicyId, - enabled: true, - inputs: [], - package: { - name: 'filetest', - title: 'For File Tests', - version: '0.1.0', + inputOnlyBasePackagePolicy = { + name: 'input-only-test-1', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + inputs: [ + { + type: 'logfile', + policy_template: 'logs', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'input_package.logs' }, + vars: { + paths: { type: 'text', value: ['/tmp/test.log'] }, + tags: { type: 'text', value: ['tag1'] }, + ignore_older: { value: '72h', type: 'text' }, + 'data_stream.dataset': { type: 'text', value: 'generic' }, + }, + }, + ], }, - }); - packagePolicyId2 = packagePolicyResponse2.item.id; + ], + package: { name: 'input_package', title: 'Input only package', version: '1.0.0' }, + }; - const { body: packagePolicyResponse3 } = await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'update-package-policy-with_required_variables-1', - description: '', - namespace: 'default', - policy_id: agentPolicyId, - inputs: { - 'with_required_variables-test_input': { - streams: { - 'with_required_variables.log': { - vars: { test_var_required: 'I am required' }, + const [ + { body: packagePolicyResponse }, + { body: packagePolicyResponse2 }, + { body: packagePolicyResponse3 }, + { body: endpointPackagePolicyResponse }, + { body: inputOnlyPolicyResponse }, + ] = await Promise.all([ + supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-1', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }), + supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-2', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }), + supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'update-package-policy-with_required_variables-1', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + inputs: { + 'with_required_variables-test_input': { + streams: { + 'with_required_variables.log': { + vars: { test_var_required: 'I am required' }, + }, }, }, }, - }, - package: { - name: 'with_required_variables', - version: '0.1.0', - }, - }); - packagePolicyId3 = packagePolicyResponse3.item.id; - - const { body: endpointPackagePolicyResponse } = await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'endpoint-1', - description: '', - namespace: 'default', - policy_id: agentPolicyId, - enabled: true, - inputs: [ - { - enabled: true, - streams: [], - type: 'endpoint', + package: { + name: 'with_required_variables', + version: '0.1.0', }, - ], - force: true, - package: { - name: 'endpoint', - title: 'Elastic Defend', - version: '8.6.1', - }, - }); + }), + supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'endpoint-1', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + inputs: [ + { + enabled: true, + streams: [], + type: 'endpoint', + }, + ], + force: true, + package: { + name: 'endpoint', + title: 'Elastic Defend', + version: '8.6.1', + }, + }), + supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send(inputOnlyBasePackagePolicy), + ]); + packagePolicyId = packagePolicyResponse.item.id; + packagePolicyId2 = packagePolicyResponse2.item.id; + packagePolicyId3 = packagePolicyResponse3.item.id; endpointPackagePolicyId = endpointPackagePolicyResponse.item.id; + inputOnlyPackagePolicyId = inputOnlyPolicyResponse.item.id; }); after(async function () { @@ -167,12 +205,14 @@ export default function (providerContext: FtrProviderContext) { .post(`/api/fleet/agent_policies/delete`) .set('kbn-xsrf', 'xxxx') .send({ agentPolicyId }); - // uninstall endpoint package await supertest .delete(`/api/fleet/epm/packages/endpoint-8.6.1`) .set('kbn-xsrf', 'xxxx') - .send({ force: true }) + .expect(200); + await supertest + .delete(`/api/fleet/epm/packages/input_package-1.0.0`) + .set('kbn-xsrf', 'xxxx') .expect(200); }); @@ -438,6 +478,34 @@ export default function (providerContext: FtrProviderContext) { .expect(400); expect(body.message).eql('Input not found: with_required_variables-i-do-not-exists'); }); + + it('should return a 400 if namespace is edited on input only package policy', async function () { + const { body } = await supertest + .put(`/api/fleet/package_policies/${inputOnlyPackagePolicyId}`) + .set('kbn-xsrf', 'xxxx') + .send({ + ...inputOnlyBasePackagePolicy, + namespace: 'updated_namespace', + }) + .expect(400); + expect(body.message).eql( + 'Package policy namespace cannot be modified for input only packages, please create a new package policy.' + ); + }); + it('should return a 400 if dataset is edited on input only package policy', async function () { + const updatedPolicy = JSON.parse(JSON.stringify(inputOnlyBasePackagePolicy)); + + updatedPolicy.inputs[0].streams[0].vars['data_stream.dataset'].value = 'updated_dataset'; + + const { body } = await supertest + .put(`/api/fleet/package_policies/${inputOnlyPackagePolicyId}`) + .set('kbn-xsrf', 'xxxx') + .send(updatedPolicy) + .expect(400); + expect(body.message).eql( + 'Package policy dataset cannot be modified for input only packages, please create a new package policy.' + ); + }); }); }); }