[Fleet] enable feature flag enableSyncIntegrationsOnRemote (#220215)

## Summary

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

- Enable feature flag `enableSyncIntegrationsOnRemote`
- Added check to hide sync integrations feature in serverless
- Moved creating the follower index from Fleet setup to output
create/update API and async task (create if does not exist)
- Follower index is not hidden for now, because if hidden, it's not
showing up on the CCR UI

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Julia Bardi 2025-06-04 12:18:21 +02:00 committed by GitHub
parent 013384e06d
commit d65f9c5d25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1276 additions and 147 deletions

View file

@ -45110,6 +45110,339 @@
]
}
},
"/api/fleet/remote_synced_integrations/status": {
"get": {
"description": "[Required authorization] Route required privileges: fleet-settings-read AND integrations-read.",
"operationId": "get-fleet-remote-synced-integrations-status",
"parameters": [],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"custom_assets": {
"additionalProperties": {
"additionalProperties": false,
"properties": {
"error": {
"type": "string"
},
"is_deleted": {
"type": "boolean"
},
"name": {
"type": "string"
},
"package_name": {
"type": "string"
},
"package_version": {
"type": "string"
},
"sync_status": {
"enum": [
"completed",
"synchronizing",
"failed",
"warning"
],
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"type",
"name",
"package_name",
"package_version",
"sync_status"
],
"type": "object"
},
"type": "object"
},
"error": {
"type": "string"
},
"integrations": {
"items": {
"additionalProperties": false,
"properties": {
"error": {
"type": "string"
},
"id": {
"type": "string"
},
"install_status": {
"additionalProperties": false,
"properties": {
"main": {
"type": "string"
},
"remote": {
"type": "string"
}
},
"required": [
"main"
],
"type": "object"
},
"package_name": {
"type": "string"
},
"package_version": {
"type": "string"
},
"sync_status": {
"enum": [
"completed",
"synchronizing",
"failed",
"warning"
],
"type": "string"
},
"updated_at": {
"type": "string"
},
"warning": {
"type": "string"
}
},
"required": [
"sync_status",
"install_status"
],
"type": "object"
},
"type": "array"
},
"warning": {
"type": "string"
}
},
"required": [
"integrations"
],
"type": "object"
}
}
}
},
"400": {
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"description": "Generic Error",
"properties": {
"attributes": {},
"error": {
"type": "string"
},
"errorType": {
"type": "string"
},
"message": {
"type": "string"
},
"statusCode": {
"type": "number"
}
},
"required": [
"message",
"attributes"
],
"type": "object"
}
}
}
}
},
"summary": "Get CCR Remote synced integrations status",
"tags": [
"CCR Remote synced integrations"
]
}
},
"/api/fleet/remote_synced_integrations/{outputId}/remote_status": {
"get": {
"description": "[Required authorization] Route required privileges: fleet-settings-read AND integrations-read.",
"operationId": "get-fleet-remote-synced-integrations-outputid-remote-status",
"parameters": [
{
"in": "path",
"name": "outputId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"custom_assets": {
"additionalProperties": {
"additionalProperties": false,
"properties": {
"error": {
"type": "string"
},
"is_deleted": {
"type": "boolean"
},
"name": {
"type": "string"
},
"package_name": {
"type": "string"
},
"package_version": {
"type": "string"
},
"sync_status": {
"enum": [
"completed",
"synchronizing",
"failed",
"warning"
],
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"type",
"name",
"package_name",
"package_version",
"sync_status"
],
"type": "object"
},
"type": "object"
},
"error": {
"type": "string"
},
"integrations": {
"items": {
"additionalProperties": false,
"properties": {
"error": {
"type": "string"
},
"id": {
"type": "string"
},
"install_status": {
"additionalProperties": false,
"properties": {
"main": {
"type": "string"
},
"remote": {
"type": "string"
}
},
"required": [
"main"
],
"type": "object"
},
"package_name": {
"type": "string"
},
"package_version": {
"type": "string"
},
"sync_status": {
"enum": [
"completed",
"synchronizing",
"failed",
"warning"
],
"type": "string"
},
"updated_at": {
"type": "string"
},
"warning": {
"type": "string"
}
},
"required": [
"sync_status",
"install_status"
],
"type": "object"
},
"type": "array"
},
"warning": {
"type": "string"
}
},
"required": [
"integrations"
],
"type": "object"
}
}
}
},
"400": {
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"description": "Generic Error",
"properties": {
"attributes": {},
"error": {
"type": "string"
},
"errorType": {
"type": "string"
},
"message": {
"type": "string"
},
"statusCode": {
"type": "number"
}
},
"required": [
"message",
"attributes"
],
"type": "object"
}
}
}
}
},
"summary": "Get CCR Remote synced integrations status by outputId",
"tags": [
"CCR Remote synced integrations"
]
}
},
"/api/fleet/service_tokens": {
"post": {
"description": "[Required authorization] Route required privileges: fleet-agents-all.",
@ -60516,6 +60849,9 @@
{
"name": "alerting"
},
{
"name": "CCR Remote synced integrations"
},
{
"name": "connectors"
},

View file

@ -45110,6 +45110,339 @@
]
}
},
"/api/fleet/remote_synced_integrations/status": {
"get": {
"description": "[Required authorization] Route required privileges: fleet-settings-read AND integrations-read.",
"operationId": "get-fleet-remote-synced-integrations-status",
"parameters": [],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"custom_assets": {
"additionalProperties": {
"additionalProperties": false,
"properties": {
"error": {
"type": "string"
},
"is_deleted": {
"type": "boolean"
},
"name": {
"type": "string"
},
"package_name": {
"type": "string"
},
"package_version": {
"type": "string"
},
"sync_status": {
"enum": [
"completed",
"synchronizing",
"failed",
"warning"
],
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"type",
"name",
"package_name",
"package_version",
"sync_status"
],
"type": "object"
},
"type": "object"
},
"error": {
"type": "string"
},
"integrations": {
"items": {
"additionalProperties": false,
"properties": {
"error": {
"type": "string"
},
"id": {
"type": "string"
},
"install_status": {
"additionalProperties": false,
"properties": {
"main": {
"type": "string"
},
"remote": {
"type": "string"
}
},
"required": [
"main"
],
"type": "object"
},
"package_name": {
"type": "string"
},
"package_version": {
"type": "string"
},
"sync_status": {
"enum": [
"completed",
"synchronizing",
"failed",
"warning"
],
"type": "string"
},
"updated_at": {
"type": "string"
},
"warning": {
"type": "string"
}
},
"required": [
"sync_status",
"install_status"
],
"type": "object"
},
"type": "array"
},
"warning": {
"type": "string"
}
},
"required": [
"integrations"
],
"type": "object"
}
}
}
},
"400": {
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"description": "Generic Error",
"properties": {
"attributes": {},
"error": {
"type": "string"
},
"errorType": {
"type": "string"
},
"message": {
"type": "string"
},
"statusCode": {
"type": "number"
}
},
"required": [
"message",
"attributes"
],
"type": "object"
}
}
}
}
},
"summary": "Get CCR Remote synced integrations status",
"tags": [
"CCR Remote synced integrations"
]
}
},
"/api/fleet/remote_synced_integrations/{outputId}/remote_status": {
"get": {
"description": "[Required authorization] Route required privileges: fleet-settings-read AND integrations-read.",
"operationId": "get-fleet-remote-synced-integrations-outputid-remote-status",
"parameters": [
{
"in": "path",
"name": "outputId",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"custom_assets": {
"additionalProperties": {
"additionalProperties": false,
"properties": {
"error": {
"type": "string"
},
"is_deleted": {
"type": "boolean"
},
"name": {
"type": "string"
},
"package_name": {
"type": "string"
},
"package_version": {
"type": "string"
},
"sync_status": {
"enum": [
"completed",
"synchronizing",
"failed",
"warning"
],
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"type",
"name",
"package_name",
"package_version",
"sync_status"
],
"type": "object"
},
"type": "object"
},
"error": {
"type": "string"
},
"integrations": {
"items": {
"additionalProperties": false,
"properties": {
"error": {
"type": "string"
},
"id": {
"type": "string"
},
"install_status": {
"additionalProperties": false,
"properties": {
"main": {
"type": "string"
},
"remote": {
"type": "string"
}
},
"required": [
"main"
],
"type": "object"
},
"package_name": {
"type": "string"
},
"package_version": {
"type": "string"
},
"sync_status": {
"enum": [
"completed",
"synchronizing",
"failed",
"warning"
],
"type": "string"
},
"updated_at": {
"type": "string"
},
"warning": {
"type": "string"
}
},
"required": [
"sync_status",
"install_status"
],
"type": "object"
},
"type": "array"
},
"warning": {
"type": "string"
}
},
"required": [
"integrations"
],
"type": "object"
}
}
}
},
"400": {
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"description": "Generic Error",
"properties": {
"attributes": {},
"error": {
"type": "string"
},
"errorType": {
"type": "string"
},
"message": {
"type": "string"
},
"statusCode": {
"type": "number"
}
},
"required": [
"message",
"attributes"
],
"type": "object"
}
}
}
}
},
"summary": "Get CCR Remote synced integrations status by outputId",
"tags": [
"CCR Remote synced integrations"
]
}
},
"/api/fleet/service_tokens": {
"post": {
"description": "[Required authorization] Route required privileges: fleet-agents-all.",
@ -60107,6 +60440,9 @@
{
"name": "alerting"
},
{
"name": "CCR Remote synced integrations"
},
{
"name": "connectors"
},

View file

@ -66,6 +66,7 @@ tags:
Configure APM source maps. A source map allows minified files to be mapped back to original source code--allowing you to maintain the speed advantage of minified code, without losing the ability to quickly and easily debug your application.
For best results, uploading source maps should become a part of your deployment procedure, and not something you only do when you see unhelpful errors. That's because uploading source maps after errors happen won't make old errors magically readable--errors must occur again for source mapping to occur.
name: APM sourcemaps
- name: CCR Remote synced integrations
- name: connectors
description: |
Connectors provide a central place to store connection information for services and integrations with Elastic or third party systems. Alerting rules can use connectors to run actions when rule conditions are met.
@ -39651,6 +39652,233 @@ paths:
summary: Update a proxy
tags:
- Fleet proxies
/api/fleet/remote_synced_integrations/{outputId}/remote_status:
get:
description: '[Required authorization] Route required privileges: fleet-settings-read AND integrations-read.'
operationId: get-fleet-remote-synced-integrations-outputid-remote-status
parameters:
- in: path
name: outputId
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
additionalProperties: false
type: object
properties:
custom_assets:
additionalProperties:
additionalProperties: false
type: object
properties:
error:
type: string
is_deleted:
type: boolean
name:
type: string
package_name:
type: string
package_version:
type: string
sync_status:
enum:
- completed
- synchronizing
- failed
- warning
type: string
type:
type: string
required:
- type
- name
- package_name
- package_version
- sync_status
type: object
error:
type: string
integrations:
items:
additionalProperties: false
type: object
properties:
error:
type: string
id:
type: string
install_status:
additionalProperties: false
type: object
properties:
main:
type: string
remote:
type: string
required:
- main
package_name:
type: string
package_version:
type: string
sync_status:
enum:
- completed
- synchronizing
- failed
- warning
type: string
updated_at:
type: string
warning:
type: string
required:
- sync_status
- install_status
type: array
warning:
type: string
required:
- integrations
'400':
content:
application/json:
schema:
additionalProperties: false
description: Generic Error
type: object
properties:
attributes: {}
error:
type: string
errorType:
type: string
message:
type: string
statusCode:
type: number
required:
- message
- attributes
summary: Get CCR Remote synced integrations status by outputId
tags:
- CCR Remote synced integrations
/api/fleet/remote_synced_integrations/status:
get:
description: '[Required authorization] Route required privileges: fleet-settings-read AND integrations-read.'
operationId: get-fleet-remote-synced-integrations-status
parameters: []
responses:
'200':
content:
application/json:
schema:
additionalProperties: false
type: object
properties:
custom_assets:
additionalProperties:
additionalProperties: false
type: object
properties:
error:
type: string
is_deleted:
type: boolean
name:
type: string
package_name:
type: string
package_version:
type: string
sync_status:
enum:
- completed
- synchronizing
- failed
- warning
type: string
type:
type: string
required:
- type
- name
- package_name
- package_version
- sync_status
type: object
error:
type: string
integrations:
items:
additionalProperties: false
type: object
properties:
error:
type: string
id:
type: string
install_status:
additionalProperties: false
type: object
properties:
main:
type: string
remote:
type: string
required:
- main
package_name:
type: string
package_version:
type: string
sync_status:
enum:
- completed
- synchronizing
- failed
- warning
type: string
updated_at:
type: string
warning:
type: string
required:
- sync_status
- install_status
type: array
warning:
type: string
required:
- integrations
'400':
content:
application/json:
schema:
additionalProperties: false
description: Generic Error
type: object
properties:
attributes: {}
error:
type: string
errorType:
type: string
message:
type: string
statusCode:
type: number
required:
- message
- attributes
summary: Get CCR Remote synced integrations status
tags:
- CCR Remote synced integrations
/api/fleet/service_tokens:
post:
description: '[Required authorization] Route required privileges: fleet-agents-all.'

View file

@ -80,6 +80,7 @@ tags:
description: Cases documentation
url: https://www.elastic.co/docs/explore-analyze/alerts-cases/cases
x-displayName: Cases
- name: CCR Remote synced integrations
- name: connectors
description: |
Connectors provide a central place to store connection information for services and integrations with Elastic or third party systems. Alerting rules can use connectors to run actions when rule conditions are met.
@ -41893,6 +41894,233 @@ paths:
summary: Update a proxy
tags:
- Fleet proxies
/api/fleet/remote_synced_integrations/{outputId}/remote_status:
get:
description: '[Required authorization] Route required privileges: fleet-settings-read AND integrations-read.'
operationId: get-fleet-remote-synced-integrations-outputid-remote-status
parameters:
- in: path
name: outputId
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
additionalProperties: false
type: object
properties:
custom_assets:
additionalProperties:
additionalProperties: false
type: object
properties:
error:
type: string
is_deleted:
type: boolean
name:
type: string
package_name:
type: string
package_version:
type: string
sync_status:
enum:
- completed
- synchronizing
- failed
- warning
type: string
type:
type: string
required:
- type
- name
- package_name
- package_version
- sync_status
type: object
error:
type: string
integrations:
items:
additionalProperties: false
type: object
properties:
error:
type: string
id:
type: string
install_status:
additionalProperties: false
type: object
properties:
main:
type: string
remote:
type: string
required:
- main
package_name:
type: string
package_version:
type: string
sync_status:
enum:
- completed
- synchronizing
- failed
- warning
type: string
updated_at:
type: string
warning:
type: string
required:
- sync_status
- install_status
type: array
warning:
type: string
required:
- integrations
'400':
content:
application/json:
schema:
additionalProperties: false
description: Generic Error
type: object
properties:
attributes: {}
error:
type: string
errorType:
type: string
message:
type: string
statusCode:
type: number
required:
- message
- attributes
summary: Get CCR Remote synced integrations status by outputId
tags:
- CCR Remote synced integrations
/api/fleet/remote_synced_integrations/status:
get:
description: '[Required authorization] Route required privileges: fleet-settings-read AND integrations-read.'
operationId: get-fleet-remote-synced-integrations-status
parameters: []
responses:
'200':
content:
application/json:
schema:
additionalProperties: false
type: object
properties:
custom_assets:
additionalProperties:
additionalProperties: false
type: object
properties:
error:
type: string
is_deleted:
type: boolean
name:
type: string
package_name:
type: string
package_version:
type: string
sync_status:
enum:
- completed
- synchronizing
- failed
- warning
type: string
type:
type: string
required:
- type
- name
- package_name
- package_version
- sync_status
type: object
error:
type: string
integrations:
items:
additionalProperties: false
type: object
properties:
error:
type: string
id:
type: string
install_status:
additionalProperties: false
type: object
properties:
main:
type: string
remote:
type: string
required:
- main
package_name:
type: string
package_version:
type: string
sync_status:
enum:
- completed
- synchronizing
- failed
- warning
type: string
updated_at:
type: string
warning:
type: string
required:
- sync_status
- install_status
type: array
warning:
type: string
required:
- integrations
'400':
content:
application/json:
schema:
additionalProperties: false
description: Generic Error
type: object
properties:
attributes: {}
error:
type: string
errorType:
type: string
message:
type: string
statusCode:
type: number
required:
- message
- attributes
summary: Get CCR Remote synced integrations status
tags:
- CCR Remote synced integrations
/api/fleet/service_tokens:
post:
description: '[Required authorization] Route required privileges: fleet-agents-all.'

View file

@ -11,7 +11,7 @@ const _allowedExperimentalValues = {
showExperimentalShipperOptions: false,
useSpaceAwareness: false,
enableAutomaticAgentUpgrades: true,
enableSyncIntegrationsOnRemote: false,
enableSyncIntegrationsOnRemote: true,
enableSSLSecrets: false,
installedIntegrationsTabularUI: false,
enabledUpgradeAgentlessDeploymentsTask: false,

View file

@ -106,6 +106,24 @@ Save the responses as they will be required in Cluster A (see next section).
- Choose a name, put `localhost:9300` for "Seed nodes", and save (check "Yes, I have setup trust")
- Make sure the connection status is "Connected"
- Equivalent Dev Tools API request
```
PUT /_cluster/settings
{
"persistent" : {
"cluster" : {
"remote" : {
"local" : {
"seeds" : [
"localhost:9300"
]
}
}
}
}
}
```
### Set up CCR
Please note that [CCR](https://www.elastic.co/guide/en/elasticsearch/reference/current/ccr-getting-started-tutorial.html) requires both clusters to have the same license. At the time of writing an `enterprise` license is needed.
@ -114,6 +132,16 @@ On cluster 1, navigate to *Stack Management > Cross-Cluster Replication* and c
- Leader index `fleet-synced-integrations`
- Follower index `fleet-synced-integrations-ccr-remote1`
- Equivalent Dev Tools API request
```
PUT /fleet-synced-integrations-ccr-local/_ccr/follow
{
"remote_cluster" : "local",
"leader_index" : "fleet-synced-integrations"
}
```
### Set up local ES output
This configuration is required to kick off the integration sync. The local host needs to match the remote ES output configured on A (see next section). Note that `kibana.dev.yml` is read by both kibana instances so it's better to add it in the UI to avoid conflicts.

View file

@ -46,7 +46,7 @@ export interface IsConvertedToSecret {
}
export const OutputFormRemoteEsSection: React.FunctionComponent<Props> = (props) => {
const { docLinks } = useStartServices();
const { docLinks, cloud } = useStartServices();
const { inputs, useSecretsStorage, onToggleSecretStorage } = props;
const [isConvertedToSecret, setIsConvertedToSecret] = React.useState<IsConvertedToSecret>({
serviceToken: false,
@ -54,7 +54,8 @@ export const OutputFormRemoteEsSection: React.FunctionComponent<Props> = (props)
sslKey: false,
});
const { enableSyncIntegrationsOnRemote, enableSSLSecrets } = ExperimentalFeaturesService.get();
const enableSyncIntegrations = enableSyncIntegrationsOnRemote && licenseService.isEnterprise();
const enableSyncIntegrations =
enableSyncIntegrationsOnRemote && licenseService.isEnterprise() && !cloud?.isServerlessEnabled;
const [isRemoteClusterInstructionsOpen, setIsRemoteClusterInstructionsOpen] =
React.useState(false);

View file

@ -11,7 +11,7 @@ import { EuiBasicTable, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiIconTip } f
import type { EuiBasicTableColumn } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { licenseService, useAuthz, useLink } from '../../../../hooks';
import { licenseService, useAuthz, useLink, useStartServices } from '../../../../hooks';
import type { Output } from '../../../../types';
import { OutputHealth } from '../edit_output_flyout/output_health';
@ -57,7 +57,9 @@ export const OutputsTable: React.FunctionComponent<OutputsTableProps> = ({
const authz = useAuthz();
const { getHref } = useLink();
const { enableSyncIntegrationsOnRemote } = ExperimentalFeaturesService.get();
const enableSyncIntegrations = enableSyncIntegrationsOnRemote && licenseService.isEnterprise();
const { cloud } = useStartServices();
const enableSyncIntegrations =
enableSyncIntegrationsOnRemote && licenseService.isEnterprise() && !cloud?.isServerlessEnabled;
const columns = useMemo((): Array<EuiBasicTableColumn<Output>> => {
return [

View file

@ -15,81 +15,77 @@ import { GetRemoteSyncedIntegrationsStatusResponseSchema } from '../../types/mod
import { GetRemoteSyncedIntegrationsInfoRequestSchema } from '../../types';
import { canEnableSyncIntegrations } from '../../services/setup/fleet_synced_integrations';
import {
getRemoteSyncedIntegrationsStatusHandler,
getRemoteSyncedIntegrationsInfoHandler,
} from './handler';
export const registerRoutes = (router: FleetAuthzRouter) => {
if (canEnableSyncIntegrations()) {
router.versioned
.get({
path: REMOTE_SYNCED_INTEGRATIONS_API_ROUTES.STATUS_PATTERN,
security: {
authz: {
requiredPrivileges: [
FLEET_API_PRIVILEGES.SETTINGS.READ,
FLEET_API_PRIVILEGES.INTEGRATIONS.READ,
],
},
router.versioned
.get({
path: REMOTE_SYNCED_INTEGRATIONS_API_ROUTES.STATUS_PATTERN,
security: {
authz: {
requiredPrivileges: [
FLEET_API_PRIVILEGES.SETTINGS.READ,
FLEET_API_PRIVILEGES.INTEGRATIONS.READ,
],
},
summary: `Get CCR Remote synced integrations status`,
options: {
tags: ['oas-tag:CCR Remote synced integrations'],
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {},
response: {
200: {
body: () => GetRemoteSyncedIntegrationsStatusResponseSchema,
},
400: {
body: genericErrorResponse,
},
},
summary: `Get CCR Remote synced integrations status`,
options: {
tags: ['oas-tag:CCR Remote synced integrations'],
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: {},
response: {
200: {
body: () => GetRemoteSyncedIntegrationsStatusResponseSchema,
},
400: {
body: genericErrorResponse,
},
},
},
getRemoteSyncedIntegrationsStatusHandler
);
},
getRemoteSyncedIntegrationsStatusHandler
);
router.versioned
.get({
path: REMOTE_SYNCED_INTEGRATIONS_API_ROUTES.INFO_PATTERN,
security: {
authz: {
requiredPrivileges: [
FLEET_API_PRIVILEGES.SETTINGS.READ,
FLEET_API_PRIVILEGES.INTEGRATIONS.READ,
],
},
router.versioned
.get({
path: REMOTE_SYNCED_INTEGRATIONS_API_ROUTES.INFO_PATTERN,
security: {
authz: {
requiredPrivileges: [
FLEET_API_PRIVILEGES.SETTINGS.READ,
FLEET_API_PRIVILEGES.INTEGRATIONS.READ,
],
},
summary: `Get CCR Remote synced integrations status by outputId`,
options: {
tags: ['oas-tag:CCR Remote synced integrations'],
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: GetRemoteSyncedIntegrationsInfoRequestSchema,
response: {
200: {
body: () => GetRemoteSyncedIntegrationsStatusResponseSchema,
},
400: {
body: genericErrorResponse,
},
},
summary: `Get CCR Remote synced integrations status by outputId`,
options: {
tags: ['oas-tag:CCR Remote synced integrations'],
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: GetRemoteSyncedIntegrationsInfoRequestSchema,
response: {
200: {
body: () => GetRemoteSyncedIntegrationsStatusResponseSchema,
},
400: {
body: genericErrorResponse,
},
},
},
getRemoteSyncedIntegrationsInfoHandler
);
}
},
getRemoteSyncedIntegrationsInfoHandler
);
};

View file

@ -83,7 +83,10 @@ import {
} from './secrets';
import { findAgentlessPolicies } from './outputs/helpers';
import { patchUpdateDataWithRequireEncryptedAADFields } from './outputs/so_helpers';
import { canEnableSyncIntegrations } from './setup/fleet_synced_integrations';
import {
canEnableSyncIntegrations,
createOrUpdateFleetSyncedIntegrationsIndex,
} from './setup/fleet_synced_integrations';
type Nullable<T> = { [P in keyof T]: T[P] | null };
@ -368,15 +371,18 @@ async function updateAgentPoliciesDataOutputId(
}
}
function validateRemoteSyncIntegrationsCanBeEnabled(output: Partial<NewOutput>) {
if (
output.type === outputType.RemoteElasticsearch &&
(output.sync_integrations === true || output.sync_uninstalled_integrations === true) &&
!canEnableSyncIntegrations()
) {
async function remoteSyncIntegrationsCheck(
esClient: ElasticsearchClient,
output: Partial<NewOutput>
) {
const syncIntegrationsEnabled =
output.type === outputType.RemoteElasticsearch && output.sync_integrations === true;
if (syncIntegrationsEnabled && !canEnableSyncIntegrations()) {
throw new OutputUnauthorizedError(
'Remote sync integrations require at least an Enterprise license.'
);
} else if (syncIntegrationsEnabled) {
await createOrUpdateFleetSyncedIntegrationsIndex(esClient);
}
}
@ -692,7 +698,7 @@ class OutputService {
}
}
validateRemoteSyncIntegrationsCanBeEnabled(output);
await remoteSyncIntegrationsCheck(esClient, output);
const id = options?.id ? outputIdToUuid(options.id) : SavedObjectsUtils.generateId();
@ -1118,7 +1124,7 @@ class OutputService {
updateData.shipper = null;
}
}
validateRemoteSyncIntegrationsCanBeEnabled(data);
await remoteSyncIntegrationsCheck(esClient, data);
// Store secret values if enabled; if not, store plain text values
if (await isOutputSecretStorageEnabled(esClient, soClient)) {

View file

@ -24,10 +24,7 @@ import { getPreconfiguredDeleteUnenrolledAgentsSettingFromConfig } from './preco
import { _runSetupWithLock, setupFleet } from './setup';
import { isPackageInstalled } from './epm/packages/install';
import { upgradeAgentPolicySchemaVersion } from './setup/upgrade_agent_policy_schema_version';
import {
createCCSIndexPatterns,
createOrUpdateFleetSyncedIntegrationsIndex,
} from './setup/fleet_synced_integrations';
import { createCCSIndexPatterns } from './setup/fleet_synced_integrations';
import { getSpaceAwareSaveobjectsClients } from './epm/kibana/assets/saved_objects';
jest.mock('./app_context');
@ -107,7 +104,6 @@ describe('setupFleet', () => {
(getPreconfiguredDeleteUnenrolledAgentsSettingFromConfig as jest.Mock).mockResolvedValue([]);
(isPackageInstalled as jest.Mock).mockResolvedValue(true);
(upgradeAgentPolicySchemaVersion as jest.Mock).mockResolvedValue(undefined);
(createOrUpdateFleetSyncedIntegrationsIndex as jest.Mock).mockResolvedValue(undefined);
(createCCSIndexPatterns as jest.Mock).mockResolvedValue(undefined);
(getSpaceAwareSaveobjectsClients as jest.Mock).mockReturnValue({});
});

View file

@ -66,10 +66,7 @@ import {
} from './preconfiguration/delete_unenrolled_agent_setting';
import { backfillPackagePolicySupportsAgentless } from './backfill_agentless';
import { updateDeprecatedComponentTemplates } from './setup/update_deprecated_component_templates';
import {
createCCSIndexPatterns,
createOrUpdateFleetSyncedIntegrationsIndex,
} from './setup/fleet_synced_integrations';
import { createCCSIndexPatterns } from './setup/fleet_synced_integrations';
import { ensureCorrectAgentlessSettingsIds } from './agentless_settings_ids';
import { getSpaceAwareSaveobjectsClients } from './epm/kibana/assets/saved_objects';
@ -281,9 +278,6 @@ async function createSetupSideEffects(
logger.debug('Update deprecated _source.mode in component templates');
await updateDeprecatedComponentTemplates(esClient);
logger.debug('Create or update fleet-synced-integrations index');
await createOrUpdateFleetSyncedIntegrationsIndex(esClient);
logger.debug('Create CCS index patterns for remote clusters');
const { savedObjectsImporter } = getSpaceAwareSaveobjectsClients();
await createCCSIndexPatterns(esClient, soClient, savedObjectsImporter);

View file

@ -17,10 +17,14 @@ jest.mock('../app_context', () => ({
getExperimentalFeatures: jest.fn().mockReturnValue({ enableSyncIntegrationsOnRemote: true }),
getLogger: jest.fn().mockReturnValue({
error: jest.fn(),
debug: jest.fn(),
}),
getConfig: jest.fn().mockReturnValue({
enableManagedLogsAndMetricsDataviews: true,
}),
getCloud: jest.fn().mockReturnValue({
isServerlessEnabled: false,
}),
},
}));
@ -147,32 +151,6 @@ describe('fleet_synced_integrations', () => {
jest.spyOn(licenseService, 'isEnterprise').mockReturnValue(false);
});
it('should not create index', async () => {
mockExists.mockResolvedValue(false);
jest.spyOn(licenseService, 'isEnterprise').mockReturnValue(false);
await createOrUpdateFleetSyncedIntegrationsIndex(esClientMock);
expect(esClientMock.indices.create).not.toHaveBeenCalled();
});
it('should not update index', async () => {
mockExists.mockResolvedValue(true);
mockGetMapping.mockResolvedValue({
'fleet-synced-integrations': {
mappings: {
_meta: {
version: '0.0',
},
},
},
});
await createOrUpdateFleetSyncedIntegrationsIndex(esClientMock);
expect(esClientMock.indices.putMapping).not.toHaveBeenCalled();
});
it('should not create index patterns for remote clusters', async () => {
await createCCSIndexPatterns(esClientMock, soClientMock, soImporterMock);

View file

@ -67,14 +67,12 @@ export const FLEET_SYNCED_INTEGRATIONS_INDEX_CONFIG = {
export const canEnableSyncIntegrations = () => {
const { enableSyncIntegrationsOnRemote } = appContextService.getExperimentalFeatures();
return enableSyncIntegrationsOnRemote && licenseService.isEnterprise();
const isServerless = appContextService.getCloud()?.isServerlessEnabled;
return enableSyncIntegrationsOnRemote && licenseService.isEnterprise() && !isServerless;
};
export async function createOrUpdateFleetSyncedIntegrationsIndex(esClient: ElasticsearchClient) {
if (!canEnableSyncIntegrations()) {
return;
}
appContextService.getLogger().debug('Create or update fleet-synced-integrations index');
await createOrUpdateIndex(
esClient,
FLEET_SYNCED_INTEGRATIONS_INDEX_NAME,

View file

@ -39,6 +39,7 @@ export const getFollowerIndex = async (
const indices = await esClient.indices.get(
{
index: FLEET_SYNCED_INTEGRATIONS_CCR_INDEX_PREFIX,
expand_wildcards: 'all',
},
{ signal: abortController.signal }
);

View file

@ -40,6 +40,10 @@ jest.mock('../../services/app_context', () => ({
appContextService: {
getExperimentalFeatures: jest.fn().mockReturnValue({ enableSyncIntegrationsOnRemote: true }),
start: jest.fn(),
getCloud: jest.fn().mockReturnValue({ isServerlessEnabled: false }),
getLogger: jest.fn().mockReturnValue({
debug: jest.fn(),
}),
},
}));
@ -145,6 +149,15 @@ describe('SyncIntegrationsTask', () => {
const [{ elasticsearch }] = await mockCore.getStartServices();
esClient = elasticsearch.client.asInternalUser as ElasticsearchClientMock;
esClient.indices.exists.mockResolvedValue(true);
esClient.indices.getMapping.mockResolvedValue({
'fleet-synced-integrations': {
mappings: {
_meta: {
version: '1.0',
},
},
},
});
esClient.cluster.getComponentTemplate.mockResolvedValue({
component_templates: [
{

View file

@ -24,6 +24,7 @@ import { getInstalledPackageSavedObjects } from '../../services/epm/packages/get
import {
FLEET_SYNCED_INTEGRATIONS_INDEX_NAME,
canEnableSyncIntegrations,
createOrUpdateFleetSyncedIntegrationsIndex,
} from '../../services/setup/fleet_synced_integrations';
import { syncIntegrationsOnRemote } from './sync_integrations_on_remote';
@ -31,7 +32,7 @@ import { getCustomAssets } from './custom_assets';
import type { SyncIntegrationsData } from './model';
export const TYPE = 'fleet:sync-integrations-task';
export const VERSION = '1.0.4';
export const VERSION = '1.0.5';
const TITLE = 'Fleet Sync Integrations Task';
const SCOPE = ['fleet'];
const INTERVAL = '5m';
@ -122,14 +123,14 @@ export class SyncIntegrationsTask {
this.logger.info(`[runTask()] started`);
const [coreStart, _startDeps, { packageService }] = (await core.getStartServices()) as any;
const esClient = coreStart.elasticsearch.client.asInternalUser;
const soClient = new SavedObjectsClient(coreStart.savedObjects.createInternalRepository());
if (!canEnableSyncIntegrations()) {
return;
}
const [coreStart, _startDeps, { packageService }] = (await core.getStartServices()) as any;
const esClient = coreStart.elasticsearch.client.asInternalUser;
const soClient = new SavedObjectsClient(coreStart.savedObjects.createInternalRepository());
try {
// write integrations on main cluster
await this.updateSyncedIntegrationsData(esClient, soClient);
@ -155,15 +156,6 @@ export class SyncIntegrationsTask {
}
};
private syncedIntegrationsIndexExists = async (esClient: ElasticsearchClient) => {
return await esClient.indices.exists(
{
index: FLEET_SYNCED_INTEGRATIONS_INDEX_NAME,
},
{ signal: this.abortController.signal }
);
};
private getSyncedIntegrationDoc = async (
esClient: ElasticsearchClient
): Promise<SyncIntegrationsData | undefined> => {
@ -191,15 +183,6 @@ export class SyncIntegrationsTask {
esClient: ElasticsearchClient,
soClient: SavedObjectsClient
) => {
const indexExists = await this.syncedIntegrationsIndexExists(esClient);
if (!indexExists) {
this.logger.info(
`[SyncIntegrationsTask] index ${FLEET_SYNCED_INTEGRATIONS_INDEX_NAME} does not exist`
);
return;
}
const outputs = await outputService.list(soClient);
const remoteESOutputs = outputs.items.filter(
(output) => output.type === outputType.RemoteElasticsearch
@ -208,6 +191,10 @@ export class SyncIntegrationsTask {
(output) => (output as NewRemoteElasticsearchOutput).sync_integrations
);
if (isSyncEnabled) {
await createOrUpdateFleetSyncedIntegrationsIndex(esClient);
}
const previousSyncIntegrationsData = await this.getSyncedIntegrationDoc(esClient);
if (!isSyncEnabled) {

View file

@ -792,7 +792,7 @@ export default function (providerContext: FtrProviderContext) {
}
});
it('should not allow to update kibana_api_key on an existing remote_elasticsearch output if the license is not at least enterprise', async function () {
it('should allow to update kibana_api_key on an existing remote_elasticsearch output', async function () {
const res = await supertest
.post(`/api/fleet/outputs`)
.set('kbn-xsrf', 'xxxx')
@ -804,7 +804,7 @@ export default function (providerContext: FtrProviderContext) {
})
.expect(200);
const outputId = res.body.item.id;
await supertest
const updatedRes = await supertest
.put(`/api/fleet/outputs/${outputId}`)
.set('kbn-xsrf', 'xxxx')
.send({
@ -815,7 +815,8 @@ export default function (providerContext: FtrProviderContext) {
kibana_url: 'https://testhost',
kibana_api_key: 'bbbb',
})
.expect(400);
.expect(200);
expect(updatedRes.body.item.kibana_api_key).to.equal('bbbb');
});
it('should bump all policies in all spaces if updating the default output', async () => {
@ -1821,7 +1822,7 @@ export default function (providerContext: FtrProviderContext) {
.expect(200);
});
it('should not allow to create a new remote_elasticsearch output with kibana_api_key if the license is not at least enterprise', async function () {
it('should allow to create a new remote_elasticsearch output with kibana_api_key field', async function () {
await supertest
.post(`/api/fleet/outputs`)
.set('kbn-xsrf', 'xxxx')
@ -1833,7 +1834,7 @@ export default function (providerContext: FtrProviderContext) {
kibana_url: 'https://testhost',
kibana_api_key: 'aaaa',
})
.expect(400);
.expect(200);
});
});