From e6c3e6e693df903521e3e74838697c2261fed345 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:13:18 -0700 Subject: [PATCH] [ResponseOps] Cleanup alerting RBAC exception code (#197719) Resolves https://github.com/elastic/response-ops-team/issues/250 ## Summary This PR eliminates the alerting RBAC exemption code. It removes all references to `getAuthorizationModeBySource` and `bulkGetAuthorizationModeBySource`, along with the corresponding legacy RBAC usage counters. Additionally, downstream code paths that rely on RBAC for authorization have been updated, and all related test cases have been removed. --- .../actions_client/actions_client.test.ts | 114 ------- .../server/actions_client/actions_client.ts | 65 +--- .../connector/methods/execute/execute.ts | 64 ++-- .../connector/methods/get_all/get_all.test.ts | 19 -- .../actions_authorization.test.ts | 19 -- .../authorization/actions_authorization.ts | 68 ++-- .../get_authorization_mode_by_source.test.ts | 277 ---------------- .../get_authorization_mode_by_source.ts | 90 ----- .../lib/track_legacy_rbac_exemption.test.ts | 44 --- .../server/lib/track_legacy_rbac_exemption.ts | 22 -- x-pack/plugins/actions/server/plugin.ts | 18 +- .../group2/tests/alerting/index.ts | 36 +- .../group2/tests/alerting/rbac_legacy.ts | 310 ------------------ 13 files changed, 75 insertions(+), 1071 deletions(-) delete mode 100644 x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.test.ts delete mode 100644 x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.ts delete mode 100644 x-pack/plugins/actions/server/lib/track_legacy_rbac_exemption.test.ts delete mode 100644 x-pack/plugins/actions/server/lib/track_legacy_rbac_exemption.ts delete mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/rbac_legacy.ts diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts index 7f15dd6287d6b..fbb64fd8303c9 100644 --- a/x-pack/plugins/actions/server/actions_client/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client/actions_client.test.ts @@ -39,13 +39,7 @@ import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/us import { actionExecutorMock } from '../lib/action_executor.mock'; import { v4 as uuidv4 } from 'uuid'; import { ActionsAuthorization } from '../authorization/actions_authorization'; -import { - getAuthorizationModeBySource, - AuthorizationMode, - bulkGetAuthorizationModeBySource, -} from '../authorization/get_authorization_mode_by_source'; import { actionsAuthorizationMock } from '../authorization/actions_authorization.mock'; -import { trackLegacyRBACExemption } from '../lib/track_legacy_rbac_exemption'; import { ConnectorTokenClient } from '../lib/connector_token_client'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { SavedObject } from '@kbn/core/server'; @@ -68,25 +62,6 @@ jest.mock('@kbn/core-saved-objects-utils-server', () => { }; }); -jest.mock('../lib/track_legacy_rbac_exemption', () => ({ - trackLegacyRBACExemption: jest.fn(), -})); - -jest.mock('../authorization/get_authorization_mode_by_source', () => { - return { - getAuthorizationModeBySource: jest.fn(() => { - return 1; - }), - bulkGetAuthorizationModeBySource: jest.fn(() => { - return 1; - }), - AuthorizationMode: { - Legacy: 0, - RBAC: 1, - }, - }; -}); - jest.mock('../lib/get_oauth_jwt_access_token', () => ({ getOAuthJwtAccessToken: jest.fn(), })); @@ -2745,9 +2720,6 @@ describe('update()', () => { describe('execute()', () => { describe('authorization', () => { test('ensures user is authorised to excecute actions', async () => { - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.RBAC; - }); unsecuredSavedObjectsClient.get.mockResolvedValueOnce(actionTypeIdFromSavedObjectMock()); await actionsClient.execute({ actionId: 'action-id', @@ -2764,9 +2736,6 @@ describe('execute()', () => { }); test('throws when user is not authorised to create the type of action', async () => { - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.RBAC; - }); authorization.ensureAuthorized.mockRejectedValue( new Error(`Unauthorized to execute all actions`) ); @@ -2790,28 +2759,7 @@ describe('execute()', () => { }); }); - test('tracks legacy RBAC', async () => { - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.Legacy; - }); - - await actionsClient.execute({ - actionId: 'action-id', - params: { - name: 'my name', - }, - source: asHttpRequestExecutionSource(request), - }); - - expect(trackLegacyRBACExemption as jest.Mock).toBeCalledWith('execute', mockUsageCounter); - expect(authorization.ensureAuthorized).not.toHaveBeenCalled(); - }); - test('ensures that system actions privileges are being authorized correctly', async () => { - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.RBAC; - }); - actionsClient = new ActionsClient({ inMemoryConnectors: [ { @@ -2872,10 +2820,6 @@ describe('execute()', () => { }); test('does not authorize kibana privileges for non system actions', async () => { - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.RBAC; - }); - actionsClient = new ActionsClient({ inMemoryConnectors: [ { @@ -2939,10 +2883,6 @@ describe('execute()', () => { }); test('pass the params to the actionTypeRegistry when authorizing system actions', async () => { - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.RBAC; - }); - const getKibanaPrivileges = jest.fn().mockReturnValue(['test/create']); actionsClient = new ActionsClient({ @@ -3106,9 +3046,6 @@ describe('execute()', () => { describe('bulkEnqueueExecution()', () => { describe('authorization', () => { test('ensures user is authorised to execute actions', async () => { - (bulkGetAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return { [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }; - }); await actionsClient.bulkEnqueueExecution([ { id: uuidv4(), @@ -3136,9 +3073,6 @@ describe('bulkEnqueueExecution()', () => { }); test('throws when user is not authorised to create the type of action', async () => { - (bulkGetAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return { [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }; - }); authorization.ensureAuthorized.mockRejectedValue( new Error(`Unauthorized to execute all actions`) ); @@ -3170,45 +3104,9 @@ describe('bulkEnqueueExecution()', () => { operation: 'execute', }); }); - - test('tracks legacy RBAC', async () => { - (bulkGetAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return { [AuthorizationMode.RBAC]: 0, [AuthorizationMode.Legacy]: 2 }; - }); - - await actionsClient.bulkEnqueueExecution([ - { - id: uuidv4(), - params: {}, - spaceId: 'default', - executionId: '123abc', - apiKey: null, - source: asHttpRequestExecutionSource(request), - actionTypeId: 'my-action-type', - }, - { - id: uuidv4(), - params: {}, - spaceId: 'default', - executionId: '456def', - apiKey: null, - source: asHttpRequestExecutionSource(request), - actionTypeId: 'my-action-type', - }, - ]); - - expect(trackLegacyRBACExemption as jest.Mock).toBeCalledWith( - 'bulkEnqueueExecution', - mockUsageCounter, - 2 - ); - }); }); test('calls the bulkExecutionEnqueuer with the appropriate parameters', async () => { - (bulkGetAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return { [AuthorizationMode.RBAC]: 0, [AuthorizationMode.Legacy]: 0 }; - }); const opts = [ { id: uuidv4(), @@ -3504,17 +3402,11 @@ describe('getGlobalExecutionLogWithAuth()', () => { test('ensures user is authorised to access logs', async () => { eventLogClient.aggregateEventsWithAuthFilter.mockResolvedValue(results); - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.RBAC; - }); await actionsClient.getGlobalExecutionLogWithAuth(opts); expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ operation: 'get' }); }); test('throws when user is not authorised to access logs', async () => { - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.RBAC; - }); authorization.ensureAuthorized.mockRejectedValue(new Error(`Unauthorized to access logs`)); await expect(actionsClient.getGlobalExecutionLogWithAuth(opts)).rejects.toMatchInlineSnapshot( @@ -3563,17 +3455,11 @@ describe('getGlobalExecutionKpiWithAuth()', () => { test('ensures user is authorised to access kpi', async () => { eventLogClient.aggregateEventsWithAuthFilter.mockResolvedValue(results); - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.RBAC; - }); await actionsClient.getGlobalExecutionKpiWithAuth(opts); expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ operation: 'get' }); }); test('throws when user is not authorised to access kpi', async () => { - (getAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => { - return AuthorizationMode.RBAC; - }); authorization.ensureAuthorized.mockRejectedValue(new Error(`Unauthorized to access kpi`)); await expect(actionsClient.getGlobalExecutionKpiWithAuth(opts)).rejects.toMatchInlineSnapshot( diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.ts b/x-pack/plugins/actions/server/actions_client/actions_client.ts index de029ed2acf54..5c563fbd6aa82 100644 --- a/x-pack/plugins/actions/server/actions_client/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client/actions_client.ts @@ -10,7 +10,7 @@ import url from 'url'; import { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { i18n } from '@kbn/i18n'; -import { compact, uniq } from 'lodash'; +import { uniq } from 'lodash'; import { IScopedClusterClient, SavedObjectsClientContract, @@ -35,7 +35,7 @@ import { IExecutionLogResult, } from '../../common'; import { ActionTypeRegistry } from '../action_type_registry'; -import { ActionExecutorContract, ActionExecutionSource, parseDate } from '../lib'; +import { ActionExecutorContract, parseDate } from '../lib'; import { ActionResult, RawAction, @@ -52,13 +52,7 @@ import { ExecutionResponse, } from '../create_execute_function'; import { ActionsAuthorization } from '../authorization/actions_authorization'; -import { - getAuthorizationModeBySource, - bulkGetAuthorizationModeBySource, - AuthorizationMode, -} from '../authorization/get_authorization_mode_by_source'; import { connectorAuditEvent, ConnectorAuditAction } from '../lib/audit_events'; -import { trackLegacyRBACExemption } from '../lib/track_legacy_rbac_exemption'; import { ActionsConfigurationUtilities } from '../actions_config'; import { OAuthClientCredentialsParams, @@ -496,51 +490,26 @@ export class ActionsClient { public async bulkEnqueueExecution( options: EnqueueExecutionOptions[] ): Promise { - const sources: Array> = compact( - (options ?? []).map((option) => option.source) - ); - - const authModes = await bulkGetAuthorizationModeBySource( - this.context.logger, - this.context.unsecuredSavedObjectsClient, - sources + /** + * For scheduled executions the additional authorization check + * for system actions (kibana privileges) will be performed + * inside the ActionExecutor at execution time + */ + await this.context.authorization.ensureAuthorized({ operation: 'execute' }); + await Promise.all( + uniq(options.map((o) => o.actionTypeId)).map((actionTypeId) => + this.context.authorization.ensureAuthorized({ operation: 'execute', actionTypeId }) + ) ); - if (authModes[AuthorizationMode.RBAC] > 0) { - /** - * For scheduled executions the additional authorization check - * for system actions (kibana privileges) will be performed - * inside the ActionExecutor at execution time - */ - await this.context.authorization.ensureAuthorized({ operation: 'execute' }); - await Promise.all( - uniq(options.map((o) => o.actionTypeId)).map((actionTypeId) => - this.context.authorization.ensureAuthorized({ operation: 'execute', actionTypeId }) - ) - ); - } - if (authModes[AuthorizationMode.Legacy] > 0) { - trackLegacyRBACExemption( - 'bulkEnqueueExecution', - this.context.usageCounter, - authModes[AuthorizationMode.Legacy] - ); - } return this.context.bulkExecutionEnqueuer(this.context.unsecuredSavedObjectsClient, options); } public async ephemeralEnqueuedExecution(options: EnqueueExecutionOptions): Promise { - const { source } = options; - if ( - (await getAuthorizationModeBySource(this.context.unsecuredSavedObjectsClient, source)) === - AuthorizationMode.RBAC - ) { - await this.context.authorization.ensureAuthorized({ - operation: 'execute', - actionTypeId: options.actionTypeId, - }); - } else { - trackLegacyRBACExemption('ephemeralEnqueuedExecution', this.context.usageCounter); - } + await this.context.authorization.ensureAuthorized({ + operation: 'execute', + actionTypeId: options.actionTypeId, + }); + return this.context.ephemeralExecutionEnqueuer( this.context.unsecuredSavedObjectsClient, options diff --git a/x-pack/plugins/actions/server/application/connector/methods/execute/execute.ts b/x-pack/plugins/actions/server/application/connector/methods/execute/execute.ts index f9922e0b61a8d..fc96f3a38caf5 100644 --- a/x-pack/plugins/actions/server/application/connector/methods/execute/execute.ts +++ b/x-pack/plugins/actions/server/application/connector/methods/execute/execute.ts @@ -10,11 +10,6 @@ import { RawAction, ActionTypeExecutorResult } from '../../../../types'; import { getSystemActionKibanaPrivileges } from '../../../../lib/get_system_action_kibana_privileges'; import { isPreconfigured } from '../../../../lib/is_preconfigured'; import { isSystemAction } from '../../../../lib/is_system_action'; -import { - getAuthorizationModeBySource, - AuthorizationMode, -} from '../../../../authorization/get_authorization_mode_by_source'; -import { trackLegacyRBACExemption } from '../../../../lib/track_legacy_rbac_exemption'; import { ConnectorExecuteParams } from './types'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../constants/saved_objects'; import { ActionsClientContext } from '../../../../actions_client'; @@ -25,43 +20,34 @@ export async function execute( ): Promise> { const log = context.logger; const { actionId, params, source, relatedSavedObjects } = connectorExecuteParams; - - if ( - (await getAuthorizationModeBySource(context.unsecuredSavedObjectsClient, source)) === - AuthorizationMode.RBAC - ) { - const additionalPrivileges = getSystemActionKibanaPrivileges(context, actionId, params); - let actionTypeId: string | undefined; - - try { - if (isPreconfigured(context, actionId) || isSystemAction(context, actionId)) { - const connector = context.inMemoryConnectors.find( - (inMemoryConnector) => inMemoryConnector.id === actionId - ); - - actionTypeId = connector?.actionTypeId; - } else { - // TODO: Optimize so we don't do another get on top of getAuthorizationModeBySource and within the actionExecutor.execute - const { attributes } = await context.unsecuredSavedObjectsClient.get( - ACTION_SAVED_OBJECT_TYPE, - actionId - ); - - actionTypeId = attributes.actionTypeId; - } - } catch (err) { - log.debug(`Failed to retrieve actionTypeId for action [${actionId}]`, err); + const additionalPrivileges = getSystemActionKibanaPrivileges(context, actionId, params); + let actionTypeId: string | undefined; + + try { + if (isPreconfigured(context, actionId) || isSystemAction(context, actionId)) { + const connector = context.inMemoryConnectors.find( + (inMemoryConnector) => inMemoryConnector.id === actionId + ); + + actionTypeId = connector?.actionTypeId; + } else { + const { attributes } = await context.unsecuredSavedObjectsClient.get( + ACTION_SAVED_OBJECT_TYPE, + actionId + ); + + actionTypeId = attributes.actionTypeId; } - - await context.authorization.ensureAuthorized({ - operation: 'execute', - additionalPrivileges, - actionTypeId, - }); - } else { - trackLegacyRBACExemption('execute', context.usageCounter); + } catch (err) { + log.debug(`Failed to retrieve actionTypeId for action [${actionId}]`, err); } + await context.authorization.ensureAuthorized({ + operation: 'execute', + additionalPrivileges, + actionTypeId, + }); + return context.actionExecutor.execute({ actionId, params, diff --git a/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts b/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts index ee11cf6a35d82..0f86a8e582e34 100644 --- a/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts +++ b/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts @@ -36,25 +36,6 @@ jest.mock('@kbn/core-saved-objects-utils-server', () => { }; }); -jest.mock('../../../../lib/track_legacy_rbac_exemption', () => ({ - trackLegacyRBACExemption: jest.fn(), -})); - -jest.mock('../../../../authorization/get_authorization_mode_by_source', () => { - return { - getAuthorizationModeBySource: jest.fn(() => { - return 1; - }), - bulkGetAuthorizationModeBySource: jest.fn(() => { - return 1; - }), - AuthorizationMode: { - Legacy: 0, - RBAC: 1, - }, - }; -}); - jest.mock('../../../../lib/get_oauth_jwt_access_token', () => ({ getOAuthJwtAccessToken: jest.fn(), })); diff --git a/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts b/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts index 41eab4fbc2e43..7755071e69c24 100644 --- a/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts +++ b/x-pack/plugins/actions/server/authorization/actions_authorization.test.ts @@ -12,7 +12,6 @@ import { ACTION_SAVED_OBJECT_TYPE, ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, } from '../constants/saved_objects'; -import { AuthorizationMode } from './get_authorization_mode_by_source'; import { CONNECTORS_ADVANCED_EXECUTE_PRIVILEGE_API_TAG, CONNECTORS_BASIC_EXECUTE_PRIVILEGE_API_TAG, @@ -164,24 +163,6 @@ describe('ensureAuthorized', () => { ).rejects.toThrowErrorMatchingInlineSnapshot(`"Unauthorized to create a \\"myType\\" action"`); }); - test('exempts users from requiring privileges to execute actions when authorizationMode is Legacy', async () => { - const { authorization } = mockSecurity(); - const checkPrivileges: jest.MockedFunction< - ReturnType - > = jest.fn(); - authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); - const actionsAuthorization = new ActionsAuthorization({ - request, - authorization, - authorizationMode: AuthorizationMode.Legacy, - }); - - await actionsAuthorization.ensureAuthorized({ operation: 'execute', actionTypeId: 'myType' }); - - expect(authorization.actions.savedObject.get).not.toHaveBeenCalled(); - expect(checkPrivileges).not.toHaveBeenCalled(); - }); - test('checks additional privileges correctly', async () => { const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< diff --git a/x-pack/plugins/actions/server/authorization/actions_authorization.ts b/x-pack/plugins/actions/server/authorization/actions_authorization.ts index 5739af64050ee..e392f8bbcc14a 100644 --- a/x-pack/plugins/actions/server/authorization/actions_authorization.ts +++ b/x-pack/plugins/actions/server/authorization/actions_authorization.ts @@ -13,19 +13,10 @@ import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, } from '../constants/saved_objects'; import { isBidirectionalConnectorType } from '../lib/bidirectional_connectors'; -import { AuthorizationMode } from './get_authorization_mode_by_source'; export interface ConstructorOptions { request: KibanaRequest; authorization?: SecurityPluginSetup['authz']; - // In order to support legacy Alerts which predate the introduction of the - // Actions feature in Kibana we need a way of "dialing down" the level of - // authorization for certain opearations. - // Specifically, we want to allow these old alerts and their scheduled - // actions to continue to execute - which requires that we exempt auth on - // `get` for Connectors and `execute` for Action execution when used by - // these legacy alerts - authorizationMode?: AuthorizationMode; } const operationAlias: Record string[]> = { @@ -38,21 +29,13 @@ const operationAlias: Record; - -describe(`#getAuthorizationModeBySource`, () => { - test('should return RBAC if no source is provided', async () => { - expect(await getAuthorizationModeBySource(unsecuredSavedObjectsClient)).toEqual( - AuthorizationMode.RBAC - ); - }); - - test('should return RBAC if source is not an alert', async () => { - expect( - await getAuthorizationModeBySource( - unsecuredSavedObjectsClient, - asSavedObjectExecutionSource({ - type: 'action', - id: uuidv4(), - }) - ) - ).toEqual(AuthorizationMode.RBAC); - }); - - test('should return RBAC if source alert is not marked as legacy', async () => { - const id = uuidv4(); - unsecuredSavedObjectsClient.get.mockResolvedValue(mockRuleSO({ id })); - expect( - await getAuthorizationModeBySource( - unsecuredSavedObjectsClient, - asSavedObjectExecutionSource({ - type: 'alert', - id, - }) - ) - ).toEqual(AuthorizationMode.RBAC); - }); - - test('should return Legacy if source alert is marked as legacy', async () => { - const id = uuidv4(); - unsecuredSavedObjectsClient.get.mockResolvedValue( - mockRuleSO({ id, attributes: { meta: { versionApiKeyLastmodified: 'pre-7.10.0' } } }) - ); - expect( - await getAuthorizationModeBySource( - unsecuredSavedObjectsClient, - asSavedObjectExecutionSource({ - type: 'alert', - id, - }) - ) - ).toEqual(AuthorizationMode.Legacy); - }); - - test('should return RBAC if source alert is marked as modern', async () => { - const id = uuidv4(); - unsecuredSavedObjectsClient.get.mockResolvedValue( - mockRuleSO({ id, attributes: { meta: { versionApiKeyLastmodified: '7.10.0' } } }) - ); - expect( - await getAuthorizationModeBySource( - unsecuredSavedObjectsClient, - asSavedObjectExecutionSource({ - type: 'alert', - id, - }) - ) - ).toEqual(AuthorizationMode.RBAC); - }); - - test('should return RBAC if source alert doesnt have a last modified version', async () => { - const id = uuidv4(); - unsecuredSavedObjectsClient.get.mockResolvedValue(mockRuleSO({ id, attributes: { meta: {} } })); - expect( - await getAuthorizationModeBySource( - unsecuredSavedObjectsClient, - asSavedObjectExecutionSource({ - type: 'alert', - id, - }) - ) - ).toEqual(AuthorizationMode.RBAC); - }); -}); - -describe(`#bulkGetAuthorizationModeBySource`, () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - test('should return RBAC if no sources are provided', async () => { - expect(await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient)).toEqual({ - [AuthorizationMode.RBAC]: 1, - [AuthorizationMode.Legacy]: 0, - }); - expect(unsecuredSavedObjectsClient.bulkGet).not.toHaveBeenCalled(); - }); - - test('should return RBAC if no alert sources are provided', async () => { - expect( - await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ - asSavedObjectExecutionSource({ - type: 'action', - id: uuidv4(), - }), - asHttpRequestExecutionSource({} as KibanaRequest), - ]) - ).toEqual({ [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }); - - expect(unsecuredSavedObjectsClient.bulkGet).not.toHaveBeenCalled(); - }); - - test('should consolidate duplicate alert sources', async () => { - unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ - saved_objects: [mockRuleSO({ id: '1' }), mockRuleSO({ id: '2' })], - }); - expect( - await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ - asSavedObjectExecutionSource({ - type: 'alert', - id: '1', - }), - asSavedObjectExecutionSource({ - type: 'alert', - id: '1', - }), - asSavedObjectExecutionSource({ - type: 'alert', - id: '2', - }), - ]) - ).toEqual({ [AuthorizationMode.RBAC]: 2, [AuthorizationMode.Legacy]: 0 }); - - expect(unsecuredSavedObjectsClient.bulkGet).toHaveBeenCalledWith([ - { - type: 'alert', - id: '1', - }, - { - type: 'alert', - id: '2', - }, - ]); - }); - - test('should return RBAC if source alert is not marked as legacy', async () => { - const id = uuidv4(); - unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [mockRuleSO({ id })] }); - expect( - await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ - asSavedObjectExecutionSource({ - type: 'alert', - id, - }), - ]) - ).toEqual({ [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }); - }); - - test('should return Legacy if source alert is marked as legacy', async () => { - const id = uuidv4(); - unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ - saved_objects: [ - mockRuleSO({ id, attributes: { meta: { versionApiKeyLastmodified: 'pre-7.10.0' } } }), - ], - }); - expect( - await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ - asSavedObjectExecutionSource({ - type: 'alert', - id, - }), - ]) - ).toEqual({ [AuthorizationMode.RBAC]: 0, [AuthorizationMode.Legacy]: 1 }); - }); - - test('should return RBAC if source alert is marked as modern', async () => { - const id = uuidv4(); - unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ - saved_objects: [ - mockRuleSO({ id, attributes: { meta: { versionApiKeyLastmodified: '7.10.0' } } }), - ], - }); - expect( - await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ - asSavedObjectExecutionSource({ - type: 'alert', - id, - }), - ]) - ).toEqual({ [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }); - }); - - test('should return RBAC if source alert doesnt have a last modified version', async () => { - const id = uuidv4(); - unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ - saved_objects: [mockRuleSO({ id, attributes: { meta: {} } })], - }); - expect( - await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ - asSavedObjectExecutionSource({ - type: 'alert', - id, - }), - ]) - ).toEqual({ [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 }); - }); - - test('should return RBAC and log warning if error getting source alert', async () => { - unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ - saved_objects: [ - mockRuleSO({ id: '1', attributes: { meta: { versionApiKeyLastmodified: 'pre-7.10.0' } } }), - // @ts-expect-error - { - id: '2', - type: 'alert', - error: { statusCode: 404, error: 'failed to get', message: 'fail' }, - }, - ], - }); - expect( - await bulkGetAuthorizationModeBySource(logger, unsecuredSavedObjectsClient, [ - asSavedObjectExecutionSource({ - type: 'alert', - id: '1', - }), - asSavedObjectExecutionSource({ - type: 'alert', - id: '2', - }), - ]) - ).toEqual({ [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 1 }); - - expect(logger.warn).toHaveBeenCalledWith( - `Error retrieving saved object [alert/2] - fail - default to using RBAC authorization mode.` - ); - }); -}); - -const mockRuleSO = (overrides: Record = {}) => ({ - id: '1', - type: 'alert', - attributes: { - consumer: 'myApp', - schedule: { interval: '10s' }, - alertTypeId: 'myType', - enabled: false, - actions: [ - { - group: 'default', - id: '1', - actionTypeId: '1', - actionRef: '1', - params: { - foo: true, - }, - }, - ], - }, - version: '123', - references: [], - ...overrides, -}); diff --git a/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.ts b/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.ts deleted file mode 100644 index ace66798b24ba..0000000000000 --- a/x-pack/plugins/actions/server/authorization/get_authorization_mode_by_source.ts +++ /dev/null @@ -1,90 +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 { Logger, SavedObjectsClientContract } from '@kbn/core/server'; -import { ActionExecutionSource, isSavedObjectExecutionSource } from '../lib'; -import { ALERT_SAVED_OBJECT_TYPE } from '../constants/saved_objects'; -import { SavedObjectExecutionSource } from '../lib/action_execution_source'; - -const LEGACY_VERSION = 'pre-7.10.0'; - -export enum AuthorizationMode { - Legacy, - RBAC, -} - -export async function getAuthorizationModeBySource( - unsecuredSavedObjectsClient: SavedObjectsClientContract, - executionSource?: ActionExecutionSource -): Promise { - return isSavedObjectExecutionSource(executionSource) && - executionSource?.source?.type === ALERT_SAVED_OBJECT_TYPE && - ( - await unsecuredSavedObjectsClient.get<{ - meta?: { - versionApiKeyLastmodified?: string; - }; - }>(ALERT_SAVED_OBJECT_TYPE, executionSource.source.id) - ).attributes.meta?.versionApiKeyLastmodified === LEGACY_VERSION - ? AuthorizationMode.Legacy - : AuthorizationMode.RBAC; -} - -export async function bulkGetAuthorizationModeBySource( - logger: Logger, - unsecuredSavedObjectsClient: SavedObjectsClientContract, - executionSources: Array> = [] -): Promise> { - const authModes = { [AuthorizationMode.Legacy]: 0, [AuthorizationMode.RBAC]: 0 }; - - const alertSavedObjectExecutionSources: SavedObjectExecutionSource[] = executionSources.filter( - (source) => - isSavedObjectExecutionSource(source) && source?.source?.type === ALERT_SAVED_OBJECT_TYPE - ) as SavedObjectExecutionSource[]; - - // If no ALERT_SAVED_OBJECT_TYPE source, default to RBAC - if (alertSavedObjectExecutionSources.length === 0) { - authModes[AuthorizationMode.RBAC] = 1; - return authModes; - } - - // Collect the unique rule IDs for ALERT_SAVED_OBJECT_TYPE sources and bulk get the associated SOs - const rulesIds = new Set( - alertSavedObjectExecutionSources.map((source: SavedObjectExecutionSource) => source?.source?.id) - ); - - // Get rule saved objects to determine whether to use RBAC or legacy authorization source - const ruleSOs = await unsecuredSavedObjectsClient.bulkGet<{ - meta?: { - versionApiKeyLastmodified?: string; - }; - }>( - [...rulesIds].map((id) => ({ - type: ALERT_SAVED_OBJECT_TYPE, - id, - })) - ); - - return ruleSOs.saved_objects.reduce((acc, ruleSO) => { - if (ruleSO.error) { - logger.warn( - `Error retrieving saved object [${ruleSO.type}/${ruleSO.id}] - ${ruleSO.error?.message} - default to using RBAC authorization mode.` - ); - // If there's an error retrieving the saved object, default to RBAC auth mode to avoid privilege de-escalation - authModes[AuthorizationMode.RBAC]++; - } else { - // Check whether this is a legacy rule - const isLegacy = ruleSO.attributes?.meta?.versionApiKeyLastmodified === LEGACY_VERSION; - if (isLegacy) { - authModes[AuthorizationMode.Legacy]++; - } else { - authModes[AuthorizationMode.RBAC]++; - } - } - return acc; - }, authModes); -} diff --git a/x-pack/plugins/actions/server/lib/track_legacy_rbac_exemption.test.ts b/x-pack/plugins/actions/server/lib/track_legacy_rbac_exemption.test.ts deleted file mode 100644 index 6d44a5d982e62..0000000000000 --- a/x-pack/plugins/actions/server/lib/track_legacy_rbac_exemption.test.ts +++ /dev/null @@ -1,44 +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 { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; -import { trackLegacyRBACExemption } from './track_legacy_rbac_exemption'; - -describe('trackLegacyRBACExemption', () => { - it('should call `usageCounter.incrementCounter`', () => { - const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); - const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); - - trackLegacyRBACExemption('test', mockUsageCounter); - expect(mockUsageCounter.incrementCounter).toHaveBeenCalledWith({ - counterName: `source_test`, - counterType: 'legacyRBACExemption', - incrementBy: 1, - }); - }); - - it('should do nothing if no usage counter is provided', () => { - let err; - try { - trackLegacyRBACExemption('test', undefined); - } catch (e) { - err = e; - } - expect(err).toBeUndefined(); - }); - - it('should call `usageCounter.incrementCounter` and increment by the passed in value', () => { - const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); - const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); - - trackLegacyRBACExemption('test', mockUsageCounter, 15); - expect(mockUsageCounter.incrementCounter).toHaveBeenCalledWith({ - counterName: `source_test`, - counterType: 'legacyRBACExemption', - incrementBy: 15, - }); - }); -}); diff --git a/x-pack/plugins/actions/server/lib/track_legacy_rbac_exemption.ts b/x-pack/plugins/actions/server/lib/track_legacy_rbac_exemption.ts deleted file mode 100644 index b6f1af3d9de76..0000000000000 --- a/x-pack/plugins/actions/server/lib/track_legacy_rbac_exemption.ts +++ /dev/null @@ -1,22 +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 { UsageCounter } from '@kbn/usage-collection-plugin/server'; - -export function trackLegacyRBACExemption( - source: string, - usageCounter?: UsageCounter, - increment?: number -) { - if (usageCounter) { - usageCounter.incrementCounter({ - counterName: `source_${source}`, - counterType: 'legacyRBACExemption', - incrementBy: increment ? increment : 1, - }); - } -} diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 00dc17c2f92d7..36fc4b68d443c 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -84,10 +84,6 @@ import { setupSavedObjects } from './saved_objects'; import { ACTIONS_FEATURE } from './feature'; import { ActionsAuthorization } from './authorization/actions_authorization'; import { ActionExecutionSource } from './lib/action_execution_source'; -import { - getAuthorizationModeBySource, - AuthorizationMode, -} from './authorization/get_authorization_mode_by_source'; import { ensureSufficientLicense } from './lib/ensure_sufficient_license'; import { renderMustacheObject } from './lib/mustache_renderer'; import { getAlertHistoryEsIndex } from './preconfigured_connectors/alert_history_es_index/alert_history_es_index'; @@ -467,10 +463,7 @@ export class ActionsPlugin implements Plugin getActionsClientWithRequest(request); @@ -641,13 +631,9 @@ export class ActionsPlugin implements Plugin { + private instantiateAuthorization = (request: KibanaRequest) => { return new ActionsAuthorization({ request, - authorizationMode, authorization: this.security?.authz, }); }; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/index.ts index 245425e0bbfea..a6f090030a6d4 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/index.ts @@ -11,33 +11,19 @@ import { setupSpacesAndUsers, tearDown } from '../../../setup'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { describe('Alerts', () => { - describe('legacy alerts', function () { - before(async () => { - await setupSpacesAndUsers(getService); - }); - - after(async () => { - await tearDown(getService); - }); - - loadTestFile(require.resolve('./rbac_legacy')); + before(async () => { + await setupSpacesAndUsers(getService); }); - describe('alerts', () => { - before(async () => { - await setupSpacesAndUsers(getService); - }); - - after(async () => { - await tearDown(getService); - }); - - loadTestFile(require.resolve('./mute_all')); - loadTestFile(require.resolve('./mute_instance')); - loadTestFile(require.resolve('./unmute_all')); - loadTestFile(require.resolve('./unmute_instance')); - loadTestFile(require.resolve('./update')); - loadTestFile(require.resolve('./update_api_key')); + after(async () => { + await tearDown(getService); }); + + loadTestFile(require.resolve('./mute_all')); + loadTestFile(require.resolve('./mute_instance')); + loadTestFile(require.resolve('./unmute_all')); + loadTestFile(require.resolve('./unmute_instance')); + loadTestFile(require.resolve('./update')); + loadTestFile(require.resolve('./update_api_key')); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/rbac_legacy.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/rbac_legacy.ts deleted file mode 100644 index 0785fd8fb77fe..0000000000000 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/rbac_legacy.ts +++ /dev/null @@ -1,310 +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 expect from '@kbn/expect'; -import { SavedObjectsUtils } from '@kbn/core/server'; -import { ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; -import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server'; -import { UserAtSpaceScenarios, Superuser } from '../../../scenarios'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover, AlertUtils } from '../../../../common/lib'; -import { setupSpacesAndUsers } from '../../../setup'; - -// eslint-disable-next-line import/no-default-export -export default function alertTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const es = getService('es'); - const retry = getService('retry'); - const esArchiver = getService('esArchiver'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const esTestIndexTool = new ESTestIndexTool(es, retry); - - const MIGRATED_ACTION_ID = SavedObjectsUtils.getConvertedObjectId( - 'space1', - 'action', - '17f38826-5a8d-4a76-975a-b496e7fffe0b' - ); - - const MIGRATED_ALERT_ID: Record = { - space_1_all_alerts_none_actions: SavedObjectsUtils.getConvertedObjectId( - 'space1', - RULE_SAVED_OBJECT_TYPE, - '6ee9630a-a20e-44af-9465-217a3717d2ab' - ), - space_1_all_with_restricted_fixture: SavedObjectsUtils.getConvertedObjectId( - 'space1', - RULE_SAVED_OBJECT_TYPE, - '5cc59319-74ee-4edc-8646-a79ea91067cd' - ), - space_1_all: SavedObjectsUtils.getConvertedObjectId( - 'space1', - RULE_SAVED_OBJECT_TYPE, - 'd41a6abb-b93b-46df-a80a-926221ea847c' - ), - global_read: SavedObjectsUtils.getConvertedObjectId( - 'space1', - RULE_SAVED_OBJECT_TYPE, - '362e362b-a137-4aa2-9434-43e3d0d84a34' - ), - superuser: SavedObjectsUtils.getConvertedObjectId( - 'space1', - RULE_SAVED_OBJECT_TYPE, - 'b384be60-ec53-4b26-857e-0253ee55b277' - ), - }; - - describe('alerts', () => { - const authorizationIndex = '.kibana-test-authorization'; - const objectRemover = new ObjectRemover(supertest); - - before(async () => { - await esTestIndexTool.destroy(); - // Not 100% sure why, seems the rules need to be loaded separately to avoid the task - // failing to load the rule during execution and deleting itself. Otherwise - // we have flakiness - await esArchiver.load('x-pack/test/functional/es_archives/alerts_legacy/rules'); - await esArchiver.load('x-pack/test/functional/es_archives/alerts_legacy/tasks'); - await esTestIndexTool.setup(); - await es.indices.create({ index: authorizationIndex }); - await setupSpacesAndUsers(getService); - }); - - after(async () => { - await esTestIndexTool.destroy(); - await es.indices.delete({ index: authorizationIndex }); - await esArchiver.unload('x-pack/test/functional/es_archives/alerts_legacy/tasks'); - await esArchiver.unload('x-pack/test/functional/es_archives/alerts_legacy/rules'); - }); - - for (const scenario of UserAtSpaceScenarios) { - const { user, space } = scenario; - - describe(scenario.id, () => { - let alertUtils: AlertUtils; - - before(() => { - alertUtils = new AlertUtils({ - user, - space, - supertestWithoutAuth, - indexRecordActionId: MIGRATED_ACTION_ID, - objectRemover, - }); - }); - - it('should schedule actions on legacy alerts', async () => { - const reference = `alert:migrated-to-7.10:${user.username}`; - const migratedAlertId = MIGRATED_ALERT_ID[user.username]; - - switch (scenario.id) { - case 'no_kibana_privileges at space1': - case 'space_1_all at space2': - // These cases are not relevant as we're testing the migration of alerts which - // were valid pre 7.10.0 and which become invalid after the introduction of RBAC in 7.10.0 - // these cases were invalid pre 7.10.0 and remain invalid post 7.10.0 - break; - case 'space_1_all at space1': - case 'space_1_all_with_restricted_fixture at space1': - case 'superuser at space1': - await resetTaskStatus(migratedAlertId); - await ensureLegacyAlertHasBeenMigrated(migratedAlertId); - - await updateMigratedAlertToUseApiKeyOfCurrentUser(migratedAlertId); - await rescheduleTask(migratedAlertId); - - await alertUtils.disable(migratedAlertId); - await updateAlertSoThatItIsNoLongerLegacy(migratedAlertId); - - // update alert as user with privileges - so it is no longer a legacy alert - await retry.try(async () => { - const updatedKeyResponse = await alertUtils.getUpdateApiKeyRequest(migratedAlertId); - expect(updatedKeyResponse.statusCode).to.eql(204); - }); - // As we update the task multiple times in this test case, we might be updating the one already picked up by the task manager. - // To avoid 409 conflict error, we disable the rule above before updating and wait for 3 seconds - // after updating it to be sure that one task manager cycle is done. So we are not updating a task that is in progress. - await new Promise((resolve) => setTimeout(resolve, 3000)); - await alertUtils.enable(migratedAlertId); - await ensureAlertIsRunning(); - break; - case 'global_read at space1': - await resetTaskStatus(migratedAlertId); - await ensureLegacyAlertHasBeenMigrated(migratedAlertId); - - await updateMigratedAlertToUseApiKeyOfCurrentUser(migratedAlertId); - await rescheduleTask(migratedAlertId); - - await ensureAlertIsRunning(); - - await updateAlertSoThatItIsNoLongerLegacy(migratedAlertId); - - // attempt to update alert as user with no Alerts privileges - as it is no longer a legacy alert - // this should fail, as the user doesn't have the `updateApiKey` privilege for Alerts - const failedUpdateKeyDueToAlertsPrivilegesResponse = - await alertUtils.getUpdateApiKeyRequest(migratedAlertId); - - expect(failedUpdateKeyDueToAlertsPrivilegesResponse.statusCode).to.eql(403); - expect(failedUpdateKeyDueToAlertsPrivilegesResponse.body).to.eql({ - error: 'Forbidden', - message: - 'Unauthorized by "alertsFixture" to updateApiKey "test.always-firing" rule', - statusCode: 403, - }); - break; - case 'space_1_all_alerts_none_actions at space1': - await resetTaskStatus(migratedAlertId); - await ensureLegacyAlertHasBeenMigrated(migratedAlertId); - - await updateMigratedAlertToUseApiKeyOfCurrentUser(migratedAlertId); - await rescheduleTask(migratedAlertId); - - await ensureAlertIsRunning(); - await updateAlertSoThatItIsNoLongerLegacy(migratedAlertId); - - // attempt to update alert as user with no Actions privileges - as it is no longer a legacy alert - // this should fail, as the user doesn't have the `execute` privilege for Actions - const failedUpdateKeyDueToActionsPrivilegesResponse = - await alertUtils.getUpdateApiKeyRequest(migratedAlertId); - - expect(failedUpdateKeyDueToActionsPrivilegesResponse.statusCode).to.eql(403); - expect(failedUpdateKeyDueToActionsPrivilegesResponse.body).to.eql({ - error: 'Forbidden', - message: 'Unauthorized to execute actions', - statusCode: 403, - }); - break; - default: - throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); - } - - async function resetTaskStatus(alertId: string) { - // occasionally when the task manager starts running while the alert saved objects - // are mid-migration, the task will fail and set its status to "failed". this prevents - // the alert from running ever again and downstream tasks that depend on successful alert - // execution will fail. this ensures the task status is set to "idle" so the - // task manager will continue claiming and executing it. - await supertest - .put(`${getUrlPrefix(space.id)}/api/alerts_fixture/${alertId}/reset_task_status`) - .set('kbn-xsrf', 'foo') - .send({ - status: 'idle', - }) - .expect(200); - } - - async function ensureLegacyAlertHasBeenMigrated(alertId: string) { - const getResponse = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${alertId}`) - .auth(user.username, user.password); - expect(getResponse.status).to.eql(200); - } - - async function updateMigratedAlertToUseApiKeyOfCurrentUser(alertId: string) { - // swap out api key to run as the current user - const swapResponse = await alertUtils.replaceApiKeys(alertId); - expect(swapResponse.statusCode).to.eql(200); - // ensure the alert is till marked as legacy despite the update of the Api key - // this is important as proper update *should* update the legacy status of the alert - // and we want to ensure we don't accidentally introduce a change that might break our support of legacy alerts - expect(swapResponse.body.id).to.eql(alertId); - expect(swapResponse.body.attributes.meta.versionApiKeyLastmodified).to.eql( - 'pre-7.10.0' - ); - } - - async function rescheduleTask(alertId: string) { - // Get scheduled task id - const getResponse = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerting/rule/${alertId}`) - .auth(user.username, user.password) - .expect(200); - - // loading the archive likely caused the task to fail so ensure it's rescheduled to run in 2 seconds, - // otherwise this test will stall for 5 minutes - // no other attributes are touched, only runAt, so unless it would have ran when runAt expired, it - // won't run now - await supertest - .put( - `${getUrlPrefix(space.id)}/api/alerts_fixture/${ - getResponse.body.scheduled_task_id - }/reschedule_task` - ) - .set('kbn-xsrf', 'foo') - .send({ - runAt: getRunAt(2000), - }) - .expect(200); - } - - async function ensureAlertIsRunning() { - // ensure the alert still runs and that it can schedule actions - const alwaysFiringResponse = await esTestIndexTool.search( - 'alert:test.always-firing', - reference - ); - // @ts-expect-error doesnt handle total: number - const numberOfAlertExecutions = alwaysFiringResponse.body.hits.total.value; - - const indexRecordResponse = await esTestIndexTool.search( - 'action:test.index-record', - reference - ); - // @ts-expect-error doesnt handle total: number - const numberOfActionExecutions = indexRecordResponse.body.hits.total.value; - - // wait for alert to execute and for its action to be scheduled and run - await retry.try(async () => { - const alertSearchResult = await esTestIndexTool.search( - 'alert:test.always-firing', - reference - ); - - const actionSearchResult = await esTestIndexTool.search( - 'action:test.index-record', - reference - ); - - // @ts-expect-error doesnt handle total: number - expect(alertSearchResult.body.hits.total.value).to.be.greaterThan( - numberOfAlertExecutions - ); - // @ts-expect-error doesnt handle total: number - expect(actionSearchResult.body.hits.total.value).to.be.greaterThan( - numberOfActionExecutions - ); - }); - } - - async function updateAlertSoThatItIsNoLongerLegacy(alertId: string) { - // update the alert as super user (to avoid privilege limitations) so that it is no longer a legacy alert - await retry.try(async () => { - const response = await alertUtils.updateAlwaysFiringAction({ - alertId, - actionId: MIGRATED_ACTION_ID, - user: Superuser, - reference, - overwrites: { - name: 'Updated Alert', - schedule: { interval: '2s' }, - throttle: '2s', - }, - }); - - expect(response.statusCode).to.eql(200); - }); - } - }); - }); - } - }); -} - -function getRunAt(delayInMs: number) { - const runAt = new Date(); - runAt.setMilliseconds(new Date().getMilliseconds() + delayInMs); - return runAt.toISOString(); -}