Skip to content

[CPS] Introduce kbn-project-routing header for simplified project_routing propagation#256839

Closed
gsoldevila wants to merge 8 commits intoelastic:mainfrom
gsoldevila:cps/kbn-project-routing-header
Closed

[CPS] Introduce kbn-project-routing header for simplified project_routing propagation#256839
gsoldevila wants to merge 8 commits intoelastic:mainfrom
gsoldevila:cps/kbn-project-routing-header

Conversation

@gsoldevila
Copy link
Copy Markdown
Member

Summary

Closes elastic/kibana-team#3032

Introduces a kbn-project-routing HTTP header as a cleaner propagation channel for CPS project_routing, eliminating the need to thread the value through request bodies and service layers on every app that integrates with CPS.

Currently apps must pass projectRouting in the HTTP body, extract it server-side, and inject it explicitly into asScoped(). This pattern was established in #250666 and #253654 and would need to be replicated by every app that opts into CPS.

With this change, apps only need to:

  • Client side: set { 'kbn-project-routing': value } in the HTTP request headers via the Kibana HTTP client
  • Server side: call esClient.asScoped(request, { projectRouting: 'request-header' })

Changes

New kbn-project-routing header constant added to @kbn/cps-common and @kbn/cps-server-utils.

New RequestHeaderRouting interface and asScoped overload in the public @kbn/core-elasticsearch-server API. Falls back to origin-only routing when the header is absent.

OnRequestHandlerFactory refactored (internal type) from a mixed union where ScopeableUrlRequest was embedded directly in the projectRouting field, to a proper discriminated union (FactoryRoutingOpts). The 'space' and 'request-header' cases now carry their request objects explicitly as a typed request field.

ClusterClientMock.asScoped updated to accept (request: ScopeableRequest, opts?: AsScopedOptions) to cover all overloads correctly.

Testing

  • 4 new unit tests in cps_request_handler_factory.test.ts: header present, header absent (fallback), header as array (first value taken), CPS disabled (strips regardless)
  • 2 new unit tests in cluster_client.test.ts: header routing and absent-header fallback

Follow-up

A separate PR (branched from this one) will demonstrate adoption by simplifying the data plugin's existing projectRouting body-propagation path to use the header instead.

Made with Cursor

@gsoldevila gsoldevila added Team:Core Platform Core services: plugins, logging, config, saved objects, http, ES client, i18n, etc t// release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting labels Mar 10, 2026
…ting propagation

Adds a new `projectRouting: 'request-header'` option to `IClusterClient.asScoped()`.
When selected, the scoped Elasticsearch client reads the `project_routing` value from
the `kbn-project-routing` HTTP header on the incoming request instead of requiring
callers to thread the value through request bodies and service layers.

- Adds `KBN_PROJECT_ROUTING_HEADER` constant to `@kbn/cps-common` and `@kbn/cps-server-utils`
- Adds `RequestHeaderRouting` interface to the public `@kbn/core-elasticsearch-server` API
- Refactors `OnRequestHandlerFactory` (internal) from a mixed union with an embedded
  `ScopeableUrlRequest` to a proper discriminated union (`FactoryRoutingOpts`), making
  the `'space'` and `'request-header'` cases explicitly typed
- Falls back to `'origin-only'` routing when the header is absent
- Updates the `ClusterClientMock` to accept `(request, opts?)` for all overloads

Closes elastic/kibana-team#3032

Made-with: Cursor
…uting

The Kibana HTTP client rejects headers starting with 'kbn-', so the
header must use the 'x-kbn-' prefix instead.

Made-with: Cursor
@gsoldevila gsoldevila force-pushed the cps/kbn-project-routing-header branch from a01d506 to edbe973 Compare March 12, 2026 13:51
…load

- Renames `projectRouting: 'space'` to `'space-npre'` in FactoryRoutingOpts,
  AsScopedOptions, SpaceNPRERouting, and all call sites/tests, to make the
  Named Project Routing Expression intent explicit in the option name.

- Absorbs `logger` into FactoryRoutingOpts (via CommonFactoryRoutingOpts
  intersection) so the factory signature is self-contained.

- Adds a broad `asScoped(request: KibanaRequest, opts?: AsScopedOptions)`
  overload to IClusterClient, allowing callers that hold an unnarrowed
  AsScopedOptions to call asScoped without manual routing dispatch.

- Removes the now-redundant `createScopedEsClient` helper in SearchService,
  replacing both call sites with a direct `elasticsearch.client.asScoped(request, opts)`.

Made-with: Cursor
@gsoldevila gsoldevila marked this pull request as ready for review March 12, 2026 15:03
@gsoldevila gsoldevila requested review from a team as code owners March 12, 2026 15:03
…ining callers

The asScoped(request: KibanaRequest, opts?: AsScopedOptions) overload
on IClusterClient changed the ClusterClientMock signature to require a
request argument, breaking all tests that call asScoped() with no args
(actions_telemetry, security user_profile, etc.).

Changes:
- Remove the broad overload and the KibanaRequest import from IClusterClient
- Revert ClusterClientMock.asScoped back to () => ScopedClusterClientMock
- Restore createScopedEsClient in SearchService with a switch on
  projectRouting (handles 'space-npre', 'all', 'origin-only',
  'request-header', and the default no-opts case)
- Fix the remaining 'space' -> 'space-npre' renames that CI caught:
  search_service.test.ts (description text),
  alerting/get_executor_services.ts (constant + comment),
  alerting/get_executor_services.test.ts (SpaceNPRERouting literal)

Made-with: Cursor
@gsoldevila gsoldevila requested a review from a team as a code owner March 12, 2026 16:37
gsoldevila and others added 4 commits March 12, 2026 21:39
Remove 'origin-only' and 'all' from the public AsScopedOptions. These
were never intended as general-purpose options for plugin developers:
- 'origin-only' is the implicit default when asScoped() is called with no
  opts, and is used internally by asInternalUser (FactoryRoutingOpts keeps
  it as an internal detail).
- 'all' has no public callsites; broadcasting to all projects can be
  achieved client-side by setting the x-kbn-project-routing header to
  '_alias:*' and relying on 'request-header' routing.

The two remaining public options are:
- 'space-npre': route to the NPRE configured for the current Kibana space
  (requires a ScopeableUrlRequest so the space can be extracted from the URL)
- 'request-header': read project_routing from the x-kbn-project-routing
  request header; falls back to origin-only when the header is absent

This makes the ClusterClient.asScoped() switch trivial enough to inline,
so the createScopedEsClient helper in SearchService is removed in favour
of a direct asScoped(request, opts ?? { projectRouting: 'request-header' })
call. The 'request-header' default ensures that search requests issued by
the data plugin always respect the project selection made by the user in
the CPS project picker.

Changes:
- Remove OriginOnlyRouting and AllProjectsRouting interfaces from the
  public package (kept internally in FactoryRoutingOpts)
- Narrow AsScopedOptions.projectRouting to 'space-npre' | 'request-header'
- Update ClusterClient.asScoped overloads and implementation body
  (no-opts → 'origin-only' internally; opts present → 'space-npre' or
  'request-header')
- Remove createScopedEsClient from SearchService; inline the asScoped call
- Update cluster_client.test.ts and search_service.test.ts accordingly

Made-with: Cursor
@elasticmachine
Copy link
Copy Markdown
Contributor

elasticmachine commented Mar 13, 2026

⏳ Build in-progress, with failures

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #64 / Cloud Security Posture GET /internal/cloud_security_posture/stats CSPM Compliance Dashboard Stats API should return CSPM benchmarks V2

History

@gsoldevila
Copy link
Copy Markdown
Member Author

Will be tackled on #256844

@gsoldevila gsoldevila closed this Mar 13, 2026
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:Core Platform Core services: plugins, logging, config, saved objects, http, ES client, i18n, etc t//

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants