From 083b1b4ab06783349df5fadf16aabf601070bb2c Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Tue, 25 Jun 2024 18:32:16 +0200 Subject: [PATCH 01/45] [Security-in-Core]initial work for exposing api keys service in core --- .../security/core-security-common/index.ts | 2 + .../src/api_keys/api_keys.ts | 10 ++++ .../src/api_keys/index.ts | 9 +++ .../src/security_route_handler_context.ts | 1 + .../src/utils/convert_security_api.test.ts | 10 ++++ .../src/utils/default_implementation.test.ts | 7 +++ .../src/utils/default_implementation.ts | 1 + .../src/api_keys.mock.ts | 17 ++++++ .../src/security_service.mock.ts | 4 ++ .../core-security-server/src/authc.ts | 3 +- .../src/request_handler_context.ts | 3 +- .../server/api_keys/api_keys_service.ts | 49 +++++++++++++++ .../plugins/security/server/api_keys/index.ts | 8 +++ .../authentication/api_keys/api_keys.ts | 60 ++++++++++--------- .../authentication/authentication_service.ts | 5 +- .../security/server/build_delegate_apis.ts | 10 ++++ x-pack/plugins/security/server/plugin.ts | 27 ++++++++- 17 files changed, 190 insertions(+), 36 deletions(-) create mode 100644 packages/core/security/core-security-common/src/api_keys/api_keys.ts create mode 100644 packages/core/security/core-security-common/src/api_keys/index.ts create mode 100644 packages/core/security/core-security-server-mocks/src/api_keys.mock.ts create mode 100644 x-pack/plugins/security/server/api_keys/api_keys_service.ts create mode 100644 x-pack/plugins/security/server/api_keys/index.ts diff --git a/packages/core/security/core-security-common/index.ts b/packages/core/security/core-security-common/index.ts index d11d52117cfd..c6bbddbd40ec 100644 --- a/packages/core/security/core-security-common/index.ts +++ b/packages/core/security/core-security-common/index.ts @@ -12,3 +12,5 @@ export type { AuthenticatedUser, AuthenticationProvider, } from './src/authentication'; + +export type { APIKeysService } from './src/api_keys'; diff --git a/packages/core/security/core-security-common/src/api_keys/api_keys.ts b/packages/core/security/core-security-common/src/api_keys/api_keys.ts new file mode 100644 index 000000000000..c0dfc7d838b0 --- /dev/null +++ b/packages/core/security/core-security-common/src/api_keys/api_keys.ts @@ -0,0 +1,10 @@ +/* + * 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 { APIKeys } from '@kbn/security-plugin-types-server'; + +export interface APIKeysService extends APIKeys {} diff --git a/packages/core/security/core-security-common/src/api_keys/index.ts b/packages/core/security/core-security-common/src/api_keys/index.ts new file mode 100644 index 000000000000..5bad4506b1f8 --- /dev/null +++ b/packages/core/security/core-security-common/src/api_keys/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 { APIKeysService } from './api_keys'; 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 4fa328782dd0..a91daced601d 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,7 @@ export class CoreSecurityRouteHandlerContext implements SecurityRequestHandlerCo if (this.#authc == null) { this.#authc = { getCurrentUser: () => this.securityStart.authc.getCurrentUser(this.request), + apiKeys: this.securityStart.authc.apiKeys, }; } 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 7c2e49092f73..ee4b052dd469 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,15 @@ 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(), + }, }, audit: { asScoped: jest.fn().mockReturnValue(createAuditLoggerMock.create()), @@ -23,6 +32,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 e4348404671b..113a6ebb3c33 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,13 @@ describe('getDefaultSecurityImplementation', () => { }); }); + describe('authc.apiKeys', () => { + it('returns null', async () => { + const { apiKeys } = implementation.authc; + expect(apiKeys).toBeNull(); + }); + }); + 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 91819807f106..dc7f12afe097 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 @@ -12,6 +12,7 @@ export const getDefaultSecurityImplementation = (): CoreSecurityDelegateContract return { authc: { getCurrentUser: () => null, + apiKeys: null, }, audit: { asScoped: () => { diff --git a/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts b/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts new file mode 100644 index 000000000000..f9ccee0f02ae --- /dev/null +++ b/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts @@ -0,0 +1,17 @@ +/* + * 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 const apiKeysServiceMock = { + areAPIKeysEnabled: jest.fn(), + areCrossClusterAPIKeysEnabled: jest.fn(), + validate: jest.fn(), + invalidate: jest.fn(), + invalidateAsInternalUser: jest.fn(), + grantAsInternalUser: 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 b19539fd862c..202b9c450886 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 { apiKeysServiceMock } from './api_keys.mock'; import { auditServiceMock, type MockedAuditService } from './audit.mock'; const createSetupMock = () => { @@ -33,6 +34,7 @@ const createStartMock = (): SecurityStartMock => { const mock = { authc: { getCurrentUser: jest.fn(), + apiKeys: apiKeysServiceMock, }, audit: auditServiceMock.create(), }; @@ -58,6 +60,7 @@ const createInternalStartMock = (): InternalSecurityStartMock => { const mock = { authc: { getCurrentUser: jest.fn(), + apiKeys: apiKeysServiceMock, }, audit: auditServiceMock.create(), }; @@ -79,6 +82,7 @@ const createRequestHandlerContextMock = () => { const mock: jest.MockedObjectDeep = { authc: { getCurrentUser: jest.fn(), + apiKeys: apiKeysServiceMock, }, audit: { logger: { diff --git a/packages/core/security/core-security-server/src/authc.ts b/packages/core/security/core-security-server/src/authc.ts index 97654104858e..84d10275d340 100644 --- a/packages/core/security/core-security-server/src/authc.ts +++ b/packages/core/security/core-security-server/src/authc.ts @@ -7,7 +7,7 @@ */ import type { KibanaRequest } from '@kbn/core-http-server'; -import type { AuthenticatedUser } from '@kbn/core-security-common'; +import type { AuthenticatedUser, APIKeysService } from '@kbn/core-security-common'; /** * Core's authentication service @@ -22,4 +22,5 @@ export interface CoreAuthenticationService { * @param request The request to retrieve the authenticated user for. */ getCurrentUser(request: KibanaRequest): AuthenticatedUser | null; + apiKeys: APIKeysService | null; } 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 37915c24ddaa..cbe13ba631a6 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 @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { AuthenticatedUser } from '@kbn/core-security-common'; +import type { AuthenticatedUser, APIKeysService } from '@kbn/core-security-common'; import { AuditLogger } from './audit_logging/audit_logger'; export interface SecurityRequestHandlerContext { @@ -16,6 +16,7 @@ export interface SecurityRequestHandlerContext { export interface AuthcRequestHandlerContext { getCurrentUser(): AuthenticatedUser | null; + apiKeys: APIKeysService | null; } export interface AuditRequestHandlerContext { diff --git a/x-pack/plugins/security/server/api_keys/api_keys_service.ts b/x-pack/plugins/security/server/api_keys/api_keys_service.ts new file mode 100644 index 000000000000..a1cd61adf8fc --- /dev/null +++ b/x-pack/plugins/security/server/api_keys/api_keys_service.ts @@ -0,0 +1,49 @@ +/* + * 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 { IClusterClient, Logger } from '@kbn/core/server'; +import type { KibanaFeature } from '@kbn/features-plugin/server'; +import type { SecurityLicense } from '@kbn/security-plugin-types-common'; + +import { APIKeys } from '../authentication/api_keys'; + +// import type { UpdateAPIKeyParams, UpdateAPIKeyResult } from '../../routes/api_keys'; + +// export type { UpdateAPIKeyParams, UpdateAPIKeyResult }; + +const ELASTICSEARCH_CLIENT_AUTHENTICATION_HEADER = 'es-client-authentication'; + +/** + * Represents the options to create an APIKey class instance that will be + * shared between functions (create, invalidate, etc). + */ +export interface SetupOptions { + getClusterClient: () => Promise; + getKibanaFeatures: () => Promise; + license: SecurityLicense; + applicationName: string; +} + +export class APIKeysService { + private logger: Logger; + + constructor(_logger: Logger) { + this.logger = _logger.get('ecs'); + } + + setup({ getClusterClient, license, applicationName, getKibanaFeatures }: SetupOptions) { + const apiKeysService = new APIKeys({ + getClusterClient, + logger: this.logger.get('api-key'), + license, + applicationName, + getKibanaFeatures, + }); + return apiKeysService; + } + stop() {} +} diff --git a/x-pack/plugins/security/server/api_keys/index.ts b/x-pack/plugins/security/server/api_keys/index.ts new file mode 100644 index 000000000000..86bbca84c4b9 --- /dev/null +++ b/x-pack/plugins/security/server/api_keys/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { APIKeysService } from './api_keys_service'; 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 1fbd99b4dd81..b7751be89dd7 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 @@ -40,10 +40,10 @@ const ELASTICSEARCH_CLIENT_AUTHENTICATION_HEADER = 'es-client-authentication'; */ export interface ConstructorOptions { logger: Logger; - clusterClient: IClusterClient; + getClusterClient: () => Promise; license: SecurityLicense; applicationName: string; - kibanaFeatures: KibanaFeature[]; + getKibanaFeatures: () => Promise; } type GrantAPIKeyParams = @@ -64,23 +64,23 @@ type GrantAPIKeyParams = */ export class APIKeys implements APIKeysType { private readonly logger: Logger; - private readonly clusterClient: IClusterClient; + private readonly getClusterClient: () => Promise; private readonly license: SecurityLicense; private readonly applicationName: string; - private readonly kibanaFeatures: KibanaFeature[]; + private readonly getKibanaFeatures: () => Promise; constructor({ logger, - clusterClient, + getClusterClient, license, applicationName, - kibanaFeatures, + getKibanaFeatures, }: ConstructorOptions) { this.logger = logger; - this.clusterClient = clusterClient; + this.getClusterClient = getClusterClient; this.license = license; this.applicationName = applicationName; - this.kibanaFeatures = kibanaFeatures; + this.getKibanaFeatures = getKibanaFeatures; } /** @@ -96,9 +96,9 @@ export class APIKeys implements APIKeysType { this.logger.debug( `Testing if API Keys are enabled by attempting to invalidate a non-existant key: ${id}` ); - + const clusterClient = await this.getClusterClient(); try { - await this.clusterClient.asInternalUser.security.invalidateApiKey({ + await clusterClient.asInternalUser.security.invalidateApiKey({ body: { ids: [id], }, @@ -125,9 +125,9 @@ 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}` ); - + const clusterClient = await this.getClusterClient(); try { - await this.clusterClient.asInternalUser.transport.request({ + await clusterClient.asInternalUser.transport.request({ method: 'PUT', path: `/_security/cross_cluster/api_key/${id}`, body: {}, // We are sending an empty request body and expect a validation error if Update cross-cluster API key endpoint is available. @@ -155,9 +155,9 @@ export class APIKeys implements APIKeysType { if (!this.license.isEnabled()) { return null; } - + const clusterClient = await this.getClusterClient(); const { type, expiration, name, metadata } = createParams; - const scopedClusterClient = this.clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); this.logger.debug('Trying to create an API key'); @@ -170,6 +170,7 @@ export class APIKeys implements APIKeysType { body: { name, expiration, metadata, access: createParams.access }, }); } else { + const features = await this.getKibanaFeatures(); result = await scopedClusterClient.asCurrentUser.security.createApiKey({ body: { name, @@ -180,6 +181,7 @@ export class APIKeys implements APIKeysType { ? createParams.role_descriptors : this.parseRoleDescriptorsWithKibanaPrivileges( createParams.kibana_role_descriptors, + features, false ), }, @@ -211,9 +213,9 @@ export class APIKeys implements APIKeysType { if (!this.license.isEnabled()) { return null; } - + const clusterClient = await this.getClusterClient(); const { type, id, metadata } = updateParams; - const scopedClusterClient = this.clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); this.logger.debug('Trying to edit an API key'); @@ -226,6 +228,7 @@ export class APIKeys implements APIKeysType { body: { metadata, access: updateParams.access }, }); } else { + const features = await this.getKibanaFeatures(); result = await scopedClusterClient.asCurrentUser.security.updateApiKey({ id, metadata, @@ -234,6 +237,7 @@ export class APIKeys implements APIKeysType { ? updateParams.role_descriptors : this.parseRoleDescriptorsWithKibanaPrivileges( updateParams.kibana_role_descriptors, + features, true ), }); @@ -279,12 +283,13 @@ export class APIKeys implements APIKeysType { ); const { expiration, metadata, name } = createParams; - + const features = await this.getKibanaFeatures(); const roleDescriptors = 'role_descriptors' in createParams ? createParams.role_descriptors : this.parseRoleDescriptorsWithKibanaPrivileges( createParams.kibana_role_descriptors, + features, false ); @@ -293,11 +298,11 @@ export class APIKeys implements APIKeysType { authorizationHeader, clientAuthorizationHeader ); - + const clusterClient = await this.getClusterClient(); // User needs `manage_api_key` or `grant_api_key` privilege to use this API let result: GrantAPIKeyResult; try { - result = await this.clusterClient.asInternalUser.security.grantApiKey({ body: params }); + result = await clusterClient.asInternalUser.security.grantApiKey({ body: params }); this.logger.debug('API key was granted successfully'); } catch (e) { this.logger.error(`Failed to grant API key: ${e.message}`); @@ -318,11 +323,11 @@ export class APIKeys implements APIKeysType { } this.logger.debug(`Trying to invalidate ${params.ids.length} an API key as current user`); - + const clusterClient = await this.getClusterClient(); let result: InvalidateAPIKeyResult; try { // User needs `manage_api_key` privilege to use this API - result = await this.clusterClient.asScoped(request).asCurrentUser.security.invalidateApiKey({ + result = await clusterClient.asScoped(request).asCurrentUser.security.invalidateApiKey({ body: { ids: params.ids, }, @@ -354,9 +359,10 @@ export class APIKeys implements APIKeysType { this.logger.debug(`Trying to invalidate ${params.ids.length} API keys`); let result: InvalidateAPIKeyResult; + const clusterClient = await this.getClusterClient(); try { // Internal user needs `cluster:admin/xpack/security/api_key/invalidate` privilege to use this API - result = await this.clusterClient.asInternalUser.security.invalidateApiKey({ + result = await clusterClient.asInternalUser.security.invalidateApiKey({ body: { ids: params.ids, }, @@ -384,9 +390,9 @@ export class APIKeys implements APIKeysType { const fakeRequest = getFakeKibanaRequest(apiKeyPrams); this.logger.debug(`Trying to validate an API key`); - + const clusterClient = await this.getClusterClient(); try { - await this.clusterClient.asScoped(fakeRequest).asCurrentUser.security.authenticate(); + await clusterClient.asScoped(fakeRequest).asCurrentUser.security.authenticate(); this.logger.debug(`API key was validated successfully`); return true; } catch (e) { @@ -445,6 +451,7 @@ export class APIKeys implements APIKeysType { private parseRoleDescriptorsWithKibanaPrivileges( kibanaRoleDescriptors: CreateRestAPIKeyWithKibanaPrivilegesParams['kibana_role_descriptors'], + features: KibanaFeature[], isEdit: boolean ) { const roleDescriptors = Object.create(null); @@ -452,10 +459,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.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index abc400c0b630..ec5aaa0824da 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -342,13 +342,12 @@ export class AuthenticationService { customLogoutURL, }: AuthenticationServiceStartParams): InternalAuthenticationServiceStart { const apiKeys = new APIKeys({ - clusterClient, + getClusterClient: () => Promise.resolve(clusterClient), logger: this.logger.get('api-key'), license: this.license, applicationName, - kibanaFeatures, + getKibanaFeatures: () => Promise.resolve(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.ts b/x-pack/plugins/security/server/build_delegate_apis.ts index fb782f3db256..4d56d917aedc 100644 --- a/x-pack/plugins/security/server/build_delegate_apis.ts +++ b/x-pack/plugins/security/server/build_delegate_apis.ts @@ -24,6 +24,16 @@ 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), + 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/plugin.ts b/x-pack/plugins/security/server/plugin.ts index cf362926bdd0..1c6b796b8a5f 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -24,6 +24,7 @@ import type { } from '@kbn/features-plugin/server'; import type { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/server'; import type { + APIKeys as APIKeysType, AuditServiceSetup, AuthorizationServiceSetup, SecurityPluginSetup as SecurityPluginSetupWithoutDeprecatedMembers, @@ -39,6 +40,7 @@ import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { AnalyticsService } from './analytics'; import type { AnonymousAccessServiceStart } from './anonymous_access'; import { AnonymousAccessService } from './anonymous_access'; +import { APIKeysService } from './api_keys'; import { AuditService } from './audit'; import type { InternalAuthenticationServiceStart } from './authentication'; import { AuthenticationService } from './authentication'; @@ -75,7 +77,10 @@ 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; + apiKeys: APIKeysType | null; + }; /** * @deprecated Use `authz` methods from the `SecurityServiceStart` contract instead. */ @@ -108,6 +113,7 @@ export class SecurityPlugin private readonly logger: Logger; private authorizationSetup?: AuthorizationServiceSetupInternal; private auditSetup?: AuditServiceSetup; + private configSubscription?: Subscription; private config?: ConfigType; @@ -153,6 +159,7 @@ export class SecurityPlugin }; private readonly auditService: AuditService; + private readonly apiKeysService: APIKeysService; private readonly securityLicenseService = new SecurityLicenseService(); private readonly analyticsService: AnalyticsService; private readonly authorizationService = new AuthorizationService(); @@ -184,6 +191,8 @@ export class SecurityPlugin this.initializerContext.logger.get('authentication') ); this.auditService = new AuditService(this.initializerContext.logger.get('audit')); + this.apiKeysService = new APIKeysService(this.initializerContext.logger.get('api-keys')); + this.elasticsearchService = new ElasticsearchService( this.initializerContext.logger.get('elasticsearch') ); @@ -261,6 +270,15 @@ export class SecurityPlugin recordAuditLoggingUsage: () => this.getFeatureUsageService().recordAuditLoggingUsage(), }); + const apiKeysSetup = this.apiKeysService.setup({ + getClusterClient: () => + startServicesPromise.then(({ elasticsearch }) => elasticsearch.client), + license, + applicationName: 'SomethingNameSid', + getKibanaFeatures: () => + startServicesPromise.then((services) => services.features.getKibanaFeatures()), + }); + this.anonymousAccessService.setup(); this.authorizationSetup = this.authorizationService.setup({ @@ -330,7 +348,10 @@ export class SecurityPlugin return Object.freeze({ audit: this.auditSetup, - authc: { getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request) }, + authc: { + getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request), + apiKeys: apiKeysSetup, + }, authz: { actions: this.authorizationSetup.actions, checkPrivilegesWithRequest: this.authorizationSetup.checkPrivilegesWithRequest, @@ -411,8 +432,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, From 25f05090550d6ed493101b7ec078c00e9d73dbf1 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Tue, 25 Jun 2024 21:05:40 +0200 Subject: [PATCH 02/45] move public security types to core-security-common --- .../security/core-security-common/index.ts | 18 +- .../src/api_keys/api_keys.ts | 199 ++++++++++++- .../src/api_keys/index.ts | 18 +- .../security/plugin_types_common/index.ts | 3 + .../src/authorization/index.ts | 3 + .../src/authorization/role.ts | 267 +++++++++++++++++ .../security/plugin_types_server/index.ts | 7 +- .../src/authentication/api_keys/api_keys.ts | 196 +------------ .../src/authentication/api_keys/index.ts | 31 +- .../authentication/authentication_service.ts | 5 +- .../src/authentication/index.ts | 3 +- .../src/authorization/index.ts | 13 +- .../src/authorization/role_schema.ts | 274 ------------------ 13 files changed, 543 insertions(+), 494 deletions(-) delete mode 100644 x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts diff --git a/packages/core/security/core-security-common/index.ts b/packages/core/security/core-security-common/index.ts index c6bbddbd40ec..9c946e676a4f 100644 --- a/packages/core/security/core-security-common/index.ts +++ b/packages/core/security/core-security-common/index.ts @@ -13,4 +13,20 @@ export type { AuthenticationProvider, } from './src/authentication'; -export type { APIKeysService } from './src/api_keys'; +export type { + APIKeysService, + CreateAPIKeyParams, + CreateAPIKeyResult, + InvalidateAPIKeyResult, + InvalidateAPIKeysParams, + ValidateAPIKeyParams, + CreateRestAPIKeyParams, + CreateRestAPIKeyWithKibanaPrivilegesParams, + CreateCrossClusterAPIKeyParams, + GrantAPIKeyResult, +} from './src/api_keys'; +export { + restApiKeySchema, + crossClusterApiKeySchema, + getRestApiKeyWithKibanaPrivilegesSchema, +} from './src/api_keys'; diff --git a/packages/core/security/core-security-common/src/api_keys/api_keys.ts b/packages/core/security/core-security-common/src/api_keys/api_keys.ts index c0dfc7d838b0..1801fb37a74b 100644 --- a/packages/core/security/core-security-common/src/api_keys/api_keys.ts +++ b/packages/core/security/core-security-common/src/api_keys/api_keys.ts @@ -5,6 +5,201 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type { APIKeys } from '@kbn/security-plugin-types-server'; -export interface APIKeysService extends APIKeys {} +import type { estypes } from '@elastic/elasticsearch'; + +import type { KibanaRequest } from '@kbn/core/server'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { getKibanaRoleSchema, elasticsearchRoleSchema } from '@kbn/security-plugin-types-common'; + +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(), + 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 getRestApiKeyWithKibanaPrivilegesSchema = ( + getBasePrivilegeNames: Parameters[0] +) => + restApiKeySchema.extends({ + role_descriptors: null, + kibana_role_descriptors: schema.recordOf( + schema.string(), + schema.object({ + elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), + kibana: getKibanaRoleSchema(getBasePrivilegeNames), + }) + ), + }); + +export const crossClusterApiKeySchema = restApiKeySchema.extends({ + type: schema.literal('cross_cluster'), + role_descriptors: null, + 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' } + ), +}); diff --git a/packages/core/security/core-security-common/src/api_keys/index.ts b/packages/core/security/core-security-common/src/api_keys/index.ts index 5bad4506b1f8..030c734e50c7 100644 --- a/packages/core/security/core-security-common/src/api_keys/index.ts +++ b/packages/core/security/core-security-common/src/api_keys/index.ts @@ -6,4 +6,20 @@ * Side Public License, v 1. */ -export type { APIKeysService } from './api_keys'; +export type { + APIKeys as APIKeysService, + CreateAPIKeyParams, + CreateAPIKeyResult, + InvalidateAPIKeyResult, + InvalidateAPIKeysParams, + ValidateAPIKeyParams, + CreateRestAPIKeyParams, + CreateRestAPIKeyWithKibanaPrivilegesParams, + CreateCrossClusterAPIKeyParams, + GrantAPIKeyResult, +} from './api_keys'; +export { + restApiKeySchema, + crossClusterApiKeySchema, + getRestApiKeyWithKibanaPrivilegesSchema, +} from './api_keys'; diff --git a/x-pack/packages/security/plugin_types_common/index.ts b/x-pack/packages/security/plugin_types_common/index.ts index 8dd0ff726103..db5715c8d364 100644 --- a/x-pack/packages/security/plugin_types_common/index.ts +++ b/x-pack/packages/security/plugin_types_common/index.ts @@ -18,7 +18,10 @@ export type { RoleRemoteIndexPrivilege, RoleRemoteClusterPrivilege, FeaturesPrivileges, + ElasticsearchPrivilegesType, + KibanaPrivilegesType, } from './src/authorization'; +export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './src/authorization'; export type { SecurityLicense, SecurityLicenseFeatures, LoginLayout } from './src/licensing'; export type { UserProfileUserInfo, diff --git a/x-pack/packages/security/plugin_types_common/src/authorization/index.ts b/x-pack/packages/security/plugin_types_common/src/authorization/index.ts index 89857a18865a..eb8c8e9b632a 100644 --- a/x-pack/packages/security/plugin_types_common/src/authorization/index.ts +++ b/x-pack/packages/security/plugin_types_common/src/authorization/index.ts @@ -12,4 +12,7 @@ export type { RoleIndexPrivilege, RoleRemoteIndexPrivilege, RoleRemoteClusterPrivilege, + ElasticsearchPrivilegesType, + KibanaPrivilegesType, } from './role'; +export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './role'; diff --git a/x-pack/packages/security/plugin_types_common/src/authorization/role.ts b/x-pack/packages/security/plugin_types_common/src/authorization/role.ts index 3a20b64d4d06..d664823c7325 100644 --- a/x-pack/packages/security/plugin_types_common/src/authorization/role.ts +++ b/x-pack/packages/security/plugin_types_common/src/authorization/role.ts @@ -4,6 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import _ from 'lodash'; + +import type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import type { FeaturesPrivileges } from './features_privileges'; @@ -53,3 +57,266 @@ export interface Role { _transform_error?: string[]; _unrecognized_applications?: string[]; } + +export const GLOBAL_RESOURCE = '*'; +/** + * Elasticsearch specific portion of the role definition. + * See more details at https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api.html#security-role-apis. + */ +export const elasticsearchRoleSchema = schema.object({ + /** + * An optional list of cluster privileges. These privileges define the cluster level actions that + * users with this role are able to execute + */ + cluster: schema.maybe(schema.arrayOf(schema.string())), + + /** + * An optional list of remote cluster privileges. These privileges define the remote cluster level actions that + * users with this role are able to execute + */ + remote_cluster: schema.maybe( + schema.arrayOf( + schema.object({ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + clusters: schema.arrayOf(schema.string(), { minSize: 1 }), + }) + ) + ), + + /** + * An optional list of indices permissions entries. + */ + indices: schema.maybe( + schema.arrayOf( + schema.object({ + /** + * Required list of indices (or index name patterns) to which the permissions in this + * entry apply. + */ + names: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional set of the document fields that the owners of the role have read access to. + */ + field_security: schema.maybe( + schema.recordOf( + schema.oneOf([schema.literal('grant'), schema.literal('except')]), + schema.arrayOf(schema.string()) + ) + ), + + /** + * Required list of the index level privileges that the owners of the role have on the + * specified indices. + */ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional search query that defines the documents the owners of the role have read access + * to. A document within the specified indices must match this query in order for it to be + * accessible by the owners of the role. + */ + query: schema.maybe(schema.string()), + + /** + * An optional flag used to indicate if index pattern wildcards or regexps should cover + * restricted indices. + */ + allow_restricted_indices: schema.maybe(schema.boolean()), + }) + ) + ), + + /** + * An optional list of remote indices permissions entries. + */ + remote_indices: schema.maybe( + schema.arrayOf( + schema.object({ + /** + * Required list of remote clusters to which the permissions in this entry apply. + */ + clusters: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * Required list of remote indices (or index name patterns) to which the permissions in this + * entry apply. + */ + names: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional set of the document fields that the owners of the role have read access to. + */ + field_security: schema.maybe( + schema.recordOf( + schema.oneOf([schema.literal('grant'), schema.literal('except')]), + schema.arrayOf(schema.string()) + ) + ), + + /** + * Required list of the index level privileges that the owners of the role have on the + * specified indices. + */ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional search query that defines the documents the owners of the role have read access + * to. A document within the specified indices must match this query in order for it to be + * accessible by the owners of the role. + */ + query: schema.maybe(schema.string()), + + /** + * An optional flag used to indicate if index pattern wildcards or regexps should cover + * restricted indices. + */ + allow_restricted_indices: schema.maybe(schema.boolean()), + }) + ) + ), + + /** + * An optional list of users that the owners of this role can impersonate. + */ + run_as: schema.maybe(schema.arrayOf(schema.string())), +}); + +const allSpacesSchema = schema.arrayOf(schema.literal(GLOBAL_RESOURCE), { + minSize: 1, + maxSize: 1, +}); + +/** + * Schema for the list of space IDs used within Kibana specific role definition. + */ +const spacesSchema = schema.oneOf( + [ + allSpacesSchema, + schema.arrayOf( + schema.string({ + validate(value) { + if (!/^[a-z0-9_-]+$/.test(value)) { + return `must be lower case, a-z, 0-9, '_', and '-' are allowed`; + } + }, + }) + ), + ], + { defaultValue: [GLOBAL_RESOURCE] } +); + +const FEATURE_NAME_VALUE_REGEX = /^[a-zA-Z0-9_-]+$/; + +/** + * Kibana specific portion of the role definition. It's represented as a list of base and/or + * feature Kibana privileges. None of the entries should apply to the same spaces. + */ +export const getKibanaRoleSchema = ( + getBasePrivilegeNames: () => { global: string[]; space: string[] } +) => + schema.arrayOf( + schema.object( + { + /** + * An optional list of space IDs to which the permissions in this entry apply. If not + * specified it defaults to special "global" space ID (all spaces). + */ + spaces: spacesSchema, + + /** + * An optional list of Kibana base privileges. If this entry applies to special "global" + * space (all spaces) then specified base privileges should be within known base "global" + * privilege list, otherwise - within known "space" privilege list. Base privileges + * definition isn't allowed when feature privileges are defined and required otherwise. + */ + base: schema.maybe( + schema.conditional( + schema.siblingRef('spaces'), + allSpacesSchema, + schema.arrayOf( + schema.string({ + validate(value) { + const globalPrivileges = getBasePrivilegeNames().global; + if (!globalPrivileges.some((privilege) => privilege === value)) { + return `unknown global privilege "${value}", must be one of [${globalPrivileges}]`; + } + }, + }) + ), + schema.arrayOf( + schema.string({ + validate(value) { + const spacePrivileges = getBasePrivilegeNames().space; + if (!spacePrivileges.some((privilege) => privilege === value)) { + return `unknown space privilege "${value}", must be one of [${spacePrivileges}]`; + } + }, + }) + ) + ) + ), + + /** + * An optional dictionary of Kibana feature privileges where the key is the ID of the + * feature and the value is a list of feature specific privilege IDs. Both feature and + * privilege IDs should consist of allowed set of characters. Feature privileges + * definition isn't allowed when base privileges are defined and required otherwise. + */ + feature: schema.maybe( + schema.recordOf( + schema.string({ + validate(value) { + if (!FEATURE_NAME_VALUE_REGEX.test(value)) { + return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; + } + }, + }), + schema.arrayOf( + schema.string({ + validate(value) { + if (!FEATURE_NAME_VALUE_REGEX.test(value)) { + return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; + } + }, + }) + ) + ) + ), + }, + { + validate(value) { + if ( + (value.base === undefined || value.base.length === 0) && + (value.feature === undefined || Object.values(value.feature).flat().length === 0) + ) { + return 'either [base] or [feature] is expected, but none of them specified'; + } + + if ( + value.base !== undefined && + value.base.length > 0 && + value.feature !== undefined && + Object.keys(value.feature).length > 0 + ) { + return `definition of [feature] isn't allowed when non-empty [base] is defined.`; + } + }, + } + ), + { + validate(value) { + for (const [indexA, valueA] of value.entries()) { + for (const valueB of value.slice(indexA + 1)) { + const spaceIntersection = _.intersection(valueA.spaces, valueB.spaces); + if (spaceIntersection.length !== 0) { + return `more than one privilege is applied to the following spaces: [${spaceIntersection}]`; + } + } + } + }, + } + ); + +export type ElasticsearchPrivilegesType = TypeOf; +export type KibanaPrivilegesType = TypeOf>; diff --git a/x-pack/packages/security/plugin_types_server/index.ts b/x-pack/packages/security/plugin_types_server/index.ts index 1228b9d36f96..c014fe58aff3 100644 --- a/x-pack/packages/security/plugin_types_server/index.ts +++ b/x-pack/packages/security/plugin_types_server/index.ts @@ -79,4 +79,9 @@ export { updateRestApiKeySchema, updateCrossClusterApiKeySchema, } from './src/authentication'; -export { GLOBAL_RESOURCE, elasticsearchRoleSchema, getKibanaRoleSchema } from './src/authorization'; + +export { + GLOBAL_RESOURCE, + getKibanaRoleSchema, + elasticsearchRoleSchema, +} from '@kbn/security-plugin-types-common'; 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 2ced5478b46e..c4f59a2e5938 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 @@ -7,201 +7,9 @@ import type { estypes } from '@elastic/elasticsearch'; -import type { KibanaRequest } from '@kbn/core/server'; import { schema, TypeOf } 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(), - 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 getRestApiKeyWithKibanaPrivilegesSchema = ( - getBasePrivilegeNames: Parameters[0] -) => - restApiKeySchema.extends({ - role_descriptors: null, - kibana_role_descriptors: schema.recordOf( - schema.string(), - schema.object({ - elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), - kibana: getKibanaRoleSchema(getBasePrivilegeNames), - }) - ), - }); - -export const crossClusterApiKeySchema = restApiKeySchema.extends({ - type: schema.literal('cross_cluster'), - role_descriptors: null, - 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' } - ), -}); +import { restApiKeySchema, crossClusterApiKeySchema } from '@kbn/core-security-common'; +import { getKibanaRoleSchema, elasticsearchRoleSchema } from '@kbn/security-plugin-types-common'; /** * Response of Kibana Update API key endpoint. 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 ec36a99b4da6..02b880ac1fca 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 @@ -6,16 +6,6 @@ */ export type { - CreateAPIKeyParams, - CreateAPIKeyResult, - InvalidateAPIKeyResult, - InvalidateAPIKeysParams, - ValidateAPIKeyParams, - CreateRestAPIKeyParams, - CreateRestAPIKeyWithKibanaPrivilegesParams, - CreateCrossClusterAPIKeyParams, - GrantAPIKeyResult, - APIKeys, UpdateAPIKeyParams, UpdateAPIKeyResult, UpdateCrossClusterAPIKeyParams, @@ -23,10 +13,25 @@ export type { UpdateRestAPIKeyWithKibanaPrivilegesParams, } from './api_keys'; export { - crossClusterApiKeySchema, - getRestApiKeyWithKibanaPrivilegesSchema, getUpdateRestApiKeyWithKibanaPrivilegesSchema, - restApiKeySchema, updateRestApiKeySchema, updateCrossClusterApiKeySchema, } from './api_keys'; + +export { + restApiKeySchema, + crossClusterApiKeySchema, + getRestApiKeyWithKibanaPrivilegesSchema, +} from '@kbn/core-security-common'; + +export type { + CreateAPIKeyParams, + CreateAPIKeyResult, + InvalidateAPIKeyResult, + InvalidateAPIKeysParams, + ValidateAPIKeyParams, + CreateRestAPIKeyParams, + CreateRestAPIKeyWithKibanaPrivilegesParams, + CreateCrossClusterAPIKeyParams, + GrantAPIKeyResult, +} from '@kbn/core-security-common'; 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 5dc8827786a4..e54bc2c1ab98 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 @@ -7,13 +7,12 @@ import type { KibanaRequest } from '@kbn/core/server'; import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; - -import type { APIKeys } from './api_keys'; +import type { APIKeysService } from '@kbn/core-security-common'; /** * 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 6e30f9ebcec2..e3c9fa94b440 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 @@ -14,7 +14,6 @@ export type { InvalidateAPIKeyResult, InvalidateAPIKeysParams, ValidateAPIKeyParams, - APIKeys, GrantAPIKeyResult, UpdateAPIKeyParams, UpdateAPIKeyResult, @@ -31,3 +30,5 @@ export { updateRestApiKeySchema, updateCrossClusterApiKeySchema, } from './api_keys'; + +export type { APIKeysService as APIKeys } from '@kbn/core-security-common'; 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 54364d7817f3..f1df636aec27 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,12 @@ 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'; +export type { + ElasticsearchPrivilegesType, + KibanaPrivilegesType, +} from '@kbn/security-plugin-types-common'; +export { + getKibanaRoleSchema, + elasticsearchRoleSchema, + GLOBAL_RESOURCE, +} from '@kbn/security-plugin-types-common'; diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts b/x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts deleted file mode 100644 index 3d673fa25dc5..000000000000 --- a/x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts +++ /dev/null @@ -1,274 +0,0 @@ -/* - * 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 _ from 'lodash'; - -import type { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; - -import { GLOBAL_RESOURCE } from './constants'; - -/** - * Elasticsearch specific portion of the role definition. - * See more details at https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api.html#security-role-apis. - */ -export const elasticsearchRoleSchema = schema.object({ - /** - * An optional list of cluster privileges. These privileges define the cluster level actions that - * users with this role are able to execute - */ - cluster: schema.maybe(schema.arrayOf(schema.string())), - - /** - * An optional list of remote cluster privileges. These privileges define the remote cluster level actions that - * users with this role are able to execute - */ - remote_cluster: schema.maybe( - schema.arrayOf( - schema.object({ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - clusters: schema.arrayOf(schema.string(), { minSize: 1 }), - }) - ) - ), - - /** - * An optional list of indices permissions entries. - */ - indices: schema.maybe( - schema.arrayOf( - schema.object({ - /** - * Required list of indices (or index name patterns) to which the permissions in this - * entry apply. - */ - names: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional set of the document fields that the owners of the role have read access to. - */ - field_security: schema.maybe( - schema.recordOf( - schema.oneOf([schema.literal('grant'), schema.literal('except')]), - schema.arrayOf(schema.string()) - ) - ), - - /** - * Required list of the index level privileges that the owners of the role have on the - * specified indices. - */ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional search query that defines the documents the owners of the role have read access - * to. A document within the specified indices must match this query in order for it to be - * accessible by the owners of the role. - */ - query: schema.maybe(schema.string()), - - /** - * An optional flag used to indicate if index pattern wildcards or regexps should cover - * restricted indices. - */ - allow_restricted_indices: schema.maybe(schema.boolean()), - }) - ) - ), - - /** - * An optional list of remote indices permissions entries. - */ - remote_indices: schema.maybe( - schema.arrayOf( - schema.object({ - /** - * Required list of remote clusters to which the permissions in this entry apply. - */ - clusters: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * Required list of remote indices (or index name patterns) to which the permissions in this - * entry apply. - */ - names: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional set of the document fields that the owners of the role have read access to. - */ - field_security: schema.maybe( - schema.recordOf( - schema.oneOf([schema.literal('grant'), schema.literal('except')]), - schema.arrayOf(schema.string()) - ) - ), - - /** - * Required list of the index level privileges that the owners of the role have on the - * specified indices. - */ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional search query that defines the documents the owners of the role have read access - * to. A document within the specified indices must match this query in order for it to be - * accessible by the owners of the role. - */ - query: schema.maybe(schema.string()), - - /** - * An optional flag used to indicate if index pattern wildcards or regexps should cover - * restricted indices. - */ - allow_restricted_indices: schema.maybe(schema.boolean()), - }) - ) - ), - - /** - * An optional list of users that the owners of this role can impersonate. - */ - run_as: schema.maybe(schema.arrayOf(schema.string())), -}); - -const allSpacesSchema = schema.arrayOf(schema.literal(GLOBAL_RESOURCE), { - minSize: 1, - maxSize: 1, -}); - -/** - * Schema for the list of space IDs used within Kibana specific role definition. - */ -const spacesSchema = schema.oneOf( - [ - allSpacesSchema, - schema.arrayOf( - schema.string({ - validate(value) { - if (!/^[a-z0-9_-]+$/.test(value)) { - return `must be lower case, a-z, 0-9, '_', and '-' are allowed`; - } - }, - }) - ), - ], - { defaultValue: [GLOBAL_RESOURCE] } -); - -const FEATURE_NAME_VALUE_REGEX = /^[a-zA-Z0-9_-]+$/; - -/** - * Kibana specific portion of the role definition. It's represented as a list of base and/or - * feature Kibana privileges. None of the entries should apply to the same spaces. - */ -export const getKibanaRoleSchema = ( - getBasePrivilegeNames: () => { global: string[]; space: string[] } -) => - schema.arrayOf( - schema.object( - { - /** - * An optional list of space IDs to which the permissions in this entry apply. If not - * specified it defaults to special "global" space ID (all spaces). - */ - spaces: spacesSchema, - - /** - * An optional list of Kibana base privileges. If this entry applies to special "global" - * space (all spaces) then specified base privileges should be within known base "global" - * privilege list, otherwise - within known "space" privilege list. Base privileges - * definition isn't allowed when feature privileges are defined and required otherwise. - */ - base: schema.maybe( - schema.conditional( - schema.siblingRef('spaces'), - allSpacesSchema, - schema.arrayOf( - schema.string({ - validate(value) { - const globalPrivileges = getBasePrivilegeNames().global; - if (!globalPrivileges.some((privilege) => privilege === value)) { - return `unknown global privilege "${value}", must be one of [${globalPrivileges}]`; - } - }, - }) - ), - schema.arrayOf( - schema.string({ - validate(value) { - const spacePrivileges = getBasePrivilegeNames().space; - if (!spacePrivileges.some((privilege) => privilege === value)) { - return `unknown space privilege "${value}", must be one of [${spacePrivileges}]`; - } - }, - }) - ) - ) - ), - - /** - * An optional dictionary of Kibana feature privileges where the key is the ID of the - * feature and the value is a list of feature specific privilege IDs. Both feature and - * privilege IDs should consist of allowed set of characters. Feature privileges - * definition isn't allowed when base privileges are defined and required otherwise. - */ - feature: schema.maybe( - schema.recordOf( - schema.string({ - validate(value) { - if (!FEATURE_NAME_VALUE_REGEX.test(value)) { - return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; - } - }, - }), - schema.arrayOf( - schema.string({ - validate(value) { - if (!FEATURE_NAME_VALUE_REGEX.test(value)) { - return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; - } - }, - }) - ) - ) - ), - }, - { - validate(value) { - if ( - (value.base === undefined || value.base.length === 0) && - (value.feature === undefined || Object.values(value.feature).flat().length === 0) - ) { - return 'either [base] or [feature] is expected, but none of them specified'; - } - - if ( - value.base !== undefined && - value.base.length > 0 && - value.feature !== undefined && - Object.keys(value.feature).length > 0 - ) { - return `definition of [feature] isn't allowed when non-empty [base] is defined.`; - } - }, - } - ), - { - validate(value) { - for (const [indexA, valueA] of value.entries()) { - for (const valueB of value.slice(indexA + 1)) { - const spaceIntersection = _.intersection(valueA.spaces, valueB.spaces); - if (spaceIntersection.length !== 0) { - return `more than one privilege is applied to the following spaces: [${spaceIntersection}]`; - } - } - } - }, - } - ); - -export type ElasticsearchPrivilegesType = TypeOf; -export type KibanaPrivilegesType = TypeOf>; From 3c0f0c8093e6f07713d20a0be700398d577e404a Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 25 Jun 2024 19:44:05 +0000 Subject: [PATCH 03/45] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/core/security/core-security-common/tsconfig.json | 6 +++++- x-pack/packages/security/plugin_types_common/tsconfig.json | 3 ++- x-pack/packages/security/plugin_types_server/tsconfig.json | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core/security/core-security-common/tsconfig.json b/packages/core/security/core-security-common/tsconfig.json index b05325b824a6..b59b950eb869 100644 --- a/packages/core/security/core-security-common/tsconfig.json +++ b/packages/core/security/core-security-common/tsconfig.json @@ -15,5 +15,9 @@ "exclude": [ "target/**/*" ], - "kbn_references": [] + "kbn_references": [ + "@kbn/core", + "@kbn/config-schema", + "@kbn/security-plugin-types-common", + ] } diff --git a/x-pack/packages/security/plugin_types_common/tsconfig.json b/x-pack/packages/security/plugin_types_common/tsconfig.json index 353f6770352a..b1befe45cd4a 100644 --- a/x-pack/packages/security/plugin_types_common/tsconfig.json +++ b/x-pack/packages/security/plugin_types_common/tsconfig.json @@ -12,6 +12,7 @@ "kbn_references": [ "@kbn/licensing-plugin", "@kbn/core-security-common", - "@kbn/core-user-profile-common" + "@kbn/core-user-profile-common", + "@kbn/config-schema" ] } diff --git a/x-pack/packages/security/plugin_types_server/tsconfig.json b/x-pack/packages/security/plugin_types_server/tsconfig.json index 51a1cf53c62b..c54d8780af93 100644 --- a/x-pack/packages/security/plugin_types_server/tsconfig.json +++ b/x-pack/packages/security/plugin_types_server/tsconfig.json @@ -15,5 +15,6 @@ "@kbn/security-plugin-types-common", "@kbn/core-user-profile-server", "@kbn/core-security-server", + "@kbn/core-security-common", ] } From eb130ebfcd1f901ea966fea53a51e60ea3988d44 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Tue, 25 Jun 2024 21:58:12 +0200 Subject: [PATCH 04/45] remove unused types --- x-pack/plugins/security/server/api_keys/api_keys_service.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/x-pack/plugins/security/server/api_keys/api_keys_service.ts b/x-pack/plugins/security/server/api_keys/api_keys_service.ts index a1cd61adf8fc..c4d0f37b48a4 100644 --- a/x-pack/plugins/security/server/api_keys/api_keys_service.ts +++ b/x-pack/plugins/security/server/api_keys/api_keys_service.ts @@ -11,12 +11,6 @@ import type { SecurityLicense } from '@kbn/security-plugin-types-common'; import { APIKeys } from '../authentication/api_keys'; -// import type { UpdateAPIKeyParams, UpdateAPIKeyResult } from '../../routes/api_keys'; - -// export type { UpdateAPIKeyParams, UpdateAPIKeyResult }; - -const ELASTICSEARCH_CLIENT_AUTHENTICATION_HEADER = 'es-client-authentication'; - /** * Represents the options to create an APIKey class instance that will be * shared between functions (create, invalidate, etc). From 99ba029a39800371779e63bc969519f77c3d5a39 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 09:27:56 +0200 Subject: [PATCH 05/45] update jest test for api keys --- .../security/server/authentication/api_keys/api_keys.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7c63ace9fc70..e790676cad2d 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 @@ -45,11 +45,11 @@ describe('API Keys', () => { logger = loggingSystemMock.create().get('api-keys'); apiKeys = new APIKeys({ - clusterClient: mockClusterClient, + getClusterClient: () => Promise.resolve(mockClusterClient), logger, license: mockLicense, applicationName: 'kibana-.kibana', - kibanaFeatures: [], + getKibanaFeatures: () => Promise.resolve([]), }); }); From 73dc2f02f8a58f65e11a1802a18d90662797e450 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 10:46:02 +0200 Subject: [PATCH 06/45] remove cyclical ts deps, update tsdocs partial --- .../security/core-security-common/index.ts | 3 + .../src/api_keys/api_keys.ts | 9 +- .../core-security-common/src/roles/index.ts | 10 + .../core-security-common/src/roles/schema.ts | 274 ++++++++++++++++++ .../core-security-common/tsconfig.json | 21 +- .../security/plugin_types_common/index.ts | 3 - .../src/authorization/index.ts | 3 - .../src/authorization/role.ts | 267 ----------------- .../security/plugin_types_server/index.ts | 7 +- .../src/authentication/api_keys/api_keys.ts | 8 +- .../src/authorization/index.ts | 9 - .../authorization/roles/model/put_payload.ts | 2 +- 12 files changed, 309 insertions(+), 307 deletions(-) create mode 100644 packages/core/security/core-security-common/src/roles/index.ts create mode 100644 packages/core/security/core-security-common/src/roles/schema.ts diff --git a/packages/core/security/core-security-common/index.ts b/packages/core/security/core-security-common/index.ts index 9c946e676a4f..4ecbbf67150c 100644 --- a/packages/core/security/core-security-common/index.ts +++ b/packages/core/security/core-security-common/index.ts @@ -30,3 +30,6 @@ export { crossClusterApiKeySchema, getRestApiKeyWithKibanaPrivilegesSchema, } from './src/api_keys'; + +export { elasticsearchRoleSchema, getKibanaRoleSchema, GLOBAL_RESOURCE } from './src/roles'; +export type { KibanaPrivilegesType, ElasticsearchPrivilegesType } from './src/roles'; diff --git a/packages/core/security/core-security-common/src/api_keys/api_keys.ts b/packages/core/security/core-security-common/src/api_keys/api_keys.ts index 1801fb37a74b..4ceac0b45816 100644 --- a/packages/core/security/core-security-common/src/api_keys/api_keys.ts +++ b/packages/core/security/core-security-common/src/api_keys/api_keys.ts @@ -10,8 +10,11 @@ import type { estypes } from '@elastic/elasticsearch'; import type { KibanaRequest } from '@kbn/core/server'; import { schema, TypeOf } from '@kbn/config-schema'; -import { getKibanaRoleSchema, elasticsearchRoleSchema } from '@kbn/security-plugin-types-common'; +import { getKibanaRoleSchema, elasticsearchRoleSchema } 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. @@ -121,6 +124,9 @@ export interface ValidateAPIKeyParams { * Represents the params for invalidating multiple API keys */ export interface InvalidateAPIKeysParams { + /** + * List of unique API key IDs + */ ids: string[]; } @@ -163,6 +169,7 @@ export const restApiKeySchema = schema.object({ metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), }); +/** */ export const getRestApiKeyWithKibanaPrivilegesSchema = ( getBasePrivilegeNames: Parameters[0] ) => diff --git a/packages/core/security/core-security-common/src/roles/index.ts b/packages/core/security/core-security-common/src/roles/index.ts new file mode 100644 index 000000000000..ebea32d00410 --- /dev/null +++ b/packages/core/security/core-security-common/src/roles/index.ts @@ -0,0 +1,10 @@ +/* + * 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 { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './schema'; +export type { ElasticsearchPrivilegesType, KibanaPrivilegesType } from './schema'; diff --git a/packages/core/security/core-security-common/src/roles/schema.ts b/packages/core/security/core-security-common/src/roles/schema.ts new file mode 100644 index 000000000000..e853b043cb61 --- /dev/null +++ b/packages/core/security/core-security-common/src/roles/schema.ts @@ -0,0 +1,274 @@ +/* + * 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 _ from 'lodash'; + +import type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; + +export const GLOBAL_RESOURCE = '*'; +/** + * Elasticsearch specific portion of the role definition. + * See more details at https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api.html#security-role-apis. + */ +export const elasticsearchRoleSchema = schema.object({ + /** + * An optional list of cluster privileges. These privileges define the cluster level actions that + * users with this role are able to execute + */ + cluster: schema.maybe(schema.arrayOf(schema.string())), + + /** + * An optional list of remote cluster privileges. These privileges define the remote cluster level actions that + * users with this role are able to execute + */ + remote_cluster: schema.maybe( + schema.arrayOf( + schema.object({ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + clusters: schema.arrayOf(schema.string(), { minSize: 1 }), + }) + ) + ), + + /** + * An optional list of indices permissions entries. + */ + indices: schema.maybe( + schema.arrayOf( + schema.object({ + /** + * Required list of indices (or index name patterns) to which the permissions in this + * entry apply. + */ + names: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional set of the document fields that the owners of the role have read access to. + */ + field_security: schema.maybe( + schema.recordOf( + schema.oneOf([schema.literal('grant'), schema.literal('except')]), + schema.arrayOf(schema.string()) + ) + ), + + /** + * Required list of the index level privileges that the owners of the role have on the + * specified indices. + */ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional search query that defines the documents the owners of the role have read access + * to. A document within the specified indices must match this query in order for it to be + * accessible by the owners of the role. + */ + query: schema.maybe(schema.string()), + + /** + * An optional flag used to indicate if index pattern wildcards or regexps should cover + * restricted indices. + */ + allow_restricted_indices: schema.maybe(schema.boolean()), + }) + ) + ), + + /** + * An optional list of remote indices permissions entries. + */ + remote_indices: schema.maybe( + schema.arrayOf( + schema.object({ + /** + * Required list of remote clusters to which the permissions in this entry apply. + */ + clusters: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * Required list of remote indices (or index name patterns) to which the permissions in this + * entry apply. + */ + names: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional set of the document fields that the owners of the role have read access to. + */ + field_security: schema.maybe( + schema.recordOf( + schema.oneOf([schema.literal('grant'), schema.literal('except')]), + schema.arrayOf(schema.string()) + ) + ), + + /** + * Required list of the index level privileges that the owners of the role have on the + * specified indices. + */ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional search query that defines the documents the owners of the role have read access + * to. A document within the specified indices must match this query in order for it to be + * accessible by the owners of the role. + */ + query: schema.maybe(schema.string()), + + /** + * An optional flag used to indicate if index pattern wildcards or regexps should cover + * restricted indices. + */ + allow_restricted_indices: schema.maybe(schema.boolean()), + }) + ) + ), + + /** + * An optional list of users that the owners of this role can impersonate. + */ + run_as: schema.maybe(schema.arrayOf(schema.string())), +}); + +const allSpacesSchema = schema.arrayOf(schema.literal(GLOBAL_RESOURCE), { + minSize: 1, + maxSize: 1, +}); + +/** + * Schema for the list of space IDs used within Kibana specific role definition. + */ +const spacesSchema = schema.oneOf( + [ + allSpacesSchema, + schema.arrayOf( + schema.string({ + validate(value) { + if (!/^[a-z0-9_-]+$/.test(value)) { + return `must be lower case, a-z, 0-9, '_', and '-' are allowed`; + } + }, + }) + ), + ], + { defaultValue: [GLOBAL_RESOURCE] } +); + +const FEATURE_NAME_VALUE_REGEX = /^[a-zA-Z0-9_-]+$/; + +/** + * Kibana specific portion of the role definition. It's represented as a list of base and/or + * feature Kibana privileges. None of the entries should apply to the same spaces. + */ +export const getKibanaRoleSchema = ( + getBasePrivilegeNames: () => { global: string[]; space: string[] } +) => + schema.arrayOf( + schema.object( + { + /** + * An optional list of space IDs to which the permissions in this entry apply. If not + * specified it defaults to special "global" space ID (all spaces). + */ + spaces: spacesSchema, + + /** + * An optional list of Kibana base privileges. If this entry applies to special "global" + * space (all spaces) then specified base privileges should be within known base "global" + * privilege list, otherwise - within known "space" privilege list. Base privileges + * definition isn't allowed when feature privileges are defined and required otherwise. + */ + base: schema.maybe( + schema.conditional( + schema.siblingRef('spaces'), + allSpacesSchema, + schema.arrayOf( + schema.string({ + validate(value) { + const globalPrivileges = getBasePrivilegeNames().global; + if (!globalPrivileges.some((privilege) => privilege === value)) { + return `unknown global privilege "${value}", must be one of [${globalPrivileges}]`; + } + }, + }) + ), + schema.arrayOf( + schema.string({ + validate(value) { + const spacePrivileges = getBasePrivilegeNames().space; + if (!spacePrivileges.some((privilege) => privilege === value)) { + return `unknown space privilege "${value}", must be one of [${spacePrivileges}]`; + } + }, + }) + ) + ) + ), + + /** + * An optional dictionary of Kibana feature privileges where the key is the ID of the + * feature and the value is a list of feature specific privilege IDs. Both feature and + * privilege IDs should consist of allowed set of characters. Feature privileges + * definition isn't allowed when base privileges are defined and required otherwise. + */ + feature: schema.maybe( + schema.recordOf( + schema.string({ + validate(value) { + if (!FEATURE_NAME_VALUE_REGEX.test(value)) { + return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; + } + }, + }), + schema.arrayOf( + schema.string({ + validate(value) { + if (!FEATURE_NAME_VALUE_REGEX.test(value)) { + return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; + } + }, + }) + ) + ) + ), + }, + { + validate(value) { + if ( + (value.base === undefined || value.base.length === 0) && + (value.feature === undefined || Object.values(value.feature).flat().length === 0) + ) { + return 'either [base] or [feature] is expected, but none of them specified'; + } + + if ( + value.base !== undefined && + value.base.length > 0 && + value.feature !== undefined && + Object.keys(value.feature).length > 0 + ) { + return `definition of [feature] isn't allowed when non-empty [base] is defined.`; + } + }, + } + ), + { + validate(value) { + for (const [indexA, valueA] of value.entries()) { + for (const valueB of value.slice(indexA + 1)) { + const spaceIntersection = _.intersection(valueA.spaces, valueB.spaces); + if (spaceIntersection.length !== 0) { + return `more than one privilege is applied to the following spaces: [${spaceIntersection}]`; + } + } + } + }, + } + ); + +export type ElasticsearchPrivilegesType = TypeOf; +export type KibanaPrivilegesType = TypeOf>; diff --git a/packages/core/security/core-security-common/tsconfig.json b/packages/core/security/core-security-common/tsconfig.json index b59b950eb869..0641e55b7d33 100644 --- a/packages/core/security/core-security-common/tsconfig.json +++ b/packages/core/security/core-security-common/tsconfig.json @@ -2,22 +2,9 @@ "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", - "types": [ - "jest", - "node", - "react" - ] + "types": ["jest", "node", "react"] }, - "include": [ - "**/*.ts", - "**/*.tsx", - ], - "exclude": [ - "target/**/*" - ], - "kbn_references": [ - "@kbn/core", - "@kbn/config-schema", - "@kbn/security-plugin-types-common", - ] + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["target/**/*"], + "kbn_references": ["@kbn/core", "@kbn/config-schema"] } diff --git a/x-pack/packages/security/plugin_types_common/index.ts b/x-pack/packages/security/plugin_types_common/index.ts index db5715c8d364..8dd0ff726103 100644 --- a/x-pack/packages/security/plugin_types_common/index.ts +++ b/x-pack/packages/security/plugin_types_common/index.ts @@ -18,10 +18,7 @@ export type { RoleRemoteIndexPrivilege, RoleRemoteClusterPrivilege, FeaturesPrivileges, - ElasticsearchPrivilegesType, - KibanaPrivilegesType, } from './src/authorization'; -export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './src/authorization'; export type { SecurityLicense, SecurityLicenseFeatures, LoginLayout } from './src/licensing'; export type { UserProfileUserInfo, diff --git a/x-pack/packages/security/plugin_types_common/src/authorization/index.ts b/x-pack/packages/security/plugin_types_common/src/authorization/index.ts index eb8c8e9b632a..89857a18865a 100644 --- a/x-pack/packages/security/plugin_types_common/src/authorization/index.ts +++ b/x-pack/packages/security/plugin_types_common/src/authorization/index.ts @@ -12,7 +12,4 @@ export type { RoleIndexPrivilege, RoleRemoteIndexPrivilege, RoleRemoteClusterPrivilege, - ElasticsearchPrivilegesType, - KibanaPrivilegesType, } from './role'; -export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './role'; diff --git a/x-pack/packages/security/plugin_types_common/src/authorization/role.ts b/x-pack/packages/security/plugin_types_common/src/authorization/role.ts index d664823c7325..3a20b64d4d06 100644 --- a/x-pack/packages/security/plugin_types_common/src/authorization/role.ts +++ b/x-pack/packages/security/plugin_types_common/src/authorization/role.ts @@ -4,10 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import _ from 'lodash'; - -import type { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; import type { FeaturesPrivileges } from './features_privileges'; @@ -57,266 +53,3 @@ export interface Role { _transform_error?: string[]; _unrecognized_applications?: string[]; } - -export const GLOBAL_RESOURCE = '*'; -/** - * Elasticsearch specific portion of the role definition. - * See more details at https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api.html#security-role-apis. - */ -export const elasticsearchRoleSchema = schema.object({ - /** - * An optional list of cluster privileges. These privileges define the cluster level actions that - * users with this role are able to execute - */ - cluster: schema.maybe(schema.arrayOf(schema.string())), - - /** - * An optional list of remote cluster privileges. These privileges define the remote cluster level actions that - * users with this role are able to execute - */ - remote_cluster: schema.maybe( - schema.arrayOf( - schema.object({ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - clusters: schema.arrayOf(schema.string(), { minSize: 1 }), - }) - ) - ), - - /** - * An optional list of indices permissions entries. - */ - indices: schema.maybe( - schema.arrayOf( - schema.object({ - /** - * Required list of indices (or index name patterns) to which the permissions in this - * entry apply. - */ - names: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional set of the document fields that the owners of the role have read access to. - */ - field_security: schema.maybe( - schema.recordOf( - schema.oneOf([schema.literal('grant'), schema.literal('except')]), - schema.arrayOf(schema.string()) - ) - ), - - /** - * Required list of the index level privileges that the owners of the role have on the - * specified indices. - */ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional search query that defines the documents the owners of the role have read access - * to. A document within the specified indices must match this query in order for it to be - * accessible by the owners of the role. - */ - query: schema.maybe(schema.string()), - - /** - * An optional flag used to indicate if index pattern wildcards or regexps should cover - * restricted indices. - */ - allow_restricted_indices: schema.maybe(schema.boolean()), - }) - ) - ), - - /** - * An optional list of remote indices permissions entries. - */ - remote_indices: schema.maybe( - schema.arrayOf( - schema.object({ - /** - * Required list of remote clusters to which the permissions in this entry apply. - */ - clusters: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * Required list of remote indices (or index name patterns) to which the permissions in this - * entry apply. - */ - names: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional set of the document fields that the owners of the role have read access to. - */ - field_security: schema.maybe( - schema.recordOf( - schema.oneOf([schema.literal('grant'), schema.literal('except')]), - schema.arrayOf(schema.string()) - ) - ), - - /** - * Required list of the index level privileges that the owners of the role have on the - * specified indices. - */ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional search query that defines the documents the owners of the role have read access - * to. A document within the specified indices must match this query in order for it to be - * accessible by the owners of the role. - */ - query: schema.maybe(schema.string()), - - /** - * An optional flag used to indicate if index pattern wildcards or regexps should cover - * restricted indices. - */ - allow_restricted_indices: schema.maybe(schema.boolean()), - }) - ) - ), - - /** - * An optional list of users that the owners of this role can impersonate. - */ - run_as: schema.maybe(schema.arrayOf(schema.string())), -}); - -const allSpacesSchema = schema.arrayOf(schema.literal(GLOBAL_RESOURCE), { - minSize: 1, - maxSize: 1, -}); - -/** - * Schema for the list of space IDs used within Kibana specific role definition. - */ -const spacesSchema = schema.oneOf( - [ - allSpacesSchema, - schema.arrayOf( - schema.string({ - validate(value) { - if (!/^[a-z0-9_-]+$/.test(value)) { - return `must be lower case, a-z, 0-9, '_', and '-' are allowed`; - } - }, - }) - ), - ], - { defaultValue: [GLOBAL_RESOURCE] } -); - -const FEATURE_NAME_VALUE_REGEX = /^[a-zA-Z0-9_-]+$/; - -/** - * Kibana specific portion of the role definition. It's represented as a list of base and/or - * feature Kibana privileges. None of the entries should apply to the same spaces. - */ -export const getKibanaRoleSchema = ( - getBasePrivilegeNames: () => { global: string[]; space: string[] } -) => - schema.arrayOf( - schema.object( - { - /** - * An optional list of space IDs to which the permissions in this entry apply. If not - * specified it defaults to special "global" space ID (all spaces). - */ - spaces: spacesSchema, - - /** - * An optional list of Kibana base privileges. If this entry applies to special "global" - * space (all spaces) then specified base privileges should be within known base "global" - * privilege list, otherwise - within known "space" privilege list. Base privileges - * definition isn't allowed when feature privileges are defined and required otherwise. - */ - base: schema.maybe( - schema.conditional( - schema.siblingRef('spaces'), - allSpacesSchema, - schema.arrayOf( - schema.string({ - validate(value) { - const globalPrivileges = getBasePrivilegeNames().global; - if (!globalPrivileges.some((privilege) => privilege === value)) { - return `unknown global privilege "${value}", must be one of [${globalPrivileges}]`; - } - }, - }) - ), - schema.arrayOf( - schema.string({ - validate(value) { - const spacePrivileges = getBasePrivilegeNames().space; - if (!spacePrivileges.some((privilege) => privilege === value)) { - return `unknown space privilege "${value}", must be one of [${spacePrivileges}]`; - } - }, - }) - ) - ) - ), - - /** - * An optional dictionary of Kibana feature privileges where the key is the ID of the - * feature and the value is a list of feature specific privilege IDs. Both feature and - * privilege IDs should consist of allowed set of characters. Feature privileges - * definition isn't allowed when base privileges are defined and required otherwise. - */ - feature: schema.maybe( - schema.recordOf( - schema.string({ - validate(value) { - if (!FEATURE_NAME_VALUE_REGEX.test(value)) { - return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; - } - }, - }), - schema.arrayOf( - schema.string({ - validate(value) { - if (!FEATURE_NAME_VALUE_REGEX.test(value)) { - return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; - } - }, - }) - ) - ) - ), - }, - { - validate(value) { - if ( - (value.base === undefined || value.base.length === 0) && - (value.feature === undefined || Object.values(value.feature).flat().length === 0) - ) { - return 'either [base] or [feature] is expected, but none of them specified'; - } - - if ( - value.base !== undefined && - value.base.length > 0 && - value.feature !== undefined && - Object.keys(value.feature).length > 0 - ) { - return `definition of [feature] isn't allowed when non-empty [base] is defined.`; - } - }, - } - ), - { - validate(value) { - for (const [indexA, valueA] of value.entries()) { - for (const valueB of value.slice(indexA + 1)) { - const spaceIntersection = _.intersection(valueA.spaces, valueB.spaces); - if (spaceIntersection.length !== 0) { - return `more than one privilege is applied to the following spaces: [${spaceIntersection}]`; - } - } - } - }, - } - ); - -export type ElasticsearchPrivilegesType = TypeOf; -export type KibanaPrivilegesType = TypeOf>; diff --git a/x-pack/packages/security/plugin_types_server/index.ts b/x-pack/packages/security/plugin_types_server/index.ts index c014fe58aff3..f8ae5214e4a1 100644 --- a/x-pack/packages/security/plugin_types_server/index.ts +++ b/x-pack/packages/security/plugin_types_server/index.ts @@ -39,7 +39,6 @@ export type { CheckPrivilegesWithRequest, CheckSavedObjectsPrivilegesWithRequest, CheckPrivilegesDynamicallyWithRequest, - KibanaPrivilegesType, SavedObjectActions, UIActions, CheckPrivilegesPayload, @@ -51,7 +50,6 @@ export type { CheckPrivilegesOptions, CheckUserProfilesPrivilegesPayload, CheckUserProfilesPrivilegesResponse, - ElasticsearchPrivilegesType, CasesActions, CheckPrivileges, AlertingActions, @@ -80,8 +78,9 @@ export { updateCrossClusterApiKeySchema, } from './src/authentication'; +export type { ElasticsearchPrivilegesType, KibanaPrivilegesType } from '@kbn/core-security-common'; export { - GLOBAL_RESOURCE, getKibanaRoleSchema, elasticsearchRoleSchema, -} from '@kbn/security-plugin-types-common'; + GLOBAL_RESOURCE, +} from '@kbn/core-security-common'; 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 c4f59a2e5938..a42b928d8cf3 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 @@ -8,8 +8,12 @@ import type { estypes } from '@elastic/elasticsearch'; import { schema, TypeOf } from '@kbn/config-schema'; -import { restApiKeySchema, crossClusterApiKeySchema } from '@kbn/core-security-common'; -import { getKibanaRoleSchema, elasticsearchRoleSchema } from '@kbn/security-plugin-types-common'; +import { + restApiKeySchema, + crossClusterApiKeySchema, + elasticsearchRoleSchema, + getKibanaRoleSchema, +} from '@kbn/core-security-common'; /** * Response of Kibana Update API key endpoint. 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 f1df636aec27..2e30c8df573f 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,12 +42,3 @@ export type { PrivilegeDeprecationsRolesByFeatureIdResponse, } from './deprecations'; export type { AuthorizationMode } from './mode'; -export type { - ElasticsearchPrivilegesType, - KibanaPrivilegesType, -} from '@kbn/security-plugin-types-common'; -export { - getKibanaRoleSchema, - elasticsearchRoleSchema, - GLOBAL_RESOURCE, -} from '@kbn/security-plugin-types-common'; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts index 52c8178a651a..53064a828224 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { elasticsearchRoleSchema, getKibanaRoleSchema } from '@kbn/security-plugin-types-server'; +import { elasticsearchRoleSchema, getKibanaRoleSchema } from '@kbn/core-security-common'; import type { ElasticsearchRole } from '../../../../authorization'; import { transformPrivilegesToElasticsearchPrivileges } from '../../../../lib'; From 0b13f8d55bd4524965c49b7bbefc8724387d8541 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 11:01:51 +0200 Subject: [PATCH 07/45] update security plugin unit test --- x-pack/plugins/security/server/plugin.test.ts | 410 ++++++++++++++++++ x-pack/plugins/security/server/plugin.ts | 5 +- 2 files changed, 413 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index be3d00b77cff..dac0d2f6963e 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -79,6 +79,416 @@ describe('Security Plugin', () => { }, }, "authc": Object { + "apiKeys": APIKeys { + "applicationName": "kibana-.kibana", + "getClusterClient": [Function], + "getKibanaFeatures": [Function], + "license": Object { + "features$": Observable { + "operator": [Function], + "source": Observable { + "_subscribe": [Function], + }, + }, + "getFeatures": [Function], + "getUnavailableReason": [Function], + "hasAtLeast": [Function], + "isEnabled": [Function], + "isLicenseAvailable": [Function], + }, + "logger": Object { + "context": Array [ + "api-key", + ], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "Registering security_authentication_type event type.", + ], + Array [ + "Registering security_csp_violation event type.", + ], + Array [ + "Registering security_permissions_policy_violation event type.", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction] { + "calls": Array [ + Array [ + "ecs", + ], + Array [ + "ecs", + ], + Array [ + "cookie", + ], + Array [ + "api-key", + ], + Array [ + "deprecations", + ], + Array [ + "deprecations", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Object { + "context": Array [ + "ecs", + ], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "Registering security_authentication_type event type.", + ], + Array [ + "Registering security_csp_violation event type.", + ], + Array [ + "Registering security_permissions_policy_violation event type.", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction] { + "calls": Array [ + Array [ + "ecs", + ], + Array [ + "ecs", + ], + Array [ + "cookie", + ], + Array [ + "api-key", + ], + Array [ + "deprecations", + ], + Array [ + "deprecations", + ], + ], + "results": [Circular], + }, + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + }, + Object { + "type": "return", + "value": Object { + "context": Array [ + "ecs", + ], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "Registering security_authentication_type event type.", + ], + Array [ + "Registering security_csp_violation event type.", + ], + Array [ + "Registering security_permissions_policy_violation event type.", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction] { + "calls": Array [ + Array [ + "ecs", + ], + Array [ + "ecs", + ], + Array [ + "cookie", + ], + Array [ + "api-key", + ], + Array [ + "deprecations", + ], + Array [ + "deprecations", + ], + ], + "results": [Circular], + }, + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + }, + Object { + "type": "return", + "value": Object { + "context": Array [ + "cookie", + ], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "Registering security_authentication_type event type.", + ], + Array [ + "Registering security_csp_violation event type.", + ], + Array [ + "Registering security_permissions_policy_violation event type.", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction] { + "calls": Array [ + Array [ + "ecs", + ], + Array [ + "ecs", + ], + Array [ + "cookie", + ], + Array [ + "api-key", + ], + Array [ + "deprecations", + ], + Array [ + "deprecations", + ], + ], + "results": [Circular], + }, + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + }, + Object { + "type": "return", + "value": [Circular], + }, + Object { + "type": "return", + "value": Object { + "context": Array [ + "deprecations", + ], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "Registering security_authentication_type event type.", + ], + Array [ + "Registering security_csp_violation event type.", + ], + Array [ + "Registering security_permissions_policy_violation event type.", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction] { + "calls": Array [ + Array [ + "ecs", + ], + Array [ + "ecs", + ], + Array [ + "cookie", + ], + Array [ + "api-key", + ], + Array [ + "deprecations", + ], + Array [ + "deprecations", + ], + ], + "results": [Circular], + }, + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + }, + Object { + "type": "return", + "value": Object { + "context": Array [ + "deprecations", + ], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "Registering security_authentication_type event type.", + ], + Array [ + "Registering security_csp_violation event type.", + ], + Array [ + "Registering security_permissions_policy_violation event type.", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction] { + "calls": Array [ + Array [ + "ecs", + ], + Array [ + "ecs", + ], + Array [ + "cookie", + ], + Array [ + "api-key", + ], + Array [ + "deprecations", + ], + Array [ + "deprecations", + ], + ], + "results": [Circular], + }, + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + }, + ], + }, + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + }, "getCurrentUser": [Function], }, "authz": Object { diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 1c6b796b8a5f..b922e7228df9 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -63,6 +63,7 @@ import { registerSecurityUsageCollector } from './usage_collector'; import { UserProfileService } from './user_profile'; import type { UserProfileServiceStartInternal } from './user_profile'; import type { AuthenticatedUser, SecurityLicense } from '../common'; +import { APPLICATION_PREFIX } from '../common/constants'; import { SecurityLicenseService } from '../common/licensing'; export type SpacesService = Pick< @@ -269,12 +270,12 @@ export class SecurityPlugin getCurrentUser, recordAuditLoggingUsage: () => this.getFeatureUsageService().recordAuditLoggingUsage(), }); - + const applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`; const apiKeysSetup = this.apiKeysService.setup({ getClusterClient: () => startServicesPromise.then(({ elasticsearch }) => elasticsearch.client), license, - applicationName: 'SomethingNameSid', + applicationName, getKibanaFeatures: () => startServicesPromise.then((services) => services.features.getKibanaFeatures()), }); From d021dddc9de0a835d13475fd24b9e1f272fdc6c6 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 26 Jun 2024 09:15:12 +0000 Subject: [PATCH 08/45] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/packages/security/plugin_types_common/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/packages/security/plugin_types_common/tsconfig.json b/x-pack/packages/security/plugin_types_common/tsconfig.json index b1befe45cd4a..6e53e4992c31 100644 --- a/x-pack/packages/security/plugin_types_common/tsconfig.json +++ b/x-pack/packages/security/plugin_types_common/tsconfig.json @@ -13,6 +13,5 @@ "@kbn/licensing-plugin", "@kbn/core-security-common", "@kbn/core-user-profile-common", - "@kbn/config-schema" ] } From 1987f8c7472f37175fa30377237bd2157a302adf Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 11:40:09 +0200 Subject: [PATCH 09/45] fix cyclical dependency on kibana/core --- .../core/security/core-security-common/src/api_keys/api_keys.ts | 2 +- packages/core/security/core-security-common/tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/security/core-security-common/src/api_keys/api_keys.ts b/packages/core/security/core-security-common/src/api_keys/api_keys.ts index 4ceac0b45816..5b7d8ad1f9c1 100644 --- a/packages/core/security/core-security-common/src/api_keys/api_keys.ts +++ b/packages/core/security/core-security-common/src/api_keys/api_keys.ts @@ -8,7 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; -import type { KibanaRequest } from '@kbn/core/server'; +import type { KibanaRequest } from '@kbn/core-http-server'; import { schema, TypeOf } from '@kbn/config-schema'; import { getKibanaRoleSchema, elasticsearchRoleSchema } from '../roles'; diff --git a/packages/core/security/core-security-common/tsconfig.json b/packages/core/security/core-security-common/tsconfig.json index 0641e55b7d33..27759c6c98cb 100644 --- a/packages/core/security/core-security-common/tsconfig.json +++ b/packages/core/security/core-security-common/tsconfig.json @@ -6,5 +6,5 @@ }, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["target/**/*"], - "kbn_references": ["@kbn/core", "@kbn/config-schema"] + "kbn_references": ["@kbn/config-schema"] } From 1042962a9598ff7c94bdf8e9b45ba5a88303f210 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 11:46:40 +0200 Subject: [PATCH 10/45] fix formatting --- .../security/core-security-common/src/api_keys/api_keys.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/security/core-security-common/src/api_keys/api_keys.ts b/packages/core/security/core-security-common/src/api_keys/api_keys.ts index 5b7d8ad1f9c1..3ebd40a68299 100644 --- a/packages/core/security/core-security-common/src/api_keys/api_keys.ts +++ b/packages/core/security/core-security-common/src/api_keys/api_keys.ts @@ -13,7 +13,9 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { getKibanaRoleSchema, elasticsearchRoleSchema } 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. + * 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 { /** From 7892ff2938470fa35452d617501eb0bbae173d9c Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 12:48:15 +0200 Subject: [PATCH 11/45] update delegate api test --- .../security/server/build_delegate_apis.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 59963ad2aef8..32851e0566e8 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', () => { + 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', () => { From 471f9399e1d74ad6d7c4e2c339b5642aa5fa14ab Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 13:14:02 +0200 Subject: [PATCH 12/45] simplify api key type exports --- .../security/plugin_types_server/index.ts | 33 +++++++++++-------- .../src/authentication/api_keys/index.ts | 18 ---------- .../src/authentication/index.ts | 12 ------- 3 files changed, 20 insertions(+), 43 deletions(-) diff --git a/x-pack/packages/security/plugin_types_server/index.ts b/x-pack/packages/security/plugin_types_server/index.ts index f8ae5214e4a1..dacad9b4c98b 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, @@ -70,17 +61,33 @@ export type { } from './src/user_profile'; export { - restApiKeySchema, - getRestApiKeyWithKibanaPrivilegesSchema, getUpdateRestApiKeyWithKibanaPrivilegesSchema, - crossClusterApiKeySchema, updateRestApiKeySchema, updateCrossClusterApiKeySchema, } from './src/authentication'; -export type { ElasticsearchPrivilegesType, KibanaPrivilegesType } from '@kbn/core-security-common'; +export type { + ElasticsearchPrivilegesType, + KibanaPrivilegesType, + CreateAPIKeyParams, + CreateAPIKeyResult, + InvalidateAPIKeyResult, + InvalidateAPIKeysParams, + ValidateAPIKeyParams, + CreateRestAPIKeyParams, + CreateRestAPIKeyWithKibanaPrivilegesParams, + CreateCrossClusterAPIKeyParams, + GrantAPIKeyResult, +} from '@kbn/core-security-common'; export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE, } from '@kbn/core-security-common'; +export { + restApiKeySchema, + crossClusterApiKeySchema, + getRestApiKeyWithKibanaPrivilegesSchema, +} from '@kbn/core-security-common'; + +export type { APIKeysService } from '@kbn/core-security-common'; 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 02b880ac1fca..978fa52202de 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 @@ -17,21 +17,3 @@ export { updateRestApiKeySchema, updateCrossClusterApiKeySchema, } from './api_keys'; - -export { - restApiKeySchema, - crossClusterApiKeySchema, - getRestApiKeyWithKibanaPrivilegesSchema, -} from '@kbn/core-security-common'; - -export type { - CreateAPIKeyParams, - CreateAPIKeyResult, - InvalidateAPIKeyResult, - InvalidateAPIKeysParams, - ValidateAPIKeyParams, - CreateRestAPIKeyParams, - CreateRestAPIKeyWithKibanaPrivilegesParams, - CreateCrossClusterAPIKeyParams, - GrantAPIKeyResult, -} from '@kbn/core-security-common'; 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 e3c9fa94b440..ff0dfa29dd1a 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 @@ -6,15 +6,6 @@ */ export type { - CreateAPIKeyParams, - CreateAPIKeyResult, - CreateRestAPIKeyParams, - CreateRestAPIKeyWithKibanaPrivilegesParams, - CreateCrossClusterAPIKeyParams, - InvalidateAPIKeyResult, - InvalidateAPIKeysParams, - ValidateAPIKeyParams, - GrantAPIKeyResult, UpdateAPIKeyParams, UpdateAPIKeyResult, UpdateCrossClusterAPIKeyParams, @@ -23,10 +14,7 @@ export type { } from './api_keys'; export type { AuthenticationServiceStart } from './authentication_service'; export { - restApiKeySchema, - getRestApiKeyWithKibanaPrivilegesSchema, getUpdateRestApiKeyWithKibanaPrivilegesSchema, - crossClusterApiKeySchema, updateRestApiKeySchema, updateCrossClusterApiKeySchema, } from './api_keys'; From 5ac332dfe9cfd95b54119fbb6a3e5e68898cc81a Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 13:19:35 +0200 Subject: [PATCH 13/45] update comments for public ts docs --- .../core/security/core-security-common/src/roles/schema.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core/security/core-security-common/src/roles/schema.ts b/packages/core/security/core-security-common/src/roles/schema.ts index e853b043cb61..0a4871117151 100644 --- a/packages/core/security/core-security-common/src/roles/schema.ts +++ b/packages/core/security/core-security-common/src/roles/schema.ts @@ -270,5 +270,11 @@ export const getKibanaRoleSchema = ( } ); +/** + * Type representing Elasticsearch specific portion of the role definition. + */ export type ElasticsearchPrivilegesType = TypeOf; +/** + * Type representing Kibana specific portion of the role definition. + */ export type KibanaPrivilegesType = TypeOf>; From 0dfc26e421c24129b11fecc3de185737846c724e Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 26 Jun 2024 11:33:29 +0000 Subject: [PATCH 14/45] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/core/security/core-security-common/tsconfig.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/security/core-security-common/tsconfig.json b/packages/core/security/core-security-common/tsconfig.json index 27759c6c98cb..0b3f92069132 100644 --- a/packages/core/security/core-security-common/tsconfig.json +++ b/packages/core/security/core-security-common/tsconfig.json @@ -6,5 +6,8 @@ }, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["target/**/*"], - "kbn_references": ["@kbn/config-schema"] + "kbn_references": [ + "@kbn/config-schema", + "@kbn/core-http-server", + ] } From 10a38b658d4452355c375a3face97ccacd9f02bb Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 15:28:58 +0200 Subject: [PATCH 15/45] fix types --- x-pack/plugins/security/server/mocks.ts | 13 ++++++++++++- x-pack/plugins/security/server/plugin.ts | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index ba0dbaafeef3..2058c30b176b 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -19,7 +19,18 @@ function createSetupMock() { const mockAuthz = authorizationMock.create(); return { audit: auditServiceMock.create(), - authc: { getCurrentUser: jest.fn() }, + 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(), + }, + }, 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 b922e7228df9..9d57839288d2 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -270,6 +270,7 @@ export class SecurityPlugin getCurrentUser, recordAuditLoggingUsage: () => this.getFeatureUsageService().recordAuditLoggingUsage(), }); + const applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`; const apiKeysSetup = this.apiKeysService.setup({ getClusterClient: () => From fc8af4d3139649eb26ba25d56b2b41a3264f3ad2 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 15:36:16 +0200 Subject: [PATCH 16/45] remove unneeded comma --- .../security/plugin_types_common/tsconfig.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/x-pack/packages/security/plugin_types_common/tsconfig.json b/x-pack/packages/security/plugin_types_common/tsconfig.json index 6e53e4992c31..9822c02e2509 100644 --- a/x-pack/packages/security/plugin_types_common/tsconfig.json +++ b/x-pack/packages/security/plugin_types_common/tsconfig.json @@ -3,15 +3,11 @@ "compilerOptions": { "outDir": "target/types" }, - "include": [ - "**/*.ts", - ], - "exclude": [ - "target/**/*" - ], + "include": ["**/*.ts"], + "exclude": ["target/**/*"], "kbn_references": [ "@kbn/licensing-plugin", "@kbn/core-security-common", - "@kbn/core-user-profile-common", + "@kbn/core-user-profile-common" ] } From f8d8fae0ad22a71fa633463f58279adfcff810ec Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 15:37:51 +0200 Subject: [PATCH 17/45] remove unneeded comma --- .../packages/security/plugin_types_common/tsconfig.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/packages/security/plugin_types_common/tsconfig.json b/x-pack/packages/security/plugin_types_common/tsconfig.json index 9822c02e2509..353f6770352a 100644 --- a/x-pack/packages/security/plugin_types_common/tsconfig.json +++ b/x-pack/packages/security/plugin_types_common/tsconfig.json @@ -3,8 +3,12 @@ "compilerOptions": { "outDir": "target/types" }, - "include": ["**/*.ts"], - "exclude": ["target/**/*"], + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], "kbn_references": [ "@kbn/licensing-plugin", "@kbn/core-security-common", From 114bfc1105292f57f31d1688c98bb1ca1ae00c09 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 15:39:04 +0200 Subject: [PATCH 18/45] consolidate exported types --- x-pack/packages/security/plugin_types_server/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x-pack/packages/security/plugin_types_server/index.ts b/x-pack/packages/security/plugin_types_server/index.ts index dacad9b4c98b..35e8475b39e6 100644 --- a/x-pack/packages/security/plugin_types_server/index.ts +++ b/x-pack/packages/security/plugin_types_server/index.ts @@ -67,6 +67,7 @@ export { } from './src/authentication'; export type { + APIKeysService, ElasticsearchPrivilegesType, KibanaPrivilegesType, CreateAPIKeyParams, @@ -83,11 +84,7 @@ export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE, -} from '@kbn/core-security-common'; -export { restApiKeySchema, crossClusterApiKeySchema, getRestApiKeyWithKibanaPrivilegesSchema, } from '@kbn/core-security-common'; - -export type { APIKeysService } from '@kbn/core-security-common'; From 219d8f6d043d81b59adb888dcbc9db112b71036e Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 16:36:53 +0200 Subject: [PATCH 19/45] fix eslint type issue --- x-pack/plugins/security/server/build_delegate_apis.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 32851e0566e8..88c9fa158977 100644 --- a/x-pack/plugins/security/server/build_delegate_apis.test.ts +++ b/x-pack/plugins/security/server/build_delegate_apis.test.ts @@ -73,7 +73,7 @@ describe('buildSecurityApi', () => { }); it('returns the result from the service', async () => { - authc.apiKeys.areAPIKeysEnabled.mockReturnValue(Promise.resolve(false)); + void authc.apiKeys.areAPIKeysEnabled.mockReturnValue(Promise.resolve(false)); const areAPIKeysEnabled = await authc.apiKeys.areAPIKeysEnabled(); From 381316a6f67022fd51963bd1828de1c25111c295 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Wed, 26 Jun 2024 17:55:30 +0200 Subject: [PATCH 20/45] properly await apiKeysEnabled --- x-pack/plugins/security/server/build_delegate_apis.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 88c9fa158977..297ee6ebf92c 100644 --- a/x-pack/plugins/security/server/build_delegate_apis.test.ts +++ b/x-pack/plugins/security/server/build_delegate_apis.test.ts @@ -67,13 +67,13 @@ describe('buildSecurityApi', () => { }); describe('authc.apiKeys', () => { - it('properly delegates to the service', () => { - authc.apiKeys.areAPIKeysEnabled(); + 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 () => { - void authc.apiKeys.areAPIKeysEnabled.mockReturnValue(Promise.resolve(false)); + authc.apiKeys.areAPIKeysEnabled.mockReturnValue(Promise.resolve(false)); const areAPIKeysEnabled = await authc.apiKeys.areAPIKeysEnabled(); From fa37f7d365466a0e83b33e3c2c14eafa6ee392a1 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 12:34:51 +0200 Subject: [PATCH 21/45] move changes to core-security-server, expose update in public schema --- .../security/core-security-common/index.ts | 21 ------- .../core-security-server-mocks/index.ts | 1 + .../src/api_keys.mock.ts | 22 ++++--- .../src/security_service.mock.ts | 8 +-- .../security/core-security-server/index.ts | 29 +++++++++ .../core-security-server/src/authc.ts | 3 +- .../src/authentication}/api_keys/api_keys.ts | 62 ++++++++++++++++++- .../src/authentication}/api_keys/index.ts | 9 +++ .../src/request_handler_context.ts | 6 +- .../src/roles/index.ts | 0 .../src/roles/schema.ts | 0 .../core-security-server/tsconfig.json | 1 + .../security/plugin_types_server/index.ts | 12 ++-- .../src/authentication/api_keys/api_keys.ts | 61 ------------------ .../src/authentication/api_keys/index.ts | 19 ------ .../authentication/authentication_service.ts | 2 +- .../src/authentication/index.ts | 11 ++-- .../authentication/api_keys/api_keys.mock.ts | 23 ------- .../authentication_service.mock.ts | 2 +- .../security/server/build_delegate_apis.ts | 1 + x-pack/plugins/security/server/mocks.ts | 1 + 21 files changed, 141 insertions(+), 153 deletions(-) rename packages/core/security/{core-security-common/src => core-security-server/src/authentication}/api_keys/api_keys.ts (75%) rename packages/core/security/{core-security-common/src => core-security-server/src/authentication}/api_keys/index.ts (75%) rename packages/core/security/{core-security-common => core-security-server}/src/roles/index.ts (100%) rename packages/core/security/{core-security-common => core-security-server}/src/roles/schema.ts (100%) delete mode 100644 x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts delete mode 100644 x-pack/packages/security/plugin_types_server/src/authentication/api_keys/index.ts delete mode 100644 x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts diff --git a/packages/core/security/core-security-common/index.ts b/packages/core/security/core-security-common/index.ts index 4ecbbf67150c..d11d52117cfd 100644 --- a/packages/core/security/core-security-common/index.ts +++ b/packages/core/security/core-security-common/index.ts @@ -12,24 +12,3 @@ export type { AuthenticatedUser, AuthenticationProvider, } from './src/authentication'; - -export type { - APIKeysService, - CreateAPIKeyParams, - CreateAPIKeyResult, - InvalidateAPIKeyResult, - InvalidateAPIKeysParams, - ValidateAPIKeyParams, - CreateRestAPIKeyParams, - CreateRestAPIKeyWithKibanaPrivilegesParams, - CreateCrossClusterAPIKeyParams, - GrantAPIKeyResult, -} from './src/api_keys'; -export { - restApiKeySchema, - crossClusterApiKeySchema, - getRestApiKeyWithKibanaPrivilegesSchema, -} from './src/api_keys'; - -export { elasticsearchRoleSchema, getKibanaRoleSchema, GLOBAL_RESOURCE } from './src/roles'; -export type { KibanaPrivilegesType, ElasticsearchPrivilegesType } from './src/roles'; diff --git a/packages/core/security/core-security-server-mocks/index.ts b/packages/core/security/core-security-server-mocks/index.ts index 23c49282252f..c834759973c1 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/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts b/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts index f9ccee0f02ae..ee9135ac35de 100644 --- a/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts +++ b/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts @@ -6,12 +6,18 @@ * Side Public License, v 1. */ -export const apiKeysServiceMock = { - areAPIKeysEnabled: jest.fn(), - areCrossClusterAPIKeysEnabled: jest.fn(), - validate: jest.fn(), - invalidate: jest.fn(), - invalidateAsInternalUser: jest.fn(), - grantAsInternalUser: jest.fn(), - create: jest.fn(), +import type { APIKeysService } from '@kbn/core-security-server'; +import type { PublicMethodsOf } from '@kbn/utility-types'; + +export const apiKeysMock = { + create: (): jest.Mocked> => ({ + areAPIKeysEnabled: jest.fn(), + areCrossClusterAPIKeysEnabled: jest.fn(), + create: jest.fn(), + update: jest.fn(), + grantAsInternalUser: jest.fn(), + validate: jest.fn(), + invalidate: jest.fn(), + invalidateAsInternalUser: 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 202b9c450886..c19308ebcc45 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,7 +15,7 @@ import type { InternalSecurityServiceSetup, InternalSecurityServiceStart, } from '@kbn/core-security-server-internal'; -import { apiKeysServiceMock } from './api_keys.mock'; +import { apiKeysMock } from './api_keys.mock'; import { auditServiceMock, type MockedAuditService } from './audit.mock'; const createSetupMock = () => { @@ -34,7 +34,7 @@ const createStartMock = (): SecurityStartMock => { const mock = { authc: { getCurrentUser: jest.fn(), - apiKeys: apiKeysServiceMock, + apiKeys: apiKeysMock.create(), }, audit: auditServiceMock.create(), }; @@ -60,7 +60,7 @@ const createInternalStartMock = (): InternalSecurityStartMock => { const mock = { authc: { getCurrentUser: jest.fn(), - apiKeys: apiKeysServiceMock, + apiKeys: apiKeysMock.create(), }, audit: auditServiceMock.create(), }; @@ -82,7 +82,7 @@ const createRequestHandlerContextMock = () => { const mock: jest.MockedObjectDeep = { authc: { getCurrentUser: jest.fn(), - apiKeys: apiKeysServiceMock, + apiKeys: apiKeysMock.create(), }, audit: { logger: { diff --git a/packages/core/security/core-security-server/index.ts b/packages/core/security/core-security-server/index.ts index a4d3027c97fd..559201dea1cb 100644 --- a/packages/core/security/core-security-server/index.ts +++ b/packages/core/security/core-security-server/index.ts @@ -26,3 +26,32 @@ export type { AuditRequest, } from './src/audit_logging/audit_events'; export type { AuditLogger } from './src/audit_logging/audit_logger'; + +export type { + APIKeysService, + CreateAPIKeyParams, + CreateAPIKeyResult, + InvalidateAPIKeyResult, + InvalidateAPIKeysParams, + ValidateAPIKeyParams, + CreateRestAPIKeyParams, + CreateRestAPIKeyWithKibanaPrivilegesParams, + CreateCrossClusterAPIKeyParams, + GrantAPIKeyResult, + UpdateAPIKeyParams, + UpdateAPIKeyResult, + UpdateCrossClusterAPIKeyParams, + UpdateRestAPIKeyParams, + UpdateRestAPIKeyWithKibanaPrivilegesParams, +} from './src/authentication/api_keys'; +export { + restApiKeySchema, + crossClusterApiKeySchema, + getRestApiKeyWithKibanaPrivilegesSchema, + getUpdateRestApiKeyWithKibanaPrivilegesSchema, + updateRestApiKeySchema, + updateCrossClusterApiKeySchema, +} from './src/authentication/api_keys'; + +export { elasticsearchRoleSchema, getKibanaRoleSchema, GLOBAL_RESOURCE } from './src/roles'; +export type { KibanaPrivilegesType, ElasticsearchPrivilegesType } from './src/roles'; diff --git a/packages/core/security/core-security-server/src/authc.ts b/packages/core/security/core-security-server/src/authc.ts index 84d10275d340..984928ade596 100644 --- a/packages/core/security/core-security-server/src/authc.ts +++ b/packages/core/security/core-security-server/src/authc.ts @@ -7,7 +7,8 @@ */ import type { KibanaRequest } from '@kbn/core-http-server'; -import type { AuthenticatedUser, APIKeysService } from '@kbn/core-security-common'; +import type { AuthenticatedUser } from '@kbn/core-security-common'; +import type { APIKeysService } from './authentication/api_keys'; /** * Core's authentication service diff --git a/packages/core/security/core-security-common/src/api_keys/api_keys.ts b/packages/core/security/core-security-server/src/authentication/api_keys/api_keys.ts similarity index 75% rename from packages/core/security/core-security-common/src/api_keys/api_keys.ts rename to packages/core/security/core-security-server/src/authentication/api_keys/api_keys.ts index 3ebd40a68299..1c835c191da5 100644 --- a/packages/core/security/core-security-common/src/api_keys/api_keys.ts +++ b/packages/core/security/core-security-server/src/authentication/api_keys/api_keys.ts @@ -10,7 +10,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { KibanaRequest } from '@kbn/core-http-server'; import { schema, TypeOf } from '@kbn/config-schema'; -import { getKibanaRoleSchema, elasticsearchRoleSchema } from '../roles'; +import { getKibanaRoleSchema, elasticsearchRoleSchema } from '../../roles'; /** * Interface for managing API keys in Elasticsearch, including creation, @@ -43,6 +43,21 @@ export interface APIKeys { 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. @@ -212,3 +227,48 @@ export const crossClusterApiKeySchema = restApiKeySchema.extends({ { unknowns: 'allow' } ), }); + +/** + * 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, + id: schema.string(), +}); + +export const updateCrossClusterApiKeySchema = crossClusterApiKeySchema.extends({ + name: null, + id: schema.string(), +}); + +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, + id: schema.string(), + kibana_role_descriptors: schema.recordOf( + schema.string(), + schema.object({ + elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), + kibana: getKibanaRoleSchema(getBasePrivilegeNames), + }) + ), + }); diff --git a/packages/core/security/core-security-common/src/api_keys/index.ts b/packages/core/security/core-security-server/src/authentication/api_keys/index.ts similarity index 75% rename from packages/core/security/core-security-common/src/api_keys/index.ts rename to packages/core/security/core-security-server/src/authentication/api_keys/index.ts index 030c734e50c7..acb9682f3f5f 100644 --- a/packages/core/security/core-security-common/src/api_keys/index.ts +++ b/packages/core/security/core-security-server/src/authentication/api_keys/index.ts @@ -17,9 +17,18 @@ export type { CreateRestAPIKeyWithKibanaPrivilegesParams, CreateCrossClusterAPIKeyParams, GrantAPIKeyResult, + UpdateAPIKeyParams, + UpdateAPIKeyResult, + UpdateCrossClusterAPIKeyParams, + UpdateRestAPIKeyParams, + UpdateRestAPIKeyWithKibanaPrivilegesParams, } from './api_keys'; + export { restApiKeySchema, crossClusterApiKeySchema, getRestApiKeyWithKibanaPrivilegesSchema, + getUpdateRestApiKeyWithKibanaPrivilegesSchema, + updateRestApiKeySchema, + updateCrossClusterApiKeySchema, } 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 cbe13ba631a6..90f6c2a598f0 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 @@ -6,8 +6,10 @@ * Side Public License, v 1. */ -import type { AuthenticatedUser, APIKeysService } from '@kbn/core-security-common'; +import type { AuthenticatedUser } from '@kbn/core-security-common'; + import { AuditLogger } from './audit_logging/audit_logger'; +import type { APIKeys } from './authentication/api_keys/api_keys'; export interface SecurityRequestHandlerContext { authc: AuthcRequestHandlerContext; @@ -16,7 +18,7 @@ export interface SecurityRequestHandlerContext { export interface AuthcRequestHandlerContext { getCurrentUser(): AuthenticatedUser | null; - apiKeys: APIKeysService | null; + apiKeys: APIKeys | null; } export interface AuditRequestHandlerContext { diff --git a/packages/core/security/core-security-common/src/roles/index.ts b/packages/core/security/core-security-server/src/roles/index.ts similarity index 100% rename from packages/core/security/core-security-common/src/roles/index.ts rename to packages/core/security/core-security-server/src/roles/index.ts diff --git a/packages/core/security/core-security-common/src/roles/schema.ts b/packages/core/security/core-security-server/src/roles/schema.ts similarity index 100% rename from packages/core/security/core-security-common/src/roles/schema.ts rename to packages/core/security/core-security-server/src/roles/schema.ts diff --git a/packages/core/security/core-security-server/tsconfig.json b/packages/core/security/core-security-server/tsconfig.json index 0304c8ef6dee..12c5c8379c7b 100644 --- a/packages/core/security/core-security-server/tsconfig.json +++ b/packages/core/security/core-security-server/tsconfig.json @@ -17,5 +17,6 @@ "@kbn/core-security-common", "@kbn/core-http-server", "@kbn/logging", + "@kbn/config-schema", ] } diff --git a/x-pack/packages/security/plugin_types_server/index.ts b/x-pack/packages/security/plugin_types_server/index.ts index 35e8475b39e6..70dda1a63f50 100644 --- a/x-pack/packages/security/plugin_types_server/index.ts +++ b/x-pack/packages/security/plugin_types_server/index.ts @@ -67,9 +67,9 @@ export { } from './src/authentication'; export type { - APIKeysService, ElasticsearchPrivilegesType, KibanaPrivilegesType, + APIKeysService, CreateAPIKeyParams, CreateAPIKeyResult, InvalidateAPIKeyResult, @@ -79,12 +79,12 @@ export type { CreateRestAPIKeyWithKibanaPrivilegesParams, CreateCrossClusterAPIKeyParams, GrantAPIKeyResult, -} from '@kbn/core-security-common'; +} from '@kbn/core-security-server'; export { - getKibanaRoleSchema, - elasticsearchRoleSchema, - GLOBAL_RESOURCE, restApiKeySchema, crossClusterApiKeySchema, getRestApiKeyWithKibanaPrivilegesSchema, -} from '@kbn/core-security-common'; + getKibanaRoleSchema, + elasticsearchRoleSchema, + GLOBAL_RESOURCE, +} from '@kbn/core-security-server'; 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 deleted file mode 100644 index a42b928d8cf3..000000000000 --- a/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 { estypes } from '@elastic/elasticsearch'; - -import { schema, TypeOf } from '@kbn/config-schema'; -import { - restApiKeySchema, - crossClusterApiKeySchema, - elasticsearchRoleSchema, - getKibanaRoleSchema, -} from '@kbn/core-security-common'; - -/** - * 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, - id: schema.string(), -}); - -export const updateCrossClusterApiKeySchema = crossClusterApiKeySchema.extends({ - name: null, - id: schema.string(), -}); - -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, - id: schema.string(), - kibana_role_descriptors: schema.recordOf( - schema.string(), - schema.object({ - elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), - kibana: getKibanaRoleSchema(getBasePrivilegeNames), - }) - ), - }); 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 deleted file mode 100644 index 978fa52202de..000000000000 --- a/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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. - */ - -export type { - UpdateAPIKeyParams, - UpdateAPIKeyResult, - UpdateCrossClusterAPIKeyParams, - UpdateRestAPIKeyParams, - UpdateRestAPIKeyWithKibanaPrivilegesParams, -} from './api_keys'; -export { - getUpdateRestApiKeyWithKibanaPrivilegesSchema, - updateRestApiKeySchema, - updateCrossClusterApiKeySchema, -} from './api_keys'; 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 e54bc2c1ab98..5d066bb6565c 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 @@ -7,7 +7,7 @@ import type { KibanaRequest } from '@kbn/core/server'; import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; -import type { APIKeysService } from '@kbn/core-security-common'; +import type { APIKeysService } from '@kbn/core-security-server'; /** * Authentication services available on the security plugin's start contract. 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 ff0dfa29dd1a..370504fa44c2 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,18 +5,19 @@ * 2.0. */ +export type { AuthenticationServiceStart } from './authentication_service'; + export type { + APIKeysService as APIKeys, UpdateAPIKeyParams, UpdateAPIKeyResult, UpdateCrossClusterAPIKeyParams, UpdateRestAPIKeyParams, UpdateRestAPIKeyWithKibanaPrivilegesParams, -} from './api_keys'; -export type { AuthenticationServiceStart } from './authentication_service'; +} from '@kbn/core-security-server'; + export { getUpdateRestApiKeyWithKibanaPrivilegesSchema, updateRestApiKeySchema, updateCrossClusterApiKeySchema, -} from './api_keys'; - -export type { APIKeysService as APIKeys } from '@kbn/core-security-common'; +} from '@kbn/core-security-server'; diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts deleted file mode 100644 index cfa857ca833a..000000000000 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 { PublicMethodsOf } from '@kbn/utility-types'; - -import type { APIKeys } from './api_keys'; - -export const apiKeysMock = { - create: (): jest.Mocked> => ({ - areAPIKeysEnabled: jest.fn(), - areCrossClusterAPIKeysEnabled: jest.fn(), - create: jest.fn(), - update: jest.fn(), - grantAsInternalUser: jest.fn(), - validate: jest.fn(), - invalidate: jest.fn(), - invalidateAsInternalUser: jest.fn(), - }), -}; 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 de87e7161bda..676b039eefaa 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/build_delegate_apis.ts b/x-pack/plugins/security/server/build_delegate_apis.ts index 4d56d917aedc..f6d57cfc8a4a 100644 --- a/x-pack/plugins/security/server/build_delegate_apis.ts +++ b/x-pack/plugins/security/server/build_delegate_apis.ts @@ -30,6 +30,7 @@ export const buildSecurityApi = ({ 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), diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index 2058c30b176b..2b37fb29c234 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -29,6 +29,7 @@ function createSetupMock() { invalidateAsInternalUser: jest.fn(), grantAsInternalUser: jest.fn(), create: jest.fn(), + update: jest.fn(), }, }, authz: { From 2b6438eac02f93284e780bd886707815290f0736 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 12:37:01 +0200 Subject: [PATCH 22/45] update imports --- .../server/routes/authorization/roles/model/put_payload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts index 53064a828224..86d3eb3620a2 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { elasticsearchRoleSchema, getKibanaRoleSchema } from '@kbn/core-security-common'; +import { elasticsearchRoleSchema, getKibanaRoleSchema } from '@kbn/core-security-server'; import type { ElasticsearchRole } from '../../../../authorization'; import { transformPrivilegesToElasticsearchPrivileges } from '../../../../lib'; From a8c77954e31432df431d2e3d52ca54cab5a382cd Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:49:21 +0000 Subject: [PATCH 23/45] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/core/security/core-security-common/tsconfig.json | 2 -- .../core/security/core-security-server-mocks/tsconfig.json | 1 + x-pack/packages/security/plugin_types_server/tsconfig.json | 2 -- x-pack/plugins/security/tsconfig.json | 3 ++- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/core/security/core-security-common/tsconfig.json b/packages/core/security/core-security-common/tsconfig.json index 0b3f92069132..54ce077b4101 100644 --- a/packages/core/security/core-security-common/tsconfig.json +++ b/packages/core/security/core-security-common/tsconfig.json @@ -7,7 +7,5 @@ "include": ["**/*.ts", "**/*.tsx"], "exclude": ["target/**/*"], "kbn_references": [ - "@kbn/config-schema", - "@kbn/core-http-server", ] } diff --git a/packages/core/security/core-security-server-mocks/tsconfig.json b/packages/core/security/core-security-server-mocks/tsconfig.json index 28181e131bad..b71af4615b33 100644 --- a/packages/core/security/core-security-server-mocks/tsconfig.json +++ b/packages/core/security/core-security-server-mocks/tsconfig.json @@ -17,5 +17,6 @@ "@kbn/core-security-server", "@kbn/core-security-server-internal", "@kbn/core-http-server", + "@kbn/utility-types", ] } diff --git a/x-pack/packages/security/plugin_types_server/tsconfig.json b/x-pack/packages/security/plugin_types_server/tsconfig.json index c54d8780af93..9f2cf7f89f17 100644 --- a/x-pack/packages/security/plugin_types_server/tsconfig.json +++ b/x-pack/packages/security/plugin_types_server/tsconfig.json @@ -10,11 +10,9 @@ "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", ] } diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index 728fcec8d911..10c1ada6ede1 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -82,7 +82,8 @@ "@kbn/core-user-profile-server", "@kbn/core-user-profile-browser", "@kbn/security-api-key-management", - "@kbn/security-form-components" + "@kbn/security-form-components", + "@kbn/core-security-server-mocks" ], "exclude": [ "target/**/*", From 9a21a9a399e4b9d7d860ee332e3d3903472e0ce2 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 12:55:22 +0200 Subject: [PATCH 24/45] update mocks --- .../security/core-security-server-mocks/src/api_keys.mock.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts b/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts index ee9135ac35de..108f8380264e 100644 --- a/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts +++ b/packages/core/security/core-security-server-mocks/src/api_keys.mock.ts @@ -7,10 +7,9 @@ */ import type { APIKeysService } from '@kbn/core-security-server'; -import type { PublicMethodsOf } from '@kbn/utility-types'; export const apiKeysMock = { - create: (): jest.Mocked> => ({ + create: (): jest.MockedObjectDeep => ({ areAPIKeysEnabled: jest.fn(), areCrossClusterAPIKeysEnabled: jest.fn(), create: jest.fn(), From 11844e51123d17fe60ba7685e583cad96b6ca6cc Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 12:58:59 +0200 Subject: [PATCH 25/45] fix types --- .../src/api_keys/api_keys.ts | 274 ++++++++++++++++++ .../src/api_keys/index.ts | 34 +++ 2 files changed, 308 insertions(+) create mode 100644 packages/core/security/core-security-server/src/api_keys/api_keys.ts create mode 100644 packages/core/security/core-security-server/src/api_keys/index.ts diff --git a/packages/core/security/core-security-server/src/api_keys/api_keys.ts b/packages/core/security/core-security-server/src/api_keys/api_keys.ts new file mode 100644 index 000000000000..873fe67bfa18 --- /dev/null +++ b/packages/core/security/core-security-server/src/api_keys/api_keys.ts @@ -0,0 +1,274 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; +import { getKibanaRoleSchema, elasticsearchRoleSchema } 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 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 { + /** + * 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; + }; + }>; +} + +export const restApiKeySchema = schema.object({ + type: schema.maybe(schema.literal('rest')), + name: schema.string(), + 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 getRestApiKeyWithKibanaPrivilegesSchema = ( + getBasePrivilegeNames: Parameters[0] +) => + restApiKeySchema.extends({ + role_descriptors: null, + kibana_role_descriptors: schema.recordOf( + schema.string(), + schema.object({ + elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), + kibana: getKibanaRoleSchema(getBasePrivilegeNames), + }) + ), + }); + +export const crossClusterApiKeySchema = restApiKeySchema.extends({ + type: schema.literal('cross_cluster'), + role_descriptors: null, + 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' } + ), +}); + +/** + * 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, + id: schema.string(), +}); + +export const updateCrossClusterApiKeySchema = crossClusterApiKeySchema.extends({ + name: null, + id: schema.string(), +}); + +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, + id: schema.string(), + kibana_role_descriptors: schema.recordOf( + schema.string(), + schema.object({ + elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), + kibana: getKibanaRoleSchema(getBasePrivilegeNames), + }) + ), + }); diff --git a/packages/core/security/core-security-server/src/api_keys/index.ts b/packages/core/security/core-security-server/src/api_keys/index.ts new file mode 100644 index 000000000000..acb9682f3f5f --- /dev/null +++ b/packages/core/security/core-security-server/src/api_keys/index.ts @@ -0,0 +1,34 @@ +/* + * 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 { + restApiKeySchema, + crossClusterApiKeySchema, + getRestApiKeyWithKibanaPrivilegesSchema, + getUpdateRestApiKeyWithKibanaPrivilegesSchema, + updateRestApiKeySchema, + updateCrossClusterApiKeySchema, +} from './api_keys'; From 43c8a07436fb78a2a5d7b646b4775e6ba43a0dd6 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 12:59:37 +0200 Subject: [PATCH 26/45] revert formatting changes for tsconfig --- .../core-security-common/tsconfig.json | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/core/security/core-security-common/tsconfig.json b/packages/core/security/core-security-common/tsconfig.json index 54ce077b4101..b05325b824a6 100644 --- a/packages/core/security/core-security-common/tsconfig.json +++ b/packages/core/security/core-security-common/tsconfig.json @@ -2,10 +2,18 @@ "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", - "types": ["jest", "node", "react"] + "types": [ + "jest", + "node", + "react" + ] }, - "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["target/**/*"], - "kbn_references": [ - ] + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] } From 850a22a7d92040f16070ede8185b0f6bba118bd6 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 13:00:46 +0200 Subject: [PATCH 27/45] update mocks for update fn --- .../src/utils/convert_security_api.test.ts | 1 + 1 file changed, 1 insertion(+) 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 ee4b052dd469..40d9e788ea01 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 @@ -23,6 +23,7 @@ describe('convertSecurityApi', () => { invalidateAsInternalUser: jest.fn(), grantAsInternalUser: jest.fn(), create: jest.fn(), + update: jest.fn(), }, }, audit: { From 1932f538e210d9ad7a7b510be35d5dcdd4b56c1f Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 13:02:35 +0200 Subject: [PATCH 28/45] remove unused kbn reference --- .../core/security/core-security-server-mocks/tsconfig.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/security/core-security-server-mocks/tsconfig.json b/packages/core/security/core-security-server-mocks/tsconfig.json index b71af4615b33..d0f6bbeb972d 100644 --- a/packages/core/security/core-security-server-mocks/tsconfig.json +++ b/packages/core/security/core-security-server-mocks/tsconfig.json @@ -16,7 +16,6 @@ "kbn_references": [ "@kbn/core-security-server", "@kbn/core-security-server-internal", - "@kbn/core-http-server", - "@kbn/utility-types", + "@kbn/core-http-server" ] } From ec18b5b18ee60e36c8215c005cdf3ff83d026532 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 13:03:52 +0200 Subject: [PATCH 29/45] remove empty comment --- .../core/security/core-security-server/src/api_keys/api_keys.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/security/core-security-server/src/api_keys/api_keys.ts b/packages/core/security/core-security-server/src/api_keys/api_keys.ts index 873fe67bfa18..595f73e86f1c 100644 --- a/packages/core/security/core-security-server/src/api_keys/api_keys.ts +++ b/packages/core/security/core-security-server/src/api_keys/api_keys.ts @@ -186,7 +186,6 @@ export const restApiKeySchema = schema.object({ metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), }); -/** */ export const getRestApiKeyWithKibanaPrivilegesSchema = ( getBasePrivilegeNames: Parameters[0] ) => From 7aa2dd35f12f70486988512ee9bd7b15a68e05ef Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 13:05:21 +0200 Subject: [PATCH 30/45] fix import name --- .../core-security-server/src/request_handler_context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 90f6c2a598f0..32db3180e9fa 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 @@ -9,7 +9,7 @@ import type { AuthenticatedUser } from '@kbn/core-security-common'; import { AuditLogger } from './audit_logging/audit_logger'; -import type { APIKeys } from './authentication/api_keys/api_keys'; +import type { APIKeysService } from './authentication/api_keys'; export interface SecurityRequestHandlerContext { authc: AuthcRequestHandlerContext; @@ -18,7 +18,7 @@ export interface SecurityRequestHandlerContext { export interface AuthcRequestHandlerContext { getCurrentUser(): AuthenticatedUser | null; - apiKeys: APIKeys | null; + apiKeys: APIKeysService | null; } export interface AuditRequestHandlerContext { From 5c12825c3406bde00071a70184ee74ecedf9a40f Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 13:07:57 +0200 Subject: [PATCH 31/45] update mocks --- x-pack/plugins/security/server/mocks.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index 2b37fb29c234..b85652f71528 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -7,6 +7,8 @@ import type { TransportResult } from '@elastic/elasticsearch'; +import { apiKeysMock } from '@kbn/core-security-server-mocks'; + import { auditServiceMock } from './audit/mocks'; import { authenticationServiceMock } from './authentication/authentication_service.mock'; import { authorizationMock } from './authorization/index.mock'; @@ -21,16 +23,7 @@ function createSetupMock() { audit: auditServiceMock.create(), 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(), - }, + apiKeys: apiKeysMock.create(), }, authz: { actions: mockAuthz.actions, From d04f88b8aeeb6a5820936ff1fd751851de180624 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 15:19:02 +0200 Subject: [PATCH 32/45] update default implementation to be stub --- .../src/utils/default_implementation.test.ts | 6 ++++-- .../src/utils/default_implementation.ts | 13 ++++++++++++- .../core/security/core-security-server/src/authc.ts | 2 +- .../src/request_handler_context.ts | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) 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 113a6ebb3c33..bc7fac96b7dd 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 @@ -24,9 +24,11 @@ describe('getDefaultSecurityImplementation', () => { }); describe('authc.apiKeys', () => { - it('returns null', async () => { + it('returns stub object', async () => { const { apiKeys } = implementation.authc; - expect(apiKeys).toBeNull(); + const areAPIKeysEnabled = await apiKeys.areAPIKeysEnabled(); + + expect(areAPIKeysEnabled).toBe(false); }); }); 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 dc7f12afe097..1646b97a297a 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,11 +8,22 @@ import type { CoreSecurityDelegateContract } from '@kbn/core-security-server'; +const API_KEYS_DISABLED_ERROR = new Error('API keys are disabled'); + export const getDefaultSecurityImplementation = (): CoreSecurityDelegateContract => { return { authc: { getCurrentUser: () => null, - apiKeys: null, + apiKeys: { + areAPIKeysEnabled: () => Promise.resolve(false), + areCrossClusterAPIKeysEnabled: () => Promise.resolve(false), + create: () => Promise.reject(API_KEYS_DISABLED_ERROR), + update: () => Promise.reject(API_KEYS_DISABLED_ERROR), + grantAsInternalUser: () => Promise.reject(API_KEYS_DISABLED_ERROR), + validate: () => Promise.reject(API_KEYS_DISABLED_ERROR), + invalidate: () => Promise.reject(API_KEYS_DISABLED_ERROR), + invalidateAsInternalUser: () => Promise.reject(API_KEYS_DISABLED_ERROR), + }, }, audit: { asScoped: () => { diff --git a/packages/core/security/core-security-server/src/authc.ts b/packages/core/security/core-security-server/src/authc.ts index 984928ade596..85ba4fc71542 100644 --- a/packages/core/security/core-security-server/src/authc.ts +++ b/packages/core/security/core-security-server/src/authc.ts @@ -23,5 +23,5 @@ export interface CoreAuthenticationService { * @param request The request to retrieve the authenticated user for. */ getCurrentUser(request: KibanaRequest): AuthenticatedUser | null; - apiKeys: APIKeysService | null; + apiKeys: APIKeysService; } 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 32db3180e9fa..81dc08542360 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 @@ -18,7 +18,7 @@ export interface SecurityRequestHandlerContext { export interface AuthcRequestHandlerContext { getCurrentUser(): AuthenticatedUser | null; - apiKeys: APIKeysService | null; + apiKeys: APIKeysService; } export interface AuditRequestHandlerContext { From fb9a78f7ae26a9420cd2927d9a8c19d87a09cdf8 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 16:41:53 +0200 Subject: [PATCH 33/45] create explicit decoupled public types, move schema back to plugin --- .../security/core-security-server/index.ts | 9 - .../src/authentication/api_keys/api_keys.ts | 150 ++++----- .../src/authentication/api_keys/index.ts | 9 - .../core-security-server/src/roles/index.ts | 1 - .../core-security-server/src/roles/schema.ts | 294 ++---------------- .../security/plugin_types_server/index.ts | 7 +- .../src/authentication/api_keys/api_keys.ts | 86 +++++ .../src/authentication/api_keys/index.ts | 15 + .../src/authentication/index.ts | 5 +- .../src/authorization/index.ts | 3 + .../src/authorization/role_schema.ts | 274 ++++++++++++++++ .../authentication/api_keys/api_keys.ts | 26 ++ .../authorization/roles/model/put_payload.ts | 2 +- 13 files changed, 502 insertions(+), 379 deletions(-) create mode 100644 x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts create mode 100644 x-pack/packages/security/plugin_types_server/src/authentication/api_keys/index.ts create mode 100644 x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts diff --git a/packages/core/security/core-security-server/index.ts b/packages/core/security/core-security-server/index.ts index 559201dea1cb..4cf71a20ca53 100644 --- a/packages/core/security/core-security-server/index.ts +++ b/packages/core/security/core-security-server/index.ts @@ -44,14 +44,5 @@ export type { UpdateRestAPIKeyParams, UpdateRestAPIKeyWithKibanaPrivilegesParams, } from './src/authentication/api_keys'; -export { - restApiKeySchema, - crossClusterApiKeySchema, - getRestApiKeyWithKibanaPrivilegesSchema, - getUpdateRestApiKeyWithKibanaPrivilegesSchema, - updateRestApiKeySchema, - updateCrossClusterApiKeySchema, -} from './src/authentication/api_keys'; -export { elasticsearchRoleSchema, getKibanaRoleSchema, GLOBAL_RESOURCE } from './src/roles'; export type { KibanaPrivilegesType, ElasticsearchPrivilegesType } from './src/roles'; 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 index 1c835c191da5..d638585a0238 100644 --- 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 @@ -9,8 +9,8 @@ import type { estypes } from '@elastic/elasticsearch'; import type { KibanaRequest } from '@kbn/core-http-server'; -import { schema, TypeOf } from '@kbn/config-schema'; -import { getKibanaRoleSchema, elasticsearchRoleSchema } from '../../roles'; + +import { ElasticsearchPrivilegesType, KibanaPrivilegesType } from '../../roles'; /** * Interface for managing API keys in Elasticsearch, including creation, @@ -101,11 +101,45 @@ export type CreateAPIKeyParams = */ export type CreateAPIKeyResult = estypes.SecurityCreateApiKeyResponse; -export type CreateRestAPIKeyParams = TypeOf; -export type CreateRestAPIKeyWithKibanaPrivilegesParams = TypeOf< - ReturnType ->; -export type CreateCrossClusterAPIKeyParams = TypeOf; +export interface CreateRestAPIKeyParams { + type?: 'rest'; + expiration?: string; + name: string; + role_descriptors: Record; + metadata?: { [key: string]: unknown }; +} + +export type CreateRestAPIKeyWithKibanaPrivilegesParams = Omit< + CreateRestAPIKeyParams, + 'role_descriptors' +> & { + role_descriptors: null; + kibana_role_descriptors: Record< + string, + { + elasticsearch: ElasticsearchPrivilegesType & { [key: string]: unknown }; + kibana: KibanaPrivilegesType; + } + >; +}; +export type CreateCrossClusterAPIKeyParams = Omit< + CreateRestAPIKeyParams, + 'type' | 'role_descriptors' +> & { + type?: 'cross_cluster'; + role_descriptors: null; + access: { + search: Array<{ + names: string[]; + query?: unknown; + field_security?: unknown; + allow_restricted_indices?: boolean; + }>; + replication?: Array<{ + names: string[]; + }>; + }; +}; export interface GrantAPIKeyResult { /** @@ -176,58 +210,6 @@ export interface InvalidateAPIKeyResult { }>; } -export const restApiKeySchema = schema.object({ - type: schema.maybe(schema.literal('rest')), - name: schema.string(), - 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 getRestApiKeyWithKibanaPrivilegesSchema = ( - getBasePrivilegeNames: Parameters[0] -) => - restApiKeySchema.extends({ - role_descriptors: null, - kibana_role_descriptors: schema.recordOf( - schema.string(), - schema.object({ - elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), - kibana: getKibanaRoleSchema(getBasePrivilegeNames), - }) - ), - }); - -export const crossClusterApiKeySchema = restApiKeySchema.extends({ - type: schema.literal('cross_cluster'), - role_descriptors: null, - 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' } - ), -}); - /** * Response of Kibana Update API key endpoint. */ @@ -241,34 +223,26 @@ export type UpdateAPIKeyParams = | UpdateCrossClusterAPIKeyParams | UpdateRestAPIKeyWithKibanaPrivilegesParams; -export const updateRestApiKeySchema = restApiKeySchema.extends({ - name: null, - id: schema.string(), -}); - -export const updateCrossClusterApiKeySchema = crossClusterApiKeySchema.extends({ - name: null, - id: schema.string(), -}); +export type UpdateRestAPIKeyParams = Omit & { + name: null; + id: string; +}; -export type UpdateRestAPIKeyParams = TypeOf; -export type UpdateCrossClusterAPIKeyParams = TypeOf; -export type UpdateRestAPIKeyWithKibanaPrivilegesParams = TypeOf< - ReturnType ->; +export type UpdateCrossClusterAPIKeyParams = Omit & { + name: null; + id: string; +}; -export const getUpdateRestApiKeyWithKibanaPrivilegesSchema = ( - getBasePrivilegeNames: Parameters[0] -) => - restApiKeySchema.extends({ - role_descriptors: null, - name: null, - id: schema.string(), - kibana_role_descriptors: schema.recordOf( - schema.string(), - schema.object({ - elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), - kibana: getKibanaRoleSchema(getBasePrivilegeNames), - }) - ), - }); +export type UpdateRestAPIKeyWithKibanaPrivilegesParams = Omit< + CreateRestAPIKeyParams, + 'name' | 'role_descriptors' +> & { + id: string; + kibana_role_descriptors: Record< + string, + { + elasticsearch: ElasticsearchPrivilegesType & { [key: string]: unknown }; + kibana: KibanaPrivilegesType; + } + >; +}; 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 index acb9682f3f5f..8b457d3597c4 100644 --- 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 @@ -23,12 +23,3 @@ export type { UpdateRestAPIKeyParams, UpdateRestAPIKeyWithKibanaPrivilegesParams, } from './api_keys'; - -export { - restApiKeySchema, - crossClusterApiKeySchema, - getRestApiKeyWithKibanaPrivilegesSchema, - getUpdateRestApiKeyWithKibanaPrivilegesSchema, - updateRestApiKeySchema, - updateCrossClusterApiKeySchema, -} from './api_keys'; diff --git a/packages/core/security/core-security-server/src/roles/index.ts b/packages/core/security/core-security-server/src/roles/index.ts index ebea32d00410..420f6780fdd8 100644 --- a/packages/core/security/core-security-server/src/roles/index.ts +++ b/packages/core/security/core-security-server/src/roles/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export { getKibanaRoleSchema, elasticsearchRoleSchema, GLOBAL_RESOURCE } from './schema'; 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 index 0a4871117151..693916ef3d9b 100644 --- a/packages/core/security/core-security-server/src/roles/schema.ts +++ b/packages/core/security/core-security-server/src/roles/schema.ts @@ -5,276 +5,38 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import _ from 'lodash'; - -import type { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; - -export const GLOBAL_RESOURCE = '*'; -/** - * Elasticsearch specific portion of the role definition. - * See more details at https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api.html#security-role-apis. - */ -export const elasticsearchRoleSchema = schema.object({ - /** - * An optional list of cluster privileges. These privileges define the cluster level actions that - * users with this role are able to execute - */ - cluster: schema.maybe(schema.arrayOf(schema.string())), - - /** - * An optional list of remote cluster privileges. These privileges define the remote cluster level actions that - * users with this role are able to execute - */ - remote_cluster: schema.maybe( - schema.arrayOf( - schema.object({ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - clusters: schema.arrayOf(schema.string(), { minSize: 1 }), - }) - ) - ), - - /** - * An optional list of indices permissions entries. - */ - indices: schema.maybe( - schema.arrayOf( - schema.object({ - /** - * Required list of indices (or index name patterns) to which the permissions in this - * entry apply. - */ - names: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional set of the document fields that the owners of the role have read access to. - */ - field_security: schema.maybe( - schema.recordOf( - schema.oneOf([schema.literal('grant'), schema.literal('except')]), - schema.arrayOf(schema.string()) - ) - ), - - /** - * Required list of the index level privileges that the owners of the role have on the - * specified indices. - */ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional search query that defines the documents the owners of the role have read access - * to. A document within the specified indices must match this query in order for it to be - * accessible by the owners of the role. - */ - query: schema.maybe(schema.string()), - - /** - * An optional flag used to indicate if index pattern wildcards or regexps should cover - * restricted indices. - */ - allow_restricted_indices: schema.maybe(schema.boolean()), - }) - ) - ), - - /** - * An optional list of remote indices permissions entries. - */ - remote_indices: schema.maybe( - schema.arrayOf( - schema.object({ - /** - * Required list of remote clusters to which the permissions in this entry apply. - */ - clusters: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * Required list of remote indices (or index name patterns) to which the permissions in this - * entry apply. - */ - names: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional set of the document fields that the owners of the role have read access to. - */ - field_security: schema.maybe( - schema.recordOf( - schema.oneOf([schema.literal('grant'), schema.literal('except')]), - schema.arrayOf(schema.string()) - ) - ), - - /** - * Required list of the index level privileges that the owners of the role have on the - * specified indices. - */ - privileges: schema.arrayOf(schema.string(), { minSize: 1 }), - - /** - * An optional search query that defines the documents the owners of the role have read access - * to. A document within the specified indices must match this query in order for it to be - * accessible by the owners of the role. - */ - query: schema.maybe(schema.string()), - - /** - * An optional flag used to indicate if index pattern wildcards or regexps should cover - * restricted indices. - */ - allow_restricted_indices: schema.maybe(schema.boolean()), - }) - ) - ), - - /** - * An optional list of users that the owners of this role can impersonate. - */ - run_as: schema.maybe(schema.arrayOf(schema.string())), -}); - -const allSpacesSchema = schema.arrayOf(schema.literal(GLOBAL_RESOURCE), { - minSize: 1, - maxSize: 1, -}); - -/** - * Schema for the list of space IDs used within Kibana specific role definition. - */ -const spacesSchema = schema.oneOf( - [ - allSpacesSchema, - schema.arrayOf( - schema.string({ - validate(value) { - if (!/^[a-z0-9_-]+$/.test(value)) { - return `must be lower case, a-z, 0-9, '_', and '-' are allowed`; - } - }, - }) - ), - ], - { defaultValue: [GLOBAL_RESOURCE] } -); - -const FEATURE_NAME_VALUE_REGEX = /^[a-zA-Z0-9_-]+$/; - -/** - * Kibana specific portion of the role definition. It's represented as a list of base and/or - * feature Kibana privileges. None of the entries should apply to the same spaces. - */ -export const getKibanaRoleSchema = ( - getBasePrivilegeNames: () => { global: string[]; space: string[] } -) => - schema.arrayOf( - schema.object( - { - /** - * An optional list of space IDs to which the permissions in this entry apply. If not - * specified it defaults to special "global" space ID (all spaces). - */ - spaces: spacesSchema, - - /** - * An optional list of Kibana base privileges. If this entry applies to special "global" - * space (all spaces) then specified base privileges should be within known base "global" - * privilege list, otherwise - within known "space" privilege list. Base privileges - * definition isn't allowed when feature privileges are defined and required otherwise. - */ - base: schema.maybe( - schema.conditional( - schema.siblingRef('spaces'), - allSpacesSchema, - schema.arrayOf( - schema.string({ - validate(value) { - const globalPrivileges = getBasePrivilegeNames().global; - if (!globalPrivileges.some((privilege) => privilege === value)) { - return `unknown global privilege "${value}", must be one of [${globalPrivileges}]`; - } - }, - }) - ), - schema.arrayOf( - schema.string({ - validate(value) { - const spacePrivileges = getBasePrivilegeNames().space; - if (!spacePrivileges.some((privilege) => privilege === value)) { - return `unknown space privilege "${value}", must be one of [${spacePrivileges}]`; - } - }, - }) - ) - ) - ), - - /** - * An optional dictionary of Kibana feature privileges where the key is the ID of the - * feature and the value is a list of feature specific privilege IDs. Both feature and - * privilege IDs should consist of allowed set of characters. Feature privileges - * definition isn't allowed when base privileges are defined and required otherwise. - */ - feature: schema.maybe( - schema.recordOf( - schema.string({ - validate(value) { - if (!FEATURE_NAME_VALUE_REGEX.test(value)) { - return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; - } - }, - }), - schema.arrayOf( - schema.string({ - validate(value) { - if (!FEATURE_NAME_VALUE_REGEX.test(value)) { - return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; - } - }, - }) - ) - ) - ), - }, - { - validate(value) { - if ( - (value.base === undefined || value.base.length === 0) && - (value.feature === undefined || Object.values(value.feature).flat().length === 0) - ) { - return 'either [base] or [feature] is expected, but none of them specified'; - } - - if ( - value.base !== undefined && - value.base.length > 0 && - value.feature !== undefined && - Object.keys(value.feature).length > 0 - ) { - return `definition of [feature] isn't allowed when non-empty [base] is defined.`; - } - }, - } - ), - { - validate(value) { - for (const [indexA, valueA] of value.entries()) { - for (const valueB of value.slice(indexA + 1)) { - const spaceIntersection = _.intersection(valueA.spaces, valueB.spaces); - if (spaceIntersection.length !== 0) { - return `more than one privilege is applied to the following spaces: [${spaceIntersection}]`; - } - } - } - }, - } - ); /** * Type representing Elasticsearch specific portion of the role definition. */ -export type ElasticsearchPrivilegesType = TypeOf; +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 = TypeOf>; +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 70dda1a63f50..fd4697bb0317 100644 --- a/x-pack/packages/security/plugin_types_server/index.ts +++ b/x-pack/packages/security/plugin_types_server/index.ts @@ -80,11 +80,10 @@ export type { CreateCrossClusterAPIKeyParams, GrantAPIKeyResult, } from '@kbn/core-security-server'; + export { restApiKeySchema, crossClusterApiKeySchema, getRestApiKeyWithKibanaPrivilegesSchema, - getKibanaRoleSchema, - elasticsearchRoleSchema, - GLOBAL_RESOURCE, -} from '@kbn/core-security-server'; +} 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 new file mode 100644 index 000000000000..ce3004848bf2 --- /dev/null +++ b/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts @@ -0,0 +1,86 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { getKibanaRoleSchema, elasticsearchRoleSchema } from '../../authorization'; + +export const restApiKeySchema = schema.object({ + type: schema.maybe(schema.literal('rest')), + name: schema.string(), + 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 getRestApiKeyWithKibanaPrivilegesSchema = ( + getBasePrivilegeNames: Parameters[0] +) => + restApiKeySchema.extends({ + role_descriptors: null, + kibana_role_descriptors: schema.recordOf( + schema.string(), + schema.object({ + elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), + kibana: getKibanaRoleSchema(getBasePrivilegeNames), + }) + ), + }); + +export const crossClusterApiKeySchema = restApiKeySchema.extends({ + type: schema.literal('cross_cluster'), + role_descriptors: null, + 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 const updateRestApiKeySchema = restApiKeySchema.extends({ + name: null, + id: schema.string(), +}); + +export const updateCrossClusterApiKeySchema = crossClusterApiKeySchema.extends({ + name: null, + id: schema.string(), +}); + +export const getUpdateRestApiKeyWithKibanaPrivilegesSchema = ( + getBasePrivilegeNames: Parameters[0] +) => + restApiKeySchema.extends({ + role_descriptors: null, + name: null, + id: schema.string(), + kibana_role_descriptors: schema.recordOf( + schema.string(), + schema.object({ + elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), + kibana: getKibanaRoleSchema(getBasePrivilegeNames), + }) + ), + }); 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 new file mode 100644 index 000000000000..167368205255 --- /dev/null +++ b/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/index.ts @@ -0,0 +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. + */ + +export { + crossClusterApiKeySchema, + getRestApiKeyWithKibanaPrivilegesSchema, + getUpdateRestApiKeyWithKibanaPrivilegesSchema, + restApiKeySchema, + updateRestApiKeySchema, + updateCrossClusterApiKeySchema, +} from './api_keys'; 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 370504fa44c2..4a5e7da782ba 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 @@ -17,7 +17,10 @@ export type { } from '@kbn/core-security-server'; export { + crossClusterApiKeySchema, + getRestApiKeyWithKibanaPrivilegesSchema, getUpdateRestApiKeyWithKibanaPrivilegesSchema, + restApiKeySchema, updateRestApiKeySchema, updateCrossClusterApiKeySchema, -} from '@kbn/core-security-server'; +} 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 2e30c8df573f..baeeeddc1fa7 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,3 +42,6 @@ export type { PrivilegeDeprecationsRolesByFeatureIdResponse, } from './deprecations'; export type { AuthorizationMode } from './mode'; + +export { GLOBAL_RESOURCE } from './constants'; +export { elasticsearchRoleSchema, getKibanaRoleSchema } from './role_schema'; diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts b/x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts new file mode 100644 index 000000000000..3d673fa25dc5 --- /dev/null +++ b/x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts @@ -0,0 +1,274 @@ +/* + * 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 _ from 'lodash'; + +import type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; + +import { GLOBAL_RESOURCE } from './constants'; + +/** + * Elasticsearch specific portion of the role definition. + * See more details at https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api.html#security-role-apis. + */ +export const elasticsearchRoleSchema = schema.object({ + /** + * An optional list of cluster privileges. These privileges define the cluster level actions that + * users with this role are able to execute + */ + cluster: schema.maybe(schema.arrayOf(schema.string())), + + /** + * An optional list of remote cluster privileges. These privileges define the remote cluster level actions that + * users with this role are able to execute + */ + remote_cluster: schema.maybe( + schema.arrayOf( + schema.object({ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + clusters: schema.arrayOf(schema.string(), { minSize: 1 }), + }) + ) + ), + + /** + * An optional list of indices permissions entries. + */ + indices: schema.maybe( + schema.arrayOf( + schema.object({ + /** + * Required list of indices (or index name patterns) to which the permissions in this + * entry apply. + */ + names: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional set of the document fields that the owners of the role have read access to. + */ + field_security: schema.maybe( + schema.recordOf( + schema.oneOf([schema.literal('grant'), schema.literal('except')]), + schema.arrayOf(schema.string()) + ) + ), + + /** + * Required list of the index level privileges that the owners of the role have on the + * specified indices. + */ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional search query that defines the documents the owners of the role have read access + * to. A document within the specified indices must match this query in order for it to be + * accessible by the owners of the role. + */ + query: schema.maybe(schema.string()), + + /** + * An optional flag used to indicate if index pattern wildcards or regexps should cover + * restricted indices. + */ + allow_restricted_indices: schema.maybe(schema.boolean()), + }) + ) + ), + + /** + * An optional list of remote indices permissions entries. + */ + remote_indices: schema.maybe( + schema.arrayOf( + schema.object({ + /** + * Required list of remote clusters to which the permissions in this entry apply. + */ + clusters: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * Required list of remote indices (or index name patterns) to which the permissions in this + * entry apply. + */ + names: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional set of the document fields that the owners of the role have read access to. + */ + field_security: schema.maybe( + schema.recordOf( + schema.oneOf([schema.literal('grant'), schema.literal('except')]), + schema.arrayOf(schema.string()) + ) + ), + + /** + * Required list of the index level privileges that the owners of the role have on the + * specified indices. + */ + privileges: schema.arrayOf(schema.string(), { minSize: 1 }), + + /** + * An optional search query that defines the documents the owners of the role have read access + * to. A document within the specified indices must match this query in order for it to be + * accessible by the owners of the role. + */ + query: schema.maybe(schema.string()), + + /** + * An optional flag used to indicate if index pattern wildcards or regexps should cover + * restricted indices. + */ + allow_restricted_indices: schema.maybe(schema.boolean()), + }) + ) + ), + + /** + * An optional list of users that the owners of this role can impersonate. + */ + run_as: schema.maybe(schema.arrayOf(schema.string())), +}); + +const allSpacesSchema = schema.arrayOf(schema.literal(GLOBAL_RESOURCE), { + minSize: 1, + maxSize: 1, +}); + +/** + * Schema for the list of space IDs used within Kibana specific role definition. + */ +const spacesSchema = schema.oneOf( + [ + allSpacesSchema, + schema.arrayOf( + schema.string({ + validate(value) { + if (!/^[a-z0-9_-]+$/.test(value)) { + return `must be lower case, a-z, 0-9, '_', and '-' are allowed`; + } + }, + }) + ), + ], + { defaultValue: [GLOBAL_RESOURCE] } +); + +const FEATURE_NAME_VALUE_REGEX = /^[a-zA-Z0-9_-]+$/; + +/** + * Kibana specific portion of the role definition. It's represented as a list of base and/or + * feature Kibana privileges. None of the entries should apply to the same spaces. + */ +export const getKibanaRoleSchema = ( + getBasePrivilegeNames: () => { global: string[]; space: string[] } +) => + schema.arrayOf( + schema.object( + { + /** + * An optional list of space IDs to which the permissions in this entry apply. If not + * specified it defaults to special "global" space ID (all spaces). + */ + spaces: spacesSchema, + + /** + * An optional list of Kibana base privileges. If this entry applies to special "global" + * space (all spaces) then specified base privileges should be within known base "global" + * privilege list, otherwise - within known "space" privilege list. Base privileges + * definition isn't allowed when feature privileges are defined and required otherwise. + */ + base: schema.maybe( + schema.conditional( + schema.siblingRef('spaces'), + allSpacesSchema, + schema.arrayOf( + schema.string({ + validate(value) { + const globalPrivileges = getBasePrivilegeNames().global; + if (!globalPrivileges.some((privilege) => privilege === value)) { + return `unknown global privilege "${value}", must be one of [${globalPrivileges}]`; + } + }, + }) + ), + schema.arrayOf( + schema.string({ + validate(value) { + const spacePrivileges = getBasePrivilegeNames().space; + if (!spacePrivileges.some((privilege) => privilege === value)) { + return `unknown space privilege "${value}", must be one of [${spacePrivileges}]`; + } + }, + }) + ) + ) + ), + + /** + * An optional dictionary of Kibana feature privileges where the key is the ID of the + * feature and the value is a list of feature specific privilege IDs. Both feature and + * privilege IDs should consist of allowed set of characters. Feature privileges + * definition isn't allowed when base privileges are defined and required otherwise. + */ + feature: schema.maybe( + schema.recordOf( + schema.string({ + validate(value) { + if (!FEATURE_NAME_VALUE_REGEX.test(value)) { + return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; + } + }, + }), + schema.arrayOf( + schema.string({ + validate(value) { + if (!FEATURE_NAME_VALUE_REGEX.test(value)) { + return `only a-z, A-Z, 0-9, '_', and '-' are allowed`; + } + }, + }) + ) + ) + ), + }, + { + validate(value) { + if ( + (value.base === undefined || value.base.length === 0) && + (value.feature === undefined || Object.values(value.feature).flat().length === 0) + ) { + return 'either [base] or [feature] is expected, but none of them specified'; + } + + if ( + value.base !== undefined && + value.base.length > 0 && + value.feature !== undefined && + Object.keys(value.feature).length > 0 + ) { + return `definition of [feature] isn't allowed when non-empty [base] is defined.`; + } + }, + } + ), + { + validate(value) { + for (const [indexA, valueA] of value.entries()) { + for (const valueB of value.slice(indexA + 1)) { + const spaceIntersection = _.intersection(valueA.spaces, valueB.spaces); + if (spaceIntersection.length !== 0) { + return `more than one privilege is applied to the following spaces: [${spaceIntersection}]`; + } + } + } + }, + } + ); + +export type ElasticsearchPrivilegesType = TypeOf; +export type KibanaPrivilegesType = TypeOf>; 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 b7751be89dd7..780092694410 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 @@ -13,6 +13,7 @@ import type { APIKeys as APIKeysType, CreateAPIKeyParams, CreateAPIKeyResult, + CreateCrossClusterAPIKeyParams, CreateRestAPIKeyParams, CreateRestAPIKeyWithKibanaPrivilegesParams, GrantAPIKeyResult, @@ -59,6 +60,30 @@ type GrantAPIKeyParams = access_token: string; }; +function isCreateRestAPIKeyParams(params: any): params is CreateRestAPIKeyParams { + return params && typeof params.name === 'string' && typeof params.role_descriptors === 'object'; +} + +function isCreateRestAPIKeyWithKibanaPrivilegesParams( + params: any +): params is CreateRestAPIKeyWithKibanaPrivilegesParams { + return ( + params && + typeof params.name === 'string' && + params.role_descriptors === null && + typeof params.kibana_role_descriptors === 'object' + ); +} + +function isCreateCrossClusterAPIKeyParams(params: any): params is CreateCrossClusterAPIKeyParams { + return ( + params && + typeof params.name === 'string' && + params.role_descriptors === null && + typeof params.access === 'object' + ); +} + /** * Class responsible for managing Elasticsearch API keys. */ @@ -162,6 +187,7 @@ export class APIKeys implements APIKeysType { this.logger.debug('Trying to create an API key'); let result: CreateAPIKeyResult; + try { if (type === 'cross_cluster') { result = await scopedClusterClient.asCurrentUser.transport.request({ diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts index 86d3eb3620a2..52c8178a651a 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts @@ -7,7 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { elasticsearchRoleSchema, getKibanaRoleSchema } from '@kbn/core-security-server'; +import { elasticsearchRoleSchema, getKibanaRoleSchema } from '@kbn/security-plugin-types-server'; import type { ElasticsearchRole } from '../../../../authorization'; import { transformPrivilegesToElasticsearchPrivileges } from '../../../../lib'; From 54952ea044bf04f6889f25a868db3cffbf7d60ac Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:54:29 +0000 Subject: [PATCH 34/45] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/packages/security/plugin_types_server/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/packages/security/plugin_types_server/tsconfig.json b/x-pack/packages/security/plugin_types_server/tsconfig.json index 9f2cf7f89f17..2f4ae387ac2b 100644 --- a/x-pack/packages/security/plugin_types_server/tsconfig.json +++ b/x-pack/packages/security/plugin_types_server/tsconfig.json @@ -14,5 +14,6 @@ "@kbn/security-plugin-types-common", "@kbn/core-user-profile-server", "@kbn/core-security-server", + "@kbn/config-schema", ] } From 50c100fa375c84a8e03d8bc5f505d7d32d8d1994 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Thu, 27 Jun 2024 17:57:47 +0200 Subject: [PATCH 35/45] remove typeguards --- .../authentication/api_keys/api_keys.ts | 24 ------------------- 1 file changed, 24 deletions(-) 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 780092694410..648da2bb2453 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 @@ -60,30 +60,6 @@ type GrantAPIKeyParams = access_token: string; }; -function isCreateRestAPIKeyParams(params: any): params is CreateRestAPIKeyParams { - return params && typeof params.name === 'string' && typeof params.role_descriptors === 'object'; -} - -function isCreateRestAPIKeyWithKibanaPrivilegesParams( - params: any -): params is CreateRestAPIKeyWithKibanaPrivilegesParams { - return ( - params && - typeof params.name === 'string' && - params.role_descriptors === null && - typeof params.kibana_role_descriptors === 'object' - ); -} - -function isCreateCrossClusterAPIKeyParams(params: any): params is CreateCrossClusterAPIKeyParams { - return ( - params && - typeof params.name === 'string' && - params.role_descriptors === null && - typeof params.access === 'object' - ); -} - /** * Class responsible for managing Elasticsearch API keys. */ From 6b1235e80e4ed8a2caccc30f6bee33d925318909 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Fri, 28 Jun 2024 16:41:58 +0200 Subject: [PATCH 36/45] fix types, update context to expose only contract we need --- .../src/security_route_handler_context.ts | 21 +- .../src/security_service.mock.ts | 8 +- .../security/core-security-server/index.ts | 2 + .../src/api_keys/api_keys.ts | 273 ------------ .../src/api_keys/index.ts | 34 -- .../src/authentication/api_keys/api_keys.ts | 73 ++-- .../api_keys/api_keys_context.ts | 64 +++ .../src/authentication/api_keys/index.ts | 2 + .../src/request_handler_context.ts | 4 +- .../security/plugin_types_server/index.ts | 1 + .../src/authentication/api_keys/api_keys.ts | 57 ++- .../server/api_keys/api_keys_service.ts | 43 -- .../plugins/security/server/api_keys/index.ts | 8 - .../authentication/api_keys/api_keys.test.ts | 17 +- .../authentication/api_keys/api_keys.ts | 17 +- x-pack/plugins/security/server/plugin.test.ts | 410 ------------------ x-pack/plugins/security/server/plugin.ts | 17 - 17 files changed, 207 insertions(+), 844 deletions(-) delete mode 100644 packages/core/security/core-security-server/src/api_keys/api_keys.ts delete mode 100644 packages/core/security/core-security-server/src/api_keys/index.ts create mode 100644 packages/core/security/core-security-server/src/authentication/api_keys/api_keys_context.ts delete mode 100644 x-pack/plugins/security/server/api_keys/api_keys_service.ts delete mode 100644 x-pack/plugins/security/server/api_keys/index.ts 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 a91daced601d..06ad28d8e95e 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,7 +26,26 @@ export class CoreSecurityRouteHandlerContext implements SecurityRequestHandlerCo if (this.#authc == null) { this.#authc = { getCurrentUser: () => this.securityStart.authc.getCurrentUser(this.request), - apiKeys: this.securityStart.authc.apiKeys, + apiKeys: { + areAPIKeysEnabled: this.securityStart.authc.apiKeys.areAPIKeysEnabled.bind( + this.securityStart.authc.apiKeys + ), + create: this.securityStart.authc.apiKeys.create.bind( + this.securityStart.authc.apiKeys, + this.request + ), + update: this.securityStart.authc.apiKeys.update.bind( + this.securityStart.authc.apiKeys, + this.request + ), + validate: this.securityStart.authc.apiKeys.validate.bind( + this.securityStart.authc.apiKeys + ), + invalidate: this.securityStart.authc.apiKeys.invalidate.bind( + this.securityStart.authc.apiKeys, + this.request + ), + }, }; } return this.#authc; 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 c19308ebcc45..8c28db943149 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 @@ -82,7 +82,13 @@ const createRequestHandlerContextMock = () => { const mock: jest.MockedObjectDeep = { authc: { getCurrentUser: jest.fn(), - apiKeys: apiKeysMock.create(), + 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 4cf71a20ca53..41d0c6ec99f9 100644 --- a/packages/core/security/core-security-server/index.ts +++ b/packages/core/security/core-security-server/index.ts @@ -28,6 +28,7 @@ export type { export type { AuditLogger } from './src/audit_logging/audit_logger'; export type { + APIKeysServiceWithContext, APIKeysService, CreateAPIKeyParams, CreateAPIKeyResult, @@ -46,3 +47,4 @@ export type { } from './src/authentication/api_keys'; export type { KibanaPrivilegesType, ElasticsearchPrivilegesType } from './src/roles'; +export { isCreateRestAPIKeyParams } from './src/authentication/api_keys'; diff --git a/packages/core/security/core-security-server/src/api_keys/api_keys.ts b/packages/core/security/core-security-server/src/api_keys/api_keys.ts deleted file mode 100644 index 595f73e86f1c..000000000000 --- a/packages/core/security/core-security-server/src/api_keys/api_keys.ts +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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 { schema, TypeOf } from '@kbn/config-schema'; -import { getKibanaRoleSchema, elasticsearchRoleSchema } 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 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 { - /** - * 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; - }; - }>; -} - -export const restApiKeySchema = schema.object({ - type: schema.maybe(schema.literal('rest')), - name: schema.string(), - 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 getRestApiKeyWithKibanaPrivilegesSchema = ( - getBasePrivilegeNames: Parameters[0] -) => - restApiKeySchema.extends({ - role_descriptors: null, - kibana_role_descriptors: schema.recordOf( - schema.string(), - schema.object({ - elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), - kibana: getKibanaRoleSchema(getBasePrivilegeNames), - }) - ), - }); - -export const crossClusterApiKeySchema = restApiKeySchema.extends({ - type: schema.literal('cross_cluster'), - role_descriptors: null, - 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' } - ), -}); - -/** - * 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, - id: schema.string(), -}); - -export const updateCrossClusterApiKeySchema = crossClusterApiKeySchema.extends({ - name: null, - id: schema.string(), -}); - -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, - id: schema.string(), - kibana_role_descriptors: schema.recordOf( - schema.string(), - schema.object({ - elasticsearch: elasticsearchRoleSchema.extends({}, { unknowns: 'allow' }), - kibana: getKibanaRoleSchema(getBasePrivilegeNames), - }) - ), - }); diff --git a/packages/core/security/core-security-server/src/api_keys/index.ts b/packages/core/security/core-security-server/src/api_keys/index.ts deleted file mode 100644 index acb9682f3f5f..000000000000 --- a/packages/core/security/core-security-server/src/api_keys/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 { - restApiKeySchema, - crossClusterApiKeySchema, - getRestApiKeyWithKibanaPrivilegesSchema, - getUpdateRestApiKeyWithKibanaPrivilegesSchema, - updateRestApiKeySchema, - updateCrossClusterApiKeySchema, -} from './api_keys'; 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 index d638585a0238..e70232960f77 100644 --- 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 @@ -105,15 +105,15 @@ export interface CreateRestAPIKeyParams { type?: 'rest'; expiration?: string; name: string; - role_descriptors: Record; - metadata?: { [key: string]: unknown }; + role_descriptors: Record; + metadata?: { [key: string]: any }; } -export type CreateRestAPIKeyWithKibanaPrivilegesParams = Omit< - CreateRestAPIKeyParams, - 'role_descriptors' -> & { - role_descriptors: null; +export interface CreateRestAPIKeyWithKibanaPrivilegesParams { + type?: 'rest'; + expiration?: string; + name: string; + metadata?: { [key: string]: any }; kibana_role_descriptors: Record< string, { @@ -121,15 +121,14 @@ export type CreateRestAPIKeyWithKibanaPrivilegesParams = Omit< kibana: KibanaPrivilegesType; } >; -}; -export type CreateCrossClusterAPIKeyParams = Omit< - CreateRestAPIKeyParams, - 'type' | 'role_descriptors' -> & { - type?: 'cross_cluster'; - role_descriptors: null; +} +export interface CreateCrossClusterAPIKeyParams { + type: 'cross_cluster'; + expiration?: string; + name: string; + metadata?: { [key: string]: any }; access: { - search: Array<{ + search?: Array<{ names: string[]; query?: unknown; field_security?: unknown; @@ -139,7 +138,7 @@ export type CreateCrossClusterAPIKeyParams = Omit< names: string[]; }>; }; -}; +} export interface GrantAPIKeyResult { /** @@ -223,21 +222,37 @@ export type UpdateAPIKeyParams = | UpdateCrossClusterAPIKeyParams | UpdateRestAPIKeyWithKibanaPrivilegesParams; -export type UpdateRestAPIKeyParams = Omit & { - name: null; +export interface UpdateRestAPIKeyParams { id: string; -}; + type?: 'rest'; + expiration?: string; + role_descriptors: Record; + metadata?: { [key: string]: any }; +} -export type UpdateCrossClusterAPIKeyParams = Omit & { - name: null; +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 type UpdateRestAPIKeyWithKibanaPrivilegesParams = Omit< - CreateRestAPIKeyParams, - 'name' | 'role_descriptors' -> & { +export interface UpdateRestAPIKeyWithKibanaPrivilegesParams { id: string; + type?: 'rest'; + expiration?: string; + metadata?: { [key: string]: any }; kibana_role_descriptors: Record< string, { @@ -245,4 +260,8 @@ export type UpdateRestAPIKeyWithKibanaPrivilegesParams = Omit< kibana: KibanaPrivilegesType; } >; -}; +} + +export function isCreateRestAPIKeyParams(params: any): params is CreateRestAPIKeyParams { + return 'role_descriptor' 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 000000000000..95a415bf4f82 --- /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 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 index 8b457d3597c4..da7163bb5087 100644 --- 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 @@ -23,3 +23,5 @@ export type { 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 81dc08542360..6cb13b3afb9a 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 @@ -9,7 +9,7 @@ import type { AuthenticatedUser } from '@kbn/core-security-common'; import { AuditLogger } from './audit_logging/audit_logger'; -import type { APIKeysService } from './authentication/api_keys'; +import type { APIKeysServiceWithContext } from './authentication/api_keys'; export interface SecurityRequestHandlerContext { authc: AuthcRequestHandlerContext; @@ -18,7 +18,7 @@ export interface SecurityRequestHandlerContext { export interface AuthcRequestHandlerContext { getCurrentUser(): AuthenticatedUser | null; - apiKeys: APIKeysService; + apiKeys: APIKeysServiceWithContext; } export interface AuditRequestHandlerContext { diff --git a/x-pack/packages/security/plugin_types_server/index.ts b/x-pack/packages/security/plugin_types_server/index.ts index fd4697bb0317..21ab0eb2b39a 100644 --- a/x-pack/packages/security/plugin_types_server/index.ts +++ b/x-pack/packages/security/plugin_types_server/index.ts @@ -80,6 +80,7 @@ export type { CreateCrossClusterAPIKeyParams, GrantAPIKeyResult, } from '@kbn/core-security-server'; +export { isCreateRestAPIKeyParams } from '@kbn/core-security-server'; export { restApiKeySchema, 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 ce3004848bf2..c331802c7f69 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 @@ -21,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({ @@ -32,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( @@ -59,22 +64,52 @@ export const crossClusterApiKeySchema = restApiKeySchema.extends({ ), }); -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 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/plugins/security/server/api_keys/api_keys_service.ts b/x-pack/plugins/security/server/api_keys/api_keys_service.ts deleted file mode 100644 index c4d0f37b48a4..000000000000 --- a/x-pack/plugins/security/server/api_keys/api_keys_service.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { IClusterClient, Logger } from '@kbn/core/server'; -import type { KibanaFeature } from '@kbn/features-plugin/server'; -import type { SecurityLicense } from '@kbn/security-plugin-types-common'; - -import { APIKeys } from '../authentication/api_keys'; - -/** - * Represents the options to create an APIKey class instance that will be - * shared between functions (create, invalidate, etc). - */ -export interface SetupOptions { - getClusterClient: () => Promise; - getKibanaFeatures: () => Promise; - license: SecurityLicense; - applicationName: string; -} - -export class APIKeysService { - private logger: Logger; - - constructor(_logger: Logger) { - this.logger = _logger.get('ecs'); - } - - setup({ getClusterClient, license, applicationName, getKibanaFeatures }: SetupOptions) { - const apiKeysService = new APIKeys({ - getClusterClient, - logger: this.logger.get('api-key'), - license, - applicationName, - getKibanaFeatures, - }); - return apiKeysService; - } - stop() {} -} diff --git a/x-pack/plugins/security/server/api_keys/index.ts b/x-pack/plugins/security/server/api_keys/index.ts deleted file mode 100644 index 86bbca84c4b9..000000000000 --- a/x-pack/plugins/security/server/api_keys/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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. - */ - -export { APIKeysService } from './api_keys_service'; 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 e790676cad2d..a2f4941c7b27 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,7 +240,7 @@ 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({ @@ -343,7 +344,7 @@ describe('API Keys', () => { const result = await apiKeys.update(httpServerMock.createKibanaRequest(), { id: 'test_id', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, metadata: {}, }); @@ -370,7 +371,7 @@ describe('API Keys', () => { const result = await apiKeys.update(httpServerMock.createKibanaRequest(), { id: 'test_id', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, metadata: {}, }); @@ -473,7 +474,7 @@ describe('API Keys', () => { }), { name: 'test_api_key', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', } ); @@ -512,7 +513,7 @@ describe('API Keys', () => { }), { name: 'test_api_key', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', } ); @@ -527,7 +528,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 +554,7 @@ describe('API Keys', () => { }), { name: 'test_api_key', - role_descriptors: { foo: true }, + role_descriptors: roleDescriptors, expiration: '1d', } ); @@ -592,7 +593,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 648da2bb2453..3f7cc073c1c2 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 @@ -13,7 +13,6 @@ import type { APIKeys as APIKeysType, CreateAPIKeyParams, CreateAPIKeyResult, - CreateCrossClusterAPIKeyParams, CreateRestAPIKeyParams, CreateRestAPIKeyWithKibanaPrivilegesParams, GrantAPIKeyResult, @@ -21,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'; @@ -178,14 +178,13 @@ export class APIKeys implements APIKeysType { name, expiration, metadata, - role_descriptors: - 'role_descriptors' in createParams - ? createParams.role_descriptors - : this.parseRoleDescriptorsWithKibanaPrivileges( - createParams.kibana_role_descriptors, - features, - false - ), + role_descriptors: isCreateRestAPIKeyParams(createParams) + ? createParams.role_descriptors + : this.parseRoleDescriptorsWithKibanaPrivileges( + createParams.kibana_role_descriptors, + features, + false + ), }, }); } diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index dac0d2f6963e..be3d00b77cff 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -79,416 +79,6 @@ describe('Security Plugin', () => { }, }, "authc": Object { - "apiKeys": APIKeys { - "applicationName": "kibana-.kibana", - "getClusterClient": [Function], - "getKibanaFeatures": [Function], - "license": Object { - "features$": Observable { - "operator": [Function], - "source": Observable { - "_subscribe": [Function], - }, - }, - "getFeatures": [Function], - "getUnavailableReason": [Function], - "hasAtLeast": [Function], - "isEnabled": [Function], - "isLicenseAvailable": [Function], - }, - "logger": Object { - "context": Array [ - "api-key", - ], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "Registering security_authentication_type event type.", - ], - Array [ - "Registering security_csp_violation event type.", - ], - Array [ - "Registering security_permissions_policy_violation event type.", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction] { - "calls": Array [ - Array [ - "ecs", - ], - Array [ - "ecs", - ], - Array [ - "cookie", - ], - Array [ - "api-key", - ], - Array [ - "deprecations", - ], - Array [ - "deprecations", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": Object { - "context": Array [ - "ecs", - ], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "Registering security_authentication_type event type.", - ], - Array [ - "Registering security_csp_violation event type.", - ], - Array [ - "Registering security_permissions_policy_violation event type.", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction] { - "calls": Array [ - Array [ - "ecs", - ], - Array [ - "ecs", - ], - Array [ - "cookie", - ], - Array [ - "api-key", - ], - Array [ - "deprecations", - ], - Array [ - "deprecations", - ], - ], - "results": [Circular], - }, - "info": [MockFunction], - "isLevelEnabled": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - }, - Object { - "type": "return", - "value": Object { - "context": Array [ - "ecs", - ], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "Registering security_authentication_type event type.", - ], - Array [ - "Registering security_csp_violation event type.", - ], - Array [ - "Registering security_permissions_policy_violation event type.", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction] { - "calls": Array [ - Array [ - "ecs", - ], - Array [ - "ecs", - ], - Array [ - "cookie", - ], - Array [ - "api-key", - ], - Array [ - "deprecations", - ], - Array [ - "deprecations", - ], - ], - "results": [Circular], - }, - "info": [MockFunction], - "isLevelEnabled": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - }, - Object { - "type": "return", - "value": Object { - "context": Array [ - "cookie", - ], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "Registering security_authentication_type event type.", - ], - Array [ - "Registering security_csp_violation event type.", - ], - Array [ - "Registering security_permissions_policy_violation event type.", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction] { - "calls": Array [ - Array [ - "ecs", - ], - Array [ - "ecs", - ], - Array [ - "cookie", - ], - Array [ - "api-key", - ], - Array [ - "deprecations", - ], - Array [ - "deprecations", - ], - ], - "results": [Circular], - }, - "info": [MockFunction], - "isLevelEnabled": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - }, - Object { - "type": "return", - "value": [Circular], - }, - Object { - "type": "return", - "value": Object { - "context": Array [ - "deprecations", - ], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "Registering security_authentication_type event type.", - ], - Array [ - "Registering security_csp_violation event type.", - ], - Array [ - "Registering security_permissions_policy_violation event type.", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction] { - "calls": Array [ - Array [ - "ecs", - ], - Array [ - "ecs", - ], - Array [ - "cookie", - ], - Array [ - "api-key", - ], - Array [ - "deprecations", - ], - Array [ - "deprecations", - ], - ], - "results": [Circular], - }, - "info": [MockFunction], - "isLevelEnabled": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - }, - Object { - "type": "return", - "value": Object { - "context": Array [ - "deprecations", - ], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "Registering security_authentication_type event type.", - ], - Array [ - "Registering security_csp_violation event type.", - ], - Array [ - "Registering security_permissions_policy_violation event type.", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - Object { - "type": "return", - "value": undefined, - }, - ], - }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction] { - "calls": Array [ - Array [ - "ecs", - ], - Array [ - "ecs", - ], - Array [ - "cookie", - ], - Array [ - "api-key", - ], - Array [ - "deprecations", - ], - Array [ - "deprecations", - ], - ], - "results": [Circular], - }, - "info": [MockFunction], - "isLevelEnabled": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - }, - ], - }, - "info": [MockFunction], - "isLevelEnabled": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - }, "getCurrentUser": [Function], }, "authz": Object { diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 9d57839288d2..c166b5f0613f 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -24,7 +24,6 @@ import type { } from '@kbn/features-plugin/server'; import type { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/server'; import type { - APIKeys as APIKeysType, AuditServiceSetup, AuthorizationServiceSetup, SecurityPluginSetup as SecurityPluginSetupWithoutDeprecatedMembers, @@ -40,7 +39,6 @@ import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { AnalyticsService } from './analytics'; import type { AnonymousAccessServiceStart } from './anonymous_access'; import { AnonymousAccessService } from './anonymous_access'; -import { APIKeysService } from './api_keys'; import { AuditService } from './audit'; import type { InternalAuthenticationServiceStart } from './authentication'; import { AuthenticationService } from './authentication'; @@ -63,7 +61,6 @@ import { registerSecurityUsageCollector } from './usage_collector'; import { UserProfileService } from './user_profile'; import type { UserProfileServiceStartInternal } from './user_profile'; import type { AuthenticatedUser, SecurityLicense } from '../common'; -import { APPLICATION_PREFIX } from '../common/constants'; import { SecurityLicenseService } from '../common/licensing'; export type SpacesService = Pick< @@ -80,7 +77,6 @@ export interface SecurityPluginSetup extends SecurityPluginSetupWithoutDeprecate */ authc: { getCurrentUser: (request: KibanaRequest) => AuthenticatedUser | null; - apiKeys: APIKeysType | null; }; /** * @deprecated Use `authz` methods from the `SecurityServiceStart` contract instead. @@ -160,7 +156,6 @@ export class SecurityPlugin }; private readonly auditService: AuditService; - private readonly apiKeysService: APIKeysService; private readonly securityLicenseService = new SecurityLicenseService(); private readonly analyticsService: AnalyticsService; private readonly authorizationService = new AuthorizationService(); @@ -192,7 +187,6 @@ export class SecurityPlugin this.initializerContext.logger.get('authentication') ); this.auditService = new AuditService(this.initializerContext.logger.get('audit')); - this.apiKeysService = new APIKeysService(this.initializerContext.logger.get('api-keys')); this.elasticsearchService = new ElasticsearchService( this.initializerContext.logger.get('elasticsearch') @@ -271,16 +265,6 @@ export class SecurityPlugin recordAuditLoggingUsage: () => this.getFeatureUsageService().recordAuditLoggingUsage(), }); - const applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`; - const apiKeysSetup = this.apiKeysService.setup({ - getClusterClient: () => - startServicesPromise.then(({ elasticsearch }) => elasticsearch.client), - license, - applicationName, - getKibanaFeatures: () => - startServicesPromise.then((services) => services.features.getKibanaFeatures()), - }); - this.anonymousAccessService.setup(); this.authorizationSetup = this.authorizationService.setup({ @@ -352,7 +336,6 @@ export class SecurityPlugin audit: this.auditSetup, authc: { getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request), - apiKeys: apiKeysSetup, }, authz: { actions: this.authorizationSetup.actions, From 087db0645cf4f224b7fbd6a510766150110759ed Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Fri, 28 Jun 2024 16:43:27 +0200 Subject: [PATCH 37/45] add reject when api disabled --- .../src/utils/default_implementation.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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 1646b97a297a..8eaeb7b2577b 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 @@ -9,6 +9,7 @@ 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 { @@ -17,12 +18,12 @@ export const getDefaultSecurityImplementation = (): CoreSecurityDelegateContract apiKeys: { areAPIKeysEnabled: () => Promise.resolve(false), areCrossClusterAPIKeysEnabled: () => Promise.resolve(false), - create: () => Promise.reject(API_KEYS_DISABLED_ERROR), - update: () => Promise.reject(API_KEYS_DISABLED_ERROR), - grantAsInternalUser: () => Promise.reject(API_KEYS_DISABLED_ERROR), - validate: () => Promise.reject(API_KEYS_DISABLED_ERROR), - invalidate: () => Promise.reject(API_KEYS_DISABLED_ERROR), - invalidateAsInternalUser: () => Promise.reject(API_KEYS_DISABLED_ERROR), + 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: { From 06d897d691c9ea924f6737b6530de4e5fad9460c Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 28 Jun 2024 14:54:41 +0000 Subject: [PATCH 38/45] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/core/security/core-security-server/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/security/core-security-server/tsconfig.json b/packages/core/security/core-security-server/tsconfig.json index 12c5c8379c7b..0304c8ef6dee 100644 --- a/packages/core/security/core-security-server/tsconfig.json +++ b/packages/core/security/core-security-server/tsconfig.json @@ -17,6 +17,5 @@ "@kbn/core-security-common", "@kbn/core-http-server", "@kbn/logging", - "@kbn/config-schema", ] } From b0796068073dc5c5fdb26850a269fb9865e3543f Mon Sep 17 00:00:00 2001 From: Sid Date: Fri, 28 Jun 2024 17:03:32 +0200 Subject: [PATCH 39/45] Update api_keys_context.ts --- .../src/authentication/api_keys/api_keys_context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 95a415bf4f82..7090f7312774 100644 --- 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 @@ -17,7 +17,7 @@ import type { } from './api_keys'; /** - * Public API Keys service exposed through core to manage + * 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. From 51b5177edab387f4836fe713bbd103d45efb7339 Mon Sep 17 00:00:00 2001 From: Sid Date: Fri, 28 Jun 2024 17:04:33 +0200 Subject: [PATCH 40/45] Update api_keys.ts --- .../core-security-server/src/authentication/api_keys/api_keys.ts | 1 + 1 file changed, 1 insertion(+) 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 index e70232960f77..3037114971be 100644 --- 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 @@ -122,6 +122,7 @@ export interface CreateRestAPIKeyWithKibanaPrivilegesParams { } >; } + export interface CreateCrossClusterAPIKeyParams { type: 'cross_cluster'; expiration?: string; From 7b2c26faf377a405bbce6fa3ba2fffefdaed48fd Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Fri, 28 Jun 2024 17:15:38 +0200 Subject: [PATCH 41/45] revert changes to api keys class constructor --- .../authentication/api_keys/api_keys.test.ts | 4 +- .../authentication/api_keys/api_keys.ts | 51 ++++++++----------- .../authentication/authentication_service.ts | 4 +- 3 files changed, 25 insertions(+), 34 deletions(-) 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 a2f4941c7b27..9c03cc7dc477 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 @@ -46,11 +46,11 @@ describe('API Keys', () => { logger = loggingSystemMock.create().get('api-keys'); apiKeys = new APIKeys({ - getClusterClient: () => Promise.resolve(mockClusterClient), + clusterClient: mockClusterClient, logger, license: mockLicense, applicationName: 'kibana-.kibana', - getKibanaFeatures: () => Promise.resolve([]), + kibanaFeatures: [], }); }); 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 3f7cc073c1c2..054ab59fbd0b 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 @@ -41,10 +41,10 @@ const ELASTICSEARCH_CLIENT_AUTHENTICATION_HEADER = 'es-client-authentication'; */ export interface ConstructorOptions { logger: Logger; - getClusterClient: () => Promise; + clusterClient: IClusterClient; license: SecurityLicense; applicationName: string; - getKibanaFeatures: () => Promise; + kibanaFeatures: KibanaFeature[]; } type GrantAPIKeyParams = @@ -65,23 +65,23 @@ type GrantAPIKeyParams = */ export class APIKeys implements APIKeysType { private readonly logger: Logger; - private readonly getClusterClient: () => Promise; + private readonly clusterClient: IClusterClient; private readonly license: SecurityLicense; private readonly applicationName: string; - private readonly getKibanaFeatures: () => Promise; + private readonly kibanaFeatures: KibanaFeature[]; constructor({ logger, - getClusterClient, + clusterClient, license, applicationName, - getKibanaFeatures, + kibanaFeatures, }: ConstructorOptions) { this.logger = logger; - this.getClusterClient = getClusterClient; + this.clusterClient = clusterClient; this.license = license; this.applicationName = applicationName; - this.getKibanaFeatures = getKibanaFeatures; + this.kibanaFeatures = kibanaFeatures; } /** @@ -97,9 +97,8 @@ export class APIKeys implements APIKeysType { this.logger.debug( `Testing if API Keys are enabled by attempting to invalidate a non-existant key: ${id}` ); - const clusterClient = await this.getClusterClient(); try { - await clusterClient.asInternalUser.security.invalidateApiKey({ + await this.clusterClient.asInternalUser.security.invalidateApiKey({ body: { ids: [id], }, @@ -126,9 +125,8 @@ 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}` ); - const clusterClient = await this.getClusterClient(); try { - await clusterClient.asInternalUser.transport.request({ + await this.clusterClient.asInternalUser.transport.request({ method: 'PUT', path: `/_security/cross_cluster/api_key/${id}`, body: {}, // We are sending an empty request body and expect a validation error if Update cross-cluster API key endpoint is available. @@ -156,9 +154,8 @@ export class APIKeys implements APIKeysType { if (!this.license.isEnabled()) { return null; } - const clusterClient = await this.getClusterClient(); const { type, expiration, name, metadata } = createParams; - const scopedClusterClient = clusterClient.asScoped(request); + const scopedClusterClient = this.clusterClient.asScoped(request); this.logger.debug('Trying to create an API key'); @@ -172,7 +169,6 @@ export class APIKeys implements APIKeysType { body: { name, expiration, metadata, access: createParams.access }, }); } else { - const features = await this.getKibanaFeatures(); result = await scopedClusterClient.asCurrentUser.security.createApiKey({ body: { name, @@ -182,7 +178,7 @@ export class APIKeys implements APIKeysType { ? createParams.role_descriptors : this.parseRoleDescriptorsWithKibanaPrivileges( createParams.kibana_role_descriptors, - features, + this.kibanaFeatures, false ), }, @@ -214,9 +210,9 @@ export class APIKeys implements APIKeysType { if (!this.license.isEnabled()) { return null; } - const clusterClient = await this.getClusterClient(); + const { type, id, metadata } = updateParams; - const scopedClusterClient = clusterClient.asScoped(request); + const scopedClusterClient = this.clusterClient.asScoped(request); this.logger.debug('Trying to edit an API key'); @@ -229,7 +225,6 @@ export class APIKeys implements APIKeysType { body: { metadata, access: updateParams.access }, }); } else { - const features = await this.getKibanaFeatures(); result = await scopedClusterClient.asCurrentUser.security.updateApiKey({ id, metadata, @@ -238,7 +233,7 @@ export class APIKeys implements APIKeysType { ? updateParams.role_descriptors : this.parseRoleDescriptorsWithKibanaPrivileges( updateParams.kibana_role_descriptors, - features, + this.kibanaFeatures, true ), }); @@ -284,13 +279,12 @@ export class APIKeys implements APIKeysType { ); const { expiration, metadata, name } = createParams; - const features = await this.getKibanaFeatures(); const roleDescriptors = 'role_descriptors' in createParams ? createParams.role_descriptors : this.parseRoleDescriptorsWithKibanaPrivileges( createParams.kibana_role_descriptors, - features, + this.kibanaFeatures, false ); @@ -299,11 +293,10 @@ export class APIKeys implements APIKeysType { authorizationHeader, clientAuthorizationHeader ); - const clusterClient = await this.getClusterClient(); // User needs `manage_api_key` or `grant_api_key` privilege to use this API let result: GrantAPIKeyResult; try { - result = await clusterClient.asInternalUser.security.grantApiKey({ body: params }); + result = await this.clusterClient.asInternalUser.security.grantApiKey({ body: params }); this.logger.debug('API key was granted successfully'); } catch (e) { this.logger.error(`Failed to grant API key: ${e.message}`); @@ -324,11 +317,10 @@ export class APIKeys implements APIKeysType { } this.logger.debug(`Trying to invalidate ${params.ids.length} an API key as current user`); - const clusterClient = await this.getClusterClient(); let result: InvalidateAPIKeyResult; try { // User needs `manage_api_key` privilege to use this API - result = await clusterClient.asScoped(request).asCurrentUser.security.invalidateApiKey({ + result = await this.clusterClient.asScoped(request).asCurrentUser.security.invalidateApiKey({ body: { ids: params.ids, }, @@ -360,10 +352,10 @@ export class APIKeys implements APIKeysType { this.logger.debug(`Trying to invalidate ${params.ids.length} API keys`); let result: InvalidateAPIKeyResult; - const clusterClient = await this.getClusterClient(); + try { // Internal user needs `cluster:admin/xpack/security/api_key/invalidate` privilege to use this API - result = await clusterClient.asInternalUser.security.invalidateApiKey({ + result = await this.clusterClient.asInternalUser.security.invalidateApiKey({ body: { ids: params.ids, }, @@ -391,9 +383,8 @@ export class APIKeys implements APIKeysType { const fakeRequest = getFakeKibanaRequest(apiKeyPrams); this.logger.debug(`Trying to validate an API key`); - const clusterClient = await this.getClusterClient(); try { - await clusterClient.asScoped(fakeRequest).asCurrentUser.security.authenticate(); + await this.clusterClient.asScoped(fakeRequest).asCurrentUser.security.authenticate(); this.logger.debug(`API key was validated successfully`); return true; } catch (e) { diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index ec5aaa0824da..cf99084d4d0b 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -342,11 +342,11 @@ export class AuthenticationService { customLogoutURL, }: AuthenticationServiceStartParams): InternalAuthenticationServiceStart { const apiKeys = new APIKeys({ - getClusterClient: () => Promise.resolve(clusterClient), + clusterClient, logger: this.logger.get('api-key'), license: this.license, applicationName, - getKibanaFeatures: () => Promise.resolve(kibanaFeatures), + kibanaFeatures, }); /** * Retrieves server protocol name/host name/port and merges it with `xpack.security.public` config From caddc9014d1108c7455e7b13e3ccee4b1ef1c4e1 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Fri, 28 Jun 2024 18:54:13 +0200 Subject: [PATCH 42/45] update type guard to use correct key --- .../src/authentication/api_keys/api_keys.ts | 2 +- .../security/server/authentication/api_keys/api_keys.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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 index 3037114971be..e842c38d8674 100644 --- 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 @@ -264,5 +264,5 @@ export interface UpdateRestAPIKeyWithKibanaPrivilegesParams { } export function isCreateRestAPIKeyParams(params: any): params is CreateRestAPIKeyParams { - return 'role_descriptor' in params; + return 'role_descriptors' in params; } 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 9c03cc7dc477..198bc004ecb6 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 @@ -243,6 +243,7 @@ describe('API Keys', () => { role_descriptors: roleDescriptors, expiration: '1d', }); + expect(result).toEqual({ api_key: 'abc123', expiration: '1d', @@ -254,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', }, }); From 7435d8728d0ccf0e3795f7dbe020d776e2e169e7 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:48:48 +0000 Subject: [PATCH 43/45] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../src/authentication/authentication_service.ts | 1 - 1 file changed, 1 deletion(-) 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 e11d714e04c4..5d066bb6565c 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 @@ -9,7 +9,6 @@ import type { KibanaRequest } from '@kbn/core/server'; 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. */ From df6e9456f24e01696610beb9327e248d1f5f350b Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:44:59 +0000 Subject: [PATCH 44/45] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/elastic_assistant/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/elastic_assistant/tsconfig.json b/x-pack/plugins/elastic_assistant/tsconfig.json index f63a8da53019..8f546d6e5fe0 100644 --- a/x-pack/plugins/elastic_assistant/tsconfig.json +++ b/x-pack/plugins/elastic_assistant/tsconfig.json @@ -45,6 +45,7 @@ "@kbn/core-saved-objects-api-server", "@kbn/langchain", "@kbn/stack-connectors-plugin", + "@kbn/security-plugin", ], "exclude": [ "target/**/*", From 1faeaa4d5923bb820879448b99bef7ff1ecd9bc6 Mon Sep 17 00:00:00 2001 From: Sid Mantri Date: Tue, 2 Jul 2024 11:31:57 +0200 Subject: [PATCH 45/45] use arrow functions instead of bind --- .../src/security_route_handler_context.ts | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) 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 06ad28d8e95e..bae1c11d152a 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 @@ -27,24 +27,14 @@ export class CoreSecurityRouteHandlerContext implements SecurityRequestHandlerCo this.#authc = { getCurrentUser: () => this.securityStart.authc.getCurrentUser(this.request), apiKeys: { - areAPIKeysEnabled: this.securityStart.authc.apiKeys.areAPIKeysEnabled.bind( - this.securityStart.authc.apiKeys - ), - create: this.securityStart.authc.apiKeys.create.bind( - this.securityStart.authc.apiKeys, - this.request - ), - update: this.securityStart.authc.apiKeys.update.bind( - this.securityStart.authc.apiKeys, - this.request - ), - validate: this.securityStart.authc.apiKeys.validate.bind( - this.securityStart.authc.apiKeys - ), - invalidate: this.securityStart.authc.apiKeys.invalidate.bind( - this.securityStart.authc.apiKeys, - this.request - ), + 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), }, }; }