[Fleet] Add PrivateLink endpoint support for Fleet Server and ES output in serverless#275601
Conversation
|
Pinging @elastic/fleet (Team:Fleet) |
| const outputItems = outputs.data?.items.filter((item) => !item.is_internal); | ||
| const fleetServerHostsItems = fleetServerHosts.data?.items.filter((item) => !item.is_internal); | ||
| const outputItems = outputs.data?.items.filter( | ||
| (item) => item.id !== SERVERLESS_AGENTLESS_OUTPUT_ID |
There was a problem hiding this comment.
Is there any concern that we might have a future internal item with an ID that isn't the hardcoded agentless ID? Maybe not, just wondering. If there is, we could be more defensive, e.g.:
!item.is_internal || item.id === SERVERLESS_PRIVATE_*There was a problem hiding this comment.
Yes that's something we should consider. In fact the IDs could actually be more the just these two, if they extend the functionality to Azure and CGP.
There was a problem hiding this comment.
In this specific case, the check is to avoid using the private links for Agentless. I got no explicit specs for it, so it's safer to exclude from using these links for now.
| return; | ||
| } | ||
| } catch (e) { | ||
| // Private endpoint SO not present — PrivateLink not enabled for this project. |
There was a problem hiding this comment.
Should we check the error type here? Wondering if this could swallow a legit transient error.
| content={ | ||
| <FormattedMessage | ||
| id="xpack.fleet.settings.fleetServerHostsTable.privateLinkBadgeTooltip" | ||
| defaultMessage="This Fleet Server host uses AWS PrivateLink for private network connectivity." |
There was a problem hiding this comment.
Not an issue here, but I'm guessing this will be made conditional if Azure or GCP private connectivity is added?
There was a problem hiding this comment.
One inline comment on error-handling consistency between the two serverless URL validators. Otherwise the change looks well-structured and the added test coverage (allow_edit sync, PrivateLink cleanup/restore, URL validation) is thorough.
Generated by Claude Reviewer for issue #275601 · 188.4 AIC · ⌖ 7.9 AIC · ⊞ 3.9K
| /> | ||
| } | ||
| > | ||
| <EuiBadge color="subdued"> |
There was a problem hiding this comment.
Nit: is there a semantic reason for the colour? Not that it looks bad, just wondering.
|
Tested locally and works well! Apart from the few nits above, I only have one concern: it is possible to add any field to or also to remove In contrast, this is not possible with outputs: The practical impact seems minor, but the fix sounds fairly simple: the outputs path has a |
@jillguyonnet That's a good catch! I'll fix it |
|
@elasticmachine merge upstream |
vishaangelova
left a comment
There was a problem hiding this comment.
LGTM for OAS docs and UI copy
a couple of suggestions on the errors
| } | ||
|
|
||
| throw new FleetServerHostUnauthorizedError( | ||
| `Fleet server host must have default URL in serverless: ${serverlessDefaultFleetServerHost.host_urls}` |
There was a problem hiding this comment.
| `Fleet server host must have default URL in serverless: ${serverlessDefaultFleetServerHost.host_urls}` | |
| `Fleet Server host must have a default URL in Serverless: ${serverlessDefaultFleetServerHost.host_urls}` |
| !isEqual(originalItem[key], data[key]) | ||
| ) { | ||
| throw new FleetServerHostUnauthorizedError( | ||
| `Preconfigured fleet server host ${id} ${key} cannot be updated outside of kibana config file.` |
There was a problem hiding this comment.
| `Preconfigured fleet server host ${id} ${key} cannot be updated outside of kibana config file.` | |
| `Preconfigured Fleet Server host ${id} ${key} cannot be updated outside of the Kibana config file.` |
|
@elasticmachine merge upstream |
📝 WalkthroughWalkthroughThis PR adds serverless PrivateLink support for Fleet Server hosts and Elasticsearch outputs. It introduces Changes
Sequence Diagram(s)Diagrams included within the hidden review stack artifact illustrate the update-guard flow and serverless validation flow. Suggested labels: 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
x-pack/platform/plugins/shared/fleet/server/routes/output/handler.ts (1)
189-199: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winHandle the actual not-found error shape from
outputService.get().Elsewhere in this file, missing outputs from
outputService.get()are treated as Boom 404s (Lines 90-94 and 126-129), not Saved Objects not-found errors. With the current check, a missinges-private-outputwill bubble out here instead of falling back to the intended default/private host validation path, so serverless POST/PUT can return the wrong error when PrivateLink is not configured. The new tests also mock the wrong error shape, so they won't catch it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@x-pack/platform/plugins/shared/fleet/server/routes/output/handler.ts` around lines 189 - 199, The private output lookup in the route handler is checking the wrong missing-error shape, so a missing es-private-output can escape instead of continuing the default/private host validation flow. Update the try/catch around outputService.get(SERVERLESS_PRIVATE_OUTPUT_ID) in the handler to treat the same Boom 404/not-found shape used elsewhere in this file as the expected missing case, and only rethrow unexpected errors. Also adjust the related tests to mock the Boom 404 shape rather than a SavedObjects not-found error so the fallback path is actually covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx`:
- Around line 151-178: The form currently treats preconfigured Fleet Server
hosts as read-only except for is_default, so editable keys listed in allow_edit
are still blocked. Update use_fleet_server_host_form and its related submit
logic to derive disabled state from allow_edit for each field (name, host_urls,
proxy_id, SSL inputs, etc.) instead of hardcoding is_default-only behavior,
using fleetServerHost.allow_edit as the source of truth. Also adjust the PUT
payload assembly and submit-enable checks so the form sends only the changed
allowed fields rather than always `{ is_default }`, and ensure the existing
helpers like useInput, useSwitchInput, and useComboInput respect the per-field
editability.
In
`@x-pack/platform/plugins/shared/fleet/server/services/fleet_server_host.test.ts`:
- Around line 502-518: The test in fleetServerHostService.update is asserting
the wrong promise behavior for a successful update path. In the should not throw
FleetServerHostUnauthorizedError when updating an allow_edit field case, replace
the rejects.not.toThrow expectation with a resolving assertion on the update
result so it verifies fleetServerHostService.update succeeds when mocked with a
valid preconfigured host and allowed field update.
In
`@x-pack/platform/plugins/shared/fleet/server/services/preconfiguration/outputs.ts`:
- Around line 168-172: The runtime default flags are being preserved by
applyAllowEditOverrides(), but bumpAllAgentPoliciesForOutput() still uses the
original config values from outputData, so policy bumps can ignore a PrivateLink
output that was promoted to default. Update the flow in this preconfiguration
output path so the values passed into bumpAllAgentPoliciesForOutput() come from
the overridden data after applyAllowEditOverrides() (or otherwise merge the
runtime is_default / is_default_monitoring flags into the object used for the
bump). Make sure the outputData used for later persistence and policy updates
stays aligned with the runtime defaults.
---
Duplicate comments:
In `@x-pack/platform/plugins/shared/fleet/server/routes/output/handler.ts`:
- Around line 189-199: The private output lookup in the route handler is
checking the wrong missing-error shape, so a missing es-private-output can
escape instead of continuing the default/private host validation flow. Update
the try/catch around outputService.get(SERVERLESS_PRIVATE_OUTPUT_ID) in the
handler to treat the same Boom 404/not-found shape used elsewhere in this file
as the expected missing case, and only rethrow unexpected errors. Also adjust
the related tests to mock the Boom 404 shape rather than a SavedObjects
not-found error so the fallback path is actually covered.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Enterprise
Run ID: 26827eaf-2b16-48b6-97eb-125a30753673
📒 Files selected for processing (31)
oas_docs/output/kibana.serverless.yamloas_docs/output/kibana.yamlx-pack/platform/plugins/shared/fleet/common/constants/fleet_server_policy_config.tsx-pack/platform/plugins/shared/fleet/common/constants/output.tsx-pack/platform/plugins/shared/fleet/common/types/models/fleet_server_policy_config.tsx-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsxx-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.tsxx-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsxx-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsxx-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsxx-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_table/index.tsxx-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/outputs_table/index.tsxx-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/index.tsxx-pack/platform/plugins/shared/fleet/server/config.tsx-pack/platform/plugins/shared/fleet/server/constants/index.tsx-pack/platform/plugins/shared/fleet/server/routes/fleet_server_hosts/handler.test.tsx-pack/platform/plugins/shared/fleet/server/routes/fleet_server_hosts/handler.tsx-pack/platform/plugins/shared/fleet/server/routes/output/handler.test.tsx-pack/platform/plugins/shared/fleet/server/routes/output/handler.tsx-pack/platform/plugins/shared/fleet/server/services/fleet_server_host.test.tsx-pack/platform/plugins/shared/fleet/server/services/fleet_server_host.tsx-pack/platform/plugins/shared/fleet/server/services/preconfiguration/fleet_server_host.test.tsx-pack/platform/plugins/shared/fleet/server/services/preconfiguration/fleet_server_host.tsx-pack/platform/plugins/shared/fleet/server/services/preconfiguration/outputs.test.tsx-pack/platform/plugins/shared/fleet/server/services/preconfiguration/outputs.tsx-pack/platform/plugins/shared/fleet/server/services/preconfiguration/outputs_privatelink.test.tsx-pack/platform/plugins/shared/fleet/server/services/preconfiguration/utils.tsx-pack/platform/plugins/shared/fleet/server/types/models/preconfiguration.tsx-pack/platform/plugins/shared/fleet/server/types/rest_spec/fleet_server_policy_config.tsx-pack/platform/plugins/shared/fleet/server/types/rest_spec/settings.tsx-pack/platform/plugins/shared/fleet/server/types/so_attributes.ts
| // Fields listed in allow_edit on a preconfigured host can be changed at runtime. | ||
| const allowEdit = fleetServerHost?.allow_edit ?? []; | ||
|
|
||
| const nameInput = useInput(fleetServerHost?.name ?? '', validateName, isEditDisabled); | ||
| const isDefaultInput = useSwitchInput( | ||
| fleetServerHost?.is_default ?? false, | ||
| isEditDisabled || fleetServerHost?.is_default | ||
| (isEditDisabled && !allowEdit.includes('is_default')) || !!fleetServerHost?.is_default | ||
| ); | ||
|
|
||
| const isServerless = cloud?.isServerlessEnabled; | ||
| // Set the host URLs to default for new Fleet server host in serverless. | ||
| const hostUrlsDefaultValue = | ||
| isServerless && !fleetServerHost?.host_urls | ||
| ? defaultFleetServerHost?.host_urls || [] | ||
| : fleetServerHost?.host_urls || []; | ||
|
|
||
| // For an existing row always show that row's own URLs; only fall back to the default | ||
| // host URLs when creating a new Fleet Server host in serverless (no fleetServerHost yet). | ||
| const hostUrlsDefaultValue = fleetServerHost?.host_urls?.length | ||
| ? fleetServerHost.host_urls | ||
| : isServerless | ||
| ? defaultFleetServerHost?.host_urls || [] | ||
| : []; | ||
|
|
||
| const hostUrlsDisabled = isEditDisabled || isServerless; | ||
| const hostUrlsInput = useComboInput( | ||
| 'hostUrls', | ||
| hostUrlsDefaultValue, | ||
| validateFleetServerHosts, | ||
| hostUrlsDisabled | ||
| ); | ||
| const proxyIdInput = useInput(fleetServerHost?.proxy_id ?? '', () => undefined, isEditDisabled); | ||
|
|
||
| const proxyIdInput = useInput(fleetServerHost?.proxy_id ?? '', () => undefined, isEditDisabled); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Honor allow_edit for all editable preconfigured-host fields.
Line 150 makes every preconfigured host effectively read-only up front, so Lines 154-178 still disable name, host_urls, proxy_id, and SSL inputs even when those keys are present in allow_edit. Lines 305-343 then hardcode every preconfigured PUT body to { is_default }, and Lines 399-400 only enable submit when isDefaultInput changes. The server-side update path accepts any key listed in allow_edit, so valid config-driven edits outside the is_default case are impossible from this UI.
Also applies to: 303-343, 398-416
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx`
around lines 151 - 178, The form currently treats preconfigured Fleet Server
hosts as read-only except for is_default, so editable keys listed in allow_edit
are still blocked. Update use_fleet_server_host_form and its related submit
logic to derive disabled state from allow_edit for each field (name, host_urls,
proxy_id, SSL inputs, etc.) instead of hardcoding is_default-only behavior,
using fleetServerHost.allow_edit as the source of truth. Also adjust the PUT
payload assembly and submit-enable checks so the form sends only the changed
allowed fields rather than always `{ is_default }`, and ensure the existing
helpers like useInput, useSwitchInput, and useComboInput respect the per-field
editability.
There was a problem hiding this comment.
Valid gap, low risk for now: allow_edit is currently only ever injected with is_default/is_default_monitoring, and the UI already handles those. If a future config ever adds other fields to allow_edit, this form would need updating too to avoid a UI/API mismatch. Leaving out of scope for this PR, will track as a follow-up.
There was a problem hiding this comment.
@criamico, agreed — reasonable to defer since allow_edit is currently constrained to is_default/is_default_monitoring. Want me to open a follow-up issue to track generalizing the form for future allow_edit fields?
(ᵔ◡ᵔ)
🧠 Learnings used
Learnt from: cesco-f
Repo: elastic/kibana PR: 270575
File: x-pack/platform/plugins/shared/streams_app/public/components/sig_events/significant_events_discovery/components/detections_tab/index.tsx:205-210
Timestamp: 2026-05-22T11:33:44.088Z
Learning: When reviewing Kibana React code that uses EUI’s `EuiBasicTable` for row-level interaction, use the supported `rowProps` approach (e.g., provide an `onClick` in `rowProps`) for handling row clicks. Do not recommend adding `role="button"` or `tabIndex` directly to `<tr>` elements via `rowProps`; `role="button"` on a `<tr>` is semantically invalid per the ARIA spec. Rely on EUI’s built-in table keyboard/navigation behavior instead of forcing extra ARIA/tabIndex attributes on `<tr>`.
Learnt from: mykolaharmash
Repo: elastic/kibana PR: 275382
File: x-pack/platform/plugins/shared/streams_app/public/components/significant_events/significant_events_discovery/components/significant_events_tab/significant_event_flyout.tsx:86-87
Timestamp: 2026-06-30T14:19:22.134Z
Learning: When reviewing TSX in this repository, `types/react` is pinned to React 18 (`18.2.79`). Therefore, a zero-argument `useRef<T>()` is expected to type-check and should not be flagged as a “React 19-only” typing error. Only apply that warning if the repo’s React typings/dependency has been updated to React 19 (or later); otherwise, treat the current `useRef<T>()` usage as valid.
| it('should not throw FleetServerHostUnauthorizedError when updating an allow_edit field', async () => { | ||
| const soClient = getMockedSoClient({ findHosts: true }); | ||
| mockGetPreconfiguredHost({ is_default: false }); | ||
| soClient.update.mockResolvedValue({ id: 'private-fleet-server', attributes: {} } as any); | ||
| (agentPolicyService.bumpAllAgentPoliciesForFleetServerHosts as jest.Mock).mockResolvedValue( | ||
| undefined | ||
| ); | ||
|
|
||
| const result = fleetServerHostService.update(soClient, esClientMock, 'private-fleet-server', { | ||
| is_default: true, | ||
| }); | ||
|
|
||
| // Should not be rejected with the preconfigured field auth error | ||
| await expect(result).rejects.not.toThrow( | ||
| 'Preconfigured Fleet Server host private-fleet-server is_default cannot be updated outside of the Kibana config file.' | ||
| ); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use a resolving assertion for the happy path.
This setup mocks fleetServerHostService.update() to succeed, so rejects.not.toThrow(...) fails on the success path and would also allow unrelated rejections to pass the test. Assert that the promise resolves instead.
Suggested fix
- const result = fleetServerHostService.update(soClient, esClientMock, 'private-fleet-server', {
- is_default: true,
- });
-
- // Should not be rejected with the preconfigured field auth error
- await expect(result).rejects.not.toThrow(
- 'Preconfigured Fleet Server host private-fleet-server is_default cannot be updated outside of the Kibana config file.'
- );
+ await expect(
+ fleetServerHostService.update(soClient, esClientMock, 'private-fleet-server', {
+ is_default: true,
+ })
+ ).resolves.toMatchObject({ is_default: true });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('should not throw FleetServerHostUnauthorizedError when updating an allow_edit field', async () => { | |
| const soClient = getMockedSoClient({ findHosts: true }); | |
| mockGetPreconfiguredHost({ is_default: false }); | |
| soClient.update.mockResolvedValue({ id: 'private-fleet-server', attributes: {} } as any); | |
| (agentPolicyService.bumpAllAgentPoliciesForFleetServerHosts as jest.Mock).mockResolvedValue( | |
| undefined | |
| ); | |
| const result = fleetServerHostService.update(soClient, esClientMock, 'private-fleet-server', { | |
| is_default: true, | |
| }); | |
| // Should not be rejected with the preconfigured field auth error | |
| await expect(result).rejects.not.toThrow( | |
| 'Preconfigured Fleet Server host private-fleet-server is_default cannot be updated outside of the Kibana config file.' | |
| ); | |
| }); | |
| it('should not throw FleetServerHostUnauthorizedError when updating an allow_edit field', async () => { | |
| const soClient = getMockedSoClient({ findHosts: true }); | |
| mockGetPreconfiguredHost({ is_default: false }); | |
| soClient.update.mockResolvedValue({ id: 'private-fleet-server', attributes: {} } as any); | |
| (agentPolicyService.bumpAllAgentPoliciesForFleetServerHosts as jest.Mock).mockResolvedValue( | |
| undefined | |
| ); | |
| await expect( | |
| fleetServerHostService.update(soClient, esClientMock, 'private-fleet-server', { | |
| is_default: true, | |
| }) | |
| ).resolves.toMatchObject({ is_default: true }); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@x-pack/platform/plugins/shared/fleet/server/services/fleet_server_host.test.ts`
around lines 502 - 518, The test in fleetServerHostService.update is asserting
the wrong promise behavior for a successful update path. In the should not throw
FleetServerHostUnauthorizedError when updating an allow_edit field case, replace
the rejects.not.toThrow expectation with a resolving assertion on the update
result so it verifies fleetServerHostService.update succeeds when mocked with a
valid preconfigured host and allowed field update.
There was a problem hiding this comment.
@criamico, understood — fair tradeoff given the two other tests already validate the security-critical blocking behavior. No objection to deferring the mock-chain fix. Feel free to open a follow-up issue if you'd like to track it, otherwise I'll leave it as-is.
🧠 Learnings used
Learnt from: kapral18
Repo: elastic/kibana PR: 258942
File: x-pack/platform/plugins/shared/index_management/public/application/components/template_form/steps/step_review.test.tsx:16-22
Timestamp: 2026-03-21T02:25:45.389Z
Learning: In elastic/kibana test files, it is acceptable to use the TypeScript `unknown` type for mock/spy “prop capture” callback functions (for example, `jest.fn()` implementations that record props for later assertions). Do not treat `unknown` here as a type-safety violation. Avoid importing concrete props types solely for the capture helper when the tests do not assert on the prop types themselves, since it adds unnecessary coupling/dependencies.
There was a problem hiding this comment.
Agreed the assertion is weak (masks an unrelated mock-chain error rather than proving success). Low risk though — the security-critical direction (blocking disallowed field edits) is covered by the two other tests in this block. Can be followed up separately to fix the mock chain rather than block this PR on it.
| applyAllowEditOverrides( | ||
| data as unknown as Record<string, unknown>, | ||
| existingOutput as unknown as Record<string, unknown>, | ||
| output.allow_edit | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Use the overridden default flags when bumping agent policies.
applyAllowEditOverrides() now preserves runtime is_default / is_default_monitoring in data, but the later bumpAllAgentPoliciesForOutput() call still reads outputData.* from config. If a PrivateLink output was promoted to default at runtime and sync updates some other field, Kibana persists the runtime default yet bumps policies as if it were still non-default.
Suggested fix
- await agentPolicyService.bumpAllAgentPoliciesForOutput(esClient, id, {
- isDefault: outputData.is_default,
- isDefaultMonitoring: outputData.is_default_monitoring,
- });
+ await agentPolicyService.bumpAllAgentPoliciesForOutput(esClient, id, {
+ isDefault: data.is_default,
+ isDefaultMonitoring: data.is_default_monitoring,
+ });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@x-pack/platform/plugins/shared/fleet/server/services/preconfiguration/outputs.ts`
around lines 168 - 172, The runtime default flags are being preserved by
applyAllowEditOverrides(), but bumpAllAgentPoliciesForOutput() still uses the
original config values from outputData, so policy bumps can ignore a PrivateLink
output that was promoted to default. Update the flow in this preconfiguration
output path so the values passed into bumpAllAgentPoliciesForOutput() come from
the overridden data after applyAllowEditOverrides() (or otherwise merge the
runtime is_default / is_default_monitoring flags into the object used for the
bump). Make sure the outputData used for later persistence and policy updates
stays aligned with the runtime defaults.
💛 Build succeeded, but was flaky
Failed CI StepsTest Failures
Metrics [docs]Async chunks
Page load bundle
History
cc @criamico |
…ut in serverless (elastic#275601) Closes [elastic/enhancements#28498](elastic/enhancements#28498) ## Summary Adds AWS PrivateLink private endpoint support for Fleet Server and Elasticsearch output in serverless. When the project controller injects a `private-fleet-server` Fleet Server host and `es-private-output` ES output via preconfiguration (`kibana.yml`) (or projects with PrivateLink enabled, Kibana now: - Reads the private endpoint SOs from config via the preconfiguration service - Relaxes the serverless URL validation to show the private endpoint URLs (in addition to the public default) in the settings table - Exposes the private Fleet Server host and ES output as selectable options in agent policy advanced settings - This allows users to route specific agent policies through private endpoints without affecting policies on public connectivity. The per-policy assignment (via the agent policy dropdown) always takes precedence over the general settings toggle. - The new privateLink endpoints are only shown when they exist in preconfiguration. If they are removed from preconfiguration, the related SOs are also deleted and the default goes back to the elastic defaults. - The `allow_edits` flag added to outputs and fleet server hosts makes possible to edit the defaults, so even if Kibana restarts the new defaults are persisted from the SO (otherwise if would be overriden every time there is a kibana restart) https://github.com/user-attachments/assets/aa8f2925-5887-4395-bab6-48779f2d2733 ### Testing steps (local env) - Start serverless environment - Add the following to kibana.dev.yml: ``` xpack.fleet.fleetServerHosts: - id: default-fleet-server name: Default Fleet server is_default: true host_urls: ['http://localhost:8220'] - id: private-fleet-server name: Private Fleet Server is_default: false host_urls: ['https://my-private-url.fleet.private.us-east-1.aws.elastic.cloud:443'] xpack.fleet.outputs: - id: es-default-output name: Default output type: elasticsearch is_default: true is_default_monitoring: true hosts: ['https://localhost:9200'] - id: es-private-output name: Private Elasticsearch Output type: elasticsearch is_default: false is_default_monitoring: false hosts: ['https://my-private-es.es.private.us-east-1.aws.elastic.cloud:443'] ``` - Navigate to Fleet > Settings and check the fleet server hosts url and the outputs url are visible and they can be set as defaults - Navigate to any agent policy > Settings and check that the fleet server hosts and outputs selector are now editable and contain the private urls ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] 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) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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 - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
…reconfigured default (#276363) ## Summary Setting a fleet server host as default (via API or UI) unsets `is_default` on the existing default host through a recursive `update()` call. That call forwarded the caller's `fromPreconfiguration` flag, which is `undefined` for non-preconfiguration requests. When the current default is a **preconfigured** host, the field-level preconfiguration guard added in #275601 then rejected the internal `is_default: false` update with: ``` Preconfigured Fleet Server host <id> is_default cannot be updated outside of the Kibana config file. ``` This broke Fleet Server setup — e.g. the `security_solution` endpoint dev/CI harness (`startFleetServerWithDocker` → `POST /api/fleet/fleet_server_hosts` with `is_default: true`) started failing with `403 Forbidden`, which surfaced as Defend Workflows Cypress failures (Fleet Server never becomes healthy → response actions fail with `Action [...] not found`). ### Root cause The "undefault the existing default host" step is a **system-level side effect** of choosing a new default, not a user edit. But both `create()` and `update()` forwarded `options?.fromPreconfiguration` into that recursive call, so any non-preconfiguration path (API request, UI edit) that set a new default while a preconfigured default existed hit the guard. Before #275601 this was harmless — nothing rejected field edits on preconfigured hosts. #275601 added a field-level guard that inspects every changed field, turning the long-standing forwarded flag into a 403. ### Fix Pass `fromPreconfiguration: true` unconditionally on the internal undefault call in both `create()` and `update()`. User edits to preconfigured hosts remain guarded because they go through the primary write path, which keeps the caller's real flag. `bulkCreateForPreconfiguration` is left untouched — its callers always pass `true`. ## Testing - Added regression test *"should undefault an existing preconfigured default host when setting a new default via API"* in `fleet_server_host.test.ts`. It updates a regular host to `is_default: true` while the current default is a preconfigured host with empty `allow_edit`, and asserts the update resolves and the preconfigured host is undefaulted (`soClient.update` called with `is_default: false`). - Confirmed the new test **fails without the fix** with the exact CI error, and passes with it. Full suite: 19/19 green. ## Release note Fixes a `403 Forbidden` error ("Preconfigured Fleet Server host ... is_default cannot be updated outside of the Kibana config file") when setting a new default Fleet Server host while the current default is a preconfigured host. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## Summary The `fleet-default-output` is created with `is_preconfigured: true` to prevent deletion. However, PR #275601 added a field-level guard that blocks API updates to ALL fields on preconfigured outputs unless they appear in `allow_edit`. Since `fleet-default-output` had no `allow_edit`, its `hosts`, `ca_sha256`, and `ca_trusted_fingerprint` fields became locked — even though they were always legitimately API-writable (they mirror `xpack.fleet.agents.elasticsearch.*` config and tooling like CI harnesses needs to rewrite them, e.g. rewriting `localhost` to the real local IP). This adds `allow_edit: ['hosts', 'ca_sha256', 'ca_trusted_fingerprint']` to `fleet-default-output`, restoring pre-#275601 behaviour for this specific output. The three fields listed are exactly the ones populated from config and the only ones that legitimately change when the underlying cluster's connectivity details change. **Operator note — config-vs-runtime precedence:** `allow_edit` has a second effect beyond API-writability: on Kibana restart, the preconfig sync will no longer overwrite stored values with config values for these three fields (the existing runtime value wins). Concretely, if an operator later changes `xpack.fleet.agents.elasticsearch.hosts` or the `ca_*` fields in `kibana.yml` and restarts, `fleet-default-output` will **not** pick up the new config value — the API-set value takes precedence. This is the intended semantic of `allow_edit` and matches the CI-harness use case (where the API-rewritten host should stick across restarts), but operators who rely on config-driven host updates should be aware that they would need to update the output via the Fleet API as well. **Not affected:** - Cloud/ECH: agents use the `es-containerhost` output (which already has its own `allow_edit`), not `fleet-default-output` - Serverless: `validateOutputServerless()` enforces a secondary guard that rejects hosts not matching cloud-injected URLs regardless of `allow_edit` ## Related - Fixes regression introduced by #275601 - Related to #276363 (same class of bug on fleet server hosts) ## Test plan - [ ] Start Kibana locally with `xpack.fleet.agents.elasticsearch.hosts` set to `http://localhost:9200` - [ ] Confirm `fleet-default-output` is created with `is_preconfigured: true` - [ ] Via Fleet API (`PUT /api/fleet/outputs/fleet-default-output`), update `hosts` to a different URL — should succeed (was 400 before this fix) - [ ] Confirm `ca_sha256` and `ca_trusted_fingerprint` are similarly updatable - [ ] Confirm attempting to update other fields (e.g. `name`) is still blocked with 400 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Cristina Amico <mariacristina.amico@elastic.co>
…c#276418) ## Summary The `fleet-default-output` is created with `is_preconfigured: true` to prevent deletion. However, PR elastic#275601 added a field-level guard that blocks API updates to ALL fields on preconfigured outputs unless they appear in `allow_edit`. Since `fleet-default-output` had no `allow_edit`, its `hosts`, `ca_sha256`, and `ca_trusted_fingerprint` fields became locked — even though they were always legitimately API-writable (they mirror `xpack.fleet.agents.elasticsearch.*` config and tooling like CI harnesses needs to rewrite them, e.g. rewriting `localhost` to the real local IP). This adds `allow_edit: ['hosts', 'ca_sha256', 'ca_trusted_fingerprint']` to `fleet-default-output`, restoring pre-elastic#275601 behaviour for this specific output. The three fields listed are exactly the ones populated from config and the only ones that legitimately change when the underlying cluster's connectivity details change. **Operator note — config-vs-runtime precedence:** `allow_edit` has a second effect beyond API-writability: on Kibana restart, the preconfig sync will no longer overwrite stored values with config values for these three fields (the existing runtime value wins). Concretely, if an operator later changes `xpack.fleet.agents.elasticsearch.hosts` or the `ca_*` fields in `kibana.yml` and restarts, `fleet-default-output` will **not** pick up the new config value — the API-set value takes precedence. This is the intended semantic of `allow_edit` and matches the CI-harness use case (where the API-rewritten host should stick across restarts), but operators who rely on config-driven host updates should be aware that they would need to update the output via the Fleet API as well. **Not affected:** - Cloud/ECH: agents use the `es-containerhost` output (which already has its own `allow_edit`), not `fleet-default-output` - Serverless: `validateOutputServerless()` enforces a secondary guard that rejects hosts not matching cloud-injected URLs regardless of `allow_edit` ## Related - Fixes regression introduced by elastic#275601 - Related to elastic#276363 (same class of bug on fleet server hosts) ## Test plan - [ ] Start Kibana locally with `xpack.fleet.agents.elasticsearch.hosts` set to `http://localhost:9200` - [ ] Confirm `fleet-default-output` is created with `is_preconfigured: true` - [ ] Via Fleet API (`PUT /api/fleet/outputs/fleet-default-output`), update `hosts` to a different URL — should succeed (was 400 before this fix) - [ ] Confirm `ca_sha256` and `ca_trusted_fingerprint` are similarly updatable - [ ] Confirm attempting to update other fields (e.g. `name`) is still blocked with 400 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Cristina Amico <mariacristina.amico@elastic.co> (cherry picked from commit e416d84)
…276418) (#277005) # Backport This will backport the following commits from `main` to `9.4`: - [[Fleet] Allow API updates to default output connection fields (#276418)](#276418) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Julia Bardi","email":"90178898+juliaElastic@users.noreply.github.com"},"sourceCommit":{"committedDate":"2026-07-08T14:12:01Z","message":"[Fleet] Allow API updates to default output connection fields (#276418)\n\n## Summary\n\nThe `fleet-default-output` is created with `is_preconfigured: true` to\nprevent deletion. However, PR #275601 added a field-level guard that\nblocks API updates to ALL fields on preconfigured outputs unless they\nappear in `allow_edit`. Since `fleet-default-output` had no\n`allow_edit`, its `hosts`, `ca_sha256`, and `ca_trusted_fingerprint`\nfields became locked — even though they were always legitimately\nAPI-writable (they mirror `xpack.fleet.agents.elasticsearch.*` config\nand tooling like CI harnesses needs to rewrite them, e.g. rewriting\n`localhost` to the real local IP).\n\nThis adds `allow_edit: ['hosts', 'ca_sha256', 'ca_trusted_fingerprint']`\nto `fleet-default-output`, restoring pre-#275601 behaviour for this\nspecific output. The three fields listed are exactly the ones populated\nfrom config and the only ones that legitimately change when the\nunderlying cluster's connectivity details change.\n\n**Operator note — config-vs-runtime precedence:** `allow_edit` has a\nsecond effect beyond API-writability: on Kibana restart, the preconfig\nsync will no longer overwrite stored values with config values for these\nthree fields (the existing runtime value wins). Concretely, if an\noperator later changes `xpack.fleet.agents.elasticsearch.hosts` or the\n`ca_*` fields in `kibana.yml` and restarts, `fleet-default-output` will\n**not** pick up the new config value — the API-set value takes\nprecedence. This is the intended semantic of `allow_edit` and matches\nthe CI-harness use case (where the API-rewritten host should stick\nacross restarts), but operators who rely on config-driven host updates\nshould be aware that they would need to update the output via the Fleet\nAPI as well.\n\n**Not affected:**\n- Cloud/ECH: agents use the `es-containerhost` output (which already has\nits own `allow_edit`), not `fleet-default-output`\n- Serverless: `validateOutputServerless()` enforces a secondary guard\nthat rejects hosts not matching cloud-injected URLs regardless of\n`allow_edit`\n\n## Related\n\n- Fixes regression introduced by #275601\n- Related to #276363 (same class of bug on fleet server hosts)\n\n## Test plan\n\n- [ ] Start Kibana locally with `xpack.fleet.agents.elasticsearch.hosts`\nset to `http://localhost:9200`\n- [ ] Confirm `fleet-default-output` is created with `is_preconfigured:\ntrue`\n- [ ] Via Fleet API (`PUT /api/fleet/outputs/fleet-default-output`),\nupdate `hosts` to a different URL — should succeed (was 400 before this\nfix)\n- [ ] Confirm `ca_sha256` and `ca_trusted_fingerprint` are similarly\nupdatable\n- [ ] Confirm attempting to update other fields (e.g. `name`) is still\nblocked with 400\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Cristina Amico <mariacristina.amico@elastic.co>","sha":"e416d84b178ada78bae3b59ee841eec2d9bf2fbf","branchLabelMapping":{"^v9.5.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Fleet","backport:version","v9.5.0","v9.4.4"],"title":"[Fleet] Allow API updates to default output connection fields","number":276418,"url":"https://github.com/elastic/kibana/pull/276418","mergeCommit":{"message":"[Fleet] Allow API updates to default output connection fields (#276418)\n\n## Summary\n\nThe `fleet-default-output` is created with `is_preconfigured: true` to\nprevent deletion. However, PR #275601 added a field-level guard that\nblocks API updates to ALL fields on preconfigured outputs unless they\nappear in `allow_edit`. Since `fleet-default-output` had no\n`allow_edit`, its `hosts`, `ca_sha256`, and `ca_trusted_fingerprint`\nfields became locked — even though they were always legitimately\nAPI-writable (they mirror `xpack.fleet.agents.elasticsearch.*` config\nand tooling like CI harnesses needs to rewrite them, e.g. rewriting\n`localhost` to the real local IP).\n\nThis adds `allow_edit: ['hosts', 'ca_sha256', 'ca_trusted_fingerprint']`\nto `fleet-default-output`, restoring pre-#275601 behaviour for this\nspecific output. The three fields listed are exactly the ones populated\nfrom config and the only ones that legitimately change when the\nunderlying cluster's connectivity details change.\n\n**Operator note — config-vs-runtime precedence:** `allow_edit` has a\nsecond effect beyond API-writability: on Kibana restart, the preconfig\nsync will no longer overwrite stored values with config values for these\nthree fields (the existing runtime value wins). Concretely, if an\noperator later changes `xpack.fleet.agents.elasticsearch.hosts` or the\n`ca_*` fields in `kibana.yml` and restarts, `fleet-default-output` will\n**not** pick up the new config value — the API-set value takes\nprecedence. This is the intended semantic of `allow_edit` and matches\nthe CI-harness use case (where the API-rewritten host should stick\nacross restarts), but operators who rely on config-driven host updates\nshould be aware that they would need to update the output via the Fleet\nAPI as well.\n\n**Not affected:**\n- Cloud/ECH: agents use the `es-containerhost` output (which already has\nits own `allow_edit`), not `fleet-default-output`\n- Serverless: `validateOutputServerless()` enforces a secondary guard\nthat rejects hosts not matching cloud-injected URLs regardless of\n`allow_edit`\n\n## Related\n\n- Fixes regression introduced by #275601\n- Related to #276363 (same class of bug on fleet server hosts)\n\n## Test plan\n\n- [ ] Start Kibana locally with `xpack.fleet.agents.elasticsearch.hosts`\nset to `http://localhost:9200`\n- [ ] Confirm `fleet-default-output` is created with `is_preconfigured:\ntrue`\n- [ ] Via Fleet API (`PUT /api/fleet/outputs/fleet-default-output`),\nupdate `hosts` to a different URL — should succeed (was 400 before this\nfix)\n- [ ] Confirm `ca_sha256` and `ca_trusted_fingerprint` are similarly\nupdatable\n- [ ] Confirm attempting to update other fields (e.g. `name`) is still\nblocked with 400\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Cristina Amico <mariacristina.amico@elastic.co>","sha":"e416d84b178ada78bae3b59ee841eec2d9bf2fbf"}},"sourceBranch":"main","suggestedTargetBranches":["9.4"],"targetPullRequestStates":[{"branch":"main","label":"v9.5.0","branchLabelMappingKey":"^v9.5.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/276418","number":276418,"mergeCommit":{"message":"[Fleet] Allow API updates to default output connection fields (#276418)\n\n## Summary\n\nThe `fleet-default-output` is created with `is_preconfigured: true` to\nprevent deletion. However, PR #275601 added a field-level guard that\nblocks API updates to ALL fields on preconfigured outputs unless they\nappear in `allow_edit`. Since `fleet-default-output` had no\n`allow_edit`, its `hosts`, `ca_sha256`, and `ca_trusted_fingerprint`\nfields became locked — even though they were always legitimately\nAPI-writable (they mirror `xpack.fleet.agents.elasticsearch.*` config\nand tooling like CI harnesses needs to rewrite them, e.g. rewriting\n`localhost` to the real local IP).\n\nThis adds `allow_edit: ['hosts', 'ca_sha256', 'ca_trusted_fingerprint']`\nto `fleet-default-output`, restoring pre-#275601 behaviour for this\nspecific output. The three fields listed are exactly the ones populated\nfrom config and the only ones that legitimately change when the\nunderlying cluster's connectivity details change.\n\n**Operator note — config-vs-runtime precedence:** `allow_edit` has a\nsecond effect beyond API-writability: on Kibana restart, the preconfig\nsync will no longer overwrite stored values with config values for these\nthree fields (the existing runtime value wins). Concretely, if an\noperator later changes `xpack.fleet.agents.elasticsearch.hosts` or the\n`ca_*` fields in `kibana.yml` and restarts, `fleet-default-output` will\n**not** pick up the new config value — the API-set value takes\nprecedence. This is the intended semantic of `allow_edit` and matches\nthe CI-harness use case (where the API-rewritten host should stick\nacross restarts), but operators who rely on config-driven host updates\nshould be aware that they would need to update the output via the Fleet\nAPI as well.\n\n**Not affected:**\n- Cloud/ECH: agents use the `es-containerhost` output (which already has\nits own `allow_edit`), not `fleet-default-output`\n- Serverless: `validateOutputServerless()` enforces a secondary guard\nthat rejects hosts not matching cloud-injected URLs regardless of\n`allow_edit`\n\n## Related\n\n- Fixes regression introduced by #275601\n- Related to #276363 (same class of bug on fleet server hosts)\n\n## Test plan\n\n- [ ] Start Kibana locally with `xpack.fleet.agents.elasticsearch.hosts`\nset to `http://localhost:9200`\n- [ ] Confirm `fleet-default-output` is created with `is_preconfigured:\ntrue`\n- [ ] Via Fleet API (`PUT /api/fleet/outputs/fleet-default-output`),\nupdate `hosts` to a different URL — should succeed (was 400 before this\nfix)\n- [ ] Confirm `ca_sha256` and `ca_trusted_fingerprint` are similarly\nupdatable\n- [ ] Confirm attempting to update other fields (e.g. `name`) is still\nblocked with 400\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Cristina Amico <mariacristina.amico@elastic.co>","sha":"e416d84b178ada78bae3b59ee841eec2d9bf2fbf"}},{"branch":"9.4","label":"v9.4.4","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Cristina Amico <mariacristina.amico@elastic.co>

Closes elastic/enhancements#28498
Summary
Adds AWS PrivateLink private endpoint support for Fleet Server and Elasticsearch output in serverless. When the project controller injects a
private-fleet-serverFleet Server host andes-private-outputES output via preconfiguration (kibana.yml) (or projects with PrivateLink enabled, Kibana now:allow_editsflag added to outputs and fleet server hosts makes possible to edit the defaults, so even if Kibana restarts the new defaults are persisted from the SO (otherwise if would be overriden every time there is a kibana restart)Screen.Recording.2026-06-30.at.23.40.57.mov
Testing steps (local env)
Checklist
Check the PR satisfies following conditions.
Reviewers should verify this PR satisfies this list as well.
release_note:breakinglabel should be applied in these situations.release_note:*label is applied per the guidelinesbackport:*labels.Identify risks
Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss.
Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging.