Skip to content

Commit bb97506

Browse files
kc13greinerazasypkinkibanamachine
authored
Kibana convert api (#254299)
## Summary Exposing UIAM API Key `convert` API to transform ES native API Keys into UIAM API Keys <img width="1754" height="671" alt="Screenshot 2026-02-26 at 11 26 45 PM" src="https://github.com/user-attachments/assets/39d660b0-26ec-4595-b4b7-ad735a53b1b5" /> ## Testing Run Kibana & Elasticsearch in UIAM mode: ```shell ## Run Elasticsearch in UIAM mode $ yarn es serverless --projectType observability --uiam ## Run Kibana in UIAM mode $ yarn start --serverless=oblt --uiam ``` Create a native ES API Key (any will do!), copy the key value Navigate to DevTools and call the `convert` endpoint that has been exposed through MockIdP: ``` POST kbn:/mock_idp/uiam/convert_api_keys { "keys": [ "RmhXb2Nwd0JYNFNUSmFER291MGc6YzZmNHEzMVBoai0wNXZSMkhaNUh6UQ==" ] } ``` Result: ``` { "results": [ { "status": "success", "id": "0b6Q5cQ3QRCz7afNV5UsIA", "key": "essu_dev_djE6MGI2UTVjUTNRUkN6N2FmTlY1VXNJQToyN256a21YZU05bjhWWklqWEZ4ZTF5OStpNzNBa3BUTGNlMW8yQ2VLclhJPQAAAAAJQBl0", "organization_id": "org1234567890", "description": "kurt test key", "internal": true, "role_assignments": { "project": { "observability": [ { "role_id": "observability-application-only", "organization_id": "org1234567890", "all": true, "application_roles": [ "admin" ] } ] } }, "creation_date": "2026-02-18T21:33:59.201099712Z" } ] } ``` --------- Co-authored-by: Aleh Zasypkin <aleh.zasypkin@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com>
1 parent 18aae8e commit bb97506

File tree

28 files changed

+705
-6
lines changed

28 files changed

+705
-6
lines changed

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,47 @@ export const plugin: PluginInitializer<void, void, PluginSetupDependencies> = as
357357
}
358358
}
359359
);
360+
361+
router.post(
362+
{
363+
path: '/mock_idp/uiam/convert_api_keys',
364+
validate: {
365+
body: schema.object({
366+
keys: schema.arrayOf(schema.string(), { minSize: 1 }),
367+
}),
368+
},
369+
options: { authRequired: 'optional' },
370+
security: { authz: { enabled: false, reason: 'Mock IDP plugin for testing' } },
371+
},
372+
async (context, request, response) => {
373+
try {
374+
const { keys } = request.body;
375+
const [
376+
{
377+
security: { authc },
378+
},
379+
] = await core.getStartServices();
380+
381+
const result = await authc.apiKeys.uiam?.convert(keys);
382+
383+
if (!result) {
384+
return response.badRequest({
385+
body: { message: 'Failed to convert API keys' },
386+
});
387+
}
388+
389+
return response.ok({
390+
body: result,
391+
});
392+
} catch (err) {
393+
logger.error(`Failed to convert API keys via UIAM: ${err}`, err);
394+
return response.customError({
395+
statusCode: 500,
396+
body: { message: err.message },
397+
});
398+
}
399+
}
400+
);
360401
},
361402
start() {},
362403
stop() {},

src/cli/serve/serve.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,19 @@ function tryConfigureServerlessSamlProvider(rawConfig, opts, extraCliOptions) {
474474
if (!_.has(rawConfig, 'xpack.cloud.projects_url')) {
475475
lodashSet(rawConfig, 'xpack.cloud.projects_url', '');
476476
}
477+
478+
// The UIAM service needs a network-accessible ES URL to validate API keys during conversion.
479+
// The security plugin decodes cloud.id to obtain this URL. In the local Docker setup,
480+
// the UIAM container reaches ES via host.docker.internal on the host network.
481+
if (!_.has(rawConfig, 'xpack.cloud.id')) {
482+
lodashSet(
483+
rawConfig,
484+
'xpack.cloud.id',
485+
// Decodes to: docker.internal:9200$host:9200$kibana:9200
486+
// Producing ES URL: https://host.docker.internal:9200
487+
'local-dev:ZG9ja2VyLmludGVybmFsOjkyMDAkaG9zdDo5MjAwJGtpYmFuYTo5MjAw'
488+
);
489+
}
477490
}
478491

479492
return true;

src/cli/serve/serve.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ describe('applyConfigOverrides', () => {
124124
plugins: { paths: [] },
125125
xpack: {
126126
cloud: {
127+
id: 'local-dev:ZG9ja2VyLmludGVybmFsOjkyMDAkaG9zdDo5MjAwJGtpYmFuYTo5MjAw',
127128
organization_id: 'org1234567890',
128129
projects_url: '',
129130
serverless: { project_id: 'abcdef12345678901234567890123456' },

src/core/packages/security/server-internal/src/security_route_handler_context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export class CoreSecurityRouteHandlerContext implements SecurityRequestHandlerCo
4343
grant: (grantUiamApiKeyParams) => uiam.grant(this.request, grantUiamApiKeyParams),
4444
invalidate: (invalidateUiamApiKeyParams) =>
4545
uiam.invalidate(this.request, invalidateUiamApiKeyParams),
46+
convert: (convertUiamApiKeyParams) => uiam.convert(convertUiamApiKeyParams),
4647
}
4748
: null,
4849
},

src/core/packages/security/server-internal/src/utils/convert_security_api.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe('convertSecurityApi', () => {
2929
uiam: {
3030
grant: jest.fn(),
3131
invalidate: jest.fn(),
32+
convert: jest.fn(),
3233
},
3334
},
3435
},

src/core/packages/security/server-mocks/src/api_keys.mock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const apiKeysMock = {
2424
uiam: {
2525
grant: jest.fn(),
2626
invalidate: jest.fn(),
27+
convert: jest.fn(),
2728
},
2829
}),
2930
};

src/core/packages/security/server-mocks/src/security_service.mock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ const createRequestHandlerContextMock = () => {
100100
uiam: {
101101
grant: jest.fn(),
102102
invalidate: jest.fn(),
103+
convert: jest.fn(),
103104
},
104105
}),
105106
}),

src/core/packages/security/server/src/authentication/api_keys/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export type {
3636
UiamAPIKeysWithContextType,
3737
GrantUiamAPIKeyParams,
3838
InvalidateUiamAPIKeyParams,
39+
ConvertUiamAPIKeyResult,
40+
ConvertUiamAPIKeyResultSuccess,
41+
ConvertUiamAPIKeyResultFailed,
42+
ConvertUiamAPIKeysResponse,
3943
} from './uiam';
4044

4145
export interface APIKeysType extends NativeAPIKeysType {

src/core/packages/security/server/src/authentication/api_keys/uiam/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@ export type {
1111
UiamAPIKeysType,
1212
GrantUiamAPIKeyParams,
1313
InvalidateUiamAPIKeyParams,
14+
ConvertUiamAPIKeyResult,
15+
ConvertUiamAPIKeyResultSuccess,
16+
ConvertUiamAPIKeyResultFailed,
17+
ConvertUiamAPIKeysResponse,
1418
} from './uiam_api_keys';
1519
export type { UiamAPIKeysWithContextType } from './uiam_api_keys_context';

src/core/packages/security/server/src/authentication/api_keys/uiam/uiam_api_keys.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ export interface UiamAPIKeysType {
3838
request: KibanaRequest,
3939
params: InvalidateUiamAPIKeyParams
4040
): Promise<InvalidateAPIKeyResult | null>;
41+
42+
/**
43+
* Converts Elasticsearch API keys into UIAM API keys.
44+
*
45+
* @param keys The base64-encoded Elasticsearch API key values to convert.
46+
* @returns A promise that resolves to a response containing per-key success/failure results, or null if the license is not enabled.
47+
*/
48+
convert(keys: string[]): Promise<ConvertUiamAPIKeysResponse | null>;
4149
}
4250

4351
/**
@@ -64,3 +72,43 @@ export interface InvalidateUiamAPIKeyParams {
6472
*/
6573
id: string;
6674
}
75+
76+
/**
77+
* A successful result from converting an Elasticsearch API key into a UIAM API key.
78+
*/
79+
export interface ConvertUiamAPIKeyResultSuccess {
80+
status: 'success';
81+
id: string;
82+
key: string;
83+
description: string;
84+
organization_id: string;
85+
internal: boolean;
86+
role_assignments: Record<string, unknown>;
87+
creation_date: string;
88+
expiration_date: string | null;
89+
}
90+
91+
/**
92+
* A failed result from converting an Elasticsearch API key into a UIAM API key.
93+
*/
94+
export interface ConvertUiamAPIKeyResultFailed {
95+
status: 'failed';
96+
code: string;
97+
message: string;
98+
resource: string | null;
99+
type: string;
100+
}
101+
102+
/**
103+
* A single result entry from the convert API keys operation; either success or failure.
104+
*/
105+
export type ConvertUiamAPIKeyResult =
106+
| ConvertUiamAPIKeyResultSuccess
107+
| ConvertUiamAPIKeyResultFailed;
108+
109+
/**
110+
* Response from the UIAM convert API keys operation.
111+
*/
112+
export interface ConvertUiamAPIKeysResponse {
113+
results: ConvertUiamAPIKeyResult[];
114+
}

0 commit comments

Comments
 (0)