Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7d49821
[Task Manager] Use strategy pattern for API key management
ersin-erdal Mar 24, 2026
e09be06
Changes from node scripts/lint_ts_projects --fix
kibanamachine Mar 24, 2026
3edf02b
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Mar 24, 2026
440fed0
fix tests
ersin-erdal Mar 24, 2026
15647e3
fix snapshots
ersin-erdal Mar 25, 2026
2dc8ed5
Merge branch 'main' into api-keys-strategies
ersin-erdal Mar 25, 2026
f8019a0
Merge branch 'main' into api-keys-strategies
ersin-erdal Mar 25, 2026
b89299c
Merge branch 'main' into api-keys-strategies
ersin-erdal Mar 26, 2026
b850acb
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Mar 26, 2026
f533e97
Merge branch 'main' into api-keys-strategies
ersin-erdal Mar 26, 2026
60edaff
Merge branch 'main' into api-keys-strategies
ersin-erdal Mar 27, 2026
6860ba7
remove redundant yarn.lock
ersin-erdal Mar 27, 2026
13c821e
Merge branch 'main' into api-keys-strategies
ersin-erdal Mar 30, 2026
f0f727e
encrypt uiamAPiKey in api_key_to_invalidate
ersin-erdal Apr 1, 2026
1bcf94b
fix tests
ersin-erdal Apr 1, 2026
a896902
remove bulkMarkApiKeysForInvalidation
ersin-erdal Apr 2, 2026
977b86c
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 2, 2026
90fd09f
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 14, 2026
784d41b
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 14, 2026
27d5adf
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 14, 2026
15a50b7
skip UIAM API key grant when request credential is not compatible
ersin-erdal Apr 14, 2026
0ed22f2
add schedule/delete routes and Scout UIAM API key tests for task manager
ersin-erdal Apr 14, 2026
3a9614e
tighten input validation on schedule route schema
ersin-erdal Apr 14, 2026
e49c309
add Scout test files to tsconfig and reference @kbn/scout
ersin-erdal Apr 14, 2026
3eb2a70
address comments
ersin-erdal Apr 14, 2026
636ff63
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Apr 14, 2026
f30a8c4
remove noop task type, use existing task type with enabled: false
ersin-erdal Apr 15, 2026
46d94d3
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 15, 2026
98480d0
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 15, 2026
f8ec66c
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 15, 2026
7180316
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 15, 2026
156635c
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 16, 2026
969f3f8
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 16, 2026
d230160
Move Task Manager schedule/delete FTR routes to ftr_apis plugin
ersin-erdal Apr 16, 2026
a49ab29
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 16, 2026
316f3dd
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 16, 2026
133d04a
use ['ftrApis'] privileges
ersin-erdal Apr 16, 2026
5dfc7a6
Add xpack.task_manager.grant_uiam_api_keys rollout flag
ersin-erdal Apr 17, 2026
81eec75
Fix Scout task_manager UIAM tests and address review feedback
ersin-erdal Apr 17, 2026
4dec24d
Move task_api_keys Scout spec behind feature-flag config set
ersin-erdal Apr 17, 2026
f5cb90c
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Apr 17, 2026
ca13796
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 20, 2026
d51be60
Merge branch 'main' into api-keys-strategies
ersin-erdal Apr 20, 2026
d5d6e29
Run Task Manager Scout API specs sequentially
ersin-erdal Apr 20, 2026
468bf0c
add comments and some guardrails
ersin-erdal Apr 20, 2026
7f39929
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/kbn-check-saved-objects-cli/current_fields.json
Original file line number Diff line number Diff line change
Expand Up @@ -1420,7 +1420,8 @@
"status",
"taskType",
"userScope",
"userScope.apiKeyId"
"userScope.apiKeyId",
"userScope.uiamApiKeyId"
],
"telemetry": [],
"threshold-explorer-view": [],
Expand Down
3 changes: 3 additions & 0 deletions packages/kbn-check-saved-objects-cli/current_mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4734,6 +4734,9 @@
"properties": {
"apiKeyId": {
"type": "keyword"
},
"uiamApiKeyId": {
"type": "keyword"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"10.8.0": [
{
"taskType": "string",
"scheduledAt": "string",
"startedAt": "string?|null",
"retryAt": "string?|null",
"runAt": "string",
"params": "string",
"state": "string",
"stateVersion": "number?",
"traceparent": "string",
"user": "string?",
"scope": "array?",
"ownerId": "string?|null",
"enabled": "boolean?",
"timeoutOverride": "string?",
"attempts": "number",
"status": "idle|claiming|running|failed|unrecognized|dead_letter",
"version": "string?",
"partition": "number?",
"priority": "number?",
"interval": "string",
"apiKey": "string?",
"userScope": "object?",
"schedule": "object?",
"cost": "tiny?|normal?|extralarge?"
}
],
"10.9.0": [
{
"taskType": "string",
"scheduledAt": "string",
"startedAt": "string?|null",
"retryAt": "string?|null",
"runAt": "string",
"params": "string",
"state": "string",
"stateVersion": "number?",
"traceparent": "string",
"user": "string?",
"scope": "array?",
"ownerId": "string?|null",
"enabled": "boolean?",
"timeoutOverride": "string?",
"attempts": "number",
"status": "idle|claiming|running|failed|unrecognized|dead_letter",
"version": "string?",
"partition": "number?",
"priority": "number?",
"interval": "string",
"apiKey": "string?",
"uiamApiKey": "string?",
"userScope": "object?",
"schedule": "object?",
"cost": "tiny?|normal?|extralarge?"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"synthetics-private-location": "f5efabeefafbb12ed0809db3cd04f893ff9099ead8f526be82a9b0348e444f65",
"synthetics-privates-locations": "42aebb3aa4f3710a3e270d54bf33718a4d1d7a983556a51f75bd96b1e4fdf048",
"tag": "03a522e92aed789a4bbf1a5dd19159c3ec061cb052337df9270728def4b3bbe0",
"task": "52bb9355724d6546a8e485c161c7f039493acb79e913b31ce1d0b9839fe38117",
"task": "4e6f0a8e825e3159959f1dc0fd4d6c5d9b15707863a741905b183dc6d1144f44",
"telemetry": "fb5e3ce0b2955f10aa8cd75fdafdd0559bf5d77eaf6e2c228079684f01f28fbd",
"threshold-explorer-view": "9b0a770f5444531f92dd50832dcf655cb0c9cd7f18af205338e0c9d73c6df6a6",
"trial-companion-nba-milestone": "83f29f99e2ffaf00ed8e05f3366ed0df1fb36a77193aeb151e13bae8b1d9692f",
Expand Down Expand Up @@ -1274,8 +1274,9 @@ describe('checking migration metadata changes on all registered SO types', () =>
"tag|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709",
"=====================================================",
"task|global: 8277e4031824bb161fa73897294701786c15eb9a",
"task|mappings: 02ff4224787d1516899101bacf1c411fa0149383",
"task|mappings: a4616c952ab46648bba6f807a339d76b1388078e",
"task|schemas: da39a3ee5e6b4b0d3255bfef95601890afd80709",
"task|10.9.0: 2c8f74c9a9d07540f767ad8e99174474da9bec93ab858ae1e005a301f620c39b",
"task|10.8.0: deed2eb105aa3f19fa1827868c6b5569523624614fb73a8fcb8600d86c0dface",
"task|10.7.0: 6afacb50669e4a3ebd48d5790d1677c138885b1540acf5e832dbe8dc82e7cd5c",
"task|10.6.0: a554a701424daf84a260b61390464deb9296c7372ac3438301c2fb046ded11f9",
Expand Down Expand Up @@ -1537,7 +1538,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"synthetics-private-location": "10.0.0",
"synthetics-privates-locations": "10.1.0",
"tag": "10.0.0",
"task": "10.8.0",
"task": "10.9.0",
"telemetry": "10.0.0",
"threshold-explorer-view": "10.0.0",
"trial-companion-nba-milestone": "10.1.0",
Expand Down Expand Up @@ -1702,7 +1703,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"synthetics-private-location": "0.0.0",
"synthetics-privates-locations": "10.1.0",
"tag": "0.0.0",
"task": "10.8.0",
"task": "10.9.0",
"telemetry": "0.0.0",
"threshold-explorer-view": "0.0.0",
"trial-companion-nba-milestone": "10.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class TaskManagerDependenciesPlugin {
public setup(_: CoreSetup, plugin: TaskManagerDependenciesPluginSetup) {
plugin.encryptedSavedObjects.registerType({
type: 'task',
attributesToEncrypt: new Set(['apiKey']),
attributesToEncrypt: new Set(['apiKey', 'uiamApiKey']),
attributesToIncludeInAAD: new Set(['id', 'taskType']),
enforceRandomId: false,
});
Expand All @@ -47,7 +47,7 @@ export class TaskManagerDependenciesPlugin {
);
}

public start(_: CoreStart, plugin: TaskManagerDependenciesPluginStart) {
public start(core: CoreStart, plugin: TaskManagerDependenciesPluginStart) {
plugin.taskManager.registerEncryptedSavedObjectsClient(
plugin.encryptedSavedObjects.getClient({
includedHiddenTypes: ['task'],
Expand All @@ -56,5 +56,6 @@ export class TaskManagerDependenciesPlugin {
plugin.taskManager.registerApiKeyInvalidateFn(
plugin.security?.authc.apiKeys.invalidateAsInternalUser
);
plugin.taskManager.registerUiamApiKeyInvalidateFn(core.security.authc.apiKeys.uiam?.invalidate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('checking changes on all registered encrypted SO types', () => {
"synthetics-monitor": "f1c060b7be3b30187c4adcb35d74f1fa8a4290bd7faf04fec869de2aa387e21b",
"synthetics-monitor-multi-space": "39c4c6abd28c4173f77c1c89306e92b6b92492c0029274e10620a170be4d4a67",
"synthetics-param": "747ba9d1b7addf5b131713abe7868bd767af6ce0cf8b6b0f335f4ef34b280c7e",
"task": "2d8e9bf532f469805b82051f545b915785d99eabfa050cb1aefbc715c6096b97",
"task": "d6cc30871dc78caf3f451de1275a3803879ec9935b4a2e34076dee56878c228f",
"uptime-synthetics-api-key": "5ca81f180763e85397fa8c6508adcd60efd0f916e29bac6dcd5b4564f1db7375",
"user_connector_token": "b443b022b46b79c0ff9fa674aecc64176a5fcbd09c2db2d9f050a6a88435732e",
}
Expand Down Expand Up @@ -151,6 +151,7 @@ describe('checking changes on all registered encrypted SO types', () => {
"oauth_state|1",
"synthetics-monitor|2",
"synthetics-monitor|1",
"task|9",
"task|8",
"task|7",
"task|6",
Expand Down
1 change: 1 addition & 0 deletions x-pack/platform/plugins/shared/task_manager/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ dependsOn:
- '@kbn/core-execution-context-server-mocks'
- '@kbn/core-execution-context-common'
- '@kbn/core-http-server'
- '@kbn/core-security-server'
tags:
- plugin
- prod
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type {
Logger,
SecurityServiceStart,
IBasePath,
KibanaRequest,
SavedObjectsClientContract,
} from '@kbn/core/server';
import type { ApiKeyType } from '../config';
import type { ConcreteTaskInstance, TaskInstance, TaskUserScope } from '../task';
import { INVALIDATE_API_KEY_SO_NAME } from '../saved_objects';

export type { ApiKeyType } from '../config';

export interface ApiKeySOFields {
apiKey: string;
uiamApiKey?: string;
userScope: TaskUserScope;
}

export interface InvalidationTarget {
apiKeyId: string;
uiamApiKey?: string;
}

export interface ApiKeyStrategy {
readonly shouldGrantUiam: boolean;
readonly typeToUse: ApiKeyType;

grantApiKeys(
taskInstances: TaskInstance[],
request: KibanaRequest,
security: SecurityServiceStart,
basePath: IBasePath
): Promise<Map<string, ApiKeySOFields>>;

getApiKeyForFakeRequest(taskInstance: ConcreteTaskInstance): string | undefined;

getApiKeyIdsForInvalidation(taskInstance: ConcreteTaskInstance): InvalidationTarget[];

markForInvalidation(
targets: InvalidationTarget[],
logger: Logger,
savedObjectsClient: SavedObjectsClientContract
): Promise<void>;
}

export const markApiKeysForInvalidation = async (
targets: InvalidationTarget[],
logger: Logger,
savedObjectsClient: SavedObjectsClientContract
): Promise<void> => {
if (targets.length === 0) {
return;
}

try {
await savedObjectsClient.bulkCreate(
targets.map((target) => ({
attributes: {
apiKeyId: target.apiKeyId,
createdAt: new Date().toISOString(),
...(target.uiamApiKey ? { uiamApiKey: target.uiamApiKey } : {}),

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.

is it okay to write an actual api key to a regular SO here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, no it is not, converted it to an encrypted field.

},
type: INVALIDATE_API_KEY_SO_NAME,
}))
);
} catch (e) {
logger.error(`Failed to bulk mark ${targets.length} API keys for invalidation: ${e.message}`);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { coreMock } from '@kbn/core/server/mocks';
import { ApiKeyType } from '../config';
import { createApiKeyStrategy } from './create_api_key_strategy';
import { EsApiKeyStrategy } from './es_api_key_strategy';
import { EsAndUiamApiKeyStrategy } from './es_and_uiam_api_key_strategy';

describe('createApiKeyStrategy', () => {
test('returns EsApiKeyStrategy when UIAM is not available', () => {
const coreStart = coreMock.createStart();
coreStart.security.authc.apiKeys.uiam = null as never;

const strategy = createApiKeyStrategy(ApiKeyType.ES, coreStart.security);
expect(strategy).toBeInstanceOf(EsApiKeyStrategy);
});

test('returns EsAndUiamApiKeyStrategy when UIAM is available', () => {
const coreStart = coreMock.createStart();
coreStart.security.authc.apiKeys.uiam = {
grant: jest.fn(),
invalidate: jest.fn(),
convert: jest.fn(),
} as never;

const strategy = createApiKeyStrategy(ApiKeyType.ES, coreStart.security);
expect(strategy).toBeInstanceOf(EsAndUiamApiKeyStrategy);
});

test('passes apiKeyType to EsAndUiamApiKeyStrategy', () => {
const coreStart = coreMock.createStart();
coreStart.security.authc.apiKeys.uiam = {
grant: jest.fn(),
invalidate: jest.fn(),
convert: jest.fn(),
} as never;

const strategy = createApiKeyStrategy(ApiKeyType.UIAM, coreStart.security);
expect(strategy.typeToUse).toBe(ApiKeyType.UIAM);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { SecurityServiceStart } from '@kbn/core/server';
import type { ApiKeyType } from '../config';
import type { ApiKeyStrategy } from './api_key_strategy';
import { EsApiKeyStrategy } from './es_api_key_strategy';
import { EsAndUiamApiKeyStrategy } from './es_and_uiam_api_key_strategy';

export const createApiKeyStrategy = (
apiKeyType: ApiKeyType,
security: SecurityServiceStart
): ApiKeyStrategy => {
if (security.authc.apiKeys.uiam) {
return new EsAndUiamApiKeyStrategy(apiKeyType, security);
}
return new EsApiKeyStrategy();
};
Loading
Loading