diff --git a/.buildkite/pipelines/flaky_tests/runner.js b/.buildkite/pipelines/flaky_tests/runner.js index e93c46e672a76..aa2e1f21c149b 100644 --- a/.buildkite/pipelines/flaky_tests/runner.js +++ b/.buildkite/pipelines/flaky_tests/runner.js @@ -116,7 +116,7 @@ steps.push({ label: 'Build Kibana Distribution and Plugins', agents: { queue: 'c2-8' }, key: 'build', - if: "build.env('BUILD_ID_FOR_ARTIFACTS') == null || build.env('BUILD_ID_FOR_ARTIFACTS') == ''", + if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''", }); for (const testSuite of testSuites) { diff --git a/docs/management/cases/add-connectors.asciidoc b/docs/management/cases/add-connectors.asciidoc new file mode 100644 index 0000000000000..cd0ed1e1b6402 --- /dev/null +++ b/docs/management/cases/add-connectors.asciidoc @@ -0,0 +1,56 @@ +[[add-case-connectors]] +== Add connectors + +preview::[] + +You can add connectors to cases to push information to these external incident +management systems: + +* IBM Resilient +* Jira +* ServiceNow ITSM +* ServiceNow SecOps +* {swimlane} + +NOTE: To create connectors and send cases to external systems, you must have the +appropriate {kib} feature privileges. Refer to <>. + +[discrete] +[[create-case-connectors]] +== Create connectors + +You can create connectors in *Management > {stack-manage-app} > {rules-ui}*, as +described in <>. Alternatively, you can create them in +*Management > {stack-manage-app} > Cases*: + +. Click *Edit external connection*. ++ +[role="screenshot"] +image::images/cases-connectors.png[] + +. From the *Incident management system* list, select *Add new connector*. + +. Select an external incident management system. + +. Enter your required settings. Refer to <>, +<>, <>, <>, +or <> for connector configuration details. + +. Click *Save*. + +[discrete] +[[edit-case-connector-settings]] +== Edit connector settings + +You can create additional connectors, update existing connectors, change +the default connector, and change case closure options. + +. Go to *Management > {stack-manage-app} > Cases*, click *Edit external connection*. + +. To change whether cases are automatically closed after they are sent to an +external system, update the case closure options. + +. To change the default connector for new cases, select the connector from the +*Incident management system* list. + +. To update a connector, click *Update * and edit the connector fields as required. diff --git a/docs/management/cases/cases.asciidoc b/docs/management/cases/cases.asciidoc index ec61e6342f1e6..c08b99894eea0 100644 --- a/docs/management/cases/cases.asciidoc +++ b/docs/management/cases/cases.asciidoc @@ -5,7 +5,8 @@ preview::[] Cases are used to open and track issues directly in {kib}. All cases list the original reporter and all the users who contribute to a case (_participants_). -You can also send cases to third party systems by configuring external connectors. +You can also send cases to external incident management systems by configuring +connectors. [role="screenshot"] image::images/cases.png[Cases page] @@ -17,4 +18,5 @@ You also cannot attach alerts from the {observability} or {security-app} to cases in *{stack-manage-app}*. * <> -* <> \ No newline at end of file +* <> +* <> \ No newline at end of file diff --git a/docs/management/cases/images/cases-connectors.png b/docs/management/cases/images/cases-connectors.png new file mode 100644 index 0000000000000..95af429aef2de Binary files /dev/null and b/docs/management/cases/images/cases-connectors.png differ diff --git a/docs/management/cases/index.asciidoc b/docs/management/cases/index.asciidoc index 86e68cbfbe77f..981c8a9821a99 100644 --- a/docs/management/cases/index.asciidoc +++ b/docs/management/cases/index.asciidoc @@ -1,4 +1,4 @@ include::cases.asciidoc[] include::setup-cases.asciidoc[leveloffset=+1] include::manage-cases.asciidoc[leveloffset=+1] -//=== Configure external connectors \ No newline at end of file +include::add-connectors.asciidoc[leveloffset=+1] \ No newline at end of file diff --git a/docs/management/cases/manage-cases.asciidoc b/docs/management/cases/manage-cases.asciidoc index e8b46f3e14870..f4693ef25950f 100644 --- a/docs/management/cases/manage-cases.asciidoc +++ b/docs/management/cases/manage-cases.asciidoc @@ -16,9 +16,8 @@ TIP: In the `Description` area, you can use https://www.markdownguide.org/cheat-sheet[Markdown] syntax to create formatted text. -. For *External incident management system*, select a connector. If you've -previously added one, that connector displays as the default selection. -Otherwise, the default setting is `No connector selected`. +. For *External incident management system*, select a connector. For more +information, refer to <>. . After you've completed all of the required fields, click *Create case*. diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 6db1459d90c64..f16ea62aa1b0c 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -4247,6 +4247,9 @@ }, "description": { "type": "string" + }, + "force": { + "type": "boolean" } }, "required": [ diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 6aaeaeaf16081..28040efa5f41b 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -2676,6 +2676,8 @@ components: type: string description: type: string + force: + type: boolean required: - name - namespace 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 7502a27e11daf..bd14910303a0d 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 @@ -53,6 +53,8 @@ properties: type: string description: type: string + force: + type: boolean required: - name - 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 1908d38ab408a..de2045ff3e47c 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -139,7 +139,7 @@ export const updatePackagePolicyHandler: RequestHandler< throw Boom.notFound('Package policy not found'); } - const body = { ...request.body }; + const { force, ...body } = request.body; // removed fields not recognized by schema const packagePolicyInputs = packagePolicy.inputs.map((input) => { const newInput = { @@ -180,7 +180,7 @@ export const updatePackagePolicyHandler: RequestHandler< esClient, request.params.packagePolicyId, newData, - { user }, + { user, force }, packagePolicy.package?.version ); return response.ok({ 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 704071154ac94..2fde004048a36 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -632,7 +632,7 @@ describe('Package policy service', () => { ).rejects.toThrow('Saved object [abc/123] conflict'); }); - it('should only update input vars that are not frozen', async () => { + it('should throw if the user try to update input vars that are frozen', async () => { const savedObjectsClient = savedObjectsClientMock.create(); const mockPackagePolicy = createPackagePolicyMock(); const mockInputs = [ @@ -743,22 +743,143 @@ describe('Package policy service', () => { ); const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - const result = await packagePolicyService.update( + const res = packagePolicyService.update( savedObjectsClient, elasticsearchClient, 'the-package-policy-id', { ...mockPackagePolicy, inputs: inputsUpdate } ); + await expect(res).rejects.toThrow('cat is a frozen variable and cannot be modified'); + }); + + it('should allow to update input vars that are frozen with the force flag', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + const mockPackagePolicy = createPackagePolicyMock(); + const mockInputs = [ + { + config: {}, + enabled: true, + keep_enabled: true, + type: 'endpoint', + vars: { + dog: { + type: 'text', + value: 'dalmatian', + }, + cat: { + type: 'text', + value: 'siamese', + frozen: true, + }, + }, + streams: [ + { + data_stream: { + type: 'birds', + dataset: 'migratory.patterns', + }, + enabled: false, + id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`, + vars: { + paths: { + value: ['north', 'south'], + type: 'text', + frozen: true, + }, + period: { + value: '6mo', + type: 'text', + }, + }, + }, + ], + }, + ]; + const inputsUpdate = [ + { + config: {}, + enabled: false, + type: 'endpoint', + vars: { + dog: { + type: 'text', + value: 'labrador', + }, + cat: { + type: 'text', + value: 'tabby', + }, + }, + streams: [ + { + data_stream: { + type: 'birds', + dataset: 'migratory.patterns', + }, + enabled: false, + id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`, + vars: { + paths: { + value: ['east', 'west'], + type: 'text', + }, + period: { + value: '12mo', + type: 'text', + }, + }, + }, + ], + }, + ]; + const attributes = { + ...mockPackagePolicy, + inputs: mockInputs, + }; + + savedObjectsClient.get.mockResolvedValue({ + id: 'test', + type: 'abcd', + references: [], + version: 'test', + attributes, + }); + + savedObjectsClient.update.mockImplementation( + async ( + type: string, + id: string, + attrs: any + ): Promise> => { + savedObjectsClient.get.mockResolvedValue({ + id: 'test', + type: 'abcd', + references: [], + version: 'test', + attributes: attrs, + }); + return attrs; + } + ); + const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + + const result = await packagePolicyService.update( + savedObjectsClient, + elasticsearchClient, + 'the-package-policy-id', + { ...mockPackagePolicy, inputs: inputsUpdate }, + { force: true } + ); + const [modifiedInput] = result.inputs; expect(modifiedInput.enabled).toEqual(true); expect(modifiedInput.vars!.dog.value).toEqual('labrador'); - expect(modifiedInput.vars!.cat.value).toEqual('siamese'); + expect(modifiedInput.vars!.cat.value).toEqual('tabby'); const [modifiedStream] = modifiedInput.streams; - expect(modifiedStream.vars!.paths.value).toEqual(expect.arrayContaining(['north', 'south'])); + expect(modifiedStream.vars!.paths.value).toEqual(expect.arrayContaining(['east', 'west'])); expect(modifiedStream.vars!.period.value).toEqual('12mo'); }); - it('should add new input vars when updating', async () => { const savedObjectsClient = savedObjectsClientMock.create(); const mockPackagePolicy = createPackagePolicyMock(); @@ -810,7 +931,7 @@ describe('Package policy service', () => { }, cat: { type: 'text', - value: 'tabby', + value: 'siamese', }, }, streams: [ @@ -823,7 +944,7 @@ describe('Package policy service', () => { id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`, vars: { paths: { - value: ['east', 'west'], + value: ['north', 'south'], type: 'text', }, period: { diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 546ae9c6fb9ac..bd68b6c1d4b2a 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { omit, partition } from 'lodash'; +import { omit, partition, isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; import semverLt from 'semver/functions/lt'; import { getFlattenedObject } from '@kbn/std'; @@ -358,7 +358,7 @@ class PackagePolicyService implements PackagePolicyServiceInterface { esClient: ElasticsearchClient, id: string, packagePolicyUpdate: UpdatePackagePolicy, - options?: { user?: AuthenticatedUser }, + options?: { user?: AuthenticatedUser; force?: boolean }, currentVersion?: string ): Promise { const packagePolicy = { ...packagePolicyUpdate, name: packagePolicyUpdate.name.trim() }; @@ -386,7 +386,7 @@ class PackagePolicyService implements PackagePolicyServiceInterface { assignStreamIdToInput(oldPackagePolicy.id, input) ); - inputs = enforceFrozenInputs(oldPackagePolicy.inputs, inputs); + inputs = enforceFrozenInputs(oldPackagePolicy.inputs, inputs, options?.force); let elasticsearch: PackagePolicy['elasticsearch']; if (packagePolicy.package?.name) { const pkgInfo = await getPackageInfo({ @@ -1119,21 +1119,25 @@ async function _compilePackageStream( return { ...stream }; } -function enforceFrozenInputs(oldInputs: PackagePolicyInput[], newInputs: PackagePolicyInput[]) { +function enforceFrozenInputs( + oldInputs: PackagePolicyInput[], + newInputs: PackagePolicyInput[], + force = false +) { const resultInputs = [...newInputs]; for (const input of resultInputs) { const oldInput = oldInputs.find((i) => i.type === input.type); if (oldInput?.keep_enabled) input.enabled = oldInput.enabled; if (input.vars && oldInput?.vars) { - input.vars = _enforceFrozenVars(oldInput.vars, input.vars); + input.vars = _enforceFrozenVars(oldInput.vars, input.vars, force); } if (input.streams && oldInput?.streams) { for (const stream of input.streams) { const oldStream = oldInput.streams.find((s) => s.id === stream.id); if (oldStream?.keep_enabled) stream.enabled = oldStream.enabled; if (stream.vars && oldStream?.vars) { - stream.vars = _enforceFrozenVars(oldStream.vars, stream.vars); + stream.vars = _enforceFrozenVars(oldStream.vars, stream.vars, force); } } } @@ -1144,12 +1148,21 @@ function enforceFrozenInputs(oldInputs: PackagePolicyInput[], newInputs: Package function _enforceFrozenVars( oldVars: Record, - newVars: Record + newVars: Record, + force = false ) { const resultVars: Record = {}; for (const [key, val] of Object.entries(newVars)) { if (oldVars[key]?.frozen) { - resultVars[key] = oldVars[key]; + if (force) { + resultVars[key] = val; + } else if (!isEqual(oldVars[key].value, val.value) || oldVars[key].type !== val.type) { + throw new PackagePolicyValidationError( + `${key} is a frozen variable and cannot be modified` + ); + } else { + resultVars[key] = oldVars[key]; + } } else { resultVars[key] = val; } @@ -1206,7 +1219,7 @@ export interface PackagePolicyServiceInterface { esClient: ElasticsearchClient, id: string, packagePolicyUpdate: UpdatePackagePolicy, - options?: { user?: AuthenticatedUser }, + options?: { user?: AuthenticatedUser; force?: boolean }, currentVersion?: string ): Promise; 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 904e4e18a8541..1d4dc1c1b3b65 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -138,6 +138,7 @@ export const UpdatePackagePolicyRequestBodySchema = schema.object({ ) ), version: schema.maybe(schema.string()), + force: schema.maybe(schema.boolean()), }); export const UpdatePackagePolicySchema = schema.object({