Skip to content

[Fleet] Add PrivateLink endpoint support for Fleet Server and ES output in serverless#275601

Merged
criamico merged 16 commits into
elastic:mainfrom
criamico:28498_fleet_private_link
Jul 1, 2026
Merged

[Fleet] Add PrivateLink endpoint support for Fleet Server and ES output in serverless#275601
criamico merged 16 commits into
elastic:mainfrom
criamico:28498_fleet_private_link

Conversation

@criamico

@criamico criamico commented Jun 30, 2026

Copy link
Copy Markdown
Member

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-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)
Screen.Recording.2026-06-30.at.23.40.57.mov

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, uses sentence case text and includes i18n support
  • Documentation was added for features that require explanation or tutorials
  • Unit or functional tests 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
  • 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 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
  • Review the backport guidelines 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.

@criamico criamico self-assigned this Jun 30, 2026
@criamico criamico added Team:Fleet Team label for Observability Data Collection Fleet team release_note:feature Makes this part of the condensed release notes backport:skip This PR does not require backporting labels Jun 30, 2026
@criamico
criamico marked this pull request as ready for review July 1, 2026 08:36
@criamico
criamico requested a review from a team as a code owner July 1, 2026 08:36
@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

Pinging @elastic/fleet (Team:Fleet)

@criamico
criamico requested a review from vishaangelova July 1, 2026 08:48
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_*

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@elastic-vault-github-plugin-prod
elastic-vault-github-plugin-prod Bot requested a review from a team as a code owner July 1, 2026 09:25
return;
}
} catch (e) {
// Private endpoint SO not present — PrivateLink not enabled for this project.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue here, but I'm guessing this will be made conditional if Azure or GCP private connectivity is added?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 671e915

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: is there a semantic reason for the colour? Not that it looks bad, just wondering.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, this part was done in a rush and needs some polishing. I'm removing the color so it's more in line with the other badges in the page:
Screenshot 2026-07-01 at 12 37 05

@jillguyonnet

Copy link
Copy Markdown
Member

Tested locally and works well! Apart from the few nits above, I only have one concern: it is possible to add any field to allow_edit with the API, e.g. host URLs:

PUT kbn:/api/fleet/fleet_server_hosts/private-fleet-server
{
  "allow_edit": ["host_urls", "is_default"]
}

or also to remove is_default from there.

In contrast, this is not possible with outputs:

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Preconfigured output es-private-output allow_edit cannot be updated outside of kibana config file."
}

The practical impact seems minor, but the fix sounds fairly simple: the outputs path has a _validateFieldsAreEditable guard that compares what the caller is trying to modify against the SO's allow_edit list, but the fleet server host update service method has no equivalent. Adding a guard that checks originalItem.is_preconfigured on the service layer should fix it.

@criamico

criamico commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

Tested locally and works well! Apart from the few nits above, I only have one concern: it is possible to add any field to allow_edit with the API, e.g. host URLs:
..

@jillguyonnet That's a good catch! I'll fix it

@criamico

criamico commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

@elasticmachine merge upstream

@vishaangelova vishaangelova left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}`

@vishaangelova vishaangelova Jul 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`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.`

@vishaangelova vishaangelova Jul 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`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.`

@jillguyonnet jillguyonnet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes, LGTM! 🚀

@criamico

criamico commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

@elasticmachine merge upstream

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds serverless PrivateLink support for Fleet Server hosts and Elasticsearch outputs. It introduces allow_edit fields to schemas and types, new constants (SERVERLESS_PRIVATE_OUTPUT_ID, SERVERLESS_PRIVATE_FLEET_SERVER_HOST_ID), and config fields for private host/endpoint URLs. Server-side preconfiguration logic now injects and merges allow_edit for PrivateLink entries, guards field-level updates on preconfigured hosts, and handles cleanup/restoration of defaults when PrivateLink resources are removed. Route handlers validate host/output URLs against default or private endpoints. UI components add provider badges and filter internal PrivateLink items into visible tables and selectors. OpenAPI specs are updated with the new allow_edit property.

Changes

Cohort / File(s) Summary
Constants & schemas Added SERVERLESS_PRIVATE_OUTPUT_ID, SERVERLESS_PRIVATE_FLEET_SERVER_HOST_ID; allow_edit field added to types/schemas; new config fields privateFleetServerHost/privateElasticsearchHost; OpenAPI spec updates
FleetServerHostService Per-field update authorization guard based on allow_edit; new applyAllowEditOverrides utility
Preconfiguration (hosts/outputs) Injects allow_edit for PrivateLink entries; merges overrides during sync; restores public default and deletes PrivateLink entity on removal
Route handlers Validates host/output URLs against default or private endpoint saved objects
Public hooks Serverless-aware filtering of PrivateLink/internal items in output and fleet-server-host selectors
Fleet Server host form Restricts editable fields and submission payload for preconfigured hosts based on allow_edit
Settings UI tables/page Adds PrivateLink provider badges; adjusts item filtering and default host selection

Sequence Diagram(s)

Diagrams included within the hidden review stack artifact illustrate the update-guard flow and serverless validation flow.

Suggested labels: release_note:enhancement, ci:cloud-deploy, v9.5.0

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • 🛠️ Update Documentation

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Handle 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 missing es-private-output will 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6fa5257 and 07564e8.

📒 Files selected for processing (31)
  • oas_docs/output/kibana.serverless.yaml
  • oas_docs/output/kibana.yaml
  • x-pack/platform/plugins/shared/fleet/common/constants/fleet_server_policy_config.ts
  • x-pack/platform/plugins/shared/fleet/common/constants/output.ts
  • x-pack/platform/plugins/shared/fleet/common/types/models/fleet_server_policy_config.ts
  • x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsx
  • x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.tsx
  • x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx
  • x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx
  • x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx
  • x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_table/index.tsx
  • x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/outputs_table/index.tsx
  • x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/index.tsx
  • x-pack/platform/plugins/shared/fleet/server/config.ts
  • x-pack/platform/plugins/shared/fleet/server/constants/index.ts
  • x-pack/platform/plugins/shared/fleet/server/routes/fleet_server_hosts/handler.test.ts
  • x-pack/platform/plugins/shared/fleet/server/routes/fleet_server_hosts/handler.ts
  • x-pack/platform/plugins/shared/fleet/server/routes/output/handler.test.ts
  • x-pack/platform/plugins/shared/fleet/server/routes/output/handler.ts
  • x-pack/platform/plugins/shared/fleet/server/services/fleet_server_host.test.ts
  • x-pack/platform/plugins/shared/fleet/server/services/fleet_server_host.ts
  • x-pack/platform/plugins/shared/fleet/server/services/preconfiguration/fleet_server_host.test.ts
  • x-pack/platform/plugins/shared/fleet/server/services/preconfiguration/fleet_server_host.ts
  • x-pack/platform/plugins/shared/fleet/server/services/preconfiguration/outputs.test.ts
  • x-pack/platform/plugins/shared/fleet/server/services/preconfiguration/outputs.ts
  • x-pack/platform/plugins/shared/fleet/server/services/preconfiguration/outputs_privatelink.test.ts
  • x-pack/platform/plugins/shared/fleet/server/services/preconfiguration/utils.ts
  • x-pack/platform/plugins/shared/fleet/server/types/models/preconfiguration.ts
  • x-pack/platform/plugins/shared/fleet/server/types/rest_spec/fleet_server_policy_config.ts
  • x-pack/platform/plugins/shared/fleet/server/types/rest_spec/settings.ts
  • x-pack/platform/plugins/shared/fleet/server/types/so_attributes.ts

Comment on lines +151 to +178
// 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);

@coderabbitai coderabbitai Bot Jul 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Comment on lines +502 to +518
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.'
);
});

@coderabbitai coderabbitai Bot Jul 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +168 to +172
applyAllowEditOverrides(
data as unknown as Record<string, unknown>,
existingOutput as unknown as Record<string, unknown>,
output.allow_edit
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

@criamico
criamico enabled auto-merge (squash) July 1, 2026 15:24
@kibanamachine

Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] Scout Lane #9 - stateful-classic / default / local-stateful-classic - UptimeIntegrationDeprecation - returns true when non-managed synthetics policies exist

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
fleet 2.8MB 2.8MB +1.8KB

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
fleet 214.1KB 214.1KB +71.0B

History

cc @criamico

@criamico
criamico merged commit 1f05b4e into elastic:main Jul 1, 2026
40 checks passed
MykhailoKondrat pushed a commit to MykhailoKondrat/kibana that referenced this pull request Jul 2, 2026
…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>
juliaElastic added a commit that referenced this pull request Jul 6, 2026
…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>
juliaElastic added a commit that referenced this pull request Jul 8, 2026
## 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>
kibanamachine added a commit to kibanamachine/kibana that referenced this pull request Jul 8, 2026
…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)
kibanamachine added a commit that referenced this pull request Jul 8, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting release_note:feature Makes this part of the condensed release notes Team:Fleet Team label for Observability Data Collection Fleet team v9.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants