Skip to content

Commit 37a649c

Browse files
authored
Merge branch 'main' into 16717-event-driven-triggers-testing-ux
2 parents 907bf61 + 5ed988c commit 37a649c

296 files changed

Lines changed: 11870 additions & 2883 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/reference/configuration-reference/internationalization-settings.md

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,40 @@ When a user sets a preferred language, it is stored in their user profile and ta
3030

3131
{{kib}} resolves the display language using the following priority chain:
3232

33-
1. **User profile setting** — The language selected by the user in their profile or the user menu (must be one of `i18n.locales`).
34-
2. **`i18n.defaultLocale` config** — The server-wide default set in `kibana.yml`.
33+
1. **User profile setting** — The language selected by the user in their
34+
profile or the user menu (must be one of `i18n.locales`).
35+
2. **`KBN_LOCALE` cookie** — The most recently rendered locale on this
36+
browser. {{kib}} writes this cookie on every rendered response, so it
37+
tracks profile changes automatically. The cookie is the fallback used
38+
on surfaces where the profile isn't available — login pages, error
39+
pages, and any browsing the user does after signing out. Only used
40+
when the cookie value matches a locale {{kib}} can serve.
41+
3. **`Accept-Language` header** {applies_to}`serverless: ga` — On
42+
serverless deployments, {{kib}} consults the browser's
43+
`Accept-Language` preferences when neither the profile setting nor
44+
the cookie produces a match. The first weighted preference that's an
45+
exact match (region included) for an entry in `i18n.locales` wins.
46+
This step is skipped on traditional/self-managed deployments to keep
47+
existing users' language stable across upgrades.
48+
4. **`i18n.defaultLocale` config** — The server-wide default set in `kibana.yml`.
49+
50+
#### About the `KBN_LOCALE` cookie
51+
52+
{{kib}} sets a `KBN_LOCALE` cookie on every rendered response containing
53+
the resolved locale id (for example, `KBN_LOCALE=ja-JP`). Attributes:
54+
55+
- Path scoped to the {{kib}} `serverBasePath`.
56+
- `SameSite=Lax`, `Max-Age` of one year, and `Secure` when the response is over HTTPS.
57+
- `HttpOnly`. The value is a preference, not a secret, but {{kib}} does not need browser-side JavaScript to read it.
58+
59+
Privacy posture: `KBN_LOCALE` is a strictly-necessary preference cookie.
60+
It does not track the user, store identity, or enable cross-site activity.
61+
62+
To disable the cookie entirely, set `i18n.allowLocaleCookie: false` in
63+
`kibana.yml`. When disabled, the per-user language selection still works via
64+
user profiles; however, anonymous pages and pages visited after signing out
65+
will always fall back to `i18n.defaultLocale` (or `Accept-Language` on
66+
serverless deployments) rather than remembering the previously resolved locale.
3567

3668
## Example configurations
3769

@@ -51,4 +83,7 @@ i18n.defaultLocale: "en"
5183

5284
# 4. Legacy form — still works, logs a deprecation warning at startup:
5385
i18n.locale: "ja-JP"
54-
```
86+
87+
# 5. Disable the KBN_LOCALE cookie:
88+
i18n.allowLocaleCookie: false
89+
```

docs/reference/configuration-reference/internationalization-settings.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
# This file is used to generate "i18n settings" page in the product docs
3-
# For the schema and an example refer to the automated settings reference:
3+
# For the schema and an example refer to the automated settings reference:
44
# https://github.com/elastic/docs-builder/blob/main/docs/syntax/automated_settings.md
55

66
product: Kibana
@@ -39,6 +39,19 @@ groups:
3939
ech: ga
4040
self: ga
4141

42+
- setting: i18n.allowLocaleCookie
43+
description: |
44+
When `true` (the default), {{kib}} writes a `KBN_LOCALE` cookie on every
45+
rendered response so the browser remembers the resolved locale across
46+
page loads, anonymous pages, and post-logout browsing. Set to `false`
47+
to disable the cookie.
48+
datatype: boolean
49+
default: "true"
50+
applies_to:
51+
stack: ga 9.5+
52+
ech: ga
53+
self: ga
54+
4255
- setting: i18n.locale
4356
description: |
4457
Set the {{kib}} interface language.
@@ -59,4 +72,4 @@ groups:
5972
applies_to:
6073
stack: deprecated 9.5+
6174
ech: ga
62-
self: ga
75+
self: ga

packages/kbn-mock-idp-plugin/public/role_switcher.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,16 @@ export const useAuthenticator = (reloadPage = false) => {
3333
body: JSON.stringify(params),
3434
});
3535

36-
if (reloadPage) {
37-
const form = createForm(
38-
services.http.basePath.prepend('/api/security/saml/callback'),
39-
response
40-
);
36+
const { acsUrl, ...samlFields } = response;
37+
const formAction = acsUrl ?? services.http.basePath.prepend('/api/security/saml/callback');
38+
39+
if (reloadPage || acsUrl) {
40+
const form = createForm(formAction, samlFields);
4141
form.submit();
4242
await new Promise(() => {});
4343
} else {
4444
await services.http.post('/api/security/saml/callback', {
45-
body: JSON.stringify(response),
45+
body: JSON.stringify(samlFields),
4646
asResponse: true,
4747
rawResponse: true,
4848
});

packages/kbn-mock-idp-plugin/server/plugin.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,13 @@ export const plugin: PluginInitializer<void, void, PluginSetupDependencies> = as
201201
const parsed = new URL(request.body.url, 'https://localhost');
202202
const relayState = parsed.searchParams.get('RelayState') ?? undefined;
203203

204+
// Kibana-bound ACS URLs are intentionally left to the `onPreResponse` rewrite above;
205+
// we only override here for external SPs (e.g. UIAM).
206+
const externalAcsUrl =
207+
samlRequestInfo?.acsUrl && !samlRequestInfo.acsUrl.startsWith(MOCK_IDP_SP_BASE_URL)
208+
? samlRequestInfo.acsUrl
209+
: undefined;
210+
204211
return response.ok({
205212
body: {
206213
SAMLResponse: await createSAMLResponse({
@@ -212,9 +219,13 @@ export const plugin: PluginInitializer<void, void, PluginSetupDependencies> = as
212219
? { authnRequestId: samlRequestInfo.requestId }
213220
: {}),
214221
...(samlRequestInfo?.issuer ? { spEntityId: samlRequestInfo.issuer } : {}),
222+
...(externalAcsUrl ? { acsUrl: externalAcsUrl } : {}),
215223
...serverlessOptions,
216224
}),
217225
...(relayState ? { RelayState: relayState } : {}),
226+
// Echoed alongside SAMLResponse so the browser's auto-submitted form posts to UIAM
227+
// instead of the default Kibana ACS endpoint (see mock_idp_page form action).
228+
...(externalAcsUrl ? { acsUrl: externalAcsUrl } : {}),
218229
},
219230
});
220231
} catch (err) {

packages/kbn-optimizer/limits.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ pageLoadAssetSize:
169169
securitySolutionEss: 38689
170170
securitySolutionServerless: 52082
171171
serverless: 7412
172-
serverlessObservability: 19300
172+
serverlessObservability: 21437
173173
serverlessSearch: 26287
174174
serverlessVectordb: 7618
175175
serverlessWorkplaceAI: 4855

src/cli/serve/serve.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ function tryConfigureServerlessSamlProvider(rawConfig, opts, extraCliOptions) {
462462
// Ensure the plugin is loaded in dynamically to exclude from production build
463463
const {
464464
MOCK_IDP_REALM_NAME,
465+
MOCK_IDP_UIAM_OAUTH_BASE_URL,
465466
MOCK_IDP_UIAM_SERVICE_URL,
466467
MOCK_IDP_UIAM_SHARED_SECRET,
467468
MOCK_IDP_UIAM_ORGANIZATION_ID,
@@ -515,6 +516,14 @@ function tryConfigureServerlessSamlProvider(rawConfig, opts, extraCliOptions) {
515516
lodashSet(rawConfig, 'xpack.security.uiam.ssl.verificationMode', 'none');
516517
lodashSet(rawConfig, 'mockIdpPlugin.uiam.enabled', true);
517518

519+
// SAML POST binding submits the response cross-origin to UIAM's ACS endpoint, so the
520+
// enforced `form-action` directive (default `'self'`) needs to allow the UIAM origin.
521+
const uiamOAuthOrigin = new url.URL(MOCK_IDP_UIAM_OAUTH_BASE_URL).origin;
522+
const existingFormAction = _.get(rawConfig, 'csp.form_action', []);
523+
if (!existingFormAction.includes(uiamOAuthOrigin)) {
524+
lodashSet(rawConfig, 'csp.form_action', [...existingFormAction, uiamOAuthOrigin]);
525+
}
526+
518527
if (!_.has(rawConfig, 'xpack.security.uiam.url')) {
519528
lodashSet(rawConfig, 'xpack.security.uiam.url', MOCK_IDP_UIAM_SERVICE_URL);
520529
}

src/cli/serve/serve.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ describe('applyConfigOverrides', () => {
8585

8686
it('alters config to enable SAML Mock IdP and UIAM in serverless dev mode', () => {
8787
expect(applyConfigOverrides({}, { dev: true, serverless: true, uiam: true }, {}, {})).toEqual({
88+
csp: { form_action: ['https://localhost:8444'] },
8889
elasticsearch: {
8990
hosts: ['https://localhost:9200'],
9091
serviceAccountToken: kibanaDevServiceAccount.token,

src/core/packages/http/resources-server-internal/src/http_resources_service.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,42 @@ describe('HttpResources service', () => {
134134
}
135135
);
136136
});
137+
138+
it('merges Set-Cookie from rendering with caller-provided headers', async () => {
139+
register(routeConfig, async (ctx, req, res) => {
140+
return res.renderCoreApp({ headers: { 'x-extra': 'yes' } });
141+
});
142+
const [[, routeHandler]] = router.get.mock.calls;
143+
144+
const responseFactory = createHttpResourcesResponseFactory();
145+
await routeHandler(context, kibanaRequest, responseFactory);
146+
147+
expect(responseFactory.ok).toHaveBeenCalledWith(
148+
expect.objectContaining({
149+
headers: expect.objectContaining({
150+
'set-cookie': expect.arrayContaining([expect.stringContaining('KBN_LOCALE=')]),
151+
'x-extra': 'yes',
152+
}),
153+
})
154+
);
155+
});
156+
157+
it('preserves both KBN_LOCALE and a caller-provided set-cookie', async () => {
158+
register(routeConfig, async (ctx, req, res) => {
159+
return res.renderCoreApp({ headers: { 'set-cookie': 'foo=bar' } });
160+
});
161+
const [[, routeHandler]] = router.get.mock.calls;
162+
163+
const responseFactory = createHttpResourcesResponseFactory();
164+
await routeHandler(context, kibanaRequest, responseFactory);
165+
166+
const { headers } = (responseFactory.ok as jest.Mock).mock.calls[0][0];
167+
const cookies: string[] = Array.isArray(headers['set-cookie'])
168+
? headers['set-cookie']
169+
: [headers['set-cookie']];
170+
expect(cookies.some((c: string) => c.includes('KBN_LOCALE='))).toBe(true);
171+
expect(cookies.some((c: string) => c.includes('foo=bar'))).toBe(true);
172+
});
137173
});
138174

139175
describe('renderAnonymousCoreApp', () => {
@@ -156,6 +192,23 @@ describe('HttpResources service', () => {
156192
}
157193
);
158194
});
195+
196+
it('preserves both KBN_LOCALE and a caller-provided set-cookie', async () => {
197+
register(routeConfig, async (ctx, req, res) => {
198+
return res.renderAnonymousCoreApp({ headers: { 'set-cookie': 'foo=bar' } });
199+
});
200+
const [[, routeHandler]] = router.get.mock.calls;
201+
202+
const responseFactory = createHttpResourcesResponseFactory();
203+
await routeHandler(context, kibanaRequest, responseFactory);
204+
205+
const { headers } = (responseFactory.ok as jest.Mock).mock.calls[0][0];
206+
const cookies: string[] = Array.isArray(headers['set-cookie'])
207+
? headers['set-cookie']
208+
: [headers['set-cookie']];
209+
expect(cookies.some((c: string) => c.includes('KBN_LOCALE='))).toBe(true);
210+
expect(cookies.some((c: string) => c.includes('foo=bar'))).toBe(true);
211+
});
159212
});
160213

161214
describe('renderHtml', () => {

src/core/packages/http/resources-server-internal/src/http_resources_service.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
RouteConfig,
1515
KibanaRequest,
1616
KibanaResponseFactory,
17+
ResponseHeaders,
1718
} from '@kbn/core-http-server';
1819
import type {
1920
InternalHttpServiceSetup,
@@ -34,6 +35,26 @@ import type {
3435

3536
import type { InternalHttpResourcesSetup } from './types';
3637

38+
const toArray = (val: string | string[] | undefined): string[] => {
39+
if (val === undefined) return [];
40+
return Array.isArray(val) ? val : [val];
41+
};
42+
43+
const mergeRenderHeaders = (
44+
renderHeaders: ResponseHeaders,
45+
optionsHeaders: ResponseHeaders = {}
46+
): ResponseHeaders => {
47+
const setCookie = [
48+
...toArray(renderHeaders['set-cookie']),
49+
...toArray(optionsHeaders['set-cookie']),
50+
];
51+
return {
52+
...renderHeaders,
53+
...optionsHeaders,
54+
...(setCookie.length > 0 ? { 'set-cookie': setCookie } : {}),
55+
};
56+
};
57+
3758
/**
3859
* @internal
3960
*/
@@ -114,26 +135,26 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
114135
return {
115136
async renderCoreApp(options: HttpResourcesRenderOptions = {}) {
116137
const { uiSettings } = await context.core;
117-
const body = await deps.rendering.render(request, uiSettings, {
138+
const { body, headers: renderHeaders } = await deps.rendering.render(request, uiSettings, {
118139
isAnonymousPage: false,
119140
includeExposedConfigKeys: options.includeExposedConfigKeys,
120141
});
121142

122143
return response.ok({
123144
body,
124-
headers: options.headers,
145+
headers: mergeRenderHeaders(renderHeaders, options.headers),
125146
});
126147
},
127148
async renderAnonymousCoreApp(options: HttpResourcesRenderOptions = {}) {
128149
const { uiSettings } = await context.core;
129-
const body = await deps.rendering.render(request, uiSettings, {
150+
const { body, headers: renderHeaders } = await deps.rendering.render(request, uiSettings, {
130151
isAnonymousPage: true,
131152
includeExposedConfigKeys: options.includeExposedConfigKeys,
132153
});
133154

134155
return response.ok({
135156
body,
136-
headers: options.headers,
157+
headers: mergeRenderHeaders(renderHeaders, options.headers),
137158
});
138159
},
139160
renderHtml(options: HttpResourcesResponseOptions) {

src/core/packages/i18n/server-internal/moon.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ dependsOn:
3232
- '@kbn/std'
3333
- '@kbn/repo-packages'
3434
- '@kbn/core-http-router-server-mocks'
35+
- '@kbn/core-http-router-server-internal'
3536
tags:
3637
- shared-server
3738
- package

0 commit comments

Comments
 (0)