mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ResponseOps] Allow users authenticated with an API keys to manage alerting rules (#154189)
Resolves https://github.com/elastic/kibana/issues/152140 ## Summary Updates the following functions in the Rules Client to re-use the API key in context and avoid having the system invalidate them when no longer in use: - bulk_delete - bulk_edit - clone - create - delete - update - update_api_key Also adds a new field to the rule SO to help determine when whether an api key was created by a user or created by us. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### To verify - Follow these [instructions](https://www.elastic.co/guide/en/kibana/master/api-keys.html#create-api-key) to create an api key. Make sure to copy your api key - Run the following ``` curl -X POST "http://localhost:5601/api/alerting/rule/" -H 'Authorization: ApiKey ${API_KEY}' -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d' { "rule_type_id": "example.pattern", "name": "pattern", "schedule": { "interval": "5s" }, "actions": [ ], "consumer": "alerts", "tags": [], "notify_when": "onActionGroupChange", "params": { "patterns": { "instA": " a - - a " } } }' ``` - Verify that the request returns a rule with`"api_key_created_by_user":true` - Try this with the other rules clients functions listed above to verify that you can manage alerting rules when authenticated with an api key - Verify that `"api_key_created_by_user":false` when you remove the api key header and add `-u ${USERNAME}:${PASSWORD}` to authenticate
This commit is contained in:
parent
60fe5af19c
commit
331eb60a8b
70 changed files with 1191 additions and 101 deletions
|
@ -57,7 +57,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
Object {
|
||||
"action": "6cfc277ed3211639e37546ac625f4a68f2494215",
|
||||
"action_task_params": "5f419caba96dd8c77d0f94013e71d43890e3d5d6",
|
||||
"alert": "1e4cd6941f1eb39c729c646e91fbfb9700de84b9",
|
||||
"alert": "7bec97d7775a025ecf36a33baf17386b9e7b4c3c",
|
||||
"api_key_pending_invalidation": "16e7bcf8e78764102d7f525542d5b616809a21ee",
|
||||
"apm-indices": "d19dd7fb51f2d2cbc1f8769481721e0953f9a6d2",
|
||||
"apm-server-schema": "1d42f17eff9ec6c16d3a9324d9539e2d123d0a9a",
|
||||
|
|
|
@ -168,6 +168,7 @@ export interface Rule<Params extends RuleTypeParams = never> {
|
|||
updatedAt: Date;
|
||||
apiKey: string | null;
|
||||
apiKeyOwner: string | null;
|
||||
apiKeyCreatedByUser?: boolean | null;
|
||||
throttle?: string | null;
|
||||
muteAll: boolean;
|
||||
notifyWhen?: RuleNotifyWhenType | null;
|
||||
|
|
|
@ -109,6 +109,7 @@ export function transformRule(input: ApiRule): Rule {
|
|||
updated_at: updatedAt,
|
||||
api_key: apiKey,
|
||||
api_key_owner: apiKeyOwner,
|
||||
api_key_created_by_user: apiKeyCreatedByUser,
|
||||
notify_when: notifyWhen,
|
||||
mute_all: muteAll,
|
||||
muted_alert_ids: mutedInstanceIds,
|
||||
|
@ -140,6 +141,8 @@ export function transformRule(input: ApiRule): Rule {
|
|||
...(nextRun ? { nextRun: new Date(nextRun) } : {}),
|
||||
...(monitoring ? { monitoring: transformMonitoring(monitoring) } : {}),
|
||||
...(lastRun ? { lastRun: transformLastRun(lastRun) } : {}),
|
||||
...(apiKeyCreatedByUser !== undefined ? { apiKeyCreatedByUser } : {}),
|
||||
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ const rewriteBodyRes: RewriteResponseCase<PartialRule<RuleTypeParams>> = ({
|
|||
createdAt,
|
||||
updatedAt,
|
||||
apiKeyOwner,
|
||||
apiKeyCreatedByUser,
|
||||
notifyWhen,
|
||||
muteAll,
|
||||
mutedInstanceIds,
|
||||
|
@ -75,6 +76,7 @@ const rewriteBodyRes: RewriteResponseCase<PartialRule<RuleTypeParams>> = ({
|
|||
: {}),
|
||||
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
|
||||
...(nextRun ? { next_run: nextRun } : {}),
|
||||
...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}),
|
||||
});
|
||||
|
||||
export const cloneRuleRoute = (
|
||||
|
|
|
@ -74,6 +74,7 @@ const rewriteBodyRes: RewriteResponseCase<SanitizedRule<RuleTypeParams>> = ({
|
|||
createdAt,
|
||||
updatedAt,
|
||||
apiKeyOwner,
|
||||
apiKeyCreatedByUser,
|
||||
notifyWhen,
|
||||
muteAll,
|
||||
mutedInstanceIds,
|
||||
|
@ -103,6 +104,7 @@ const rewriteBodyRes: RewriteResponseCase<SanitizedRule<RuleTypeParams>> = ({
|
|||
actions: rewriteActionsRes(actions),
|
||||
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
|
||||
...(nextRun ? { next_run: nextRun } : {}),
|
||||
...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}),
|
||||
});
|
||||
|
||||
export const createRuleRoute = ({ router, licenseState, usageCounter }: RouteOptions) => {
|
||||
|
|
|
@ -29,6 +29,7 @@ const rewriteBodyRes: RewriteResponseCase<SanitizedRule<RuleTypeParams>> = ({
|
|||
createdAt,
|
||||
updatedAt,
|
||||
apiKeyOwner,
|
||||
apiKeyCreatedByUser,
|
||||
notifyWhen,
|
||||
muteAll,
|
||||
mutedInstanceIds,
|
||||
|
@ -78,6 +79,7 @@ const rewriteBodyRes: RewriteResponseCase<SanitizedRule<RuleTypeParams>> = ({
|
|||
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
|
||||
...(nextRun ? { next_run: nextRun } : {}),
|
||||
...(viewInAppRelativeUrl ? { view_in_app_relative_url: viewInAppRelativeUrl } : {}),
|
||||
...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}),
|
||||
});
|
||||
|
||||
interface BuildGetRulesRouteParams {
|
||||
|
|
|
@ -25,6 +25,7 @@ export const rewriteRule = ({
|
|||
createdAt,
|
||||
updatedAt,
|
||||
apiKeyOwner,
|
||||
apiKeyCreatedByUser,
|
||||
notifyWhen,
|
||||
muteAll,
|
||||
mutedInstanceIds,
|
||||
|
@ -76,4 +77,5 @@ export const rewriteRule = ({
|
|||
})),
|
||||
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
|
||||
...(nextRun ? { next_run: nextRun } : {}),
|
||||
...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}),
|
||||
});
|
||||
|
|
|
@ -33,6 +33,7 @@ const rewriteBodyRes: RewriteResponseCase<ResolvedSanitizedRule<RuleTypeParams>>
|
|||
createdAt,
|
||||
updatedAt,
|
||||
apiKeyOwner,
|
||||
apiKeyCreatedByUser,
|
||||
notifyWhen,
|
||||
muteAll,
|
||||
mutedInstanceIds,
|
||||
|
@ -62,6 +63,7 @@ const rewriteBodyRes: RewriteResponseCase<ResolvedSanitizedRule<RuleTypeParams>>
|
|||
actions: rewriteActionsRes(actions),
|
||||
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
|
||||
...(nextRun ? { next_run: nextRun } : {}),
|
||||
...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}),
|
||||
});
|
||||
|
||||
export const resolveRuleRoute = (
|
||||
|
|
|
@ -74,6 +74,7 @@ const rewriteBodyRes: RewriteResponseCase<PartialRule<RuleTypeParams>> = ({
|
|||
createdAt,
|
||||
updatedAt,
|
||||
apiKeyOwner,
|
||||
apiKeyCreatedByUser,
|
||||
notifyWhen,
|
||||
muteAll,
|
||||
mutedInstanceIds,
|
||||
|
@ -113,6 +114,7 @@ const rewriteBodyRes: RewriteResponseCase<PartialRule<RuleTypeParams>> = ({
|
|||
: {}),
|
||||
...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}),
|
||||
...(nextRun ? { next_run: nextRun } : {}),
|
||||
...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}),
|
||||
});
|
||||
|
||||
export const updateRuleRoute = (
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 { apiKeyAsAlertAttributes } from './api_key_as_alert_attributes';
|
||||
|
||||
describe('apiKeyAsAlertAttributes', () => {
|
||||
test('return attributes', () => {
|
||||
expect(
|
||||
apiKeyAsAlertAttributes(
|
||||
{
|
||||
apiKeysEnabled: true,
|
||||
result: {
|
||||
id: '123',
|
||||
name: '123',
|
||||
api_key: 'abc',
|
||||
},
|
||||
},
|
||||
'test',
|
||||
false
|
||||
)
|
||||
).toEqual({
|
||||
apiKey: 'MTIzOmFiYw==',
|
||||
apiKeyOwner: 'test',
|
||||
apiKeyCreatedByUser: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns null attributes when api keys are not enabled', () => {
|
||||
expect(
|
||||
apiKeyAsAlertAttributes(
|
||||
{
|
||||
apiKeysEnabled: false,
|
||||
},
|
||||
'test',
|
||||
false
|
||||
)
|
||||
).toEqual({
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns apiKeyCreatedByUser as true when createdByUser is passed in', () => {
|
||||
expect(
|
||||
apiKeyAsAlertAttributes(
|
||||
{
|
||||
apiKeysEnabled: true,
|
||||
result: {
|
||||
id: '123',
|
||||
name: '123',
|
||||
api_key: 'abc',
|
||||
},
|
||||
},
|
||||
'test',
|
||||
true
|
||||
)
|
||||
).toEqual({
|
||||
apiKey: 'MTIzOmFiYw==',
|
||||
apiKeyOwner: 'test',
|
||||
apiKeyCreatedByUser: true,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -10,15 +10,18 @@ import { CreateAPIKeyResult } from '../types';
|
|||
|
||||
export function apiKeyAsAlertAttributes(
|
||||
apiKey: CreateAPIKeyResult | null,
|
||||
username: string | null
|
||||
): Pick<RawRule, 'apiKey' | 'apiKeyOwner'> {
|
||||
username: string | null,
|
||||
createdByUser: boolean
|
||||
): Pick<RawRule, 'apiKey' | 'apiKeyOwner' | 'apiKeyCreatedByUser'> {
|
||||
return apiKey && apiKey.apiKeysEnabled
|
||||
? {
|
||||
apiKeyOwner: username,
|
||||
apiKey: Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64'),
|
||||
apiKeyCreatedByUser: createdByUser,
|
||||
}
|
||||
: {
|
||||
apiKeyOwner: null,
|
||||
apiKey: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* 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 { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
|
||||
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock';
|
||||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
|
||||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||
import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks';
|
||||
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
|
||||
import { ActionsAuthorization } from '@kbn/actions-plugin/server';
|
||||
import { RawRule } from '../../types';
|
||||
import { getBeforeSetup, mockedDateString } from '../tests/lib';
|
||||
import { createNewAPIKeySet } from './create_new_api_key_set';
|
||||
import { RulesClientContext } from '../types';
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
|
||||
const authorization = alertingAuthorizationMock.create();
|
||||
const actionsAuthorization = actionsAuthorizationMock.create();
|
||||
|
||||
const kibanaVersion = 'v8.0.0';
|
||||
const rulesClientParams: jest.Mocked<RulesClientContext> = {
|
||||
taskManager,
|
||||
ruleTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
authorization: authorization as unknown as AlertingAuthorization,
|
||||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization,
|
||||
spaceId: 'default',
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
encryptedSavedObjectsClient: encryptedSavedObjects,
|
||||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
minimumScheduleIntervalInMs: 1,
|
||||
fieldsToExcludeFromPublicApi: [],
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
const username = 'test';
|
||||
const attributes: RawRule = {
|
||||
enabled: true,
|
||||
name: 'rule-name',
|
||||
tags: ['tag-1', 'tag-2'],
|
||||
alertTypeId: '123',
|
||||
consumer: 'rule-consumer',
|
||||
legacyId: null,
|
||||
schedule: { interval: '1s' },
|
||||
actions: [],
|
||||
params: {},
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
createdAt: mockedDateString,
|
||||
updatedAt: mockedDateString,
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: null,
|
||||
notifyWhen: null,
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'unknown',
|
||||
lastExecutionDate: '2020-08-20T19:23:38Z',
|
||||
error: null,
|
||||
warning: null,
|
||||
},
|
||||
revision: 0,
|
||||
};
|
||||
|
||||
describe('createNewAPIKeySet', () => {
|
||||
beforeEach(() => {
|
||||
getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry);
|
||||
});
|
||||
|
||||
test('create new api keys', async () => {
|
||||
rulesClientParams.createAPIKey.mockResolvedValueOnce({
|
||||
apiKeysEnabled: true,
|
||||
result: { id: '123', name: '123', api_key: 'abc' },
|
||||
});
|
||||
const apiKey = await createNewAPIKeySet(rulesClientParams, {
|
||||
id: attributes.alertTypeId,
|
||||
ruleName: attributes.name,
|
||||
username,
|
||||
shouldUpdateApiKey: true,
|
||||
});
|
||||
expect(apiKey).toEqual({
|
||||
apiKey: 'MTIzOmFiYw==',
|
||||
apiKeyCreatedByUser: undefined,
|
||||
apiKeyOwner: 'test',
|
||||
});
|
||||
expect(rulesClientParams.createAPIKey).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should get api key from the request if the user is authenticated using api keys', async () => {
|
||||
rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({
|
||||
apiKeysEnabled: true,
|
||||
result: { id: '123', name: '123', api_key: 'abc' },
|
||||
});
|
||||
rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true);
|
||||
const apiKey = await createNewAPIKeySet(rulesClientParams, {
|
||||
id: attributes.alertTypeId,
|
||||
ruleName: attributes.name,
|
||||
username,
|
||||
shouldUpdateApiKey: true,
|
||||
});
|
||||
expect(apiKey).toEqual({
|
||||
apiKey: 'MTIzOmFiYw==',
|
||||
apiKeyCreatedByUser: true,
|
||||
apiKeyOwner: 'test',
|
||||
});
|
||||
expect(rulesClientParams.getAuthenticationAPIKey).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClientParams.isAuthenticationTypeAPIKey).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should throw an error if getting the api key fails', async () => {
|
||||
rulesClientParams.createAPIKey.mockRejectedValueOnce(new Error('Test failure'));
|
||||
await expect(
|
||||
async () =>
|
||||
await createNewAPIKeySet(rulesClientParams, {
|
||||
id: attributes.alertTypeId,
|
||||
ruleName: attributes.name,
|
||||
username,
|
||||
shouldUpdateApiKey: true,
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Error creating API key for rule - Test failure"`
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw an error if getting the api key fails and an error message is passed in', async () => {
|
||||
rulesClientParams.createAPIKey.mockRejectedValueOnce(new Error('Test failure'));
|
||||
await expect(
|
||||
async () =>
|
||||
await createNewAPIKeySet(rulesClientParams, {
|
||||
id: attributes.alertTypeId,
|
||||
ruleName: attributes.name,
|
||||
username,
|
||||
shouldUpdateApiKey: true,
|
||||
errorMessage: 'Error updating rule: could not create API key',
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Error updating rule: could not create API key - Test failure"`
|
||||
);
|
||||
});
|
||||
|
||||
test('should return null if shouldUpdateApiKey: false', async () => {
|
||||
const apiKey = await createNewAPIKeySet(rulesClientParams, {
|
||||
id: attributes.alertTypeId,
|
||||
ruleName: attributes.name,
|
||||
username,
|
||||
shouldUpdateApiKey: false,
|
||||
});
|
||||
expect(apiKey).toEqual({
|
||||
apiKey: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
apiKeyOwner: null,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -13,21 +13,33 @@ import { RulesClientContext } from '../types';
|
|||
export async function createNewAPIKeySet(
|
||||
context: RulesClientContext,
|
||||
{
|
||||
attributes,
|
||||
id,
|
||||
ruleName,
|
||||
username,
|
||||
shouldUpdateApiKey,
|
||||
errorMessage,
|
||||
}: {
|
||||
attributes: RawRule;
|
||||
id: string;
|
||||
ruleName: string;
|
||||
username: string | null;
|
||||
shouldUpdateApiKey: boolean;
|
||||
errorMessage?: string;
|
||||
}
|
||||
): Promise<Pick<RawRule, 'apiKey' | 'apiKeyOwner'>> {
|
||||
): Promise<Pick<RawRule, 'apiKey' | 'apiKeyOwner' | 'apiKeyCreatedByUser'>> {
|
||||
let createdAPIKey = null;
|
||||
let isAuthTypeApiKey = false;
|
||||
try {
|
||||
createdAPIKey = await context.createAPIKey(
|
||||
generateAPIKeyName(attributes.alertTypeId, attributes.name)
|
||||
);
|
||||
isAuthTypeApiKey = context.isAuthenticationTypeAPIKey();
|
||||
const name = generateAPIKeyName(id, ruleName);
|
||||
createdAPIKey = shouldUpdateApiKey
|
||||
? isAuthTypeApiKey
|
||||
? context.getAuthenticationAPIKey(`${name}-user-created`)
|
||||
: await context.createAPIKey(name)
|
||||
: null;
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error creating API key for rule: ${error.message}`);
|
||||
const message = errorMessage ? errorMessage : 'Error creating API key for rule';
|
||||
throw Boom.badRequest(`${message} - ${error.message}`);
|
||||
}
|
||||
|
||||
return apiKeyAsAlertAttributes(createdAPIKey, username);
|
||||
return apiKeyAsAlertAttributes(createdAPIKey, username, isAuthTypeApiKey);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ export async function createRuleSavedObject<Params extends RuleTypeParams = neve
|
|||
} catch (e) {
|
||||
// Avoid unused API key
|
||||
await bulkMarkApiKeysForInvalidation(
|
||||
{ apiKeys: rawRule.apiKey ? [rawRule.apiKey] : [] },
|
||||
{ apiKeys: rawRule.apiKey && !rawRule.apiKeyCreatedByUser ? [rawRule.apiKey] : [] },
|
||||
context.logger,
|
||||
context.unsecuredSavedObjectsClient
|
||||
);
|
||||
|
|
|
@ -110,7 +110,7 @@ const bulkDeleteWithOCC = async (
|
|||
async () => {
|
||||
for await (const response of rulesFinder.find()) {
|
||||
for (const rule of response.saved_objects) {
|
||||
if (rule.attributes.apiKey) {
|
||||
if (rule.attributes.apiKey && !rule.attributes.apiKeyCreatedByUser) {
|
||||
apiKeyToRuleIdMapping[rule.id] = rule.attributes.apiKey;
|
||||
}
|
||||
if (rule.attributes.name) {
|
||||
|
|
|
@ -41,8 +41,6 @@ import {
|
|||
applyBulkEditOperation,
|
||||
buildKueryNodeFilter,
|
||||
injectReferencesIntoActions,
|
||||
generateAPIKeyName,
|
||||
apiKeyAsAlertAttributes,
|
||||
getBulkSnoozeAttributes,
|
||||
getBulkUnsnoozeAttributes,
|
||||
verifySnoozeScheduleLimit,
|
||||
|
@ -61,13 +59,13 @@ import {
|
|||
validateActions,
|
||||
updateMeta,
|
||||
addGeneratedActionValues,
|
||||
createNewAPIKeySet,
|
||||
} from '../lib';
|
||||
import {
|
||||
NormalizedAlertAction,
|
||||
BulkOperationError,
|
||||
RuleBulkOperationAggregation,
|
||||
RulesClientContext,
|
||||
CreateAPIKeyResult,
|
||||
NormalizedAlertActionWithGeneratedValues,
|
||||
} from '../types';
|
||||
|
||||
|
@ -121,9 +119,17 @@ export type BulkEditOperation =
|
|||
value?: undefined;
|
||||
};
|
||||
|
||||
type ApiKeysMap = Map<string, { oldApiKey?: string; newApiKey?: string }>;
|
||||
type ApiKeysMap = Map<
|
||||
string,
|
||||
{
|
||||
oldApiKey?: string;
|
||||
newApiKey?: string;
|
||||
oldApiKeyCreatedByUser?: boolean | null;
|
||||
newApiKeyCreatedByUser?: boolean | null;
|
||||
}
|
||||
>;
|
||||
|
||||
type ApiKeyAttributes = Pick<RawRule, 'apiKey' | 'apiKeyOwner'>;
|
||||
type ApiKeyAttributes = Pick<RawRule, 'apiKey' | 'apiKeyOwner' | 'apiKeyCreatedByUser'>;
|
||||
|
||||
type RuleType = ReturnType<RuleTypeRegistry['get']>;
|
||||
|
||||
|
@ -433,7 +439,10 @@ async function updateRuleAttributesAndParamsInMemory<Params extends RuleTypePara
|
|||
}): Promise<void> {
|
||||
try {
|
||||
if (rule.attributes.apiKey) {
|
||||
apiKeysMap.set(rule.id, { oldApiKey: rule.attributes.apiKey });
|
||||
apiKeysMap.set(rule.id, {
|
||||
oldApiKey: rule.attributes.apiKey,
|
||||
oldApiKeyCreatedByUser: rule.attributes.apiKeyCreatedByUser,
|
||||
});
|
||||
}
|
||||
|
||||
const ruleType = context.ruleTypeRegistry.get(rule.attributes.alertTypeId);
|
||||
|
@ -734,23 +743,20 @@ async function prepareApiKeys(
|
|||
hasUpdateApiKeyOperation: boolean,
|
||||
username: string | null
|
||||
): Promise<{ apiKeyAttributes: ApiKeyAttributes }> {
|
||||
const shouldUpdateApiKey = attributes.enabled || hasUpdateApiKeyOperation;
|
||||
const apiKeyAttributes = await createNewAPIKeySet(context, {
|
||||
id: ruleType.id,
|
||||
ruleName: attributes.name,
|
||||
username,
|
||||
shouldUpdateApiKey: attributes.enabled || hasUpdateApiKeyOperation,
|
||||
errorMessage: 'Error updating rule: could not create API key',
|
||||
});
|
||||
|
||||
let createdAPIKey: CreateAPIKeyResult | null = null;
|
||||
try {
|
||||
createdAPIKey = shouldUpdateApiKey
|
||||
? await context.createAPIKey(generateAPIKeyName(ruleType.id, attributes.name))
|
||||
: null;
|
||||
} catch (error) {
|
||||
throw Error(`Error updating rule: could not create API key - ${error.message}`);
|
||||
}
|
||||
|
||||
const apiKeyAttributes = apiKeyAsAlertAttributes(createdAPIKey, username);
|
||||
// collect generated API keys
|
||||
if (apiKeyAttributes.apiKey) {
|
||||
apiKeysMap.set(rule.id, {
|
||||
...apiKeysMap.get(rule.id),
|
||||
newApiKey: apiKeyAttributes.apiKey,
|
||||
newApiKeyCreatedByUser: apiKeyAttributes.apiKeyCreatedByUser,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -812,7 +818,7 @@ async function saveBulkUpdatedRules(
|
|||
await bulkMarkApiKeysForInvalidation(
|
||||
{
|
||||
apiKeys: Array.from(apiKeysMap.values())
|
||||
.filter((value) => value.newApiKey)
|
||||
.filter((value) => value.newApiKey && !value.newApiKeyCreatedByUser)
|
||||
.map((value) => value.newApiKey as string),
|
||||
},
|
||||
context.logger,
|
||||
|
@ -824,13 +830,15 @@ async function saveBulkUpdatedRules(
|
|||
|
||||
result.saved_objects.map(({ id, error }) => {
|
||||
const oldApiKey = apiKeysMap.get(id)?.oldApiKey;
|
||||
const oldApiKeyCreatedByUser = apiKeysMap.get(id)?.oldApiKeyCreatedByUser;
|
||||
const newApiKey = apiKeysMap.get(id)?.newApiKey;
|
||||
const newApiKeyCreatedByUser = apiKeysMap.get(id)?.newApiKeyCreatedByUser;
|
||||
|
||||
// if SO wasn't saved and has new API key it will be invalidated
|
||||
if (error && newApiKey) {
|
||||
if (error && newApiKey && !newApiKeyCreatedByUser) {
|
||||
apiKeysToInvalidate.push(newApiKey);
|
||||
// if SO saved and has old Api Key it will be invalidate
|
||||
} else if (!error && oldApiKey) {
|
||||
} else if (!error && oldApiKey && !oldApiKeyCreatedByUser) {
|
||||
apiKeysToInvalidate.push(oldApiKey);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -146,7 +146,12 @@ const bulkEnableRulesWithOCC = async (
|
|||
const updatedAttributes = updateMeta(context, {
|
||||
...rule.attributes,
|
||||
...(!rule.attributes.apiKey &&
|
||||
(await createNewAPIKeySet(context, { attributes: rule.attributes, username }))),
|
||||
(await createNewAPIKeySet(context, {
|
||||
id: rule.attributes.alertTypeId,
|
||||
ruleName: rule.attributes.name,
|
||||
username,
|
||||
shouldUpdateApiKey: true,
|
||||
}))),
|
||||
enabled: true,
|
||||
updatedBy: username,
|
||||
updatedAt: new Date().toISOString(),
|
||||
|
|
|
@ -17,8 +17,7 @@ import { parseDuration } from '../../../common/parse_duration';
|
|||
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
|
||||
import { getRuleExecutionStatusPending } from '../../lib/rule_execution_status';
|
||||
import { isDetectionEngineAADRuleType } from '../../saved_objects/migrations/utils';
|
||||
import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common';
|
||||
import { createRuleSavedObject } from '../lib';
|
||||
import { createNewAPIKeySet, createRuleSavedObject } from '../lib';
|
||||
import { RulesClientContext } from '../types';
|
||||
|
||||
export type CloneArguments = [string, { newId?: string }];
|
||||
|
@ -95,18 +94,18 @@ export async function clone<Params extends RuleTypeParams = never>(
|
|||
const createTime = Date.now();
|
||||
const lastRunTimestamp = new Date();
|
||||
const legacyId = Semver.lt(context.kibanaVersion, '8.0.0') ? id : null;
|
||||
let createdAPIKey = null;
|
||||
try {
|
||||
createdAPIKey = ruleSavedObject.attributes.enabled
|
||||
? await context.createAPIKey(generateAPIKeyName(ruleType.id, ruleName))
|
||||
: null;
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`);
|
||||
}
|
||||
const apiKeyAttributes = await createNewAPIKeySet(context, {
|
||||
id: ruleType.id,
|
||||
ruleName,
|
||||
username,
|
||||
shouldUpdateApiKey: ruleSavedObject.attributes.enabled,
|
||||
errorMessage: 'Error creating rule: could not create API key',
|
||||
});
|
||||
|
||||
const rawRule: RawRule = {
|
||||
...ruleSavedObject.attributes,
|
||||
name: ruleName,
|
||||
...apiKeyAsAlertAttributes(createdAPIKey, username),
|
||||
...apiKeyAttributes,
|
||||
legacyId,
|
||||
createdBy: username,
|
||||
updatedBy: username,
|
||||
|
|
|
@ -40,6 +40,7 @@ export interface CreateOptions<Params extends RuleTypeParams> {
|
|||
| 'updatedAt'
|
||||
| 'apiKey'
|
||||
| 'apiKeyOwner'
|
||||
| 'apiKeyCreatedByUser'
|
||||
| 'muteAll'
|
||||
| 'mutedInstanceIds'
|
||||
| 'actions'
|
||||
|
@ -91,11 +92,20 @@ export async function create<Params extends RuleTypeParams = never>(
|
|||
const username = await context.getUserName();
|
||||
|
||||
let createdAPIKey = null;
|
||||
let isAuthTypeApiKey = false;
|
||||
try {
|
||||
isAuthTypeApiKey = context.isAuthenticationTypeAPIKey();
|
||||
const name = generateAPIKeyName(ruleType.id, data.name);
|
||||
createdAPIKey = data.enabled
|
||||
? await withSpan({ name: 'createAPIKey', type: 'rules' }, () =>
|
||||
context.createAPIKey(generateAPIKeyName(ruleType.id, data.name))
|
||||
)
|
||||
? isAuthTypeApiKey
|
||||
? context.getAuthenticationAPIKey(`${name}-user-created`)
|
||||
: await withSpan(
|
||||
{
|
||||
name: 'createAPIKey',
|
||||
type: 'rules',
|
||||
},
|
||||
() => context.createAPIKey(name)
|
||||
)
|
||||
: null;
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`);
|
||||
|
@ -144,7 +154,7 @@ export async function create<Params extends RuleTypeParams = never>(
|
|||
|
||||
const rawRule: RawRule = {
|
||||
...data,
|
||||
...apiKeyAsAlertAttributes(createdAPIKey, username),
|
||||
...apiKeyAsAlertAttributes(createdAPIKey, username, isAuthTypeApiKey),
|
||||
legacyId,
|
||||
actions,
|
||||
createdBy: username,
|
||||
|
|
|
@ -23,6 +23,7 @@ export async function deleteRule(context: RulesClientContext, { id }: { id: stri
|
|||
async function deleteWithOCC(context: RulesClientContext, { id }: { id: string }) {
|
||||
let taskIdToRemove: string | undefined | null;
|
||||
let apiKeyToInvalidate: string | null = null;
|
||||
let apiKeyCreatedByUser: boolean | undefined | null = false;
|
||||
let attributes: RawRule;
|
||||
|
||||
try {
|
||||
|
@ -31,6 +32,7 @@ async function deleteWithOCC(context: RulesClientContext, { id }: { id: string }
|
|||
namespace: context.namespace,
|
||||
});
|
||||
apiKeyToInvalidate = decryptedAlert.attributes.apiKey;
|
||||
apiKeyCreatedByUser = decryptedAlert.attributes.apiKeyCreatedByUser;
|
||||
taskIdToRemove = decryptedAlert.attributes.scheduledTaskId;
|
||||
attributes = decryptedAlert.attributes;
|
||||
} catch (e) {
|
||||
|
@ -73,7 +75,7 @@ async function deleteWithOCC(context: RulesClientContext, { id }: { id: string }
|
|||
|
||||
await Promise.all([
|
||||
taskIdToRemove ? context.taskManager.removeIfExists(taskIdToRemove) : null,
|
||||
apiKeyToInvalidate
|
||||
apiKeyToInvalidate && !apiKeyCreatedByUser
|
||||
? bulkMarkApiKeysForInvalidation(
|
||||
{ apiKeys: [apiKeyToInvalidate] },
|
||||
context.logger,
|
||||
|
|
|
@ -83,7 +83,13 @@ async function enableWithOCC(context: RulesClientContext, { id }: { id: string }
|
|||
|
||||
const updateAttributes = updateMeta(context, {
|
||||
...attributes,
|
||||
...(!existingApiKey && (await createNewAPIKeySet(context, { attributes, username }))),
|
||||
...(!existingApiKey &&
|
||||
(await createNewAPIKeySet(context, {
|
||||
id: attributes.alertTypeId,
|
||||
ruleName: attributes.name,
|
||||
username,
|
||||
shouldUpdateApiKey: true,
|
||||
}))),
|
||||
...(attributes.monitoring && {
|
||||
monitoring: resetMonitoringLastRun(attributes.monitoring),
|
||||
}),
|
||||
|
|
|
@ -32,8 +32,8 @@ import {
|
|||
getPartialRuleFromRaw,
|
||||
addGeneratedActionValues,
|
||||
incrementRevision,
|
||||
createNewAPIKeySet,
|
||||
} from '../lib';
|
||||
import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common';
|
||||
|
||||
export interface UpdateOptions<Params extends RuleTypeParams> {
|
||||
id: string;
|
||||
|
@ -122,7 +122,7 @@ async function updateWithOCC<Params extends RuleTypeParams>(
|
|||
);
|
||||
|
||||
await Promise.all([
|
||||
alertSavedObject.attributes.apiKey
|
||||
alertSavedObject.attributes.apiKey && !alertSavedObject.attributes.apiKeyCreatedByUser
|
||||
? bulkMarkApiKeysForInvalidation(
|
||||
{ apiKeys: [alertSavedObject.attributes.apiKey] },
|
||||
context.logger,
|
||||
|
@ -206,16 +206,13 @@ async function updateAlert<Params extends RuleTypeParams>(
|
|||
|
||||
const username = await context.getUserName();
|
||||
|
||||
let createdAPIKey = null;
|
||||
try {
|
||||
createdAPIKey = attributes.enabled
|
||||
? await context.createAPIKey(generateAPIKeyName(ruleType.id, data.name))
|
||||
: null;
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error updating rule: could not create API key - ${error.message}`);
|
||||
}
|
||||
|
||||
const apiKeyAttributes = apiKeyAsAlertAttributes(createdAPIKey, username);
|
||||
const apiKeyAttributes = await createNewAPIKeySet(context, {
|
||||
id: ruleType.id,
|
||||
ruleName: data.name,
|
||||
username,
|
||||
shouldUpdateApiKey: attributes.enabled,
|
||||
errorMessage: 'Error updating rule: could not create API key',
|
||||
});
|
||||
const notifyWhen = getRuleNotifyWhenType(data.notifyWhen ?? null, data.throttle ?? null);
|
||||
|
||||
// Increment revision if applicable field has changed
|
||||
|
@ -260,7 +257,12 @@ async function updateAlert<Params extends RuleTypeParams>(
|
|||
} catch (e) {
|
||||
// Avoid unused API key
|
||||
await bulkMarkApiKeysForInvalidation(
|
||||
{ apiKeys: createAttributes.apiKey ? [createAttributes.apiKey] : [] },
|
||||
{
|
||||
apiKeys:
|
||||
createAttributes.apiKey && !createAttributes.apiKeyCreatedByUser
|
||||
? [createAttributes.apiKey]
|
||||
: [],
|
||||
},
|
||||
context.logger,
|
||||
context.unsecuredSavedObjectsClient
|
||||
);
|
||||
|
|
|
@ -5,14 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
import { RawRule } from '../../types';
|
||||
import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization';
|
||||
import { retryIfConflicts } from '../../lib/retry_if_conflicts';
|
||||
import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation';
|
||||
import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events';
|
||||
import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common';
|
||||
import { updateMeta } from '../lib';
|
||||
import { createNewAPIKeySet, updateMeta } from '../lib';
|
||||
import { RulesClientContext } from '../types';
|
||||
|
||||
export async function updateApiKey(
|
||||
|
@ -27,7 +25,8 @@ export async function updateApiKey(
|
|||
}
|
||||
|
||||
async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: string }) {
|
||||
let apiKeyToInvalidate: string | null = null;
|
||||
let oldApiKeyToInvalidate: string | null = null;
|
||||
let oldApiKeyCreatedByUser: boolean | undefined | null = false;
|
||||
let attributes: RawRule;
|
||||
let version: string | undefined;
|
||||
|
||||
|
@ -36,7 +35,8 @@ async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: st
|
|||
await context.encryptedSavedObjectsClient.getDecryptedAsInternalUser<RawRule>('alert', id, {
|
||||
namespace: context.namespace,
|
||||
});
|
||||
apiKeyToInvalidate = decryptedAlert.attributes.apiKey;
|
||||
oldApiKeyToInvalidate = decryptedAlert.attributes.apiKey;
|
||||
oldApiKeyCreatedByUser = decryptedAlert.attributes.apiKeyCreatedByUser;
|
||||
attributes = decryptedAlert.attributes;
|
||||
version = decryptedAlert.version;
|
||||
} catch (e) {
|
||||
|
@ -73,20 +73,17 @@ async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: st
|
|||
|
||||
const username = await context.getUserName();
|
||||
|
||||
let createdAPIKey = null;
|
||||
try {
|
||||
createdAPIKey = await context.createAPIKey(
|
||||
generateAPIKeyName(attributes.alertTypeId, attributes.name)
|
||||
);
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(
|
||||
`Error updating API key for rule: could not create API key - ${error.message}`
|
||||
);
|
||||
}
|
||||
const apiKeyAttributes = await createNewAPIKeySet(context, {
|
||||
id: attributes.alertTypeId,
|
||||
ruleName: attributes.name,
|
||||
username,
|
||||
shouldUpdateApiKey: true,
|
||||
errorMessage: 'Error updating API key for rule: could not create API key',
|
||||
});
|
||||
|
||||
const updateAttributes = updateMeta(context, {
|
||||
...attributes,
|
||||
...apiKeyAsAlertAttributes(createdAPIKey, username),
|
||||
...apiKeyAttributes,
|
||||
updatedAt: new Date().toISOString(),
|
||||
updatedBy: username,
|
||||
});
|
||||
|
@ -106,16 +103,21 @@ async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: st
|
|||
} catch (e) {
|
||||
// Avoid unused API key
|
||||
await bulkMarkApiKeysForInvalidation(
|
||||
{ apiKeys: updateAttributes.apiKey ? [updateAttributes.apiKey] : [] },
|
||||
{
|
||||
apiKeys:
|
||||
updateAttributes.apiKey && !updateAttributes.apiKeyCreatedByUser
|
||||
? [updateAttributes.apiKey]
|
||||
: [],
|
||||
},
|
||||
context.logger,
|
||||
context.unsecuredSavedObjectsClient
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (apiKeyToInvalidate) {
|
||||
if (oldApiKeyToInvalidate && !oldApiKeyCreatedByUser) {
|
||||
await bulkMarkApiKeysForInvalidation(
|
||||
{ apiKeys: [apiKeyToInvalidate] },
|
||||
{ apiKeys: [oldApiKeyToInvalidate] },
|
||||
context.logger,
|
||||
context.unsecuredSavedObjectsClient
|
||||
);
|
||||
|
|
|
@ -71,6 +71,7 @@ export const fieldsToExcludeFromRevisionUpdates: ReadonlySet<keyof RuleTypeParam
|
|||
'alertTypeId',
|
||||
'apiKey',
|
||||
'apiKeyOwner',
|
||||
'apiKeyCreatedByUser',
|
||||
'consumer',
|
||||
'createdAt',
|
||||
'createdBy',
|
||||
|
|
|
@ -50,6 +50,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -19,7 +19,13 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks';
|
|||
import { getBeforeSetup, setGlobalDate } from './lib';
|
||||
import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { enabledRule1, enabledRule2, returnedRule1, returnedRule2 } from './test_helpers';
|
||||
import {
|
||||
defaultRule,
|
||||
enabledRule1,
|
||||
enabledRule2,
|
||||
returnedRule1,
|
||||
returnedRule2,
|
||||
} from './test_helpers';
|
||||
|
||||
jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({
|
||||
bulkMarkApiKeysForInvalidation: jest.fn(),
|
||||
|
@ -53,6 +59,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
kibanaVersion,
|
||||
auditLogger,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
const getBulkOperationStatusErrorResponse = (statusCode: number) => ({
|
||||
|
@ -66,6 +74,36 @@ const getBulkOperationStatusErrorResponse = (statusCode: number) => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const enabledRule3 = {
|
||||
...defaultRule,
|
||||
id: 'id3',
|
||||
attributes: {
|
||||
...defaultRule.attributes,
|
||||
enabled: true,
|
||||
scheduledTaskId: 'id3',
|
||||
apiKey: Buffer.from('789:ghi').toString('base64'),
|
||||
apiKeyCreatedByUser: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const returnedRule3 = {
|
||||
actions: [],
|
||||
alertTypeId: 'fakeType',
|
||||
apiKey: 'Nzg5OmdoaQ==',
|
||||
apiKeyCreatedByUser: true,
|
||||
consumer: 'fakeConsumer',
|
||||
enabled: true,
|
||||
id: 'id3',
|
||||
name: 'fakeName',
|
||||
notifyWhen: undefined,
|
||||
params: undefined,
|
||||
schedule: {
|
||||
interval: '5m',
|
||||
},
|
||||
scheduledTaskId: 'id3',
|
||||
snoozeSchedule: [],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry);
|
||||
jest.clearAllMocks();
|
||||
|
@ -77,7 +115,7 @@ describe('bulkDelete', () => {
|
|||
let rulesClient: RulesClient;
|
||||
|
||||
const mockCreatePointInTimeFinderAsInternalUser = (
|
||||
response = { saved_objects: [enabledRule1, enabledRule2] }
|
||||
response = { saved_objects: [enabledRule1, enabledRule2, enabledRule3] }
|
||||
) => {
|
||||
encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest
|
||||
.fn()
|
||||
|
@ -126,11 +164,12 @@ describe('bulkDelete', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('should try to delete rules, one successful and one with 500 error', async () => {
|
||||
test('should try to delete rules, two successful and one with 500 error', async () => {
|
||||
unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({
|
||||
statuses: [
|
||||
{ id: 'id1', type: 'alert', success: true },
|
||||
getBulkOperationStatusErrorResponse(500),
|
||||
{ id: 'id3', type: 'alert', success: true },
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -140,9 +179,10 @@ describe('bulkDelete', () => {
|
|||
expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledWith([
|
||||
enabledRule1,
|
||||
enabledRule2,
|
||||
enabledRule3,
|
||||
]);
|
||||
expect(taskManager.bulkRemove).toHaveBeenCalledTimes(1);
|
||||
expect(taskManager.bulkRemove).toHaveBeenCalledWith(['id1']);
|
||||
expect(taskManager.bulkRemove).toHaveBeenCalledWith(['id1', 'id3']);
|
||||
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1);
|
||||
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith(
|
||||
{ apiKeys: ['MTIzOmFiYw=='] },
|
||||
|
@ -150,7 +190,7 @@ describe('bulkDelete', () => {
|
|||
expect.anything()
|
||||
);
|
||||
expect(result).toStrictEqual({
|
||||
rules: [returnedRule1],
|
||||
rules: [returnedRule1, returnedRule3],
|
||||
errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 500 }],
|
||||
total: 2,
|
||||
taskIdsFailedToBeDeleted: [],
|
||||
|
@ -313,6 +353,25 @@ describe('bulkDelete', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('should not mark API keys for invalidation if the user is authenticated using an api key', async () => {
|
||||
unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({
|
||||
statuses: [
|
||||
{ id: 'id3', type: 'alert', success: true },
|
||||
{ id: 'id1', type: 'alert', success: true },
|
||||
{ id: 'id2', type: 'alert', success: true },
|
||||
],
|
||||
});
|
||||
|
||||
await rulesClient.bulkDeleteRules({ filter: 'fake_filter' });
|
||||
|
||||
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1);
|
||||
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith(
|
||||
{ apiKeys: ['MTIzOmFiYw==', 'MzIxOmFiYw=='] },
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
describe('taskManager', () => {
|
||||
test('should return task id if deleting task failed', async () => {
|
||||
unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({
|
||||
|
|
|
@ -73,6 +73,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
auditLogger,
|
||||
eventLogger,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -47,6 +47,9 @@ const auditLogger = auditLoggerMock.create();
|
|||
|
||||
const kibanaVersion = 'v8.2.0';
|
||||
const createAPIKeyMock = jest.fn();
|
||||
const isAuthenticationTypeApiKeyMock = jest.fn();
|
||||
const getAuthenticationApiKeyMock = jest.fn();
|
||||
|
||||
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
||||
taskManager,
|
||||
ruleTypeRegistry,
|
||||
|
@ -64,6 +67,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
kibanaVersion,
|
||||
auditLogger,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: isAuthenticationTypeApiKeyMock,
|
||||
getAuthenticationAPIKey: getAuthenticationApiKeyMock,
|
||||
};
|
||||
const paramsModifier = jest.fn();
|
||||
|
||||
|
@ -104,6 +109,7 @@ describe('bulkEdit()', () => {
|
|||
attributes: {
|
||||
...existingRule.attributes,
|
||||
apiKey: MOCK_API_KEY,
|
||||
apiKeyCreatedByUser: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -580,6 +586,7 @@ describe('bulkEdit()', () => {
|
|||
],
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
meta: { versionApiKeyLastmodified: 'v8.2.0' },
|
||||
name: 'my rule name',
|
||||
enabled: false,
|
||||
|
@ -781,6 +788,7 @@ describe('bulkEdit()', () => {
|
|||
],
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
meta: { versionApiKeyLastmodified: 'v8.2.0' },
|
||||
name: 'my rule name',
|
||||
enabled: false,
|
||||
|
@ -2070,6 +2078,142 @@ describe('bulkEdit()', () => {
|
|||
});
|
||||
expect(rulesClientParams.createAPIKey).toHaveBeenCalledWith('Alerting: myType/my rule name');
|
||||
});
|
||||
|
||||
describe('set by the user when authenticated using api keys', () => {
|
||||
beforeEach(() => {
|
||||
isAuthenticationTypeApiKeyMock.mockReturnValue(true);
|
||||
getAuthenticationApiKeyMock.mockReturnValue({
|
||||
apiKeysEnabled: true,
|
||||
result: { api_key: '111' },
|
||||
});
|
||||
mockCreatePointInTimeFinderAsInternalUser({
|
||||
saved_objects: [
|
||||
{
|
||||
...existingDecryptedRule,
|
||||
attributes: {
|
||||
...existingDecryptedRule.attributes,
|
||||
enabled: true,
|
||||
apiKeyCreatedByUser: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('should not call bulkMarkApiKeysForInvalidation', async () => {
|
||||
await rulesClient.bulkEdit({
|
||||
filter: 'alert.attributes.tags: "APM"',
|
||||
operations: [
|
||||
{
|
||||
field: 'tags',
|
||||
operation: 'add',
|
||||
value: ['test-1'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call bulkMarkApiKeysForInvalidation with empty array if bulkCreate failed', async () => {
|
||||
unsecuredSavedObjectsClient.bulkCreate.mockImplementation(() => {
|
||||
throw new Error('Fail');
|
||||
});
|
||||
|
||||
await expect(
|
||||
rulesClient.bulkEdit({
|
||||
filter: 'alert.attributes.tags: "APM"',
|
||||
operations: [
|
||||
{
|
||||
field: 'tags',
|
||||
operation: 'add',
|
||||
value: ['test-1'],
|
||||
},
|
||||
],
|
||||
})
|
||||
).rejects.toThrow('Fail');
|
||||
|
||||
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1);
|
||||
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith(
|
||||
{ apiKeys: [] },
|
||||
expect.any(Object),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
test('should call bulkMarkApiKeysForInvalidation with empty array if SO update failed', async () => {
|
||||
unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
enabled: true,
|
||||
tags: ['foo'],
|
||||
alertTypeId: 'myType',
|
||||
schedule: { interval: '1m' },
|
||||
consumer: 'myApp',
|
||||
scheduledTaskId: 'task-123',
|
||||
params: { index: ['test-index-*'] },
|
||||
throttle: null,
|
||||
notifyWhen: null,
|
||||
actions: [],
|
||||
},
|
||||
references: [],
|
||||
version: '123',
|
||||
error: {
|
||||
error: 'test failure',
|
||||
statusCode: 500,
|
||||
message: 'test failure',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await rulesClient.bulkEdit({
|
||||
filter: 'alert.attributes.tags: "APM"',
|
||||
operations: [
|
||||
{
|
||||
field: 'tags',
|
||||
operation: 'add',
|
||||
value: ['test-1'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not call get apiKey if rule is disabled', async () => {
|
||||
await rulesClient.bulkEdit({
|
||||
filter: 'alert.attributes.tags: "APM"',
|
||||
operations: [
|
||||
{
|
||||
field: 'tags',
|
||||
operation: 'add',
|
||||
value: ['test-1'],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(rulesClientParams.getAuthenticationAPIKey).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('should return error in rule errors if key is not generated', async () => {
|
||||
await rulesClient.bulkEdit({
|
||||
filter: 'alert.attributes.tags: "APM"',
|
||||
operations: [
|
||||
{
|
||||
field: 'tags',
|
||||
operation: 'add',
|
||||
value: ['test-1'],
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(rulesClientParams.getAuthenticationAPIKey).toHaveBeenCalledWith(
|
||||
'Alerting: myType/my rule name-user-created'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('params validation', () => {
|
||||
|
|
|
@ -64,6 +64,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
kibanaVersion,
|
||||
auditLogger,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -60,6 +60,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
kibanaVersion,
|
||||
auditLogger,
|
||||
eventLogger,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
describe('clearExpiredSnoozes()', () => {
|
||||
|
|
|
@ -68,6 +68,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
kibanaVersion,
|
||||
auditLogger,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -415,6 +417,7 @@ describe('create()', () => {
|
|||
],
|
||||
"alertTypeId": "123",
|
||||
"apiKey": null,
|
||||
"apiKeyCreatedByUser": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "bar",
|
||||
"createdAt": "2019-02-12T21:01:22.479Z",
|
||||
|
@ -636,6 +639,7 @@ describe('create()', () => {
|
|||
],
|
||||
"alertTypeId": "123",
|
||||
"apiKey": null,
|
||||
"apiKeyCreatedByUser": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "bar",
|
||||
"createdAt": "2019-02-12T21:01:22.479Z",
|
||||
|
@ -1087,6 +1091,7 @@ describe('create()', () => {
|
|||
alertTypeId: '123',
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
consumer: 'bar',
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
createdBy: 'elastic',
|
||||
|
@ -1297,6 +1302,7 @@ describe('create()', () => {
|
|||
alertTypeId: '123',
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
consumer: 'bar',
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
createdBy: 'elastic',
|
||||
|
@ -1477,6 +1483,7 @@ describe('create()', () => {
|
|||
alertTypeId: '123',
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
legacyId: null,
|
||||
consumer: 'bar',
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
|
@ -1650,6 +1657,7 @@ describe('create()', () => {
|
|||
params: { bar: true },
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
createdBy: 'elastic',
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
updatedBy: 'elastic',
|
||||
|
@ -1787,6 +1795,7 @@ describe('create()', () => {
|
|||
params: { bar: true },
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
createdBy: 'elastic',
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
updatedBy: 'elastic',
|
||||
|
@ -1924,6 +1933,7 @@ describe('create()', () => {
|
|||
params: { bar: true },
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
createdBy: 'elastic',
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
updatedBy: 'elastic',
|
||||
|
@ -2083,6 +2093,7 @@ describe('create()', () => {
|
|||
],
|
||||
apiKeyOwner: null,
|
||||
apiKey: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
legacyId: null,
|
||||
createdBy: 'elastic',
|
||||
updatedBy: 'elastic',
|
||||
|
@ -2562,6 +2573,7 @@ describe('create()', () => {
|
|||
params: { bar: true },
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
createdBy: 'elastic',
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
updatedBy: 'elastic',
|
||||
|
@ -3309,4 +3321,137 @@ describe('create()', () => {
|
|||
expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled();
|
||||
expect(taskManager.schedule).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('calls the authentication API key function if the user is authenticated using an api key', async () => {
|
||||
const data = getMockData();
|
||||
rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({
|
||||
apiKeysEnabled: true,
|
||||
result: { id: '123', name: '123', api_key: 'abc' },
|
||||
});
|
||||
rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true);
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
alertTypeId: '123',
|
||||
schedule: { interval: '1m' },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
actions: [],
|
||||
scheduledTaskId: 'task-123',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
});
|
||||
await rulesClient.create({ data });
|
||||
|
||||
expect(rulesClientParams.isAuthenticationTypeAPIKey).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClientParams.getAuthenticationAPIKey).toHaveBeenCalledTimes(1);
|
||||
expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith(
|
||||
'alert',
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
actionRef: 'action_0',
|
||||
group: 'default',
|
||||
actionTypeId: 'test',
|
||||
params: { foo: true },
|
||||
uuid: '151',
|
||||
},
|
||||
],
|
||||
alertTypeId: '123',
|
||||
consumer: 'bar',
|
||||
name: 'abc',
|
||||
legacyId: null,
|
||||
params: { bar: true },
|
||||
apiKey: Buffer.from('123:abc').toString('base64'),
|
||||
apiKeyOwner: 'elastic',
|
||||
apiKeyCreatedByUser: true,
|
||||
createdBy: 'elastic',
|
||||
createdAt: '2019-02-12T21:01:22.479Z',
|
||||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
enabled: true,
|
||||
meta: {
|
||||
versionApiKeyLastmodified: kibanaVersion,
|
||||
},
|
||||
schedule: { interval: '1m' },
|
||||
throttle: null,
|
||||
notifyWhen: null,
|
||||
muteAll: false,
|
||||
snoozeSchedule: [],
|
||||
mutedInstanceIds: [],
|
||||
tags: ['foo'],
|
||||
executionStatus: {
|
||||
lastExecutionDate: '2019-02-12T21:01:22.479Z',
|
||||
status: 'pending',
|
||||
error: null,
|
||||
warning: null,
|
||||
},
|
||||
monitoring: getDefaultMonitoring('2019-02-12T21:01:22.479Z'),
|
||||
revision: 0,
|
||||
running: false,
|
||||
},
|
||||
{
|
||||
id: 'mock-saved-object-id',
|
||||
references: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('throws error and does not add API key to invalidatePendingApiKey SO when create saved object fails if the user is authenticated using an api key', async () => {
|
||||
const data = getMockData();
|
||||
rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({
|
||||
apiKeysEnabled: true,
|
||||
result: { id: '123', name: '123', api_key: 'abc' },
|
||||
});
|
||||
rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true);
|
||||
unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Test failure'));
|
||||
await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Test failure"`
|
||||
);
|
||||
expect(taskManager.schedule).not.toHaveBeenCalled();
|
||||
expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1);
|
||||
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1);
|
||||
expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith(
|
||||
{ apiKeys: [] },
|
||||
expect.any(Object),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,6 +48,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -161,6 +163,20 @@ describe('delete()', () => {
|
|||
expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test(`doesn't invalidate API key if set by the user when authenticated using api keys`, async () => {
|
||||
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({
|
||||
...existingAlert,
|
||||
attributes: {
|
||||
...existingAlert.attributes,
|
||||
apiKeyCreatedByUser: true,
|
||||
},
|
||||
});
|
||||
|
||||
await rulesClient.delete({ id: '1' });
|
||||
expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled();
|
||||
expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('swallows error when invalidate API key throws', async () => {
|
||||
unsecuredSavedObjectsClient.create.mockRejectedValueOnce(new Error('Fail'));
|
||||
await rulesClient.delete({ id: '1' });
|
||||
|
|
|
@ -55,6 +55,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
kibanaVersion,
|
||||
auditLogger,
|
||||
eventLogger,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -48,6 +48,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
setGlobalDate();
|
||||
|
@ -375,7 +377,7 @@ describe('enable()', () => {
|
|||
});
|
||||
await expect(
|
||||
async () => await rulesClient.enable({ id: '1' })
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Error creating API key for rule: no"`);
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Error creating API key for rule - no"`);
|
||||
expect(taskManager.bulkEnable).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -43,6 +43,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -50,6 +50,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -48,6 +48,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -51,6 +51,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -46,6 +46,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -43,6 +43,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -44,6 +44,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
setGlobalDate();
|
||||
|
|
|
@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -42,6 +42,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getActionsClient: jest.fn(),
|
||||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -69,6 +69,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
kibanaVersion,
|
||||
auditLogger,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -381,6 +383,7 @@ describe('update()', () => {
|
|||
],
|
||||
"alertTypeId": "myType",
|
||||
"apiKey": null,
|
||||
"apiKeyCreatedByUser": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "myApp",
|
||||
"enabled": true,
|
||||
|
@ -622,6 +625,7 @@ describe('update()', () => {
|
|||
alertTypeId: 'myType',
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
consumer: 'myApp',
|
||||
enabled: true,
|
||||
meta: { versionApiKeyLastmodified: 'v7.10.0' },
|
||||
|
@ -809,6 +813,7 @@ describe('update()', () => {
|
|||
alertTypeId: 'myType',
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
consumer: 'myApp',
|
||||
enabled: true,
|
||||
meta: { versionApiKeyLastmodified: 'v7.10.0' },
|
||||
|
@ -987,6 +992,7 @@ describe('update()', () => {
|
|||
],
|
||||
"alertTypeId": "myType",
|
||||
"apiKey": "MTIzOmFiYw==",
|
||||
"apiKeyCreatedByUser": undefined,
|
||||
"apiKeyOwner": "elastic",
|
||||
"consumer": "myApp",
|
||||
"enabled": true,
|
||||
|
@ -1139,6 +1145,7 @@ describe('update()', () => {
|
|||
],
|
||||
"alertTypeId": "myType",
|
||||
"apiKey": null,
|
||||
"apiKeyCreatedByUser": null,
|
||||
"apiKeyOwner": null,
|
||||
"consumer": "myApp",
|
||||
"enabled": false,
|
||||
|
@ -2155,6 +2162,7 @@ describe('update()', () => {
|
|||
alertTypeId: 'myType',
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
consumer: 'myApp',
|
||||
enabled: true,
|
||||
meta: { versionApiKeyLastmodified: 'v7.10.0' },
|
||||
|
@ -2702,6 +2710,7 @@ describe('update()', () => {
|
|||
alertTypeId: 'myType',
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
consumer: 'myApp',
|
||||
enabled: true,
|
||||
mapped_params: { risk_score: 40, severity: '20-low' },
|
||||
|
@ -2724,4 +2733,210 @@ describe('update()', () => {
|
|||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the authentication API key function if the user is authenticated using an api key', async () => {
|
||||
rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true);
|
||||
rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({
|
||||
apiKeysEnabled: true,
|
||||
result: { id: '123', name: '123', api_key: 'abc' },
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
enabled: true,
|
||||
schedule: { interval: '1m' },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
actionRef: 'action_0',
|
||||
actionTypeId: 'test',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
apiKey: Buffer.from('123:abc').toString('base64'),
|
||||
apiKeyCreatedByUser: true,
|
||||
revision: 1,
|
||||
scheduledTaskId: 'task-123',
|
||||
},
|
||||
updated_at: new Date().toISOString(),
|
||||
references: [
|
||||
{
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
});
|
||||
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({
|
||||
...existingDecryptedAlert,
|
||||
attributes: {
|
||||
...existingDecryptedAlert.attributes,
|
||||
apiKeyCreatedByUser: true,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await rulesClient.update({
|
||||
id: '1',
|
||||
data: {
|
||||
schedule: { interval: '1m' },
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
throttle: '5m',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"apiKey": "MTIzOmFiYw==",
|
||||
"apiKeyCreatedByUser": true,
|
||||
"createdAt": 2019-02-12T21:01:22.479Z,
|
||||
"enabled": true,
|
||||
"id": "1",
|
||||
"notifyWhen": "onThrottleInterval",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"revision": 1,
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"scheduledTaskId": "task-123",
|
||||
"updatedAt": 2019-02-12T21:01:22.479Z,
|
||||
}
|
||||
`);
|
||||
expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledTimes(1);
|
||||
expect(bulkMarkApiKeysForInvalidationMock).not.toHaveBeenCalled();
|
||||
|
||||
expect(unsecuredSavedObjectsClient.create.mock.calls[0]).toHaveLength(3);
|
||||
expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toEqual('alert');
|
||||
expect(unsecuredSavedObjectsClient.create.mock.calls[0][1]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"actionRef": "action_0",
|
||||
"actionTypeId": "test",
|
||||
"group": "default",
|
||||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
"uuid": "152",
|
||||
},
|
||||
],
|
||||
"alertTypeId": "myType",
|
||||
"apiKey": "MTIzOmFiYw==",
|
||||
"apiKeyCreatedByUser": true,
|
||||
"apiKeyOwner": "elastic",
|
||||
"consumer": "myApp",
|
||||
"enabled": true,
|
||||
"meta": Object {
|
||||
"versionApiKeyLastmodified": "v7.10.0",
|
||||
},
|
||||
"name": "abc",
|
||||
"notifyWhen": "onThrottleInterval",
|
||||
"params": Object {
|
||||
"bar": true,
|
||||
},
|
||||
"revision": 1,
|
||||
"schedule": Object {
|
||||
"interval": "1m",
|
||||
},
|
||||
"scheduledTaskId": "task-123",
|
||||
"tags": Array [
|
||||
"foo",
|
||||
],
|
||||
"throttle": "5m",
|
||||
"updatedAt": "2019-02-12T21:01:22.479Z",
|
||||
"updatedBy": "elastic",
|
||||
}
|
||||
`);
|
||||
expect(unsecuredSavedObjectsClient.create.mock.calls[0][2]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"id": "1",
|
||||
"overwrite": true,
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"name": "action_0",
|
||||
"type": "action",
|
||||
},
|
||||
],
|
||||
"version": "123",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('throws when unsecuredSavedObjectsClient update fails and does not invalidate newly created API key if the user is authenticated using an api key', async () => {
|
||||
rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true);
|
||||
rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({
|
||||
apiKeysEnabled: true,
|
||||
result: { id: '123', name: '123', api_key: 'abc' },
|
||||
});
|
||||
unsecuredSavedObjectsClient.create.mockRejectedValue(new Error('Fail'));
|
||||
await expect(
|
||||
rulesClient.update({
|
||||
id: '1',
|
||||
data: {
|
||||
schedule: { interval: '1m' },
|
||||
name: 'abc',
|
||||
tags: ['foo'],
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
frequency: {
|
||||
summary: false,
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
throttle: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`);
|
||||
expect(bulkMarkApiKeysForInvalidationMock).toHaveBeenCalledTimes(1);
|
||||
expect(bulkMarkApiKeysForInvalidationMock).toHaveBeenCalledWith(
|
||||
{
|
||||
apiKeys: [],
|
||||
},
|
||||
expect.any(Object),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -49,6 +49,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
auditLogger,
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -99,6 +101,7 @@ describe('updateApiKey()', () => {
|
|||
});
|
||||
|
||||
test('updates the API key for the alert', async () => {
|
||||
rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(false);
|
||||
rulesClientParams.createAPIKey.mockResolvedValueOnce({
|
||||
apiKeysEnabled: true,
|
||||
result: { id: '234', name: '123', api_key: 'abc' },
|
||||
|
@ -118,6 +121,7 @@ describe('updateApiKey()', () => {
|
|||
enabled: true,
|
||||
apiKey: Buffer.from('234:abc').toString('base64'),
|
||||
apiKeyOwner: 'elastic',
|
||||
apiKeyCreatedByUser: false,
|
||||
revision: 0,
|
||||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
|
@ -146,6 +150,110 @@ describe('updateApiKey()', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('updates the API key for the alert and does not invalidate the old api key if created by a user authenticated using an api key', async () => {
|
||||
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({
|
||||
...existingEncryptedAlert,
|
||||
attributes: {
|
||||
...existingEncryptedAlert.attributes,
|
||||
apiKeyCreatedByUser: true,
|
||||
},
|
||||
});
|
||||
rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(false);
|
||||
rulesClientParams.createAPIKey.mockResolvedValueOnce({
|
||||
apiKeysEnabled: true,
|
||||
result: { id: '234', name: '123', api_key: 'abc' },
|
||||
});
|
||||
await rulesClient.updateApiKey({ id: '1' });
|
||||
expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled();
|
||||
expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', {
|
||||
namespace: 'default',
|
||||
});
|
||||
expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith(
|
||||
'alert',
|
||||
'1',
|
||||
{
|
||||
schedule: { interval: '10s' },
|
||||
alertTypeId: 'myType',
|
||||
consumer: 'myApp',
|
||||
enabled: true,
|
||||
apiKey: Buffer.from('234:abc').toString('base64'),
|
||||
apiKeyOwner: 'elastic',
|
||||
apiKeyCreatedByUser: false,
|
||||
revision: 0,
|
||||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
actionTypeId: '1',
|
||||
actionRef: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
versionApiKeyLastmodified: kibanaVersion,
|
||||
},
|
||||
},
|
||||
{ version: '123' }
|
||||
);
|
||||
expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('calls the authentication API key function if the user is authenticated using an api key', async () => {
|
||||
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue({
|
||||
...existingEncryptedAlert,
|
||||
attributes: {
|
||||
...existingEncryptedAlert.attributes,
|
||||
apiKeyCreatedByUser: true,
|
||||
},
|
||||
});
|
||||
rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true);
|
||||
rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({
|
||||
apiKeysEnabled: true,
|
||||
result: { id: '234', name: '123', api_key: 'abc' },
|
||||
});
|
||||
await rulesClient.updateApiKey({ id: '1' });
|
||||
expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled();
|
||||
expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', {
|
||||
namespace: 'default',
|
||||
});
|
||||
expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith(
|
||||
'alert',
|
||||
'1',
|
||||
{
|
||||
schedule: { interval: '10s' },
|
||||
alertTypeId: 'myType',
|
||||
consumer: 'myApp',
|
||||
enabled: true,
|
||||
apiKey: Buffer.from('234:abc').toString('base64'),
|
||||
apiKeyOwner: 'elastic',
|
||||
apiKeyCreatedByUser: true,
|
||||
revision: 0,
|
||||
updatedBy: 'elastic',
|
||||
updatedAt: '2019-02-12T21:01:22.479Z',
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
actionTypeId: '1',
|
||||
actionRef: '1',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
versionApiKeyLastmodified: kibanaVersion,
|
||||
},
|
||||
},
|
||||
{ version: '123' }
|
||||
);
|
||||
expect(bulkMarkApiKeysForInvalidation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('throws an error if API key creation throws', async () => {
|
||||
rulesClientParams.createAPIKey.mockImplementation(() => {
|
||||
throw new Error('no');
|
||||
|
|
|
@ -69,6 +69,8 @@ export interface RulesClientContext {
|
|||
readonly auditLogger?: AuditLogger;
|
||||
readonly eventLogger?: IEventLogger;
|
||||
readonly fieldsToExcludeFromPublicApi: Array<keyof SanitizedRule>;
|
||||
readonly isAuthenticationTypeAPIKey: () => boolean;
|
||||
readonly getAuthenticationAPIKey: (name: string) => CreateAPIKeyResult;
|
||||
}
|
||||
|
||||
export type NormalizedAlertAction = Omit<RuleAction, 'actionTypeId'>;
|
||||
|
|
|
@ -53,6 +53,8 @@ const rulesClientParams: jest.Mocked<ConstructorOptions> = {
|
|||
getEventLogClient: jest.fn(),
|
||||
kibanaVersion,
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: jest.fn(),
|
||||
getAuthenticationAPIKey: jest.fn(),
|
||||
};
|
||||
|
||||
// this suite consists of two suites running tests against mutable RulesClient APIs:
|
||||
|
|
|
@ -122,6 +122,8 @@ test('creates a rules client with proper constructor arguments when security is
|
|||
encryptedSavedObjectsClient: rulesClientFactoryParams.encryptedSavedObjectsClient,
|
||||
kibanaVersion: '7.10.0',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: expect.any(Function),
|
||||
getAuthenticationAPIKey: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -160,6 +162,8 @@ test('creates a rules client with proper constructor arguments', async () => {
|
|||
getEventLogClient: expect.any(Function),
|
||||
kibanaVersion: '7.10.0',
|
||||
minimumScheduleInterval: { value: '1m', enforce: false },
|
||||
isAuthenticationTypeAPIKey: expect.any(Function),
|
||||
getAuthenticationAPIKey: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -12,7 +12,11 @@ import {
|
|||
PluginInitializerContext,
|
||||
} from '@kbn/core/server';
|
||||
import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server';
|
||||
import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server';
|
||||
import {
|
||||
HTTPAuthorizationHeader,
|
||||
SecurityPluginSetup,
|
||||
SecurityPluginStart,
|
||||
} from '@kbn/security-plugin/server';
|
||||
import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
|
||||
import { IEventLogClientService, IEventLogger } from '@kbn/event-log-plugin/server';
|
||||
|
@ -133,6 +137,30 @@ export class RulesClientFactory {
|
|||
return eventLog.getClient(request);
|
||||
},
|
||||
eventLogger: this.eventLogger,
|
||||
isAuthenticationTypeAPIKey() {
|
||||
if (!securityPluginStart) {
|
||||
return false;
|
||||
}
|
||||
const user = securityPluginStart.authc.getCurrentUser(request);
|
||||
return user && user.authentication_type ? user.authentication_type === 'api_key' : false;
|
||||
},
|
||||
getAuthenticationAPIKey(name: string) {
|
||||
const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request);
|
||||
if (authorizationHeader && authorizationHeader.credentials) {
|
||||
const apiKey = Buffer.from(authorizationHeader.credentials, 'base64')
|
||||
.toString()
|
||||
.split(':');
|
||||
return {
|
||||
apiKeysEnabled: true,
|
||||
result: {
|
||||
name,
|
||||
id: apiKey[0],
|
||||
api_key: apiKey[1],
|
||||
},
|
||||
};
|
||||
}
|
||||
return { apiKeysEnabled: false };
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { SavedObjectsTypeMappingDefinition } from '@kbn/core/server';
|
||||
|
||||
export const alertMappings: SavedObjectsTypeMappingDefinition = {
|
||||
dynamic: false,
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
|
|
|
@ -34,6 +34,7 @@ describe('transform rule for export', () => {
|
|||
updatedAt: date,
|
||||
apiKey: '4tndskbuhewotw4klrhgjewrt9u',
|
||||
apiKeyOwner: 'me',
|
||||
apiKeyCreatedByUser: true,
|
||||
throttle: null,
|
||||
legacyId: '1',
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
|
@ -66,6 +67,7 @@ describe('transform rule for export', () => {
|
|||
updatedAt: date,
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
throttle: null,
|
||||
legacyId: '2',
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
|
@ -91,6 +93,7 @@ describe('transform rule for export', () => {
|
|||
enabled: false,
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
scheduledTaskId: null,
|
||||
legacyId: null,
|
||||
executionStatus: {
|
||||
|
|
|
@ -26,6 +26,7 @@ function transformRuleForExport(
|
|||
enabled: false,
|
||||
apiKey: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
scheduledTaskId: null,
|
||||
executionStatus: getRuleExecutionStatusPending(exportDate),
|
||||
},
|
||||
|
|
|
@ -357,6 +357,7 @@ export interface RawRule extends SavedObjectAttributes {
|
|||
updatedAt: string;
|
||||
apiKey: string | null;
|
||||
apiKeyOwner: string | null;
|
||||
apiKeyCreatedByUser?: boolean | null;
|
||||
throttle?: string | null;
|
||||
notifyWhen?: RuleNotifyWhenType | null;
|
||||
muteAll: boolean;
|
||||
|
|
|
@ -20,13 +20,6 @@ jest.mock('../lib/alerts/fetch_licenses', () => ({
|
|||
jest.mock('../lib/alerts/fetch_clusters', () => ({
|
||||
fetchClusters: jest.fn(),
|
||||
}));
|
||||
jest.mock('moment', () => {
|
||||
const actual = jest.requireActual('moment');
|
||||
return {
|
||||
...actual,
|
||||
duration: () => ({ humanize: () => 'HUMANIZED_DURATION' }),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../static_globals', () => ({
|
||||
Globals: {
|
||||
|
@ -120,7 +113,12 @@ describe('LicenseExpirationRule', () => {
|
|||
getState.mockReset();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should fire actions', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2023-03-30T00:00:00.000Z'));
|
||||
const alert = new LicenseExpirationRule();
|
||||
const type = alert.getRuleType();
|
||||
await type.executor({
|
||||
|
@ -169,7 +167,7 @@ describe('LicenseExpirationRule', () => {
|
|||
],
|
||||
},
|
||||
severity: 'danger',
|
||||
triggeredMS: 1,
|
||||
triggeredMS: 1680134400000,
|
||||
lastCheckedMS: 0,
|
||||
},
|
||||
},
|
||||
|
@ -179,11 +177,11 @@ describe('LicenseExpirationRule', () => {
|
|||
action: '[Please update your license.](elasticsearch/nodes)',
|
||||
actionPlain: 'Please update your license.',
|
||||
internalFullMessage:
|
||||
'License expiration alert is firing for testCluster. Your license expires in HUMANIZED_DURATION. [Please update your license.](elasticsearch/nodes)',
|
||||
'License expiration alert is firing for testCluster. Your license expires in 53 years. [Please update your license.](elasticsearch/nodes)',
|
||||
internalShortMessage:
|
||||
'License expiration alert is firing for testCluster. Your license expires in HUMANIZED_DURATION. Please update your license.',
|
||||
'License expiration alert is firing for testCluster. Your license expires in 53 years. Please update your license.',
|
||||
clusterName,
|
||||
expiredDate: 'HUMANIZED_DURATION',
|
||||
expiredDate: '53 years',
|
||||
state: 'firing',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -123,6 +123,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
notify_when: 'onThrottleInterval',
|
||||
updated_by: user.username,
|
||||
api_key_owner: user.username,
|
||||
api_key_created_by_user: false,
|
||||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
execution_status: response.body.execution_status,
|
||||
|
|
|
@ -79,6 +79,7 @@ const getTestUtils = (
|
|||
notify_when: 'onThrottleInterval',
|
||||
updated_by: 'elastic',
|
||||
api_key_owner: 'elastic',
|
||||
api_key_created_by_user: false,
|
||||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
execution_status: response.body.execution_status,
|
||||
|
|
|
@ -119,6 +119,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
enabled: true,
|
||||
updated_by: user.username,
|
||||
api_key_owner: user.username,
|
||||
api_key_created_by_user: false,
|
||||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
actions: [
|
||||
|
@ -219,6 +220,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
enabled: true,
|
||||
updated_by: user.username,
|
||||
api_key_owner: user.username,
|
||||
api_key_created_by_user: false,
|
||||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
scheduled_task_id: createdAlert.scheduled_task_id,
|
||||
|
@ -321,6 +323,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
enabled: true,
|
||||
updated_by: user.username,
|
||||
api_key_owner: user.username,
|
||||
api_key_created_by_user: false,
|
||||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
scheduled_task_id: createdAlert.scheduled_task_id,
|
||||
|
@ -423,6 +426,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
enabled: true,
|
||||
updated_by: user.username,
|
||||
api_key_owner: user.username,
|
||||
api_key_created_by_user: false,
|
||||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
scheduled_task_id: createdAlert.scheduled_task_id,
|
||||
|
@ -523,6 +527,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
enabled: true,
|
||||
updated_by: user.username,
|
||||
api_key_owner: user.username,
|
||||
api_key_created_by_user: false,
|
||||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
scheduled_task_id: createdAlert.scheduled_task_id,
|
||||
|
|
|
@ -13,6 +13,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common
|
|||
const getDefaultRules = (response: any) => ({
|
||||
id: response.body.rules[0].id,
|
||||
apiKey: response.body.rules[0].apiKey,
|
||||
apiKeyCreatedByUser: false,
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
enabled: true,
|
||||
name: 'abc',
|
||||
|
@ -46,6 +47,7 @@ const getThreeRules = (response: any) => {
|
|||
rules.push({
|
||||
id: response.body.rules[i].id,
|
||||
apiKey: response.body.rules[i].apiKey,
|
||||
apiKeyCreatedByUser: false,
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
enabled: true,
|
||||
name: 'abc',
|
||||
|
|
|
@ -19,6 +19,7 @@ const getDefaultRules = (response: any) => ({
|
|||
consumer: 'alertsFixture',
|
||||
throttle: '1m',
|
||||
alertTypeId: 'test.noop',
|
||||
apiKeyCreatedByUser: false,
|
||||
apiKeyOwner: response.body.rules[0].apiKeyOwner,
|
||||
createdBy: 'elastic',
|
||||
updatedBy: response.body.rules[0].updatedBy,
|
||||
|
|
|
@ -83,6 +83,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
consumer: 'alertsFixture',
|
||||
throttle: '1m',
|
||||
alertTypeId: 'test.noop',
|
||||
apiKeyCreatedByUser: false,
|
||||
apiKeyOwner: response.body.rules[0].apiKeyOwner,
|
||||
createdBy: 'elastic',
|
||||
updatedBy: response.body.rules[0].updatedBy,
|
||||
|
|
|
@ -161,6 +161,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
updated_by: user.username,
|
||||
api_key_created_by_user: false,
|
||||
api_key_owner: user.username,
|
||||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
|
|
|
@ -90,6 +90,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
scheduled_task_id: response.body.scheduled_task_id,
|
||||
updated_by: null,
|
||||
api_key_owner: null,
|
||||
api_key_created_by_user: null,
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
mute_all: false,
|
||||
|
@ -194,6 +195,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
scheduled_task_id: response.body.scheduled_task_id,
|
||||
updated_by: null,
|
||||
api_key_owner: null,
|
||||
api_key_created_by_user: null,
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
mute_all: false,
|
||||
|
@ -498,6 +500,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
scheduledTaskId: response.body.scheduledTaskId,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
throttle: '1m',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
muteAll: false,
|
||||
|
|
|
@ -109,6 +109,7 @@ const findTestUtils = (
|
|||
params: {},
|
||||
created_by: null,
|
||||
api_key_owner: null,
|
||||
api_key_created_by_user: null,
|
||||
scheduled_task_id: match.scheduled_task_id,
|
||||
updated_by: null,
|
||||
throttle: null,
|
||||
|
@ -361,6 +362,7 @@ export default function createFindTests({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
createdBy: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
scheduledTaskId: match.scheduledTaskId,
|
||||
updatedBy: null,
|
||||
throttle: '1m',
|
||||
|
|
|
@ -49,6 +49,7 @@ const getTestUtils = (
|
|||
scheduled_task_id: response.body.scheduled_task_id,
|
||||
updated_by: null,
|
||||
api_key_owner: null,
|
||||
api_key_created_by_user: null,
|
||||
throttle: '1m',
|
||||
notify_when: 'onThrottleInterval',
|
||||
mute_all: false,
|
||||
|
@ -149,6 +150,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
|
|||
scheduledTaskId: response.body.scheduledTaskId,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
throttle: '1m',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
muteAll: false,
|
||||
|
|
|
@ -57,6 +57,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
enabled: true,
|
||||
updated_by: null,
|
||||
api_key_owner: null,
|
||||
api_key_created_by_user: null,
|
||||
mute_all: false,
|
||||
muted_alert_ids: [],
|
||||
notify_when: 'onThrottleInterval',
|
||||
|
@ -163,6 +164,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
enabled: true,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
apiKeyCreatedByUser: null,
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
|
|
|
@ -63,6 +63,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
notifyWhen: 'onActionGroupChange',
|
||||
consumer: 'uptime',
|
||||
alertTypeId: 'xpack.synthetics.alerts.monitorStatus',
|
||||
apiKeyCreatedByUser: false,
|
||||
tags: ['SYNTHETICS_DEFAULT_ALERT'],
|
||||
name: 'Synthetics internal alert',
|
||||
enabled: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue