[Fleet] Add sync integrations fields to remote ES output (#208516)

## Summary

Closes https://github.com/elastic/kibana/issues/192361

Added `sync_integrations` flag to remote elasticsearch output to the API
and UI with the Remote Kibana URL and API key (secret).

To test:
- enable feature flag: `xpack.fleet.enableExperimental:
['enableSyncIntegrationsOnRemote']`
- add/edit output and enable Synchronize integrations switch
- When the switch is enabled, Kibana URL and API key is required,
otherwise optional (but still has to be valid URL)
- test preconfigured output with the new fields

<img width="724" alt="image"
src="https://github.com/user-attachments/assets/5cf4c0b1-f8c3-4447-a0ef-a8aa8e362277"
/>

Example preconfig:
```
  - name: 'Preconfiged remote output'
    type: 'remote_elasticsearch'
    id: 'remote-output2'
    hosts: ["http://192.168.64.1:9200"]
    sync_integrations: true
    kibana_url: "http://localhost:5601"
    secrets:
      service_token: token
      kibana_api_key: key
```

Added callout to help create the API key (privileges have to be
confirmed when https://github.com/elastic/kibana/issues/192363 is done)

<img width="714" alt="image"
src="https://github.com/user-attachments/assets/5a5e7a8c-0a56-4234-ad66-b15f5f53de76"
/>


### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Julia Bardi 2025-02-03 12:27:34 +01:00 committed by GitHub
parent 8fe5738b24
commit 07a61abfd4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1053 additions and 33 deletions

View file

@ -26835,6 +26835,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -26855,6 +26863,25 @@
"secrets": {
"additionalProperties": true,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": true,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -26968,6 +26995,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -27925,6 +27955,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -27945,6 +27983,25 @@
"secrets": {
"additionalProperties": false,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -28058,6 +28115,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -28953,6 +29013,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -28973,6 +29041,25 @@
"secrets": {
"additionalProperties": true,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": true,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -29086,6 +29173,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -30131,6 +30221,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -30151,6 +30249,25 @@
"secrets": {
"additionalProperties": true,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": true,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -30264,6 +30381,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -31206,6 +31326,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -31226,6 +31354,25 @@
"secrets": {
"additionalProperties": false,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -31339,6 +31486,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -32219,6 +32369,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -32239,6 +32397,25 @@
"secrets": {
"additionalProperties": true,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": true,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -32352,6 +32529,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"

View file

@ -26835,6 +26835,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -26855,6 +26863,25 @@
"secrets": {
"additionalProperties": true,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": true,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -26968,6 +26995,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -27925,6 +27955,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -27945,6 +27983,25 @@
"secrets": {
"additionalProperties": false,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -28058,6 +28115,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -28953,6 +29013,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -28973,6 +29041,25 @@
"secrets": {
"additionalProperties": true,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": true,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -29086,6 +29173,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -30131,6 +30221,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -30151,6 +30249,25 @@
"secrets": {
"additionalProperties": true,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": true,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -30264,6 +30381,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -31206,6 +31326,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -31226,6 +31354,25 @@
"secrets": {
"additionalProperties": false,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -31339,6 +31486,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"
@ -32219,6 +32369,14 @@
"is_preconfigured": {
"type": "boolean"
},
"kibana_api_key": {
"nullable": true,
"type": "string"
},
"kibana_url": {
"nullable": true,
"type": "string"
},
"name": {
"type": "string"
},
@ -32239,6 +32397,25 @@
"secrets": {
"additionalProperties": true,
"properties": {
"kibana_api_key": {
"anyOf": [
{
"additionalProperties": true,
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
{
"type": "string"
}
]
},
"service_token": {
"anyOf": [
{
@ -32352,6 +32529,9 @@
},
"type": "object"
},
"sync_integrations": {
"type": "boolean"
},
"type": {
"enum": [
"remote_elasticsearch"

View file

@ -24805,6 +24805,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -24822,6 +24828,16 @@ paths:
additionalProperties: true
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: true
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: true
@ -24901,6 +24917,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -25532,6 +25550,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -25549,6 +25573,16 @@ paths:
additionalProperties: false
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: false
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: false
@ -25628,6 +25662,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -26221,6 +26257,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -26238,6 +26280,16 @@ paths:
additionalProperties: true
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: true
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: true
@ -26317,6 +26369,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -27006,6 +27060,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -27023,6 +27083,16 @@ paths:
additionalProperties: true
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: true
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: true
@ -27102,6 +27172,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -27720,6 +27792,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -27737,6 +27815,16 @@ paths:
additionalProperties: false
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: false
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: false
@ -27816,6 +27904,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -28396,6 +28486,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -28413,6 +28509,16 @@ paths:
additionalProperties: true
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: true
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: true
@ -28492,6 +28598,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch

View file

@ -26794,6 +26794,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -26811,6 +26817,16 @@ paths:
additionalProperties: true
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: true
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: true
@ -26890,6 +26906,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -27520,6 +27538,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -27537,6 +27561,16 @@ paths:
additionalProperties: false
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: false
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: false
@ -27616,6 +27650,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -28209,6 +28245,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -28226,6 +28268,16 @@ paths:
additionalProperties: true
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: true
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: true
@ -28305,6 +28357,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -28992,6 +29046,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -29009,6 +29069,16 @@ paths:
additionalProperties: true
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: true
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: true
@ -29088,6 +29158,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -29705,6 +29777,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -29722,6 +29800,16 @@ paths:
additionalProperties: false
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: false
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: false
@ -29801,6 +29889,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch
@ -30381,6 +30471,12 @@ paths:
type: boolean
is_preconfigured:
type: boolean
kibana_api_key:
nullable: true
type: string
kibana_url:
nullable: true
type: string
name:
type: string
preset:
@ -30398,6 +30494,16 @@ paths:
additionalProperties: true
type: object
properties:
kibana_api_key:
anyOf:
- additionalProperties: true
type: object
properties:
id:
type: string
required:
- id
- type: string
service_token:
anyOf:
- additionalProperties: true
@ -30477,6 +30583,8 @@ paths:
- certificate
- strict
type: string
sync_integrations:
type: boolean
type:
enum:
- remote_elasticsearch

View file

@ -2115,6 +2115,7 @@
}
},
"ingest-outputs": {
"dynamic": false,
"properties": {
"allow_edit": {
"enabled": false

View file

@ -126,7 +126,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"infrastructure-ui-source": "113182d6895764378dfe7fa9fa027244f3a457c4",
"ingest-agent-policies": "57ebfb047cf0b81c6fa0ceed8586fa7199c7c5e2",
"ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d",
"ingest-outputs": "55988d5f778bbe0e76caa7e6468707a0a056bdd8",
"ingest-outputs": "6743521f501bd77b1523dbb1df48d7c47fdad529",
"ingest-package-policies": "870f8c21fe3602f31075430a1fdfb052c62d4a14",
"ingest_manager_settings": "111a616eb72627c002029c19feb9e6c439a10505",
"inventory-view": "fd2b7fe713956f261018dded00d8f8c986417763",

View file

@ -11,6 +11,7 @@ const _allowedExperimentalValues = {
showExperimentalShipperOptions: false,
useSpaceAwareness: false,
enableAutomaticAgentUpgrades: false,
enableSyncIntegrationsOnRemote: false,
};
/**

View file

@ -65,7 +65,11 @@ export interface NewRemoteElasticsearchOutput extends NewBaseOutput {
service_token?: string | null;
secrets?: {
service_token?: OutputSecret;
kibana_api_key?: OutputSecret;
};
sync_integrations?: boolean;
kibana_url?: string | null;
kibana_api_key?: string | null;
}
export interface NewLogstashOutput extends NewBaseOutput {

View file

@ -319,7 +319,9 @@ describe('EditOutputFlyout', () => {
});
it('should render the flyout if the output provided is a remote ES output', async () => {
jest.spyOn(ExperimentalFeaturesService, 'get').mockReturnValue({} as any);
jest
.spyOn(ExperimentalFeaturesService, 'get')
.mockReturnValue({ enableSyncIntegrationsOnRemote: true } as any);
mockedUseFleetStatus.mockReturnValue({
isLoading: false,
@ -333,6 +335,8 @@ describe('EditOutputFlyout', () => {
id: 'outputR',
is_default: false,
is_default_monitoring: false,
kibana_url: 'http://localhost',
sync_integrations: true,
});
remoteEsOutputLabels.forEach((label) => {
@ -345,10 +349,18 @@ describe('EditOutputFlyout', () => {
);
expect(utils.queryByTestId('serviceTokenSecretInput')).not.toBeNull();
expect(utils.queryByTestId('kibanaAPIKeyCallout')).not.toBeNull();
expect(
(utils.getByTestId('settingsOutputsFlyout.kibanaURLInput') as HTMLInputElement).value
).toEqual('http://localhost');
expect(utils.queryByTestId('kibanaAPIKeySecretInput')).not.toBeNull();
});
it('should populate secret service token input with plain text value when editing remote ES output', async () => {
jest.spyOn(ExperimentalFeaturesService, 'get').mockReturnValue({} as any);
jest
.spyOn(ExperimentalFeaturesService, 'get')
.mockReturnValue({ enableSyncIntegrationsOnRemote: true } as any);
mockedUseFleetStatus.mockReturnValue({
isLoading: false,
@ -364,11 +376,13 @@ describe('EditOutputFlyout', () => {
is_default_monitoring: false,
service_token: '1234',
hosts: ['https://localhost:9200'],
kibana_api_key: 'key',
});
expect((utils.getByTestId('serviceTokenSecretInput') as HTMLInputElement).value).toEqual(
'1234'
);
expect((utils.getByTestId('kibanaAPIKeySecretInput') as HTMLInputElement).value).toEqual('key');
fireEvent.click(utils.getByText('Save and apply settings'));
@ -376,8 +390,9 @@ describe('EditOutputFlyout', () => {
expect(mockSendPutOutput).toHaveBeenCalledWith(
'outputR',
expect.objectContaining({
secrets: { service_token: '1234' },
secrets: { service_token: '1234', kibana_api_key: 'key' },
service_token: undefined,
kibana_api_key: undefined,
})
);
});

View file

@ -6,12 +6,21 @@
*/
import React, { useEffect } from 'react';
import { EuiCallOut, EuiCodeBlock, EuiFieldText, EuiSpacer } from '@elastic/eui';
import {
EuiCallOut,
EuiCodeBlock,
EuiFieldText,
EuiFormRow,
EuiSpacer,
EuiSwitch,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { MultiRowInput } from '../multi_row_input';
import { ExperimentalFeaturesService } from '../../../../services';
import type { OutputFormInputsType } from './use_output_form';
import { SecretFormRow } from './output_form_secret_form_row';
@ -25,7 +34,9 @@ export const OutputFormRemoteEsSection: React.FunctionComponent<Props> = (props)
const { inputs, useSecretsStorage, onToggleSecretStorage } = props;
const [isConvertedToSecret, setIsConvertedToSecret] = React.useState({
serviceToken: false,
kibanaAPIKey: false,
});
const { enableSyncIntegrationsOnRemote } = ExperimentalFeaturesService.get();
const [isFirstLoad, setIsFirstLoad] = React.useState(true);
@ -34,16 +45,30 @@ export const OutputFormRemoteEsSection: React.FunctionComponent<Props> = (props)
setIsFirstLoad(false);
// populate the secret input with the value of the plain input in order to re-save the output with secret storage
if (useSecretsStorage) {
let isServiceTokenSecret = false;
if (inputs.serviceTokenInput.value && !inputs.serviceTokenSecretInput.value) {
inputs.serviceTokenSecretInput.setValue(inputs.serviceTokenInput.value);
inputs.serviceTokenInput.clear();
setIsConvertedToSecret({ serviceToken: true });
isServiceTokenSecret = true;
}
let isKibanaAPIKeySecret = false;
if (inputs.kibanaAPIKeyInput.value && !inputs.kibanaAPIKeySecretInput.value) {
inputs.kibanaAPIKeySecretInput.setValue(inputs.kibanaAPIKeyInput.value);
inputs.kibanaAPIKeyInput.clear();
isKibanaAPIKeySecret = true;
}
setIsConvertedToSecret({
...isConvertedToSecret,
serviceToken: isServiceTokenSecret,
kibanaAPIKey: isKibanaAPIKeySecret,
});
}
}, [
useSecretsStorage,
inputs.serviceTokenInput,
inputs.serviceTokenSecretInput,
inputs.kibanaAPIKeyInput,
inputs.kibanaAPIKeySecretInput,
isFirstLoad,
setIsFirstLoad,
isConvertedToSecret,
@ -52,10 +77,12 @@ export const OutputFormRemoteEsSection: React.FunctionComponent<Props> = (props)
const onToggleSecretAndClearValue = (secretEnabled: boolean) => {
if (secretEnabled) {
inputs.serviceTokenInput.clear();
inputs.kibanaAPIKeyInput.clear();
} else {
inputs.serviceTokenSecretInput.setValue('');
inputs.kibanaAPIKeySecretInput.setValue('');
}
setIsConvertedToSecret({ ...isConvertedToSecret, serviceToken: false });
setIsConvertedToSecret({ ...isConvertedToSecret, serviceToken: false, kibanaAPIKey: false });
onToggleSecretStorage(secretEnabled);
};
@ -144,6 +171,132 @@ export const OutputFormRemoteEsSection: React.FunctionComponent<Props> = (props)
</EuiCodeBlock>
</EuiCallOut>
<EuiSpacer size="m" />
{enableSyncIntegrationsOnRemote ? (
<>
<EuiFormRow
fullWidth
helpText={
<FormattedMessage
id="xpack.fleet.settings.editOutputFlyout.syncIntegrationsFormRowLabel"
defaultMessage="If enabled, Integration assets will be installed on the remote Elasticsearch cluster"
/>
}
{...inputs.syncIntegrationsInput.formRowProps}
>
<EuiSwitch
{...inputs.syncIntegrationsInput.props}
label={
<FormattedMessage
id="xpack.fleet.settings.editOutputFlyout.syncIntegrationsSwitchLabel"
defaultMessage="Synchronize integrations"
/>
}
/>
</EuiFormRow>
<EuiFormRow
fullWidth
label={
<FormattedMessage
id="xpack.fleet.settings.editOutputFlyout.kibanaURLInputLabel"
defaultMessage="Remote Kibana URL"
/>
}
{...inputs.kibanaURLInput.formRowProps}
>
<EuiFieldText
data-test-subj="settingsOutputsFlyout.kibanaURLInput"
fullWidth
{...inputs.kibanaURLInput.props}
placeholder={i18n.translate(
'xpack.fleet.settings.editOutputFlyout.kibanaURLInputPlaceholder',
{
defaultMessage: 'Specify Kibana URL',
}
)}
/>
</EuiFormRow>
<EuiSpacer size="m" />
{!useSecretsStorage ? (
<SecretFormRow
fullWidth
label={
<FormattedMessage
id="xpack.fleet.settings.editOutputFlyout.kibanaAPIKeyLabel"
defaultMessage="Remote Kibana API Key"
/>
}
{...inputs.kibanaAPIKeyInput.formRowProps}
useSecretsStorage={useSecretsStorage}
onToggleSecretStorage={onToggleSecretAndClearValue}
>
<EuiFieldText
fullWidth
data-test-subj="kibanaAPIKeySecretInput"
{...inputs.kibanaAPIKeyInput.props}
placeholder={i18n.translate(
'xpack.fleet.settings.editOutputFlyout.kibanaAPIKeyPlaceholder',
{
defaultMessage: 'Specify Kibana API Key',
}
)}
/>
</SecretFormRow>
) : (
<SecretFormRow
fullWidth
title={i18n.translate('xpack.fleet.settings.editOutputFlyout.kibanaAPIKeyLabel', {
defaultMessage: 'Remote Kibana API Key',
})}
{...inputs.kibanaAPIKeySecretInput.formRowProps}
cancelEdit={inputs.kibanaAPIKeySecretInput.cancelEdit}
useSecretsStorage={useSecretsStorage}
isConvertedToSecret={isConvertedToSecret.kibanaAPIKey}
onToggleSecretStorage={onToggleSecretAndClearValue}
>
<EuiFieldText
data-test-subj="kibanaAPIKeySecretInput"
fullWidth
{...inputs.kibanaAPIKeySecretInput.props}
placeholder={i18n.translate(
'xpack.fleet.settings.editOutputFlyout.kibanaAPIKeyPlaceholder',
{
defaultMessage: 'Specify Kibana API Key',
}
)}
/>
</SecretFormRow>
)}
<EuiSpacer size="m" />
<EuiCallOut
title={
<FormattedMessage
id="xpack.fleet.settings.editOutputFlyout.kibanaAPIKeyCalloutText"
defaultMessage="Create an API Key by running this API request in the Remote Kibana Console and copy the encoded value"
/>
}
data-test-subj="kibanaAPIKeyCallout"
>
<EuiCodeBlock isCopyable={true}>
{` POST /_security/api_key
{
"name": "integration_sync_api_key",
"role_descriptors": {
"integration_writer": {
"cluster": [],
"indices":[],
"applications": [{
"application": "kibana-.kibana",
"privileges": ["feature_fleet.read", "feature_fleetv2.read"],
"resources": ["*"]
}]
}
}
}`}
</EuiCodeBlock>
</EuiCallOut>
<EuiSpacer size="m" />
</>
) : null}
</>
);
};

View file

@ -12,6 +12,8 @@ import {
validateCATrustedFingerPrint,
validateKafkaHeaders,
validateKafkaHosts,
validateKibanaURL,
validateKibanaAPIKey,
} from './output_form_validators';
describe('Output form validation', () => {
@ -130,6 +132,58 @@ describe('Output form validation', () => {
});
});
describe('validateKibanaURL', () => {
it('should not work with empty url', () => {
const res = validateKibanaURL('', true);
expect(res).toEqual(['URL is required']);
});
it('should work with empty url if syncEnabled is false', () => {
const res = validateKibanaURL('', false);
expect(res).toBeUndefined();
});
it('should work with valid url', () => {
const res = validateKibanaURL('https://test.fr:9200', true);
expect(res).toBeUndefined();
});
it('should return an error with invalid url', () => {
const res = validateKibanaURL('toto', false);
expect(res).toEqual(['Invalid URL']);
});
it('should return an error with url with invalid port', () => {
const res = validateKibanaURL('https://test.fr:qwerty9200', true);
expect(res).toEqual(['Invalid URL']);
});
it('should return an error when invalid protocol', () => {
const res = validateKibanaURL('ftp://test.fr', false);
expect(res).toEqual(['Invalid protocol']);
});
});
describe('validateKibanaAPIKey', () => {
it('should not work with empty url', () => {
const res = validateKibanaAPIKey('');
expect(res).toEqual(['Kibana API Key is required']);
});
it('should work with valid url', () => {
const res = validateKibanaAPIKey('apikey');
expect(res).toBeUndefined();
});
});
describe('validateLogstashHosts', () => {
it('should not work without any urls', () => {
const res = validateLogstashHosts([]);

View file

@ -241,6 +241,35 @@ export function validateName(value: string) {
}
}
export function validateKibanaURL(val: string, syncEnabled: boolean) {
try {
if (syncEnabled && !val) {
return [
i18n.translate('xpack.fleet.settings.outputForm.urlRequiredError', {
defaultMessage: 'URL is required',
}),
];
} else if (!val) {
return;
} else {
const urlParsed = new URL(val);
if (!['http:', 'https:'].includes(urlParsed.protocol)) {
return [
i18n.translate('xpack.fleet.settings.outputForm.invalidProtocolError', {
defaultMessage: 'Invalid protocol',
}),
];
}
}
} catch (error) {
return [
i18n.translate('xpack.fleet.settings.outputForm.invalidURLError', {
defaultMessage: 'Invalid URL',
}),
];
}
}
export function validateKafkaUsername(value: string) {
if (!value || value === '') {
return [
@ -286,6 +315,18 @@ export function validateServiceToken(value: string) {
export const validateServiceTokenSecret = toSecretValidator(validateServiceToken);
export function validateKibanaAPIKey(value: string) {
if (!value || value === '') {
return [
i18n.translate('xpack.fleet.settings.outputForm.kibanaAPIKeyRequiredErrorMessage', {
defaultMessage: 'Kibana API Key is required',
}),
];
}
}
export const validateKibanaAPIKeySecret = toSecretValidator(validateKibanaAPIKey);
export function validateSSLCertificate(value: string) {
if (!value || value === '') {
return [

View file

@ -74,6 +74,9 @@ import {
validateKafkaHosts,
validateKafkaPartitioningGroupEvents,
validateDynamicKafkaTopics,
validateKibanaURL,
validateKibanaAPIKey,
validateKibanaAPIKeySecret,
} from './output_form_validators';
import { confirmUpdate } from './confirm_update';
@ -97,6 +100,10 @@ export interface OutputFormInputsType {
caTrustedFingerprintInput: ReturnType<typeof useInput>;
serviceTokenInput: ReturnType<typeof useInput>;
serviceTokenSecretInput: ReturnType<typeof useSecretInput>;
syncIntegrationsInput: ReturnType<typeof useSwitchInput>;
kibanaURLInput: ReturnType<typeof useInput>;
kibanaAPIKeyInput: ReturnType<typeof useInput>;
kibanaAPIKeySecretInput: ReturnType<typeof useSecretInput>;
sslCertificateInput: ReturnType<typeof useInput>;
sslKeyInput: ReturnType<typeof useInput>;
sslKeySecretInput: ReturnType<typeof useSecretInput>;
@ -262,7 +269,7 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
isDisabled('preset')
);
// Remtote ES inputs
// Remote ES inputs
const serviceTokenInput = useInput(
(output as NewRemoteElasticsearchOutput)?.service_token ?? '',
validateServiceToken,
@ -274,6 +281,29 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
validateServiceTokenSecret,
isDisabled('service_token')
);
const syncIntegrationsInput = useSwitchInput(
(output as NewRemoteElasticsearchOutput)?.sync_integrations ?? false,
isDisabled('sync_integrations')
);
const kibanaAPIKeyInput = useInput(
(output as NewRemoteElasticsearchOutput)?.kibana_api_key ?? '',
syncIntegrationsInput.value ? validateKibanaAPIKey : undefined,
isDisabled('kibana_api_key')
);
const kibanaAPIKeySecretInput = useSecretInput(
(output as NewRemoteElasticsearchOutput)?.secrets?.kibana_api_key ?? '',
syncIntegrationsInput.value ? validateKibanaAPIKeySecret : undefined,
isDisabled('kibana_api_key')
);
const kibanaURLInput = useInput(
(output as NewRemoteElasticsearchOutput)?.kibana_url ?? '',
(val) => validateKibanaURL(val, syncIntegrationsInput.value),
isDisabled('kibana_url')
);
/*
Shipper feature flag - currently depends on the content of the yaml
# Enables the shipper:
@ -556,6 +586,10 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
caTrustedFingerprintInput,
serviceTokenInput,
serviceTokenSecretInput,
kibanaAPIKeyInput,
kibanaAPIKeySecretInput,
syncIntegrationsInput,
kibanaURLInput,
sslCertificateInput,
sslKeyInput,
sslKeySecretInput,
@ -615,6 +649,9 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
const caTrustedFingerprintValid = caTrustedFingerprintInput.validate();
const serviceTokenValid = serviceTokenInput.validate();
const serviceTokenSecretValid = serviceTokenSecretInput.validate();
const kibanaAPIKeyValid = kibanaAPIKeyInput.validate();
const kibanaAPIKeySecretValid = kibanaAPIKeySecretInput.validate();
const kibanaURLInputValid = kibanaURLInput.validate();
const sslCertificateValid = sslCertificateInput.validate();
const sslKeyValid = sslKeyInput.validate();
const sslKeySecretValid = sslKeySecretInput.validate();
@ -666,7 +703,12 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
additionalYamlConfigValid &&
nameInputValid &&
((serviceTokenInput.value && serviceTokenValid) ||
(serviceTokenSecretInput.value && serviceTokenSecretValid))
(serviceTokenSecretInput.value && serviceTokenSecretValid)) &&
((!syncIntegrationsInput.value && kibanaURLInputValid) ||
(syncIntegrationsInput.value &&
((kibanaAPIKeyInput.value && kibanaAPIKeyValid) ||
(kibanaAPIKeySecretInput.value && kibanaAPIKeySecretValid)) &&
kibanaURLInputValid))
);
} else {
// validate ES
@ -695,6 +737,10 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
caTrustedFingerprintInput,
serviceTokenInput,
serviceTokenSecretInput,
kibanaAPIKeyInput,
kibanaAPIKeySecretInput,
syncIntegrationsInput,
kibanaURLInput,
sslCertificateInput,
sslKeyInput,
sslKeySecretInput,
@ -912,6 +958,18 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
...shipperParams,
} as NewLogstashOutput;
case outputType.RemoteElasticsearch:
let secrets;
if (!serviceTokenInput.value && serviceTokenSecretInput.value) {
secrets = {
service_token: serviceTokenSecretInput.value,
};
}
if (!kibanaAPIKeyInput.value && kibanaAPIKeySecretInput.value) {
secrets = {
...(secrets ?? {}),
kibana_api_key: kibanaAPIKeySecretInput.value,
};
}
return {
name: nameInput.value,
type: outputType.RemoteElasticsearch,
@ -921,12 +979,10 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
preset: presetInput.value,
config_yaml: additionalYamlConfigInput.value,
service_token: serviceTokenInput.value || undefined,
...(!serviceTokenInput.value &&
serviceTokenSecretInput.value && {
secrets: {
service_token: serviceTokenSecretInput.value,
},
}),
kibana_api_key: kibanaAPIKeyInput.value || undefined,
...(secrets ? { secrets } : {}),
sync_integrations: syncIntegrationsInput.value,
kibana_url: kibanaURLInput.value || null,
proxy_id: proxyIdValue,
...shipperParams,
} as NewRemoteElasticsearchOutput;
@ -1036,6 +1092,10 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
presetInput.value,
serviceTokenInput.value,
serviceTokenSecretInput.value,
kibanaAPIKeyInput.value,
kibanaAPIKeySecretInput.value,
syncIntegrationsInput.value,
kibanaURLInput.value,
caTrustedFingerprintInput.value,
confirm,
notifications.toasts,

View file

@ -258,4 +258,41 @@ describe('output handler', () => {
expect(res).toEqual({ body: { item: { id: 'output1' } } });
});
it('should return error if both kibana_api_key and secrets.kibana_api_key is provided for remote_elasticsearch output', async () => {
jest
.spyOn(appContextService, 'getCloud')
.mockReturnValue({ isServerlessEnabled: false } as any);
const res = await postOutputHandlerWithErrorHandler(
mockContext,
{
body: {
type: 'remote_elasticsearch',
kibana_api_key: 'value1',
secrets: { kibana_api_key: 'value2' },
},
} as any,
mockResponse as any
);
expect(res).toEqual({
body: { message: 'Cannot specify both kibana_api_key and secrets.kibana_api_key' },
statusCode: 400,
});
});
it('should return ok if one of kibana_api_key and secrets.kibana_api_key is provided for remote_elasticsearch output', async () => {
jest
.spyOn(appContextService, 'getCloud')
.mockReturnValue({ isServerlessEnabled: false } as any);
const res = await postOutputHandlerWithErrorHandler(
mockContext,
{ body: { type: 'remote_elasticsearch', secrets: { kibana_api_key: 'value2' } } } as any,
mockResponse as any
);
expect(res).toEqual({ body: { item: { id: 'output1' } } });
});
});

View file

@ -44,12 +44,13 @@ function ensureNoDuplicateSecrets(output: Partial<Output>) {
) {
throw Boom.badRequest('Cannot specify both ssl.key and secrets.ssl.key');
}
if (
output.type === outputType.RemoteElasticsearch &&
output.service_token &&
output.secrets?.service_token
) {
throw Boom.badRequest('Cannot specify both service_token and secrets.service_token');
if (output.type === outputType.RemoteElasticsearch) {
if (output.service_token && output.secrets?.service_token) {
throw Boom.badRequest('Cannot specify both service_token and secrets.service_token');
}
if (output.kibana_api_key && output.secrets?.kibana_api_key) {
throw Boom.badRequest('Cannot specify both kibana_api_key and secrets.kibana_api_key');
}
}
}

View file

@ -402,6 +402,7 @@ export const getSavedObjectTypes = (
importableAndExportable: false,
},
mappings: {
dynamic: false,
properties: {
output_id: { type: 'keyword', index: false },
name: { type: 'keyword' },
@ -613,6 +614,14 @@ export const getSavedObjectTypes = (
},
],
},
'8': {
changes: [
{
type: 'mappings_addition',
addedMappings: {},
},
],
},
},
migrations: {
'7.13.0': migrateOutputToV7130,

View file

@ -309,8 +309,11 @@ Object {
"hosts": Array [
"http://127.0.0.1:9201",
],
"kibana_api_key": undefined,
"kibana_url": undefined,
"preset": "balanced",
"service_token": undefined,
"sync_integrations": undefined,
"type": "remote_elasticsearch",
},
},

View file

@ -518,6 +518,9 @@ export function transformOutputToFullPolicyOutput(
if (output.type === outputType.RemoteElasticsearch) {
newOutput.service_token = output.service_token;
newOutput.kibana_api_key = output.kibana_api_key;
newOutput.kibana_url = output.kibana_url;
newOutput.sync_integrations = output.sync_integrations;
}
if (outputTypeSupportPresets(output.type)) {

View file

@ -2183,6 +2183,7 @@ describe('Output Service', () => {
expect(soClient.update).toBeCalledWith(expect.anything(), expect.anything(), {
type: 'remote_elasticsearch',
kibana_api_key: null,
service_token: null,
});
});

View file

@ -696,6 +696,9 @@ class OutputService {
if (!output.service_token && output.secrets?.service_token) {
data.service_token = output.secrets?.service_token as string;
}
if (!output.kibana_api_key && output.secrets?.kibana_api_key) {
data.kibana_api_key = output.secrets?.kibana_api_key as string;
}
}
}
@ -950,6 +953,7 @@ class OutputService {
updateData.ca_trusted_fingerprint = null;
updateData.ca_sha256 = null;
delete (updateData as Nullable<OutputSoRemoteElasticsearchAttributes>).service_token;
delete (updateData as Nullable<OutputSoRemoteElasticsearchAttributes>).kibana_api_key;
}
if (data.type !== outputType.Logstash) {
@ -1072,6 +1076,9 @@ class OutputService {
if (!data.service_token) {
updateData.service_token = null;
}
if (!data.kibana_api_key) {
updateData.kibana_api_key = null;
}
}
if (!data.preset && data.type === outputType.Elasticsearch) {
@ -1121,6 +1128,9 @@ class OutputService {
if (!data.service_token && data.secrets?.service_token) {
updateData.service_token = data.secrets?.service_token as string;
}
if (!data.kibana_api_key && data.secrets?.kibana_api_key) {
updateData.kibana_api_key = data.secrets?.kibana_api_key as string;
}
}
}

View file

@ -79,6 +79,7 @@ describe('output preconfiguration', () => {
const keyHash = (await hashSecret('secretKey')) as string;
const passwordHash = (await hashSecret('secretPassword')) as string;
const serviceTokenHash = (await hashSecret('secretServiceToken')) as string;
const serviceKibanaAPIKeyHash = (await hashSecret('secretKibanaAPIKey')) as string;
mockedOutputService.bulkGet.mockImplementation(async (soClient, id): Promise<Output[]> => {
return [
{
@ -221,6 +222,7 @@ describe('output preconfiguration', () => {
hosts: ['https:es.co:80'],
is_preconfigured: true,
service_token: 'unsecureServiceToken',
kibana_api_key: 'unsecureKibanaApiKey',
},
{
id: 'existing-remote-es-output-with-secrets-1',
@ -235,6 +237,10 @@ describe('output preconfiguration', () => {
id: '101112',
hash: serviceTokenHash,
},
kibana_api_key: {
id: '131415',
hash: serviceKibanaAPIKeyHash,
},
},
},
{
@ -247,6 +253,7 @@ describe('output preconfiguration', () => {
is_preconfigured: true,
secrets: {
service_token: 'secretServiceToken',
kibana_api_key: 'secretKibanaAPIKey',
},
},
];
@ -456,6 +463,9 @@ describe('output preconfiguration', () => {
is_default_monitoring: false,
hosts: ['test.fr'],
service_token: 'serviceToken',
kibana_api_key: 'kibanaAPIKey',
kibana_url: 'http://kibana.co',
sync_integrations: true,
} as PreconfiguredOutput,
]);
@ -809,6 +819,7 @@ describe('output preconfiguration', () => {
is_preconfigured: true,
secrets: {
service_token: 'secretServiceToken', // no change
kibana_api_key: 'secretKibanaAPIKey',
},
},
]);
@ -898,6 +909,7 @@ describe('output preconfiguration', () => {
hosts: ['https:es.co:80'],
is_preconfigured: true,
service_token: 'unsecureServiceToken',
kibana_api_key: 'unsecureKibanaAPIKey',
} as PreconfiguredOutput,
]);
@ -969,6 +981,7 @@ describe('output preconfiguration', () => {
is_preconfigured: true,
secrets: {
service_token: 'secretServiceToken',
kibana_api_key: 'secretKibanaAPIKey',
},
},
]);

View file

@ -198,12 +198,21 @@ async function hashSecrets(output: PreconfiguredOutput) {
}
if (output.type === 'remote_elasticsearch') {
const remoteESOutput = output as NewRemoteElasticsearchOutput;
let secrets;
if (typeof remoteESOutput.secrets?.service_token === 'string') {
const serviceToken = await hashSecret(remoteESOutput.secrets?.service_token);
return {
secrets = {
service_token: serviceToken,
};
}
if (typeof remoteESOutput.secrets?.kibana_api_key === 'string') {
const kibanaAPIKey = await hashSecret(remoteESOutput.secrets?.kibana_api_key);
secrets = {
...(secrets ? secrets : {}),
kibana_api_key: kibanaAPIKey,
};
}
return secrets;
}
return undefined;
@ -350,10 +359,17 @@ async function isPreconfiguredOutputDifferentFromCurrent(
) {
return false;
}
const serviceTokenIsDifferent = await isSecretDifferent(
preconfiguredOutput.secrets?.service_token,
existingOutput.secrets?.service_token
);
const serviceTokenIsDifferent =
(await isSecretDifferent(
preconfiguredOutput.secrets?.service_token,
existingOutput.secrets?.service_token
)) ||
(await isSecretDifferent(
preconfiguredOutput.secrets?.kibana_api_key,
existingOutput.secrets?.kibana_api_key
)) ||
isDifferent(existingOutput.kibana_url, preconfiguredOutput.kibana_url) ||
isDifferent(existingOutput.sync_integrations, preconfiguredOutput.sync_integrations);
return serviceTokenIsDifferent;
};

View file

@ -333,6 +333,12 @@ function getOutputSecretPaths(
value: remoteESOutput.secrets.service_token,
});
}
if (remoteESOutput.secrets?.kibana_api_key) {
outputSecretPaths.push({
path: 'secrets.kibana_api_key',
value: remoteESOutput.secrets.kibana_api_key,
});
}
}
return outputSecretPaths;
@ -378,13 +384,17 @@ export function getOutputSecretReferences(output: Output): PolicySecretReference
});
}
if (
output.type === 'remote_elasticsearch' &&
typeof output?.secrets?.service_token === 'object'
) {
outputSecretPaths.push({
id: output.secrets.service_token.id,
});
if (output.type === 'remote_elasticsearch') {
if (typeof output?.secrets?.service_token === 'object') {
outputSecretPaths.push({
id: output.secrets.service_token.id,
});
}
if (typeof output?.secrets?.kibana_api_key === 'object') {
outputSecretPaths.push({
id: output.secrets.kibana_api_key.id,
});
}
}
return outputSecretPaths;

View file

@ -152,8 +152,12 @@ export const RemoteElasticSearchSchema = {
secrets: schema.maybe(
schema.object({
service_token: schema.maybe(secretRefSchema),
kibana_api_key: schema.maybe(secretRefSchema),
})
),
sync_integrations: schema.maybe(schema.boolean()),
kibana_url: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
kibana_api_key: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
};
const RemoteElasticSearchUpdateSchema = {
@ -163,8 +167,12 @@ const RemoteElasticSearchUpdateSchema = {
secrets: schema.maybe(
schema.object({
service_token: schema.maybe(secretRefSchema),
kibana_api_key: schema.maybe(secretRefSchema),
})
),
sync_integrations: schema.maybe(schema.boolean()),
kibana_url: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
kibana_api_key: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])),
};
/**

View file

@ -170,7 +170,11 @@ export interface OutputSoRemoteElasticsearchAttributes extends OutputSoBaseAttri
service_token?: string;
secrets?: {
service_token?: { id: string };
kibana_api_key?: { id: string };
};
sync_integrations?: boolean;
kibana_url?: string;
kibana_api_key?: string;
}
interface OutputSoLogstashAttributes extends OutputSoBaseAttributes {