As part of the work towards adding RBAC & Feature Controls support in Alerting (https://github.com/elastic/kibana/issues/43994), we've decided that the AlertsClient will handle authorisation instead of relying on the SavedObjectsClient on its own. To prevent (or at least, minimise the chances of) bypassing this auth model by using the SavedObjects client this PR makes the `Alert` SavedObject type a _hidden_ type and given the AlertsClient permission to interact with it.
This commit is contained in:
parent
ce750ab498
commit
38d6b86d7e
18 changed files with 648 additions and 507 deletions
|
@ -88,8 +88,8 @@ export interface AlertingPluginsSetup {
|
|||
}
|
||||
export interface AlertingPluginsStart {
|
||||
actions: ActionsPluginStartContract;
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
|
||||
taskManager: TaskManagerStartContract;
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
|
||||
}
|
||||
|
||||
export class AlertingPlugin {
|
||||
|
@ -126,6 +126,7 @@ export class AlertingPlugin {
|
|||
this.licenseState = new LicenseState(plugins.licensing.license$);
|
||||
this.spaces = plugins.spaces?.spacesService;
|
||||
this.security = plugins.security;
|
||||
|
||||
this.isESOUsingEphemeralEncryptionKey =
|
||||
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
|
||||
|
||||
|
@ -164,7 +165,7 @@ export class AlertingPlugin {
|
|||
});
|
||||
}
|
||||
|
||||
core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext());
|
||||
core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext(core));
|
||||
|
||||
// Routes
|
||||
const router = core.http.createRouter();
|
||||
|
@ -201,7 +202,9 @@ export class AlertingPlugin {
|
|||
security,
|
||||
} = this;
|
||||
|
||||
const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient();
|
||||
const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient({
|
||||
includedHiddenTypes: ['alert'],
|
||||
});
|
||||
|
||||
alertsClientFactory.initialize({
|
||||
alertTypeRegistry: alertTypeRegistry!,
|
||||
|
@ -231,26 +234,32 @@ export class AlertingPlugin {
|
|||
return {
|
||||
listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!),
|
||||
// Ability to get an alerts client from legacy code
|
||||
getAlertsClientWithRequest(request: KibanaRequest) {
|
||||
getAlertsClientWithRequest: (request: KibanaRequest) => {
|
||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
||||
throw new Error(
|
||||
`Unable to create alerts client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml`
|
||||
);
|
||||
}
|
||||
return alertsClientFactory!.create(request, core.savedObjects.getScopedClient(request));
|
||||
return alertsClientFactory!.create(
|
||||
request,
|
||||
this.getScopedClientWithAlertSavedObjectType(core.savedObjects, request)
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private createRouteHandlerContext = (): IContextProvider<
|
||||
RequestHandler<unknown, unknown, unknown>,
|
||||
'alerting'
|
||||
> => {
|
||||
private createRouteHandlerContext = (
|
||||
core: CoreSetup
|
||||
): IContextProvider<RequestHandler<unknown, unknown, unknown>, 'alerting'> => {
|
||||
const { alertTypeRegistry, alertsClientFactory } = this;
|
||||
return async function alertsRouteHandlerContext(context, request) {
|
||||
return async (context, request) => {
|
||||
const [{ savedObjects }] = await core.getStartServices();
|
||||
return {
|
||||
getAlertsClient: () => {
|
||||
return alertsClientFactory!.create(request, context.core.savedObjects.client);
|
||||
return alertsClientFactory!.create(
|
||||
request,
|
||||
this.getScopedClientWithAlertSavedObjectType(savedObjects, request)
|
||||
);
|
||||
},
|
||||
listTypes: alertTypeRegistry!.list.bind(alertTypeRegistry!),
|
||||
};
|
||||
|
@ -263,7 +272,7 @@ export class AlertingPlugin {
|
|||
): (request: KibanaRequest) => Services {
|
||||
return request => ({
|
||||
callCluster: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser,
|
||||
savedObjectsClient: savedObjects.getScopedClient(request),
|
||||
savedObjectsClient: this.getScopedClientWithAlertSavedObjectType(savedObjects, request),
|
||||
getScopedCallCluster(clusterClient: IClusterClient) {
|
||||
return clusterClient.asScoped(request).callAsCurrentUser;
|
||||
},
|
||||
|
@ -278,6 +287,13 @@ export class AlertingPlugin {
|
|||
return this.spaces && spaceId ? this.spaces.getBasePath(spaceId) : this.serverBasePath!;
|
||||
};
|
||||
|
||||
private getScopedClientWithAlertSavedObjectType(
|
||||
savedObjects: SavedObjectsServiceStart,
|
||||
request: KibanaRequest
|
||||
) {
|
||||
return savedObjects.getScopedClient(request, { includedHiddenTypes: ['alert'] });
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (this.licenseState) {
|
||||
this.licenseState.clean();
|
||||
|
|
|
@ -14,7 +14,7 @@ export function setupSavedObjects(
|
|||
) {
|
||||
savedObjects.registerType({
|
||||
name: 'alert',
|
||||
hidden: false,
|
||||
hidden: true,
|
||||
namespaceType: 'single',
|
||||
mappings: mappings.alert,
|
||||
});
|
||||
|
|
|
@ -68,7 +68,19 @@ router.get(
|
|||
...
|
||||
```
|
||||
|
||||
5. To retrieve Saved Object with decrypted content use the dedicated `getDecryptedAsInternalUser` API method.
|
||||
5. Instantiate an EncryptedSavedObjects client so that you can interact with Saved Objects whose content has been encrypted.
|
||||
|
||||
```typescript
|
||||
const esoClient = encryptedSavedObjects.getClient();
|
||||
```
|
||||
|
||||
If your SavedObject type is a _hidden_ type, then you will have to specify it as an included type:
|
||||
|
||||
```typescript
|
||||
const esoClient = encryptedSavedObjects.getClient({ includedHiddenTypes: ['myHiddenType'] });
|
||||
```
|
||||
|
||||
6. To retrieve Saved Object with decrypted content use the dedicated `getDecryptedAsInternalUser` API method.
|
||||
|
||||
**Note:** As name suggests the method will retrieve the encrypted values and decrypt them on behalf of the internal Kibana
|
||||
user to make it possible to use this method even when user request context is not available (e.g. in background tasks).
|
||||
|
@ -77,7 +89,7 @@ and preferably only as a part of the Kibana server routines that are outside of
|
|||
user has control over.
|
||||
|
||||
```typescript
|
||||
const savedObjectWithDecryptedContent = await encryptedSavedObjects.getDecryptedAsInternalUser(
|
||||
const savedObjectWithDecryptedContent = await esoClient.getDecryptedAsInternalUser(
|
||||
'my-saved-object-type',
|
||||
'saved-object-id'
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin';
|
||||
import { EncryptedSavedObjectsClient } from './saved_objects';
|
||||
import { EncryptedSavedObjectsClient, EncryptedSavedObjectsClientOptions } from './saved_objects';
|
||||
|
||||
function createEncryptedSavedObjectsSetupMock() {
|
||||
return {
|
||||
|
@ -18,11 +18,11 @@ function createEncryptedSavedObjectsSetupMock() {
|
|||
function createEncryptedSavedObjectsStartMock() {
|
||||
return {
|
||||
isEncryptionError: jest.fn(),
|
||||
getClient: jest.fn(() => createEncryptedSavedObjectsClienttMock()),
|
||||
getClient: jest.fn(opts => createEncryptedSavedObjectsClienttMock(opts)),
|
||||
} as jest.Mocked<EncryptedSavedObjectsPluginStart>;
|
||||
}
|
||||
|
||||
function createEncryptedSavedObjectsClienttMock() {
|
||||
function createEncryptedSavedObjectsClienttMock(opts?: EncryptedSavedObjectsClientOptions) {
|
||||
return {
|
||||
getDecryptedAsInternalUser: jest.fn(),
|
||||
} as jest.Mocked<EncryptedSavedObjectsClient>;
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
EncryptionError,
|
||||
} from './crypto';
|
||||
import { EncryptedSavedObjectsAuditLogger } from './audit';
|
||||
import { SavedObjectsSetup, setupSavedObjects } from './saved_objects';
|
||||
import { setupSavedObjects, ClientInstanciator } from './saved_objects';
|
||||
|
||||
export interface PluginsSetup {
|
||||
security?: SecurityPluginSetup;
|
||||
|
@ -28,7 +28,7 @@ export interface EncryptedSavedObjectsPluginSetup {
|
|||
|
||||
export interface EncryptedSavedObjectsPluginStart {
|
||||
isEncryptionError: (error: Error) => boolean;
|
||||
getClient: SavedObjectsSetup;
|
||||
getClient: ClientInstanciator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,7 +46,7 @@ export interface LegacyAPI {
|
|||
*/
|
||||
export class Plugin {
|
||||
private readonly logger: Logger;
|
||||
private savedObjectsSetup!: SavedObjectsSetup;
|
||||
private savedObjectsSetup!: ClientInstanciator;
|
||||
|
||||
private legacyAPI?: LegacyAPI;
|
||||
private readonly getLegacyAPI = () => {
|
||||
|
@ -95,7 +95,7 @@ export class Plugin {
|
|||
this.logger.debug('Starting plugin');
|
||||
return {
|
||||
isEncryptionError: (error: Error) => error instanceof EncryptionError,
|
||||
getClient: (includedHiddenTypes?: string[]) => this.savedObjectsSetup(includedHiddenTypes),
|
||||
getClient: (options = {}) => this.savedObjectsSetup(options),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsSetup, setupSavedObjects } from '.';
|
||||
import { ClientInstanciator, setupSavedObjects } from '.';
|
||||
|
||||
import {
|
||||
coreMock,
|
||||
|
@ -24,7 +24,7 @@ import {
|
|||
import { EncryptedSavedObjectsService } from '../crypto';
|
||||
|
||||
describe('#setupSavedObjects', () => {
|
||||
let setupContract: SavedObjectsSetup;
|
||||
let setupContract: ClientInstanciator;
|
||||
let coreStartMock: ReturnType<typeof coreMock.createStart>;
|
||||
let coreSetupMock: ReturnType<typeof coreMock.createSetup>;
|
||||
let mockSavedObjectsRepository: jest.Mocked<ISavedObjectsRepository>;
|
||||
|
@ -91,7 +91,7 @@ describe('#setupSavedObjects', () => {
|
|||
|
||||
describe('#setupContract', () => {
|
||||
it('includes hiddenTypes when specified', async () => {
|
||||
await setupContract(['hiddenType']);
|
||||
await setupContract({ includedHiddenTypes: ['hiddenType'] });
|
||||
expect(coreStartMock.savedObjects.createInternalRepository).toHaveBeenCalledWith([
|
||||
'hiddenType',
|
||||
]);
|
||||
|
|
|
@ -23,7 +23,13 @@ interface SetupSavedObjectsParams {
|
|||
getStartServices: StartServicesAccessor;
|
||||
}
|
||||
|
||||
export type SavedObjectsSetup = (includedHiddenTypes?: string[]) => EncryptedSavedObjectsClient;
|
||||
export type ClientInstanciator = (
|
||||
options?: EncryptedSavedObjectsClientOptions
|
||||
) => EncryptedSavedObjectsClient;
|
||||
|
||||
export interface EncryptedSavedObjectsClientOptions {
|
||||
includedHiddenTypes?: string[];
|
||||
}
|
||||
|
||||
export interface EncryptedSavedObjectsClient {
|
||||
getDecryptedAsInternalUser: <T = unknown>(
|
||||
|
@ -38,7 +44,7 @@ export function setupSavedObjects({
|
|||
savedObjects,
|
||||
security,
|
||||
getStartServices,
|
||||
}: SetupSavedObjectsParams): SavedObjectsSetup {
|
||||
}: SetupSavedObjectsParams): ClientInstanciator {
|
||||
// Register custom saved object client that will encrypt, decrypt and strip saved object
|
||||
// attributes where appropriate for any saved object repository request. We choose max possible
|
||||
// priority for this wrapper to allow all other wrappers to set proper `namespace` for the Saved
|
||||
|
@ -56,11 +62,11 @@ export function setupSavedObjects({
|
|||
})
|
||||
);
|
||||
|
||||
return (includedHiddenTypes?: string[]) => {
|
||||
return clientOpts => {
|
||||
const internalRepositoryAndTypeRegistryPromise = getStartServices().then(
|
||||
([core]) =>
|
||||
[
|
||||
core.savedObjects.createInternalRepository(includedHiddenTypes),
|
||||
core.savedObjects.createInternalRepository(clientOpts?.includedHiddenTypes),
|
||||
core.savedObjects.getTypeRegistry(),
|
||||
] as [ISavedObjectsRepository, ISavedObjectTypeRegistry]
|
||||
);
|
||||
|
|
|
@ -19,7 +19,6 @@ import { SpacesPluginSetup } from '../../../../../../../plugins/spaces/server';
|
|||
interface FixtureSetupDeps {
|
||||
spaces?: SpacesPluginSetup;
|
||||
}
|
||||
|
||||
interface FixtureStartDeps {
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
|
||||
}
|
||||
|
@ -44,12 +43,14 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
|||
): Promise<IKibanaResponse<any>> {
|
||||
try {
|
||||
let namespace: string | undefined;
|
||||
const [, { encryptedSavedObjects }] = await core.getStartServices();
|
||||
if (spaces && req.body.spaceId) {
|
||||
namespace = spaces.spacesService.spaceIdToNamespace(req.body.spaceId);
|
||||
}
|
||||
const [, { encryptedSavedObjects }] = await core.getStartServices();
|
||||
await encryptedSavedObjects
|
||||
.getClient()
|
||||
.getClient({
|
||||
includedHiddenTypes: ['alert'],
|
||||
})
|
||||
.getDecryptedAsInternalUser(req.body.type, req.body.id, {
|
||||
namespace,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CoreSetup } from 'kibana/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { FixtureStartDeps, FixtureSetupDeps } from './plugin';
|
||||
import { ActionType, ActionTypeExecutorOptions } from '../../../../../../../plugins/actions/server';
|
||||
|
||||
export function defineActionTypes(
|
||||
core: CoreSetup<FixtureStartDeps>,
|
||||
{ actions }: Pick<FixtureSetupDeps, 'actions'>
|
||||
) {
|
||||
const clusterClient = core.elasticsearch.adminClient;
|
||||
|
||||
// Action types
|
||||
const noopActionType: ActionType = {
|
||||
id: 'test.noop',
|
||||
name: 'Test: Noop',
|
||||
minimumLicenseRequired: 'gold',
|
||||
async executor() {
|
||||
return { status: 'ok', actionId: '' };
|
||||
},
|
||||
};
|
||||
const indexRecordActionType: ActionType = {
|
||||
id: 'test.index-record',
|
||||
name: 'Test: Index Record',
|
||||
minimumLicenseRequired: 'gold',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
message: schema.string(),
|
||||
}),
|
||||
config: schema.object({
|
||||
unencrypted: schema.string(),
|
||||
}),
|
||||
secrets: schema.object({
|
||||
encrypted: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ config, secrets, params, services, actionId }: ActionTypeExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
params,
|
||||
config,
|
||||
secrets,
|
||||
reference: params.reference,
|
||||
source: 'action:test.index-record',
|
||||
},
|
||||
});
|
||||
return { status: 'ok', actionId };
|
||||
},
|
||||
};
|
||||
const failingActionType: ActionType = {
|
||||
id: 'test.failing',
|
||||
name: 'Test: Failing',
|
||||
minimumLicenseRequired: 'gold',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ config, secrets, params, services }: ActionTypeExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
params,
|
||||
config,
|
||||
secrets,
|
||||
reference: params.reference,
|
||||
source: 'action:test.failing',
|
||||
},
|
||||
});
|
||||
throw new Error(`expected failure for ${params.index} ${params.reference}`);
|
||||
},
|
||||
};
|
||||
const rateLimitedActionType: ActionType = {
|
||||
id: 'test.rate-limit',
|
||||
name: 'Test: Rate Limit',
|
||||
minimumLicenseRequired: 'gold',
|
||||
maxAttempts: 2,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
retryAt: schema.number(),
|
||||
}),
|
||||
},
|
||||
async executor({ config, params, services }: ActionTypeExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
params,
|
||||
config,
|
||||
reference: params.reference,
|
||||
source: 'action:test.rate-limit',
|
||||
},
|
||||
});
|
||||
return {
|
||||
status: 'error',
|
||||
retry: new Date(params.retryAt),
|
||||
actionId: '',
|
||||
};
|
||||
},
|
||||
};
|
||||
const authorizationActionType: ActionType = {
|
||||
id: 'test.authorization',
|
||||
name: 'Test: Authorization',
|
||||
minimumLicenseRequired: 'gold',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
callClusterAuthorizationIndex: schema.string(),
|
||||
savedObjectsClientType: schema.string(),
|
||||
savedObjectsClientId: schema.string(),
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ params, services, actionId }: ActionTypeExecutorOptions) {
|
||||
// Call cluster
|
||||
let callClusterSuccess = false;
|
||||
let callClusterError;
|
||||
try {
|
||||
await services.callCluster('index', {
|
||||
index: params.callClusterAuthorizationIndex,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
param1: 'test',
|
||||
},
|
||||
});
|
||||
callClusterSuccess = true;
|
||||
} catch (e) {
|
||||
callClusterError = e;
|
||||
}
|
||||
// Call scoped cluster
|
||||
const callScopedCluster = services.getScopedCallCluster(clusterClient);
|
||||
let callScopedClusterSuccess = false;
|
||||
let callScopedClusterError;
|
||||
try {
|
||||
await callScopedCluster('index', {
|
||||
index: params.callClusterAuthorizationIndex,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
param1: 'test',
|
||||
},
|
||||
});
|
||||
callScopedClusterSuccess = true;
|
||||
} catch (e) {
|
||||
callScopedClusterError = e;
|
||||
}
|
||||
// Saved objects client
|
||||
let savedObjectsClientSuccess = false;
|
||||
let savedObjectsClientError;
|
||||
try {
|
||||
await services.savedObjectsClient.get(
|
||||
params.savedObjectsClientType,
|
||||
params.savedObjectsClientId
|
||||
);
|
||||
savedObjectsClientSuccess = true;
|
||||
} catch (e) {
|
||||
savedObjectsClientError = e;
|
||||
}
|
||||
// Save the result
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state: {
|
||||
callClusterSuccess,
|
||||
callClusterError,
|
||||
callScopedClusterSuccess,
|
||||
callScopedClusterError,
|
||||
savedObjectsClientSuccess,
|
||||
savedObjectsClientError,
|
||||
},
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'action:test.authorization',
|
||||
},
|
||||
});
|
||||
return {
|
||||
actionId,
|
||||
status: 'ok',
|
||||
};
|
||||
},
|
||||
};
|
||||
actions.registerType(noopActionType);
|
||||
actions.registerType(indexRecordActionType);
|
||||
actions.registerType(failingActionType);
|
||||
actions.registerType(rateLimitedActionType);
|
||||
actions.registerType(authorizationActionType);
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CoreSetup } from 'kibana/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { times } from 'lodash';
|
||||
import { FixtureStartDeps, FixtureSetupDeps } from './plugin';
|
||||
import { AlertType, AlertExecutorOptions } from '../../../../../../../plugins/alerting/server';
|
||||
|
||||
export function defineAlertTypes(
|
||||
core: CoreSetup<FixtureStartDeps>,
|
||||
{ alerting }: Pick<FixtureSetupDeps, 'alerting'>
|
||||
) {
|
||||
const clusterClient = core.elasticsearch.adminClient;
|
||||
const alwaysFiringAlertType: AlertType = {
|
||||
id: 'test.always-firing',
|
||||
name: 'Test: Always Firing',
|
||||
actionGroups: [
|
||||
{ id: 'default', name: 'Default' },
|
||||
{ id: 'other', name: 'Other' },
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
actionVariables: {
|
||||
state: [{ name: 'instanceStateValue', description: 'the instance state value' }],
|
||||
context: [{ name: 'instanceContextValue', description: 'the instance context value' }],
|
||||
},
|
||||
async executor(alertExecutorOptions: AlertExecutorOptions) {
|
||||
const {
|
||||
services,
|
||||
params,
|
||||
state,
|
||||
alertId,
|
||||
spaceId,
|
||||
namespace,
|
||||
name,
|
||||
tags,
|
||||
createdBy,
|
||||
updatedBy,
|
||||
} = alertExecutorOptions;
|
||||
let group = 'default';
|
||||
const alertInfo = { alertId, spaceId, namespace, name, tags, createdBy, updatedBy };
|
||||
|
||||
if (params.groupsToScheduleActionsInSeries) {
|
||||
const index = state.groupInSeriesIndex || 0;
|
||||
group = params.groupsToScheduleActionsInSeries[index];
|
||||
}
|
||||
|
||||
if (group) {
|
||||
services
|
||||
.alertInstanceFactory('1')
|
||||
.replaceState({ instanceStateValue: true })
|
||||
.scheduleActions(group, {
|
||||
instanceContextValue: true,
|
||||
});
|
||||
}
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state,
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'alert:test.always-firing',
|
||||
alertInfo,
|
||||
},
|
||||
});
|
||||
return {
|
||||
globalStateValue: true,
|
||||
groupInSeriesIndex: (state.groupInSeriesIndex || 0) + 1,
|
||||
};
|
||||
},
|
||||
};
|
||||
// Alert types
|
||||
const cumulativeFiringAlertType: AlertType = {
|
||||
id: 'test.cumulative-firing',
|
||||
name: 'Test: Cumulative Firing',
|
||||
actionGroups: [
|
||||
{ id: 'default', name: 'Default' },
|
||||
{ id: 'other', name: 'Other' },
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
async executor(alertExecutorOptions: AlertExecutorOptions) {
|
||||
const { services, state } = alertExecutorOptions;
|
||||
const group = 'default';
|
||||
|
||||
const runCount = (state.runCount || 0) + 1;
|
||||
|
||||
times(runCount, index => {
|
||||
services
|
||||
.alertInstanceFactory(`instance-${index}`)
|
||||
.replaceState({ instanceStateValue: true })
|
||||
.scheduleActions(group);
|
||||
});
|
||||
|
||||
return {
|
||||
runCount,
|
||||
};
|
||||
},
|
||||
};
|
||||
const neverFiringAlertType: AlertType = {
|
||||
id: 'test.never-firing',
|
||||
name: 'Test: Never firing',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state,
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'alert:test.never-firing',
|
||||
},
|
||||
});
|
||||
return {
|
||||
globalStateValue: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
const failingAlertType: AlertType = {
|
||||
id: 'test.failing',
|
||||
name: 'Test: Failing',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state,
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'alert:test.failing',
|
||||
},
|
||||
});
|
||||
throw new Error('Failed to execute alert type');
|
||||
},
|
||||
};
|
||||
const authorizationAlertType: AlertType = {
|
||||
id: 'test.authorization',
|
||||
name: 'Test: Authorization',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerting',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
callClusterAuthorizationIndex: schema.string(),
|
||||
savedObjectsClientType: schema.string(),
|
||||
savedObjectsClientId: schema.string(),
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {
|
||||
// Call cluster
|
||||
let callClusterSuccess = false;
|
||||
let callClusterError;
|
||||
try {
|
||||
await services.callCluster('index', {
|
||||
index: params.callClusterAuthorizationIndex,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
param1: 'test',
|
||||
},
|
||||
});
|
||||
callClusterSuccess = true;
|
||||
} catch (e) {
|
||||
callClusterError = e;
|
||||
}
|
||||
// Call scoped cluster
|
||||
const callScopedCluster = services.getScopedCallCluster(clusterClient);
|
||||
let callScopedClusterSuccess = false;
|
||||
let callScopedClusterError;
|
||||
try {
|
||||
await callScopedCluster('index', {
|
||||
index: params.callClusterAuthorizationIndex,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
param1: 'test',
|
||||
},
|
||||
});
|
||||
callScopedClusterSuccess = true;
|
||||
} catch (e) {
|
||||
callScopedClusterError = e;
|
||||
}
|
||||
// Saved objects client
|
||||
let savedObjectsClientSuccess = false;
|
||||
let savedObjectsClientError;
|
||||
try {
|
||||
await services.savedObjectsClient.get(
|
||||
params.savedObjectsClientType,
|
||||
params.savedObjectsClientId
|
||||
);
|
||||
savedObjectsClientSuccess = true;
|
||||
} catch (e) {
|
||||
savedObjectsClientError = e;
|
||||
}
|
||||
// Save the result
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state: {
|
||||
callClusterSuccess,
|
||||
callClusterError,
|
||||
callScopedClusterSuccess,
|
||||
callScopedClusterError,
|
||||
savedObjectsClientSuccess,
|
||||
savedObjectsClientError,
|
||||
},
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'alert:test.authorization',
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
const validationAlertType: AlertType = {
|
||||
id: 'test.validation',
|
||||
name: 'Test: Validation',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
param1: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {},
|
||||
};
|
||||
const noopAlertType: AlertType = {
|
||||
id: 'test.noop',
|
||||
name: 'Test: Noop',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {},
|
||||
};
|
||||
const onlyContextVariablesAlertType: AlertType = {
|
||||
id: 'test.onlyContextVariables',
|
||||
name: 'Test: Only Context Variables',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
actionVariables: {
|
||||
context: [{ name: 'aContextVariable', description: 'this is a context variable' }],
|
||||
},
|
||||
async executor(opts: AlertExecutorOptions) {},
|
||||
};
|
||||
const onlyStateVariablesAlertType: AlertType = {
|
||||
id: 'test.onlyStateVariables',
|
||||
name: 'Test: Only State Variables',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
actionVariables: {
|
||||
state: [{ name: 'aStateVariable', description: 'this is a state variable' }],
|
||||
},
|
||||
async executor(opts: AlertExecutorOptions) {},
|
||||
};
|
||||
alerting.registerType(alwaysFiringAlertType);
|
||||
alerting.registerType(cumulativeFiringAlertType);
|
||||
alerting.registerType(neverFiringAlertType);
|
||||
alerting.registerType(failingAlertType);
|
||||
alerting.registerType(validationAlertType);
|
||||
alerting.registerType(authorizationAlertType);
|
||||
alerting.registerType(noopAlertType);
|
||||
alerting.registerType(onlyContextVariablesAlertType);
|
||||
alerting.registerType(onlyStateVariablesAlertType);
|
||||
}
|
|
@ -5,22 +5,21 @@
|
|||
*/
|
||||
|
||||
import { Plugin, CoreSetup } from 'kibana/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { times } from 'lodash';
|
||||
import { PluginSetupContract as ActionsPluginSetup } from '../../../../../../../plugins/actions/server/plugin';
|
||||
import { PluginSetupContract as AlertingPluginSetup } from '../../../../../../../plugins/alerting/server/plugin';
|
||||
import { EncryptedSavedObjectsPluginStart } from '../../../../../../../plugins/encrypted_saved_objects/server';
|
||||
import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../../plugins/features/server';
|
||||
import { ActionType, ActionTypeExecutorOptions } from '../../../../../../../plugins/actions/server';
|
||||
import { AlertType, AlertExecutorOptions } from '../../../../../../../plugins/alerting/server';
|
||||
import { defineAlertTypes } from './alert_types';
|
||||
import { defineActionTypes } from './action_types';
|
||||
import { defineRoutes } from './routes';
|
||||
|
||||
interface FixtureSetupDeps {
|
||||
export interface FixtureSetupDeps {
|
||||
features: FeaturesPluginSetup;
|
||||
actions: ActionsPluginSetup;
|
||||
alerting: AlertingPluginSetup;
|
||||
}
|
||||
|
||||
interface FixtureStartDeps {
|
||||
export interface FixtureStartDeps {
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
|
||||
}
|
||||
|
||||
|
@ -29,7 +28,6 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
|||
core: CoreSetup<FixtureStartDeps>,
|
||||
{ features, actions, alerting }: FixtureSetupDeps
|
||||
) {
|
||||
const clusterClient = core.elasticsearch.adminClient;
|
||||
features.registerFeature({
|
||||
id: 'alerting',
|
||||
name: 'Alerting',
|
||||
|
@ -55,469 +53,10 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
|||
},
|
||||
},
|
||||
});
|
||||
// Action types
|
||||
const noopActionType: ActionType = {
|
||||
id: 'test.noop',
|
||||
name: 'Test: Noop',
|
||||
minimumLicenseRequired: 'gold',
|
||||
async executor() {
|
||||
return { status: 'ok', actionId: '' };
|
||||
},
|
||||
};
|
||||
const indexRecordActionType: ActionType = {
|
||||
id: 'test.index-record',
|
||||
name: 'Test: Index Record',
|
||||
minimumLicenseRequired: 'gold',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
message: schema.string(),
|
||||
}),
|
||||
config: schema.object({
|
||||
unencrypted: schema.string(),
|
||||
}),
|
||||
secrets: schema.object({
|
||||
encrypted: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ config, secrets, params, services, actionId }: ActionTypeExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
params,
|
||||
config,
|
||||
secrets,
|
||||
reference: params.reference,
|
||||
source: 'action:test.index-record',
|
||||
},
|
||||
});
|
||||
return { status: 'ok', actionId };
|
||||
},
|
||||
};
|
||||
const failingActionType: ActionType = {
|
||||
id: 'test.failing',
|
||||
name: 'Test: Failing',
|
||||
minimumLicenseRequired: 'gold',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ config, secrets, params, services }: ActionTypeExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
params,
|
||||
config,
|
||||
secrets,
|
||||
reference: params.reference,
|
||||
source: 'action:test.failing',
|
||||
},
|
||||
});
|
||||
throw new Error(`expected failure for ${params.index} ${params.reference}`);
|
||||
},
|
||||
};
|
||||
const rateLimitedActionType: ActionType = {
|
||||
id: 'test.rate-limit',
|
||||
name: 'Test: Rate Limit',
|
||||
minimumLicenseRequired: 'gold',
|
||||
maxAttempts: 2,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
retryAt: schema.number(),
|
||||
}),
|
||||
},
|
||||
async executor({ config, params, services }: ActionTypeExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
params,
|
||||
config,
|
||||
reference: params.reference,
|
||||
source: 'action:test.rate-limit',
|
||||
},
|
||||
});
|
||||
return {
|
||||
status: 'error',
|
||||
retry: new Date(params.retryAt),
|
||||
actionId: '',
|
||||
};
|
||||
},
|
||||
};
|
||||
const authorizationActionType: ActionType = {
|
||||
id: 'test.authorization',
|
||||
name: 'Test: Authorization',
|
||||
minimumLicenseRequired: 'gold',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
callClusterAuthorizationIndex: schema.string(),
|
||||
savedObjectsClientType: schema.string(),
|
||||
savedObjectsClientId: schema.string(),
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ params, services, actionId }: ActionTypeExecutorOptions) {
|
||||
// Call cluster
|
||||
let callClusterSuccess = false;
|
||||
let callClusterError;
|
||||
try {
|
||||
await services.callCluster('index', {
|
||||
index: params.callClusterAuthorizationIndex,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
param1: 'test',
|
||||
},
|
||||
});
|
||||
callClusterSuccess = true;
|
||||
} catch (e) {
|
||||
callClusterError = e;
|
||||
}
|
||||
// Call scoped cluster
|
||||
const callScopedCluster = services.getScopedCallCluster(clusterClient);
|
||||
let callScopedClusterSuccess = false;
|
||||
let callScopedClusterError;
|
||||
try {
|
||||
await callScopedCluster('index', {
|
||||
index: params.callClusterAuthorizationIndex,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
param1: 'test',
|
||||
},
|
||||
});
|
||||
callScopedClusterSuccess = true;
|
||||
} catch (e) {
|
||||
callScopedClusterError = e;
|
||||
}
|
||||
// Saved objects client
|
||||
let savedObjectsClientSuccess = false;
|
||||
let savedObjectsClientError;
|
||||
try {
|
||||
await services.savedObjectsClient.get(
|
||||
params.savedObjectsClientType,
|
||||
params.savedObjectsClientId
|
||||
);
|
||||
savedObjectsClientSuccess = true;
|
||||
} catch (e) {
|
||||
savedObjectsClientError = e;
|
||||
}
|
||||
// Save the result
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state: {
|
||||
callClusterSuccess,
|
||||
callClusterError,
|
||||
callScopedClusterSuccess,
|
||||
callScopedClusterError,
|
||||
savedObjectsClientSuccess,
|
||||
savedObjectsClientError,
|
||||
},
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'action:test.authorization',
|
||||
},
|
||||
});
|
||||
return {
|
||||
actionId,
|
||||
status: 'ok',
|
||||
};
|
||||
},
|
||||
};
|
||||
actions.registerType(noopActionType);
|
||||
actions.registerType(indexRecordActionType);
|
||||
actions.registerType(failingActionType);
|
||||
actions.registerType(rateLimitedActionType);
|
||||
actions.registerType(authorizationActionType);
|
||||
|
||||
const alwaysFiringAlertType: AlertType = {
|
||||
id: 'test.always-firing',
|
||||
name: 'Test: Always Firing',
|
||||
actionGroups: [
|
||||
{ id: 'default', name: 'Default' },
|
||||
{ id: 'other', name: 'Other' },
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
actionVariables: {
|
||||
state: [{ name: 'instanceStateValue', description: 'the instance state value' }],
|
||||
context: [{ name: 'instanceContextValue', description: 'the instance context value' }],
|
||||
},
|
||||
async executor(alertExecutorOptions: AlertExecutorOptions) {
|
||||
const {
|
||||
services,
|
||||
params,
|
||||
state,
|
||||
alertId,
|
||||
spaceId,
|
||||
namespace,
|
||||
name,
|
||||
tags,
|
||||
createdBy,
|
||||
updatedBy,
|
||||
} = alertExecutorOptions;
|
||||
let group = 'default';
|
||||
const alertInfo = { alertId, spaceId, namespace, name, tags, createdBy, updatedBy };
|
||||
|
||||
if (params.groupsToScheduleActionsInSeries) {
|
||||
const index = state.groupInSeriesIndex || 0;
|
||||
group = params.groupsToScheduleActionsInSeries[index];
|
||||
}
|
||||
|
||||
if (group) {
|
||||
services
|
||||
.alertInstanceFactory('1')
|
||||
.replaceState({ instanceStateValue: true })
|
||||
.scheduleActions(group, {
|
||||
instanceContextValue: true,
|
||||
});
|
||||
}
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state,
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'alert:test.always-firing',
|
||||
alertInfo,
|
||||
},
|
||||
});
|
||||
return {
|
||||
globalStateValue: true,
|
||||
groupInSeriesIndex: (state.groupInSeriesIndex || 0) + 1,
|
||||
};
|
||||
},
|
||||
};
|
||||
// Alert types
|
||||
const cumulativeFiringAlertType: AlertType = {
|
||||
id: 'test.cumulative-firing',
|
||||
name: 'Test: Cumulative Firing',
|
||||
actionGroups: [
|
||||
{ id: 'default', name: 'Default' },
|
||||
{ id: 'other', name: 'Other' },
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
async executor(alertExecutorOptions: AlertExecutorOptions) {
|
||||
const { services, state } = alertExecutorOptions;
|
||||
const group = 'default';
|
||||
|
||||
const runCount = (state.runCount || 0) + 1;
|
||||
|
||||
times(runCount, index => {
|
||||
services
|
||||
.alertInstanceFactory(`instance-${index}`)
|
||||
.replaceState({ instanceStateValue: true })
|
||||
.scheduleActions(group);
|
||||
});
|
||||
|
||||
return {
|
||||
runCount,
|
||||
};
|
||||
},
|
||||
};
|
||||
const neverFiringAlertType: AlertType = {
|
||||
id: 'test.never-firing',
|
||||
name: 'Test: Never firing',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state,
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'alert:test.never-firing',
|
||||
},
|
||||
});
|
||||
return {
|
||||
globalStateValue: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
const failingAlertType: AlertType = {
|
||||
id: 'test.failing',
|
||||
name: 'Test: Failing',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state,
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'alert:test.failing',
|
||||
},
|
||||
});
|
||||
throw new Error('Failed to execute alert type');
|
||||
},
|
||||
};
|
||||
const authorizationAlertType: AlertType = {
|
||||
id: 'test.authorization',
|
||||
name: 'Test: Authorization',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerting',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
callClusterAuthorizationIndex: schema.string(),
|
||||
savedObjectsClientType: schema.string(),
|
||||
savedObjectsClientId: schema.string(),
|
||||
index: schema.string(),
|
||||
reference: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {
|
||||
// Call cluster
|
||||
let callClusterSuccess = false;
|
||||
let callClusterError;
|
||||
try {
|
||||
await services.callCluster('index', {
|
||||
index: params.callClusterAuthorizationIndex,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
param1: 'test',
|
||||
},
|
||||
});
|
||||
callClusterSuccess = true;
|
||||
} catch (e) {
|
||||
callClusterError = e;
|
||||
}
|
||||
// Call scoped cluster
|
||||
const callScopedCluster = services.getScopedCallCluster(clusterClient);
|
||||
let callScopedClusterSuccess = false;
|
||||
let callScopedClusterError;
|
||||
try {
|
||||
await callScopedCluster('index', {
|
||||
index: params.callClusterAuthorizationIndex,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
param1: 'test',
|
||||
},
|
||||
});
|
||||
callScopedClusterSuccess = true;
|
||||
} catch (e) {
|
||||
callScopedClusterError = e;
|
||||
}
|
||||
// Saved objects client
|
||||
let savedObjectsClientSuccess = false;
|
||||
let savedObjectsClientError;
|
||||
try {
|
||||
await services.savedObjectsClient.get(
|
||||
params.savedObjectsClientType,
|
||||
params.savedObjectsClientId
|
||||
);
|
||||
savedObjectsClientSuccess = true;
|
||||
} catch (e) {
|
||||
savedObjectsClientError = e;
|
||||
}
|
||||
// Save the result
|
||||
await services.callCluster('index', {
|
||||
index: params.index,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
state: {
|
||||
callClusterSuccess,
|
||||
callClusterError,
|
||||
callScopedClusterSuccess,
|
||||
callScopedClusterError,
|
||||
savedObjectsClientSuccess,
|
||||
savedObjectsClientError,
|
||||
},
|
||||
params,
|
||||
reference: params.reference,
|
||||
source: 'alert:test.authorization',
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
const validationAlertType: AlertType = {
|
||||
id: 'test.validation',
|
||||
name: 'Test: Validation',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
param1: schema.string(),
|
||||
}),
|
||||
},
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {},
|
||||
};
|
||||
const noopAlertType: AlertType = {
|
||||
id: 'test.noop',
|
||||
name: 'Test: Noop',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {},
|
||||
};
|
||||
const onlyContextVariablesAlertType: AlertType = {
|
||||
id: 'test.onlyContextVariables',
|
||||
name: 'Test: Only Context Variables',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
actionVariables: {
|
||||
context: [{ name: 'aContextVariable', description: 'this is a context variable' }],
|
||||
},
|
||||
async executor(opts: AlertExecutorOptions) {},
|
||||
};
|
||||
const onlyStateVariablesAlertType: AlertType = {
|
||||
id: 'test.onlyStateVariables',
|
||||
name: 'Test: Only State Variables',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
producer: 'alerting',
|
||||
defaultActionGroupId: 'default',
|
||||
actionVariables: {
|
||||
state: [{ name: 'aStateVariable', description: 'this is a state variable' }],
|
||||
},
|
||||
async executor(opts: AlertExecutorOptions) {},
|
||||
};
|
||||
alerting.registerType(alwaysFiringAlertType);
|
||||
alerting.registerType(cumulativeFiringAlertType);
|
||||
alerting.registerType(neverFiringAlertType);
|
||||
alerting.registerType(failingAlertType);
|
||||
alerting.registerType(validationAlertType);
|
||||
alerting.registerType(authorizationAlertType);
|
||||
alerting.registerType(noopAlertType);
|
||||
alerting.registerType(onlyContextVariablesAlertType);
|
||||
alerting.registerType(onlyStateVariablesAlertType);
|
||||
defineActionTypes(core, { actions });
|
||||
defineAlertTypes(core, { alerting });
|
||||
defineRoutes(core);
|
||||
}
|
||||
|
||||
public start() {}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import {
|
||||
CoreSetup,
|
||||
RequestHandlerContext,
|
||||
KibanaRequest,
|
||||
KibanaResponseFactory,
|
||||
IKibanaResponse,
|
||||
} from 'kibana/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
export function defineRoutes(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.put(
|
||||
{
|
||||
path: '/api/alerts_fixture/saved_object/{type}/{id}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
type: schema.string(),
|
||||
id: schema.string(),
|
||||
}),
|
||||
body: schema.object({
|
||||
attributes: schema.recordOf(schema.string(), schema.any()),
|
||||
version: schema.maybe(schema.string()),
|
||||
references: schema.maybe(
|
||||
schema.arrayOf(
|
||||
schema.object({
|
||||
name: schema.string(),
|
||||
type: schema.string(),
|
||||
id: schema.string(),
|
||||
})
|
||||
)
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (
|
||||
context: RequestHandlerContext,
|
||||
req: KibanaRequest<any, any, any, any>,
|
||||
res: KibanaResponseFactory
|
||||
): Promise<IKibanaResponse<any>> => {
|
||||
const { type, id } = req.params;
|
||||
const { attributes, version, references } = req.body;
|
||||
const options = { version, references };
|
||||
|
||||
const [{ savedObjects }] = await core.getStartServices();
|
||||
const savedObjectsWithAlerts = await savedObjects.getScopedClient(req, {
|
||||
includedHiddenTypes: ['alert'],
|
||||
});
|
||||
const result = await savedObjectsWithAlerts.update(type, id, attributes, options);
|
||||
return res.ok({ body: result });
|
||||
}
|
||||
);
|
||||
}
|
|
@ -117,7 +117,9 @@ export default function createDeleteTests({ getService }: FtrProviderContext) {
|
|||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(space.id)}/api/saved_objects/alert/${createdAlert.id}`)
|
||||
.put(
|
||||
`${getUrlPrefix(space.id)}/api/alerts_fixture/saved_object/alert/${createdAlert.id}`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
attributes: {
|
||||
|
|
|
@ -93,7 +93,9 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte
|
|||
objectRemover.add(space.id, createdAlert.id, 'alert');
|
||||
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(space.id)}/api/saved_objects/alert/${createdAlert.id}`)
|
||||
.put(
|
||||
`${getUrlPrefix(space.id)}/api/alerts_fixture/saved_object/alert/${createdAlert.id}`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
attributes: {
|
||||
|
|
|
@ -98,7 +98,9 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex
|
|||
objectRemover.add(space.id, createdAlert.id, 'alert');
|
||||
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(space.id)}/api/saved_objects/alert/${createdAlert.id}`)
|
||||
.put(
|
||||
`${getUrlPrefix(space.id)}/api/alerts_fixture/saved_object/alert/${createdAlert.id}`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
attributes: {
|
||||
|
|
|
@ -117,7 +117,9 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
objectRemover.add(space.id, createdAlert.id, 'alert');
|
||||
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(space.id)}/api/saved_objects/alert/${createdAlert.id}`)
|
||||
.put(
|
||||
`${getUrlPrefix(space.id)}/api/alerts_fixture/saved_object/alert/${createdAlert.id}`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
attributes: {
|
||||
|
|
|
@ -83,7 +83,9 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte
|
|||
objectRemover.add(space.id, createdAlert.id, 'alert');
|
||||
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(space.id)}/api/saved_objects/alert/${createdAlert.id}`)
|
||||
.put(
|
||||
`${getUrlPrefix(space.id)}/api/alerts_fixture/saved_object/alert/${createdAlert.id}`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
attributes: {
|
||||
|
|
|
@ -26,7 +26,9 @@ export function registerHiddenSORoutes(
|
|||
try {
|
||||
return response.ok({
|
||||
body: await encryptedSavedObjects
|
||||
.getClient([request.params.type])
|
||||
.getClient({
|
||||
includedHiddenTypes: [request.params.type],
|
||||
})
|
||||
.getDecryptedAsInternalUser(request.params.type, request.params.id, { namespace }),
|
||||
});
|
||||
} catch (err) {
|
||||
|
|
Loading…
Reference in a new issue