diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index aaceec07701cbd..e6e426e7a86239 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -54,7 +54,19 @@ Review important information about the {kib} 8.x releases. [[release-notes-8.10.3]] == {kib} 8.10.3 -The 8.10.3 release includes the following bug fixes. +[float] +[[security-update-8.10.3]] +=== Security updates + +* **Kibana heap buffer overflow vulnerability** ++ +On Sept 11, 2023, Google Chrome announced CVE-2023-4863, described as “Heap buffer overflow in libwebp in Google Chrome prior to 116.0.5845.187 and libwebp 1.3.2 allowed a remote attacker to perform an out of bounds memory write via a crafted HTML page”. Kibana includes a bundled version of headless Chromium that is only used for Kibana’s reporting capabilities and which is affected by this vulnerability. An exploit for Kibana has not been identified, however as a resolution, the bundled version of Chromium is updated in this release. ++ +The issue is resolved in 8.10.3. ++ +For more information, see our related +https://discuss.elastic.co/t/kibana-8-10-3-7-17-14-security-update/344735[security +announcement]. [float] [[enhancement-v8.10.3]] diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 9784908948f438..074e0f1c921c67 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -824,6 +824,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { elasticsearch: `${SEARCH_UI_DOCS}tutorials/elasticsearch`, }, serverlessClients: { + clientLib: `${SERVERLESS_ELASTICSEARCH_DOCS}clients`, goApiReference: `${SERVERLESS_ELASTICSEARCH_DOCS}go-client-getting-started`, goGettingStarted: `${SERVERLESS_ELASTICSEARCH_DOCS}go-client-getting-started`, httpApis: `${SERVERLESS_ELASTICSEARCH_DOCS}http-apis`, @@ -847,6 +848,9 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { gettingStartedIngest: `${SERVERLESS_ELASTICSEARCH_DOCS}get-started#ingest`, gettingStartedSearch: `${SERVERLESS_ELASTICSEARCH_DOCS}get-started#search`, }, + serverlessSecurity: { + apiKeyPrivileges: `${SERVERLESS_DOCS}api-keys#restrict-privileges`, + }, synthetics: { featureRoles: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetics-feature-roles.html`, }, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index ebb4fb07fe21f0..7a61d9f3dd30e5 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -581,6 +581,7 @@ export interface DocLinks { readonly elasticsearch: string; }; readonly serverlessClients: { + readonly clientLib: string; readonly goApiReference: string; readonly goGettingStarted: string; readonly httpApis: string; @@ -604,6 +605,9 @@ export interface DocLinks { readonly integrationsConnectorClient: string; readonly integrationsLogstash: string; }; + readonly serverlessSecurity: { + readonly apiKeyPrivileges: string; + }; readonly synthetics: { readonly featureRoles: string; }; diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx index aa61ecdd1bfe66..a1540e88a5cd6f 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.tsx @@ -538,6 +538,7 @@ export const UnifiedDataTable = ({ fieldFormats: services.fieldFormats, maxEntries: maxDocFieldsDisplayed, externalCustomRenderers, + isPlainRecord, }), [ dataView, @@ -547,6 +548,7 @@ export const UnifiedDataTable = ({ maxDocFieldsDisplayed, services.fieldFormats, externalCustomRenderers, + isPlainRecord, ] ); diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx index d522ae70d28b52..7db7ffedfdecf4 100644 --- a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx +++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx @@ -75,6 +75,18 @@ const rowsSource: EsHitRecord[] = [ }, ]; +const rowsSourceWithEmptyValues: EsHitRecord[] = [ + { + _id: '1', + _index: 'test', + _score: 1, + _source: { bytes: 100, extension: null }, + highlight: { + extension: ['@kibana-highlighted-field.gz@/kibana-highlighted-field'], + }, + }, +]; + const rowsFields: EsHitRecord[] = [ { _id: '1', @@ -344,6 +356,77 @@ describe('Unified data table cell rendering', function () { `); }); + it('renders _source column correctly if on text based mode and have nulls', () => { + const DataTableCellValue = getRenderCellValueFn({ + dataView: dataViewMock, + rows: rowsSourceWithEmptyValues.map(build), + useNewFieldsApi: false, + shouldShowFieldHandler: (fieldName) => ['extension', 'bytes'].includes(fieldName), + closePopover: jest.fn(), + fieldFormats: mockServices.fieldFormats as unknown as FieldFormatsStart, + maxEntries: 100, + isPlainRecord: true, + }); + const component = shallow( + + ); + expect(component).toMatchInlineSnapshot(` + + + bytesDisplayName + + + + _index + + + + _score + + + + `); + }); + it('renders fields-based column correctly', () => { const DataTableCellValue = getRenderCellValueFn({ dataView: dataViewMock, diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx index 2f9a3dd92b575a..581230b5257572 100644 --- a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx +++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx @@ -43,6 +43,7 @@ export const getRenderCellValueFn = ({ fieldFormats, maxEntries, externalCustomRenderers, + isPlainRecord, }: { dataView: DataView; rows: DataTableRecord[] | undefined; @@ -55,6 +56,7 @@ export const getRenderCellValueFn = ({ string, (props: EuiDataGridCellValueElementProps) => React.ReactNode >; + isPlainRecord?: boolean; }) => { return ({ rowIndex, @@ -144,17 +146,22 @@ export const getRenderCellValueFn = ({ compressed className={classnames('unifiedDataTable__descriptionList', CELL_CLASS)} > - {pairs.map(([fieldDisplayName, value]) => ( - - - {fieldDisplayName} - - - - ))} + {pairs.map(([fieldDisplayName, value, fieldName]) => { + // temporary solution for text based mode. As there are a lot of unsupported fields we want to + // hide the empty one from the Document view + if (isPlainRecord && fieldName && row.flattened[fieldName] === null) return null; + return ( + + + {fieldDisplayName} + + + + ); + })} ); } diff --git a/test/functional/apps/visualize/group3/_annotation_listing.ts b/test/functional/apps/visualize/group3/_annotation_listing.ts index cdb0cb615be472..5d33f714159f0c 100644 --- a/test/functional/apps/visualize/group3/_annotation_listing.ts +++ b/test/functional/apps/visualize/group3/_annotation_listing.ts @@ -13,6 +13,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['visualize', 'annotationEditor']); const listingTable = getService('listingTable'); const kibanaServer = getService('kibanaServer'); + const testSubjects = getService('testSubjects'); const find = getService('find'); const retry = getService('retry'); const log = getService('log'); @@ -32,6 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.visualize.selectAnnotationsTab(); + await listingTable.waitUntilTableIsLoaded(); }); after(async function () { @@ -156,7 +158,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe.skip('data view switching', () => { + describe('data view switching', () => { it('recovers from missing data view', async () => { await listingTable.clickItemLink('eventAnnotation', 'missing data view'); @@ -175,7 +177,36 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.annotationEditor.saveGroup(); }); - it('recovers from missing field in data view', () => {}); + it('recovers from missing field in data view', async () => { + const assertShowingMissingFieldError = async (yes: boolean) => { + const [failureExists, canvasExists] = await Promise.all([ + testSubjects.exists('embeddable-lens-failure'), + find.existsByCssSelector('canvas', 1000), + ]); + expect(failureExists).to.be(yes); + expect(canvasExists).to.be(!yes); + }; + + await listingTable.clickItemLink('eventAnnotation', 'Group with additional fields'); + + await assertShowingMissingFieldError(false); + + await retry.try(async () => { + await PageObjects.annotationEditor.editGroupMetadata({ + dataView: 'Data view without fields', + }); + + await assertShowingMissingFieldError(true); + }); + + await retry.try(async () => { + await PageObjects.annotationEditor.editGroupMetadata({ + dataView: 'logs*', + }); + + await assertShowingMissingFieldError(false); + }); + }); }); }); }); diff --git a/test/functional/fixtures/kbn_archiver/annotation_listing_page_search.json b/test/functional/fixtures/kbn_archiver/annotation_listing_page_search.json index 34f4fb8ed1b48c..a54a042effb6ec 100644 --- a/test/functional/fixtures/kbn_archiver/annotation_listing_page_search.json +++ b/test/functional/fixtures/kbn_archiver/annotation_listing_page_search.json @@ -21,6 +21,29 @@ "version": "WzIyNywxXQ==" } +{ + "attributes": { + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "Data view without fields", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "timestamp", + "title": "kibana_sample_data_logs", + "typeMeta": "{}" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-07T17:23:20.906Z", + "id": "data-view-without-fields", + "managed": false, + "references": [], + "type": "index-pattern", + "typeMigrationVersion": "8.0.0", + "updated_at": "2023-09-11T15:50:59.227Z", + "version": "WzIyNywxXQ==" +} + { "attributes": { "fieldAttrs": "{}", @@ -44,6 +67,44 @@ "version": "WzIyNywxXQ==" } +{ + "attributes": { + "annotations": [ + { + "extraFields": [ + "@message.raw" + ], + "icon": "triangle", + "id": "3d28ce7e-fc5e-409b-aea3-4d9e15010843", + "key": { + "type": "point_in_time" + }, + "label": "Event", + "timeField": "@timestamp", + "type": "query" + } + ], + "dataViewSpec": null, + "description": "", + "ignoreGlobalFilters": true, + "title": "Group with additional fields" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-10-06T17:15:58.790Z", + "id": "12371e00-5174-11ee-a5c4-7dce2e3293a7", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-10-06T17:17:05.384Z", + "version": "WzE4MywxXQ==" +} + { "attributes": { "annotations": [ diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index d675b1b42bb361..3a5df3768ae96b 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -41,6 +41,7 @@ export const EPM_API_ROUTES = { VERIFICATION_KEY_ID: `${EPM_API_ROOT}/verification_key_id`, STATS_PATTERN: `${EPM_PACKAGES_MANY}/{pkgName}/stats`, BULK_ASSETS_PATTERN: `${EPM_API_ROOT}/bulk_assets`, + INPUTS_PATTERN: `${EPM_API_ROOT}/templates/{pkgName}/{pkgVersion}/inputs`, INFO_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED, INSTALL_FROM_REGISTRY_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED, diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index a4604a7d7427b0..f87ab5a3edacc3 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -1420,6 +1420,56 @@ } ] }, + "/epm/templates/{pkgName}/{pkgVersion}/inputs": { + "get": { + "summary": "Get inputs template", + "tags": [ + "Elastic Package Manager (EPM)" + ], + "responses": { + "400": { + "$ref": "#/components/responses/error" + } + }, + "operationId": "get-inputs-template", + "security": [ + { + "basicAuth": [] + } + ] + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "pkgName", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "pkgVersion", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string", + "enum": [ + "json", + "yaml", + "yml" + ] + }, + "name": "format", + "description": "Format of response - json or yaml", + "in": "query" + } + ] + }, "/agents/setup": { "get": { "summary": "Get agent setup info", diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index be132c9f19e488..4629a95af27e80 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -894,6 +894,37 @@ paths: name: pkgName in: path required: true + /epm/templates/{pkgName}/{pkgVersion}/inputs: + get: + summary: Get inputs template + tags: + - Elastic Package Manager (EPM) + responses: + '400': + $ref: '#/components/responses/error' + operationId: get-inputs-template + security: + - basicAuth: [] + parameters: + - schema: + type: string + name: pkgName + in: path + required: true + - schema: + type: string + name: pkgVersion + in: path + required: true + - schema: + type: string + enum: + - json + - yaml + - yml + name: format + description: Format of response - json or yaml + in: query /agents/setup: get: summary: Get agent setup info diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index b8a7e024f3c4e3..92bffe4968092f 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -46,6 +46,8 @@ paths: $ref: paths/epm@get_file.yaml '/epm/packages/{pkgName}/stats': $ref: 'paths/epm@packages@{pkg_name}@stats.yaml' + '/epm/templates/{pkgName}/{pkgVersion}/inputs': + $ref: 'paths/epm@templates@{pkg_name}@{pkg_version}@inputs.yaml' # Agent endpoints /agents/setup: diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@templates@{pkg_name}@{pkg_version}@inputs.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@templates@{pkg_name}@{pkg_version}@inputs.yaml new file mode 100644 index 00000000000000..9888fd0036ea17 --- /dev/null +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@templates@{pkg_name}@{pkg_version}@inputs.yaml @@ -0,0 +1,30 @@ +get: + summary: Get inputs template + tags: + - Elastic Package Manager (EPM) + responses: + '400': + $ref: ../components/responses/error.yaml + operationId: get-inputs-template + security: + - basicAuth: [] +parameters: + - schema: + type: string + name: pkgName + in: path + required: true + - schema: + type: string + name: pkgVersion + in: path + required: true + - schema: + type: string + enum: + - json + - yaml + - yml + name: format + description: 'Format of response - json or yaml' + in: query diff --git a/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts index 30389ee45cbde3..18d995c96f2b8a 100644 --- a/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts +++ b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts @@ -39,7 +39,7 @@ export const fullAgentPolicyToYaml = (policy: FullAgentPolicy, toYaml: typeof sa return _formatSecrets(policy.secret_references, yaml); }; -function _sortYamlKeys(keyA: string, keyB: string) { +export function _sortYamlKeys(keyA: string, keyB: string) { const indexA = POLICY_KEYS_ORDER.indexOf(keyA); const indexB = POLICY_KEYS_ORDER.indexOf(keyB); if (indexA >= 0 && indexB < 0) { diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 5781fa623bb9b2..8ce1bf7006f6b3 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -53,6 +53,7 @@ import type { GetLimitedPackagesRequestSchema, GetBulkAssetsRequestSchema, CreateCustomIntegrationRequestSchema, + GetInputsRequestSchema, } from '../../types'; import { bulkInstallPackages, @@ -67,6 +68,7 @@ import { getLimitedPackages, getInstallation, getBulkAssets, + getTemplateInputs, } from '../../services/epm/packages'; import type { BulkInstallResponse } from '../../services/epm/packages'; import { defaultFleetErrorHandler, fleetErrorToResponseOptions, FleetError } from '../../errors'; @@ -650,6 +652,28 @@ export const reauthorizeTransformsHandler: FleetRequestHandler< } }; +export const getInputsHandler: FleetRequestHandler< + TypeOf, + TypeOf, + undefined +> = async (context, request, response) => { + const soClient = (await context.fleet).internalSoClient; + + try { + const { pkgName, pkgVersion } = request.params; + const { format } = request.query; + let body; + if (format === 'json') { + body = await getTemplateInputs(soClient, pkgName, pkgVersion, 'json'); + } else if (format === 'yml' || format === 'yaml') { + body = await getTemplateInputs(soClient, pkgName, pkgVersion, 'yml'); + } + return response.ok({ body }); + } catch (error) { + return defaultFleetErrorHandler({ error, response }); + } +}; + // Don't expose the whole SO in the API response, only selected fields const soToInstallationInfo = (pkg: PackageListItem | PackageInfo) => { if ('savedObject' in pkg && pkg.savedObject?.attributes) { diff --git a/x-pack/plugins/fleet/server/routes/epm/index.ts b/x-pack/plugins/fleet/server/routes/epm/index.ts index 4f354ae77d7f07..6e0000bf4ccbf6 100644 --- a/x-pack/plugins/fleet/server/routes/epm/index.ts +++ b/x-pack/plugins/fleet/server/routes/epm/index.ts @@ -47,6 +47,7 @@ import { ReauthorizeTransformRequestSchema, GetDataStreamsRequestSchema, CreateCustomIntegrationRequestSchema, + GetInputsRequestSchema, } from '../../types'; import { @@ -67,6 +68,7 @@ import { reauthorizeTransformsHandler, getDataStreamsHandler, createCustomIntegrationHandler, + getInputsHandler, } from './handlers'; const MAX_FILE_SIZE_BYTES = 104857600; // 100MB @@ -145,6 +147,19 @@ export const registerRoutes = (router: FleetAuthzRouter) => { getStatsHandler ); + router.versioned + .get({ + path: EPM_API_ROUTES.INPUTS_PATTERN, + fleetAuthz: READ_PACKAGE_INFO_AUTHZ, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { request: GetInputsRequestSchema }, + }, + getInputsHandler + ); + router.versioned .get({ path: EPM_API_ROUTES.FILEPATH_PATTERN, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts new file mode 100644 index 00000000000000..04c65535ad0ad8 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SavedObjectsClientContract } from '@kbn/core/server'; + +import { merge } from 'lodash'; +import { safeDump } from 'js-yaml'; + +import { packageToPackagePolicy } from '../../../../common/services/package_to_package_policy'; +import { getInputsWithStreamIds, _compilePackagePolicyInputs } from '../../package_policy'; + +import type { + PackageInfo, + NewPackagePolicy, + PackagePolicyInput, + FullAgentPolicyInput, + FullAgentPolicyInputStream, +} from '../../../../common/types'; +import { _sortYamlKeys } from '../../../../common/services/full_agent_policy_to_yaml'; + +import { getPackageInfo } from '.'; + +type Format = 'yml' | 'json'; + +// Function based off storedPackagePolicyToAgentInputs, it only creates the `streams` section instead of the FullAgentPolicyInput +export const templatePackagePolicyToFullInputs = ( + packagePolicyInputs: PackagePolicyInput[] +): FullAgentPolicyInput[] => { + const fullInputs: FullAgentPolicyInput[] = []; + + if (!packagePolicyInputs || packagePolicyInputs.length === 0) return fullInputs; + + packagePolicyInputs.forEach((input) => { + const fullInput = { + ...(input.compiled_input || {}), + ...(input.streams.length + ? { + streams: input.streams.map((stream) => { + const fullStream: FullAgentPolicyInputStream = { + id: stream.id, + type: input.type, + data_stream: stream.data_stream, + ...stream.compiled_stream, + ...Object.entries(stream.config || {}).reduce((acc, [key, { value }]) => { + acc[key] = value; + return acc; + }, {} as { [k: string]: any }), + }; + return fullStream; + }), + } + : {}), + }; + + // deeply merge the input.config values with the full policy input + merge( + fullInput, + Object.entries(input.config || {}).reduce((acc, [key, { value }]) => { + acc[key] = value; + return acc; + }, {} as Record) + ); + fullInputs.push(fullInput); + }); + + return fullInputs; +}; + +export async function getTemplateInputs( + soClient: SavedObjectsClientContract, + pkgName: string, + pkgVersion: string, + format: Format +) { + const packageInfoMap = new Map(); + let packageInfo: PackageInfo; + + if (packageInfoMap.has(pkgName)) { + packageInfo = packageInfoMap.get(pkgName)!; + } else { + packageInfo = await getPackageInfo({ + savedObjectsClient: soClient, + pkgName, + pkgVersion, + }); + } + const emptyPackagePolicy = packageToPackagePolicy(packageInfo, ''); + const inputsWithStreamIds = getInputsWithStreamIds(emptyPackagePolicy, undefined, true); + + const compiledInputs = await _compilePackagePolicyInputs( + packageInfo, + emptyPackagePolicy.vars || {}, + inputsWithStreamIds + ); + const packagePolicyWithInputs: NewPackagePolicy = { + ...emptyPackagePolicy, + inputs: compiledInputs, + }; + const fullAgentPolicyInputs = templatePackagePolicyToFullInputs( + packagePolicyWithInputs.inputs as PackagePolicyInput[] + ); + // @ts-ignore-next-line The return type is any because in some case we can have compiled_input instead of input.streams + // we don't know what it is. An example is integration APM + const inputs: any = fullAgentPolicyInputs.flatMap((input) => input?.streams || input); + + if (format === 'json') { + return { inputs }; + } else if (format === 'yml') { + const yaml = safeDump( + { inputs }, + { + skipInvalid: true, + sortKeys: _sortYamlKeys, + } + ); + return yaml; + } + return { inputs: [] }; +} diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts new file mode 100644 index 00000000000000..e9912cc8d8bd2c --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts @@ -0,0 +1,303 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PackagePolicyInput } from '../../../../common/types'; + +import { templatePackagePolicyToFullInputs } from './get_template_inputs'; + +const packageInfoCache = new Map(); +packageInfoCache.set('mock_package-0.0.0', { + name: 'mock_package', + version: '0.0.0', + policy_templates: [ + { + multiple: true, + }, + ], +}); +packageInfoCache.set('limited_package-0.0.0', { + name: 'limited_package', + version: '0.0.0', + policy_templates: [ + { + multiple: false, + }, + ], +}); + +describe('Fleet - templatePackagePolicyToFullInputs', () => { + const mockInput: PackagePolicyInput = { + type: 'test-logs', + enabled: true, + vars: { + inputVar: { value: 'input-value' }, + inputVar2: { value: undefined }, + inputVar3: { + type: 'yaml', + value: 'testField: test', + }, + inputVar4: { value: '' }, + }, + streams: [ + { + id: 'test-logs-foo', + enabled: true, + data_stream: { dataset: 'foo', type: 'logs' }, + vars: { + fooVar: { value: 'foo-value' }, + fooVar2: { value: [1, 2] }, + }, + compiled_stream: { + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, + }, + { + id: 'test-logs-bar', + enabled: true, + data_stream: { dataset: 'bar', type: 'logs' }, + vars: { + barVar: { value: 'bar-value' }, + barVar2: { value: [1, 2] }, + barVar3: { + type: 'yaml', + value: + '- namespace: mockNamespace\n #disabledProp: ["test"]\n anotherProp: test\n- namespace: mockNamespace2\n #disabledProp: ["test2"]\n anotherProp: test2', + }, + barVar4: { + type: 'yaml', + value: '', + }, + barVar5: { + type: 'yaml', + value: 'testField: test\n invalidSpacing: foo', + }, + }, + }, + ], + }; + + const mockInput2: PackagePolicyInput = { + type: 'test-metrics', + policy_template: 'some-template', + enabled: true, + vars: { + inputVar: { value: 'input-value' }, + inputVar2: { value: undefined }, + inputVar3: { + type: 'yaml', + value: 'testField: test', + }, + inputVar4: { value: '' }, + }, + streams: [ + { + id: 'test-metrics-foo', + enabled: true, + data_stream: { dataset: 'foo', type: 'metrics' }, + vars: { + fooVar: { value: 'foo-value' }, + fooVar2: { value: [1, 2] }, + }, + compiled_stream: { + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, + }, + ], + }; + + it('returns no inputs for package policy with no inputs', async () => { + expect(await templatePackagePolicyToFullInputs([])).toEqual([]); + }); + + it('returns inputs even when inputs where disabled', async () => { + expect(await templatePackagePolicyToFullInputs([{ ...mockInput, enabled: false }])).toEqual([ + { + streams: [ + { + data_stream: { + dataset: 'foo', + type: 'logs', + }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + id: 'test-logs-foo', + type: 'test-logs', + }, + { + data_stream: { + dataset: 'bar', + type: 'logs', + }, + id: 'test-logs-bar', + type: 'test-logs', + }, + ], + }, + ]); + }); + + it('returns agent inputs with streams', async () => { + expect(await templatePackagePolicyToFullInputs([mockInput])).toEqual([ + { + streams: [ + { + id: 'test-logs-foo', + data_stream: { dataset: 'foo', type: 'logs' }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + type: 'test-logs', + }, + { + id: 'test-logs-bar', + data_stream: { dataset: 'bar', type: 'logs' }, + type: 'test-logs', + }, + ], + }, + ]); + }); + + it('returns unique agent inputs IDs, with policy template name if one exists for non-limited packages', async () => { + expect(await templatePackagePolicyToFullInputs([mockInput])).toEqual([ + { + streams: [ + { + id: 'test-logs-foo', + type: 'test-logs', + data_stream: { dataset: 'foo', type: 'logs' }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, + { + id: 'test-logs-bar', + data_stream: { dataset: 'bar', type: 'logs' }, + type: 'test-logs', + }, + ], + }, + ]); + }); + + it('returns agent inputs without streams', async () => { + expect(await templatePackagePolicyToFullInputs([mockInput2])).toEqual([ + { + streams: [ + { + data_stream: { + dataset: 'foo', + type: 'metrics', + }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + id: 'test-metrics-foo', + type: 'test-metrics', + }, + ], + }, + ]); + }); + + it('returns agent inputs without disabled streams', async () => { + expect( + await templatePackagePolicyToFullInputs([ + { + ...mockInput, + streams: [{ ...mockInput.streams[0] }, { ...mockInput.streams[1], enabled: false }], + }, + ]) + ).toEqual([ + { + streams: [ + { + id: 'test-logs-foo', + type: 'test-logs', + data_stream: { dataset: 'foo', type: 'logs' }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, + { + data_stream: { + dataset: 'bar', + type: 'logs', + }, + id: 'test-logs-bar', + type: 'test-logs', + }, + ], + }, + ]); + }); + + it('returns agent inputs with deeply merged config values', async () => { + expect( + await templatePackagePolicyToFullInputs([ + { + ...mockInput, + compiled_input: { + agent_input_template_group1_vars: { + inputVar: 'input-value', + }, + agent_input_template_group2_vars: { + inputVar3: { + testFieldGroup: { + subField1: 'subfield1', + }, + testField: 'test', + }, + }, + }, + config: { + agent_input_template_group1_vars: { + value: { + inputVar2: {}, + }, + }, + agent_input_template_group2_vars: { + value: { + inputVar3: { + testFieldGroup: { + subField2: 'subfield2', + }, + }, + inputVar4: '', + }, + }, + }, + }, + ]) + ).toEqual([ + { + agent_input_template_group1_vars: { + inputVar: 'input-value', + inputVar2: {}, + }, + agent_input_template_group2_vars: { + inputVar3: { + testField: 'test', + testFieldGroup: { + subField1: 'subfield1', + subField2: 'subfield2', + }, + }, + inputVar4: '', + }, + streams: [ + { + id: 'test-logs-foo', + data_stream: { dataset: 'foo', type: 'logs' }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + type: 'test-logs', + }, + { id: 'test-logs-bar', data_stream: { dataset: 'bar', type: 'logs' }, type: 'test-logs' }, + ], + }, + ]); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/index.ts b/x-pack/plugins/fleet/server/services/epm/packages/index.ts index 5895d4a7819c66..f6cee5a34bae0c 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/index.ts @@ -27,6 +27,7 @@ export { export { getBundledPackages } from './bundled_packages'; export { getBulkAssets } from './get_bulk_assets'; +export { getTemplateInputs } from './get_template_inputs'; export type { BulkInstallResponse, IBulkInstallPackageError } from './install'; export { handleInstallPackageFailure, installPackage, ensureInstalledPackage } from './install'; diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 46ac72f69ecabd..983fdd7e8d29b9 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -1861,16 +1861,23 @@ function validatePackagePolicyOrThrow(packagePolicy: NewPackagePolicy, pkgInfo: } } -function getInputsWithStreamIds( +// the option `allEnabled` is only used to generate inputs integration templates where everything is enabled by default +// it shouldn't be used in the normal install flow +export function getInputsWithStreamIds( packagePolicy: NewPackagePolicy, - packagePolicyId: string + packagePolicyId?: string, + allEnabled?: boolean ): PackagePolicy['inputs'] { return packagePolicy.inputs.map((input) => { return { ...input, + enabled: !!allEnabled ? true : input.enabled, streams: input.streams.map((stream) => ({ ...stream, - id: `${input.type}-${stream.data_stream.dataset}-${packagePolicyId}`, + enabled: !!allEnabled ? true : stream.enabled, + id: packagePolicyId + ? `${input.type}-${stream.data_stream.dataset}-${packagePolicyId}` + : `${input.type}-${stream.data_stream.dataset}`, })), }; }); diff --git a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts index 2c046134598a7b..da0628cbeb9115 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts @@ -247,3 +247,15 @@ export const DeletePackageRequestSchemaDeprecated = { }) ), }; + +export const GetInputsRequestSchema = { + params: schema.object({ + pkgName: schema.string(), + pkgVersion: schema.string(), + }), + query: schema.object({ + format: schema.oneOf([schema.literal('json'), schema.literal('yml'), schema.literal('yaml')], { + defaultValue: 'json', + }), + }), +}; diff --git a/x-pack/plugins/security/public/components/form_flyout.tsx b/x-pack/plugins/security/public/components/form_flyout.tsx deleted file mode 100644 index 51ab56a11d225b..00000000000000 --- a/x-pack/plugins/security/public/components/form_flyout.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EuiButtonProps, EuiFlyoutProps } from '@elastic/eui'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiPortal, - EuiTitle, -} from '@elastic/eui'; -import type { FunctionComponent, RefObject } from 'react'; -import React, { useEffect } from 'react'; - -import { FormattedMessage } from '@kbn/i18n-react'; - -import { useHtmlId } from './use_html_id'; - -export interface FormFlyoutProps extends Omit { - title: string; - initialFocus?: RefObject; - onCancel(): void; - onSubmit(): void; - submitButtonText: string; - submitButtonColor?: EuiButtonProps['color']; - isLoading?: EuiButtonProps['isLoading']; - isDisabled?: EuiButtonProps['isDisabled']; - isSubmitButtonHidden?: boolean; -} - -export const FormFlyout: FunctionComponent = ({ - title, - submitButtonText, - submitButtonColor, - onCancel, - onSubmit, - isLoading, - isDisabled, - isSubmitButtonHidden, - children, - initialFocus, - ...rest -}) => { - useEffect(() => { - if (initialFocus && initialFocus.current) { - initialFocus.current.focus(); - } - }, [initialFocus]); - - const titleId = useHtmlId('formFlyout', 'title'); - - return ( - - - - -

{title}

-
-
- {children} - - - - - - - - {!isSubmitButtonHidden && ( - - - {submitButtonText} - - - )} - - -
-
- ); -}; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_key_flyout.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_key_flyout.tsx index 6d100f2a8261ee..b3792941fc6d0f 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_key_flyout.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_key_flyout.tsx @@ -7,11 +7,17 @@ import type { ExclusiveUnion } from '@elastic/eui'; import { + EuiButton, + EuiButtonEmpty, EuiCallOut, EuiCheckableCard, EuiFieldNumber, EuiFlexGroup, EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, EuiFormFieldset, EuiFormRow, EuiHorizontalRule, @@ -36,10 +42,9 @@ import { ApiKeyBadge, ApiKeyStatus, TimeToolTip, UsernameWithIcon } from './api_ import type { ApiKeyRoleDescriptors } from '../../../../common/model'; import { DocLink } from '../../../components/doc_link'; import { FormField } from '../../../components/form_field'; -import type { FormFlyoutProps } from '../../../components/form_flyout'; -import { FormFlyout } from '../../../components/form_flyout'; import { FormRow } from '../../../components/form_row'; import { useCurrentUser } from '../../../components/use_current_user'; +import { useHtmlId } from '../../../components/use_html_id'; import { useInitialFocus } from '../../../components/use_initial_focus'; import { RolesAPIClient } from '../../roles/roles_api_client'; import { APIKeysAPIClient } from '../api_keys_api_client'; @@ -64,7 +69,7 @@ export interface ApiKeyFormValues { interface CommonApiKeyFlyoutProps { initialValues?: ApiKeyFormValues; - onCancel: FormFlyoutProps['onCancel']; + onCancel(): void; canManageCrossClusterApiKeys?: boolean; readOnly?: boolean; } @@ -175,337 +180,466 @@ export const ApiKeyFlyout: FunctionComponent = ({ const firstFieldRef = useInitialFocus([isLoading]); + const titleId = useHtmlId('formFlyout', 'title'); + const isSubmitButtonHidden = readOnly || (apiKey && !canEdit); + + const isSubmitDisabled = + isLoading || (formik.submitCount > 0 && !formik.isValid) || readOnly || (apiKey && !canEdit); + + const title = apiKey + ? readOnly || !canEdit + ? i18n.translate('xpack.security.accountManagement.apiKeyFlyout.viewTitle', { + defaultMessage: `API key details`, + }) + : i18n.translate('xpack.security.accountManagement.apiKeyFlyout.updateTitle', { + defaultMessage: `Update API key`, + }) + : i18n.translate('xpack.security.accountManagement.apiKeyFlyout.createTitle', { + defaultMessage: `Create API key`, + }); + + const submitButtonText = apiKey + ? i18n.translate('xpack.security.accountManagement.apiKeyFlyout.updateSubmitButton', { + defaultMessage: `{isSubmitting, select, true{Updating API key…} other{Update API key}}`, + values: { isSubmitting: formik.isSubmitting }, + }) + : i18n.translate('xpack.security.accountManagement.apiKeyFlyout.createSubmitButton', { + defaultMessage: `{isSubmitting, select, true{Creating API key…} other{Create API key}}`, + values: { isSubmitting: formik.isSubmitting }, + }); + return ( - 0 && !formik.isValid) || - readOnly || - (apiKey && !canEdit) - } - isSubmitButtonHidden={readOnly || (apiKey && !canEdit)} - size="m" - ownFocus - > - - {apiKey && !readOnly ? ( - !isOwner ? ( - <> - - } - /> - - - ) : hasExpired ? ( - <> - - } - /> - - - ) : null - ) : null} - -
- - } - fullWidth - > - - - - {apiKey ? ( - <> - - - - - } - > - - - - - - } - > - - - - - + + + +

{title}

+
+
+ + + {apiKey && !readOnly ? ( + !isOwner ? ( + <> + } - > - -
-
- - + + + ) : hasExpired ? ( + <> + } - > - - - -
- - ) : canManageCrossClusterApiKeys ? ( + /> + + + ) : null + ) : null} + } fullWidth > - - - - -

- -

-
- - - - - + formik.setFieldValue('type', 'rest')} - checked={formik.values.type === 'rest'} + ), + }} + fullWidth + /> +
+ + {apiKey ? ( + <> + + + + + } + > + + + + + + } + > + + + + + + } + > + + + + + + } + > + + + + + + ) : canManageCrossClusterApiKeys ? ( + - - - - -

+ } + fullWidth + > + + + + +

+ +

+
+ + -

-
- - - - - - } - onChange={() => formik.setFieldValue('type', 'cross_cluster')} - checked={formik.values.type === 'cross_cluster'} + + + } + onChange={() => formik.setFieldValue('type', 'rest')} + checked={formik.values.type === 'rest'} + /> +
+ + + +

+ +

+
+ + + + + + } + onChange={() => formik.setFieldValue('type', 'cross_cluster')} + checked={formik.values.type === 'cross_cluster'} + /> +
+ +
+ ) : ( + - - - - ) : ( - - } - > - - - )} - + } + > + + + )} + - {formik.values.type === 'cross_cluster' ? ( - - } - helpText={ - + {formik.values.type === 'cross_cluster' ? ( + - - } - fullWidth - > - formik.setFieldValue('access', value)} - validate={(value: string) => { - if (!value) { - return i18n.translate( - 'xpack.security.management.apiKeys.apiKeyFlyout.accessRequired', - { - defaultMessage: 'Enter access permissions or disable this option.', - } - ); + } + helpText={ + + + + } + fullWidth + > + formik.setFieldValue('access', value)} + validate={(value: string) => { + if (!value) { + return i18n.translate( + 'xpack.security.management.apiKeys.apiKeyFlyout.accessRequired', + { + defaultMessage: 'Enter access permissions or disable this option.', + } + ); + } + try { + JSON.parse(value); + } catch (e) { + return i18n.translate( + 'xpack.security.management.apiKeys.apiKeyFlyout.invalidJsonError', + { + defaultMessage: 'Enter valid JSON.', + } + ); + } + }} + fullWidth + languageId="xjson" + height={200} + /> + + ) : ( + + } - try { - JSON.parse(value); - } catch (e) { - return i18n.translate( - 'xpack.security.management.apiKeys.apiKeyFlyout.invalidJsonError', - { - defaultMessage: 'Enter valid JSON.', + checked={formik.values.customPrivileges} + data-test-subj="apiKeysRoleDescriptorsSwitch" + onChange={(e) => formik.setFieldValue('customPrivileges', e.target.checked)} + disabled={readOnly || (apiKey && !canEdit)} + /> + {formik.values.customPrivileges && ( + <> + + + + } - ); - } - }} - fullWidth - languageId="xjson" - height={200} - /> - - ) : ( + fullWidth + data-test-subj="apiKeysRoleDescriptorsCodeEditor" + > + + formik.setFieldValue('role_descriptors', value) + } + validate={(value: string) => { + if (!value) { + return i18n.translate( + 'xpack.security.management.apiKeys.apiKeyFlyout.roleDescriptorsRequired', + { + defaultMessage: 'Enter role descriptors or disable this option.', + } + ); + } + try { + JSON.parse(value); + } catch (e) { + return i18n.translate( + 'xpack.security.management.apiKeys.apiKeyFlyout.invalidJsonError', + { + defaultMessage: 'Enter valid JSON.', + } + ); + } + }} + fullWidth + languageId="xjson" + height={200} + /> + + + + )} + + )} + + {!apiKey && ( + <> + + + + } + checked={formik.values.customExpiration} + onChange={(e) => formik.setFieldValue('customExpiration', e.target.checked)} + disabled={readOnly || !!apiKey} + data-test-subj="apiKeyCustomExpirationSwitch" + /> + {formik.values.customExpiration && ( + <> + + + } + fullWidth + > + + + + + )} + + + )} + } - checked={formik.values.customPrivileges} - data-test-subj="apiKeysRoleDescriptorsSwitch" - onChange={(e) => formik.setFieldValue('customPrivileges', e.target.checked)} + data-test-subj="apiKeysMetadataSwitch" + checked={formik.values.includeMetadata} disabled={readOnly || (apiKey && !canEdit)} + onChange={(e) => formik.setFieldValue('includeMetadata', e.target.checked)} /> - {formik.values.customPrivileges && ( + {formik.values.includeMetadata && ( <> } fullWidth - data-test-subj="apiKeysRoleDescriptorsCodeEditor" > - formik.setFieldValue('role_descriptors', value) - } + value={formik.values.metadata} + onChange={(value: string) => formik.setFieldValue('metadata', value)} validate={(value: string) => { if (!value) { return i18n.translate( - 'xpack.security.management.apiKeys.apiKeyFlyout.roleDescriptorsRequired', + 'xpack.security.management.apiKeys.apiKeyFlyout.metadataRequired', { - defaultMessage: 'Enter role descriptors or disable this option.', + defaultMessage: 'Enter metadata or disable this option.', } ); } @@ -529,137 +663,40 @@ export const ApiKeyFlyout: FunctionComponent = ({ )} - )} - - {!apiKey && ( - <> - - - - } - checked={formik.values.customExpiration} - onChange={(e) => formik.setFieldValue('customExpiration', e.target.checked)} - disabled={readOnly || !!apiKey} - data-test-subj="apiKeyCustomExpirationSwitch" - /> - {formik.values.customExpiration && ( - <> - - - } - fullWidth - > - - - - - )} - - - )} - - - + + + + + - } - data-test-subj="apiKeysMetadataSwitch" - checked={formik.values.includeMetadata} - disabled={readOnly || (apiKey && !canEdit)} - onChange={(e) => formik.setFieldValue('includeMetadata', e.target.checked)} - /> - {formik.values.includeMetadata && ( - <> - - - - - } - fullWidth + + + {!isSubmitButtonHidden && ( + + - formik.setFieldValue('metadata', value)} - validate={(value: string) => { - if (!value) { - return i18n.translate( - 'xpack.security.management.apiKeys.apiKeyFlyout.metadataRequired', - { - defaultMessage: 'Enter metadata or disable this option.', - } - ); - } - try { - JSON.parse(value); - } catch (e) { - return i18n.translate( - 'xpack.security.management.apiKeys.apiKeyFlyout.invalidJsonError', - { - defaultMessage: 'Enter valid JSON.', - } - ); - } - }} - fullWidth - languageId="xjson" - height={200} - /> - - - + {submitButtonText} + + )} - - -
-
+ + + +
); }; diff --git a/x-pack/plugins/serverless_search/common/doc_links.ts b/x-pack/plugins/serverless_search/common/doc_links.ts index 4c93763a145c83..0c816e3c7a3899 100644 --- a/x-pack/plugins/serverless_search/common/doc_links.ts +++ b/x-pack/plugins/serverless_search/common/doc_links.ts @@ -57,11 +57,11 @@ class ESDocLinks { this.kibanaFeedback = newDocLinks.kibana.feedback; this.kibanaRunApiInConsole = newDocLinks.console.serverlessGuide; this.metadata = newDocLinks.security.mappingRoles; - this.roleDescriptors = newDocLinks.security.mappingRoles; + this.roleDescriptors = newDocLinks.serverlessSecurity.apiKeyPrivileges; this.securityApis = newDocLinks.apis.securityApis; // Client links - this.elasticsearchClients = newDocLinks.serverlessClients.httpApis; + this.elasticsearchClients = newDocLinks.serverlessClients.clientLib; // Go this.goApiReference = newDocLinks.serverlessClients.goApiReference; this.goBasicConfig = newDocLinks.serverlessClients.goGettingStarted; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts index 3be3095ca2830c..723646a1763e63 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts @@ -18,6 +18,7 @@ import { getCase, getCaseSavedObjectsFromES, resolveCase, + findCases, } from '../../../../common/lib/api'; import { superUser } from '../../../../common/lib/authentication/users'; @@ -73,6 +74,62 @@ export default function createGetTests({ getService }: FtrProviderContext) { syncAlerts: true, }); }); + + it('should return the cases correctly', async () => { + const cases = await findCases({ supertest }); + const theCase = cases.cases[0]; + + const { version, ...caseWithoutVersion } = theCase; + const { cases: _, ...caseStats } = cases; + + expect(cases.cases.length).to.eql(1); + + expect(caseStats).to.eql({ + count_closed_cases: 0, + count_in_progress_cases: 0, + count_open_cases: 1, + page: 1, + per_page: 20, + total: 1, + }); + + expect(caseWithoutVersion).to.eql({ + assignees: [], + category: null, + closed_at: null, + closed_by: null, + comments: [], + connector: { + fields: null, + id: 'connector-1', + name: 'none', + type: '.none', + }, + created_at: '2020-09-28T11:43:52.158Z', + created_by: { + email: null, + full_name: null, + username: 'elastic', + }, + customFields: [], + description: 'This is a brand new case of a bad meanie defacing data', + duration: null, + external_service: null, + id: 'e1900ac0-017f-11eb-93f8-d161651bf509', + owner: 'securitySolution', + settings: { + syncAlerts: true, + }, + severity: 'low', + status: 'open', + tags: ['defacement'], + title: 'Super Bad Security Issue', + totalAlerts: 0, + totalComment: 1, + updated_at: null, + updated_by: null, + }); + }); }); // tests upgrading a 7.11.1 saved object to the latest version diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/telemetry_usage.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/telemetry_usage.ts index 9135879f3e2eb0..5306937a97ab5b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/telemetry_usage.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/telemetry_usage.ts @@ -65,7 +65,8 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('Risk engine enabled', () => { + // FLAKY: https://github.com/elastic/kibana/issues/168429 + describe.skip('Risk engine enabled', () => { let hostId: string; let userId: string; diff --git a/x-pack/test/fleet_api_integration/apis/epm/get_templates_inputs.ts b/x-pack/test/fleet_api_integration/apis/epm/get_templates_inputs.ts new file mode 100644 index 00000000000000..e879269c89fa16 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/epm/get_templates_inputs.ts @@ -0,0 +1,200 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import fs from 'fs'; +import path from 'path'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { setupFleetAndAgents } from '../agents/services'; +import { testUsers } from '../test_users'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + const testPkgName = 'apache'; + const testPkgVersion = '0.1.4'; + + const uninstallPackage = async (name: string, version: string) => { + await supertest.delete(`/api/fleet/epm/packages/${name}/${version}`).set('kbn-xsrf', 'xxxx'); + }; + + const testPkgArchiveZip = path.join( + path.dirname(__filename), + '../fixtures/direct_upload_packages/apache_0.1.4.zip' + ); + + describe('EPM Templates - Get Inputs', () => { + skipIfNoDockerRegistry(providerContext); + setupFleetAndAgents(providerContext); + before(async () => { + const buf = fs.readFileSync(testPkgArchiveZip); + await supertest + .post(`/api/fleet/epm/packages`) + .set('kbn-xsrf', 'xxxx') + .type('application/zip') + .send(buf) + .expect(200); + }); + after(async () => { + await uninstallPackage(testPkgName, testPkgVersion); + }); + const expectedYml = `inputs: + - id: logfile-apache.access + type: logfile + data_stream: + dataset: apache.access + type: logs + paths: + - /var/log/apache2/access.log* + - /var/log/apache2/other_vhosts_access.log* + - /var/log/httpd/access_log* + exclude_files: + - .gz$ + processors: + - add_fields: + target: '' + fields: + ecs.version: 1.5.0 + - id: logfile-apache.error + type: logfile + data_stream: + dataset: apache.error + type: logs + paths: + - /var/log/apache2/error.log* + - /var/log/httpd/error_log* + exclude_files: + - .gz$ + processors: + - add_locale: null + - add_fields: + target: '' + fields: + ecs.version: 1.5.0 + - id: apache/metrics-apache.status + type: apache/metrics + data_stream: + dataset: apache.status + type: metrics + metricsets: + - status + hosts: + - 'http://127.0.0.1' + period: 10s + server_status_path: /server-status +`; + const expectedJson = [ + { + id: 'logfile-apache.access', + type: 'logfile', + data_stream: { + type: 'logs', + dataset: 'apache.access', + }, + paths: [ + '/var/log/apache2/access.log*', + '/var/log/apache2/other_vhosts_access.log*', + '/var/log/httpd/access_log*', + ], + exclude_files: ['.gz$'], + processors: [ + { + add_fields: { + target: '', + fields: { + 'ecs.version': '1.5.0', + }, + }, + }, + ], + }, + { + id: 'logfile-apache.error', + type: 'logfile', + data_stream: { + type: 'logs', + dataset: 'apache.error', + }, + paths: ['/var/log/apache2/error.log*', '/var/log/httpd/error_log*'], + exclude_files: ['.gz$'], + processors: [ + { + add_locale: null, + }, + { + add_fields: { + target: '', + fields: { + 'ecs.version': '1.5.0', + }, + }, + }, + ], + }, + { + id: 'apache/metrics-apache.status', + type: 'apache/metrics', + data_stream: { + type: 'metrics', + dataset: 'apache.status', + }, + metricsets: ['status'], + hosts: ['http://127.0.0.1'], + period: '10s', + server_status_path: '/server-status', + }, + ]; + + it('returns inputs template in json format', async function () { + const res = await supertest + .get(`/api/fleet/epm/templates/${testPkgName}/${testPkgVersion}/inputs?format=json`) + .expect(200); + const inputs = res.body.inputs; + + expect(inputs).to.eql(expectedJson); + }); + + it('returns inputs template in yaml format if format=yaml', async function () { + const res = await supertest + .get(`/api/fleet/epm/templates/${testPkgName}/${testPkgVersion}/inputs?format=yaml`) + .expect(200); + + expect(res.text).to.eql(expectedYml); + }); + + it('returns inputs template in yaml format if format=yml', async function () { + const res = await supertest + .get(`/api/fleet/epm/templates/${testPkgName}/${testPkgVersion}/inputs?format=yml`) + .expect(200); + expect(res.text).to.eql(expectedYml); + }); + + it('returns a 404 for a version that does not exists', async function () { + await supertest + .get(`/api/fleet/epm/templates/${testPkgName}/0.1.0/inputs?format=json`) + .expect(404); + }); + + it('allows user with only fleet permission to access', async () => { + await supertestWithoutAuth + .get(`/api/fleet/epm/templates/${testPkgName}/${testPkgVersion}/inputs?format=json`) + .auth(testUsers.fleet_all_only.username, testUsers.fleet_all_only.password) + .expect(200); + }); + + it('allows user with integrations read permission to access', async () => { + await supertestWithoutAuth + .get(`/api/fleet/epm/templates/${testPkgName}/${testPkgVersion}/inputs?format=json`) + .auth(testUsers.fleet_all_int_read.username, testUsers.fleet_all_int_read.password) + .expect(200); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/epm/index.js b/x-pack/test/fleet_api_integration/apis/epm/index.js index 045626e95a7406..94f7c12a15ce4d 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/index.js +++ b/x-pack/test/fleet_api_integration/apis/epm/index.js @@ -46,5 +46,6 @@ export default function loadTests({ loadTestFile, getService }) { loadTestFile(require.resolve('./install_dynamic_template_metric')); loadTestFile(require.resolve('./routing_rules')); loadTestFile(require.resolve('./install_runtime_field')); + loadTestFile(require.resolve('./get_templates_inputs')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts index a65d92dea06585..deea626b28f249 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default ({ getPageObject, getService }: FtrProviderContext) => { const common = getPageObject('common'); + const header = getPageObject('header'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); const svlCommonPage = getPageObject('svlCommonPage'); const svlObltNavigation = getService('svlObltNavigation'); @@ -19,15 +20,15 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const retry = getService('retry'); const find = getService('find'); - describe.only('Configure Case', function () { + describe('Configure Case', function () { // Error: timed out waiting for assertRadioGroupValue: Expected the radio group value to equal "close-by-pushing" this.tags(['failsOnMKI']); before(async () => { await svlCommonPage.login(); - await svlObltNavigation.navigateToLandingPage(); - await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' }); + await common.clickAndValidate('configure-case-button', 'case-configure-title'); + await header.waitUntilLoadingHasFinished(); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts index e74deac161b26e..5c71abf3ad7ba4 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default ({ getPageObject, getService }: FtrProviderContext) => { const common = getPageObject('common'); + const header = getPageObject('header'); const svlCommonPage = getPageObject('svlCommonPage'); const svlSecNavigation = getService('svlSecNavigation'); const testSubjects = getService('testSubjects'); @@ -23,12 +24,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { this.tags(['failsOnMKI']); before(async () => { await svlCommonPage.login(); - await svlSecNavigation.navigateToLandingPage(); - await testSubjects.click('solutionSideNavItemLink-cases'); - await common.clickAndValidate('configure-case-button', 'case-configure-title'); + await header.waitUntilLoadingHasFinished(); }); after(async () => {