diff --git a/packages/core/security/core-security-server-internal/src/security_route_handler_context.ts b/packages/core/security/core-security-server-internal/src/security_route_handler_context.ts index 4fa328782dd0e..bae1c11d152a4 100644 --- a/packages/core/security/core-security-server-internal/src/security_route_handler_context.ts +++ b/packages/core/security/core-security-server-internal/src/security_route_handler_context.ts @@ -26,6 +26,16 @@ export class CoreSecurityRouteHandlerContext implements SecurityRequestHandlerCo if (this.#authc == null) { this.#authc = { getCurrentUser: () => this.securityStart.authc.getCurrentUser(this.request), + apiKeys: { + areAPIKeysEnabled: () => this.securityStart.authc.apiKeys.areAPIKeysEnabled(), + create: (createParams) => + this.securityStart.authc.apiKeys.create(this.request, createParams), + update: (updateParams) => + this.securityStart.authc.apiKeys.update(this.request, updateParams), + validate: (apiKeyParams) => this.securityStart.authc.apiKeys.validate(apiKeyParams), + invalidate: (apiKeyParams) => + this.securityStart.authc.apiKeys.invalidate(this.request, apiKeyParams), + }, }; } return this.#authc; diff --git a/packages/core/security/core-security-server-internal/src/utils/convert_security_api.test.ts b/packages/core/security/core-security-server-internal/src/utils/convert_security_api.test.ts index 7c2e49092f73e..40d9e788ea01b 100644 --- a/packages/core/security/core-security-server-internal/src/utils/convert_security_api.test.ts +++ b/packages/core/security/core-security-server-internal/src/utils/convert_security_api.test.ts @@ -15,6 +15,16 @@ describe('convertSecurityApi', () => { const source: CoreSecurityDelegateContract = { authc: { getCurrentUser: jest.fn(), + apiKeys: { + areAPIKeysEnabled: jest.fn(), + areCrossClusterAPIKeysEnabled: jest.fn(), + validate: jest.fn(), + invalidate: jest.fn(), + invalidateAsInternalUser: jest.fn(), + grantAsInternalUser: jest.fn(), + create: jest.fn(), + update: jest.fn(), + }, }, audit: { asScoped: jest.fn().mockReturnValue(createAuditLoggerMock.create()), @@ -23,6 +33,7 @@ describe('convertSecurityApi', () => { }; const output = convertSecurityApi(source); expect(output.authc.getCurrentUser).toBe(source.authc.getCurrentUser); + expect(output.authc.apiKeys).toBe(source.authc.apiKeys); expect(output.audit.asScoped).toBe(source.audit.asScoped); expect(output.audit.withoutRequest).toBe(source.audit.withoutRequest); }); diff --git a/packages/core/security/core-security-server-internal/src/utils/default_implementation.test.ts b/packages/core/security/core-security-server-internal/src/utils/default_implementation.test.ts index e4348404671b9..bc7fac96b7dd3 100644 --- a/packages/core/security/core-security-server-internal/src/utils/default_implementation.test.ts +++ b/packages/core/security/core-security-server-internal/src/utils/default_implementation.test.ts @@ -23,6 +23,15 @@ describe('getDefaultSecurityImplementation', () => { }); }); + describe('authc.apiKeys', () => { + it('returns stub object', async () => { + const { apiKeys } = implementation.authc; + const areAPIKeysEnabled = await apiKeys.areAPIKeysEnabled(); + + expect(areAPIKeysEnabled).toBe(false); + }); + }); + describe('audit.asScoped', () => { it('returns null', async () => { const logger = implementation.audit.asScoped({} as any); diff --git a/packages/core/security/core-security-server-internal/src/utils/default_implementation.ts b/packages/core/security/core-security-server-internal/src/utils/default_implementation.ts index 91819807f1064..8eaeb7b2577b5 100644 --- a/packages/core/security/core-security-server-internal/src/utils/default_implementation.ts +++ b/packages/core/security/core-security-server-internal/src/utils/default_implementation.ts @@ -8,10 +8,23 @@ import type { CoreSecurityDelegateContract } from '@kbn/core-security-server'; +const API_KEYS_DISABLED_ERROR = new Error('API keys are disabled'); +const REJECT_WHEN_API_KEYS_DISABLED = () => Promise.reject(API_KEYS_DISABLED_ERROR); + export const getDefaultSecurityImplementation = (): CoreSecurityDelegateContract => { return { authc: { getCurrentUser: () => null, + apiKeys: { + areAPIKeysEnabled: () => Promise.resolve(false), + areCrossClusterAPIKeysEnabled: () => Promise.resolve(false), + create: REJECT_WHEN_API_KEYS_DISABLED, + update: REJECT_WHEN_API_KEYS_DISABLED, + grantAsInternalUser: REJECT_WHEN_API_KEYS_DISABLED, + validate: REJECT_WHEN_API_KEYS_DISABLED, + invalidate: REJECT_WHEN_API_KEYS_DISABLED, + invalidateAsInternalUser: REJECT_WHEN_API_KEYS_DISABLED, + }, }, audit: { asScoped: () => { diff --git a/packages/core/security/core-security-server-mocks/index.ts b/packages/core/security/core-security-server-mocks/index.ts index 23c49282252f0..c834759973c1e 100644 --- a/packages/core/security/core-security-server-mocks/index.ts +++ b/packages/core/security/core-security-server-mocks/index.ts @@ -9,3 +9,4 @@ export { securityServiceMock } from './src/security_service.mock'; export type { InternalSecurityStartMock, SecurityStartMock } from './src/security_service.mock'; export { auditLoggerMock } from './src/audit.mock'; +export { apiKeysMock } from './src/api_keys.mock'; diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts b/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts similarity index 59% rename from x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts rename to packages/core/security/core-security-server-mocks/src/api_keys.mock.ts index cfa857ca833a2..108f8380264e6 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts +++ b/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts @@ -1,16 +1,15 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import type { PublicMethodsOf } from '@kbn/utility-types'; - -import type { APIKeys } from './api_keys'; +import type { APIKeysService } from '@kbn/core-security-server'; export const apiKeysMock = { - create: (): jest.Mocked> => ({ + create: (): jest.MockedObjectDeep => ({ areAPIKeysEnabled: jest.fn(), areCrossClusterAPIKeysEnabled: jest.fn(), create: jest.fn(), diff --git a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts index d833048990ff5..86a39af3b16d5 100644 --- a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts +++ b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts @@ -15,6 +15,7 @@ import type { InternalSecurityServiceSetup, InternalSecurityServiceStart, } from '@kbn/core-security-server-internal'; +import { apiKeysMock } from './api_keys.mock'; import { auditServiceMock, type MockedAuditService } from './audit.mock'; import { mockAuthenticatedUser, MockAuthenticatedUserProps } from '@kbn/core-security-common/mocks'; @@ -35,6 +36,7 @@ const createStartMock = (): SecurityStartMock => { const mock = { authc: { getCurrentUser: jest.fn(), + apiKeys: apiKeysMock.create(), }, audit: auditServiceMock.create(), }; @@ -61,6 +63,7 @@ const createInternalStartMock = (): InternalSecurityStartMock => { const mock = { authc: { getCurrentUser: jest.fn(), + apiKeys: apiKeysMock.create(), }, audit: auditServiceMock.create(), }; @@ -82,6 +85,13 @@ const createRequestHandlerContextMock = () => { const mock: jest.MockedObjectDeep = { authc: { getCurrentUser: jest.fn(), + apiKeys: { + areAPIKeysEnabled: jest.fn(), + create: jest.fn(), + update: jest.fn(), + validate: jest.fn(), + invalidate: jest.fn(), + }, }, audit: { logger: { diff --git a/packages/core/security/core-security-server/index.ts b/packages/core/security/core-security-server/index.ts index 6a111ab6e27ab..b5dd091c7b87a 100644 --- a/packages/core/security/core-security-server/index.ts +++ b/packages/core/security/core-security-server/index.ts @@ -26,4 +26,26 @@ export type { AuditRequest, } from './src/audit_logging/audit_events'; export type { AuditLogger } from './src/audit_logging/audit_logger'; + +export type { + APIKeysServiceWithContext, + APIKeysService, + CreateAPIKeyParams, + CreateAPIKeyResult, + InvalidateAPIKeyResult, + InvalidateAPIKeysParams, + ValidateAPIKeyParams, + CreateRestAPIKeyParams, + CreateRestAPIKeyWithKibanaPrivilegesParams, + CreateCrossClusterAPIKeyParams, + GrantAPIKeyResult, + UpdateAPIKeyParams, + UpdateAPIKeyResult, + UpdateCrossClusterAPIKeyParams, + UpdateRestAPIKeyParams, + UpdateRestAPIKeyWithKibanaPrivilegesParams, +} from './src/authentication/api_keys'; + +export type { KibanaPrivilegesType, ElasticsearchPrivilegesType } from './src/roles'; +export { isCreateRestAPIKeyParams } from './src/authentication/api_keys'; export type { CoreFipsService } from './src/fips'; diff --git a/packages/core/security/core-security-server/src/authc.ts b/packages/core/security/core-security-server/src/authc.ts index 97654104858ea..85ba4fc71542a 100644 --- a/packages/core/security/core-security-server/src/authc.ts +++ b/packages/core/security/core-security-server/src/authc.ts @@ -8,6 +8,7 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import type { AuthenticatedUser } from '@kbn/core-security-common'; +import type { APIKeysService } from './authentication/api_keys'; /** * Core's authentication service @@ -22,4 +23,5 @@ export interface CoreAuthenticationService { * @param request The request to retrieve the authenticated user for. */ getCurrentUser(request: KibanaRequest): AuthenticatedUser | null; + apiKeys: APIKeysService; } diff --git a/packages/core/security/core-security-server/src/authentication/api_keys/api_keys.ts b/packages/core/security/core-security-server/src/authentication/api_keys/api_keys.ts new file mode 100644 index 0000000000000..e842c38d8674d --- /dev/null +++ b/packages/core/security/core-security-server/src/authentication/api_keys/api_keys.ts @@ -0,0 +1,268 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { estypes } from '@elastic/elasticsearch'; + +import type { KibanaRequest } from '@kbn/core-http-server'; + +import { ElasticsearchPrivilegesType, KibanaPrivilegesType } from '../../roles'; + +/** + * Interface for managing API keys in Elasticsearch, including creation, + * validation, and invalidation of API keys, + * as well as checking the status of API key features. + */ +export interface APIKeys { + /** + * Determines if API Keys are enabled in Elasticsearch. + */ + areAPIKeysEnabled(): Promise; + + /** + * Determines if Cross-Cluster API Keys are enabled in Elasticsearch. + */ + areCrossClusterAPIKeysEnabled(): Promise; + + /** + * Tries to create an API key for the current user. + * + * Returns newly created API key or `null` if API keys are disabled. + * + * User needs `manage_api_key` privilege to create REST API keys and `manage_security` for Cross-Cluster API keys. + * + * @param request Request instance. + * @param createParams The params to create an API key + */ + create( + request: KibanaRequest, + createParams: CreateAPIKeyParams + ): Promise; + + /** + * Attempts update an API key with the provided 'role_descriptors' and 'metadata' + * + * Returns `updated`, `true` if the update was successful, `false` if there was nothing to update + * + * User needs `manage_api_key` privilege to update REST API keys and `manage_security` for cross-cluster API keys. + * + * @param request Request instance. + * @param updateParams The params to edit an API key + */ + update( + request: KibanaRequest, + updateParams: UpdateAPIKeyParams + ): Promise; + + /** + * Tries to grant an API key for the current user. + * @param request Request instance. + * @param createParams Create operation parameters. + */ + grantAsInternalUser( + request: KibanaRequest, + createParams: CreateRestAPIKeyParams | CreateRestAPIKeyWithKibanaPrivilegesParams + ): Promise; + + /** + * Tries to validate an API key. + * @param apiKeyPrams ValidateAPIKeyParams. + */ + validate(apiKeyPrams: ValidateAPIKeyParams): Promise; + + /** + * Tries to invalidate an API keys. + * @param request Request instance. + * @param params The params to invalidate an API keys. + */ + invalidate( + request: KibanaRequest, + params: InvalidateAPIKeysParams + ): Promise; + + /** + * Tries to invalidate the API keys by using the internal user. + * @param params The params to invalidate the API keys. + */ + invalidateAsInternalUser(params: InvalidateAPIKeysParams): Promise; +} + +export type CreateAPIKeyParams = + | CreateRestAPIKeyParams + | CreateRestAPIKeyWithKibanaPrivilegesParams + | CreateCrossClusterAPIKeyParams; + +/** + * Response of Kibana Create API key endpoint. + */ +export type CreateAPIKeyResult = estypes.SecurityCreateApiKeyResponse; + +export interface CreateRestAPIKeyParams { + type?: 'rest'; + expiration?: string; + name: string; + role_descriptors: Record; + metadata?: { [key: string]: any }; +} + +export interface CreateRestAPIKeyWithKibanaPrivilegesParams { + type?: 'rest'; + expiration?: string; + name: string; + metadata?: { [key: string]: any }; + kibana_role_descriptors: Record< + string, + { + elasticsearch: ElasticsearchPrivilegesType & { [key: string]: unknown }; + kibana: KibanaPrivilegesType; + } + >; +} + +export interface CreateCrossClusterAPIKeyParams { + type: 'cross_cluster'; + expiration?: string; + name: string; + metadata?: { [key: string]: any }; + access: { + search?: Array<{ + names: string[]; + query?: unknown; + field_security?: unknown; + allow_restricted_indices?: boolean; + }>; + replication?: Array<{ + names: string[]; + }>; + }; +} + +export interface GrantAPIKeyResult { + /** + * Unique id for this API key + */ + id: string; + /** + * Name for this API key + */ + name: string; + /** + * Generated API key + */ + api_key: string; +} + +/** + * Represents the parameters for validating API Key credentials. + */ +export interface ValidateAPIKeyParams { + /** + * Unique id for this API key + */ + id: string; + + /** + * Generated API Key (secret) + */ + api_key: string; +} + +/** + * Represents the params for invalidating multiple API keys + */ +export interface InvalidateAPIKeysParams { + /** + * List of unique API key IDs + */ + ids: string[]; +} + +/** + * The return value when invalidating an API key in Elasticsearch. + */ +export interface InvalidateAPIKeyResult { + /** + * The IDs of the API keys that were invalidated as part of the request. + */ + invalidated_api_keys: string[]; + /** + * The IDs of the API keys that were already invalidated. + */ + previously_invalidated_api_keys: string[]; + /** + * The number of errors that were encountered when invalidating the API keys. + */ + error_count: number; + /** + * Details about these errors. This field is not present in the response when error_count is 0. + */ + error_details?: Array<{ + type?: string; + reason?: string; + caused_by?: { + type?: string; + reason?: string; + }; + }>; +} + +/** + * Response of Kibana Update API key endpoint. + */ +export type UpdateAPIKeyResult = estypes.SecurityUpdateApiKeyResponse; + +/** + * Request body of Kibana Update API key endpoint. + */ +export type UpdateAPIKeyParams = + | UpdateRestAPIKeyParams + | UpdateCrossClusterAPIKeyParams + | UpdateRestAPIKeyWithKibanaPrivilegesParams; + +export interface UpdateRestAPIKeyParams { + id: string; + type?: 'rest'; + expiration?: string; + role_descriptors: Record; + metadata?: { [key: string]: any }; +} + +export interface UpdateCrossClusterAPIKeyParams { + id: string; + type: 'cross_cluster'; + expiration?: string; + metadata?: { [key: string]: any }; + access: { + search?: Array<{ + names: string[]; + query?: unknown; + field_security?: unknown; + allow_restricted_indices?: boolean; + }>; + replication?: Array<{ + names: string[]; + }>; + }; +} + +export interface UpdateRestAPIKeyWithKibanaPrivilegesParams { + id: string; + type?: 'rest'; + expiration?: string; + metadata?: { [key: string]: any }; + kibana_role_descriptors: Record< + string, + { + elasticsearch: ElasticsearchPrivilegesType & { [key: string]: unknown }; + kibana: KibanaPrivilegesType; + } + >; +} + +export function isCreateRestAPIKeyParams(params: any): params is CreateRestAPIKeyParams { + return 'role_descriptors' in params; +} diff --git a/packages/core/security/core-security-server/src/authentication/api_keys/api_keys_context.ts b/packages/core/security/core-security-server/src/authentication/api_keys/api_keys_context.ts new file mode 100644 index 0000000000000..7090f7312774f --- /dev/null +++ b/packages/core/security/core-security-server/src/authentication/api_keys/api_keys_context.ts @@ -0,0 +1,64 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { + CreateAPIKeyParams, + CreateAPIKeyResult, + UpdateAPIKeyParams, + UpdateAPIKeyResult, + ValidateAPIKeyParams, + InvalidateAPIKeyResult, + InvalidateAPIKeysParams, +} from './api_keys'; + +/** + * Public API Keys service exposed through core context to manage + * API keys in Elasticsearch, including creation, + * validation, and invalidation of API keys, + * as well as checking the status of API key features. + */ +export interface APIKeysServiceWithContext { + /** + * Determines if API Keys are enabled in Elasticsearch. + */ + areAPIKeysEnabled(): Promise; + + /** + * Tries to create an API key for the current user. + * + * Returns newly created API key or `null` if API keys are disabled. + * + * User needs `manage_api_key` privilege to create REST API keys and `manage_security` for Cross-Cluster API keys. + * + * @param createParams The params to create an API key + */ + create(createParams: CreateAPIKeyParams): Promise; + + /** + * Attempts update an API key with the provided 'role_descriptors' and 'metadata' + * + * Returns `updated`, `true` if the update was successful, `false` if there was nothing to update + * + * User needs `manage_api_key` privilege to update REST API keys and `manage_security` for cross-cluster API keys. + * + * @param updateParams The params to edit an API key + */ + update(updateParams: UpdateAPIKeyParams): Promise; + + /** + * Tries to validate an API key. + * @param apiKeyPrams ValidateAPIKeyParams. + */ + validate(apiKeyPrams: ValidateAPIKeyParams): Promise; + + /** + * Tries to invalidate an API keys. + * @param params The params to invalidate an API keys. + */ + invalidate(params: InvalidateAPIKeysParams): Promise; +} diff --git a/packages/core/security/core-security-server/src/authentication/api_keys/index.ts b/packages/core/security/core-security-server/src/authentication/api_keys/index.ts new file mode 100644 index 0000000000000..da7163bb50879 --- /dev/null +++ b/packages/core/security/core-security-server/src/authentication/api_keys/index.ts @@ -0,0 +1,27 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { + APIKeys as APIKeysService, + CreateAPIKeyParams, + CreateAPIKeyResult, + InvalidateAPIKeyResult, + InvalidateAPIKeysParams, + ValidateAPIKeyParams, + CreateRestAPIKeyParams, + CreateRestAPIKeyWithKibanaPrivilegesParams, + CreateCrossClusterAPIKeyParams, + GrantAPIKeyResult, + UpdateAPIKeyParams, + UpdateAPIKeyResult, + UpdateCrossClusterAPIKeyParams, + UpdateRestAPIKeyParams, + UpdateRestAPIKeyWithKibanaPrivilegesParams, +} from './api_keys'; +export type { APIKeysServiceWithContext } from './api_keys_context'; +export { isCreateRestAPIKeyParams } from './api_keys'; diff --git a/packages/core/security/core-security-server/src/request_handler_context.ts b/packages/core/security/core-security-server/src/request_handler_context.ts index 37915c24ddaa1..6cb13b3afb9a8 100644 --- a/packages/core/security/core-security-server/src/request_handler_context.ts +++ b/packages/core/security/core-security-server/src/request_handler_context.ts @@ -7,7 +7,9 @@ */ import type { AuthenticatedUser } from '@kbn/core-security-common'; + import { AuditLogger } from './audit_logging/audit_logger'; +import type { APIKeysServiceWithContext } from './authentication/api_keys'; export interface SecurityRequestHandlerContext { authc: AuthcRequestHandlerContext; @@ -16,6 +18,7 @@ export interface SecurityRequestHandlerContext { export interface AuthcRequestHandlerContext { getCurrentUser(): AuthenticatedUser | null; + apiKeys: APIKeysServiceWithContext; } export interface AuditRequestHandlerContext { diff --git a/packages/core/security/core-security-server/src/roles/index.ts b/packages/core/security/core-security-server/src/roles/index.ts new file mode 100644 index 0000000000000..420f6780fdd85 --- /dev/null +++ b/packages/core/security/core-security-server/src/roles/index.ts @@ -0,0 +1,9 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { ElasticsearchPrivilegesType, KibanaPrivilegesType } from './schema'; diff --git a/packages/core/security/core-security-server/src/roles/schema.ts b/packages/core/security/core-security-server/src/roles/schema.ts new file mode 100644 index 0000000000000..693916ef3d9b3 --- /dev/null +++ b/packages/core/security/core-security-server/src/roles/schema.ts @@ -0,0 +1,42 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Type representing Elasticsearch specific portion of the role definition. + */ +export interface ElasticsearchPrivilegesType { + cluster?: string[]; + remote_cluster?: Array<{ + privileges: string[]; + clusters: string[]; + }>; + indices?: Array<{ + names: string[]; + field_security?: Record<'grant' | 'except', string[]>; + privileges: string[]; + query?: string; + allow_restricted_indices?: boolean; + }>; + remote_indices?: Array<{ + clusters: string[]; + names: string[]; + field_security?: Record<'grant' | 'except', string[]>; + privileges: string[]; + query?: string; + allow_restricted_indices?: boolean; + }>; + run_as?: string[]; +} +/** + * Type representing Kibana specific portion of the role definition. + */ +export type KibanaPrivilegesType = Array<{ + spaces: string[]; + base?: string[]; + feature?: Record; +}>; diff --git a/x-pack/packages/security/plugin_types_server/index.ts b/x-pack/packages/security/plugin_types_server/index.ts index 1228b9d36f961..21ab0eb2b39af 100644 --- a/x-pack/packages/security/plugin_types_server/index.ts +++ b/x-pack/packages/security/plugin_types_server/index.ts @@ -14,15 +14,6 @@ export type { AuditLogger, } from './src/audit'; export type { - CreateAPIKeyParams, - CreateAPIKeyResult, - CreateRestAPIKeyParams, - GrantAPIKeyResult, - InvalidateAPIKeysParams, - ValidateAPIKeyParams, - CreateRestAPIKeyWithKibanaPrivilegesParams, - CreateCrossClusterAPIKeyParams, - InvalidateAPIKeyResult, APIKeys, AuthenticationServiceStart, UpdateAPIKeyParams, @@ -39,7 +30,6 @@ export type { CheckPrivilegesWithRequest, CheckSavedObjectsPrivilegesWithRequest, CheckPrivilegesDynamicallyWithRequest, - KibanaPrivilegesType, SavedObjectActions, UIActions, CheckPrivilegesPayload, @@ -51,7 +41,6 @@ export type { CheckPrivilegesOptions, CheckUserProfilesPrivilegesPayload, CheckUserProfilesPrivilegesResponse, - ElasticsearchPrivilegesType, CasesActions, CheckPrivileges, AlertingActions, @@ -72,11 +61,30 @@ export type { } from './src/user_profile'; export { - restApiKeySchema, - getRestApiKeyWithKibanaPrivilegesSchema, getUpdateRestApiKeyWithKibanaPrivilegesSchema, - crossClusterApiKeySchema, updateRestApiKeySchema, updateCrossClusterApiKeySchema, } from './src/authentication'; -export { GLOBAL_RESOURCE, elasticsearchRoleSchema, getKibanaRoleSchema } from './src/authorization'; + +export type { + ElasticsearchPrivilegesType, + KibanaPrivilegesType, + APIKeysService, + CreateAPIKeyParams, + CreateAPIKeyResult, + InvalidateAPIKeyResult, + InvalidateAPIKeysParams, + ValidateAPIKeyParams, + CreateRestAPIKeyParams, + CreateRestAPIKeyWithKibanaPrivilegesParams, + CreateCrossClusterAPIKeyParams, + GrantAPIKeyResult, +} from '@kbn/core-security-server'; +export { isCreateRestAPIKeyParams } from '@kbn/core-security-server'; + +export { + restApiKeySchema, + crossClusterApiKeySchema, + getRestApiKeyWithKibanaPrivilegesSchema, +} from './src/authentication'; +export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './src/authorization'; diff --git a/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts b/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts index 2ced5478b46eb..c331802c7f693 100644 --- a/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts +++ b/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts @@ -5,153 +5,9 @@ * 2.0. */ -import type { estypes } from '@elastic/elasticsearch'; - -import type { KibanaRequest } from '@kbn/core/server'; -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { getKibanaRoleSchema, elasticsearchRoleSchema } from '../../authorization'; -export interface APIKeys { - /** - * Determines if API Keys are enabled in Elasticsearch. - */ - areAPIKeysEnabled(): Promise; - - /** - * Determines if Cross-Cluster API Keys are enabled in Elasticsearch. - */ - areCrossClusterAPIKeysEnabled(): Promise; - - /** - * Tries to create an API key for the current user. - * - * Returns newly created API key or `null` if API keys are disabled. - * - * User needs `manage_api_key` privilege to create REST API keys and `manage_security` for Cross-Cluster API keys. - * - * @param request Request instance. - * @param createParams The params to create an API key - */ - create( - request: KibanaRequest, - createParams: CreateAPIKeyParams - ): Promise; - - /** - * Tries to grant an API key for the current user. - * @param request Request instance. - * @param createParams Create operation parameters. - */ - grantAsInternalUser( - request: KibanaRequest, - createParams: CreateRestAPIKeyParams | CreateRestAPIKeyWithKibanaPrivilegesParams - ): Promise; - - /** - * Tries to validate an API key. - * @param apiKeyPrams ValidateAPIKeyParams. - */ - validate(apiKeyPrams: ValidateAPIKeyParams): Promise; - - /** - * Tries to invalidate an API keys. - * @param request Request instance. - * @param params The params to invalidate an API keys. - */ - invalidate( - request: KibanaRequest, - params: InvalidateAPIKeysParams - ): Promise; - - /** - * Tries to invalidate the API keys by using the internal user. - * @param params The params to invalidate the API keys. - */ - invalidateAsInternalUser(params: InvalidateAPIKeysParams): Promise; -} - -export type CreateAPIKeyParams = - | CreateRestAPIKeyParams - | CreateRestAPIKeyWithKibanaPrivilegesParams - | CreateCrossClusterAPIKeyParams; - -/** - * Response of Kibana Create API key endpoint. - */ -export type CreateAPIKeyResult = estypes.SecurityCreateApiKeyResponse; - -export type CreateRestAPIKeyParams = TypeOf; -export type CreateRestAPIKeyWithKibanaPrivilegesParams = TypeOf< - ReturnType ->; -export type CreateCrossClusterAPIKeyParams = TypeOf; - -export interface GrantAPIKeyResult { - /** - * Unique id for this API key - */ - id: string; - /** - * Name for this API key - */ - name: string; - /** - * Generated API key - */ - api_key: string; -} - -/** - * Represents the parameters for validating API Key credentials. - */ -export interface ValidateAPIKeyParams { - /** - * Unique id for this API key - */ - id: string; - - /** - * Generated API Key (secret) - */ - api_key: string; -} - -/** - * Represents the params for invalidating multiple API keys - */ -export interface InvalidateAPIKeysParams { - ids: string[]; -} - -/** - * The return value when invalidating an API key in Elasticsearch. - */ -export interface InvalidateAPIKeyResult { - /** - * The IDs of the API keys that were invalidated as part of the request. - */ - invalidated_api_keys: string[]; - /** - * The IDs of the API keys that were already invalidated. - */ - previously_invalidated_api_keys: string[]; - /** - * The number of errors that were encountered when invalidating the API keys. - */ - error_count: number; - /** - * Details about these errors. This field is not present in the response when error_count is 0. - */ - error_details?: Array<{ - type?: string; - reason?: string; - caused_by?: { - type?: string; - reason?: string; - }; - }>; -} - export const restApiKeySchema = schema.object({ type: schema.maybe(schema.literal('rest')), name: schema.string(), @@ -165,8 +21,11 @@ export const restApiKeySchema = schema.object({ export const getRestApiKeyWithKibanaPrivilegesSchema = ( getBasePrivilegeNames: Parameters[0] ) => - restApiKeySchema.extends({ - role_descriptors: null, + schema.object({ + type: schema.maybe(schema.literal('rest')), + name: schema.string(), + expiration: schema.maybe(schema.string()), + metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), kibana_role_descriptors: schema.recordOf( schema.string(), schema.object({ @@ -176,9 +35,11 @@ export const getRestApiKeyWithKibanaPrivilegesSchema = ( ), }); -export const crossClusterApiKeySchema = restApiKeySchema.extends({ +export const crossClusterApiKeySchema = schema.object({ type: schema.literal('cross_cluster'), - role_descriptors: null, + name: schema.string(), + expiration: schema.maybe(schema.string()), + metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), access: schema.object( { search: schema.maybe( @@ -203,41 +64,52 @@ export const crossClusterApiKeySchema = restApiKeySchema.extends({ ), }); -/** - * Response of Kibana Update API key endpoint. - */ -export type UpdateAPIKeyResult = estypes.SecurityUpdateApiKeyResponse; - -/** - * Request body of Kibana Update API key endpoint. - */ -export type UpdateAPIKeyParams = - | UpdateRestAPIKeyParams - | UpdateCrossClusterAPIKeyParams - | UpdateRestAPIKeyWithKibanaPrivilegesParams; - -export const updateRestApiKeySchema = restApiKeySchema.extends({ - name: null, +export const updateRestApiKeySchema = schema.object({ id: schema.string(), + type: schema.maybe(schema.literal('rest')), + expiration: schema.maybe(schema.string()), + role_descriptors: schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' }), { + defaultValue: {}, + }), + metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), }); -export const updateCrossClusterApiKeySchema = crossClusterApiKeySchema.extends({ - name: null, +export const updateCrossClusterApiKeySchema = schema.object({ id: schema.string(), + type: schema.literal('cross_cluster'), + expiration: schema.maybe(schema.string()), + metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), + access: schema.object( + { + search: schema.maybe( + schema.arrayOf( + schema.object({ + names: schema.arrayOf(schema.string()), + query: schema.maybe(schema.any()), + field_security: schema.maybe(schema.any()), + allow_restricted_indices: schema.maybe(schema.boolean()), + }) + ) + ), + replication: schema.maybe( + schema.arrayOf( + schema.object({ + names: schema.arrayOf(schema.string()), + }) + ) + ), + }, + { unknowns: 'allow' } + ), }); -export type UpdateRestAPIKeyParams = TypeOf; -export type UpdateCrossClusterAPIKeyParams = TypeOf; -export type UpdateRestAPIKeyWithKibanaPrivilegesParams = TypeOf< - ReturnType ->; - export const getUpdateRestApiKeyWithKibanaPrivilegesSchema = ( getBasePrivilegeNames: Parameters[0] ) => - restApiKeySchema.extends({ - role_descriptors: null, - name: null, + schema.object({ + type: schema.maybe(schema.literal('rest')), + expiration: schema.maybe(schema.string()), + metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), id: schema.string(), kibana_role_descriptors: schema.recordOf( schema.string(), diff --git a/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/index.ts b/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/index.ts index ec36a99b4da63..1673682052554 100644 --- a/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/index.ts +++ b/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/index.ts @@ -5,23 +5,6 @@ * 2.0. */ -export type { - CreateAPIKeyParams, - CreateAPIKeyResult, - InvalidateAPIKeyResult, - InvalidateAPIKeysParams, - ValidateAPIKeyParams, - CreateRestAPIKeyParams, - CreateRestAPIKeyWithKibanaPrivilegesParams, - CreateCrossClusterAPIKeyParams, - GrantAPIKeyResult, - APIKeys, - UpdateAPIKeyParams, - UpdateAPIKeyResult, - UpdateCrossClusterAPIKeyParams, - UpdateRestAPIKeyParams, - UpdateRestAPIKeyWithKibanaPrivilegesParams, -} from './api_keys'; export { crossClusterApiKeySchema, getRestApiKeyWithKibanaPrivilegesSchema, diff --git a/x-pack/packages/security/plugin_types_server/src/authentication/authentication_service.ts b/x-pack/packages/security/plugin_types_server/src/authentication/authentication_service.ts index 6bc5a73113ae3..5d066bb6565ca 100644 --- a/x-pack/packages/security/plugin_types_server/src/authentication/authentication_service.ts +++ b/x-pack/packages/security/plugin_types_server/src/authentication/authentication_service.ts @@ -6,14 +6,13 @@ */ import type { KibanaRequest } from '@kbn/core/server'; -import type { AuthenticatedUser } from '@kbn/core-security-common'; - -import type { APIKeys } from './api_keys'; +import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; +import type { APIKeysService } from '@kbn/core-security-server'; /** * Authentication services available on the security plugin's start contract. */ export interface AuthenticationServiceStart { - apiKeys: APIKeys; + apiKeys: APIKeysService; getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null; } diff --git a/x-pack/packages/security/plugin_types_server/src/authentication/index.ts b/x-pack/packages/security/plugin_types_server/src/authentication/index.ts index 6e30f9ebcec24..4a5e7da782baf 100644 --- a/x-pack/packages/security/plugin_types_server/src/authentication/index.ts +++ b/x-pack/packages/security/plugin_types_server/src/authentication/index.ts @@ -5,29 +5,22 @@ * 2.0. */ +export type { AuthenticationServiceStart } from './authentication_service'; + export type { - CreateAPIKeyParams, - CreateAPIKeyResult, - CreateRestAPIKeyParams, - CreateRestAPIKeyWithKibanaPrivilegesParams, - CreateCrossClusterAPIKeyParams, - InvalidateAPIKeyResult, - InvalidateAPIKeysParams, - ValidateAPIKeyParams, - APIKeys, - GrantAPIKeyResult, + APIKeysService as APIKeys, UpdateAPIKeyParams, UpdateAPIKeyResult, UpdateCrossClusterAPIKeyParams, UpdateRestAPIKeyParams, UpdateRestAPIKeyWithKibanaPrivilegesParams, -} from './api_keys'; -export type { AuthenticationServiceStart } from './authentication_service'; +} from '@kbn/core-security-server'; + export { - restApiKeySchema, + crossClusterApiKeySchema, getRestApiKeyWithKibanaPrivilegesSchema, getUpdateRestApiKeyWithKibanaPrivilegesSchema, - crossClusterApiKeySchema, + restApiKeySchema, updateRestApiKeySchema, updateCrossClusterApiKeySchema, } from './api_keys'; diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/index.ts b/x-pack/packages/security/plugin_types_server/src/authorization/index.ts index 54364d7817f31..baeeeddc1fa74 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/index.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/index.ts @@ -42,7 +42,6 @@ export type { PrivilegeDeprecationsRolesByFeatureIdResponse, } from './deprecations'; export type { AuthorizationMode } from './mode'; -export type { ElasticsearchPrivilegesType, KibanaPrivilegesType } from './role_schema'; export { GLOBAL_RESOURCE } from './constants'; export { elasticsearchRoleSchema, getKibanaRoleSchema } from './role_schema'; diff --git a/x-pack/packages/security/plugin_types_server/tsconfig.json b/x-pack/packages/security/plugin_types_server/tsconfig.json index 04ed00229a3de..2f4ae387ac2b5 100644 --- a/x-pack/packages/security/plugin_types_server/tsconfig.json +++ b/x-pack/packages/security/plugin_types_server/tsconfig.json @@ -10,11 +10,10 @@ "target/**/*" ], "kbn_references": [ - "@kbn/config-schema", "@kbn/core", "@kbn/security-plugin-types-common", "@kbn/core-user-profile-server", "@kbn/core-security-server", - "@kbn/core-security-common" + "@kbn/config-schema", ] } diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts index 7c63ace9fc706..198bc004ecb60 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts @@ -30,6 +30,7 @@ describe('API Keys', () => { >; let mockLicense: jest.Mocked; let logger: Logger; + const roleDescriptors: { [key: string]: any } = { foo: true }; beforeEach(() => { mockValidateKibanaPrivileges.mockReset().mockReturnValue({ validationErrors: [] }); @@ -239,9 +240,10 @@ describe('API Keys', () => { }); const result = await apiKeys.create(httpServerMock.createKibanaRequest(), { name: 'key-name', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', }); + expect(result).toEqual({ api_key: 'abc123', expiration: '1d', @@ -253,7 +255,7 @@ describe('API Keys', () => { expect(mockScopedClusterClient.asCurrentUser.security.createApiKey).toHaveBeenCalledWith({ body: { name: 'key-name', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', }, }); @@ -343,7 +345,7 @@ describe('API Keys', () => { const result = await apiKeys.update(httpServerMock.createKibanaRequest(), { id: 'test_id', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, metadata: {}, }); @@ -370,7 +372,7 @@ describe('API Keys', () => { const result = await apiKeys.update(httpServerMock.createKibanaRequest(), { id: 'test_id', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, metadata: {}, }); @@ -473,7 +475,7 @@ describe('API Keys', () => { }), { name: 'test_api_key', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', } ); @@ -512,7 +514,7 @@ describe('API Keys', () => { }), { name: 'test_api_key', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', } ); @@ -527,7 +529,7 @@ describe('API Keys', () => { body: { api_key: { name: 'test_api_key', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', }, grant_type: 'access_token', @@ -553,7 +555,7 @@ describe('API Keys', () => { }), { name: 'test_api_key', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', } ); @@ -592,7 +594,7 @@ describe('API Keys', () => { }), { name: 'test_api_key', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', } ) diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts index 1fbd99b4dd812..054ab59fbd0bb 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts @@ -20,6 +20,7 @@ import type { InvalidateAPIKeysParams, ValidateAPIKeyParams, } from '@kbn/security-plugin-types-server'; +import { isCreateRestAPIKeyParams } from '@kbn/security-plugin-types-server'; import { getFakeKibanaRequest } from './fake_kibana_request'; import type { SecurityLicense } from '../../../common'; @@ -96,7 +97,6 @@ export class APIKeys implements APIKeysType { this.logger.debug( `Testing if API Keys are enabled by attempting to invalidate a non-existant key: ${id}` ); - try { await this.clusterClient.asInternalUser.security.invalidateApiKey({ body: { @@ -125,7 +125,6 @@ export class APIKeys implements APIKeysType { this.logger.debug( `Testing if cross-cluster API Keys are enabled by attempting to update a non-existant key: ${id}` ); - try { await this.clusterClient.asInternalUser.transport.request({ method: 'PUT', @@ -155,13 +154,13 @@ export class APIKeys implements APIKeysType { if (!this.license.isEnabled()) { return null; } - const { type, expiration, name, metadata } = createParams; const scopedClusterClient = this.clusterClient.asScoped(request); this.logger.debug('Trying to create an API key'); let result: CreateAPIKeyResult; + try { if (type === 'cross_cluster') { result = await scopedClusterClient.asCurrentUser.transport.request({ @@ -175,13 +174,13 @@ export class APIKeys implements APIKeysType { name, expiration, metadata, - role_descriptors: - 'role_descriptors' in createParams - ? createParams.role_descriptors - : this.parseRoleDescriptorsWithKibanaPrivileges( - createParams.kibana_role_descriptors, - false - ), + role_descriptors: isCreateRestAPIKeyParams(createParams) + ? createParams.role_descriptors + : this.parseRoleDescriptorsWithKibanaPrivileges( + createParams.kibana_role_descriptors, + this.kibanaFeatures, + false + ), }, }); } @@ -234,6 +233,7 @@ export class APIKeys implements APIKeysType { ? updateParams.role_descriptors : this.parseRoleDescriptorsWithKibanaPrivileges( updateParams.kibana_role_descriptors, + this.kibanaFeatures, true ), }); @@ -279,12 +279,12 @@ export class APIKeys implements APIKeysType { ); const { expiration, metadata, name } = createParams; - const roleDescriptors = 'role_descriptors' in createParams ? createParams.role_descriptors : this.parseRoleDescriptorsWithKibanaPrivileges( createParams.kibana_role_descriptors, + this.kibanaFeatures, false ); @@ -293,7 +293,6 @@ export class APIKeys implements APIKeysType { authorizationHeader, clientAuthorizationHeader ); - // User needs `manage_api_key` or `grant_api_key` privilege to use this API let result: GrantAPIKeyResult; try { @@ -318,7 +317,6 @@ export class APIKeys implements APIKeysType { } this.logger.debug(`Trying to invalidate ${params.ids.length} an API key as current user`); - let result: InvalidateAPIKeyResult; try { // User needs `manage_api_key` privilege to use this API @@ -354,6 +352,7 @@ export class APIKeys implements APIKeysType { this.logger.debug(`Trying to invalidate ${params.ids.length} API keys`); let result: InvalidateAPIKeyResult; + try { // Internal user needs `cluster:admin/xpack/security/api_key/invalidate` privilege to use this API result = await this.clusterClient.asInternalUser.security.invalidateApiKey({ @@ -384,7 +383,6 @@ export class APIKeys implements APIKeysType { const fakeRequest = getFakeKibanaRequest(apiKeyPrams); this.logger.debug(`Trying to validate an API key`); - try { await this.clusterClient.asScoped(fakeRequest).asCurrentUser.security.authenticate(); this.logger.debug(`API key was validated successfully`); @@ -445,6 +443,7 @@ export class APIKeys implements APIKeysType { private parseRoleDescriptorsWithKibanaPrivileges( kibanaRoleDescriptors: CreateRestAPIKeyWithKibanaPrivilegesParams['kibana_role_descriptors'], + features: KibanaFeature[], isEdit: boolean ) { const roleDescriptors = Object.create(null); @@ -452,10 +451,7 @@ export class APIKeys implements APIKeysType { const allValidationErrors: string[] = []; if (kibanaRoleDescriptors) { Object.entries(kibanaRoleDescriptors).forEach(([roleKey, roleDescriptor]) => { - const { validationErrors } = validateKibanaPrivileges( - this.kibanaFeatures, - roleDescriptor.kibana - ); + const { validationErrors } = validateKibanaPrivileges(features, roleDescriptor.kibana); allValidationErrors.push(...validationErrors); const applications = transformPrivilegesToElasticsearchPrivileges( diff --git a/x-pack/plugins/security/server/authentication/authentication_service.mock.ts b/x-pack/plugins/security/server/authentication/authentication_service.mock.ts index de87e7161bda8..676b039eefaa4 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.mock.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { apiKeysMock } from '@kbn/core-security-server-mocks'; import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; -import { apiKeysMock } from './api_keys/api_keys.mock'; import type { InternalAuthenticationServiceStart } from './authentication_service'; export const authenticationServiceMock = { diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index abc400c0b6305..cf99084d4d0bc 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -348,7 +348,6 @@ export class AuthenticationService { applicationName, kibanaFeatures, }); - /** * Retrieves server protocol name/host name/port and merges it with `xpack.security.public` config * to construct a server base URL (deprecated, used by the SAML provider only). diff --git a/x-pack/plugins/security/server/build_delegate_apis.test.ts b/x-pack/plugins/security/server/build_delegate_apis.test.ts index 59963ad2aef8e..297ee6ebf92c4 100644 --- a/x-pack/plugins/security/server/build_delegate_apis.test.ts +++ b/x-pack/plugins/security/server/build_delegate_apis.test.ts @@ -65,6 +65,21 @@ describe('buildSecurityApi', () => { expect(auditService.asScoped(request).log).toHaveBeenCalledWith({ message: 'an event' }); }); }); + + describe('authc.apiKeys', () => { + it('properly delegates to the service', async () => { + await authc.apiKeys.areAPIKeysEnabled(); + expect(authc.apiKeys.areAPIKeysEnabled).toHaveBeenCalledTimes(1); + }); + + it('returns the result from the service', async () => { + authc.apiKeys.areAPIKeysEnabled.mockReturnValue(Promise.resolve(false)); + + const areAPIKeysEnabled = await authc.apiKeys.areAPIKeysEnabled(); + + expect(areAPIKeysEnabled).toBe(false); + }); + }); }); describe('buildUserProfileApi', () => { diff --git a/x-pack/plugins/security/server/build_delegate_apis.ts b/x-pack/plugins/security/server/build_delegate_apis.ts index fb782f3db256f..f6d57cfc8a4a8 100644 --- a/x-pack/plugins/security/server/build_delegate_apis.ts +++ b/x-pack/plugins/security/server/build_delegate_apis.ts @@ -24,6 +24,17 @@ export const buildSecurityApi = ({ getCurrentUser: (request) => { return getAuthc().getCurrentUser(request); }, + apiKeys: { + areAPIKeysEnabled: () => getAuthc().apiKeys.areAPIKeysEnabled(), + areCrossClusterAPIKeysEnabled: () => getAuthc().apiKeys.areAPIKeysEnabled(), + grantAsInternalUser: (request, createParams) => + getAuthc().apiKeys.grantAsInternalUser(request, createParams), + create: (request, createParams) => getAuthc().apiKeys.create(request, createParams), + update: (request, updateParams) => getAuthc().apiKeys.update(request, updateParams), + validate: (apiKeyParams) => getAuthc().apiKeys.validate(apiKeyParams), + invalidate: (request, params) => getAuthc().apiKeys.invalidate(request, params), + invalidateAsInternalUser: (params) => getAuthc().apiKeys.invalidateAsInternalUser(params), + }, }, audit: { asScoped(request) { diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index a5473176fc7e7..e47faeba525a0 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -7,7 +7,7 @@ import type { TransportResult } from '@elastic/elasticsearch'; -import { securityServiceMock } from '@kbn/core-security-server-mocks'; +import { apiKeysMock, securityServiceMock } from '@kbn/core-security-server-mocks'; import { auditServiceMock } from './audit/mocks'; import { authenticationServiceMock } from './authentication/authentication_service.mock'; @@ -19,7 +19,10 @@ function createSetupMock() { const mockAuthz = authorizationMock.create(); return { audit: auditServiceMock.create(), - authc: { getCurrentUser: jest.fn() }, + authc: { + getCurrentUser: jest.fn(), + apiKeys: apiKeysMock.create(), + }, authz: { actions: mockAuthz.actions, checkPrivilegesWithRequest: mockAuthz.checkPrivilegesWithRequest, diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 9d5ffde67b1d7..a500454f98fa9 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -77,7 +77,9 @@ export interface SecurityPluginSetup extends SecurityPluginSetupWithoutDeprecate /** * @deprecated Use `authc` methods from the `SecurityServiceStart` contract instead. */ - authc: { getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null }; + authc: { + getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null; + }; /** * @deprecated Use `authz` methods from the `SecurityServiceStart` contract instead. */ @@ -110,6 +112,7 @@ export class SecurityPlugin private readonly logger: Logger; private authorizationSetup?: AuthorizationServiceSetupInternal; private auditSetup?: AuditServiceSetup; + private configSubscription?: Subscription; private config?: ConfigType; @@ -189,6 +192,7 @@ export class SecurityPlugin this.initializerContext.logger.get('authentication') ); this.auditService = new AuditService(this.initializerContext.logger.get('audit')); + this.elasticsearchService = new ElasticsearchService( this.initializerContext.logger.get('elasticsearch') ); @@ -340,7 +344,9 @@ export class SecurityPlugin return Object.freeze({ audit: this.auditSetup, - authc: { getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request) }, + authc: { + getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request), + }, authz: { actions: this.authorizationSetup.actions, checkPrivilegesWithRequest: this.authorizationSetup.checkPrivilegesWithRequest, @@ -421,8 +427,8 @@ export class SecurityPlugin return Object.freeze({ authc: { - apiKeys: this.authenticationStart.apiKeys, getCurrentUser: this.authenticationStart.getCurrentUser, + apiKeys: this.authenticationStart.apiKeys, }, authz: { actions: this.authorizationSetup!.actions, diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index 64d162839cf1e..10c1ada6ede15 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -83,7 +83,7 @@ "@kbn/core-user-profile-browser", "@kbn/security-api-key-management", "@kbn/security-form-components", - "@kbn/core-security-server-mocks", + "@kbn/core-security-server-mocks" ], "exclude": [ "target/**/*",