Skip to content

[Fleet] Strip remote-cluster transform source indices on Serverless#275649

Merged
szwarckonrad merged 6 commits into
elastic:mainfrom
szwarckonrad:fleet-strip-remote-transform-source-serverless
Jul 2, 2026
Merged

[Fleet] Strip remote-cluster transform source indices on Serverless#275649
szwarckonrad merged 6 commits into
elastic:mainfrom
szwarckonrad:fleet-strip-remote-transform-source-serverless

Conversation

@szwarckonrad

@szwarckonrad szwarckonrad commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Installing the Elastic Defend integration fails on Serverless with cross-project search (CPS) enabled with a 400. The metadata_united transform carries a cross-cluster (*:) source so the managing cluster can read endpoint metadata from a remote Elasticsearch output (elastic/endpoint-package#747). CCS is stateful-only, so a Serverless project running in cross-project mode rejects any transform whose source targets remote indices (action_request_validation_exception: Cross-project calls are not supported, but remote indices were requested), which aborts the whole package install.

This strips the remote-cluster (*:) entries from the Defend metadata_united transform's source on Serverless, before putTransform, so it installs and runs against its local indices. Stateful is untouched (*: there resolves to empty when no remote is connected).

The strip is gated on isServerlessEnabled (all Serverless), not on CPS specifically: CCS is unavailable on Serverless regardless, so a *: source is never valid there — stripping it is harmless when CPS is off (it would just resolve to empty) and prevents the failure if CPS is enabled later.

Deliberately narrow + temporary

Per team decision (feature-freeze timing), this is a minimal, temporary bridge:

  • Scoped to one transform by name — the strip only touches transforms whose installationName contains endpoint.metadata_united- (matches v1 + v2 ids). No other package's transforms are affected, even if they carried a *: source.
  • Isolated for easy removal — the logic lives in its own ccs_transform_source.ts util (+ test), called from install.ts's handleTransformInstall. When the long-term approach lands, removal is a clean delete of that file plus one import/call.
  • The longer-term approach is per-environment transform variants selected declaratively at install (_meta.environments), so no source is mutated at install: [Fleet] Install package transforms by stack environment (_meta.environments) #275669 (Fleet gate) + Split metadata_united transform into stateful and serverless variants endpoint-package#750 (the two variants). Longer still, the plan is to remove these transforms entirely.

What this looks like

Installing endpoint 9.5.0-prerelease.1 (the build carrying the *: source):

Scenario Before After
Serverless, cross-project enabled ❌ 400 ✅ 200 — *: stripped, transform started
Serverless, cross-project disabled ✅ 200 (*: resolves empty) ✅ 200 — *: stripped (harmless)
Stateful ✅ 200 ✅ 200 (*: kept)
Testing
  • Unit (ccs_transform_source.test.ts): strips the Defend transform's *: on serverless (logged); a no-op without a *:, on stateful, and for any non-metadata_united transform. The isRemoteIndexExpression classifier mirrors ES RemoteClusterAware#isRemoteIndexName, so date-math (e.g. a +12:00 timezone inside <...>) and :: selectors are not misread as cluster separators.
  • Install wiring (transforms.test.ts): the strip runs during installTransforms on the legacy JSON path — serverless strips, stateful preserves.
  • Manual E2E on local serverless (cross-project enabled): endpoint install 400 → 200, installed transform source reduced to local indices, transform started.
Root cause (ES, not Fleet)

ES TransformConfig.validateNoCrossProjectWhenCrossProjectIsDisabled rejects remote source indices when CrossProjectModeDecider.crossProjectEnabled() (node setting serverless.cross_project.enabled=true, Serverless only) is true and the TRANSFORM_CROSS_PROJECT feature flag is off. defer_validation:true doesn't skip it (request-level validation). Fleet's putTransform catch only swallows security_exception/resource_already_exists_exception, so the 400 propagates and fails the install.

@szwarckonrad szwarckonrad added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting Team:Fleet Team label for Observability Data Collection Fleet team labels Jun 30, 2026
@szwarckonrad szwarckonrad self-assigned this Jun 30, 2026
@szwarckonrad

Copy link
Copy Markdown
Contributor Author

@elasticmachine merge upstream

@szwarckonrad

Copy link
Copy Markdown
Contributor Author

/ci

@kibanamachine

Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] Scout Lane #7 - stateful-classic / default / local-stateful-classic - Observability alerts - summary widget - shows active and total alert counts
  • [job] [logs] Scout Lane #9 - stateful-classic / default / local-stateful-classic - UptimeIntegrationDeprecation - returns true when non-managed synthetics policies exist

Metrics [docs]

✅ unchanged

History

cc @szwarckonrad

@szwarckonrad szwarckonrad marked this pull request as ready for review July 1, 2026 20:41
@szwarckonrad szwarckonrad requested a review from a team as a code owner July 1, 2026 20:41
@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

Pinging @elastic/fleet (Team:Fleet)

@szwarckonrad szwarckonrad merged commit 3ef2275 into elastic:main Jul 2, 2026
42 checks passed
MykhailoKondrat pushed a commit to MykhailoKondrat/kibana that referenced this pull request Jul 2, 2026
…lastic#275649)

## Summary

Installing the **Elastic Defend** integration fails on Serverless **with
cross-project search (CPS) enabled** with a `400`. The `metadata_united`
transform carries a cross-cluster (`*:`) source so the managing cluster
can read endpoint metadata from a remote Elasticsearch output
([elastic/endpoint-package#747](elastic/endpoint-package#747)).
CCS is **stateful-only**, so a Serverless project running in
cross-project mode rejects any transform whose source targets remote
indices (`action_request_validation_exception: Cross-project calls are
not supported, but remote indices were requested`), which aborts the
whole package install.

This strips the remote-cluster (`*:`) entries from **the Defend
`metadata_united` transform's** source on Serverless, before
`putTransform`, so it installs and runs against its local indices.
Stateful is untouched (`*:` there resolves to empty when no remote is
connected).

The strip is gated on `isServerlessEnabled` (all Serverless), **not** on
CPS specifically: CCS is unavailable on Serverless regardless, so a `*:`
source is never valid there — stripping it is harmless when CPS is off
(it would just resolve to empty) and prevents the failure if CPS is
enabled later.

## Deliberately narrow + temporary

Per team decision (feature-freeze timing), this is a **minimal,
temporary bridge**:
- **Scoped to one transform by name** — the strip only touches
transforms whose `installationName` contains `endpoint.metadata_united-`
(matches v1 + v2 ids). No other package's transforms are affected, even
if they carried a `*:` source.
- **Isolated for easy removal** — the logic lives in its own
`ccs_transform_source.ts` util (+ test), called from `install.ts`'s
`handleTransformInstall`. When the long-term approach lands, removal is
a clean delete of that file plus one import/call.
- The **longer-term** approach is per-environment transform variants
selected declaratively at install (`_meta.environments`), so no source
is mutated at install: **elastic#275669** (Fleet gate) +
**elastic/endpoint-package#750** (the two variants). Longer still, the
plan is to remove these transforms entirely.

## What this looks like

Installing endpoint `9.5.0-prerelease.1` (the build carrying the `*:`
source):

| Scenario | Before | After |
|---|---|---|
| Serverless, cross-project **enabled** | ❌ 400 | ✅ 200 — `*:` stripped,
transform `started` |
| Serverless, cross-project **disabled** | ✅ 200 (`*:` resolves empty) |
✅ 200 — `*:` stripped (harmless) |
| Stateful | ✅ 200 | ✅ 200 (`*:` kept) |

<details>
<summary>Testing</summary>

- **Unit** (`ccs_transform_source.test.ts`): strips the Defend
transform's `*:` on serverless (logged); a no-op without a `*:`, on
stateful, and for **any non-`metadata_united` transform**. The
`isRemoteIndexExpression` classifier mirrors ES
`RemoteClusterAware#isRemoteIndexName`, so date-math (e.g. a `+12:00`
timezone inside `<...>`) and `::` selectors are not misread as cluster
separators.
- **Install wiring** (`transforms.test.ts`): the strip runs during
`installTransforms` on the legacy JSON path — serverless strips,
stateful preserves.
- **Manual E2E** on local serverless (cross-project enabled): endpoint
install `400 → 200`, installed transform source reduced to local
indices, transform `started`.

</details>

<details>
<summary>Root cause (ES, not Fleet)</summary>

ES `TransformConfig.validateNoCrossProjectWhenCrossProjectIsDisabled`
rejects remote source indices when
`CrossProjectModeDecider.crossProjectEnabled()` (node setting
`serverless.cross_project.enabled=true`, Serverless only) is true and
the `TRANSFORM_CROSS_PROJECT` feature flag is off.
`defer_validation:true` doesn't skip it (request-level validation).
Fleet's `putTransform` catch only swallows
`security_exception`/`resource_already_exists_exception`, so the 400
propagates and fails the install.

</details>

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
szwarckonrad added a commit that referenced this pull request Jul 6, 2026
…with remote ES output (#271559)

Depends on:
elastic/elasticsearch#152211
elastic/endpoint-package#747
#275649

When an Elastic Defend agent uses a Fleet remote ES output, its endpoint
data (metadata, events, policy responses, action responses) is written
to the remote cluster rather than the managing cluster. The managing
cluster's Kibana then can't read it, so endpoint management surfaces
like host list, host details, response actions, policy responses and
suggestions come up empty or broken.

This PR makes the Security Solution endpoint read paths
cross-cluster-search (CCS) aware: when remote clusters are connected,
endpoint index patterns are prefixed with `*:` so the managing cluster
reads the data wherever the agent shipped it. Same approach osquery
shipped in #260518.

The entire pathway is gated behind a new experimental feature flag
`defendRemoteOutputCcs`, off by default. With the flag off, or when no
remote clusters are connected, every prefix is a no-op and behavior is
unchanged.

What's included

- New `ccs_utils.ts` helper (`hasConnectedRemoteClusters` +
`prefixIndexPatternsWithCcs`), modeled on osquery's.
- ccsEnabled threaded through the endpoint read paths, applied at the
DSL/index-pattern build sites:
- metadata + agent status (host list, host details, capability
validation)
  - response actions (reads / validation / status / history)
  - policy responses
  - suggestions + endpoint fields
  - workflow insights
- The `defendRemoteOutputCcs` flag is checked inside
`hasConnectedRemoteClusters`, so it short-circuits before any
`_remote/info` call when off.

Testing

Validated end-to-end on a local two-cluster setup (managing +
remote-output, CCS configured): host list, host details, all response
action types completing over CCS, policy responses, and
suggestions/endpoint fields. Also verified on a single-cluster
(no-remotes) deployment that flag-on and flag-off behave identically to
today.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
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:skip Skip the PR/issue when compiling 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.

4 participants